diff --git a/pylintrc b/pylintrc index 6ebd4c1c..277b56ce 100644 --- a/pylintrc +++ b/pylintrc @@ -6,7 +6,7 @@ #R0903 - Too Few public methods #W0511 - TODO in code #R0401 - cyclic-import -disable=C0103,R0903,W0511,R0401,unspecified-encoding,consider-using-f-string +disable=C0103,R0903,W0511,R0401,unspecified-encoding,consider-using-f-string, too-many-positional-arguments [FORMAT] # Maximum number of characters on a single line. diff --git a/pyproject.toml b/pyproject.toml index 3f3c5b87..6deabda7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering :: Bio-Informatics", ] dependencies = [ diff --git a/tests/apps/test_morph_stats.py b/tests/apps/test_morph_stats.py index dc4f56f6..b7f23ce2 100644 --- a/tests/apps/test_morph_stats.py +++ b/tests/apps/test_morph_stats.py @@ -212,10 +212,9 @@ def test_extract_stats_scalar_feature(): # silence warning about approximating soma volume with a sphere warnings.simplefilter("ignore", category=UserWarning) res = ms.extract_stats(m, config) - assert res == { - 'all': {'max_number_of_forking_points': 277}, - 'morphology': {'sum_soma_volume': 1424.4383771584492}, - } + + assert res["all"] == {'max_number_of_forking_points': 277} + assert_almost_equal(res["morphology"]["sum_soma_volume"], 1424.4383771584492, decimal=4) def test_extract_stats__kwarg_modes_multiple_features(): @@ -493,7 +492,8 @@ def test_extract_dataframe_multiproc(): actual = ms.extract_dataframe(morphs, REF_CONFIG, n_workers=os.cpu_count() + 1) # drop raw features as they require too much test data to mock actual = actual.drop(columns='raw_section_branch_orders', level=1) - assert len(w) == 1, "Warning not emitted" + assert len(w) >= 1, "Warning not emitted" + assert_frame_equal(actual, expected, check_dtype=False) with warnings.catch_warnings(record=True) as w: diff --git a/tests/core/test_section.py b/tests/core/test_section.py index 5f7717a1..1898061e 100644 --- a/tests/core/test_section.py +++ b/tests/core/test_section.py @@ -42,8 +42,8 @@ def test_section_base_func(): assert section.id == 0 assert_array_equal(section.points, [[0, 0, 0, 1], [0, 5, 0, 1]]) assert_almost_equal(section.length, 5) - assert_almost_equal(section.area, 31.41592653589793) - assert_almost_equal(section.volume, 15.707963267948964) + assert_almost_equal(section.area, 31.4159, decimal=4) + assert_almost_equal(section.volume, 15.7079, decimal=4) # __nonzero__ assert section diff --git a/tests/core/test_soma.py b/tests/core/test_soma.py index c6cc3050..d9972e16 100644 --- a/tests/core/test_soma.py +++ b/tests/core/test_soma.py @@ -101,7 +101,7 @@ def test_Soma_ThreePointCylinder(): assert 'SomaNeuromorphoThreePointCylinders' in str(sm) assert isinstance(sm, soma.SomaNeuromorphoThreePointCylinders) assert list(sm.center) == [0, 0, 0] - assert sm.radius == 44 + assert_almost_equal(sm.radius, 44.0, decimal=5) def test_Soma_ThreePointCylinder_invalid_radius(): diff --git a/tests/features/test_get_features.py b/tests/features/test_get_features.py index 21a10da9..b18b4c10 100644 --- a/tests/features/test_get_features.py +++ b/tests/features/test_get_features.py @@ -147,7 +147,9 @@ def test_max_radial_distance(): def test_section_tortuosity(): assert_allclose( - _stats(features.get('section_tortuosity', POP)), (1.0, 4.657, 440.408, 1.342), rtol=1e-3 + _stats(features.get('section_tortuosity', POP)), + (1.0, 4.657, 440.408, 1.342), + rtol=1e-3, ) assert_allclose( _stats(features.get('section_tortuosity', POP, neurite_type=NeuriteType.all)), @@ -326,6 +328,7 @@ def test_total_length(): assert_allclose( features.get('total_length', POP, neurite_type=NeuriteType.axon), [207.8797736031714, 207.81088341560977, 11767.156115224638], + rtol=1e-6, ) assert_allclose( features.get('total_length', POP, neurite_type=NeuriteType.apical_dendrite), @@ -518,12 +521,14 @@ def test_neurite_volumes(): assert_allclose( _stats(features.get('total_volume_per_neurite', POP, neurite_type=NeuriteType.axon)), (276.58135508666612, 277.5357232437392, 830.85568094763551, 276.95189364921185), + rtol=1e-3, ) assert_allclose( _stats( features.get('total_volume_per_neurite', POP, neurite_type=NeuriteType.apical_dendrite) ), (271.94122143951864, 271.94122143951864, 271.94122143951864, 271.94122143951864), + rtol=1e-3, ) assert_allclose( _stats( @@ -567,52 +572,61 @@ def test_neurite_density(): assert_allclose( _stats(features.get('neurite_volume_density', POP)), (6.1847539631150784e-06, 0.52464681266899216, 1.9767794901940539, 0.19767794901940539), + rtol=1e-6, ) assert_allclose( _stats(features.get('neurite_volume_density', POP, neurite_type=NeuriteType.all)), (6.1847539631150784e-06, 0.52464681266899216, 1.9767794901940539, 0.19767794901940539), + rtol=1e-6, ) assert_allclose( _stats(features.get('neurite_volume_density', POP, neurite_type=NeuriteType.axon)), (6.1847539631150784e-06, 0.26465213325053372, 0.5275513670655404, 0.1758504556885134), - 1e-6, + rtol=1e-6, ) assert_allclose( _stats( features.get('neurite_volume_density', POP, neurite_type=NeuriteType.apical_dendrite) ), (0.43756606998299519, 0.43756606998299519, 0.43756606998299519, 0.43756606998299519), + rtol=1e-6, ) assert_allclose( _stats( features.get('neurite_volume_density', POP, neurite_type=NeuriteType.basal_dendrite) ), (0.00034968816544949771, 0.52464681266899216, 1.0116620531455183, 0.16861034219091972), + rtol=1e-6, ) assert_allclose( _stats(features.get('neurite_volume_density', NRN)), (0.24068543213643726, 0.52464681266899216, 1.4657913638494682, 0.36644784096236704), + rtol=1e-6, ) assert_allclose( _stats(features.get('neurite_volume_density', NRN, neurite_type=NeuriteType.all)), (0.24068543213643726, 0.52464681266899216, 1.4657913638494682, 0.36644784096236704), + rtol=1e-6, ) assert_allclose( _stats(features.get('neurite_volume_density', NRN, neurite_type=NeuriteType.axon)), (0.26289304906104355, 0.26289304906104355, 0.26289304906104355, 0.26289304906104355), + rtol=1e-6, ) assert_allclose( _stats( features.get('neurite_volume_density', NRN, neurite_type=NeuriteType.apical_dendrite) ), (0.43756606998299519, 0.43756606998299519, 0.43756606998299519, 0.43756606998299519), + rtol=1e-6, ) assert_allclose( _stats( features.get('neurite_volume_density', NRN, neurite_type=NeuriteType.basal_dendrite) ), (0.24068543213643726, 0.52464681266899216, 0.76533224480542938, 0.38266612240271469), + rtol=1e-6, ) diff --git a/tests/features/test_morphology.py b/tests/features/test_morphology.py index 41c53134..587a3162 100644 --- a/tests/features/test_morphology.py +++ b/tests/features/test_morphology.py @@ -90,8 +90,8 @@ def test_soma_volume(): def test_soma_surface_area(): - assert_allclose(morphology.soma_surface_area(SIMPLE), 12.566370614359172) - assert_allclose(morphology.soma_surface_area(NRN), 0.1075095256160432) + assert_allclose(morphology.soma_surface_area(SIMPLE), 12.566370614359172, rtol=1e-6) + assert_allclose(morphology.soma_surface_area(NRN), 0.1075095256160432, rtol=1e-6) def test_soma_radius(): @@ -105,11 +105,11 @@ def surface(r0, r1, h): basal_area = surface(1, 1, 5) + surface(1, 0, 5) + surface(1, 0, 6) ret = morphology.total_area_per_neurite(SIMPLE, neurite_type=BASAL_DENDRITE) - assert_almost_equal(ret[0], basal_area) + assert_almost_equal(ret[0], basal_area, decimal=5) axon_area = surface(1, 1, 4) + surface(1, 0, 5) + surface(1, 0, 6) ret = morphology.total_area_per_neurite(SIMPLE, neurite_type=AXON) - assert_almost_equal(ret[0], axon_area) + assert_almost_equal(ret[0], axon_area, decimal=5) ret = morphology.total_area_per_neurite(SIMPLE) assert np.allclose(ret, [basal_area, axon_area]) @@ -143,6 +143,7 @@ def test_total_volume_per_neurite(): assert_allclose( total_volumes, [271.94122143951864, 281.24754646913954, 274.98039928781355, 276.73860261723024], + rtol=1e-6, ) diff --git a/tests/features/test_neurite.py b/tests/features/test_neurite.py index 558288e1..9747fb29 100644 --- a/tests/features/test_neurite.py +++ b/tests/features/test_neurite.py @@ -77,7 +77,7 @@ def test_neurite_volume_density(): 0.24068543213643726, 0.26289304906104355, ] - assert_allclose(vol_density, ref_density) + assert_allclose(vol_density, ref_density, rtol=1e-6) def test_neurite_volume_density_failed_convex_hull(): @@ -215,7 +215,7 @@ def test_segment_areas(): def test_segment_volumes(): expected = [[15.70796327, 5.23598776, 6.28318531], [12.56637061, 6.28318531, 5.23598776]] result = [neurite.segment_volumes(s) for s in SIMPLE.neurites] - assert_allclose(result, expected) + assert_allclose(result, expected, rtol=1e-6) def test_segment_midpoints(): diff --git a/tests/view/test_matplotlib_impl.py b/tests/view/test_matplotlib_impl.py index 810a5c1a..27cb4268 100644 --- a/tests/view/test_matplotlib_impl.py +++ b/tests/view/test_matplotlib_impl.py @@ -28,8 +28,10 @@ import warnings from io import StringIO from pathlib import Path +from packaging.version import parse import numpy as np +import matplotlib from neurom import load_morphology from neurom.core.types import NeuriteType from neurom.view import matplotlib_utils, matplotlib_impl @@ -112,7 +114,14 @@ def test_tree3d(get_fig_3d): xy_bounds = ax.xy_dataLim.bounds np.testing.assert_allclose(xy_bounds, (-5.0, 0.0, 11.0, 5.0)) zz_bounds = ax.zz_dataLim.bounds - np.testing.assert_allclose(zz_bounds, (0.0, 0.0, 1.0, 1.0)) + + # auto scaling of data limits was fixed for LineCollection3D + # in 3.9.1+ fixing the previously wrong bbox limits + # See: https://github.com/matplotlib/matplotlib/pull/28403 + if parse(matplotlib.__version__) < parse("3.9.1"): + np.testing.assert_allclose(zz_bounds, (0.0, 0.0, 1.0, 1.0)) + else: + np.testing.assert_allclose(zz_bounds, (0.0, 0.0, 0.0, 1.0)) def test_morph3d(get_fig_3d): diff --git a/tox.ini b/tox.ini index ca60be8e..89c07dd9 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ envlist = coverage tutorial check-packaging - py{38,39,310,311,312} + py{38,39,310,311,312,313} [testenv] deps = @@ -102,3 +102,4 @@ python = 3.10: py310, tutorial 3.11: py311, check-packaging 3.12: py312 + 3.13: py313