diff --git a/neurom/features/neurite.py b/neurom/features/neurite.py index 0c90cc50..493a0248 100644 --- a/neurom/features/neurite.py +++ b/neurom/features/neurite.py @@ -177,21 +177,23 @@ def section_bif_lengths(neurite, section_type=NeuriteType.all): @feature(shape=(...,)) -def section_branch_orders(neurite): +def section_branch_orders(neurite, section_type=NeuriteType.all): """Section branch orders.""" - return _map_sections(sf.branch_order, neurite) + return _map_sections(sf.branch_order, neurite, section_type=section_type) @feature(shape=(...,)) -def section_bif_branch_orders(neurite): +def section_bif_branch_orders(neurite, section_type=NeuriteType.all): """Bifurcation section branch orders.""" - return _map_sections(sf.branch_order, neurite, Section.ibifurcation_point) + return _map_sections( + sf.branch_order, neurite, Section.ibifurcation_point, section_type=section_type + ) @feature(shape=(...,)) -def section_term_branch_orders(neurite): +def section_term_branch_orders(neurite, section_type=NeuriteType.all): """Termination section branch orders.""" - return _map_sections(sf.branch_order, neurite, Section.ileaf) + return _map_sections(sf.branch_order, neurite, Section.ileaf, section_type=section_type) @feature(shape=(...,)) @@ -275,7 +277,7 @@ def _seg_taper_rates(sec): @feature(shape=(...,)) -def section_taper_rates(neurite): +def section_taper_rates(neurite, section_type=NeuriteType.all): """Diameter taper rates of the sections from root to tip. Taper rate is defined here as the linear fit along a section. @@ -287,17 +289,21 @@ def _sec_taper_rate(sec): path_distances = np.cumsum(interval_lengths(sec.points, prepend_zero=True)) return np.polynomial.polynomial.polyfit(path_distances, 2 * sec.points[:, COLS.R], 1)[1] - return _map_sections(_sec_taper_rate, neurite) + return _map_sections(_sec_taper_rate, neurite, section_type=section_type) @feature(shape=(...,)) -def segment_meander_angles(neurite): +def segment_meander_angles(neurite, section_type=NeuriteType.all): """Inter-segment opening angles in a section.""" - return list(chain.from_iterable(_map_sections(sf.section_meander_angles, neurite))) + return list( + chain.from_iterable( + _map_sections(sf.section_meander_angles, neurite, section_type=section_type) + ) + ) @feature(shape=(..., 3)) -def segment_midpoints(neurite): +def segment_midpoints(neurite, section_type=NeuriteType.all): """Return a list of segment mid-points.""" def _seg_midpoint(sec): @@ -305,7 +311,7 @@ def _seg_midpoint(sec): pts = sec.points[:, COLS.XYZ] return np.divide(np.add(pts[:-1], pts[1:]), 2.0) - return _map_segments(_seg_midpoint, neurite) + return _map_segments(_seg_midpoint, neurite, section_type=section_type) @feature(shape=(...,)) @@ -327,33 +333,37 @@ def _get_pathlength(section): @feature(shape=(...,)) -def segment_radial_distances(neurite, origin=None): +def segment_radial_distances(neurite, origin=None, section_type=NeuriteType.all): """Returns the list of distances between all segment mid points and origin.""" - def _radial_distances(sec, pos): + def _radial_distances(sec): """List of distances between the mid point of each segment and pos.""" mid_pts = 0.5 * (sec.points[:-1, COLS.XYZ] + sec.points[1:, COLS.XYZ]) return np.linalg.norm(mid_pts - pos[COLS.XYZ], axis=1) pos = neurite.root_node.points[0] if origin is None else origin - # return [s for ss in n.iter_sections() for s in _radial_distances(ss, pos)] - return [d for s in Section.ipreorder(neurite.root_node) for d in _radial_distances(s, pos)] - + return _map_segments(_radial_distances, neurite, section_type=section_type) @feature(shape=(...,)) -def local_bifurcation_angles(neurite): +def local_bifurcation_angles(neurite, section_type=NeuriteType.all): """Get a list of local bf angles.""" - return _map_sections(bf.local_bifurcation_angle, - neurite, - iterator_type=Section.ibifurcation_point) + return _map_sections( + bf.local_bifurcation_angle, + neurite, + iterator_type=Section.ibifurcation_point, + section_type=section_type, + ) @feature(shape=(...,)) -def remote_bifurcation_angles(neurite): +def remote_bifurcation_angles(neurite, section_type=NeuriteType.all): """Get a list of remote bf angles.""" - return _map_sections(bf.remote_bifurcation_angle, - neurite, - iterator_type=Section.ibifurcation_point) + return _map_sections( + bf.remote_bifurcation_angle, + neurite, + iterator_type=Section.ibifurcation_point, + section_type=section_type, + ) @feature(shape=(...,)) @@ -397,15 +407,15 @@ def partition_asymmetry_length(neurite, method='petilla'): @feature(shape=(...,)) -def bifurcation_partitions(neurite): +def bifurcation_partitions(neurite, section_type=NeuriteType.all): """Partition at bf points.""" - return _map_sections(bf.bifurcation_partition, - neurite, - Section.ibifurcation_point) + return _map_sections( + bf.bifurcation_partition, neurite, Section.ibifurcation_point, section_type=section_type + ) @feature(shape=(...,)) -def sibling_ratios(neurite, method='first'): +def sibling_ratios(neurite, method='first', section_type=NeuriteType.all): """Sibling ratios at bf points. The sibling ratio is the ratio between the diameters of the @@ -413,25 +423,28 @@ def sibling_ratios(neurite, method='first'): 0 and 1. Method argument allows one to consider mean diameters along the child section instead of diameter of the first point. """ - return _map_sections(partial(bf.sibling_ratio, method=method), - neurite, - Section.ibifurcation_point) + return _map_sections( + partial(bf.sibling_ratio, method=method), + neurite, + Section.ibifurcation_point, + section_type=section_type, + ) @feature(shape=(..., 2)) -def partition_pairs(neurite): +def partition_pairs(neurite, section_type=NeuriteType.all): """Partition pairs at bf points. Partition pair is defined as the number of bifurcations at the two daughters of the bifurcating section """ - return _map_sections(bf.partition_pair, - neurite, - Section.ibifurcation_point) + return _map_sections( + bf.partition_pair, neurite, Section.ibifurcation_point, section_type=section_type + ) @feature(shape=(...,)) -def diameter_power_relations(neurite, method='first'): +def diameter_power_relations(neurite, method='first', section_type=NeuriteType.all): """Calculate the diameter power relation at a bf point. Diameter power relation is defined in https://www.ncbi.nlm.nih.gov/pubmed/18568015 @@ -439,9 +452,12 @@ def diameter_power_relations(neurite, method='first'): This quantity gives an indication of how far the branching is from the Rall ratio (when =1). """ - return _map_sections(partial(bf.diameter_power_relation, method=method), - neurite, - Section.ibifurcation_point) + return _map_sections( + partial(bf.diameter_power_relation, method=method), + neurite, + Section.ibifurcation_point, + section_type=section_type, + ) @feature(shape=(...,)) @@ -467,9 +483,11 @@ def section_term_radial_distances(neurite, origin=None, section_type=NeuriteType @feature(shape=(...,)) -def section_bif_radial_distances(neurite, origin=None): +def section_bif_radial_distances(neurite, origin=None, section_type=NeuriteType.all): """Get the radial distances of the bf sections.""" - return section_radial_distances(neurite, origin, Section.ibifurcation_point) + return section_radial_distances( + neurite, origin, Section.ibifurcation_point, section_type=section_type + ) @feature(shape=(...,)) @@ -547,6 +565,6 @@ def principal_direction_extents(neurite, direction=0): @feature(shape=(...,)) -def section_strahler_orders(neurite): +def section_strahler_orders(neurite, section_type=NeuriteType.all): """Inter-segment opening angles in a section.""" - return _map_sections(sf.strahler_order, neurite) + return _map_sections(sf.strahler_order, neurite, section_type=section_type) diff --git a/tests/test_mixed.py b/tests/test_mixed.py index d886d0f1..3c7b8a65 100644 --- a/tests/test_mixed.py +++ b/tests/test_mixed.py @@ -6,6 +6,7 @@ from neurom import NeuriteType from neurom.features import get from neurom.features import _MORPHOLOGY_FEATURES, _NEURITE_FEATURES +import collections.abc @pytest.fixture def mixed_morph(): @@ -34,6 +35,46 @@ def mixed_morph(): """, reader="swc") +def _assert_feature_equal(obj, feature_name, expected_values, kwargs, use_subtrees): + + def innermost_value(iterable): + while isinstance(iterable, collections.abc.Iterable): + try: + iterable = iterable[0] + except IndexError: + # empty list + return None + return iterable + + + assert_equal = lambda a, b: npt.assert_equal( + a, b, err_msg=f"ACTUAL: {a}\nDESIRED: {b}", verbose=False + ) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + + values = get(feature_name, obj, use_subtrees=use_subtrees, **kwargs) + + # handle empty lists because allclose always passes in that case. + # See: https://github.com/numpy/numpy/issues/11071 + if isinstance(values, collections.abc.Iterable): + if isinstance(expected_values, collections.abc.Iterable): + if isinstance(innermost_value(values), (float, np.floating)): + npt.assert_allclose(values, expected_values, atol=1e-5) + else: + assert_equal(values, expected_values) + else: + assert_equal(values, expected_values) + else: + if isinstance(expected_values, collections.abc.Iterable): + assert_equal(values, expected_values) + else: + if isinstance(values, (float, np.floating)): + npt.assert_allclose(values, expected_values, atol=1e-5) + else: + assert_equal(values, expected_values) + def _dispatch_features(features, mode): @@ -368,7 +409,7 @@ def _morphology_features(mode): }, { "kwargs": {"neurite_type": NeuriteType.axon}, - "expected_wout_subtrees": [], + "expected_wout_subtrees": 0, "expected_with_subtrees": 1, }, { @@ -525,28 +566,15 @@ def _morphology_features(mode): @pytest.mark.parametrize("feature_name, kwargs, expected", _morphology_features(mode="wout-subtrees")) def test_morphology__morphology_features_wout_subtrees(feature_name, kwargs, expected, mixed_morph): - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - - npt.assert_allclose( - get(feature_name, mixed_morph, **kwargs), - expected, - rtol=1e-6 - ) + _assert_feature_equal(mixed_morph, feature_name, expected, kwargs, use_subtrees=False) @pytest.mark.parametrize("feature_name, kwargs, expected", _morphology_features(mode="with-subtrees")) -def test_morphology__morphology_features_with_subtrees(feature_name, kwargs, expected, mixed_morph): +def test_morphology__morphology_features_with_subtrees( + feature_name, kwargs, expected, mixed_morph +): + _assert_feature_equal(mixed_morph, feature_name, expected, kwargs, use_subtrees=True) - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - - npt.assert_allclose( - get(feature_name, mixed_morph, use_subtrees=True, **kwargs), - expected, - rtol=1e-6 - ) def _neurite_features(mode): @@ -678,7 +706,7 @@ def _neurite_features(mode): }, { "kwargs": {"neurite_type": NeuriteType.axon}, - "expected_wout_subtrees": 0., + "expected_wout_subtrees": [], "expected_with_subtrees": [1.414214, 1., 1.], }, { @@ -768,6 +796,88 @@ def _neurite_features(mode): "expected_with_subtrees": [1.0] * 3, } ], + "section_radial_distances": [ + { + # radial distances change when the mixed subtrees are processed because + # the root of the subtree is considered + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": + [1.0, 2.0, 1.4142135, 1.4142135, 3.1622777, 2.828427, + 3.6055512, 3.6055512, 1.0, 2.0, 1.4142135], + "expected_with_subtrees": + [1.0, 2.0, 1.4142135, 1.4142135, 3.1622777, 1.414214, + 2.236068, 2.236068, 1.0, 2.0, 1.4142135], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": + [1.0, 2.0, 1.4142135, 1.4142135, 3.1622777, 2.828427, + 3.6055512, 3.6055512], + "expected_with_subtrees": + [1.0, 2.0, 1.4142135, 1.4142135, 3.1622777], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [1.414214, 2.236068, 2.236068], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [1., 2., 1.414214], + "expected_with_subtrees": [1., 2., 1.414214], + } + + ], + "section_term_radial_distances": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": + [2.0, 1.4142135, 3.1622777, 3.6055512, 3.6055512, 2.0, 1.4142135], + "expected_with_subtrees": + [2.0, 1.4142135, 3.1622777, 2.236068, 2.236068, 2.0, 1.4142135], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [2.0, 1.4142135, 3.1622777, 3.6055512, 3.6055512], + "expected_with_subtrees": [2.0, 1.4142135, 3.1622777], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [2.236068, 2.236068], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [2., 1.414214], + "expected_with_subtrees": [2., 1.414214], + } + + ], + "section_bif_radial_distances": [ + { + # radial distances change when the mixed subtrees are processed because + # the root of the subtree is considered instead of the tree root + # heterogeneous forks are not valid forking points + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [1.0, 1.4142135, 2.828427, 1.0], + "expected_with_subtrees": [1.0, 1.4142135, 1.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [1.0, 1.4142135, 2.828427], + "expected_with_subtrees": [1.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [1.4142135,], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [1.], + "expected_with_subtrees": [1.], + } + ], "section_end_distances": [ { "kwargs": {"neurite_type": NeuriteType.all}, @@ -785,7 +895,7 @@ def _neurite_features(mode): }, { "kwargs": {"neurite_type": NeuriteType.axon}, - "expected_wout_subtrees": 0., + "expected_wout_subtrees": [], "expected_with_subtrees": [1.414214, 1., 1.], }, { @@ -807,7 +917,7 @@ def _neurite_features(mode): }, { "kwargs": {"neurite_type": NeuriteType.axon}, - "expected_wout_subtrees": 0., + "expected_wout_subtrees": [], "expected_with_subtrees": [1., 1.], }, { @@ -816,6 +926,138 @@ def _neurite_features(mode): "expected_with_subtrees": [1., 1.], } ], + "section_taper_rates": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [0.0] * 11, + "expected_with_subtrees": [0.0] * 11, + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [0.0] * 8, + "expected_with_subtrees": [0.0] * 5, + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [0.0] * 3, + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [0.0] * 3, + "expected_with_subtrees": [0.0] * 3, + } + ], + "section_bif_lengths": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [1., 1.414214, 1.414214, 1.], + "expected_with_subtrees": [1., 1.414214, 1.], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [1., 1.414214, 1.414214], + "expected_with_subtrees": [1.], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [1.414214], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [1.], + "expected_with_subtrees": [1.], + }, + ], + "section_branch_orders": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [0, 1, 1, 0, 1, 1, 2, 2, 0, 1, 1], + "expected_with_subtrees": [0, 1, 1, 0, 1, 1, 2, 2, 0, 1, 1], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [0, 1, 1, 0, 1, 1, 2, 2], + "expected_with_subtrees": [0, 1, 1, 0, 1], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [1, 2, 2], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [0, 1, 1], + "expected_with_subtrees": [0, 1, 1], + }, + ], + "section_bif_branch_orders": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [0, 0, 1, 0], + "expected_with_subtrees": [0, 1, 0], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [0, 0, 1], + "expected_with_subtrees": [0], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [1], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [0], + "expected_with_subtrees": [0], + }, + ], + "section_term_branch_orders": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [1, 1, 1, 2, 2, 1, 1], + "expected_with_subtrees": [1, 1, 1, 2, 2, 1, 1], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [1, 1, 1, 2, 2], + "expected_with_subtrees": [1, 1, 1], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [2, 2], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [1, 1], + "expected_with_subtrees": [1, 1], + }, + ], + "section_strahler_orders": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [2, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1], + "expected_with_subtrees": [2, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [2, 1, 1, 2, 1, 2, 1, 1], + "expected_with_subtrees": [2, 1, 1, 2, 1], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [2, 1, 1], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [2, 1, 1], + "expected_with_subtrees": [2, 1, 1], + }, + ], "segment_lengths": [ { "kwargs": {"neurite_type": NeuriteType.all}, @@ -833,7 +1075,7 @@ def _neurite_features(mode): }, { "kwargs": {"neurite_type": NeuriteType.axon}, - "expected_wout_subtrees": 0., + "expected_wout_subtrees": [], "expected_with_subtrees": [1.414214, 1., 1.], }, { @@ -944,6 +1186,112 @@ def _neurite_features(mode): "expected_with_subtrees": [0.0] * 3, }, ], + "segment_radial_distances": [ + { + # radial distances change when the mixed subtrees are processed because + # the root of the subtree is considered + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": + [0.5, 1.5, 1.118034, 0.70710677, 2.236068, 2.1213202, 3.2015622, 3.2015622, + 0.5, 1.5, 1.118034], + "expected_with_subtrees": + [0.5, 1.5, 1.118034, 0.70710677, 2.236068, 0.707107, 1.802776, 1.802776, + 0.5, 1.5, 1.118034], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": + [0.5, 1.5, 1.118034, 0.70710677, 2.236068, 2.1213202, 3.2015622, 3.2015622], + "expected_with_subtrees": + [0.5, 1.5, 1.118034, 0.70710677, 2.236068], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [0.707107, 1.802776, 1.802776], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [0.5, 1.5, 1.118034], + "expected_with_subtrees": [0.5, 1.5, 1.118034], + }, + ], + "segment_midpoints": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [ + [-1.5, 0.0, 0.0], [-2.5, 0.0, 0.0], [-2.0, 0.5, 0.0], [0.5, 1.5, 0. ], + [1.0, 3.0, 0.0], [1.5, 2.5, 0.0], [2.0, 3.5, 0.0], [2.5, 3.0, 0.0], + [0.0, -1.5, 0.0], [0., -2.5, 0.0], [0.5, -2.0, 0.0]], + "expected_with_subtrees": [ + [-1.5, 0.0, 0.0], [-2.5, 0.0, 0.0], [-2.0, 0.5, 0.0], [0.5, 1.5, 0. ], + [1.0, 3.0, 0.0], [1.5, 2.5, 0.0], [2.0, 3.5, 0.0], [2.5, 3.0, 0.0], + [0.0, -1.5, 0.0], [0., -2.5, 0.0], [0.5, -2.0, 0.0]], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [ + [-1.5, 0.0, 0.0], [-2.5, 0.0, 0.0], [-2.0, 0.5, 0.0], [0.5, 1.5, 0. ], + [1.0, 3.0, 0.0], [1.5, 2.5, 0.0], [2.0, 3.5, 0.0], [2.5, 3.0, 0.0]], + "expected_with_subtrees": [ + [-1.5, 0.0, 0.0], [-2.5, 0.0, 0.0], [-2.0, 0.5, 0.0], [0.5, 1.5, 0. ], + [1.0, 3.0, 0.0]], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [[1.5, 2.5, 0.0], [2.0, 3.5, 0.0], [2.5, 3.0, 0.0]], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [[0.0, -1.5, 0.0], [0., -2.5, 0.0], [0.5, -2.0, 0.0]], + "expected_with_subtrees": [[0.0, -1.5, 0.0], [0., -2.5, 0.0], [0.5, -2.0, 0.0]], + }, + ], + "segment_meander_angles": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [], + }, + ], + "number_of_sections": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": 11, + "expected_with_subtrees": 11, + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": 8, + "expected_with_subtrees": 5, + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": 0, + "expected_with_subtrees": 3, + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": 3, + "expected_with_subtrees": 3, + }, + ], "number_of_bifurcations": [ { "kwargs": {"neurite_type": NeuriteType.all}, @@ -988,36 +1336,170 @@ def _neurite_features(mode): "expected_with_subtrees": 1, }, ], - "section_bif_lengths": [ + "volume_density": [ # neurites are flat :( { "kwargs": {"neurite_type": NeuriteType.all}, - "expected_wout_subtrees": [1., 1.414214, 1.414214, 1.], - "expected_with_subtrees": [1., 1.414214, 1.], + "expected_wout_subtrees": np.nan, + "expected_with_subtrees": np.nan, }, { "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, - "expected_wout_subtrees": [1., 1.414214, 1.414214], - "expected_with_subtrees": [1.], + "expected_wout_subtrees": np.nan, + "expected_with_subtrees": np.nan, + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": 0, + "expected_with_subtrees": np.nan, + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": np.nan, + "expected_with_subtrees": np.nan, + }, + ], + "local_bifurcation_angles": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [0.5 * np.pi, 0.785398, 0.5 * np.pi, 0.5 * np.pi], + "expected_with_subtrees": [0.5 * np.pi, 0.5 * np.pi, 0.5 * np.pi], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [0.5 * np.pi, 0.785398, 0.5 * np.pi], + "expected_with_subtrees": [1.570796], }, { "kwargs": {"neurite_type": NeuriteType.axon}, "expected_wout_subtrees": [], - "expected_with_subtrees": [1.414214], + "expected_with_subtrees": [0.5 * np.pi], }, { "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, - "expected_wout_subtrees": [1.], - "expected_with_subtrees": [1.], - } + "expected_wout_subtrees": [0.5 * np.pi], + "expected_with_subtrees": [0.5 * np.pi], + }, + ], + "remote_bifurcation_angles": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [0.5 * np.pi, 0.785398, 0.5 * np.pi, 0.5 * np.pi], + "expected_with_subtrees": [0.5 * np.pi, 0.5 * np.pi, 0.5 * np.pi], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [0.5 * np.pi, 0.785398, 0.5 * np.pi], + "expected_with_subtrees": [1.570796], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [0.5 * np.pi], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [0.5 * np.pi], + "expected_with_subtrees": [0.5 * np.pi], + }, + ], + "sibling_ratios": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [1.0] * 4, + "expected_with_subtrees": [1.0] * 3, + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [1.0] * 3, + "expected_with_subtrees": [1.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [1.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [1.0], + "expected_with_subtrees": [1.0], + }, + ], + "partition_pairs": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [(1.0, 1.0), (1.0, 3.0), (1.0, 1.0), (1.0, 1.0)], + "expected_with_subtrees": [(1.0, 1.0), (1.0, 1.0), (1.0, 1.0)], + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [(1.0, 1.0), (1.0, 3.0), (1.0, 1.0)], + "expected_with_subtrees": [(1.0, 1.0)], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [(1.0, 1.0)], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [(1.0, 1.0)], + "expected_with_subtrees": [(1.0, 1.0)], + }, + ], + "diameter_power_relations": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [2.0] * 4, + "expected_with_subtrees": [2.0] * 3, + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [2.0] * 3, + "expected_with_subtrees": [2.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [2.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [2.0], + "expected_with_subtrees": [2.0], + }, + ], + "bifurcation_partitions": [ + { + "kwargs": {"neurite_type": NeuriteType.all}, + "expected_wout_subtrees": [1.0, 3.0, 1.0, 1.0], + "expected_with_subtrees": [1.0] * 3, + }, + { + "kwargs": {"neurite_type": NeuriteType.basal_dendrite}, + "expected_wout_subtrees": [1.0, 3.0, 1.0], + "expected_with_subtrees": [1.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.axon}, + "expected_wout_subtrees": [], + "expected_with_subtrees": [1.0], + }, + { + "kwargs": {"neurite_type": NeuriteType.apical_dendrite}, + "expected_wout_subtrees": [1.0], + "expected_with_subtrees": [1.0], + }, ], } - features_not_tested = set(_NEURITE_FEATURES) - set(features.keys()) + features_not_tested = list( + set(_NEURITE_FEATURES) - set(features.keys()) - set(_MORPHOLOGY_FEATURES) + ) - #assert not features_not_tested, ( - # "The following morphology tests need to be included in the mixed morphology tests:\n" - # f"{features_not_tested}" - #) +# assert not features_not_tested, ( +# "The following morphology tests need to be included in the tests:\n\n" + +# "\n".join(sorted(features_not_tested)) + "\n" +# ) return _dispatch_features(features, mode) @@ -1026,27 +1508,11 @@ def _neurite_features(mode): "feature_name, kwargs, expected", _neurite_features(mode="wout-subtrees") ) def test_morphology__neurite_features_wout_subtrees(feature_name, kwargs, expected, mixed_morph): - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - - npt.assert_allclose( - get(feature_name, mixed_morph, **kwargs), - expected, - atol=1e-6 - ) + _assert_feature_equal(mixed_morph, feature_name, expected, kwargs, use_subtrees=False) @pytest.mark.parametrize( "feature_name, kwargs, expected", _neurite_features(mode="with-subtrees") ) def test_morphology__neurite_features_with_subtrees(feature_name, kwargs, expected, mixed_morph): - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - - npt.assert_allclose( - get(feature_name, mixed_morph, use_subtrees=True, **kwargs), - expected, - atol=1e-6 - ) + _assert_feature_equal(mixed_morph, feature_name, expected, kwargs, use_subtrees=True)