Skip to content

fix(skeletonize): correct edge orientation, dataset routing, and soma detection - #24

Merged
lindseyelopes merged 1 commit into
mainfrom
skeletonize-module-fixes
Jun 26, 2026
Merged

fix(skeletonize): correct edge orientation, dataset routing, and soma detection#24
lindseyelopes merged 1 commit into
mainfrom
skeletonize-module-fixes

Conversation

@yigityargili991

@yigityargili991 yigityargili991 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Description

Hardening pass on crantpy.viz.skeletonize (the get_skeletons / skeletonize_neuron path). Fixes a skeleton-topology bug, a wrong-dataset bug, and a soma-detection bug, removes an O(n²) hotspot, and adds offline tests. Public API is unchanged.

Highlights

  • Edge orientation (topology bug). _create_node_info_dict trusted edge direction (parent, child = edge), but meshparty emits [child, parent] and precomputed CAVE edges may be unordered → inverted roots and branch points silently losing children, producing fragmented neurons. It now rebuilds parent pointers via a BFS over undirected adjacency: exactly one parent per node and one root per connected component. Affects both the pcg_skel and precomputed-fetch paths.
  • Wrong-dataset meshes. The skeletor fallback called get_cloudvolume() with no dataset, so meshes always came from the default segmentation regardless of the client's dataset. Added a real dataset parameter (so the existing @inject_dataset decorator actually works) and threaded it end-to-end (get_skeletonsskeletonize_neuronget_cloudvolume(dataset=...)).
  • Soma detection. detect_soma_skeleton skipped a segment whose only large-radius node sat at index 0 (not any(is_big) tested index values, not emptiness). detect_soma_mesh replaced an O(n²) per-vertex tree.query(k=n) loop with a single vectorized query_ball_point(..., return_length=True).
  • Robustness / cleanup. Multi-ID save_to now writes one <root_id>.swc per neuron instead of overwriting a single file; get_skeletons deduplicates root_ids, logs the previously silently-swallowed precomputed-fetch error, and iterates with as_completed (per-id failure messages); color_map uses plt.get_cmap (the old cm.get_cmap is removed in modern matplotlib) and sizes colours to the surviving neurons; _shave_skeleton uses navis.subset_neuron instead of mutating tn._nodes; dead code removed.

Type of change

  • Bug fix (correctness)
  • Tests
  • Breaking change — none. dataset is an additive optional parameter; __all__ is unchanged.

Testing

  • New tests/test_skeletonize.py: 21 offline unit tests (no CAVE credentials) covering the dict→TreeNeuron conversion (orientation invariance, branch-child retention, disconnected components, type classification), the soma-detection regressions, the detect_soma_mesh neighbour-count equivalence, and the get_skeletons dedup/ordering contract. All pass.
  • black clean; ruff errors on this file reduced from 13 → 6 (all remaining are pre-existing in untouched code; this change introduces none).
  • Not run locally: the credentialed test suite (needs CAVE/Seatable auth) and build_docs.sh (Poetry not installed locally). The docs CI builds on PR with execute_notebooks: 'off', and only docs/changelog.md changed (API docs are autodoc).

Checklist

  • Type hints on new/changed functions
  • No public API change → lazy __init__ files unchanged (mkinit is a no-op; __all__ identical)
  • Changelog updated (docs/changelog.md, [Unreleased])
  • Code formatted with Black
  • Conventional commit message

Related issues

None.

… detection

Correctness:
- _create_node_info_dict: derive parent pointers via BFS over undirected
  adjacency instead of trusting edge direction. meshparty emits [child, parent]
  and CAVE edges may be unordered, so `parent, child = edge` inverted roots and
  overwrote children of branch points, producing fragmented neurons. Now
  guarantees one parent per node and one root per connected component.
- detect_soma_skeleton: `not any(is_big)` tested index values; use
  `is_big.size == 0` so a large node at position 0 is no longer skipped.
- skeletonize_neuron / skeletonize_neurons_parallel: add a real `dataset`
  parameter so @inject_dataset works, and fetch the mesh from the matching
  segmentation source (get_cloudvolume(dataset=dataset)) instead of always the
  default dataset; thread dataset through get_skeletons and the recursive branch.
- skeletonize_neuron iterable branch: treat save_to as a directory and write one
  <root_id>.swc per neuron instead of overwriting a single file.
- get_skeletons: dedup root_ids (pd.unique) so duplicates aren't fetched twice
  and the id-based reindex stays unambiguous.

Performance:
- detect_soma_mesh: replace the O(n^2) per-vertex tree.query(k=n) loop with a
  single vectorized query_ball_point(..., return_length=True).

Robustness / observability:
- get_skeletons: log the precomputed-fetch failure at debug instead of a silent
  `except Exception: pass`; iterate futures with as_completed and report the
  failing root id.
- color_map: use plt.get_cmap (cm.get_cmap is removed in modern matplotlib) and
  size colors to the surviving neurons.
- _worker_wrapper: brief backoff before the single network retry; stop swallowing
  KeyboardInterrupt.

Cleanup:
- _shave_skeleton: use navis.subset_neuron instead of mutating tn._nodes.
- Remove dead code (unused coords/partial/cm imports, redundant local re-imports,
  duplicate np.asarray, no-op try/except, dead failed_ids).

Tests:
- Add tests/test_skeletonize.py: offline unit tests for the dict-to-TreeNeuron
  conversion, soma detection regressions, detect_soma_mesh neighbor-count
  equivalence, and the get_skeletons dedup contract. No CAVE credentials required.

Docs:
- Add changelog entries under [Unreleased].

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardening pass on crantpy.viz.skeletonize’s skeleton-fetch/skeletonize path to improve correctness (edge orientation, dataset routing, soma detection), reduce a soma-mesh performance hotspot, and add offline unit tests to lock in behavior without requiring CAVE credentials.

Changes:

  • Rebuild _create_node_info_dict parent pointers from undirected adjacency (BFS) to make skeleton topology orientation-invariant and prevent dropped branch children.
  • Thread dataset through get_skeletons / skeletonize_neuron / skeletonize_neurons_parallel so on-demand mesh fetching uses the correct dataset.
  • Fix soma detection regressions and vectorize detect_soma_mesh neighbor counting; add a comprehensive offline test suite for these helpers.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/test_skeletonize.py Adds offline unit tests covering SWC conversion/topology invariance, soma detection regressions, and get_skeletons dedup/ordering behavior.
src/crantpy/viz/skeletonize.py Fixes topology construction, threads dataset through mesh fallback paths, improves failure reporting/concurrency behavior, and optimizes soma mesh detection.
docs/changelog.md Documents the fixes, new dataset parameter plumbing, and the new offline test coverage.
Comments suppressed due to low confidence (1)

src/crantpy/viz/skeletonize.py:496

  • _worker_wrapper can return a structured error dict, but its return type annotation is Union[navis.TreeNeuron, str]. This is misleading for callers (and for static analysis) and doesn’t match the actual implementation/docstring.
def _worker_wrapper(
    x: Tuple[Callable, List[Any], Dict[str, Any]],
) -> Union[navis.TreeNeuron, str]:
    """worker wrapper (from fafbseg) with error handling and retry logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +487 to +490
warnings.warn(
"assert_id_match is not implemented yet and currently has no effect.",
category=UserWarning,
)
@lindseyelopes
lindseyelopes merged commit 67c75a1 into main Jun 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants