From 2ef100939c62763f1e531b5bf3090b4ea1464028 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 12 Sep 2025 11:21:25 +0200 Subject: [PATCH 01/28] Update __init__.py --- deeptrack/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeptrack/__init__.py b/deeptrack/__init__.py index 189550bb5..a118ad33d 100644 --- a/deeptrack/__init__.py +++ b/deeptrack/__init__.py @@ -24,6 +24,7 @@ # Create a unit registry with custom pixel-related units. units_registry = UnitRegistry(pint_definitions.split("\n")) +units = units_registry # Alias for backward compatibility from deeptrack.backend import * From 5d45a73b350cdc9b5e6bce5e48031330cf2bdb15 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 12 Sep 2025 18:29:04 +0200 Subject: [PATCH 02/28] Create test_dlcc.py --- deeptrack/tests/test_dlcc.py | 124 +++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 deeptrack/tests/test_dlcc.py diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py new file mode 100644 index 000000000..ad73608bd --- /dev/null +++ b/deeptrack/tests/test_dlcc.py @@ -0,0 +1,124 @@ +# pylint: disable=C0115:missing-class-docstring +# pylint: disable=C0116:missing-function-docstring +# pylint: disable=C0103:invalid-name + +# Use this only when running the test locally. +# import sys +# sys.path.append(".") # Adds the module to path. + +import unittest + +import deeptrack as dt +import numpy as np +from numpy.random import Generator, PCG64 + +class TestDLCC(unittest.TestCase): + + def test_3B(self): + ## PART 1 + # Image pipeline with reproducible randomness. + rng = Generator(PCG64(42)) + + image_size = 3 + + particle = dt.scatterers.MieSphere( + position=lambda: rng.uniform(image_size / 2 - 5, + image_size / 2 + 5, 2), + z=lambda: rng.uniform(-1, 1), + radius=lambda: rng.uniform(500, 600) * 1e-9, + refractive_index=lambda: rng.uniform(1.37, 1.42), + position_unit="pixel", + ) + + brightfield_microscope = dt.optics.Brightfield( + wavelength=630e-9, + NA=0.8, + resolution=1e-6, + magnification=15, + refractive_index_medium=1.33, + output_region=(0, 0, image_size, image_size), + ) + + imaged_particle = brightfield_microscope(particle) + + # First resolve + expected_first = np.array( + [[[0.84823867], [0.84193078], [0.85179172]], + [[0.80131109], [0.79478238], [0.80499449]], + [[0.76094944], [0.75431347], [0.76469805]]] + ) + actual_first = imaged_particle() + np.testing.assert_allclose(actual_first, expected_first, + rtol=1e-7, atol=1e-7) + + # No change when resolving again + np.testing.assert_allclose(imaged_particle(), expected_first, + rtol=1e-7, atol=1e-7) + + # Change after update + expected_second = np.array( + [[[1.02220767], [0.98545219], [0.95156275]], + [[0.98707199], [0.94484236], [0.90718955]], + [[0.94221739], [0.89565235], [0.85521306]]] + ) + actual_second = imaged_particle.update()() + np.testing.assert_allclose(actual_second, expected_second, + rtol=1e-7, atol=1e-7) + + ## PART 2 + # Poisson uses np.random so no randomness reproducibility, + # so images are not reproducible. + noise = dt.Poisson( + min_snr=5, + max_snr=20, + background=1, + snr=lambda min_snr, max_snr: rng.uniform(min_snr, max_snr), + ) + noisy_imaged_particle = imaged_particle >> noise + + normalization = dt.NormalizeMinMax( + lambda: rng.uniform(0.0, 0.2), + lambda: rng.uniform(0.8, 1.0), + ) + image_pipeline = noisy_imaged_particle >> normalization + + # First resolve + actual_first = image_pipeline() + + # No change when resolving again + np.testing.assert_allclose(image_pipeline(), actual_first, + rtol=1e-7, atol=1e-7) + + # Change after update + actual_second = image_pipeline.update()() + assert float(np.max(np.abs(actual_first - actual_second))) > 1e-6 + + ## PART 3 + # Images are not reproducible becaus eof Poisson, but positions are. + rng = Generator(PCG64(42)) + + pipeline = image_pipeline & particle.position + + # First resolve + _, actual_position_first = pipeline.update()() + + expected_position_first = np.array([4.23956049, 0.8887844 ]) + np.testing.assert_allclose(actual_position_first, + expected_position_first, + rtol=1e-7, atol=1e-7) + + # No change when resolving again + _, actual_position_first_2 = pipeline() + np.testing.assert_allclose(actual_position_first_2, + expected_position_first, + rtol=1e-7, atol=1e-7) + + # Change after update + expected_position_second = np.array([-2.21886367, 1.00385938]) + _, actual_position_second = pipeline.update()() + np.testing.assert_allclose(actual_position_second, + expected_position_second, + rtol=1e-7, atol=1e-7) + +if __name__ == "__main__": + unittest.main() From e485fd629391048e118b789ecbe0b5c193b3173f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 13 Sep 2025 15:39:22 +0200 Subject: [PATCH 03/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 156 ++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index ad73608bd..690ab0b26 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -11,10 +11,11 @@ import deeptrack as dt import numpy as np from numpy.random import Generator, PCG64 +import torch class TestDLCC(unittest.TestCase): - def test_3B(self): + def test_3_B(self): ## PART 1 # Image pipeline with reproducible randomness. rng = Generator(PCG64(42)) @@ -120,5 +121,158 @@ def test_3B(self): expected_position_second, rtol=1e-7, atol=1e-7) + def test_4_1(self): + ## PART 1 + # Deterministic pipeline. + + particle = dt.Sphere( + position=np.array([0.5, 0.5]) * 4, + position_unit="pixel", + radius=500 * dt.units.nm, + refractive_index=1.45 + 0.02j, + ) + + brightfield_microscope = dt.Brightfield( + wavelength=500 * dt.units.nm, + NA=1.0, + resolution=1 * dt.units.um, + magnification=10, + refractive_index_medium=1.33, + output_region=(0, 0, 4, 4), + ) + + illuminated_sample = brightfield_microscope(particle) + + # First resolve + expected_first = np.array( + [[[0.55382582], [0.55944586], [0.54341977], [0.55944587]], + [[0.55944586], [0.48773998], [0.43907532], [0.48773999]], + [[0.54341977], [0.43907532], [0.37978135], [0.43907532]], + [[0.55944587], [0.48773999], [0.43907532], [0.48773999]]] + ) + actual_first = illuminated_sample() + np.testing.assert_allclose(actual_first, expected_first, + rtol=1e-7, atol=1e-7) + + # No change when resolving again + np.testing.assert_allclose(illuminated_sample(), expected_first, + rtol=1e-7, atol=1e-7) + + # No change also after update (deterministic pipeline) + np.testing.assert_allclose(illuminated_sample.update()(), + expected_first, + rtol=1e-7, atol=1e-7) + + ## PART 2 + # Non-reproducible randomness for noisy_particle. + + clean_particle = ( + illuminated_sample + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + noise = dt.Poisson(snr=lambda: 2.0 + np.random.rand()) + + noisy_particle = ( + illuminated_sample >> noise + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + pip = noisy_particle & clean_particle + + # First resolve + expected_clean_first = torch.tensor( + [[[0.9687, 1.0000, 0.9108, 1.0000], + [1.0000, 0.6009, 0.3300, 0.6009], + [0.9108, 0.3300, 0.0000, 0.3300], + [1.0000, 0.6009, 0.3300, 0.6009]]] + ) + actual_noisy_first, actual_clean_first = pip() + torch.testing.assert_close(actual_clean_first, expected_clean_first, + rtol=1e-7, atol=1e-4) + + # No change after resolving again + actual_noisy_first_2, actual_clean_first_2 = pip() + torch.testing.assert_close(actual_clean_first_2, expected_clean_first, + rtol=1e-7, atol=1e-4) + torch.testing.assert_close(actual_noisy_first_2, actual_noisy_first, + rtol=1e-7, atol=1e-4) + + # No change for clean also after update (deterministic pipeline), + # but change for noisy + actual_noisy_second, actual_clean_second = pip.update().resolve() + torch.testing.assert_close(actual_clean_second, expected_clean_first, + rtol=1e-7, atol=1e-4) + assert not torch.allclose( + actual_noisy_first, actual_noisy_second, rtol=1e-7, atol=1e-4 + ) + + ## PART 3 + # Verify generation of blank image. + + blank = brightfield_microscope(particle ^ 0) + blank_pip = ( + blank # >> noise >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + expected = torch.tensor( + [[[1., 1., 1., 1.], + [1., 1., 1., 1.], + [1., 1., 1., 1.], + [1., 1., 1., 1.]]] + ) + torch.testing.assert_close(blank_pip(), expected, + rtol=1e-7, atol=1e-4) + + ## PART 4 + # Check diverse particle pipeline. + + diverse_particle = dt.Sphere( + position=lambda: np.array([.2, .2] + np.random.rand(2) * .6) * 4, + radius=lambda: 500 * dt.units.nm * (1 + np.random.rand()), + position_unit="pixel", + refractive_index=1.45 + 0.02j, + ) + diverse_illuminated_sample = brightfield_microscope(diverse_particle) + diverse_clean_particle = ( + diverse_illuminated_sample + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + diverse_noisy_particle = ( + diverse_illuminated_sample + >> noise + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + diverse_pip = diverse_noisy_particle & diverse_clean_particle + + # First resolve + diverse_noisy_first, diverse_clean_first = diverse_pip() + + # Idempotent without update() + diverse_noisy_first_2, diverse_clean_first_2 = diverse_pip() + torch.testing.assert_close(diverse_clean_first_2, diverse_clean_first, + rtol=1e-7, atol=1e-4) + torch.testing.assert_close(diverse_noisy_first_2, diverse_noisy_first, + rtol=1e-7, atol=1e-4) + + # After update(), BOTH should change (geometry + noise) + diverse_noisy_second, diverse_clean_second = \ + diverse_pip.update().resolve() + assert not torch.allclose(diverse_clean_second, diverse_clean_first, + rtol=1e-7, atol=1e-4) + assert not torch.allclose(diverse_noisy_second, diverse_noisy_first, + rtol=1e-7, atol=1e-4) + + if __name__ == "__main__": unittest.main() From 453ab965bc1946d7dab93c9485d81694493e3b9b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 13 Sep 2025 15:44:53 +0200 Subject: [PATCH 04/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 217 +++++++++++++++++++---------------- 1 file changed, 115 insertions(+), 102 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 690ab0b26..355fa4321 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -9,9 +9,12 @@ import unittest import deeptrack as dt +from deeptrack import TORCH_AVAILABLE import numpy as np from numpy.random import Generator, PCG64 -import torch + +if TORCH_AVAILABLE: + import torch class TestDLCC(unittest.TestCase): @@ -165,113 +168,123 @@ def test_4_1(self): ## PART 2 # Non-reproducible randomness for noisy_particle. - - clean_particle = ( - illuminated_sample - >> dt.NormalizeMinMax() - >> dt.MoveAxis(2, 0) - >> dt.pytorch.ToTensor(dtype=torch.float) - ) - - noise = dt.Poisson(snr=lambda: 2.0 + np.random.rand()) - - noisy_particle = ( - illuminated_sample >> noise - >> dt.NormalizeMinMax() - >> dt.MoveAxis(2, 0) - >> dt.pytorch.ToTensor(dtype=torch.float) - ) - - pip = noisy_particle & clean_particle - - # First resolve - expected_clean_first = torch.tensor( - [[[0.9687, 1.0000, 0.9108, 1.0000], - [1.0000, 0.6009, 0.3300, 0.6009], - [0.9108, 0.3300, 0.0000, 0.3300], - [1.0000, 0.6009, 0.3300, 0.6009]]] - ) - actual_noisy_first, actual_clean_first = pip() - torch.testing.assert_close(actual_clean_first, expected_clean_first, - rtol=1e-7, atol=1e-4) - - # No change after resolving again - actual_noisy_first_2, actual_clean_first_2 = pip() - torch.testing.assert_close(actual_clean_first_2, expected_clean_first, - rtol=1e-7, atol=1e-4) - torch.testing.assert_close(actual_noisy_first_2, actual_noisy_first, - rtol=1e-7, atol=1e-4) - - # No change for clean also after update (deterministic pipeline), - # but change for noisy - actual_noisy_second, actual_clean_second = pip.update().resolve() - torch.testing.assert_close(actual_clean_second, expected_clean_first, - rtol=1e-7, atol=1e-4) - assert not torch.allclose( - actual_noisy_first, actual_noisy_second, rtol=1e-7, atol=1e-4 - ) + if TORCH_AVAILABLE: + clean_particle = ( + illuminated_sample + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + noise = dt.Poisson(snr=lambda: 2.0 + np.random.rand()) + + noisy_particle = ( + illuminated_sample >> noise + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + pip = noisy_particle & clean_particle + + # First resolve + expected_clean_first = torch.tensor( + [[[0.9687, 1.0000, 0.9108, 1.0000], + [1.0000, 0.6009, 0.3300, 0.6009], + [0.9108, 0.3300, 0.0000, 0.3300], + [1.0000, 0.6009, 0.3300, 0.6009]]] + ) + actual_noisy_first, actual_clean_first = pip() + torch.testing.assert_close(actual_clean_first, + expected_clean_first, + rtol=1e-7, atol=1e-4) + + # No change after resolving again + actual_noisy_first_2, actual_clean_first_2 = pip() + torch.testing.assert_close(actual_clean_first_2, + expected_clean_first, + rtol=1e-7, atol=1e-4) + torch.testing.assert_close(actual_noisy_first_2, + actual_noisy_first, + rtol=1e-7, atol=1e-4) + + # No change for clean also after update (deterministic pipeline), + # but change for noisy + actual_noisy_second, actual_clean_second = pip.update().resolve() + torch.testing.assert_close(actual_clean_second, + expected_clean_first, + rtol=1e-7, atol=1e-4) + assert not torch.allclose( + actual_noisy_first, actual_noisy_second, rtol=1e-7, atol=1e-4 + ) ## PART 3 # Verify generation of blank image. - - blank = brightfield_microscope(particle ^ 0) - blank_pip = ( - blank # >> noise >> dt.NormalizeMinMax() - >> dt.MoveAxis(2, 0) - >> dt.pytorch.ToTensor(dtype=torch.float) - ) - - expected = torch.tensor( - [[[1., 1., 1., 1.], - [1., 1., 1., 1.], - [1., 1., 1., 1.], - [1., 1., 1., 1.]]] - ) - torch.testing.assert_close(blank_pip(), expected, - rtol=1e-7, atol=1e-4) + if TORCH_AVAILABLE: + blank = brightfield_microscope(particle ^ 0) + blank_pip = ( + blank # >> noise >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + expected = torch.tensor( + [[[1., 1., 1., 1.], + [1., 1., 1., 1.], + [1., 1., 1., 1.], + [1., 1., 1., 1.]]] + ) + torch.testing.assert_close(blank_pip(), expected, + rtol=1e-7, atol=1e-4) ## PART 4 # Check diverse particle pipeline. - - diverse_particle = dt.Sphere( - position=lambda: np.array([.2, .2] + np.random.rand(2) * .6) * 4, - radius=lambda: 500 * dt.units.nm * (1 + np.random.rand()), - position_unit="pixel", - refractive_index=1.45 + 0.02j, - ) - diverse_illuminated_sample = brightfield_microscope(diverse_particle) - diverse_clean_particle = ( - diverse_illuminated_sample - >> dt.NormalizeMinMax() - >> dt.MoveAxis(2, 0) - >> dt.pytorch.ToTensor(dtype=torch.float) - ) - diverse_noisy_particle = ( - diverse_illuminated_sample - >> noise - >> dt.NormalizeMinMax() - >> dt.MoveAxis(2, 0) - >> dt.pytorch.ToTensor(dtype=torch.float) - ) - diverse_pip = diverse_noisy_particle & diverse_clean_particle - - # First resolve - diverse_noisy_first, diverse_clean_first = diverse_pip() - - # Idempotent without update() - diverse_noisy_first_2, diverse_clean_first_2 = diverse_pip() - torch.testing.assert_close(diverse_clean_first_2, diverse_clean_first, - rtol=1e-7, atol=1e-4) - torch.testing.assert_close(diverse_noisy_first_2, diverse_noisy_first, - rtol=1e-7, atol=1e-4) - - # After update(), BOTH should change (geometry + noise) - diverse_noisy_second, diverse_clean_second = \ - diverse_pip.update().resolve() - assert not torch.allclose(diverse_clean_second, diverse_clean_first, - rtol=1e-7, atol=1e-4) - assert not torch.allclose(diverse_noisy_second, diverse_noisy_first, - rtol=1e-7, atol=1e-4) + if TORCH_AVAILABLE: + diverse_particle = dt.Sphere( + position=lambda: np.array([.2, .2] + + np.random.rand(2) * .6) * 4, + radius=lambda: 500 * dt.units.nm * (1 + np.random.rand()), + position_unit="pixel", + refractive_index=1.45 + 0.02j, + ) + diverse_illuminated_sample = \ + brightfield_microscope(diverse_particle) + diverse_clean_particle = ( + diverse_illuminated_sample + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + diverse_noisy_particle = ( + diverse_illuminated_sample + >> noise + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + diverse_pip = diverse_noisy_particle & diverse_clean_particle + + # First resolve + diverse_noisy_first, diverse_clean_first = diverse_pip() + + # Idempotent without update() + diverse_noisy_first_2, diverse_clean_first_2 = diverse_pip() + torch.testing.assert_close(diverse_clean_first_2, + diverse_clean_first, + rtol=1e-7, atol=1e-4) + torch.testing.assert_close(diverse_noisy_first_2, + diverse_noisy_first, + rtol=1e-7, atol=1e-4) + + # After update(), BOTH should change (geometry + noise) + diverse_noisy_second, diverse_clean_second = \ + diverse_pip.update().resolve() + assert not torch.allclose(diverse_clean_second, + diverse_clean_first, + rtol=1e-7, atol=1e-4) + assert not torch.allclose(diverse_noisy_second, + diverse_noisy_first, + rtol=1e-7, atol=1e-4) if __name__ == "__main__": From cc8967d3236bb528dc8294604f153a743fb6617e Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 13 Sep 2025 15:53:09 +0200 Subject: [PATCH 05/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 355fa4321..535949559 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -193,7 +193,8 @@ def test_4_1(self): [1.0000, 0.6009, 0.3300, 0.6009], [0.9108, 0.3300, 0.0000, 0.3300], [1.0000, 0.6009, 0.3300, 0.6009]]] - ) + ).to(device=actual_clean_first.device, + dtype=actual_clean_first.dtype) actual_noisy_first, actual_clean_first = pip() torch.testing.assert_close(actual_clean_first, expected_clean_first, @@ -214,9 +215,9 @@ def test_4_1(self): torch.testing.assert_close(actual_clean_second, expected_clean_first, rtol=1e-7, atol=1e-4) - assert not torch.allclose( + self.assertFalse(torch.allclose( actual_noisy_first, actual_noisy_second, rtol=1e-7, atol=1e-4 - ) + )) ## PART 3 # Verify generation of blank image. @@ -279,12 +280,15 @@ def test_4_1(self): # After update(), BOTH should change (geometry + noise) diverse_noisy_second, diverse_clean_second = \ diverse_pip.update().resolve() - assert not torch.allclose(diverse_clean_second, - diverse_clean_first, - rtol=1e-7, atol=1e-4) - assert not torch.allclose(diverse_noisy_second, - diverse_noisy_first, - rtol=1e-7, atol=1e-4) + self.assertFalse(torch.allclose( + diverse_clean_second, diverse_clean_first, rtol=1e-7, atol=1e-4 + )) + self.assertFalse(torch.allclose( + diverse_noisy_second, diverse_noisy_first, rtol=1e-7, atol=1e-4 + )) + + def test_4_A(self): + pass if __name__ == "__main__": From 036b9a08ec5fe20dbd0cc042f8dfbf23107a912c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 13 Sep 2025 15:53:41 +0200 Subject: [PATCH 06/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 535949559..6cb054d62 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -188,6 +188,7 @@ def test_4_1(self): pip = noisy_particle & clean_particle # First resolve + actual_noisy_first, actual_clean_first = pip() expected_clean_first = torch.tensor( [[[0.9687, 1.0000, 0.9108, 1.0000], [1.0000, 0.6009, 0.3300, 0.6009], @@ -195,7 +196,6 @@ def test_4_1(self): [1.0000, 0.6009, 0.3300, 0.6009]]] ).to(device=actual_clean_first.device, dtype=actual_clean_first.dtype) - actual_noisy_first, actual_clean_first = pip() torch.testing.assert_close(actual_clean_first, expected_clean_first, rtol=1e-7, atol=1e-4) From a2a388136b35cf1e0434f595beb949a1bb99304e Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 13 Sep 2025 18:53:15 +0200 Subject: [PATCH 07/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 132 ++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 3 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 6cb054d62..e27bedd08 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -8,14 +8,21 @@ import unittest -import deeptrack as dt -from deeptrack import TORCH_AVAILABLE +import shutil +import tempfile +from pathlib import Path + import numpy as np from numpy.random import Generator, PCG64 +from PIL import Image + +import deeptrack as dt +from deeptrack import TORCH_AVAILABLE if TORCH_AVAILABLE: import torch + class TestDLCC(unittest.TestCase): def test_3_B(self): @@ -288,7 +295,126 @@ def test_4_1(self): )) def test_4_A(self): - pass + if TORCH_AVAILABLE: + # Temporary root (deleted in finally) + tmp_root = tempfile.mkdtemp(prefix="mnist_like_") + data_root = Path(tmp_root) / "mnist" + train_dir = data_root / "train" + test_dir = data_root / "test" + + try: + train_dir.mkdir(parents=True, exist_ok=True) + test_dir.mkdir(parents=True, exist_ok=True) + + H, W = 8, 8 + + # Non-uniform patterns + grad = np.linspace(0, 255, H*W, dtype=np.uint8).reshape(H, W) + checker = ( + (np.indices((H, W)).sum(axis=0) % 2) * 255 + ).astype(np.uint8) + grad_inv = (255 - grad).astype(np.uint8) + stripes = np.tile( + ((np.arange(W) % 2) * 255).astype(np.uint8), (H, 1) + ) + + # Save 2 grayscale images in train/ + Image.fromarray(grad, mode="L") \ + .save(train_dir / "0_train.png") + Image.fromarray(checker, mode="L") \ + .save(train_dir / "1_train.png") + + # Save 2 grayscale images in test/ + Image.fromarray(grad_inv, mode="L") \ + .save(test_dir / "0_test.png") + Image.fromarray(stripes, mode="L") \ + .save(test_dir / "1_test.png") + + # print("Data root:", data_root) + # print("Train files:", sorted(os.listdir(train_dir))) + # print("Test files:", sorted(os.listdir(test_dir))) + + ## PART 1 + # Loading image files into a pipeline. + + train_files = dt.sources.ImageFolder(root=str(train_dir)) + test_files = dt.sources.ImageFolder(root=str(test_dir)) + files = dt.sources.Join(train_files, test_files) + + assert len(train_files) == 2 + assert len(test_files) == 2 + + image_pip = ( + dt.LoadImage(files.path) + >> dt.NormalizeMinMax() + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + train_dataset = dt.pytorch.Dataset( + image_pip & image_pip, + inputs=train_files, + ) + + # Get images + x_a, x_b = train_dataset[0] # Tensors, identical content + assert isinstance(x_a, torch.Tensor) + assert isinstance(x_b, torch.Tensor) + assert x_a.shape == x_b.shape + assert torch.equal(x_a, x_b) + assert len(train_dataset) == len(train_files) == 2 + assert x_a.ndim == 3 and x_a.shape[1:] == (H, W) + assert x_a.dtype == torch.float32 + assert 0.0 <= float(x_a.min()) <= float(x_a.max()) <= 1.0 + + # With DataLoader + loader = torch.utils.data.DataLoader( + train_dataset, batch_size=2, shuffle=False, + ) + for xa, xb in loader: + assert xa.shape == xb.shape + assert xa.ndim == 4 \ + and xa.shape[1:] == x_a.shape # (B,C,H,W) + + ## PART 2 + # Test dataset with label pipelines. + + label_pip = dt.Value(files.label_name[0]) >> int + test_dataset = dt.pytorch.Dataset( + image_pip & label_pip, inputs=test_files + ) + + assert len(test_dataset) == len(test_files) == 2 + + x0, y0 = test_dataset[0] + assert isinstance(x0, torch.Tensor) + assert x0.ndim == 3 and x0.shape[1:] == (H, W) + assert 0.0 <= float(x0.min()) <= float(x0.max()) <= 1.0 + assert isinstance(y0, torch.Tensor) + assert y0.ndim == 1 and y0.shape == (1,) + + # Same index is idempotent + x0b, y0b = test_dataset[0] + torch.testing.assert_close(x0b, x0, rtol=0.0, atol=0.0) + assert y0b == y0 + + # Check we see both labels {0,1} across the dataset + labels = [test_dataset[i][1] for i in range(len(test_dataset))] + assert labels == [torch.tensor([0]), torch.tensor([1])] + + # DataLoader sanity + test_loader = torch.utils.data.DataLoader( + test_dataset, batch_size=2, shuffle=False + ) + xb, yb = next(iter(test_loader)) + assert xb.ndim == 4 and xb.shape[1:] == x0.shape # (B,C,H,W) + assert yb.ndim == 2 and yb.shape[0] == xb.shape[0] + + except Exception as e: + print("Error while creating dataset:", e) + finally: + # Clean up the temporary dataset tree + shutil.rmtree(tmp_root, ignore_errors=True) if __name__ == "__main__": From 8bd2fc436613d8966de9797263bf03fccade3d10 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 13 Sep 2025 19:01:21 +0200 Subject: [PATCH 08/28] Update __init__.py --- deeptrack/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deeptrack/__init__.py b/deeptrack/__init__.py index a118ad33d..189550bb5 100644 --- a/deeptrack/__init__.py +++ b/deeptrack/__init__.py @@ -24,7 +24,6 @@ # Create a unit registry with custom pixel-related units. units_registry = UnitRegistry(pint_definitions.split("\n")) -units = units_registry # Alias for backward compatibility from deeptrack.backend import * From f972259a4a33fb23d4330003174aba8d33417de5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 13 Sep 2025 19:03:40 +0200 Subject: [PATCH 09/28] Update __init__.py --- deeptrack/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeptrack/__init__.py b/deeptrack/__init__.py index 189550bb5..a118ad33d 100644 --- a/deeptrack/__init__.py +++ b/deeptrack/__init__.py @@ -24,6 +24,7 @@ # Create a unit registry with custom pixel-related units. units_registry = UnitRegistry(pint_definitions.split("\n")) +units = units_registry # Alias for backward compatibility from deeptrack.backend import * From a2901f8016f4ebcbd8dff1a379c1af4cf726481a Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 18 Sep 2025 22:28:20 +0200 Subject: [PATCH 10/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 75 ++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index e27bedd08..092055d69 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -416,6 +416,81 @@ def test_4_A(self): # Clean up the temporary dataset tree shutil.rmtree(tmp_root, ignore_errors=True) + def test_4_B(self): + pass # Essentially same code as test_4_A + + def test_4_C(self): + if TORCH_AVAILABLE: + ## PART 1 + # Load dataframe. + from io import StringIO + import pandas as pd + + csv_data = ( + "-0.1,-2.8,-3.7,-4.3,-4.3,-3.4,-2.1,-1.8,-1.2,-0.2,-0.3,1.0\n" + "-1.1,-3.9,-4.2,-4.5,-4.0,-3.2,-1.5,-0.9,0.04,0.26,0.64,0.0\n" + "-0.5,-2.5,-3.8,-4.5,-4.1,-3.1,-1.7,-1.1,-0.3,-0.0,-0.0,1.0\n" + "0.49,-1.9,-3.6,-4.3,-4.2,-3.8,-1.6,-1.3,-0.9,-0.6,-0.4,0.0\n" + "0.80,-0.8,-2.3,-3.9,-4.3,-2.5,-1.7,-1.5,-0.7,-0.5,-0.3,1.0\n" + "0.80,-0.8,-2.3,-3.9,-3.8,-2.5,-1.7,-1.5,-0.7,-0.5,-0.3,0.0\n" + "-0.1,-2.8,-3.7,-4.3,-3.4,-2.1,-1.8,-1.2,-0.4,-0.2,-0.3,1.0\n" + "-1.1,-3.9,-4.5,-4.0,-3.2,-1.5,-0.9,-0.7,0.04,0.26,0.64,0.0\n" + "-0.5,-3.8,-4.5,-4.1,-3.1,-1.7,-1.4,-1.1,-0.3,-0.0,-0.0,1.0\n" + "-1.9,-3.6,-4.3,-4.2,-3.8,-2.9,-1.6,-1.3,-0.9,-0.6,-0.4,0.0" + ) + + dataframe = pd.read_csv(StringIO(csv_data), header=None) + raw_data = dataframe.values + ecgs = raw_data[:, 1:-2] + labels = raw_data[:, -1].astype(bool) + + sources = dt.sources.Source(ecg=ecgs, is_normal=labels) + train_sources, test_sources = \ + dt.sources.random_split(sources, [0.8, 0.2]) + normal_sources = \ + train_sources.filter(lambda ecg, is_normal: is_normal) + + assert len(sources) == 10 + assert len(train_sources) == 8 + assert len(test_sources) == 2 + + ## PART 2 + # Instantiate and use pipeline. + min_normal = np.min([source["ecg"] for source in normal_sources]) + max_normal = np.max([source["ecg"] for source in normal_sources]) + + ecg_pip = ( + dt.Value(sources.ecg - min_normal) / (max_normal - min_normal) + >> dt.Unsqueeze(axis=0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + label_pip = dt.Value(sources.is_normal) + + # All normalized values should be between 0 and 1 + for i in range(len(sources)): + ecg = ecg_pip(sources[i]) + assert isinstance(ecg, torch.Tensor) + assert 0 <= ecg.min() <= 1 + + # All labels should be bool + for i in range(len(sources)): + label = label_pip(sources[i]) + assert not isinstance(label, torch.Tensor) + assert isinstance(label, (bool, np.bool_)) + + for source in sources: + ecg, label = (ecg_pip & label_pip)(source) + assert 0 <= ecg.min() <= 1 + assert isinstance(label, (bool, np.bool_)) + + # PART 3 + train_dataset = dt.pytorch.Dataset(ecg_pip & ecg_pip, + inputs=normal_sources) + loader = torch.utils.data.DataLoader(train_dataset, batch_size=2) + + for ecg_in, ecg_out in loader: + assert torch.equal(ecg_in, ecg_out) + if __name__ == "__main__": unittest.main() From 7abf76987bc3b8aa815f270d48b53f9e3447591a Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 12:09:03 +0700 Subject: [PATCH 11/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 092055d69..c50e8f51c 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -296,7 +296,7 @@ def test_4_1(self): def test_4_A(self): if TORCH_AVAILABLE: - # Temporary root (deleted in finally) + # Temporary root (deleted finally) tmp_root = tempfile.mkdtemp(prefix="mnist_like_") data_root = Path(tmp_root) / "mnist" train_dir = data_root / "train" @@ -410,8 +410,8 @@ def test_4_A(self): assert xb.ndim == 4 and xb.shape[1:] == x0.shape # (B,C,H,W) assert yb.ndim == 2 and yb.shape[0] == xb.shape[0] - except Exception as e: - print("Error while creating dataset:", e) + except Exception: + raise finally: # Clean up the temporary dataset tree shutil.rmtree(tmp_root, ignore_errors=True) @@ -491,6 +491,9 @@ def test_4_C(self): for ecg_in, ecg_out in loader: assert torch.equal(ecg_in, ecg_out) + def test_5_1(self): + pass + if __name__ == "__main__": unittest.main() From 927d9d7565b36fb7cef441f90237d39fd8f34818 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 12:49:18 +0700 Subject: [PATCH 12/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 114 ++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index c50e8f51c..ad36f1946 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -296,7 +296,7 @@ def test_4_1(self): def test_4_A(self): if TORCH_AVAILABLE: - # Temporary root (deleted finally) + # Temporary root (deleted in finally) tmp_root = tempfile.mkdtemp(prefix="mnist_like_") data_root = Path(tmp_root) / "mnist" train_dir = data_root / "train" @@ -492,7 +492,117 @@ def test_4_C(self): assert torch.equal(ecg_in, ecg_out) def test_5_1(self): - pass + def select_labels(class_labels): + """Create a function to filter and remap labels in ...""" + def inner(segmentation): + seg = segmentation.copy() + mask = seg * np.isin(seg, class_labels).astype(np.uint8) + new_seg = (np.select([mask == c for c in class_labels], + np.arange(len(class_labels)) + 1) + .astype(np.uint8).squeeze()) + one_hot_encoded_seg = np.eye(len(class_labels) + 1)[new_seg] + return one_hot_encoded_seg + return inner + + if TORCH_AVAILABLE: + # Temporary root (deleted in finally) + tmp_root = tempfile.mkdtemp(prefix="tissue_images_like_") + data_root = Path(tmp_root) / "stack1" + raw_dir = data_root / "raw" + labels_dir = data_root / "label" + + try: + raw_dir.mkdir(parents=True, exist_ok=True) + labels_dir.mkdir(parents=True, exist_ok=True) + + H, W = 8, 12 + values = np.array([0, 50, 100, 191, 200, 255], dtype=np.uint8) + + # Save 5 RGB images in raw/ + for i in range(5): + # Simple non-uniform pattern for RGB channels + r = np.tile(np.linspace(0, 255, W, dtype=np.uint8), (H, 1)) + g = np.tile(np.linspace(255, 0, W, dtype=np.uint8), (H, 1)) + b = np.full((H, W), i * 50, dtype=np.uint8) + img_rgb = np.stack([r, g, b], axis=-1) + Image.fromarray(img_rgb, mode="RGB") \ + .save(raw_dir / f"rgb_{i}.png") + + # Save 5 grayscale images in raw/ + for i in range(5): + # Fill image cycling through values, then shift by i + arr = np.tile(values, H * W // len(values) + 1)[: H * W] + arr = np.roll(arr, i) # shift pattern + arr = arr.reshape(H, W) + Image.fromarray(arr, mode="L") \ + .save(labels_dir / f"gray_{i}.png") + + raw_path = str(raw_dir) + seg_path = str(labels_dir) + + ## PART 1 + # Loading image files into a pipeline. + raw_paths = dt.sources.ImageFolder(root=raw_path) + seg_paths = dt.sources.ImageFolder(root=seg_path) + paths = dt.sources.Source(raw=raw_paths, label=seg_paths) + train_paths, val_paths, test_paths = \ + dt.sources.random_split(paths, [0.6, 0.2, 0.2]) + + assert len(raw_paths) == 5 + assert len(seg_paths) == 5 + assert len(train_paths) == 3 + assert len(val_paths) == 1 + assert len(test_paths) == 1 + + train_srcs = train_paths.product( + flip_ud=[True, False], flip_lr=[True, False], + ) + val_srcs = val_paths.constants(flip_ud=False, flip_lr=False) + test_srcs = test_paths.constants(flip_ud=False, flip_lr=False) + + sources = dt.sources.Join(train_srcs, val_srcs, test_srcs) + + ## PART 2 + # Testing pipelines and select_labels function. + im_pip = dt.LoadImage(sources.raw.path) >> dt.NormalizeMinMax() + seg_pip = (dt.LoadImage(sources.label.path) + >> dt.Lambda(select_labels, class_labels=[255, 191])) + pip = ((im_pip & seg_pip) >> dt.FlipLR(sources.flip_lr) + >> dt.FlipUD(sources.flip_ud) >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float)) + + train_dataset = dt.pytorch.Dataset(pip, train_srcs) + val_dataset = dt.pytorch.Dataset(pip, val_srcs) + test_dataset = dt.pytorch.Dataset(pip, test_srcs) + + assert len(train_dataset) == 12 # 3 images * 4 augmentations + assert len(val_dataset) == 1 # No augmentations + assert len(test_dataset) == 1 # No augmentations + + for i in range(12): + im, seg = train_dataset[i] + assert im.min() >= 0.0 and im.max() <= 1.0 + assert seg.ndim == 3 # (num_classes, H, W) + assert seg.shape[0] == 3 # 3 channels + assert set(np.unique(seg)).issubset({0, 1}) + + im, seg = val_dataset[0] + assert im.min() >= 0.0 and im.max() <= 1.0 + assert seg.ndim == 3 # (num_classes, H, W) + assert seg.shape[0] == 3 # 3 channels + assert set(np.unique(seg)).issubset({0, 1}) + + im, seg = test_dataset[0] + assert im.min() >= 0.0 and im.max() <= 1.0 + assert seg.ndim == 3 # (num_classes, H, W) + assert seg.shape[0] == 3 # 3 channels + assert set(np.unique(seg)).issubset({0, 1}) + + except Exception: + raise + finally: + # Clean up the temporary dataset tree + shutil.rmtree(tmp_root, ignore_errors=True) if __name__ == "__main__": From 4ac1618edba1e35f078d2b3bdaaf73f5df0bbc46 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 13:37:54 +0700 Subject: [PATCH 13/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index ad36f1946..9afb06875 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -604,6 +604,47 @@ def inner(segmentation): # Clean up the temporary dataset tree shutil.rmtree(tmp_root, ignore_errors=True) + def test_5_A(self): + ## PART 1 + # Single quantum dot. + + optics = dt.Fluorescence( + wavelength=600 * dt.units.nm, + NA=0.9, + magnification=1, + resolution=0.1 * dt.units.um, + output_region=(0, 0, 3, 3), + ) + particle = dt.PointParticle( + position=(2, 2), + intensity=1.2e4, + z=0, + ) + sim_im_pip = ( + optics(particle) + >> dt.Add(30) + >> dt.Add(82) + ) + + expected = np.array([ + [[ 7.01659596], [16.36273566], [21.04978789]], + [[16.36273566], [32.64875852], [40.41116424]], + [[21.04978789], [40.41116424], [49.51533565]] + ]) + 30 + 82 + np.testing.assert_allclose(sim_im_pip(), expected, + rtol=1e-7, atol=1e-7) + np.testing.assert_allclose(sim_im_pip.update()(), expected, + rtol=1e-7, atol=1e-7) + + np.random.seed(123) + sim_im_pip = ( + optics(particle) + >> dt.Add(30) + >> np.random.poisson + >> dt.Add(82) + ) + assert np.random.poisson(100) == 106 + if __name__ == "__main__": unittest.main() From f2b5039de49b17969c3a902b1a60f416c44dcb5d Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 13:43:07 +0700 Subject: [PATCH 14/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 9afb06875..6477da250 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -626,24 +626,32 @@ def test_5_A(self): >> dt.Add(82) ) - expected = np.array([ - [[ 7.01659596], [16.36273566], [21.04978789]], - [[16.36273566], [32.64875852], [40.41116424]], - [[21.04978789], [40.41116424], [49.51533565]] - ]) + 30 + 82 + expected = np.array( + [[[ 7.01659596], [16.36273566], [21.04978789]], + [[16.36273566], [32.64875852], [40.41116424]], + [[21.04978789], [40.41116424], [49.51533565]]] + ) + 30 + 82 np.testing.assert_allclose(sim_im_pip(), expected, rtol=1e-7, atol=1e-7) np.testing.assert_allclose(sim_im_pip.update()(), expected, rtol=1e-7, atol=1e-7) - np.random.seed(123) + np.random.seed(123) # Note that this seeding is not warratied + # to give reproducible results across platforms + # so the subsequent test might fail sim_im_pip = ( optics(particle) >> dt.Add(30) >> np.random.poisson >> dt.Add(82) ) - assert np.random.poisson(100) == 106 + expected = np.array( + [[[123], [122],[138]], + [[128], [141], [151]], + [[131], [144], [162]]] + ) + # This test might fail (see above) + np.testing.assert_array_equal(sim_im_pip(), expected) if __name__ == "__main__": From 5f42b650f271947032196dc132b7f61de4d1666f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 14:01:16 +0700 Subject: [PATCH 15/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 133 +++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 6477da250..82b4bb04e 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -653,6 +653,139 @@ def test_5_A(self): # This test might fail (see above) np.testing.assert_array_equal(sim_im_pip(), expected) + ## PART 2 + # Multiple quantum dots. + + np.random.seed(123) # Note that this seeding is not warratied + # to give reproducible results across platforms + # so the subsequent test might fail + + optics = dt.Fluorescence( + wavelength=600 * dt.units.nm, + NA=0.9, + magnification=1, + resolution=0.1 * dt.units.um, + output_region=(0, 0, 8, 8), + ) + particle = dt.PointParticle( + position=lambda: np.random.uniform(0, 8, size=2), + intensity=lambda: np.random.uniform(6e3, 3e4), + z=lambda: np.random.uniform(-1.5, 1.5) * dt.units.um, + ) + postprocess = ( + dt.Add(lambda: np.random.uniform(20, 40)) + >> np.random.poisson + >> dt.Add(lambda: np.random.uniform(70, 90)) + ) + normalization = ( + dt.AsType("float") + >> dt.Subtract(110) + >> dt.Divide(250) + ) + particles = particle ^ (lambda: np.random.randint(2, 5)) + sim_im_pip = ( + optics(particles) + >> postprocess + >> normalization + ) + + expected_1 = np.array( + [[[-0.04551237], [ 0.03848763], [ 0.11448763], [ 0.17048763], + [ 0.23448763], [ 0.23448763], [ 0.11048763], [ 0.11848763]], + [[ 0.02248763], [ 0.06648763], [ 0.14648763], [ 0.29448763], + [ 0.29448763], [ 0.35048763], [ 0.36648763], [ 0.24248763]], + [[ 0.00248763], [ 0.04648763], [ 0.19048763], [ 0.26248763], + [ 0.45848763], [ 0.50648763], [ 0.45848763], [ 0.29448763]], + [[-0.02151237], [ 0.00648763], [ 0.08248763], [ 0.19048763], + [ 0.30248763], [ 0.58648763], [ 0.50648763], [ 0.43048763]], + [[-0.01751237], [ 0.04248763], [ 0.04248763], [ 0.26248763], + [ 0.37048763], [ 0.50248763], [ 0.46648763], [ 0.41848763]], + [[-0.01751237], [ 0.09848763], [ 0.18248763], [ 0.31448763], + [ 0.23848763], [ 0.35048763], [ 0.29448763], [ 0.25048763]], + [[ 0.01048763], [ 0.05448763], [ 0.19448763], [ 0.27848763], + [ 0.30648763], [ 0.22648763], [ 0.10248763], [ 0.07448763]], + [[ 0.01848763], [-0.01351237], [ 0.12648763], [ 0.18648763], + [ 0.17848763], [ 0.16648763], [ 0.03448763], [ 0.02248763]]] + ) + np.testing.assert_allclose(sim_im_pip(), expected_1, + rtol=1e-7, atol=1e-7) + np.testing.assert_allclose(sim_im_pip(), expected_1, + rtol=1e-7, atol=1e-7) + + expected_2 = np.array([[[0.05257224], + [0.05257224], + [0.08457224], + [0.05657224], + [0.12057224], + [0.12057224], + [0.10857224], + [0.12057224]], + + [[0.13257224], + [0.13657224], + [0.12857224], + [0.12857224], + [0.06457224], + [0.06057224], + [0.15657224], + [0.19657224]], + + [[0.19257224], + [0.23657224], + [0.18057224], + [0.12057224], + [0.08857224], + [0.09657224], + [0.20057224], + [0.16057224]], + + [[0.26857224], + [0.25257224], + [0.30457224], + [0.17657224], + [0.13257224], + [0.14057224], + [0.25257224], + [0.26057224]], + + [[0.46457224], + [0.57657224], + [0.36857224], + [0.20857224], + [0.11657224], + [0.12057224], + [0.20457224], + [0.20457224]], + + [[0.51257224], + [0.50857224], + [0.40457224], + [0.28457224], + [0.18457224], + [0.20457224], + [0.18457224], + [0.20457224]], + + [[0.53657224], + [0.52457224], + [0.37257224], + [0.23657224], + [0.08057224], + [0.16057224], + [0.12457224], + [0.08457224]], + + [[0.29657224], + [0.28457224], + [0.29657224], + [0.21657224], + [0.11657224], + [0.11657224], + [0.08457224], + [0.08857224]]]) + np.testing.assert_allclose(sim_im_pip.update()(), expected_2, + rtol=1e-7, atol=1e-7) + if __name__ == "__main__": unittest.main() From a6925f99e564725f83a2dfc8fd5f236b39cf7b56 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 14:04:22 +0700 Subject: [PATCH 16/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 82b4bb04e..e2abd0d50 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -470,6 +470,7 @@ def test_4_C(self): for i in range(len(sources)): ecg = ecg_pip(sources[i]) assert isinstance(ecg, torch.Tensor) + print(ecg.min()) assert 0 <= ecg.min() <= 1 # All labels should be bool From 9f83b71ee63416be8a837e6e36e66f780d10f580 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 14:10:12 +0700 Subject: [PATCH 17/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 89 ++++++++---------------------------- 1 file changed, 18 insertions(+), 71 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index e2abd0d50..17bd385d8 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -713,77 +713,24 @@ def test_5_A(self): np.testing.assert_allclose(sim_im_pip(), expected_1, rtol=1e-7, atol=1e-7) - expected_2 = np.array([[[0.05257224], - [0.05257224], - [0.08457224], - [0.05657224], - [0.12057224], - [0.12057224], - [0.10857224], - [0.12057224]], - - [[0.13257224], - [0.13657224], - [0.12857224], - [0.12857224], - [0.06457224], - [0.06057224], - [0.15657224], - [0.19657224]], - - [[0.19257224], - [0.23657224], - [0.18057224], - [0.12057224], - [0.08857224], - [0.09657224], - [0.20057224], - [0.16057224]], - - [[0.26857224], - [0.25257224], - [0.30457224], - [0.17657224], - [0.13257224], - [0.14057224], - [0.25257224], - [0.26057224]], - - [[0.46457224], - [0.57657224], - [0.36857224], - [0.20857224], - [0.11657224], - [0.12057224], - [0.20457224], - [0.20457224]], - - [[0.51257224], - [0.50857224], - [0.40457224], - [0.28457224], - [0.18457224], - [0.20457224], - [0.18457224], - [0.20457224]], - - [[0.53657224], - [0.52457224], - [0.37257224], - [0.23657224], - [0.08057224], - [0.16057224], - [0.12457224], - [0.08457224]], - - [[0.29657224], - [0.28457224], - [0.29657224], - [0.21657224], - [0.11657224], - [0.11657224], - [0.08457224], - [0.08857224]]]) + expected_2 = np.array( + [[[0.05257224], [0.05257224], [0.08457224], [0.05657224], + [0.12057224], [0.12057224], [0.10857224], [0.12057224]], + [[0.13257224], [0.13657224], [0.12857224], [0.12857224], + [0.06457224], [0.06057224], [0.15657224], [0.19657224]], + [[0.19257224], [0.23657224], [0.18057224], [0.12057224], + [0.08857224], [0.09657224], [0.20057224], [0.16057224]], + [[0.26857224], [0.25257224], [0.30457224], [0.17657224], + [0.13257224], [0.14057224], [0.25257224], [0.26057224]], + [[0.46457224], [0.57657224], [0.36857224], [0.20857224], + [0.11657224], [0.12057224], [0.20457224], [0.20457224]], + [[0.51257224], [0.50857224], [0.40457224], [0.28457224], + [0.18457224], [0.20457224], [0.18457224], [0.20457224]], + [[0.53657224], [0.52457224], [0.37257224], [0.23657224], + [0.08057224], [0.16057224], [0.12457224], [0.08457224]], + [[0.29657224], [0.28457224], [0.29657224], [0.21657224], + [0.11657224], [0.11657224], [0.08457224], [0.08857224]]] + ) np.testing.assert_allclose(sim_im_pip.update()(), expected_2, rtol=1e-7, atol=1e-7) From 26a00977fbeaffa57f76915392eca987d2ae5c03 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 14:15:15 +0700 Subject: [PATCH 18/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 17bd385d8..532cf9ab6 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -467,10 +467,9 @@ def test_4_C(self): label_pip = dt.Value(sources.is_normal) # All normalized values should be between 0 and 1 - for i in range(len(sources)): - ecg = ecg_pip(sources[i]) + for i in range(len(normal_sources)): + ecg = ecg_pip(normal_sources[i]) assert isinstance(ecg, torch.Tensor) - print(ecg.min()) assert 0 <= ecg.min() <= 1 # All labels should be bool From b5fa992266b5b41c026e901a5b3e42526a49a24e Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 14:23:14 +0700 Subject: [PATCH 19/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 532cf9ab6..646fb4bc8 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -733,6 +733,30 @@ def test_5_A(self): np.testing.assert_allclose(sim_im_pip.update()(), expected_2, rtol=1e-7, atol=1e-7) + ## PART 3 + # Complete pipeline. + sim_mask_pip = ( + particles + >> dt.SampleToMasks(lambda: lambda particle: particle > 0, + output_region=optics.output_region, + merge_method="or") + >> dt.AsType("int") + >> dt.OneHot(num_classes=2) + ) + + if TORCH_AVAILABLE: + sim_im_mask_pip = ( + (sim_im_pip & sim_mask_pip) + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + + train_dataset = dt.pytorch.Dataset( + sim_im_mask_pip, length=320, replace=.1, + ) + + im, mask = train_dataset[0] + if __name__ == "__main__": unittest.main() From b45f1162ac0a0c54253647c0f2ac01d334e447f3 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 14:29:01 +0700 Subject: [PATCH 20/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 646fb4bc8..1061df9af 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -757,6 +757,33 @@ def test_5_A(self): im, mask = train_dataset[0] + # Dataset length honored + assert len(train_dataset) == 320 + + # Types and shapes + assert isinstance(im, torch.Tensor) + assert isinstance(mask, torch.Tensor) + assert im.ndim == 3 and mask.ndim == 3 + assert im.shape[0] == 1 # single image channel + assert mask.shape[0] == 2 # one-hot: bg=0, fg=1 + assert im.shape[1:] == mask.shape[1:] + + # Mask must be one-hot per pixel and binary {0,1} + u = set(mask.unique().tolist()) + assert u.issubset({0.0, 1.0}) + assert torch.allclose(mask.sum(dim=0), torch.ones_like(mask[0])) + + # Both background and foreground should be present + fg_sum = int(mask[1].sum().item()) + bg_sum = int(mask[0].sum().item()) + assert fg_sum > 0 and bg_sum > 0 + + # Foreground pixels should be brighter than background on average + im2d = im[0] # (H, W) + fg_mean = float(im2d[mask[1].bool()].mean()) + bg_mean = float(im2d[mask[0].bool()].mean()) + assert fg_mean > bg_mean + if __name__ == "__main__": unittest.main() From 8bf4c4257d6bdd9dd199c6e8c209107ad2e99c73 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 14:52:05 +0700 Subject: [PATCH 21/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 1061df9af..97ab3da44 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -784,6 +784,96 @@ def test_5_A(self): bg_mean = float(im2d[mask[0].bool()].mean()) assert fg_mean > bg_mean + def test_5_B(self): + pass + + def test_6_1(self): + pass + + def test_6_A(self): + pass + + def test_7_1(self): + pass + + def test_7_A(self): + if TORCH_AVAILABLE: + # Hardcoded sequences: shape (num_samples=10, seq_len=4) + in_sequences = np.array([ + [1, 2, 3, 0], + [4, 5, 6, 0], + [7, 8, 9, 0], + [10, 11, 12, 0], + [13, 14, 15, 0], + [16, 17, 18, 0], + [19, 20, 21, 0], + [22, 23, 24, 0], + [25, 26, 27, 0], + [28, 29, 30, 0], + ]) + + out_sequences = np.array([ + [101, 102, 103, 0], + [104, 105, 106, 0], + [107, 108, 109, 0], + [110, 111, 112, 0], + [113, 114, 115, 0], + [116, 117, 118, 0], + [119, 120, 121, 0], + [122, 123, 124, 0], + [125, 126, 127, 0], + [128, 129, 130, 0], + ]) + + sources = dt.sources.Source( + inputs=in_sequences, + targets=out_sequences, + ) + train_sources, test_sources = \ + dt.sources.random_split(sources, [.8, .2]) + + assert len(train_sources) == 8 + assert len(test_sources) == 2 + + inputs_pip = ( + dt.Value(sources.inputs) + >> dt.pytorch.ToTensor(dtype=torch.int) + ) + outputs_pip = ( + dt.Value(sources.targets) + >> dt.pytorch.ToTensor(dtype=torch.int) + ) + + train_dataset = dt.pytorch.Dataset( + inputs_pip & outputs_pip, + inputs=train_sources, + ) + test_dataset = dt.pytorch.Dataset( + inputs_pip & outputs_pip, + inputs=test_sources, + ) + + assert len(train_dataset) == 8 + for input, output in train_dataset: + assert isinstance(input, torch.Tensor) + assert isinstance(output, torch.Tensor) + assert input.shape == torch.Size([4]) + assert output.shape == torch.Size([4]) + assert input.dtype == torch.int64 + assert output.dtype == torch.int64 + + assert len(test_dataset) == 2 + for input, output in test_dataset: + assert isinstance(input, torch.Tensor) + assert isinstance(output, torch.Tensor) + assert input.shape == torch.Size([4]) + assert output.shape == torch.Size([4]) + assert input.dtype == torch.int64 + assert output.dtype == torch.int64 + + def test_8_A(self): + pass # Essentially same code as test_7_A + if __name__ == "__main__": unittest.main() From 0a1dd8da1699b62d54bc0d25736905dd858f6deb Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 15:13:55 +0700 Subject: [PATCH 22/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 69 +++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 97ab3da44..949f40e2b 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -794,7 +794,74 @@ def test_6_A(self): pass def test_7_1(self): - pass + # Small toy dataset + # Shape: (num_samples=5, seq_len=3, num_features=2) + in_sequences = np.array([ + [[1.0, 10.0], [2.0, 11.0], [3.0, 12.0]], + [[4.0, 20.0], [5.0, 21.0], [6.0, 22.0]], + [[7.0, 30.0], [8.0, 31.0], [9.0, 32.0]], + [[10.0, 40.0], [11.0, 41.0], [12.0, 42.0]], + [[13.0, 50.0], [14.0, 51.0], [15.0, 52.0]], + ]) + + # Targets: one value per sample (e.g., "temperature") + # Shape: (num_samples=5, 1) + targets = np.array([ + [100.0], + [200.0], + [300.0], + [400.0], + [500.0], + ]) + + temp_idx = 0 + + sources = dt.sources.Source(inputs=in_sequences, targets=targets) + train_sources, val_sources = \ + dt.sources.random_split(sources, [0.8, 0.2]) + + assert len(train_sources) == 4 + assert len(val_sources) == 1 + + mean = np.mean([src["inputs"] for src in sources], axis=(0, 1)) + std = np.std([src["inputs"] for src in sources], axis=(0, 1)) + np.testing.assert_allclose(mean, np.array([ 8., 31.]), + rtol=1e-7, atol=1e-7) + np.testing.assert_allclose(std, np.array([ 4.3204938 , 14.16568624]), + rtol=1e-7, atol=1e-7) + + train_mean = np.mean([src["inputs"] for src in train_sources], + axis=(0, 1)) + train_std = np.std([src["inputs"] for src in train_sources], + axis=(0, 1)) + + inputs_pipeline = ( + dt.Value(sources.inputs - train_mean) / train_std + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + targets_pipeline = ( + dt.Value(sources.targets - train_mean[temp_idx]) + / train_std[temp_idx] + ) + + train_dataset = dt.pytorch.Dataset(inputs_pipeline & targets_pipeline, + inputs=train_sources) + val_dataset = dt.pytorch.Dataset(inputs_pipeline & targets_pipeline, + inputs=val_sources) + + assert len(train_dataset) == 4 + for inputs, target in train_dataset: + assert isinstance(inputs, torch.Tensor) + assert isinstance(target, torch.Tensor) + assert inputs.shape == torch.Size([3, 2]) + assert target.shape == torch.Size([1]) + assert inputs.dtype == torch.float32 + assert target.dtype == torch.float32 + + assert len(val_dataset) == 1 + for inputs, target in val_dataset: + assert inputs.shape == torch.Size([3, 2]) + assert target.shape == torch.Size([1]) def test_7_A(self): if TORCH_AVAILABLE: From 94e334765a1479d71caf6afc44feeef000f7af5c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 15:39:12 +0700 Subject: [PATCH 23/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 133 ++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 27 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 949f40e2b..0acd914f3 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -788,7 +788,81 @@ def test_5_B(self): pass def test_6_1(self): - pass + if TORCH_AVAILABLE: + np.random.seed(123) # Note that this seeding is not warratied + # to give reproducible results across + # platforms so the subsequent test might fail + + image_size = 5 + + particle = dt.PointParticle( + position=lambda: np.random.uniform( + image_size / 2 - 1, + image_size / 2 + 1, + size=2, + ), + ) + + optics = dt.Fluorescence( + output_region=(0, 0, image_size, image_size), + ) + + simulation = ( + optics(particle) + >> dt.NormalizeMinMax() + >> dt.Gaussian(sigma=0.1) + >> dt.MoveAxis(-1, 0) + >> dt.pytorch.ToTensor(dtype=torch.float32) + ) + + train_dataset = dt.pytorch.Dataset(simulation, length=2) + test_dataset = dt.pytorch.Dataset(simulation & particle.position, + length=10) + + # Test train dataset + expected_image = torch.tensor( + [[[ 0.0283, 0.0107, 0.1774, 0.3429, -0.2191], + [ 0.2039, 0.6161, 0.5111, 0.4461, 0.2739], + [ 0.6096, 0.7009, 0.8545, 0.7519, 0.7275], + [ 0.7522, 0.9581, 1.0386, 0.9639, 0.7323], + [ 0.3325, 0.8383, 0.7252, 0.6865, 0.5617]]], + dtype=torch.float32 + ) + image = train_dataset[0] + assert torch.allclose(image[0], expected_image, + rtol=1e-4, atol=1e-4) + + assert len(train_dataset) == 2 + for image in train_dataset: + image = image[0] + assert isinstance(image, torch.Tensor) + assert image.dtype == torch.float32 + assert image.shape == torch.Size([1, image_size, image_size]) + + # Test test dataset + expected_image = torch.tensor( + [[[0.0891, 0.3012, 0.3664, 0.3260, 0.0537], + [0.2549, 0.4170, 0.3974, 0.6752, 0.4747], + [0.3636, 0.6197, 0.7136, 0.8578, 0.7010], + [0.3770, 0.8978, 0.8853, 0.7877, 0.8980], + [0.3827, 0.7186, 0.8527, 0.7807, 0.8687]]], + dtype=torch.float32 + ) + expected_position = torch.tensor([3.2509, 2.5208]) + image, position = test_dataset[0] + assert torch.allclose(image, expected_image, + rtol=1e-4, atol=1e-4) + assert torch.allclose(position, expected_position, + rtol=1e-4, atol=1e-4) + + assert len(test_dataset) == 10 + for image, position in test_dataset: + assert isinstance(image, torch.Tensor) + assert image.shape == torch.Size([1, image_size, image_size]) + assert image.dtype == torch.float32 + + assert isinstance(position, torch.Tensor) + assert position.shape == (2,) # (x, y) particle position def test_6_A(self): pass @@ -835,33 +909,38 @@ def test_7_1(self): train_std = np.std([src["inputs"] for src in train_sources], axis=(0, 1)) - inputs_pipeline = ( - dt.Value(sources.inputs - train_mean) / train_std - >> dt.pytorch.ToTensor(dtype=torch.float) - ) - targets_pipeline = ( - dt.Value(sources.targets - train_mean[temp_idx]) - / train_std[temp_idx] - ) + if TORCH_AVAILABLE: + inputs_pipeline = ( + dt.Value(sources.inputs - train_mean) / train_std + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + targets_pipeline = ( + dt.Value(sources.targets - train_mean[temp_idx]) + / train_std[temp_idx] + ) + + train_dataset = dt.pytorch.Dataset( + inputs_pipeline & targets_pipeline, + inputs=train_sources, + ) + val_dataset = dt.pytorch.Dataset( + inputs_pipeline & targets_pipeline, + inputs=val_sources, + ) - train_dataset = dt.pytorch.Dataset(inputs_pipeline & targets_pipeline, - inputs=train_sources) - val_dataset = dt.pytorch.Dataset(inputs_pipeline & targets_pipeline, - inputs=val_sources) - - assert len(train_dataset) == 4 - for inputs, target in train_dataset: - assert isinstance(inputs, torch.Tensor) - assert isinstance(target, torch.Tensor) - assert inputs.shape == torch.Size([3, 2]) - assert target.shape == torch.Size([1]) - assert inputs.dtype == torch.float32 - assert target.dtype == torch.float32 - - assert len(val_dataset) == 1 - for inputs, target in val_dataset: - assert inputs.shape == torch.Size([3, 2]) - assert target.shape == torch.Size([1]) + assert len(train_dataset) == 4 + for inputs, target in train_dataset: + assert isinstance(inputs, torch.Tensor) + assert isinstance(target, torch.Tensor) + assert inputs.shape == torch.Size([3, 2]) + assert target.shape == torch.Size([1]) + assert inputs.dtype == torch.float32 + assert target.dtype == torch.float32 + + assert len(val_dataset) == 1 + for inputs, target in val_dataset: + assert inputs.shape == torch.Size([3, 2]) + assert target.shape == torch.Size([1]) def test_7_A(self): if TORCH_AVAILABLE: From 24444ae42a9dae2f35405c103f1697af03637065 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 18:13:33 +0700 Subject: [PATCH 24/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 137 +++++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 5 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 0acd914f3..6ab1f0717 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -8,6 +8,7 @@ import unittest +import glob import shutil import tempfile from pathlib import Path @@ -330,10 +331,6 @@ def test_4_A(self): Image.fromarray(stripes, mode="L") \ .save(test_dir / "1_test.png") - # print("Data root:", data_root) - # print("Train files:", sorted(os.listdir(train_dir))) - # print("Test files:", sorted(os.listdir(test_dir))) - ## PART 1 # Loading image files into a pipeline. @@ -865,7 +862,137 @@ def test_6_1(self): assert position.shape == (2,) # (x, y) particle position def test_6_A(self): - pass + # Temporary root (deleted in finally) + tmp_root = tempfile.mkdtemp(prefix="cells_like_") + data_root = Path(tmp_root) / "02" + image_dir = data_root / "image" + label_dir = data_root / "label" + + try: + image_dir.mkdir(parents=True, exist_ok=True) + label_dir.mkdir(parents=True, exist_ok=True) + + # Synthetic image (grayscale with some blobs) + image = np.zeros((8, 12), dtype=np.uint8) + image[1:3, 2:4] = 128 # blob 1 + image[4:6, 6:8] = 200 # blob 2 + image[6:8, 4:6] = 255 # blob 3 + + # Synthetic label mask (integer IDs for blobs) + label = np.zeros_like(image, dtype=np.uint8) + label[1:3, 2:4] = 1 + label[4:6, 6:8] = 2 + label[6:8, 4:6] = 3 + + # Save images + Image.fromarray(image, mode="L").save(image_dir / "image_0.png") + for i in range(1, 5): + Image.fromarray(np.zeros_like(image), mode="L") \ + .save(image_dir / f"image_{i}.png") + + # Save labels + Image.fromarray(label, mode="L").save(label_dir / "label_0.png") + for i in range(1, 5): + Image.fromarray(np.zeros_like(label), mode="L") \ + .save(label_dir / f"label_{i}.png") + + ## PART 1 + # Loading and analyzing the image and segmentaions. + from skimage.measure import regionprops + + sources = dt.sources.Source( + image_path=sorted(glob.glob(str(image_dir / "*.png"))), + label_path=sorted(glob.glob(str(label_dir / "*.png"))), + ) + + image_pip = dt.LoadImage(sources.image_path)[1:, 2:-4] / 256 + props_pip = ( + dt.LoadImage(sources.label_path)[1:, 2:-4] + >> regionprops + ) + + pip = image_pip & props_pip + + image, *props = pip() + + # The combined output should flatten to 1 image + 3 props = 4 items + assert len(pip()) == 4 + + assert isinstance(image, np.ndarray) + expected_image = np.array( + [[0.5, 0.5, 0.0, 0.0, 0.0, 0.0], + [0.5, 0.5, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.78125, 0.78125], + [0.0, 0.0, 0.0, 0.0, 0.78125, 0.78125], + [0.0, 0.0, 0.99609375, 0.99609375, 0.0, 0.0], + [0.0, 0.0, 0.99609375, 0.99609375, 0.0, 0.0]], + dtype=np.float32, + ) + assert np.allclose(image.squeeze(), expected_image, atol=1e-8) + + assert sorted([p.label for p in props]) == [1, 2, 3] + + ## PART 2 + # Cropping. + crop_frame_index = 0 + crop_size = 2 + crop_x0 = 1 + crop_y0 = 1 + + image, *props = pip(sources[crop_frame_index]) + crop = image[crop_x0:crop_x0 + crop_size, + crop_y0:crop_y0 + crop_size] + + expected_crop = np.array( + [[0.5, 0.0], + [0.0, 0.0]], + dtype=np.float32, + ) + assert np.allclose(crop.squeeze(), expected_crop, atol=1e-8) + + ## PART 3 + # Training pipeline. + if TORCH_AVAILABLE: + np.random.seed(123) # Note that this seeding is not warratied + # to give reproducible results across + # platforms so the subsequent test might + # fail + + train_pip = ( + dt.Value(crop) + >> dt.Multiply(lambda: np.random.uniform(0.9, 1.1)) + >> dt.Add(lambda: np.random.uniform(-0.1, 0.1)) + >> dt.MoveAxis(-1, 0) + >> dt.pytorch.ToTensor(dtype=torch.float32) + ) + + train_dataset = \ + dt.pytorch.Dataset(train_pip, length=40, replace=False) + + assert len(train_dataset) == 40 + + expected_tensor_0 = torch.tensor( + [[[ 0.4769, -0.0428], + [-0.0428, -0.0428]]], + ) + sample = train_dataset[0] + assert torch.allclose(sample[0], expected_tensor_0, + rtol=1e-4, atol=1e-4) + + expected_tensor_39 = torch.tensor( + [[[0.4829, 0.0103], + [0.0103, 0.0103]]], + ) + sample = train_dataset[39] + assert torch.allclose(sample[0], expected_tensor_39, + rtol=1e-4, atol=1e-4) + + except Exception: + raise + finally: + # Clean up the temporary dataset tree + shutil.rmtree(tmp_root, ignore_errors=True) def test_7_1(self): # Small toy dataset From a89ba585a0b6ca41354f78fa6d8bfd84bc3a3206 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 21 Sep 2025 18:37:03 +0700 Subject: [PATCH 25/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 60 +++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 6ab1f0717..17b9ad1d1 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -782,7 +782,65 @@ def test_5_A(self): assert fg_mean > bg_mean def test_5_B(self): - pass + ## PART 1 + # Loading data images and masks from files. + + # Temporary root (deleted in finally) + tmp_root = tempfile.mkdtemp(prefix="cell_counting_like_") + data_root = Path(tmp_root) / "base" + images_dir = data_root / "images" + masks_dir = data_root / "masks" + + try: + images_dir.mkdir(parents=True, exist_ok=True) + masks_dir.mkdir(parents=True, exist_ok=True) + + # Synthetic image (grayscale with some blobs) + image = np.zeros((8, 12), dtype=np.uint8) + image[1:3, 2:4] = 128 # blob 1 + image[4:6, 6:8] = 200 # blob 2 + image[6:8, 4:6] = 255 # blob 3 + + # Synthetic label mask (integer IDs for blobs in red channel) + mask_gray = np.zeros_like(image, dtype=np.uint8) + mask_gray[1:3, 2:4] = 1 + mask_gray[4:6, 6:8] = 1 + mask_gray[6:8, 4:6] = 1 + + # Expand to RGB, putting data in channel 0, zeros in channels 1 & 2 + mask_rgb = np.stack( + [mask_gray, + np.zeros_like(mask_gray), + np.zeros_like(mask_gray)], + axis=-1, + ) + + # Save images + Image.fromarray(image, mode="L").save(images_dir / "image_0.png") + for i in range(1, 5): + Image.fromarray(np.zeros_like(image), mode="L") \ + .save(images_dir / f"image_{i}.png") + + # Save labels + Image.fromarray(mask_rgb, mode="RGB") \ + .save(masks_dir / "mask_0.png") + for i in range(1, 5): + Image.fromarray(np.zeros_like(mask_rgb), mode="RGB") \ + .save(masks_dir / f"mask_{i}.png") + + + #TODO + + except Exception: + raise + finally: + # Clean up the temporary dataset tree + shutil.rmtree(tmp_root, ignore_errors=True) + + ## PART 2 + # Simulation pipeline. + + #TODO def test_6_1(self): if TORCH_AVAILABLE: From 6962293e6561e19951358a2e211886d247bc9708 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 24 Sep 2025 22:42:06 +1000 Subject: [PATCH 26/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 17b9ad1d1..9151937c7 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -477,7 +477,6 @@ def test_4_C(self): for source in sources: ecg, label = (ecg_pip & label_pip)(source) - assert 0 <= ecg.min() <= 1 assert isinstance(label, (bool, np.bool_)) # PART 3 From 326f306513139e016679e3f6c877e766b67a3f6c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 26 Sep 2025 12:17:09 +1000 Subject: [PATCH 27/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 379 ++++++++++++++++++++++++++++++++++- 1 file changed, 376 insertions(+), 3 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 9151937c7..66b41a6e8 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -783,7 +783,7 @@ def test_5_A(self): def test_5_B(self): ## PART 1 # Loading data images and masks from files. - + # Temporary root (deleted in finally) tmp_root = tempfile.mkdtemp(prefix="cell_counting_like_") data_root = Path(tmp_root) / "base" @@ -827,8 +827,48 @@ def test_5_B(self): Image.fromarray(np.zeros_like(mask_rgb), mode="RGB") \ .save(masks_dir / f"mask_{i}.png") + ## PART 1.1 + # Loading image files into a pipeline. + image_paths = dt.sources.ImageFolder(root=str(images_dir)) + mask_paths = dt.sources.ImageFolder(root=str(masks_dir)) + sources = dt.sources.Source(image=image_paths, label=mask_paths) + + assert len(image_paths) == 5 + assert len(mask_paths) == 5 + + ## PART 2.2 + # Pipelines. + image_pip = ( + dt.LoadImage(sources.image.path) + >> dt.Divide(3000) + >> dt.Clip(0, 1) + >> dt.AsType("float") + ) + mask_pip = ( + dt.LoadImage(sources.label.path)[..., :1] + >> dt.AsType("float") + ) + pip = ( + (image_pip & mask_pip) + >> dt.Crop(crop=(4, 6, None), corner=(0, 0)) + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + test_dataset = dt.pytorch.Dataset(pip, sources) + + assert len(test_dataset) == 5 + + for i in range(5): + image, mask = test_dataset[i] + + assert isinstance(image, torch.Tensor) + assert image.shape == torch.Size([1, 4, 6]) + assert image.dtype == torch.float32 + assert torch.all(image >= 0) and torch.all(image <= 1) - #TODO + assert isinstance(mask, torch.Tensor) + assert mask.shape == torch.Size([1, 4, 6]) + assert mask.dtype == torch.float32 except Exception: raise @@ -839,7 +879,340 @@ def test_5_B(self): ## PART 2 # Simulation pipeline. - #TODO + train_image_size = 6 + + def random_ellipse_axes(): + """Return the three axes of an ellipse.""" + ellipse_area = (np.random.uniform(.5, 1)) ** 2 + radius_ratio = np.random.uniform(1, 1.5) + major_axis = np.sqrt(ellipse_area) * radius_ratio + minor_axis = np.sqrt(ellipse_area) / radius_ratio + z_axis = np.sqrt(ellipse_area) * np.random.uniform(0.2, 0.4) + return (major_axis, minor_axis, z_axis) * dt.units.um + + ## PART 2.1 + np.random.seed(123) # Note that this seeding is not warratied + # to give reproducible results across + # platforms so the subsequent test might fail + + + ellipse = dt.Ellipsoid( + radius = random_ellipse_axes, + intensity=lambda: np.random.uniform(0.5, 1.5), + position=lambda: np.random.uniform(2, train_image_size - 2, + size=2), + rotation=lambda: np.random.uniform(0, 2 * np.pi), + ) + optics = dt.Fluorescence( + resolution=1e-6, + magnification=6, + wavelength=400e-9, + NA=lambda: np.random.uniform(0.9, 1.1), + output_region=(0, 0, train_image_size, train_image_size), + ) + sim_im_pip = optics(ellipse) + + # Checks + expected_image = np.array( + [[[0.62670365], [0.95904265], [1.15064654], + [1.17296235], [1.13969792], [0.9095519 ]], + [[1.21126057], [1.51766064], [1.7455454 ], + [1.76889047], [1.72424965], [1.43000316]], + [[1.72652512], [1.83151886], [1.8833134 ], + [1.88750391], [1.85299175], [1.74551291]], + [[1.77112966], [1.86331109], [1.89386948], + [1.88816495], [1.83622286], [1.73540255]], + [[1.53751879], [1.75932587], [1.7955828 ], + [1.77051135], [1.5555698 ], [1.31219839]], + [[1.03047638], [1.27223149], [1.30437236], + [1.27309201], [1.00711876], [0.66359776]]] + ) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip.update()() + assert not np.allclose(image, expected_image, atol=1e-8) + + ## PART 2.2 + import random + + np.random.seed(123) # Note that this seeding is not warratied + random.seed(123) # to give reproducible results across + # platforms so the subsequent test might fail + + ellipse = dt.Ellipsoid( + radius = random_ellipse_axes, + intensity=lambda: np.random.uniform(0.5, 1.5), + position=lambda: np.random.uniform(2, train_image_size - 2, + size=2), + rotation=lambda: np.random.uniform(0, 2 * np.pi), + ) + synthetic_nuclei = ( + (ellipse ^ (lambda: np.random.randint(5, 10))) + >> dt.Pad(px=(10, 10, 10, 10), keep_size=False) + >> dt.ElasticTransformation(alpha=100, sigma=10, order=1) + >> dt.CropTight() + ) + optics = dt.Fluorescence( + resolution=1e-6, + magnification=6, + wavelength=400e-9, + NA=lambda: np.random.uniform(0.9, 1.1), + output_region=(0, 0, train_image_size, train_image_size), + ) + sim_im_pip = optics(synthetic_nuclei) + + # Checks + expected_image = np.array( + [[[4.43875833], [5.61812011], [6.83467141], + [7.40197432], [7.15789432], [6.20212177]], + [[5.51601744], [6.96215298], [8.16738992], + [8.58847899], [8.18056869], [7.01360971]], + [[6.42507353], [8.20975942], [9.19766098], + [9.33420367], [8.80069871], [7.65615641]], + [[6.83698176], [8.57520423], [9.37970662], + [9.53241571], [8.91761343], [7.31147681]], + [[6.36655984], [8.14023641], [8.99595646], + [9.09135127], [8.37544931], [6.49717015]], + [[5.39208396], [7.11757634], [7.86945558], + [7.70038503], [6.95412321], [5.66020874]]]) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip.update()() + assert not np.allclose(image, expected_image, atol=1e-8) + + ## PART 2.3 + np.random.seed(123) # Note that this seeding is not warratied + random.seed(123) # to give reproducible results across + # platforms so the subsequent test might fail + + ellipse = dt.Ellipsoid( + radius = random_ellipse_axes, + intensity=lambda: np.random.uniform(0.5, 1.5), + position=lambda: np.random.uniform(2, train_image_size - 2, + size=2), + rotation=lambda: np.random.uniform(0, 2 * np.pi), + ) + synthetic_nuclei = ( + (ellipse ^ (lambda: np.random.randint(5, 10))) + >> dt.Pad(px=(10, 10, 10, 10), keep_size=False) + >> dt.ElasticTransformation(alpha=100, sigma=10, order=1) + >> dt.CropTight() + ) + synthetic_nuclei_mask = synthetic_nuclei > 0 + long_range_noise = ( + synthetic_nuclei + >> dt.Poisson(snr=0.2) + >> dt.GaussianBlur(sigma=3.5) + ) + short_range_noise = ( + synthetic_nuclei + >> dt.Poisson(snr=1.0) + >> dt.GaussianBlur(sigma=1.5) + ) + random_range_noise = ( + synthetic_nuclei + >> dt.Poisson(snr=lambda: np.random.uniform(0.5, 1.5)) + >> dt.GaussianBlur(sigma=lambda: np.random.uniform(0.75, 1.5)) + ) + noisy_synthetic_nuclei = ( + synthetic_nuclei_mask + * (long_range_noise + short_range_noise + random_range_noise) / 3 + ) + + optics = dt.Fluorescence( + resolution=1e-6, + magnification=6, + wavelength=400e-9, + NA=lambda: np.random.uniform(0.9, 1.1), + output_region=(0, 0, train_image_size, train_image_size), + ) + sim_im_pip = optics(noisy_synthetic_nuclei) + + # Checks + expected_image = np.array( + [[[2.70782737], [3.72377004], [4.7502782 ], + [5.21564371], [5.0711578 ], [4.39744194]], + [[3.74048155], [5.03468761], [5.86563705], + [5.95210827], [5.4688792 ], [4.64597443]], + [[4.3493022 ], [5.54970338], [6.28803747], + [6.30392203], [5.74445854], [4.7577248 ]], + [[4.44427776], [5.76434851], [6.54332137], + [6.65343518], [6.08066042], [4.8567884 ]], + [[4.24868926], [5.43328388], [6.21579624], + [6.50448421], [6.06196237], [4.61607002]], + [[3.82922766], [4.86706357], [5.50472639], + [5.59237713], [5.03817596], [3.71460963]]] + ) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip.update()() + assert not np.allclose(image, expected_image, atol=1e-8) + + ## PART 2.4 + np.random.seed(123) # Note that this seeding is not warratied + random.seed(123) # to give reproducible results across + # platforms so the subsequent test might fail + + ellipse = dt.Ellipsoid( + radius = random_ellipse_axes, + intensity=lambda: np.random.uniform(0.5, 1.5), + position=lambda: np.random.uniform(2, train_image_size - 2, + size=2), + rotation=lambda: np.random.uniform(0, 2 * np.pi), + ) + synthetic_nuclei = ( + (ellipse ^ (lambda: np.random.randint(1, 2))) + >> dt.Pad(px=(10, 10, 10, 10), keep_size=False) + >> dt.ElasticTransformation(alpha=100, sigma=10, order=1) + >> dt.CropTight() + ) + + long_range_noise = (synthetic_nuclei >> dt.Poisson(snr=0.2) + >> dt.GaussianBlur(sigma=3.5)) + short_range_noise = (synthetic_nuclei >> dt.Poisson(snr=1.0) + >> dt.GaussianBlur(sigma=1.5)) + random_range_noise = ( + synthetic_nuclei + >> dt.Poisson(snr=lambda: np.random.uniform(0.5, 1.5)) + >> dt.GaussianBlur(sigma=lambda: np.random.uniform(0.75, 1.5)) + ) + noisy_synthetic_nuclei = ( + synthetic_nuclei + * (long_range_noise + short_range_noise + random_range_noise) / 3 + ) + + non_overlap_nuclei = dt.NonOverlapping( + noisy_synthetic_nuclei, min_distance=6, + ) + + optics = dt.Fluorescence( + resolution=1e-6, magnification=6, wavelength=400e-9, + NA=lambda: np.random.uniform(0.9, 1.1), + output_region=(0, 0, train_image_size, train_image_size), + ) + sim_im_pip = ( + optics(non_overlap_nuclei) + >> dt.Gaussian(sigma=lambda: np.random.uniform(0, 0.1)) + >> dt.Divide(lambda: np.random.uniform(14, 20)) + >> dt.Add(lambda: np.random.uniform(-0.05, 0.15)) + >> dt.Clip(0, 1) >> dt.AsType("float") + ) + + sim_im_pip() + + # Checks + expected_image = np.array( + [[[0.12398151], [0.14209154], [0.15910754], + [0.15518798], [0.14829296], [0.12743581]], + [[0.15065696], [0.1624304 ], [0.18212656], + [0.18492249], [0.17675485], [0.14808888]], + [[0.16032724], [0.17263786], [0.19540503], + [0.19553262], [0.17960588], [0.15236758]], + [[0.16342678], [0.17271644], [0.18141046], + [0.17859922], [0.16860212], [0.14591775]], + [[0.14388377], [0.16010432], [0.16078891], + [0.15686093], [0.13163569], [0.11720937]], + [[0.12653167], [0.1265491 ], [0.12649258], + [0.12450134], [0.11387853], [0.10064209]]] + ) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip() + assert np.allclose(image, expected_image, atol=1e-8) + image = sim_im_pip.update()() + assert not np.allclose(image, expected_image, atol=1e-8) + + if TORCH_AVAILABLE: + ## PART 2.5 + import warnings + + from skimage import morphology as skmorph + + np.random.seed(123) # Note that this seeding is not warratied + random.seed(123) # to give reproducible results across + # platforms so the subsequent test might fail + + def get_mask(radius): + """Apply isotropic erosion to a binary mask.""" + def inner(mask): + mask = np.sum(mask, -1, keepdims=True) > 0 + mask = np.pad(mask, [(1, 1), (1, 1), (0, 0)], + mode="constant") + mask = skmorph.isotropic_erosion(mask, radius=radius) + return mask[1:-1, 1:-1] + return inner + + sim_mask_pip = ( + non_overlap_nuclei + >> dt.SampleToMasks( + get_mask, + radius=1, + output_region=optics.output_region, + merge_method="or", + ) + >> dt.AsType("float") + ) + + # Checks + expected_mask = np.array( + [[[1.], [1.], [1.], [1.], [0.], [0.]], + [[1.], [1.], [1.], [1.], [1.], [0.]], + [[1.], [1.], [1.], [1.], [1.], [1.]], + [[0.], [1.], [1.], [1.], [1.], [1.]], + [[0.], [1.], [1.], [1.], [1.], [1.]], + [[0.], [0.], [1.], [1.], [1.], [0.]]] + ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + + mask = sim_mask_pip() + assert np.allclose(mask, expected_mask, atol=1e-8) + mask = sim_mask_pip() + assert np.allclose(mask, expected_mask, atol=1e-8) + mask = sim_mask_pip.update()() + assert not np.allclose(mask, expected_mask, atol=1e-8) + + ## PART 2.6 + np.random.seed(123) # Note that this seeding is not warratied + random.seed(123) # to give reproducible results across + # platforms so the subsequent test might fail + + sim_im_mask_pip = ( + (sim_im_pip & sim_mask_pip) + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + train_dataset = dt.pytorch.Dataset( + sim_im_mask_pip, length=640, replace=0.01, + ) + + assert len(train_dataset) == 640 + + image, mask = train_dataset[639] + expected_image = torch.tensor( + [[[0.1240, 0.1421, 0.1591, 0.1552, 0.1483, 0.1274], + [0.1507, 0.1624, 0.1821, 0.1849, 0.1768, 0.1481], + [0.1603, 0.1726, 0.1954, 0.1955, 0.1796, 0.1524], + [0.1634, 0.1727, 0.1814, 0.1786, 0.1686, 0.1459], + [0.1439, 0.1601, 0.1608, 0.1569, 0.1316, 0.1172], + [0.1265, 0.1265, 0.1265, 0.1245, 0.1139, 0.1006]]] + ) + expected_mask = torch.tensor( + [[[1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 1.], + [1., 1., 1., 1., 1., 0.], + [1., 1., 1., 1., 0., 0.]]] + ) + assert torch.allclose(image, expected_image, rtol=1e-7, atol=1e-4) + assert torch.allclose(mask, expected_mask, rtol=1e-7, atol=1e-4) def test_6_1(self): if TORCH_AVAILABLE: From dec453d2627d542517a3fedc2d42d51119ccaf31 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 27 Sep 2025 00:13:57 +1000 Subject: [PATCH 28/28] Update test_dlcc.py --- deeptrack/tests/test_dlcc.py | 55 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/deeptrack/tests/test_dlcc.py b/deeptrack/tests/test_dlcc.py index 66b41a6e8..4d5bce3b1 100644 --- a/deeptrack/tests/test_dlcc.py +++ b/deeptrack/tests/test_dlcc.py @@ -838,37 +838,38 @@ def test_5_B(self): ## PART 2.2 # Pipelines. - image_pip = ( - dt.LoadImage(sources.image.path) - >> dt.Divide(3000) - >> dt.Clip(0, 1) - >> dt.AsType("float") - ) - mask_pip = ( - dt.LoadImage(sources.label.path)[..., :1] - >> dt.AsType("float") - ) - pip = ( - (image_pip & mask_pip) - >> dt.Crop(crop=(4, 6, None), corner=(0, 0)) - >> dt.MoveAxis(2, 0) - >> dt.pytorch.ToTensor(dtype=torch.float) - ) - test_dataset = dt.pytorch.Dataset(pip, sources) + if TORCH_AVAILABLE: + image_pip = ( + dt.LoadImage(sources.image.path) + >> dt.Divide(3000) + >> dt.Clip(0, 1) + >> dt.AsType("float") + ) + mask_pip = ( + dt.LoadImage(sources.label.path)[..., :1] + >> dt.AsType("float") + ) + pip = ( + (image_pip & mask_pip) + >> dt.Crop(crop=(4, 6, None), corner=(0, 0)) + >> dt.MoveAxis(2, 0) + >> dt.pytorch.ToTensor(dtype=torch.float) + ) + test_dataset = dt.pytorch.Dataset(pip, sources) - assert len(test_dataset) == 5 + assert len(test_dataset) == 5 - for i in range(5): - image, mask = test_dataset[i] + for i in range(5): + image, mask = test_dataset[i] - assert isinstance(image, torch.Tensor) - assert image.shape == torch.Size([1, 4, 6]) - assert image.dtype == torch.float32 - assert torch.all(image >= 0) and torch.all(image <= 1) + assert isinstance(image, torch.Tensor) + assert image.shape == torch.Size([1, 4, 6]) + assert image.dtype == torch.float32 + assert torch.all(image >= 0) and torch.all(image <= 1) - assert isinstance(mask, torch.Tensor) - assert mask.shape == torch.Size([1, 4, 6]) - assert mask.dtype == torch.float32 + assert isinstance(mask, torch.Tensor) + assert mask.shape == torch.Size([1, 4, 6]) + assert mask.dtype == torch.float32 except Exception: raise