Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 073dea0

Browse files
author
Alexis Arnaudon
authored
Feat: Ensure single axon, remove dummy dendrites and connect axon to soma (#150)
1 parent 29a831f commit 073dea0

File tree

20 files changed

+87
-36
lines changed

20 files changed

+87
-36
lines changed

.pylintrc

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ disable=
44
fixme,
55
invalid-name,
66
len-as-condition,
7-
no-else-return
7+
no-else-return,
8+
too-many-arguments,
9+
too-many-return-statements,
10+
too-many-branches,
11+
too-many-statements
812

913
[FORMAT]
1014
# Regexp for a line that is allowed to be longer than the limit.
@@ -13,19 +17,11 @@ ignore-long-lines=\bhttps?://\S
1317
max-line-length=100
1418

1519
[DESIGN]
16-
# Maximum number of arguments for function / method
17-
max-args=8
1820
# Argument names that match this expression will be ignored. Default to name
1921
# with leading underscore
2022
ignored-argument-names=_.*
2123
# Maximum number of locals for function / method body
2224
max-locals=15
23-
# Maximum number of return / yield for function / method body
24-
max-returns=6
25-
# Maximum number of branch for function / method body
26-
max-branches=12
27-
# Maximum number of statements in function / method body
28-
max-statements=50
2925
# Maximum number of parents for a class (see R0901).
3026
max-parents=9
3127
# Maximum number of attributes for a class (see R0902).

src/morphology_workflows/curation.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,21 +425,66 @@ def fix_root_section(morph, min_length=_ZERO_LENGTH):
425425
morph.delete_section(sec)
426426

427427

428-
def check_neurites(
428+
def _ensure_single_axon(morph):
429+
"""Keep only the largest axon if multiple are present."""
430+
sections = {}
431+
for root_section in morph.root_sections:
432+
if root_section.type == SectionType.axon:
433+
sections[root_section] = len(list(root_section.iter()))
434+
if len(sections) > 1:
435+
for sec in sorted(sections, key=sections.get, reverse=True)[1:]:
436+
morph.delete_section(sec, recursive=True)
437+
438+
439+
def _ensure_axon_at_soma(morph):
440+
"""Ensure axon is connected to soma."""
441+
for root_section in morph.root_sections:
442+
root_section_type = root_section.type
443+
for section in root_section.iter():
444+
if section.type != root_section_type:
445+
morph.append_root_section(section)
446+
morph.delete_section(section, recursive=True)
447+
break
448+
449+
450+
def _remove_dummy_neurites(morph):
451+
for root_section in morph.root_sections:
452+
if (
453+
not root_section.children
454+
and root_section.points.shape[0] == 2
455+
and np.linalg.norm(root_section.points[0] - root_section.points[1]) < 1e-5
456+
):
457+
morph.delete_section(root_section)
458+
459+
460+
def check_neurites( # noqa: PLR0913
429461
row,
430462
data_dir,
431463
axon_n_section_min=5,
432464
mock_soma_type="spherical",
433465
ensure_stub_axon=False,
466+
ensure_single_axon=True,
467+
ensure_axon_at_soma=True,
468+
remove_dummy_neurites=True,
434469
min_length_first_section=_ZERO_LENGTH,
435470
):
436471
"""Check which neurites are present, add soma if missing and mock_soma_type is not None."""
437472
new_morph_path = data_dir / Path(row.morph_path).name
438473
morph = Morphology(row.morph_path)
439474
if mock_soma_type is not None:
440475
_add_soma(morph, mock_soma_type)
476+
441477
if ensure_stub_axon and not _has_axon(row.morph_path, n_section_min=0):
442478
_add_stub_axon(morph)
479+
480+
if ensure_axon_at_soma:
481+
_ensure_axon_at_soma(morph)
482+
if ensure_single_axon:
483+
_ensure_single_axon(morph)
484+
485+
if remove_dummy_neurites:
486+
_remove_dummy_neurites(morph)
487+
443488
fix_root_section(morph, min_length_first_section)
444489
morph.write(new_morph_path)
445490
has_axon = row.get("use_axon", _has_axon(new_morph_path, n_section_min=axon_n_section_min))

src/morphology_workflows/tasks/curation.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Curation tasks."""
2+
23
import logging
34

45
import luigi
@@ -121,6 +122,18 @@ class CheckNeurites(StrIndexMixin, ElementValidationTask):
121122
description=":bool: Add a stub axon if there is no axon on the morphology",
122123
default=False,
123124
)
125+
ensure_single_axon = luigi.BoolParameter(
126+
description=":bool: Keep only largest axon if multiple are present",
127+
default=True,
128+
)
129+
ensure_axon_at_soma = luigi.BoolParameter(
130+
description=":bool: Ensure all axons are connected to the soma, and not dendrites",
131+
default=True,
132+
)
133+
remove_dummy_neurites = luigi.BoolParameter(
134+
description=":bool: Remove neurites with two equal points",
135+
default=True,
136+
)
124137
min_length_first_section = OptionalNumericalParameter(
125138
description=(
126139
":float: Resize the first section to be at least of the given size (do nothing if None "
@@ -138,6 +151,9 @@ def kwargs(self):
138151
"mock_soma_type": self.mock_soma_type,
139152
"axon_n_section_min": self.axon_n_section_min,
140153
"ensure_stub_axon": self.ensure_stub_axon,
154+
"ensure_single_axon": self.ensure_single_axon,
155+
"ensure_axon_at_soma": self.ensure_axon_at_soma,
156+
"remove_dummy_neurites": self.remove_dummy_neurites,
141157
"min_length_first_section": self.min_length_first_section,
142158
}
143159

tests/data/test_example_1/out_annotated/Annotate/report.csv

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mono-type,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/mono-type.yaml,out_anno
1010
multiple_point_section,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/multiple_point_section.yaml,,,True,0,,,out_annotated/CollectCurated/data/multiple_point_section.asc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/multiple_point_section.asc,out_annotated/HardLimit/data/multiple_point_section.yaml,True,0,,,out_annotated/CollectCurated/data/multiple_point_section.asc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/multiple_point_section.asc,,,,True,0,,,out_annotated/CollectCurated/data/multiple_point_section.asc,False,,,True,0,,,,,out_annotated/CollectCurated/data/multiple_point_section.asc,,True,0,,,out_annotated/CollectCurated/data/multiple_point_section.asc,out_annotated/HardLimit/data/multiple_point_section.yaml,out_annotated/PlotHardLimit/data/multiple_point_section.html
1111
nested_single_children,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/nested_single_children.yaml,,,True,0,,,out_annotated/CollectCurated/data/nested_single_children.asc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/nested_single_children.asc,out_annotated/HardLimit/data/nested_single_children.yaml,True,0,,,out_annotated/CollectCurated/data/nested_single_children.asc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/nested_single_children.asc,,,,True,0,,,out_annotated/CollectCurated/data/nested_single_children.asc,False,,,True,0,,,,,out_annotated/CollectCurated/data/nested_single_children.asc,,True,0,,,out_annotated/CollectCurated/data/nested_single_children.asc,out_annotated/HardLimit/data/nested_single_children.yaml,out_annotated/PlotHardLimit/data/nested_single_children.html
1212
neurite_wrong_root_point,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/neurite_wrong_root_point.yaml,,,True,0,,,out_annotated/CollectCurated/data/neurite_wrong_root_point.swc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/neurite_wrong_root_point.swc,out_annotated/HardLimit/data/neurite_wrong_root_point.yaml,True,0,,,out_annotated/CollectCurated/data/neurite_wrong_root_point.swc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/neurite_wrong_root_point.swc,,,,True,0,,,out_annotated/CollectCurated/data/neurite_wrong_root_point.swc,False,,,True,0,,,,,out_annotated/CollectCurated/data/neurite_wrong_root_point.swc,,True,0,,,out_annotated/CollectCurated/data/neurite_wrong_root_point.swc,out_annotated/HardLimit/data/neurite_wrong_root_point.yaml,out_annotated/PlotHardLimit/data/neurite_wrong_root_point.html
13-
nrn-order-already-sorted,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/nrn-order-already-sorted.yaml,out_annotated/ApicalPoint/data/nrn-order-already-sorted.yaml,out_annotated/CutLeaves/data/nrn-order-already-sorted.yaml,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,True,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,out_annotated/HardLimit/data/nrn-order-already-sorted.yaml,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,True,out_annotated/ApicalPoint/data/nrn-order-already-sorted.yaml,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,,out_annotated/CutLeaves/data/nrn-order-already-sorted.yaml,"[{""axis"": ""Z"", ""side"": -1, ""quality"": 6.0}]",True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,True,out_annotated/ApicalPoint/data/nrn-order-already-sorted.yaml,out_annotated/PlotApicalPoint/data/nrn-order-already-sorted.html,True,0,,,out_annotated/CutLeaves/data/nrn-order-already-sorted.yaml,"[{""axis"": ""Z"", ""side"": -1, ""quality"": 6.0}]",out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,out_annotated/PlotCutLeaves/data/nrn-order-already-sorted.html,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,out_annotated/HardLimit/data/nrn-order-already-sorted.yaml,out_annotated/PlotHardLimit/data/nrn-order-already-sorted.html
13+
nrn-order-already-sorted,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/nrn-order-already-sorted.yaml,out_annotated/ApicalPoint/data/nrn-order-already-sorted.yaml,out_annotated/CutLeaves/data/nrn-order-already-sorted.yaml,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,True,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,out_annotated/HardLimit/data/nrn-order-already-sorted.yaml,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,True,out_annotated/ApicalPoint/data/nrn-order-already-sorted.yaml,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,,out_annotated/CutLeaves/data/nrn-order-already-sorted.yaml,"[{""axis"": ""Z"", ""side"": -1, ""quality"": 5.0}]",True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,True,out_annotated/ApicalPoint/data/nrn-order-already-sorted.yaml,out_annotated/PlotApicalPoint/data/nrn-order-already-sorted.html,True,0,,,out_annotated/CutLeaves/data/nrn-order-already-sorted.yaml,"[{""axis"": ""Z"", ""side"": -1, ""quality"": 5.0}]",out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,out_annotated/PlotCutLeaves/data/nrn-order-already-sorted.html,True,0,,,out_annotated/CollectCurated/data/nrn-order-already-sorted.h5,out_annotated/HardLimit/data/nrn-order-already-sorted.yaml,out_annotated/PlotHardLimit/data/nrn-order-already-sorted.html
1414
nrn_ordering,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/nrn_ordering.yaml,out_annotated/ApicalPoint/data/nrn_ordering.yaml,out_annotated/CutLeaves/data/nrn_ordering.yaml,True,0,,,out_annotated/CollectCurated/data/nrn_ordering.swc,True,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/nrn_ordering.swc,out_annotated/HardLimit/data/nrn_ordering.yaml,True,0,,,out_annotated/CollectCurated/data/nrn_ordering.swc,True,out_annotated/ApicalPoint/data/nrn_ordering.yaml,True,0,,,out_annotated/CollectCurated/data/nrn_ordering.swc,,out_annotated/CutLeaves/data/nrn_ordering.yaml,"[{""axis"": ""Z"", ""side"": 1, ""quality"": 14.8}]",True,0,,,out_annotated/CollectCurated/data/nrn_ordering.swc,True,out_annotated/ApicalPoint/data/nrn_ordering.yaml,out_annotated/PlotApicalPoint/data/nrn_ordering.html,True,0,,,out_annotated/CutLeaves/data/nrn_ordering.yaml,"[{""axis"": ""Z"", ""side"": 1, ""quality"": 14.8}]",out_annotated/CollectCurated/data/nrn_ordering.swc,out_annotated/PlotCutLeaves/data/nrn_ordering.html,True,0,,,out_annotated/CollectCurated/data/nrn_ordering.swc,out_annotated/HardLimit/data/nrn_ordering.yaml,out_annotated/PlotHardLimit/data/nrn_ordering.html
1515
pia,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/pia.yaml,,,True,0,,,out_annotated/CollectCurated/data/pia.asc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/pia.asc,out_annotated/HardLimit/data/pia.yaml,True,0,,,out_annotated/CollectCurated/data/pia.asc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/pia.asc,,,,True,0,,,out_annotated/CollectCurated/data/pia.asc,False,,,True,0,,,,,out_annotated/CollectCurated/data/pia.asc,,True,0,,,out_annotated/CollectCurated/data/pia.asc,out_annotated/HardLimit/data/pia.yaml,out_annotated/PlotHardLimit/data/pia.html
1616
reversed_NRN_neurite_order,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/reversed_NRN_neurite_order.yaml,out_annotated/ApicalPoint/data/reversed_NRN_neurite_order.yaml,,True,0,,,out_annotated/CollectCurated/data/reversed_NRN_neurite_order.swc,True,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/reversed_NRN_neurite_order.swc,out_annotated/HardLimit/data/reversed_NRN_neurite_order.yaml,True,0,,,out_annotated/CollectCurated/data/reversed_NRN_neurite_order.swc,True,out_annotated/ApicalPoint/data/reversed_NRN_neurite_order.yaml,True,0,No cut leaves found,,out_annotated/CollectCurated/data/reversed_NRN_neurite_order.swc,,,,True,0,,,out_annotated/CollectCurated/data/reversed_NRN_neurite_order.swc,True,out_annotated/ApicalPoint/data/reversed_NRN_neurite_order.yaml,out_annotated/PlotApicalPoint/data/reversed_NRN_neurite_order.html,True,0,,,,,out_annotated/CollectCurated/data/reversed_NRN_neurite_order.swc,,True,0,,,out_annotated/CollectCurated/data/reversed_NRN_neurite_order.swc,out_annotated/HardLimit/data/reversed_NRN_neurite_order.yaml,out_annotated/PlotHardLimit/data/reversed_NRN_neurite_order.html
@@ -20,7 +20,9 @@ simple,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/simple.yaml,,,True,0,,,out
2020
simple-with-font,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/simple-with-font.yaml,,,True,0,,,out_annotated/CollectCurated/data/simple-with-font.asc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/simple-with-font.asc,out_annotated/HardLimit/data/simple-with-font.yaml,True,0,,,out_annotated/CollectCurated/data/simple-with-font.asc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/simple-with-font.asc,,,,True,0,,,out_annotated/CollectCurated/data/simple-with-font.asc,False,,,True,0,,,,,out_annotated/CollectCurated/data/simple-with-font.asc,,True,0,,,out_annotated/CollectCurated/data/simple-with-font.asc,out_annotated/HardLimit/data/simple-with-font.yaml,out_annotated/PlotHardLimit/data/simple-with-font.html
2121
simple-with-image-coord,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/simple-with-image-coord.yaml,,,True,0,,,out_annotated/CollectCurated/data/simple-with-image-coord.asc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/simple-with-image-coord.asc,out_annotated/HardLimit/data/simple-with-image-coord.yaml,True,0,,,out_annotated/CollectCurated/data/simple-with-image-coord.asc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/simple-with-image-coord.asc,,,,True,0,,,out_annotated/CollectCurated/data/simple-with-image-coord.asc,False,,,True,0,,,,,out_annotated/CollectCurated/data/simple-with-image-coord.asc,,True,0,,,out_annotated/CollectCurated/data/simple-with-image-coord.asc,out_annotated/HardLimit/data/simple-with-image-coord.yaml,out_annotated/PlotHardLimit/data/simple-with-image-coord.html
2222
simple2,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/simple2.yaml,,,True,0,,,out_annotated/CollectCurated/data/simple2.asc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/simple2.asc,out_annotated/HardLimit/data/simple2.yaml,True,0,,,out_annotated/CollectCurated/data/simple2.asc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/simple2.asc,,,,True,0,,,out_annotated/CollectCurated/data/simple2.asc,False,,,True,0,,,,,out_annotated/CollectCurated/data/simple2.asc,,True,0,,,out_annotated/CollectCurated/data/simple2.asc,out_annotated/HardLimit/data/simple2.yaml,out_annotated/PlotHardLimit/data/simple2.html
23-
soma_cylinders,True,0,,,L1_AAA:C,out_annotated/HardLimit/data/soma_cylinders.yaml,,,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,out_annotated/HardLimit/data/soma_cylinders.yaml,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,False,,True,0,No cut leaves found,,out_annotated/CollectCurated/data/soma_cylinders.swc,,,,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,False,,,True,0,,,,,out_annotated/CollectCurated/data/soma_cylinders.swc,,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,out_annotated/HardLimit/data/soma_cylinders.yaml,out_annotated/PlotHardLimit/data/soma_cylinders.html
23+
soma_cylinders,False,1,,,L1_AAA:C,out_annotated/HardLimit/data/soma_cylinders.yaml,,,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,out_annotated/HardLimit/data/soma_cylinders.yaml,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,False,,False,1,,"Traceback ... ValueError: Can not search for cut leaves for a neuron with no neurites.
24+
",out_annotated/CollectCurated/data/soma_cylinders.swc,,,,True,0,,,out_annotated/CollectCurated/data/soma_cylinders.swc,False,,,False,1,,,,,out_annotated/CollectCurated/data/soma_cylinders.swc,,False,1,,"Traceback ... ValueError: Can not plot axis marker for a neuron with no neurites.
25+
",out_annotated/CollectCurated/data/soma_cylinders.swc,out_annotated/HardLimit/data/soma_cylinders.yaml,
2426
soma_multiple_frustums,False,1,,,L1_AAA:C,out_annotated/HardLimit/data/soma_multiple_frustums.yaml,,,True,0,,,out_annotated/CollectCurated/data/soma_multiple_frustums.swc,False,L1_AAA:C,,True,0,No regex provided!,,L1_AAA:C,True,0,,,out_annotated/CollectCurated/data/soma_multiple_frustums.swc,out_annotated/HardLimit/data/soma_multiple_frustums.yaml,True,0,,,out_annotated/CollectCurated/data/soma_multiple_frustums.swc,False,,False,1,,"Traceback ... ValueError: Can not search for cut leaves for a neuron with no neurites.
2527
",out_annotated/CollectCurated/data/soma_multiple_frustums.swc,,,,True,0,,,out_annotated/CollectCurated/data/soma_multiple_frustums.swc,False,,,False,1,,,,,out_annotated/CollectCurated/data/soma_multiple_frustums.swc,,False,1,,"Traceback ... ValueError: Can not plot axis marker for a neuron with no neurites.
2628
",out_annotated/CollectCurated/data/soma_multiple_frustums.swc,out_annotated/HardLimit/data/soma_multiple_frustums.yaml,

0 commit comments

Comments
 (0)