Source code for braincell.vis.layout._config
# Copyright 2026 BrainX Ecosystem Limited. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""User-tunable knobs for the 2D layout engine.
Before the split of ``layout2d.py`` into :mod:`braincell.vis.layout`,
every layout parameter was a bare module-level constant buried in the
middle of the file. :class:`LayoutConfig` promotes the headline knobs
into a single frozen dataclass so callers can tune a visual without
forking the whole engine.
The goal here is not to expose *every* internal number — a handful of
low-level interpolation ratios (e.g. the 0.28/0.72 bands inside
``_stem_segment_angles_rad``) are left as hard-coded defaults because
they are not meaningful tuning targets. What :class:`LayoutConfig`
*does* surface is the set of parameters that determine the overall
shape of a layout:
* Per-family bend fractions and angular spans.
* Collision detection margins and retry limits.
* The scoring-function weights that govern how the stem family trades
off physical overlap against angular fidelity.
The dataclass is frozen so a caller can hold on to a
:class:`LayoutConfig` and reuse it without worrying about mutation.
Passing ``layout_config=None`` through the entire layout stack means
"use :data:`DEFAULT_LAYOUT_CONFIG`", which recovers the pre-Phase-2
behaviour byte-for-byte.
"""
import math
from dataclasses import dataclass
[docs]
@dataclass(frozen=True)
class LayoutConfig:
"""Configurable parameters for the 2D layout engine.
Parameters
----------
collision_margin_um : float
Radius of the "too close" bubble around each placed branch.
Any candidate whose closest approach to a previously-placed
branch is below this threshold incurs a soft penalty of
``margin - distance`` per offending segment pair. Proper
intersections incur a flat 1000.0 penalty regardless.
collision_retry_limit : int
Maximum number of candidate placements the stem-linear solver
tries before accepting the best-so-far. Raising this makes
dense morphologies cleaner at the cost of linear runtime.
stem_collision_window : int
How many of the most recently placed branches the stem-tree
solver checks for collisions against. With the spatial-hash
backend the cost per candidate is roughly linear in the
number of *segments* actually within ``collision_cell_size_um``
of the candidate; a larger window is therefore affordable but
still bounded to avoid rescoring the whole tree at deep forks.
collision_cell_size_um : float
Grid cell size for the 2D spatial-hash collision backend in
:mod:`_collision`. Should be a small multiple of the typical
branch segment length; too small and the hash wastes memory
with sparsely-populated cells, too large and queries degrade
toward the O(n²) brute force.
default_bend_fraction : float
Fraction of a branch's total length used to bend from the
attach tangent to the target angle in the stem-linear and
legacy families.
balloon_bend_fraction : float
Same idea for the balloon family. Smaller values make each
branch curve more sharply near its root.
fan_bend_fraction : float
Same idea for the fan family.
radial_bend_fraction : float
Same idea for the radial_360 family.
fan_root_left_span_rad : float
Angular span used for root children attached at the extreme
left side of the soma.
fan_root_middle_upper_span_rad : float
Angular span used for the upper root sector for center-attached
children.
fan_root_middle_lower_span_rad : float
Angular span used for the lower root sector for center-attached
children.
fan_root_right_span_rad : float
Angular span used for root children attached away from the
center/right side of the soma.
fan_root_left_max_parent_x : float
Maximum ``parent_x`` classified into the left sector.
fan_root_middle_min_parent_x : float
Inclusive lower bound of the center band.
fan_root_middle_max_parent_x : float
Inclusive upper bound of the center band.
stem_root_full_span_rad : float
Full angular span used for root children when ``root_layout``
is not ``'type_split'`` (all children packed into one arc).
stem_root_group_span_rad : float
Angular span allocated *per group* (axon group or dendrite
group) when ``root_layout='type_split'``. The two groups sit
on opposite half-planes centered on ``±π/2``.
balloon_root_span_rad : float
Angular span used by the balloon family for the root fork
when not splitting by type.
balloon_child_span_rad : float
Angular span used by the balloon family for each non-root
fork, centered on the parent's tangent.
balloon_type_split_span_rad : float
Per-group span for the balloon family when
``root_layout='type_split'``.
radial_root_span_rad : float
Angular span used by the radial_360 family for the root fork
(defaults to a full 2π).
radial_child_span_rad : float
Angular span used by the radial_360 family for each non-root
fork.
legacy_root_child_span_rad : float
Angular span for root children in the legacy layout.
stem_collision_weight : float
Weight applied to the raw collision score when computing the
total stem-tree score. With ``collision_weight=100`` a single
intersection (score ``1000.0``) contributes ``100 000`` to the
total, which dominates every other term — intentional, since
intersections are always worse than a bad angle.
stem_tail_delta_weight : float
Weight on the tail-angle deviation from the desired target.
Increase to make the engine more faithful to the target angle
at the cost of possible overlap.
stem_settle_delta_weight : float
Weight on the "settle" angle deviation from the tail. Keeps
the mid-branch from over-rotating between launch and tail.
stem_overturn_weight : float
Weight on turns wider than π/2 between launch and tail.
Discourages hairpin turns.
stem_trunk_tail_delta_weight : float
Extra per-trunk penalty on tail deviation from the attach
tangent. Keeps trunks visually continuous across a fork.
stem_side_opening_weight : float
Extra per-side penalty that rewards launches "opening away"
from the desired tail by at least ``max(|desired|, 55°)``.
"""
# --- Collision detection ---
collision_margin_um: float = 2.0
collision_retry_limit: int = 8
stem_collision_window: int = 24
collision_cell_size_um: float = 20.0
# --- Bend fractions ---
default_bend_fraction: float = 0.4
balloon_bend_fraction: float = 0.22
fan_bend_fraction: float = 0.24
radial_bend_fraction: float = 0.25
# --- Root layout spans (radians) ---
stem_root_full_span_rad: float = math.radians(150.0)
stem_root_group_span_rad: float = math.radians(120.0)
balloon_root_span_rad: float = math.radians(180.0)
balloon_child_span_rad: float = math.radians(120.0)
balloon_type_split_span_rad: float = math.radians(110.0)
fan_root_left_span_rad: float = math.radians(95.0)
fan_root_middle_upper_span_rad: float = math.radians(70.0)
fan_root_middle_lower_span_rad: float = math.radians(70.0)
fan_root_right_span_rad: float = math.radians(95.0)
radial_root_span_rad: float = 2.0 * math.pi
radial_child_span_rad: float = math.radians(150.0)
legacy_root_child_span_rad: float = math.radians(120.0)
# --- Fan root parent_x bins ---
fan_root_left_max_parent_x: float = 0.02
fan_root_middle_min_parent_x: float = 0.35
fan_root_middle_max_parent_x: float = 0.65
# --- Stem tree-layout scoring weights ---
#
# The total score for a stem candidate is:
#
# total = collision_weight * collision_score
# + tail_delta_weight * |tail - desired|
# + settle_delta_weight * |settle - tail|
# + overturn_weight * max(0, |launch-tail| - π/2)
# + (trunk) trunk_tail_delta_weight * |tail - attach|
# + (side) side_opening_weight * max(0, opening_floor - |launch-attach|)
#
# where collision_score is a per-pair sum defined in _collision.py
# (1000.0 per proper intersection, linear in "how close" for the
# margin band).
stem_collision_weight: float = 100.0
stem_tail_delta_weight: float = 3.0
stem_settle_delta_weight: float = 0.8
stem_overturn_weight: float = 6.0
stem_trunk_tail_delta_weight: float = 0.75
stem_side_opening_weight: float = 2.0
DEFAULT_LAYOUT_CONFIG = LayoutConfig()
"""Process-wide default :class:`LayoutConfig`, used when a caller passes
``layout_config=None`` (i.e. the pre-Phase-2 behaviour)."""