@@ -453,9 +453,12 @@ def write_swc(x: 'core.NeuronObject',
453
453
this might drop synapses (i.e. in case of multiple
454
454
pre- and/or postsynapses on a single node)! ``labels``
455
455
must be ``True`` for this to have any effect.
456
- return_node_map : bool
456
+ return_node_map : bool, optional
457
457
If True, will return a dictionary mapping the old node
458
458
ID to the new reindexed node IDs in the file.
459
+ If False (default), will reindex nodes but not return the mapping.
460
+ If None, will not reindex nodes (i.e. their current IDs will be written).
461
+
459
462
460
463
Returns
461
464
-------
@@ -535,7 +538,7 @@ def _write_swc(x: 'core.TreeNeuron',
535
538
write_meta : Union [bool , List [str ], dict ] = True ,
536
539
labels : Union [str , dict , bool ] = True ,
537
540
export_connectors : bool = False ,
538
- return_node_map : bool = False ) -> None :
541
+ return_node_map : Optional [ bool ] = False ) -> None :
539
542
"""Write single TreeNeuron to file."""
540
543
# Generate SWC table
541
544
res = make_swc_table (x ,
@@ -589,6 +592,45 @@ def _write_swc(x: 'core.TreeNeuron',
589
592
return node_map
590
593
591
594
595
+ def sort_swc (df : pd .DataFrame , roots , sort_children = True , inplace = False ):
596
+ """Depth-first search tree to ensure parents are always defined before children."""
597
+ children = defaultdict (list )
598
+ node_id_to_orig_idx = dict ()
599
+ for row in df .itertuples ():
600
+ child = row .node_id
601
+ parent = row .parent_id
602
+ children [parent ].append (child )
603
+ node_id_to_orig_idx [child ] = row .index
604
+
605
+ if sort_children :
606
+ to_visit = sorted (roots , reverse = True )
607
+ else :
608
+ to_visit = list (roots )[::- 1 ]
609
+
610
+ idx = 0
611
+ order = np .full (len (df ), np .nan )
612
+ count = 0
613
+ while to_visit :
614
+ node_id = to_visit .pop ()
615
+ order [node_id_to_orig_idx [node_id ]] = count
616
+ cs = children .pop (order [- 1 ], [])
617
+ if sort_children :
618
+ to_visit .append (sorted (sort_children , reverse = True ))
619
+ else :
620
+ to_visit .append (cs [::- 1 ])
621
+ count += 1
622
+
623
+ # undefined behaviour if any nodes are not reachable from the given roots
624
+
625
+ if not inplace :
626
+ df = df .copy ()
627
+
628
+ df ["_order" ] = order
629
+ df .sort_values ("_order" , inplace = True )
630
+ df .drop (columns = ["_order" ])
631
+ return df
632
+
633
+
592
634
def make_swc_table (x : 'core.TreeNeuron' ,
593
635
labels : Union [str , dict , bool ] = None ,
594
636
export_connectors : bool = False ,
@@ -606,17 +648,20 @@ def make_swc_table(x: 'core.TreeNeuron',
606
648
607
649
str : column name in node table
608
650
dict: must be of format {node_id: 'label', ...}.
609
- bool: if True, will generate automatic labels, if False all nodes have label "0".
651
+ bool: if True, will generate automatic labels for branches ("5") and ends ("6"),
652
+ soma where labelled ("1"), and optionally connectors (see below).
653
+ If False (or for all nodes not labelled as above) all nodes have label "0".
610
654
611
655
export_connectors : bool, optional
612
- If True, will label nodes with pre- ("7") and
613
- postsynapse ("8"). Because only one label can be given
614
- this might drop synapses (i.e. in case of multiple
615
- pre- or postsynapses on a single node)! ``labels``
616
- must be ``True`` for this to have any effect.
617
- return_node_map : bool
656
+ If True, will label nodes with only presynapses ("7"),
657
+ only postsynapses ("8"), or both ("9").
658
+ This overrides branch/end/soma labels.
659
+ ``labels`` must be ``True`` for this to have any effect.
660
+ return_node_map : bool, optional
618
661
If True, will return a dictionary mapping the old node
619
662
ID to the new reindexed node IDs in the file.
663
+ If False, will remap IDs but not return the mapping.
664
+ If None, will not remap IDs.
620
665
621
666
Returns
622
667
-------
@@ -625,8 +670,8 @@ def make_swc_table(x: 'core.TreeNeuron',
625
670
Only if ``return_node_map=True``.
626
671
627
672
"""
628
- # Work on a copy
629
- swc = x .nodes . copy ( )
673
+ # Work on a copy sorted in depth-first order
674
+ swc = sort_swc ( x .nodes , x . root , inplace = False )
630
675
631
676
# Add labels
632
677
swc ['label' ] = 0
@@ -642,31 +687,41 @@ def make_swc_table(x: 'core.TreeNeuron',
642
687
if not isinstance (x .soma , type (None )):
643
688
soma = utils .make_iterable (x .soma )
644
689
swc .loc [swc .node_id .isin (soma ), 'label' ] = 1
690
+
645
691
if export_connectors :
646
692
# Add synapse label
647
693
pre_ids = x .presynapses .node_id .values
648
694
post_ids = x .postsynapses .node_id .values
649
- swc .loc [swc .node_id .isin (pre_ids ), 'label' ] = 7
650
- swc .loc [swc .node_id .isin (post_ids ), 'label' ] = 8
651
-
652
- # Sort such that the parent is always before the child
653
- swc .sort_values ('parent_id' , ascending = True , inplace = True )
654
695
655
- # Reset index
656
- swc .reset_index ( drop = True , inplace = True )
696
+ is_pre = swc [ "node_id" ]. isin ( pre_ids )
697
+ swc .loc [ is_pre , 'label' ] = 7
657
698
658
- # Generate mapping
659
- new_ids = dict ( zip ( swc .node_id . values , swc . index . values + 1 ))
699
+ is_post = swc [ "node" ]. isin ( post_ids )
700
+ swc .loc [ is_post , 'label' ] = 8
660
701
661
- swc ['node_id' ] = swc .node_id .map (new_ids )
662
- # Lambda prevents potential issue with missing parents
663
- swc ['parent_id' ] = swc .parent_id .map (lambda x : new_ids .get (x , - 1 ))
702
+ is_both = np .logical_and (is_pre , is_post )
703
+ swc .loc [is_both , 'label' ] = 9
664
704
665
- # Get things in order
705
+ # Order columns
666
706
swc = swc [['node_id' , 'label' , 'x' , 'y' , 'z' , 'radius' , 'parent_id' ]]
667
707
668
- # Make sure radius has no `None`
708
+ # Make sure radius has no `None` or negative
669
709
swc ['radius' ] = swc .radius .fillna (0 )
710
+ swc ['radius' ][swc ['radius' ] < 0 ] = 0
711
+
712
+ if return_node_map is not None :
713
+ # remap IDs
714
+
715
+ # Reset index
716
+ swc .reset_index (drop = True , inplace = True )
717
+
718
+ # Generate mapping
719
+ new_ids = dict (zip (swc .node_id .values , swc .index .values + 1 ))
720
+
721
+ swc ['node_id' ] = swc .node_id .map (new_ids )
722
+ # Lambda prevents potential issue with missing parents
723
+ swc ['parent_id' ] = swc .parent_id .map (lambda x : new_ids .get (x , - 1 ))
724
+
670
725
671
726
# Adjust column titles
672
727
swc .columns = ['PointNo' , 'Label' , 'X' , 'Y' , 'Z' , 'Radius' , 'Parent' ]
0 commit comments