From 381630dfcb2661682a5e2e6096d2611a2801a3d6 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 18 Jun 2025 20:32:55 +0200 Subject: [PATCH 001/223] Update features.py --- deeptrack/features.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index a30d9892d..775cb8815 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -126,9 +126,10 @@ def merge_features( import itertools import operator import random -from typing import Any, Callable, Iterable, Literal +from typing import Any, Callable, Iterable, Literal, TYPE_CHECKING import numpy as np +from numpy.typing import NDArray import matplotlib.animation as animation import matplotlib.pyplot as plt from pint import Quantity @@ -137,12 +138,17 @@ def merge_features( from deeptrack import units from deeptrack.backend import config, xp from deeptrack.backend.core import DeepTrackNode -from deeptrack.backend.units import ConversionTable, create_context +from deeptrack.backend.units import ConversionTable from deeptrack.image import Image from deeptrack.properties import PropertyDict from deeptrack.sources import SourceItem from deeptrack.types import ArrayLike, PropertyLike + +if TYPE_CHECKING: + import torch + + MERGE_STRATEGY_OVERRIDE: int = 0 MERGE_STRATEGY_APPEND: int = 1 @@ -373,14 +379,21 @@ def device(self) -> str | torch.device: def __init__( self: Feature, - _input: Any = [], + _input: ( + NDArray + | list[NDArray] + | torch.Tensor + | list[torch.Tensor] + | Image + | list[Image] + ), **kwargs: dict[str, Any], ) -> None: """Initialize a new Feature instance. Parameters ---------- - _input: np.ndarray or list[np.ndarray] or Image or list of Images, optional + _input: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images], optional The initial input(s) for the feature, often images or other data. If not provided, defaults to an empty list. **kwargs: dict of str to Any @@ -388,10 +401,10 @@ def __init__( stored in `self.properties`, allowing for dynamic or parameterized behavior. If not provided, defaults to an empty list. - + """ - # store backend on initialization + # Store backend on initialization. self._backend = config.get_backend() # Store the dtype and device on initialization. @@ -492,6 +505,7 @@ def __call__( The output of the feature or pipeline after execution. """ + with config.with_backend(self._backend): # If image_list is as Source, activate it. self._activate_sources(image_list) From c6132f482654b8ece9fc71ab3f698a181491f84b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 18 Jun 2025 20:37:08 +0200 Subject: [PATCH 002/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 775cb8815..8cbef4c07 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -138,7 +138,7 @@ def merge_features( from deeptrack import units from deeptrack.backend import config, xp from deeptrack.backend.core import DeepTrackNode -from deeptrack.backend.units import ConversionTable +from deeptrack.backend.units import ConversionTable, create_context from deeptrack.image import Image from deeptrack.properties import PropertyDict from deeptrack.sources import SourceItem From 4b68ce1ba69bea81520e6c0cb0e89d88eeac47d0 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 18 Jun 2025 20:41:03 +0200 Subject: [PATCH 003/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 8cbef4c07..6bfe3c1a7 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -386,7 +386,7 @@ def __init__( | list[torch.Tensor] | Image | list[Image] - ), + ) = [], **kwargs: dict[str, Any], ) -> None: """Initialize a new Feature instance. From 21044e8388b58acdd7750b311ed5977ca0f2a207 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 22 Jun 2025 11:50:56 +0200 Subject: [PATCH 004/223] Update features.py --- deeptrack/features.py | 53 +++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 6bfe3c1a7..ae6ad2a9c 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1804,16 +1804,16 @@ class DummyFeature(Feature): """A no-op feature that simply returns the input unchanged. This class can serve as a container for properties that don't directly - transform the data but need to be logically grouped. Since it inherits - transform the data but need to be logically grouped. Since it inherits - from `Feature`, any keyword arguments passed to the constructor are - stored as `Property` instances in `self.properties`, enabling dynamic - behavior or parameterization without performing any transformations - on the input data. + transform the data but need to be logically grouped. + + Since it inherits from `Feature`, any keyword arguments passed to the + constructor are stored as `Property` instances in `self.properties`, + enabling dynamic behavior or parameterization without performing any + transformations on the input data. Parameters ---------- - _input: np.ndarray or list np.ndarray or Image or list of Images, optional + _input: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images], optional An optional input (image or list of images) that can be set for the feature. By default, an empty list. **kwargs: dict of str to Any @@ -1822,9 +1822,8 @@ class DummyFeature(Feature): Methods ------- - `get(image: np.ndarray | list np.ndarray | Image | list[Image], **kwargs: dict[str, Any]) -> Image | list[Image]` - Simply returns the input image(s) unchanged. - + `get(image: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images], **kwargs: Any) -> np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images]` + It simply returns the input image(s) unchanged. Examples -------- @@ -1842,29 +1841,43 @@ class DummyFeature(Feature): >>> output_image = dummy_feature(dummy_image) Verify the output is identical to the input: - >>> print(np.array_equal(dummy_image, output_image)) + >>> np.array_equal(dummy_image, output_image) True Access the properties stored in DummyFeature: - >>> print(dummy_feature.properties["value"]()) + >>> dummy_feature.properties["value"]() 42 """ def get( self: Feature, - image: np.ndarray | list[np.ndarray] | Image | list[Image], + image: ( + NDArray + | list[NDArray] + | torch.Tensor + | list[torch.Tensor] + | Image + | list[Image] + ), **kwargs: Any, - )-> Image | list[Image]: + ) -> ( + NDArray + | list[NDArray] + | torch.Tensor + | list[torch.Tensor] + | Image + | list[Image] + ): """Return the input image or list of images unchanged. - This method simply returns the input without applying any transformation. + This method simply returns the input without any transformation. It adheres to the `Feature` interface by accepting additional keyword - arguments for consistency, although they are not used in this method. + arguments for consistency, although they are not used. Parameters ---------- - image: np.ndarray or list np.ndarray or Image or list of Image + image: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images] The image or list of images to pass through without modification. **kwargs: Any Additional properties sampled from `self.properties` or passed @@ -1873,8 +1886,8 @@ def get( Returns ------- - Image or list of Images - The same `image` object that was passed in. + np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images] + The same image that was passed in. """ @@ -1930,7 +1943,7 @@ class Value(Feature): __distributed__: bool = False # Process as a single batch. def __init__( - self: Feature, + self: Feature, value: PropertyLike[float] = 0, **kwargs: dict[str, Any] ): From 0f55b17616309c7b60eef7308d9936a117b85404 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 22 Jun 2025 11:50:58 +0200 Subject: [PATCH 005/223] Update test_features.py --- deeptrack/tests/test_features.py | 78 ++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index f19d97207..e3037c40c 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -12,7 +12,14 @@ import numpy as np -from deeptrack import features, properties, scatterers, units, optics +from deeptrack import ( + features, + optics, + properties, + scatterers, + TORCH_AVAILABLE, + units, +) from deeptrack.image import Image from deeptrack.noises import Gaussian @@ -598,11 +605,10 @@ def get(self, image, **kwargs): + A.properties["addend"]()), ) ) - - def test_DummyFeature(self): - """Test that the DummyFeature correctly returns the value of its properties.""" + def test_DummyFeature(self): + # Test that DummyFeature properties are callable and can be updated. feature = features.DummyFeature(a=1, b=2, c=3) self.assertEqual(feature.a(), 1) @@ -618,6 +624,69 @@ def test_DummyFeature(self): feature.c.set_value(6) self.assertEqual(feature.c(), 6) + # Test that DummyFeature returns input unchanged and supports call + # syntax. + feature = features.DummyFeature() + input_array = np.random.rand(10, 10) + output_array = feature.get(input_array) + self.assertIs(output_array, input_array) + # For callability via __call__ (as per DeepTrack2) + output_array_call = feature(input_array) + self.assertIs(output_array_call, input_array) + + # Test with NumPy array + arr = np.zeros((3, 3)) + self.assertIs(feature.get(arr), arr) + self.assertIs(feature(arr), arr) + + # Test with list of NumPy arrays + arr_list = [np.ones((2, 2)), np.zeros((2, 2))] + self.assertEqual(feature.get(arr_list), arr_list) + self.assertEqual(feature(arr_list), arr_list) + + # Test with PyTorch + if TORCH_AVAILABLE: + import torch + + # Test with PyTorch tensor + tensor = torch.ones(4, 4) + self.assertIs(feature.get(tensor), tensor) + self.assertIs(feature(tensor), tensor) + + # Test with list of PyTorch tensors + tensor_list = [torch.zeros(2, 2), torch.ones(2, 2)] + self.assertEqual(feature.get(tensor_list), tensor_list) + self.assertEqual(feature(tensor_list), tensor_list) + + # Test with Image + img = Image(np.zeros((5, 5))) + self.assertIs(feature.get(img), img) + # feature(img) returns an array, not an Image. + self.assertTrue(np.array_equal(feature(img), img.data)) + # Note: Using feature.get(img) returns the Image object itself, + # while using feature(img) (i.e., calling the feature directly) + # returns the underlying NumPy array (img.data). This behavior + # is by design in DeepTrack2, where the __call__ method extracts + # the raw array from the Image to facilitate downstream processing + # with NumPy and similar libraries. Therefore, when testing or + # using features, always be mindful of whether you want the + # object (Image) or just its data (array). + + # Test with list of Image + img_list = [Image(np.ones((3, 3))), Image(np.zeros((3, 3)))] + self.assertEqual(feature.get(img_list), img_list) + # feature(img_list) returns a list of arrays, not a list of Images. + output = feature(img_list) + self.assertEqual(len(output), len(img_list)) + for arr, img in zip(output, img_list): + self.assertTrue(np.array_equal(arr, img.data)) + # Note: Calling feature(img_list) returns a list of NumPy arrays + # extracted from each Image in img_list, whereas feature.get(img_list) + # returns the original list of Image objects. This difference is + # intentional in DeepTrack2, where the __call__ method is designed to + # yield the underlying array data for easier interoperability with + # NumPy and downstream processing. + def test_Value(self): @@ -667,6 +736,7 @@ def test_FloorDivide(self): def test_Power(self): test_operator(self, operator.pow) + def test_LessThan(self): test_operator(self, operator.lt) From 017f21efa1f1eaa4f89a8dc759b1cec197b810c2 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 22 Jun 2025 12:00:51 +0200 Subject: [PATCH 006/223] Update features.py --- deeptrack/features.py | 57 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index ae6ad2a9c..2a42f02ad 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -130,8 +130,8 @@ def merge_features( import numpy as np from numpy.typing import NDArray -import matplotlib.animation as animation import matplotlib.pyplot as plt +from matplotlib import animation from pint import Quantity from scipy.spatial.distance import cdist @@ -145,6 +145,61 @@ def merge_features( from deeptrack.types import ArrayLike, PropertyLike +__all__ = [ + "Feature", + "StructuralFeature", + "Chain", + "Branch", + "DummyFeature", + "Value", + "ArithmeticOperationFeature", + "Add", + "Subtract", + "Multiply", + "Divide", + "FloorDivide", + "Power", + "LessThan", + "LessThanOrEquals", + "LessThanOrEqual", + "GreaterThan", + "GreaterThanOrEquals", + "GreaterThanOrEqual", + "Equals", + "Equal", + "Stack", + "Arguments", + "Probability", + "Repeat", + "Combine", + "Slice", + "Bind", + "BindResolve", + "BindUpdate", + "ConditionalSetProperty", + "ConditionalSetFeature", + "Lambda", + "Merge", + "OneOf", + "OneOfDict", + "LoadImage", + "SampleToMasks", + "AsType", + "ChannelFirst2d", + "Upscale", + "NonOverlapping", + "Store", + "Squeeze", + "Unsqueeze", + "ExpandDims", + "MoveAxis", + "Transpose", + "Permute", + "OneHot", + "TakeProperties", +] + + if TYPE_CHECKING: import torch From 3e4a546958db9576497b1160cb019f571fd18204 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 09:03:52 +0200 Subject: [PATCH 007/223] Value Update test_features.py Update features.py Update test_features.py Update features.py --- deeptrack/features.py | 184 +++++++++++++++++-------------- deeptrack/tests/test_features.py | 31 +++++- 2 files changed, 134 insertions(+), 81 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 2a42f02ad..bbd8fdd8a 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -146,57 +146,57 @@ def merge_features( __all__ = [ - "Feature", - "StructuralFeature", - "Chain", - "Branch", + "Feature", # TODO + "StructuralFeature", # TODO + "Chain", # TODO + "Branch", # TODO "DummyFeature", "Value", - "ArithmeticOperationFeature", - "Add", - "Subtract", - "Multiply", - "Divide", - "FloorDivide", - "Power", - "LessThan", - "LessThanOrEquals", - "LessThanOrEqual", - "GreaterThan", - "GreaterThanOrEquals", - "GreaterThanOrEqual", - "Equals", - "Equal", - "Stack", - "Arguments", - "Probability", - "Repeat", - "Combine", - "Slice", - "Bind", - "BindResolve", - "BindUpdate", - "ConditionalSetProperty", - "ConditionalSetFeature", - "Lambda", - "Merge", - "OneOf", - "OneOfDict", - "LoadImage", - "SampleToMasks", - "AsType", - "ChannelFirst2d", - "Upscale", - "NonOverlapping", - "Store", - "Squeeze", - "Unsqueeze", - "ExpandDims", - "MoveAxis", - "Transpose", - "Permute", - "OneHot", - "TakeProperties", + "ArithmeticOperationFeature", # TODO + "Add", # TODO + "Subtract", # TODO + "Multiply", # TODO + "Divide", # TODO + "FloorDivide", # TODO + "Power", # TODO + "LessThan", # TODO + "LessThanOrEquals", # TODO + "LessThanOrEqual", # TODO + "GreaterThan", # TODO + "GreaterThanOrEquals", # TODO + "GreaterThanOrEqual", # TODO + "Equals", # TODO + "Equal", # TODO + "Stack", # TODO + "Arguments", # TODO + "Probability", # TODO + "Repeat", # TODO + "Combine", # TODO + "Slice", # TODO + "Bind", # TODO + "BindResolve", # TODO + "BindUpdate", # TODO + "ConditionalSetProperty", # TODO + "ConditionalSetFeature", # TODO + "Lambda", # TODO + "Merge", # TODO + "OneOf", # TODO + "OneOfDict", # TODO + "LoadImage", # TODO + "SampleToMasks", # TODO + "AsType", # TODO + "ChannelFirst2d", # TODO + "Upscale", # TODO + "NonOverlapping", # TODO + "Store", # TODO + "Squeeze", # TODO + "Unsqueeze", # TODO + "ExpandDims", # TODO + "MoveAxis", # TODO + "Transpose", # TODO + "Permute", # TODO + "OneHot", # TODO + "TakeProperties", # TODO ] @@ -530,7 +530,7 @@ def __call__( self: Feature, image_list: np.ndarray | list[np.ndarray] | Image | list[Image] = None, _ID: tuple[int, ...] = (), - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Any: """Execute the feature or pipeline. @@ -1906,7 +1906,7 @@ class DummyFeature(Feature): """ def get( - self: Feature, + self: DummyFeature, image: ( NDArray | list[NDArray] @@ -1953,17 +1953,17 @@ class Value(Feature): """Represents a constant (per evaluation) value in a DeepTrack pipeline. This feature holds a constant value (e.g., a scalar or array) and supplies - it on demand to other parts of the pipeline. It does not transform the - input image but instead returns the stored value. + it on demand to other parts of the pipeline. + + Wen called with an image, it does not transform the input image but instead + returns the stored value. Parameters ---------- - value: PropertyLike[float], optional - The numerical value to store. Defaults to 0. If an `Image` is provided, - a warning is issued recommending conversion to a NumPy array for - The numerical value to store. Defaults to 0. If an `Image` is provided, - a warning is issued recommending conversion to a NumPy array for - performance reasons. + value: PropertyLike[float or array], optional + The numerical value to store. It defaults to 0. + If an `Image` is provided, a warning is issued recommending conversion + to a NumPy array or a PyTorch tensor for performance reasons. **kwargs: dict of str to Any Additional named properties passed to the `Feature` constructor. @@ -1979,42 +1979,65 @@ class Value(Feature): `get(image: Any, value: float, **kwargs: dict[str, Any]) -> float` Returns the stored value, ignoring the input image. - Examples -------- >>> import deeptrack as dt Initialize a constant value and retrieve it: >>> value = dt.Value(42) - >>> print(value()) + >>> value() 42 Override the value at call time: - >>> print(value(value=100)) + >>> value(value=100) 100 + Initialize a constant array value and retrieve it: + >>> import numpy as np + >>> + >>> arr_value = dt.Value(np.arange(4)) + >>> arr_value() + array([0, 1, 2, 3]) + + Override the array value at call time: + >>> arr_value(value=np.array([10, 20, 30, 40])) + array([10, 20, 30, 40]) + + Initialize a constant PyTorch tensor value and retrieve it: + >>> import torch + >>> + >>> tensor_value = dt.Value(torch.tensor([1., 2., 3.])) + >>> tensor_value() + tensor([1., 2., 3.]) + + Override the tensor value at call time: + >>> tensor_value(value=torch.tensor([10., 20., 30.])) + tensor([10., 20., 30.]) + """ __distributed__: bool = False # Process as a single batch. def __init__( - self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any] + self: Value, + value: PropertyLike[float | ArrayLike] = 0, + **kwargs: Any, ): """Initialize the `Value` feature to store a constant value. This feature holds a constant numerical value and provides it to the - pipeline as needed. If an `Image` object is supplied, a warning is - issued to encourage converting it to a NumPy array for performance + pipeline as needed. + + If an `Image` object is supplied, a warning is issued to encourage + converting it to a NumPy array or a PyTorch tensor for performance optimization. Parameters ---------- - value: PropertyLike[float], optional + value: PropertyLike[float or array], optional The initial value to store. If an `Image` is provided, a warning is - raised. Defaults to 0. - **kwargs: dict of str to Any + raised. It defaults to 0. + **kwargs: Any Additional keyword arguments passed to the `Feature` constructor, such as custom properties or the feature name. @@ -2023,19 +2046,20 @@ def __init__( if isinstance(value, Image): import warnings warnings.warn( - "Setting dt.Value value as an Image object is likely to lead " - "to performance deterioration. Consider converting it to a " - "numpy array using np.array." + "Passing an Image object as the value to dt.Value may lead to " + "performance deterioration. Consider converting the Image to " + "a NumPy array with np.array(image), or to a PyTorch tensor " + "with torch.tensor(np.array(image))." ) super().__init__(value=value, **kwargs) def get( self: Feature, - image: Any, - value: float, - **kwargs: dict[str, Any] - ) -> float: + image: Any, + value: float | ArrayLike, + **kwargs: Any, + ) -> float | ArrayLike: """Return the stored value, ignoring the input image. The `get` method simply returns the stored numerical value, allowing @@ -2046,16 +2070,16 @@ def get( image: Any Input data typically processed by features. For `Value`, this is ignored and does not affect the output. - value: float + value: float or array The current value to return. This may be the initial value or an overridden value supplied during the method call. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments, which are ignored but included for consistency with the feature interface. Returns ------- - float + float or array The stored or overridden `value`, returned unchanged. """ diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index e3037c40c..70f38822c 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -689,19 +689,48 @@ def test_DummyFeature(self): def test_Value(self): - + # Scalar value tests value = features.Value(value=1) self.assertEqual(value(), 1) self.assertEqual(value.value(), 1) self.assertEqual(value(value=2), 2) + self.assertEqual(value(), 2) self.assertEqual(value.value(), 2) value = features.Value(value=lambda: 1) self.assertEqual(value(), 1) self.assertEqual(value.value(), 1) self.assertNotEqual(value(value=lambda: 2), 2) + self.assertNotEqual(value(), 2) self.assertNotEqual(value.value(), 2) + # NumPy array value tests + arr = np.arange(4) + value_arr = features.Value(value=arr) + self.assertTrue(np.array_equal(value_arr(), arr)) + self.assertTrue(np.array_equal(value_arr.value(), arr)) + # Override with a new array + override_arr = np.array([10, 20, 30, 40]) + self.assertTrue( + np.array_equal(value_arr(value=override_arr), override_arr) + ) + self.assertTrue(np.array_equal(value_arr(), override_arr)) + self.assertTrue(np.array_equal(value_arr.value(), override_arr)) + + # PyTorch tensor value tests + if TORCH_AVAILABLE: + import torch + + tensor = torch.tensor([1., 2., 3.]) + value_tensor = features.Value(value=tensor) + self.assertTrue(torch.equal(value_tensor(), tensor)) + self.assertTrue(torch.equal(value_tensor.value(), tensor)) + # Override with a new tensor + override_tensor = torch.tensor([10., 20., 30.]) + self.assertTrue(torch.equal(value_tensor(value=override_tensor), override_tensor)) + self.assertTrue(torch.equal(value_tensor(), override_tensor)) + self.assertTrue(torch.equal(value_tensor.value(), override_tensor)) + def test_ArithmeticOperationFeature(self): From 83bb4911320424595f655bd13d939c8ac0514422 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 09:35:39 +0200 Subject: [PATCH 008/223] Update test_features.py --- deeptrack/tests/test_features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 70f38822c..6cd50d4af 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -739,7 +739,7 @@ def test_ArithmeticOperationFeature(self): input_values = [1, 2, 3, 4] expected_output = [11, 12, 13, 14] output = addition_feature(input_values) - self.assertEqual(output, expected_output) + self.assertEqual(output, expected_output) def test_Add(self): From 8851004153215a2bb262858d24d137c0c84d10b4 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 09:59:40 +0200 Subject: [PATCH 009/223] remove __gpu_compatible__ --- deeptrack/elementwise.py | 2 -- deeptrack/features.py | 7 ------- deeptrack/optics.py | 8 -------- deeptrack/scatterers.py | 2 -- 4 files changed, 19 deletions(-) diff --git a/deeptrack/elementwise.py b/deeptrack/elementwise.py index a40c83a76..72427c6cd 100644 --- a/deeptrack/elementwise.py +++ b/deeptrack/elementwise.py @@ -145,8 +145,6 @@ class ElementwiseFeature(Feature): Returns the result of applying the function to the input array. """ - - __gpu_compatible__: bool = True def __init__( self: ElementwiseFeature, diff --git a/deeptrack/features.py b/deeptrack/features.py index bbd8fdd8a..332ff7f27 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -276,9 +276,6 @@ class Feature(DeepTrackNode): __conversion_table__: ConversionTable Defines the unit conversions used by the feature to convert its properties into the desired units. - __gpu_compatible__: bool - Indicates whether the feature can use GPU acceleration. When enabled, - GPU execution is triggered based on input size or backend settings. Methods ------- @@ -399,7 +396,6 @@ class Feature(DeepTrackNode): __distributed__ = True __property_memorability__ = 1 __conversion_table__ = ConversionTable() - __gpu_compatible__ = False _wrap_array_with_image: bool = False _float_dtype: str @@ -2111,8 +2107,6 @@ class ArithmeticOperationFeature(Feature): __distributed__: bool Indicates that this feature’s `get(...)` method processes the input as a whole (`False`) rather than distributing calls for individual items. - __gpu_compatible__: bool - Specifies that the feature is compatible with GPU processing (`True`). Methods ------- @@ -2138,7 +2132,6 @@ class ArithmeticOperationFeature(Feature): """ __distributed__: bool = False - __gpu_compatible__: bool = True def __init__( diff --git a/deeptrack/optics.py b/deeptrack/optics.py index b9885536d..9b29ca06c 100644 --- a/deeptrack/optics.py +++ b/deeptrack/optics.py @@ -972,8 +972,6 @@ class Fluorescence(Optics): Attributes ---------- - __gpu_compatible__: bool - Indicates whether the class supports GPU acceleration. NA: float Numerical aperture of the optical system. wavelength: float @@ -1018,8 +1016,6 @@ class Fluorescence(Optics): """ - __gpu_compatible__ = True - def get( self: Fluorescence, illuminated_volume: ArrayLike[complex], @@ -1198,8 +1194,6 @@ class Brightfield(Optics): Attributes ---------- - __gpu_compatible__: bool - Indicates whether the class supports GPU acceleration. __conversion_table__: ConversionTable Table used to convert properties of the feature to desired units. NA: float @@ -1247,8 +1241,6 @@ class Brightfield(Optics): """ - __gpu_compatible__ = True - __conversion_table__ = ConversionTable( working_distance=(u.meter, u.meter), ) diff --git a/deeptrack/scatterers.py b/deeptrack/scatterers.py index 1b45fc861..5646c64ec 100644 --- a/deeptrack/scatterers.py +++ b/deeptrack/scatterers.py @@ -791,8 +791,6 @@ class MieScatterer(Scatterer): """ - __gpu_compatible__ = True - __conversion_table__ = ConversionTable( radius=(u.meter, u.meter), polarization_angle=(u.radian, u.radian), From c806cfdce4b6d1f639bbec8da688b27b0e14dd98 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 11:59:43 +0200 Subject: [PATCH 010/223] Update features.py --- deeptrack/features.py | 83 ++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 332ff7f27..0b424d410 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -152,7 +152,7 @@ def merge_features( "Branch", # TODO "DummyFeature", "Value", - "ArithmeticOperationFeature", # TODO + "ArithmeticOperationFeature", "Add", # TODO "Subtract", # TODO "Multiply", # TODO @@ -524,7 +524,15 @@ def get( def __call__( self: Feature, - image_list: np.ndarray | list[np.ndarray] | Image | list[Image] = None, + image_list: ( + NDArray[Any] + | list[NDArray[Any]] + | torch.Tensor + | list[torch.Tensor] + | Image + | list[Image] + | None + ) = None, _ID: tuple[int, ...] = (), **kwargs: Any, ) -> Any: @@ -1864,16 +1872,16 @@ class DummyFeature(Feature): Parameters ---------- - _input: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images], optional - An optional input (image or list of images) that can be set for - the feature. By default, an empty list. + _input: Any, optional + An optional input (typically an image or list of images) that can be + set for the feature. It defaults to an empty list []. **kwargs: dict of str to Any Additional keyword arguments are wrapped as `Property` instances and stored in `self.properties`. Methods ------- - `get(image: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images], **kwargs: Any) -> np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images]` + `get(image: Any, **kwargs: Any) -> Any` It simply returns the input image(s) unchanged. Examples @@ -1903,23 +1911,9 @@ class DummyFeature(Feature): def get( self: DummyFeature, - image: ( - NDArray - | list[NDArray] - | torch.Tensor - | list[torch.Tensor] - | Image - | list[Image] - ), + image: Any, **kwargs: Any, - ) -> ( - NDArray - | list[NDArray] - | torch.Tensor - | list[torch.Tensor] - | Image - | list[Image] - ): + ) -> Any: """Return the input image or list of images unchanged. This method simply returns the input without any transformation. @@ -1928,8 +1922,9 @@ def get( Parameters ---------- - image: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images] - The image or list of images to pass through without modification. + image: Any + The input (typically an image or list of images) to pass through + without modification. **kwargs: Any Additional properties sampled from `self.properties` or passed externally. These are unused here but provided for consistency @@ -1937,8 +1932,9 @@ def get( Returns ------- - np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images] - The same image that was passed in. + Any + The same input that was passed in (typically an image or list of + images). """ @@ -2051,7 +2047,7 @@ def __init__( super().__init__(value=value, **kwargs) def get( - self: Feature, + self: Value, image: Any, value: float | ArrayLike, **kwargs: Any, @@ -2088,7 +2084,10 @@ class ArithmeticOperationFeature(Feature): This feature performs an arithmetic operation (e.g., addition, subtraction, multiplication) on the input data. The inputs can be single values or lists - of values. If a list is passed, the operation is applied to each element. + of values. + + If a list is passed, the operation is applied to each element. + If both inputs are lists of different lengths, the shorter list is cycled. Parameters @@ -2097,7 +2096,7 @@ class ArithmeticOperationFeature(Feature): The arithmetic operation to apply, such as a built-in operator (`operator.add`, `operator.mul`) or a custom callable. value: float or int or list of float or int, optional - The second operand for the operation. Defaults to 0. If a list is + The second operand for the operation. It defaults to 0. If a list is provided, the operation will apply element-wise. **kwargs: dict of str to Any Additional keyword arguments passed to the parent `Feature`. @@ -2110,7 +2109,7 @@ class ArithmeticOperationFeature(Feature): Methods ------- - `get(image: Any | list of Any, value: float | int | list[float] | int, **kwargs: dict[str, Any]) -> list[Any]` + `get(image: Any | list of Any, value: float | int | list[float] | int, **kwargs: Any) -> list[Any]` Apply the arithmetic operation element-wise to the input data. Examples @@ -2133,33 +2132,35 @@ class ArithmeticOperationFeature(Feature): __distributed__: bool = False - def __init__( - self: Feature, + self: ArithmeticOperationFeature, op: Callable[[Any, Any], Any], - value: float | int | list[float | int] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[float | int | list[float | int]] = 0, + **kwargs: Any, ): """Initialize the ArithmeticOperationFeature. Parameters ---------- op: Callable[[Any, Any], Any] - The arithmetic operation to apply, such as `operator.add`, `operator.mul`, - or any custom callable that takes two arguments. - value: float or int or list of float or int, optional + The arithmetic operation to apply, such as `operator.add`, + `operator.mul`, or any custom callable that takes two arguments and + returns a single output value. + value: PropertyLike[float or int or list of float or int], optional The second operand(s) for the operation. If a list is provided, the - operation is applied element-wise. Defaults to 0. + operation is applied element-wise. It defaults to 0. **kwargs: dict of str to Any - Additional keyword arguments passed to the parent `Feature` constructor. + Additional keyword arguments passed to the parent `Feature` + constructor. """ super().__init__(value=value, **kwargs) + self.op = op def get( - self: Feature, + self: ArithmeticOperationFeature, image: Any | list[Any], value: float | int | list[float | int], **kwargs: Any, @@ -2171,7 +2172,7 @@ def get( image: Any or list of Any The input data, either a single value or a list of values, to be transformed by the arithmetic operation. - value: float, int, or list of float or int + value: float or int or list of float or int The second operand(s) for the operation. If a single value is provided, it is broadcast to match the input size. If a list is provided, it will be cycled to match the length of the input list. From a1f71d71e4fa6cf934b2bea59c359786e8f3170c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 11:59:45 +0200 Subject: [PATCH 011/223] Update test_features.py --- deeptrack/tests/test_features.py | 77 +++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 6cd50d4af..5d9dc8fa9 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -733,7 +733,7 @@ def test_Value(self): def test_ArithmeticOperationFeature(self): - + # Basic addition with lists addition_feature = \ features.ArithmeticOperationFeature(operator.add, value=10) input_values = [1, 2, 3, 4] @@ -741,6 +741,81 @@ def test_ArithmeticOperationFeature(self): output = addition_feature(input_values) self.assertEqual(output, expected_output) + # Scalar input and scalar value + output = addition_feature(5) + self.assertEqual(output, 15) + + # List input, scalar value (broadcast) + input_values = [10, 20, 30] + output = addition_feature(input_values) + self.assertEqual(output, [20, 30, 40]) + + # List input, list value (same length) + addition_feature = features.ArithmeticOperationFeature( + operator.add, value=[1, 2, 3], + ) + input_values = [10, 20, 30] + self.assertEqual(addition_feature(input_values), [11, 22, 33]) + + # List input, list value (different lengths, value list cycles) + addition_feature = features.ArithmeticOperationFeature( + operator.add, value=[1, 2], + ) + input_values = [10, 20, 30, 40, 50] + # value cycles as 1,2,1,2,1 + self.assertEqual(addition_feature(input_values), [11, 22, 31, 42, 51]) + + # NumPy array input, scalar value + addition_feature = features.ArithmeticOperationFeature( + operator.add, value=5, + ) + arr = np.array([1, 2, 3]) + self.assertEqual(addition_feature(arr.tolist()), [6, 7, 8]) + + # NumPy array input, NumPy array value + addition_feature = features.ArithmeticOperationFeature( + operator.add, value=[4, 5, 6], + ) + arr_input = [ + np.array([1, 2]), np.array([3, 4]), np.array([5, 6]), + ] + arr_value = [ + np.array([10, 20]), np.array([30, 40]), np.array([50, 60]), + ] + feature = features.ArithmeticOperationFeature( + lambda a, b: np.add(a, b), value=arr_value, + ) + for output, expected in zip( + feature(arr_input), + [np.array([11, 22]), np.array([33, 44]), np.array([55, 66])], + ): + self.assertTrue(np.array_equal(output, expected)) + + # PyTorch tensor input (if available) + if TORCH_AVAILABLE: + import torch + + addition_feature = features.ArithmeticOperationFeature( + lambda a, b: a + b, value=5, + ) + tensors = [torch.tensor(1), torch.tensor(2), torch.tensor(3)] + expected = [torch.tensor(6), torch.tensor(7), torch.tensor(8)] + output = addition_feature(tensors) + for out, exp in zip(output, expected): + self.assertTrue(torch.equal(out, exp)) + + # Tensor input, tensor value (elementwise) + t1 = [torch.tensor([1.0, 2.0]), torch.tensor([3.0, 4.0])] + t2 = [torch.tensor([10.0, 20.0]), torch.tensor([30.0, 40.0])] + feature = features.ArithmeticOperationFeature( + lambda a, b: a + b, value=t2, + ) + for output, expected in zip( + feature(t1), + [torch.tensor([11.0, 22.0]), torch.tensor([33.0, 44.0])], + ): + self.assertTrue(torch.equal(output, expected)) + def test_Add(self): test_operator(self, operator.add) From b1ca6ca0e5d1f739cfd9bacc636ab9a4af807fa6 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:06:08 +0200 Subject: [PATCH 012/223] Update features.py --- deeptrack/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 0b424d410..70a440a2c 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2239,8 +2239,8 @@ class Add(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[float | int | list[float | int]] = 0, + **kwargs: Any, ): """Initialize the Add feature. From 0a78b0f45b5174790c64fc14d43d53390d2af9ad Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:06:10 +0200 Subject: [PATCH 013/223] Update test_features.py --- deeptrack/tests/test_features.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 5d9dc8fa9..2c39ff882 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -805,13 +805,13 @@ def test_ArithmeticOperationFeature(self): self.assertTrue(torch.equal(out, exp)) # Tensor input, tensor value (elementwise) - t1 = [torch.tensor([1.0, 2.0]), torch.tensor([3.0, 4.0])] - t2 = [torch.tensor([10.0, 20.0]), torch.tensor([30.0, 40.0])] + t_input = [torch.tensor([1.0, 2.0]), torch.tensor([3.0, 4.0])] + t_value = [torch.tensor([10.0, 20.0]), torch.tensor([30.0, 40.0])] feature = features.ArithmeticOperationFeature( - lambda a, b: a + b, value=t2, + lambda a, b: a + b, value=t_value, ) for output, expected in zip( - feature(t1), + feature(t_input), [torch.tensor([11.0, 22.0]), torch.tensor([33.0, 44.0])], ): self.assertTrue(torch.equal(output, expected)) From 2ea065bf67734e718a2e7ce4f7f8905ab9e670b0 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:36:46 +0200 Subject: [PATCH 014/223] Update aberrations.py --- deeptrack/aberrations.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/deeptrack/aberrations.py b/deeptrack/aberrations.py index 6c9b41b3f..7256c3ceb 100644 --- a/deeptrack/aberrations.py +++ b/deeptrack/aberrations.py @@ -397,7 +397,7 @@ def get( n: int | list[int], m: int | list[int], coefficient: float | list[float], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Applies the Zernike phase aberration to the input pupil function. @@ -564,7 +564,7 @@ def __init__( self: "Piston", *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the Piston class. @@ -623,7 +623,7 @@ def __init__( self: VerticalTilt, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the VerticalTilt class. @@ -682,7 +682,7 @@ def __init__( self: HorizontalTilt, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the HorizontalTilt class. @@ -743,7 +743,7 @@ def __init__( self: ObliqueAstigmatism, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the ObliqueAstigmatism class. @@ -802,7 +802,7 @@ def __init__( self: Defocus, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the Defocus class. @@ -861,7 +861,7 @@ def __init__( self: Astigmatism, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the Astigmatism class. @@ -909,7 +909,7 @@ def __init__( self: ObliqueTrefoil, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: super().__init__(*args, n=3, m=-3, coefficient=coefficient, **kwargs) @@ -930,7 +930,7 @@ def __init__( self: VerticalComa, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: super().__init__(*args, n=3, m=-1, coefficient=coefficient, **kwargs) @@ -951,7 +951,7 @@ def __init__( self: HorizontalComa, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: super().__init__(*args, n=3, m=1, coefficient=coefficient, **kwargs) @@ -972,7 +972,7 @@ def __init__( self: Trefoil, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: super().__init__(*args, n=3, m=3, coefficient=coefficient, **kwargs) @@ -993,6 +993,6 @@ def __init__( self: SphericalAberration, *args: tuple[Any, ...], coefficient: PropertyLike[float | list[float]] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: super().__init__(*args, n=4, m=0, coefficient=coefficient, **kwargs) From d1b8d34c86f6e98d1b3c1ad98d57ebca5a51afa5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:36:49 +0200 Subject: [PATCH 015/223] Update optics.py --- deeptrack/optics.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/deeptrack/optics.py b/deeptrack/optics.py index 9b29ca06c..b014855fd 100644 --- a/deeptrack/optics.py +++ b/deeptrack/optics.py @@ -89,7 +89,7 @@ def _create_volume( pad: int, output_region: Tuple[int, int, int, int], refractive_index_medium: float, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> np.ndarray Combines multiple scatterer objects into a single 3D volume for imaging. @@ -101,7 +101,7 @@ def _pad_volume( limits: np.ndarray, padding: Tuple[int, int, int, int], output_region: Tuple[int, int, int, int], - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> Tuple[np.ndarray, np.ndarray] Pads a volume with zeros to avoid edge effects during imaging. @@ -205,7 +205,7 @@ def __init__( self: Microscope, sample: Feature, objective: Feature, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> None: """Initialize the `Microscope` instance. @@ -490,7 +490,7 @@ def __init__( pupil: Feature = None, illumination: Feature = None, upscale: int = 1, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> None: """Initialize the `Optics` instance. @@ -687,7 +687,7 @@ def _pupil( refractive_index_medium: float, include_aberration: bool = True, defocus: float | ArrayLike[float] = 0, - **kwargs: Dict[str, Any], + **kwargs: Any, ): """Calculates the pupil function at different focal points. @@ -791,7 +791,7 @@ def _pad_volume( limits: ArrayLike[int] = None, padding: ArrayLike[int] = None, output_region: ArrayLike[int] = None, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> tuple: """Pads the volume with zeros to avoid edge effects. @@ -885,7 +885,7 @@ def _pad_volume( def __call__( self: Optics, sample: Feature, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> Microscope: """Creates a Microscope instance with the given sample and optics. @@ -1250,7 +1250,7 @@ def get( illuminated_volume: ArrayLike[complex], limits: ArrayLike[int], fields: ArrayLike[complex], - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> Image: """Simulates imaging with brightfield microscopy. @@ -1507,7 +1507,7 @@ def __init__( self: ISCAT, illumination_angle: float = np.pi, amp_factor: float = 1, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the ISCAT class. @@ -1617,7 +1617,7 @@ def get( illuminated_volume: ArrayLike[complex], limits: ArrayLike[int], fields: ArrayLike[complex], - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> Image: """Retrieve the darkfield image of the illuminated volume. @@ -1698,7 +1698,7 @@ def __init__( constant: PropertyLike[float] = 0, vmin: PropertyLike[float] = 0, vmax: PropertyLike[float] = np.inf, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> None: """Initializes the IlluminationGradient class. @@ -1731,7 +1731,7 @@ def get( constant: float, vmin: float, vmax: float, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> ArrayLike[complex]: """Applies the gradient and constant offset to the amplitude of the field. @@ -1858,7 +1858,7 @@ def _create_volume( pad: tuple = (0, 0, 0, 0), output_region: tuple = (None, None, None, None), refractive_index_medium: float = 1.33, - **kwargs: Dict[str, Any], + **kwargs: Any, ) -> tuple: """Converts a list of scatterers into a volumetric representation. From 84fecbd9cbac0a936d9b7bf4e91262b195757556 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:36:52 +0200 Subject: [PATCH 016/223] Update image.py --- deeptrack/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/image.py b/deeptrack/image.py index 75fd0d7fe..20ecd4456 100644 --- a/deeptrack/image.py +++ b/deeptrack/image.py @@ -1004,7 +1004,7 @@ def __array_ufunc__( ufunc: np.ufunc, method: str, *inputs: tuple[Any, ...], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | tuple[Image, ...] | None: """Enable Image objects to use NumPy ufuncs. @@ -1211,7 +1211,7 @@ def __torch_function__(self, func, types, args=(), kwargs=None): def __array__( self: Image | np.ndarray, *args: tuple[Any, ...], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Convert the Image object to a NumPy array. From 6eafa18dcc74a97360df7e743c04f40890dade88 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:36:54 +0200 Subject: [PATCH 017/223] Update holography.py --- deeptrack/holography.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/deeptrack/holography.py b/deeptrack/holography.py index db1a93fc7..994d362fb 100644 --- a/deeptrack/holography.py +++ b/deeptrack/holography.py @@ -188,7 +188,7 @@ def get( self: Rescale, image: Image | np.ndarray, rescale: float, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | np.ndarray: """Rescales the image by subtracting the real part of the field before multiplication. @@ -199,7 +199,7 @@ def get( The image to rescale. rescale: float The rescaling factor. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments. Returns @@ -253,7 +253,7 @@ def get( self: FourierTransform, image: Image | np.ndarray, padding: int = 32, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Computes the Fourier transform of the image. @@ -263,7 +263,7 @@ def get( The image to transform. padding: int, optional Number of pixels to pad symmetrically around the image (default is 32). - **kwargs: dict of str to Any + **kwargs: Any Returns ------- @@ -329,7 +329,7 @@ def get( self: InverseFourierTransform, image: Image | np.ndarray, padding: int = 32, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | np.ndarray: """Computes the inverse Fourier transform and removes padding. @@ -340,7 +340,7 @@ def get( padding: int, optional Number of pixels removed symmetrically after inverse transformation (default is 32). - **kwargs: dict of str to Any + **kwargs: Any Returns ------- @@ -404,7 +404,7 @@ def get( Tz: np.ndarray, Tzinv: np.ndarray, i: int, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | np.ndarray: """Applies the power of the propagation matrix to the image. @@ -419,7 +419,7 @@ def get( i: int Power of the propagation matrix to apply. Negative values apply the inverse. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments. Returns From addebc90da79b2d77a2906c6cbe4a580fe66c0e1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:36:59 +0200 Subject: [PATCH 018/223] Update features.py --- deeptrack/features.py | 489 ++++++++++++++++++++++++------------------ 1 file changed, 280 insertions(+), 209 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 70a440a2c..e78a03b1d 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -438,7 +438,7 @@ def __init__( | Image | list[Image] ) = [], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> None: """Initialize a new Feature instance. @@ -447,7 +447,7 @@ def __init__( _input: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images], optional The initial input(s) for the feature, often images or other data. If not provided, defaults to an empty list. - **kwargs: dict of str to Any + **kwargs: Any Keyword arguments that are wrapped into `Property` instances and stored in `self.properties`, allowing for dynamic or parameterized behavior. @@ -493,7 +493,7 @@ def __init__( def get( self: Feature, image: np.ndarray | list[np.ndarray] | Image | list[Image], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | list[Image]: """Transform an image [abstract method]. @@ -504,7 +504,7 @@ def get( ---------- image: np.ndarray or list of np.ndarray or Image or list of Images The image or list of images to transform. - **kwargs: dict of str to Any + **kwargs: Any The current value of all properties in `properties`, as well as any global arguments passed to the feature. @@ -525,7 +525,9 @@ def get( def __call__( self: Feature, image_list: ( - NDArray[Any] + Feature + | list[Feature] + | NDArray[Any] | list[NDArray[Any]] | torch.Tensor | list[torch.Tensor] @@ -551,7 +553,7 @@ def __call__( image_list: np.ndarrray or list[np.ndarrray] or Image or list of Images, optional The input to the feature or pipeline. If `None`, the feature uses previously set input values or propagates properties. - **kwargs: dict of str to Any + **kwargs: Any Additional parameters passed to the pipeline. These override properties with matching names. For example, calling `feature(x, value=4)` executes `feature` on the input `x` while @@ -1531,7 +1533,7 @@ def _process_output(self): def _image_wrapped_format_input( self: Feature, image_list: np.ndarray | list[np.ndarray] | Image | list[Image], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> list[Image]: """Wraps input data as Image instances before processing. @@ -1548,7 +1550,7 @@ def _image_wrapped_format_input( def _no_wrap_format_input( self: Feature, image_list: np.ndarray | list[np.ndarray] | Image | list[Image], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> list[Image]: """Processes input data without wrapping it as Image instances. @@ -1746,7 +1748,7 @@ class Chain(StructuralFeature): feature_2: Feature The second feature in the chain, which processes the output from `feature_1`. - **kwargs: dict of str to Any, optional + **kwargs: Any, optional Additional keyword arguments passed to the parent `StructuralFeature` (and, therefore, `Feature`). @@ -1793,7 +1795,7 @@ def __init__( self: Feature, feature_1: Feature, feature_2: Feature, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the chain with two sub-features. @@ -1808,7 +1810,7 @@ def __init__( The first feature to be applied. feature_2: Feature The second feature, applied after `feature_1`. - **kwargs: dict of str to Any, optional + **kwargs: Any, optional Additional keyword arguments passed to the parent constructor (e.g., name, properties). @@ -1823,7 +1825,7 @@ def get( self: Feature, image: np.ndarray | list[np.ndarray] | Image | list[Image], _ID: tuple[int, ...] = (), - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | list[Image]: """Apply the two features sequentially to the given input image(s). @@ -1836,9 +1838,9 @@ def get( The input data, which can be an `Image` or a list of `Image` objects, to transform sequentially. _ID: tuple of int, optional - A unique identifier for caching or parallel execution. Defaults to an - empty tuple. - **kwargs: dict of str to Any + A unique identifier for caching or parallel execution. It defaults + to an empty tuple. + **kwargs: Any Additional parameters passed to or sampled by the features. These are generally unused here, as each sub-feature fetches its required properties internally. @@ -1875,7 +1877,7 @@ class DummyFeature(Feature): _input: Any, optional An optional input (typically an image or list of images) that can be set for the feature. It defaults to an empty list []. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments are wrapped as `Property` instances and stored in `self.properties`. @@ -1956,7 +1958,7 @@ class Value(Feature): The numerical value to store. It defaults to 0. If an `Image` is provided, a warning is issued recommending conversion to a NumPy array or a PyTorch tensor for performance reasons. - **kwargs: dict of str to Any + **kwargs: Any Additional named properties passed to the `Feature` constructor. Attributes @@ -2098,7 +2100,7 @@ class ArithmeticOperationFeature(Feature): value: float or int or list of float or int, optional The second operand for the operation. It defaults to 0. If a list is provided, the operation will apply element-wise. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature`. Attributes @@ -2135,7 +2137,12 @@ class ArithmeticOperationFeature(Feature): def __init__( self: ArithmeticOperationFeature, op: Callable[[Any, Any], Any], - value: PropertyLike[float | int | list[float | int]] = 0, + value: PropertyLike[ + float + | int + | ArrayLike + | list[float | int | ArrayLike] + ] = 0, **kwargs: Any, ): """Initialize the ArithmeticOperationFeature. @@ -2146,10 +2153,10 @@ def __init__( The arithmetic operation to apply, such as `operator.add`, `operator.mul`, or any custom callable that takes two arguments and returns a single output value. - value: PropertyLike[float or int or list of float or int], optional + value: PropertyLike[float or int or array, or list of float or int or array], optional The second operand(s) for the operation. If a list is provided, the operation is applied element-wise. It defaults to 0. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` constructor. @@ -2162,7 +2169,7 @@ def __init__( def get( self: ArithmeticOperationFeature, image: Any | list[Any], - value: float | int | list[float | int], + value: float | int | ArrayLike | list[float | int | ArrayLike], **kwargs: Any, ) -> list[Any]: """Apply the operation element-wise to the input data. @@ -2172,11 +2179,11 @@ def get( image: Any or list of Any The input data, either a single value or a list of values, to be transformed by the arithmetic operation. - value: float or int or list of float or int + value: float or int or array, or list of float or int or array The second operand(s) for the operation. If a single value is provided, it is broadcast to match the input size. If a list is provided, it will be cycled to match the length of the input list. - **kwargs: dict of str to Any + **kwargs: Any Additional parameters or property overrides. These are generally unused in this context but provided for compatibility with the `Feature` interface. @@ -2210,9 +2217,9 @@ class Add(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to add to the input. Defaults to 0. - **kwargs: dict of str to Any + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to add to the input. It defaults to 0. + **kwargs: Any Additional keyword arguments passed to the parent constructor. Examples @@ -2226,29 +2233,40 @@ class Add(ArithmeticOperationFeature): Alternatively, the pipeline can be created using operator overloading: >>> pipeline = dt.Value([1, 2, 3]) + 5 + >>> pipeline.resolve() + [6, 7, 8] Or: >>> pipeline = 5 + dt.Value([1, 2, 3]) + >>> pipeline.resolve() + [6, 7, 8] Or, more explicitly: >>> input_value = dt.Value([1, 2, 3]) >>> sum_feature = dt.Add(value=5) >>> pipeline = sum_feature(input_value) + >>> pipeline.resolve() + [6, 7, 8] """ def __init__( self: Feature, - value: PropertyLike[float | int | list[float | int]] = 0, + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, **kwargs: Any, ): """Initialize the Add feature. Parameters ---------- - value: PropertyLike[float], optional - The value to add to the input. Defaults to 0. - **kwargs: dict of str to Any + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to add to the input. It defaults to 0. + **kwargs: Any Additional keyword arguments passed to the parent `Feature`. """ @@ -2263,9 +2281,9 @@ class Subtract(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to subtract from the input. Defaults to 0. - **kwargs: dict of str to Any + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to subtract from the input. It defaults to 0. + **kwargs: Any Additional keyword arguments passed to the parent constructor. Examples @@ -2292,16 +2310,21 @@ class Subtract(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the Subtract feature. Parameters ---------- - value: PropertyLike[float], optional - The value to subtract from the input. Defaults to 0. - **kwargs: dict of str to Any + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to subtract from the input. it defaults to 0. + **kwargs: Any Additional keyword arguments passed to the parent `Feature`. """ @@ -2316,8 +2339,8 @@ class Multiply(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to multiply the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to multiply the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2345,15 +2368,20 @@ class Multiply(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the Multiply feature. Parameters ---------- value: PropertyLike[float], optional - The value to multiply the input. Defaults to 0. + The value to multiply the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2369,8 +2397,8 @@ class Divide(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to divide the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2398,15 +2426,20 @@ class Divide(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the Divide feature. Parameters ---------- - value: PropertyLike[float], optional - The value to divide the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2426,8 +2459,8 @@ class FloorDivide(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to floor-divide the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to floor-divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2455,15 +2488,20 @@ class FloorDivide(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the FloorDivide feature. Parameters ---------- - value: PropertyLike[float], optional - The value to fllor-divide the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to fllor-divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2479,8 +2517,8 @@ class Power(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to take the power of the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to take the power of the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2508,15 +2546,20 @@ class Power(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the Power feature. Parameters ---------- - value: PropertyLike[float], optional - The value to take the power of the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to take the power of the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2532,8 +2575,8 @@ class LessThan(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to compare (<) with the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to compare (<) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2561,15 +2604,20 @@ class LessThan(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the LessThan feature. Parameters ---------- - value: PropertyLike[float], optional - The value to compare (<) with the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to compare (<) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2585,8 +2633,8 @@ class LessThanOrEquals(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to compare (<=) with the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to compare (<=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2614,15 +2662,20 @@ class LessThanOrEquals(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the LessThanOrEquals feature. Parameters ---------- - value: PropertyLike[float], optional - The value to compare (<=) with the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to compare (<=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2641,8 +2694,8 @@ class GreaterThan(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to compare (>) with the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to compare (>) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2670,15 +2723,20 @@ class GreaterThan(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the GreaterThan feature. Parameters ---------- - value: PropertyLike[float], optional - The value to compare (>) with the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to compare (>) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2694,8 +2752,8 @@ class GreaterThanOrEquals(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to compare (<=) with the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to compare (<=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2723,15 +2781,20 @@ class GreaterThanOrEquals(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the GreaterThanOrEquals feature. Parameters ---------- - value: PropertyLike[float], optional - The value to compare (>=) with the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to compare (>=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2751,8 +2814,8 @@ class Equals(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float], optional - The value to compare (==) with the input. Defaults to 0. + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to compare (==) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2794,15 +2857,20 @@ class Equals(ArithmeticOperationFeature): def __init__( self: Feature, - value: PropertyLike[float] = 0, - **kwargs: dict[str, Any], + value: PropertyLike[ + float + | int + | ArrayLike[Any] + | list[float | int | ArrayLike[Any]] + ] = 0, + **kwargs: Any, ): """Initialize the Equals feature. Parameters ---------- - value: PropertyLike[float], optional - The value to compare (==) with the input. Defaults to 0. + value: PropertyLike[float or int or array, or list of float or int or array], optional + The value to compare (==) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2833,7 +2901,7 @@ class Stack(Feature): ---------- value: PropertyLike[Any] The feature or data to stack with the input. - **kwargs: dict of str to Any + **kwargs: Any Additional arguments passed to the parent `Feature` class. Attributes @@ -2869,7 +2937,7 @@ class Stack(Feature): def __init__( self: Feature, value: PropertyLike[Any], - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Stack feature. @@ -2877,7 +2945,7 @@ def __init__( ---------- value: PropertyLike[Any] The feature or data to stack with the input. - **kwargs: dict of str to Any + **kwargs: Any Additional arguments passed to the parent `Feature` class. """ @@ -2888,7 +2956,7 @@ def get( self: Feature, image: Any | list[Any], value: Any | list[Any], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> list[Any]: """Concatenate the input with the value. @@ -2902,7 +2970,7 @@ def get( value: Any or list[Any] The feature or data to stack with the input. Can be a single element or a list. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments (not used here). Returns @@ -3072,7 +3140,7 @@ class Probability(StructuralFeature): The probability (between 0 and 1) of resolving the feature. *args: list[Any], optional Positional arguments passed to the parent `StructuralFeature` class. - **kwargs: dict of str to Any, optional + **kwargs: Any, optional Additional keyword arguments passed to the parent `StructuralFeature` class. @@ -3105,7 +3173,7 @@ def __init__( feature: Feature, probability: PropertyLike[float], *args: list[Any], - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Probability feature. @@ -3117,7 +3185,7 @@ def __init__( The probability (between 0 and 1) of resolving the feature. *args: list[Any], optional Positional arguments passed to the parent `StructuralFeature` class. - **kwargs: dict of str to Any, optional + **kwargs: Any, optional Additional keyword arguments passed to the parent `StructuralFeature` class. """ @@ -3135,7 +3203,7 @@ def get( image: np.ndarray, probability: float, random_number: float, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Resolve the feature if a random number is less than the probability. @@ -3148,7 +3216,7 @@ def get( random_number: float A random number sampled to determine whether to resolve the feature. - **kwargs: dict of str to Any + **kwargs: Any Additional arguments passed to the feature's `resolve` method. Returns @@ -3184,7 +3252,7 @@ class Repeat(Feature): The feature to be repeated. N: int The number of times to apply the feature in sequence. - **kwargs: dict of str to Any + **kwargs: Any Attributes ---------- @@ -3230,7 +3298,7 @@ def __init__( self: Feature, feature: Feature, N: int, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Repeat feature. @@ -3246,7 +3314,7 @@ def __init__( N: int The number of times to sequentially apply `feature`, passing the output of each iteration as the input to the next. - **kwargs: dict of str to Any + **kwargs: Any Keyword arguments that override properties dynamically at each iteration and are also passed to the parent `Feature` class. @@ -3260,7 +3328,7 @@ def get( image: Any, N: int, _ID: tuple[int, ...] = (), - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Any: """Sequentially apply the feature `N` times. @@ -3278,7 +3346,7 @@ def get( _ID: tuple[int, ...], optional A unique identifier for tracking the iteration index, ensuring reproducibility, caching, and dynamic property updates. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the feature. Returns @@ -3314,7 +3382,7 @@ class Combine(StructuralFeature): features: list of Features A list of features to combine. Each feature will be resolved in the order they appear in the list. - **kwargs: dict of str to Any, optional + **kwargs: Any, optional Additional keyword arguments passed to the parent `StructuralFeature` class. @@ -3358,7 +3426,7 @@ def __init__( features: list of Features A list of features to combine. Each feature is added as a dependency to ensure proper execution in the computation graph. - **kwargs: dict of str to Any, optional + **kwargs: Any, optional Additional keyword arguments passed to the parent `StructuralFeature` class. @@ -3378,7 +3446,7 @@ def get( ---------- image_list: Any The input image or list of images to process. - **kwargs: dict of str to Any + **kwargs: Any Additional arguments passed to each feature's `resolve` method. Returns @@ -3404,7 +3472,7 @@ class Slice(Feature): slices: Iterable[int | slice | ...] The slicing instructions for each dimension. Each element corresponds to a dimension in the input image. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Methods @@ -3444,7 +3512,7 @@ def __init__( PropertyLike[int] | PropertyLike[slice] | PropertyLike[...] ] ], - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Slice feature. @@ -3453,7 +3521,7 @@ def __init__( slices: list[int | slice | ...] or tuple[int | slice | ...] The slicing instructions for each dimension, specified as a list or tuple of integers, slice objects, or ellipses (`...`). - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -3464,7 +3532,7 @@ def get( self: Feature, image: np.ndarray, slices: tuple[Any, ...] | Any, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Apply the specified slices to the input image. @@ -3476,7 +3544,7 @@ def get( The slicing instructions for the input image. Each element in the tuple corresponds to a dimension in the input image. If a single element is provided, it is converted to a tuple. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments (unused in this implementation). Returns @@ -3508,7 +3576,7 @@ class Bind(StructuralFeature): ---------- feature: Feature The child feature - **kwargs: dict of str to Any + **kwargs: Any Properties to send to child Methods @@ -3547,7 +3615,7 @@ def __init__( ---------- feature: Feature The child feature to bind. - **kwargs: dict of str to Any + **kwargs: Any Properties or arguments to pass to the child feature. """ @@ -3566,7 +3634,7 @@ def get( ---------- image: Any The input data or image to process. - **kwargs: dict of str to Any + **kwargs: Any Properties or arguments to pass to the child feature during resolution. @@ -3595,7 +3663,7 @@ class BindUpdate(StructuralFeature): ---------- feature: Feature The child feature to bind with specific arguments. - **kwargs: dict of str to Any + **kwargs: Any Properties to send to the child feature during updates. Methods @@ -3644,7 +3712,7 @@ def __init__( ---------- feature: Feature The child feature to bind with specific arguments. - **kwargs: dict of str to Any + **kwargs: Any Properties to send to the child feature during updates. Warnings @@ -3677,7 +3745,7 @@ def get( ---------- image: Any The input data or image to process. - **kwargs: dict of str to Any + **kwargs: Any Properties or arguments to pass to the child feature during resolution. @@ -3784,7 +3852,7 @@ def __init__( self: Feature, feature: Feature, condition: PropertyLike[str | bool] | None = None, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the ConditionalSetProperty feature. @@ -3796,7 +3864,7 @@ def __init__( A boolean value or the name of a boolean property in the feature's property dictionary. If the condition evaluates to `True`, the specified properties are applied. - **kwargs: dict of str to Any + **kwargs: Any Properties to apply to the child feature if the condition is `True`. @@ -3812,7 +3880,7 @@ def get( self: Feature, image: Any, condition: str | bool, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Any: """Resolve the child, conditionally applying specified properties. @@ -3878,7 +3946,7 @@ class ConditionalSetFeature(StructuralFeature): The name of the conditional property or a boolean value. If a string is provided, its value is retrieved from `kwargs` or `self.properties`. If not found, the default value is `True`. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `StructuralFeature`. Attributes @@ -3950,7 +4018,7 @@ def __init__( on_false: Feature | None = None, on_true: Feature | None = None, condition: PropertyLike[str | bool] = True, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the ConditionalSetFeature. @@ -3961,8 +4029,8 @@ def __init__( on_true: Feature, optional The feature to resolve if the condition evaluates to `True`. condition: str or bool, optional - The name of the property to listen to, or a boolean value. Defaults - to `"is_label"`. + The name of the property to listen to, or a boolean value. It + defaults to `"is_label"`. **kwargs:: dict of str to Any Additional keyword arguments for the parent `StructuralFeature`. @@ -3987,7 +4055,7 @@ def get( image: Any, *, condition: str | bool, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Resolve the appropriate feature based on the condition. @@ -4080,7 +4148,7 @@ class Lambda(Feature): def __init__( self: Feature, function: Callable[..., Callable[[Image], Image]], - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Lambda feature. @@ -4105,7 +4173,7 @@ def get( self: Feature, image: np.ndarray | Image, function: Callable[[Image], Image], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image: """Apply the custom function to the input image. @@ -4120,7 +4188,7 @@ def get( function: Callable[[Image], Image] A callable function that takes an image and returns a transformed image. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments (unused in this implementation). Returns @@ -4221,7 +4289,7 @@ def get( self: Feature, list_of_images: list[np.ndarray] | list[Image], function: Callable[[list[np.ndarray] | list[Image]], np.ndarray | list[np.ndarray] | Image | list[Image]], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | list[Image]: """Apply the custom function to a list of images. @@ -4264,7 +4332,7 @@ class OneOf(Feature): key: int | None, optional The index of the feature to resolve from the collection. If not provided, a feature is selected randomly at each execution. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Attributes @@ -4309,7 +4377,7 @@ def __init__( self: Feature, collection: Iterable[Feature], key: int | None = None, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the OneOf feature. @@ -4320,7 +4388,7 @@ def __init__( key: int | None, optional The index of the feature to resolve from the collection. If not provided, a feature is selected randomly at execution. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -4365,7 +4433,7 @@ def get( image: Any, key: int, _ID: tuple[int, ...] = (), - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Any: """Apply the selected feature to the input image. @@ -4377,7 +4445,7 @@ def get( The index of the feature to apply from the collection. _ID: tuple[int, ...], optional A unique identifier for caching and parallel processing. - **kwargs: dict of str to Any + **kwargs: Any Additional parameters passed to the selected feature. Returns @@ -4408,7 +4476,7 @@ class OneOfDict(Feature): key: Any | None, optional The key of the feature to resolve from the dictionary. If `None`, a random key is selected. - **kwargs: dict of str to Any + **kwargs: Any Additional parameters passed to the parent `Feature` class. Attributes @@ -4453,7 +4521,7 @@ def __init__( self: Feature, collection: dict[Any, Feature], key: Any | None = None, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the OneOfDict feature. @@ -4464,7 +4532,7 @@ def __init__( key: Any | None, optional The key of the feature to resolve from the dictionary. If `None`, a random key is selected. - **kwargs: dict of str to Any + **kwargs: Any Additional parameters passed to the parent `Feature` class. """ @@ -4509,7 +4577,7 @@ def get( image: Any, key: Any, _ID: tuple[int, ...] = (), - **kwargs: dict[str, Any], + **kwargs: Any, )-> Any: """Resolve the selected feature and apply it to the input. @@ -4521,7 +4589,7 @@ def get( The key of the feature to apply from the dictionary. _ID: tuple[int, ...], optional A unique identifier for caching and parallel execution. - **kwargs: dict of str to Any + **kwargs: Any Additional parameters passed to the selected feature. Returns @@ -4549,17 +4617,18 @@ class LoadImage(Feature): The path(s) to the image(s) to load. Can be a single string or a list of strings. load_options: PropertyLike[dict[str, Any]], optional - Additional options passed to the file reader. Defaults to `None`. + Additional options passed to the file reader. It defaults to `None`. as_list: PropertyLike[bool], optional If `True`, the first dimension of the image will be treated as a list. - Defaults to `False`. + It defaults to `False`. ndim: PropertyLike[int], optional - Ensures the image has at least this many dimensions. Defaults to `3`. + Ensures the image has at least this many dimensions. It defaults to + `3`. to_grayscale: PropertyLike[bool], optional - If `True`, converts the image to grayscale. Defaults to `False`. + If `True`, converts the image to grayscale. It defaults to `False`. get_one_random: PropertyLike[bool], optional If `True`, extracts a single random image from a stack of images. Only - used when `as_list` is `True`. Defaults to `False`. + used when `as_list` is `True`. It defaults to `False`. Attributes ---------- @@ -4612,7 +4681,7 @@ def __init__( ndim: PropertyLike[int] = 3, to_grayscale: PropertyLike[bool] = False, get_one_random: PropertyLike[bool] = False, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the LoadImage feature. @@ -4623,19 +4692,20 @@ def __init__( of strings. load_options: PropertyLike[dict[str, Any]], optional Additional options passed to the file reader (e.g., `mode` for OpenCV, - `allow_pickle` for NumPy). Defaults to `None`. + `allow_pickle` for NumPy). It defaults to `None`. as_list: PropertyLike[bool], optional If `True`, treats the first dimension of the image as a list of images. - Defaults to `False`. + It defaults to `False`. ndim: PropertyLike[int], optional Ensures the image has at least this many dimensions. If the loaded image - has fewer dimensions, extra dimensions are added. Defaults to `3`. + has fewer dimensions, extra dimensions are added. It defaults to + `3`. to_grayscale: PropertyLike[bool], optional - If `True`, converts the image to grayscale. Defaults to `False`. + If `True`, converts the image to grayscale. It defaults to `False`. get_one_random: PropertyLike[bool], optional If `True`, selects a single random image from a stack when `as_list=True`. - Defaults to `False`. - **kwargs: dict of str to Any + It defaults to `False`. + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class, allowing further customization. @@ -4660,7 +4730,7 @@ def get( to_grayscale: bool, as_list: bool, get_one_random: bool, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Load and process an image or a list of images from disk. @@ -4677,18 +4747,18 @@ def get( loads one image, while a list of paths loads multiple images. load_options: dict of str to Any, optional Additional options passed to the file reader (e.g., `allow_pickle` - for NumPy, `mode` for OpenCV). Defaults to `None`. + for NumPy, `mode` for OpenCV). It defaults to `None`. ndim: int Ensures the image has at least this many dimensions. If the loaded image has fewer dimensions, extra dimensions are added. to_grayscale: bool - If `True`, converts the image to grayscale. Defaults to `False`. + If `True`, converts the image to grayscale. It defaults to `False`. as_list: bool If `True`, treats the first dimension as a list of images instead of stacking them into a NumPy array. get_one_random: bool If `True`, selects a single random image from a multi-frame stack - when `as_list=True`. Defaults to `False`. + when `as_list=True`. It defaults to `False`. **kwargs: dict[str, Any] Additional keyword arguments. @@ -4892,7 +4962,7 @@ def get( self: Feature, image: np.ndarray | Image, transformation_function: Callable[[Image], Image], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image: """Apply the transformation function to a single image. @@ -4917,7 +4987,7 @@ def get( def _process_and_get( self: Feature, images: list[np.ndarray] | np.ndarray | list[Image] | Image, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Image | np.ndarray: """Process a list of images and generate a multi-layer mask. @@ -5072,7 +5142,7 @@ class AsType(Feature): Parameters ---------- dtype: PropertyLike[Any], optional - The desired data type for the image. Defaults to `"float64"`. + The desired data type for the image. It defaults to `"float64"`. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -5104,7 +5174,7 @@ class AsType(Feature): def __init__( self: Feature, dtype: PropertyLike[Any] = "float64", - **kwargs: dict[str, Any], + **kwargs: Any, ): """ Initialize the AsType feature. @@ -5112,7 +5182,7 @@ def __init__( Parameters ---------- dtype: PropertyLike[Any], optional - The desired data type for the image. Defaults to `"float64"`. + The desired data type for the image. It defaults to `"float64"`. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -5124,7 +5194,7 @@ def get( self: Feature, image: np.ndarray, dtype: str, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Convert the data type of the input image. @@ -5158,7 +5228,7 @@ class ChannelFirst2d(Feature): Parameters ---------- axis: int, optional - The axis to move to the first position. Defaults to `-1` (last axis). + The axis to move to the first position. It defaults to `-1` (last axis). **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -5198,7 +5268,7 @@ class ChannelFirst2d(Feature): def __init__( self: Feature, axis: int = -1, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the ChannelFirst2d feature. @@ -5206,7 +5276,7 @@ def __init__( ---------- axis: int, optional The axis to move to the first position. - Defaults to `-1` (last axis). + It defaults to `-1` (last axis). **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -5218,7 +5288,7 @@ def get( self: Feature, image: np.ndarray, axis: int, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Rearrange the axes of an image to channel-first format. @@ -5280,8 +5350,8 @@ class Upscale(Feature): factor: int or tuple[int, int, int], optional The factor by which to upscale the simulation. If a single integer is provided, it is applied uniformly across all axes. If a tuple of three - integers is provided, each axis is scaled individually. Defaults to 1. - **kwargs: dict of str to Any + integers is provided, each axis is scaled individually. It defaults to 1. + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Attributes @@ -5346,7 +5416,7 @@ def __init__( self: Feature, feature: Feature, factor: int | tuple[int, int, int] = 1, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Upscale feature. @@ -5358,8 +5428,8 @@ def __init__( The factor by which to upscale the simulation. If a single integer is provided, it is applied uniformly across all axes. If a tuple of three integers is provided, each axis is scaled individually. - Defaults to `1`. - **kwargs: dict of str to Any + It defaults to `1`. + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -5371,7 +5441,7 @@ def get( self: Feature, image: np.ndarray, factor: int | tuple[int, int, int], - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Simulate the pipeline at a higher resolution and return result. @@ -5383,7 +5453,7 @@ def get( The factor by which to upscale the simulation. If a single integer is provided, it is applied uniformly across all axes. If a tuple of three integers is provided, each axis is scaled individually. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the feature. Returns @@ -5443,14 +5513,14 @@ class NonOverlapping(Feature): The feature that generates the list of volumes to place non-overlapping. min_distance: float, optional - The minimum distance between volumes in pixels. Defaults to `1`. + The minimum distance between volumes in pixels. It defaults to `1`. It can be negative to allow for partial overlap. max_attempts: int, optional The maximum number of attempts to place volumes without overlap. - Defaults to `5`. + It defaults to `5`. max_iters: int, optional The maximum number of resamplings. If this number is exceeded, a - new list of volumes is generated. Defaults to `100`. + new list of volumes is generated. It defaults to `100`. Attributes ---------- @@ -5564,7 +5634,7 @@ def __init__( min_distance: float = 1, max_attempts: int = 5, max_iters: int = 100, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initializes the NonOverlapping feature. @@ -5578,13 +5648,14 @@ def __init__( The feature that generates the list of volumes. min_distance: float, optional The minimum separation distance **between volume edges**, in - pixels. Defaults to `1`. Negative values allow for partial overlap. + pixels. It defaults to `1`. Negative values allow for partial + overlap. max_attempts: int, optional The maximum number of attempts to place the volumes without - overlap. Defaults to `5`. + overlap. It defaults to `5`. max_iters: int, optional The maximum number of resampling iterations per attempt. If - exceeded, a new list of volumes is generated. Defaults to `100`. + exceeded, a new list of volumes is generated. It defaults to `100`. """ @@ -5601,7 +5672,7 @@ def get( min_distance: float, max_attempts: int, max_iters: int, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> list[np.ndarray]: """Generates a list of non-overlapping 3D volumes within a defined field of view (FOV). @@ -6097,7 +6168,7 @@ class Store(Feature): key: Any The key used to identify the stored output. replace: bool, optional - If `True`, replaces the stored value with a new computation. Defaults + If `True`, replaces the stored value with a new computation. It defaults to `False`. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -6149,7 +6220,7 @@ def __init__( feature: Feature, key: Any, replace: bool = False, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Store feature. @@ -6161,7 +6232,7 @@ def __init__( The key used to identify the stored output. replace: bool, optional If `True`, replaces the stored value with a new computation. - Defaults to `False`. + It defaults to `False`. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -6176,7 +6247,7 @@ def get( _: Any, key: Any, replace: bool, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> Any: """Evaluate and store the feature output, or return the cached result. @@ -6219,7 +6290,7 @@ class Squeeze(Feature): Parameters ---------- axis: int or tuple[int, ...], optional - The axis or axes to squeeze. Defaults to `None`, squeezing all axes. + The axis or axes to squeeze. It defaults to `None`, squeezing all axes. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -6255,14 +6326,14 @@ class Squeeze(Feature): def __init__( self: Squeeze, axis: int | tuple[int, ...] | None = None, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Squeeze feature. Parameters ---------- axis: int or tuple[int, ...], optional - The axis or axes to squeeze. Defaults to `None`, which squeezes + The axis or axes to squeeze. It defaults to `None`, which squeezes all axes. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -6275,7 +6346,7 @@ def get( self: Squeeze, image: np.ndarray, axis: int | tuple[int, ...] | None = None, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Squeeze the input image by removing singleton dimensions. @@ -6284,7 +6355,7 @@ def get( image: np.ndarray The input image to process. axis: int or tuple[int, ...], optional - The axis or axes to squeeze. Defaults to `None`, which squeezes + The axis or axes to squeeze. It defaults to `None`, which squeezes all axes. **kwargs:: dict of str to Any Additional keyword arguments (unused here). @@ -6310,7 +6381,7 @@ class Unsqueeze(Feature): ---------- axis: int or tuple[int, ...], optional The axis or axes where new singleton dimensions should be added. - Defaults to `None`, which adds a singleton dimension at the last axis. + It defaults to `None`, which adds a singleton dimension at the last axis. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -6346,7 +6417,7 @@ class Unsqueeze(Feature): def __init__( self: Unsqueeze, axis: int | tuple[int, ...] | None = -1, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Unsqueeze feature. @@ -6354,7 +6425,7 @@ def __init__( ---------- axis: int or tuple[int, ...], optional The axis or axes where new singleton dimensions should be added. - Defaults to -1, which adds a singleton dimension at the last axis. + It defaults to -1, which adds a singleton dimension at the last axis. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -6366,7 +6437,7 @@ def get( self: Unsqueeze, image: np.ndarray, axis: int | tuple[int, ...] | None = -1, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Add singleton dimensions to the input image. @@ -6377,7 +6448,7 @@ def get( The input image to process. axis: int or tuple[int, ...], optional The axis or axes where new singleton dimensions should be added. - Defaults to -1, which adds a singleton dimension at the last axis. + It defaults to -1, which adds a singleton dimension at the last axis. **kwargs:: dict of str to Any Additional keyword arguments (unused here). @@ -6437,7 +6508,7 @@ def __init__( self: MoveAxis, source: int, destination: int, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the MoveAxis feature. @@ -6459,7 +6530,7 @@ def get( image: np.ndarray, source: int, destination: int, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Move the specified axis of the input image to a new position. @@ -6530,7 +6601,7 @@ class Transpose(Feature): def __init__( self: Transpose, axes: tuple[int, ...] | None = None, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the Transpose feature. @@ -6550,7 +6621,7 @@ def get( self: Transpose, image: np.ndarray, axes: tuple[int, ...] | None = None, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Transpose the axes of the input image. @@ -6618,7 +6689,7 @@ class OneHot(Feature): def __init__( self: OneHot, num_classes: int, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the OneHot feature. @@ -6637,7 +6708,7 @@ def get( self: OneHot, image: np.ndarray, num_classes: int, - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray: """Convert the input array of labels into a one-hot encoded array. @@ -6735,7 +6806,7 @@ def __init__( self: TakeProperties, feature: Feature, *names: str, - **kwargs: dict[str, Any], + **kwargs: Any, ): """Initialize the TakeProperties feature. @@ -6745,7 +6816,7 @@ def __init__( The feature from which to extract properties. *names: str One or more names of the properties to extract. -= **kwargs: dict[str, Any], optional += **kwargs: Any, optional Additional keyword arguments passed to the parent `Feature` class. """ @@ -6758,7 +6829,7 @@ def get( image: Any, names: tuple[str, ...], _ID: tuple[int, ...] = (), - **kwargs: dict[str, Any], + **kwargs: Any, ) -> np.ndarray | tuple[np.ndarray, ...]: """Extract the specified properties from the feature pipeline. @@ -6773,8 +6844,8 @@ def get( The names of the properties to extract. _ID: tuple[int, ...], optional A unique identifier for the current computation, ensuring that - dependencies are correctly matched. Defaults to an empty tuple. - **kwargs: dict[str, Any], optional + dependencies are correctly matched. It defaults to an empty tuple. + **kwargs: Any, optional Additional keyword arguments (unused in this method). Returns From 15cdd3c47789e62a5eecb4aa0f79adcd294d67ea Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 23 Jun 2025 21:39:51 +0200 Subject: [PATCH 019/223] Update features.py --- deeptrack/features.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index e78a03b1d..daac37b7c 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2251,7 +2251,7 @@ class Add(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: Add, value: PropertyLike[ float | int @@ -2309,7 +2309,7 @@ class Subtract(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: Subtract, value: PropertyLike[ float | int @@ -2367,7 +2367,7 @@ class Multiply(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: Multiply, value: PropertyLike[ float | int @@ -2425,7 +2425,7 @@ class Divide(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: Divide, value: PropertyLike[ float | int @@ -2487,7 +2487,7 @@ class FloorDivide(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: FloorDivide, value: PropertyLike[ float | int @@ -2545,7 +2545,7 @@ class Power(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: Power, value: PropertyLike[ float | int @@ -2603,7 +2603,7 @@ class LessThan(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: LessThan, value: PropertyLike[ float | int @@ -2661,7 +2661,7 @@ class LessThanOrEquals(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: LessThanOrEquals, value: PropertyLike[ float | int @@ -2722,7 +2722,7 @@ class GreaterThan(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: GreaterThan, value: PropertyLike[ float | int @@ -2780,7 +2780,7 @@ class GreaterThanOrEquals(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: GreaterThanOrEquals, value: PropertyLike[ float | int @@ -2856,7 +2856,7 @@ class Equals(ArithmeticOperationFeature): """ def __init__( - self: Feature, + self: Equals, value: PropertyLike[ float | int From ef0676130a0c35853296232b994b9023dad8e5bb Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 09:59:39 +0200 Subject: [PATCH 020/223] Update test_features.py --- deeptrack/tests/test_features.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 2c39ff882..d967dbcc2 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -859,8 +859,8 @@ def test_GreaterThanOrEquals(self): def test_Equals(self): """ - Notes - ----- + Important Notes + --------------- - Unlike other arithmetic operators, `Equals` does not define `__eq__` (`==`) and `__req__` (`==`) in `DeepTrackNode` and `Feature`, as this would affect Python’s built-in identity comparison. @@ -868,9 +868,8 @@ def test_Equals(self): expressions involving `Feature` instances but not for comparisons involving regular Python objects. - Always use `>>` to apply `Equals` correctly in a feature chain. - """ - + equals_feature = features.Equals(value=2) input_values = np.array([1, 2, 3]) output_values = equals_feature(input_values) From c8b3df67f53663e18dd7f0ccd142503a2e1dae7a Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 09:59:41 +0200 Subject: [PATCH 021/223] Update features.py --- deeptrack/features.py | 152 +++++++++++++++++++++++++++++------------- 1 file changed, 107 insertions(+), 45 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index daac37b7c..840de666a 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -153,20 +153,20 @@ def merge_features( "DummyFeature", "Value", "ArithmeticOperationFeature", - "Add", # TODO - "Subtract", # TODO - "Multiply", # TODO - "Divide", # TODO - "FloorDivide", # TODO - "Power", # TODO - "LessThan", # TODO - "LessThanOrEquals", # TODO - "LessThanOrEqual", # TODO - "GreaterThan", # TODO - "GreaterThanOrEquals", # TODO - "GreaterThanOrEqual", # TODO - "Equals", # TODO - "Equal", # TODO + "Add", + "Subtract", + "Multiply", + "Divide", + "FloorDivide", + "Power", + "LessThan", + "LessThanOrEquals", + "LessThanOrEqual", + "GreaterThan", + "GreaterThanOrEquals", + "GreaterThanOrEqual", + "Equals", + "Equal", "Stack", # TODO "Arguments", # TODO "Probability", # TODO @@ -2297,14 +2297,20 @@ class Subtract(ArithmeticOperationFeature): Alternatively, the pipeline can be created using operator overloading: >>> pipeline = dt.Value([1, 2, 3]) - 2 + >>> pipeline.resolve() + [-1, 0, 1] Or: >>> pipeline = -2 + dt.Value([1, 2, 3]) + >>> pipeline.resolve() + [-1, 0, 1] Or, more explicitly: >>> input_value = dt.Value([1, 2, 3]) >>> sub_feature = dt.Subtract(value=2) >>> pipeline = sub_feature(input_value) + >>> pipeline.resolve() + [-1, 0, 1] """ @@ -2355,14 +2361,20 @@ class Multiply(ArithmeticOperationFeature): Alternatively, this pipeline can be created using: >>> pipeline = dt.Value([1, 2, 3]) * 5 + >>> pipeline.resolve() + [5, 10, 15] Or: >>> pipeline = 5 * dt.Value([1, 2, 3]) + >>> pipeline.resolve() + [5, 10, 15] Or, more explicitly: >>> input_value = dt.Value([1, 2, 3]) >>> mul_feature = dt.Multiply(value=5) >>> pipeline = mul_feature(input_value) + >>> pipeline.resolve() + [5, 10, 15] """ @@ -2380,7 +2392,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float], optional + value: PropertyLike[float or int or array, or list of float or int or array], optional The value to multiply the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2407,20 +2419,26 @@ class Divide(ArithmeticOperationFeature): >>> import deeptrack as dt Start by creating a pipeline using `Divide`: - >>> pipeline = Value([1, 2, 3]) >> Divide(value=5) + >>> pipeline = dt.Value([1, 2, 3]) >> dt.Divide(value=5) >>> pipeline.resolve() [0.2 0.4 0.6] Equivalently, this pipeline can be created using: - >>> pipeline = Value([1, 2, 3]) / 5 + >>> pipeline = dt.Value([1, 2, 3]) / 5 + >>> pipeline.resolve() + [0.2 0.4 0.6] Which is not equivalent to: - >>> pipeline = 5 / Value([1, 2, 3]) # Different result. + >>> pipeline = 5 / dt.Value([1, 2, 3]) # Different result + >>> pipeline.resolve() + [5.0, 2.5, 1.6666666666666667] Or, more explicitly: - >>> input_value = Value([1, 2, 3]) - >>> truediv_feature = Divide(value=5) + >>> input_value = dt.Value([1, 2, 3]) + >>> truediv_feature = dt.Divide(value=5) >>> pipeline = truediv_feature(input_value) + >>> pipeline.resolve() + [0.2 0.4 0.6] """ @@ -2471,18 +2489,24 @@ class FloorDivide(ArithmeticOperationFeature): Start by creating a pipeline using `FloorDivide`: >>> pipeline = dt.Value([-3, 3, 6]) >> dt.FloorDivide(value=5) >>> pipeline.resolve() - [0.2 0.4 0.6] + [-1, 0, 1] Equivalently, this pipeline can be created using: >>> pipeline = dt.Value([-3, 3, 6]) // 5 + >>> pipeline.resolve() + [-1, 0, 1] Which is not equivalent to: - >>> pipeline = 5 // dt.Value([-3, 3, 6]) # Different result. + >>> pipeline = 5 // dt.Value([-3, 3, 6]) # Different result + >>> pipeline.resolve() + [-2, 1, 0] Or, more explicitly: >>> input_value = dt.Value([-3, 3, 6]) >>> floordiv_feature = dt.FloorDivide(value=5) - >>> pipeline = feature(floordiv_input_value) + >>> pipeline = floordiv_feature(input_value) + >>> pipeline.resolve() + [-1, 0, 1] """ @@ -2533,14 +2557,20 @@ class Power(ArithmeticOperationFeature): Equivalently, this pipeline can be created using: >>> pipeline = dt.Value([1, 2, 3]) ** 3 + >>> pipeline.resolve() + [1, 8, 27] Which is not equivalent to: - >>> pipeline = 3 ** dt.Value([1, 2, 3]) # Different result. + >>> pipeline = 3 ** dt.Value([1, 2, 3]) # Different result + >>> pipeline.resolve() + [3, 9, 27] Or, more explicitly: >>> input_value = dt.Value([1, 2, 3]) - >>> pow_feature = Power(value=3) + >>> pow_feature = dt.Power(value=3) >>> pipeline = pow_feature(input_value) + >>> pipeline.resolve() + [1, 8, 27] """ @@ -2587,18 +2617,24 @@ class LessThan(ArithmeticOperationFeature): Start by creating a pipeline using `LessThan`: >>> pipeline = dt.Value([1, 2, 3]) >> dt.LessThan(value=2) >>> pipeline.resolve() - [True False False] + [True, False, False] Equivalently, this pipeline can be created using: >>> pipeline = dt.Value([1, 2, 3]) < 2 + >>> pipeline.resolve() + [True, False, False] Which is not equivalent to: - >>> pipeline = 2 < dt.Value([1, 2, 3]) # Different result. + >>> pipeline = 2 < dt.Value([1, 2, 3]) # Different result + >>> pipeline.resolve() + [False, False, True] Or, more explicitly: >>> input_value = dt.Value([1, 2, 3]) >>> lt_feature = dt.LessThan(value=2) >>> pipeline = lt_feature(input_value) + >>> pipeline.resolve() + [True, False, False] """ @@ -2645,18 +2681,24 @@ class LessThanOrEquals(ArithmeticOperationFeature): Start by creating a pipeline using `LessThanOrEquals`: >>> pipeline = dt.Value([1, 2, 3]) >> dt.LessThanOrEquals(value=2) >>> pipeline.resolve() - [True True False] + [True, True, False] Equivalently, this pipeline can be created using: >>> pipeline = dt.Value([1, 2, 3]) <= 2 + >>> pipeline.resolve() + [True, True, False] Which is not equivalent to: - >>> pipeline = 2 <= dt.Value([1, 2, 3]) # Different result. + >>> pipeline = 2 <= dt.Value([1, 2, 3]) # Different result + >>> pipeline.resolve() + [False, True, True] Or, more explicitly: >>> input_value = dt.Value([1, 2, 3]) >>> le_feature = dt.LessThanOrEquals(value=2) >>> pipeline = le_feature(input_value) + >>> pipeline.resolve() + [True, True, False] """ @@ -2706,18 +2748,24 @@ class GreaterThan(ArithmeticOperationFeature): Start by creating a pipeline using `GreaterThan`: >>> pipeline = dt.Value([1, 2, 3]) >> dt.GreaterThan(value=2) >>> pipeline.resolve() - [False False True] + [False, False, True] Equivalently, this pipeline can be created using: >>> pipeline = dt.Value([1, 2, 3]) > 2 + >>> pipeline.resolve() + [False, False, True] Which is not equivalent to: - >>> pipeline = 2 > dt.Value([1, 2, 3]) # Different result. + >>> pipeline = 2 > dt.Value([1, 2, 3]) # Different result + >>> pipeline.resolve() + [True, False, False] Or, most explicitly: >>> input_value = dt.Value([1, 2, 3]) >>> gt_feature = dt.GreaterThan(value=2) >>> pipeline = gt_feature(input_value) + >>> pipeline.resolve() + [False, False, True] """ @@ -2764,18 +2812,24 @@ class GreaterThanOrEquals(ArithmeticOperationFeature): Start by creating a pipeline using `GreaterThanOrEquals`: >>> pipeline = dt.Value([1, 2, 3]) >> dt.GreaterThanOrEquals(value=2) >>> pipeline.resolve() - [False True True] + [False, True, True] Equivalently, this pipeline can be created using: >>> pipeline = dt.Value([1, 2, 3]) >= 2 + >>> pipeline.resolve() + [False, True, True] Which is not equivalent to: - >>> pipeline = 2 >= dt.Value([1, 2, 3]) # Different result. + >>> pipeline = 2 >= dt.Value([1, 2, 3]) # Different result + >>> pipeline.resolve() + [True, True, False] Or, more explicitly: >>> input_value = dt.Value([1, 2, 3]) >>> ge_feature = dt.GreaterThanOrEquals(value=2) >>> pipeline = ge_feature(input_value) + >>> pipeline.resolve() + [False, True, True] """ @@ -2809,16 +2863,9 @@ def __init__( class Equals(ArithmeticOperationFeature): """Determine whether input is equal to a given value. - This feature performs element-wise comparison (==) between the input and a + This feature performs element-wise comparison between the input and a specified value. - Parameters - ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional - The value to compare (==) with the input. It defaults to 0. - **kwargs: Any - Additional keyword arguments passed to the parent constructor. - Notes ----- - Unlike other arithmetic operators, `Equals` does not define `__eq__` @@ -2828,6 +2875,13 @@ class Equals(ArithmeticOperationFeature): expressions involving `Feature` instances but not for comparisons involving regular Python objects. - Always use `>>` to apply `Equals` correctly in a feature chain. + + Parameters + ---------- + value: PropertyLike[int or float or array, or list of int or floar or array], optional + The value to compare (==) with the input. It defaults to 0. + **kwargs: Any + Additional keyword arguments passed to the parent constructor. Examples -------- @@ -2836,11 +2890,19 @@ class Equals(ArithmeticOperationFeature): Start by creating a pipeline using `Equals`: >>> pipeline = dt.Value([1, 2, 3]) >> dt.Equals(value=2) >>> pipeline.resolve() - [False True False] + [False, True, False] + + Or: + >>> input_values = [1, 2, 3] + >>> eq_feature = dt.Equals(value=2) + >>> output_values = eq_feature(input_values) + >>> print(output_values) + [False, True, False] + + These are the **only correct ways** to apply `Equals` in a pipeline. - This is the **only correct way** to apply `Equals` in a feature pipeline. + The following approaches are **incorrect**: - ### Incorrect Approaches Using `==` directly on a `Feature` instance **does not work** because `Feature` does not override `__eq__`: >>> pipeline = dt.Value([1, 2, 3]) == 2 # Incorrect @@ -2870,7 +2932,7 @@ def __init__( Parameters ---------- value: PropertyLike[float or int or array, or list of float or int or array], optional - The value to compare (==) with the input. It defaults to 0. + The value to compare with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. From 17e39211759dd86185976e12f2af6a59ea46226f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 10:03:56 +0200 Subject: [PATCH 022/223] Update test_features.py --- deeptrack/tests/test_features.py | 33 ++------------------------------ 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index d967dbcc2..03b689b81 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -159,7 +159,6 @@ def test_Feature_basics(self): self.assertIsInstance(F.properties['prop_str'](), str) self.assertEqual(F.properties['prop_str'](), 'a') - def test_Feature_properties_update(self): feature = features.DummyFeature( @@ -181,7 +180,6 @@ def test_Feature_properties_update(self): prop_dict_with_update = feature.properties() self.assertNotEqual(prop_dict, prop_dict_with_update) - def test_Feature_memorized(self): list_of_inputs = [] @@ -218,7 +216,6 @@ def get(self, input, **kwargs): feature([1]) self.assertEqual(len(list_of_inputs), 4) - def test_Feature_dependence(self): A = features.Value(lambda: np.random.rand()) @@ -262,7 +259,6 @@ def test_Feature_dependence(self): self.assertEqual(D(), C() + B()) self.assertEqual(E(), D() + C()) - def test_Feature_validation(self): class ConcreteFeature(features.Feature): @@ -283,7 +279,6 @@ def get(self, input, **kwargs): feature.prop.set_value(2) # Changes value. self.assertFalse(feature.is_valid()) - def test_Feature_store_properties_in_image(self): class FeatureAddValue(features.Feature): @@ -310,7 +305,6 @@ def get(self, image, value_to_add=0, **kwargs): output_image.get_property("value_to_add", get_one=False), [1, 1] ) - def test_Feature_with_dummy_property(self): class FeatureConcreteClass(features.Feature): @@ -327,7 +321,6 @@ def get(self, *args, **kwargs): output_image.get_property("dummy_property", get_one=False), ["foo"] ) - def test_Feature_plus_1(self): class FeatureAddValue(features.Feature): @@ -350,7 +343,6 @@ def get(self, image, value_to_add=0, **kwargs): output_image.get_property("value_to_add", get_one=True), 1 ) - def test_Feature_plus_2(self): class FeatureAddValue(features.Feature): @@ -377,7 +369,6 @@ def get(self, image, value_to_multiply=0, **kwargs): output_image21 = feature21.resolve(input_image) self.assertEqual(output_image21, 1) - def test_Feature_plus_3(self): class FeatureAppendImageOfShape(features.Feature): @@ -398,7 +389,6 @@ def get(self, *args, shape, **kwargs): self.assertEqual(output_image[0].shape, (1, 1)) self.assertEqual(output_image[1].shape, (2, 2)) - def test_Feature_arithmetic(self): inp = features.DummyFeature() @@ -411,7 +401,6 @@ def test_Feature_arithmetic(self): input_2 = [10, 20] self.assertListEqual(pipeline(input_2), [-input_2[0], -input_2[1]]) - def test_Features_chain_lambda(self): value = features.Value(value=1) @@ -424,7 +413,6 @@ def test_Features_chain_lambda(self): output_image = feature() self.assertEqual(output_image, 2) - def test_Feature_repeat(self): feature = features.Value(value=0) \ @@ -435,7 +423,6 @@ def test_Feature_repeat(self): output_image = feature() self.assertEqual(np.array(output_image), np.array(n)) - def test_Feature_repeat_random(self): feature = features.Value(value=0) >> ( @@ -450,7 +437,6 @@ def test_Feature_repeat_random(self): self.assertNotEqual(num_dups, len(values)) self.assertEqual(output_image, sum(values)) - def test_Feature_repeat_nested(self): value = features.Value(0) @@ -461,7 +447,6 @@ def test_Feature_repeat_nested(self): self.assertEqual(feature(), 15) - def test_Feature_repeat_nested_random_times(self): value = features.Value(0) @@ -476,7 +461,6 @@ def test_Feature_repeat_nested_random_times(self): feature.update() self.assertEqual(feature(), feature.feature_2.N() * 5) - def test_Feature_repeat_nested_random_addition(self): value = features.Value(0) @@ -503,7 +487,6 @@ def test_Feature_repeat_nested_random_addition(self): sum(added_values) - 3 * 4, feature() ) - def test_Feature_nested_Duplicate(self): A = features.DummyFeature( @@ -544,7 +527,6 @@ def test_Feature_nested_Duplicate(self): self.assertIn(c - b, range(0, 100)) self.assertIn(dl[ci] - c, range(0, 10)) - def test_Feature_outside_dependence(self): A = features.DummyFeature( @@ -912,6 +894,7 @@ def test_Stack(self): operator.__and__, ) + def test_Arguments_feature_passing(self): """Tests that arguments are correctly passed and updated in a feature pipeline.""" @@ -952,7 +935,6 @@ def test_Arguments_feature_passing(self): second_d = arguments.d.update()() self.assertNotEqual(first_d, second_d) # Check that values change - def test_Arguments(self): from tempfile import NamedTemporaryFile from PIL import Image as PIL_Image @@ -1131,7 +1113,6 @@ def test_Slice_constant(self): self.assertEqual(a22, input[2, 2]) self.assertEqual(a12, input[1, -1]) - def test_Slice_colon(self): input = np.arange(16).reshape((4, 4)) @@ -1153,7 +1134,6 @@ def test_Slice_colon(self): self.assertEqual(a2.tolist(), input[:, 2].tolist()) self.assertEqual(a3.tolist(), input[0:2, :].tolist()) - def test_Slice_ellipse(self): input = np.arange(16).reshape((4, 4)) @@ -1175,7 +1155,6 @@ def test_Slice_ellipse(self): self.assertEqual(a2.tolist(), input[:, ...].tolist()) self.assertEqual(a3.tolist(), input[0:2, ...].tolist()) - def test_Slice_static_dynamic(self): image = np.arange(27).reshape((3, 3, 3)) expected_output = image[:, 1:2, ::-2] @@ -1216,7 +1195,6 @@ def test_Bind(self): res = pipeline_with_small_input.update(input_value=10).resolve() self.assertEqual(res, 11) - def test_Bind_gaussian_noise(self): # Define the Gaussian noise feature and bind its properties gaussian_noise = Gaussian() @@ -1299,8 +1277,7 @@ def test_BindUpdate(self): res = pipeline_with_small_input.update(input_value=10).resolve() self.assertEqual(res, 11) - - + def test_BindUpdate_gaussian_noise(self): # Define the Gaussian noise feature and bind its properties gaussian_noise = Gaussian() @@ -1356,7 +1333,6 @@ def test_ConditionalSetProperty(self): clean_image = conditional_feature.update()(image, is_noisy=False) self.assertEqual(clean_image.std(), 0) - def test_ConditionalSetFeature(self): """Set up Gaussian noise features and test image before each test.""" @@ -1416,7 +1392,6 @@ def test_Lambda_dependence(self): B.key.set_value("c") self.assertEqual(B.prop(), 3) - def test_Lambda_dependence_twice(self): A = features.DummyFeature(a=1, b=2, c=3) @@ -1436,7 +1411,6 @@ def test_Lambda_dependence_twice(self): B.key.set_value("c") self.assertEqual(B.prop2(), 6) - def test_Lambda_dependence_other_feature(self): A = features.DummyFeature(a=1, b=2, c=3) @@ -1460,7 +1434,6 @@ def test_Lambda_dependence_other_feature(self): B.key.set_value("c") self.assertEqual(C.prop(), 12) - def test_Lambda_scaling(self): def scale_function_factory(scale=2): def scale_function(image): @@ -1571,7 +1544,6 @@ def test_OneOf_list(self): self.assertRaises(IndexError, lambda: values.update().resolve(key=3)) - def test_OneOf_tuple(self): values = features.OneOf( @@ -1844,7 +1816,6 @@ def test_Upscale(self): "The upscaled image should be similar to the original within a tolerance") - def test_NonOverlapping_resample_volume_position(self): nonOverlapping = features.NonOverlapping( From 83a3566da93bdff99eca02167734d9338c29e21f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 10:13:13 +0200 Subject: [PATCH 023/223] Update features.py --- deeptrack/features.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 840de666a..e921a24c3 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2983,21 +2983,25 @@ class Stack(Feature): Start by creating a pipeline using `Stack`: >>> pipeline = dt.Value([1, 2, 3]) >> dt.Stack(value=[4, 5]) - >>> print(pipeline.resolve()) + >>> pipeline.resolve() [1, 2, 3, 4, 5] Equivalently, this pipeline can be created using: >>> pipeline = dt.Value([1, 2, 3]) & [4, 5] + >>> pipeline.resolve() + [1, 2, 3, 4, 5] Or: - >>> pipeline = [4, 5] & dt.Value([1, 2, 3]) # Different result. + >>> pipeline = [4, 5] & dt.Value([1, 2, 3]) # Different result + >>> pipeline.resolve() + [4, 5, 1, 2, 3] """ __distributed__: bool = False def __init__( - self: Feature, + self: Stack, value: PropertyLike[Any], **kwargs: Any, ): @@ -3015,7 +3019,7 @@ def __init__( super().__init__(value=value, **kwargs) def get( - self: Feature, + self: Stack, image: Any | list[Any], value: Any | list[Any], **kwargs: Any, From 158f247db7516e5653f862050d0e6f50b00c88d0 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 10:13:15 +0200 Subject: [PATCH 024/223] Update test_features.py --- deeptrack/tests/test_features.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 03b689b81..70ac5a12c 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -888,8 +888,16 @@ def test_Stack(self): {"value": np.random.rand(10, 10)}, ], lambda a, b: [ - *(a["value"] if isinstance(a["value"], list) else [a["value"]]), - *(b["value"] if isinstance(b["value"], list) else [b["value"]]), + *( + a["value"] + if isinstance(a["value"], list) + else [a["value"]] + ), + *( + b["value"] + if isinstance(b["value"], list) + else [b["value"]] + ), ], operator.__and__, ) From 00533aa46011d787f0fab7677d4a0e20925be2ec Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 10:22:58 +0200 Subject: [PATCH 025/223] Update features.py --- deeptrack/features.py | 87 +++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index e921a24c3..50820b977 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -226,8 +226,7 @@ class Feature(DeepTrackNode): Parameters ---------- - _input: np.ndarray or list of np.ndarray or Image or list of Image, - optional. + _input: np.ndarray or Image or list[np.ndarray or Image], optional. A list of np.ndarray or `DeepTrackNode` objects or a single np.ndarray or an `Image` object representing the input data for the feature. This parameter specifies what the feature will process. If left empty, no @@ -502,7 +501,7 @@ def get( Parameters ---------- - image: np.ndarray or list of np.ndarray or Image or list of Images + image: np.ndarray or Image or list[np.ndarray or Image] The image or list of images to transform. **kwargs: Any The current value of all properties in `properties`, as well as any @@ -510,7 +509,7 @@ def get( Returns ------- - Image or list of Images + Image or list[Image] The transformed image or list of images. Raises @@ -550,7 +549,7 @@ def __call__( Parameters ---------- - image_list: np.ndarrray or list[np.ndarrray] or Image or list of Images, optional + image_list: np.ndarrray or Image or list[np.ndarrray or Image], optional The input to the feature or pipeline. If `None`, the feature uses previously set input values or propagates properties. **kwargs: Any @@ -753,7 +752,7 @@ def batch(self: Feature, batch_size: int = 32) -> tuple | list[Image]: Returns ------- - tuple or list of Images + tuple or list[Image] A tuple of stacked arrays (if the outputs are NumPy arrays or torch tensors) or a list of images if the outputs are not stackable. @@ -791,7 +790,7 @@ def action( Returns ------- - Image or list of Images + Image or list[Image] The resolved image or list of resolved images. """ @@ -989,7 +988,7 @@ def plot( Parameters ---------- - input_image: np.ndarray or list np.ndarray or Image or list of Image, optional + input_image: np.ndarray or Image or list[np.ndarray or Image], optional The input image or list of images passed as an argument to the `resolve` call. If `None`, uses previously set input values or propagates properties. resolve_kwargs: dict, optional @@ -1834,7 +1833,7 @@ def get( Parameters ---------- - image: np.ndarray or list np.ndarray or Image or list of Image + image: np.ndarray or Image or list[np.ndarray or Image] The input data, which can be an `Image` or a list of `Image` objects, to transform sequentially. _ID: tuple of int, optional @@ -1847,7 +1846,7 @@ def get( Returns ------- - Image or list of Images + Image or list[Image] The final output after `feature_1` and then `feature_2` have processed the input. @@ -1970,7 +1969,7 @@ class Value(Feature): Methods ------- - `get(image: Any, value: float, **kwargs: dict[str, Any]) -> float` + `get(image: Any, value: float, **kwargs: Any) -> float` Returns the stored value, ignoring the input image. Examples @@ -2097,7 +2096,7 @@ class ArithmeticOperationFeature(Feature): op: Callable[[Any, Any], Any] The arithmetic operation to apply, such as a built-in operator (`operator.add`, `operator.mul`) or a custom callable. - value: float or int or list of float or int, optional + value: float or int or list[float or int], optional The second operand for the operation. It defaults to 0. If a list is provided, the operation will apply element-wise. **kwargs: Any @@ -2111,7 +2110,7 @@ class ArithmeticOperationFeature(Feature): Methods ------- - `get(image: Any | list of Any, value: float | int | list[float] | int, **kwargs: Any) -> list[Any]` + `get(image: Any or list[Any], value: float or int or list[float or int], **kwargs: Any) -> list[Any]` Apply the arithmetic operation element-wise to the input data. Examples @@ -2153,7 +2152,7 @@ def __init__( The arithmetic operation to apply, such as `operator.add`, `operator.mul`, or any custom callable that takes two arguments and returns a single output value. - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The second operand(s) for the operation. If a list is provided, the operation is applied element-wise. It defaults to 0. **kwargs: Any @@ -2176,10 +2175,10 @@ def get( Parameters ---------- - image: Any or list of Any + image: Any or list[Any] The input data, either a single value or a list of values, to be transformed by the arithmetic operation. - value: float or int or array, or list of float or int or array + value: float or int or array or list[float or int or array] The second operand(s) for the operation. If a single value is provided, it is broadcast to match the input size. If a list is provided, it will be cycled to match the length of the input list. @@ -2190,7 +2189,7 @@ def get( Returns ------- - list of Any + list[Any] A list containing the results of applying the operation to the input data element-wise. @@ -2217,7 +2216,7 @@ class Add(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to add to the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2264,7 +2263,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to add to the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent `Feature`. @@ -2281,7 +2280,7 @@ class Subtract(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to subtract from the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2328,7 +2327,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to subtract from the input. it defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent `Feature`. @@ -2345,7 +2344,7 @@ class Multiply(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to multiply the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2392,7 +2391,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to multiply the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2409,7 +2408,7 @@ class Divide(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2456,7 +2455,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2477,7 +2476,7 @@ class FloorDivide(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to floor-divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2524,7 +2523,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to fllor-divide the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2541,7 +2540,7 @@ class Power(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to take the power of the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2588,7 +2587,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to take the power of the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2605,7 +2604,7 @@ class LessThan(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to compare (<) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2652,7 +2651,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to compare (<) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2669,7 +2668,7 @@ class LessThanOrEquals(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to compare (<=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2716,7 +2715,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to compare (<=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2736,7 +2735,7 @@ class GreaterThan(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to compare (>) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2783,7 +2782,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to compare (>) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2800,7 +2799,7 @@ class GreaterThanOrEquals(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to compare (<=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2847,7 +2846,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to compare (>=) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2878,7 +2877,7 @@ class Equals(ArithmeticOperationFeature): Parameters ---------- - value: PropertyLike[int or float or array, or list of int or floar or array], optional + value: PropertyLike[int or float or array or list[int or floar or array]], optional The value to compare (==) with the input. It defaults to 0. **kwargs: Any Additional keyword arguments passed to the parent constructor. @@ -2931,7 +2930,7 @@ def __init__( Parameters ---------- - value: PropertyLike[float or int or array, or list of float or int or array], optional + value: PropertyLike[float or int or array or list[float or int or array]], optional The value to compare with the input. It defaults to 0. **kwargs: Any Additional keyword arguments. @@ -2974,7 +2973,7 @@ class Stack(Feature): Methods ------- - `get(image: Any, value: Any, **kwargs: dict[str, Any]) -> list[Any]` + `get(image: Any, value: Any, **kwargs: Any) -> list[Any]` Concatenate the input with the value. Examples @@ -3445,7 +3444,7 @@ class Combine(StructuralFeature): Parameters ---------- - features: list of Features + features: list[Feature] A list of features to combine. Each feature will be resolved in the order they appear in the list. **kwargs: Any, optional @@ -3489,7 +3488,7 @@ def __init__( Parameters ---------- - features: list of Features + features: list[Feature] A list of features to combine. Each feature is added as a dependency to ensure proper execution in the computation graph. **kwargs: Any, optional @@ -4361,7 +4360,7 @@ def get( Parameters ---------- - list_of_images: list[np.ndarray] or list[Image] + list_of_images: list[np.ndarray or Image] A list of images to be processed by the function. function: Callable[[list[np.ndarray] | list[Image]], np.ndarray | list[np.ndarray] | Image | list[Image]] The function that processes the list of images and returns either: @@ -4808,7 +4807,7 @@ def get( Parameters ---------- - path: str or list of str + path: str or list[str] The file path(s) to the image(s) to be loaded. A single string loads one image, while a list of paths loads multiple images. load_options: dict of str to Any, optional From 910dae0b63cbd03f09619fad3e7426891aedc703 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 10:41:17 +0200 Subject: [PATCH 026/223] Update test_features.py --- deeptrack/tests/test_features.py | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 70ac5a12c..6466f25e9 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -902,6 +902,72 @@ def test_Stack(self): operator.__and__, ) + # Stack scalar with scalar + feature = features.Stack(value=2) + result = feature(1) + self.assertEqual(result, [1, 2]) + result = (1 & feature)() + self.assertEqual(result, [1, 1, 2]) + result = (feature & 1)() + self.assertEqual(result, [1, 2, 1]) + + # Stack scalar with list + feature = features.Stack(value=[3, 4]) + result = feature(2) + self.assertEqual(result, [2, 3, 4]) + + # Stack list with scalar + feature = features.Stack(value=5) + result = feature([1, 2, 3]) + self.assertEqual(result, [1, 2, 3, 5]) + + # Stack list with list + feature = features.Stack(value=[4, 5]) + result = feature([1, 2, 3]) + self.assertEqual(result, [1, 2, 3, 4, 5]) + + # Stack with empty lists + feature = features.Stack(value=[]) + result = feature([1, 2]) + self.assertEqual(result, [1, 2]) + + feature = features.Stack(value=[1, 2]) + result = feature([]) + self.assertEqual(result, [1, 2]) + + # Stack using Value feature + pipeline = features.Value([1, 2]) >> features.Stack(value=features.Value([3, 4])) + result = pipeline() + self.assertEqual(result, [1, 2, 3, 4]) + + # Stack using & operator (Value & list) + pipeline = features.Value([1, 2]) & [3, 4] + self.assertEqual(pipeline.resolve(), [1, 2, 3, 4]) + + # Stack using & operator (list & Value) + pipeline = [3, 4] & features.Value([1, 2]) + self.assertEqual(pipeline.resolve(), [3, 4, 1, 2]) + + # Stack NumPy arrays + arr1 = np.array([1, 2]) + arr2 = np.array([3, 4]) + feature = features.Stack(value=arr2) + result = feature(arr1) + self.assertEqual(len(result), 2) + self.assertTrue(np.array_equal(result[0], arr1)) + self.assertTrue(np.array_equal(result[1], arr2)) + + # Stack PyTorch tensors + if TORCH_AVAILABLE: + import torch + + t1 = torch.tensor([1, 2]) + t2 = torch.tensor([3, 4]) + feature = features.Stack(value=t2) + result = feature(t1) + self.assertEqual(len(result), 2) + self.assertTrue(torch.equal(result[0], t1)) + self.assertTrue(torch.equal(result[1], t2)) def test_Arguments_feature_passing(self): """Tests that arguments are correctly passed and updated in a feature pipeline.""" From 755e09b5974abdcffa042434b7592ea61c653b1d Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 10:41:21 +0200 Subject: [PATCH 027/223] Update features.py --- deeptrack/features.py | 71 ++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 50820b977..036438d41 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -416,7 +416,7 @@ def int_dtype(self) -> np.dtype | torch.dtype: def complex_dtype(self) -> np.dtype | torch.dtype: """The dtype of the complex numbers.""" return xp.get_complex_dtype(self._complex_dtype) - + @property def bool_dtype(self) -> np.dtype | torch.dtype: """The dtype of the boolean numbers.""" @@ -608,10 +608,8 @@ def __call__( return output - resolve = __call__ - def store_properties( self: Feature, toggle: bool = True, @@ -1252,7 +1250,7 @@ def __add__( """Adds another value or feature using '+'. """ - + return self >> Add(other) def __radd__( @@ -1262,7 +1260,7 @@ def __radd__( """Adds this feature to another value using right '+'. """ - + return Value(other) >> Add(self) def __sub__( @@ -1272,7 +1270,7 @@ def __sub__( """Subtracts another value or feature using '-'. """ - + return self >> Subtract(other) def __rsub__( @@ -1282,6 +1280,7 @@ def __rsub__( """Subtracts this feature from another value using right '-'. """ + return Value(other) >> Subtract(self) def __mul__( @@ -1291,7 +1290,7 @@ def __mul__( """Multiplies this feature with another value using '*'. """ - + return self >> Multiply(other) def __rmul__( @@ -1309,9 +1308,9 @@ def __truediv__( other: Any ) -> Feature: """Divides this feature by another value using '/'. - + """ - + return self >> Divide(other) def __rtruediv__( @@ -1319,9 +1318,9 @@ def __rtruediv__( other: Any ) -> Feature: """Divides another value by this feature using right '/'. - + """ - + return Value(other) >> Divide(self) def __floordiv__( @@ -1329,9 +1328,9 @@ def __floordiv__( other: Any ) -> Feature: """Performs floor division using '//'. - + """ - + return self >> FloorDivide(other) def __rfloordiv__( @@ -1339,9 +1338,9 @@ def __rfloordiv__( other: Any ) -> Feature: """Performs right floor division using '//'. - + """ - + return Value(other) >> FloorDivide(self) def __pow__( @@ -1349,9 +1348,9 @@ def __pow__( other: Any ) -> Feature: """Raises this feature to a power using '**'. - + """ - + return self >> Power(other) def __rpow__( @@ -1359,16 +1358,19 @@ def __rpow__( other: Any ) -> Feature: """Raises another value to this feature as a power using right '**'. - + """ - + return Value(other) >> Power(self) def __gt__( self: Feature, other: Any ) -> Feature: - """Checks if this feature is greater than another using '>'.""" + """Checks if this feature is greater than another using '>'. + + """ + return self >> GreaterThan(other) def __rgt__( @@ -1377,9 +1379,9 @@ def __rgt__( ) -> Feature: """Checks if another value is greater than this feature using right '>'. - + """ - + return Value(other) >> GreaterThan(self) def __lt__( @@ -1387,18 +1389,17 @@ def __lt__( other: Any ) -> Feature: """Checks if this feature is less than another using '<'. - + """ - + return self >> LessThan(other) def __rlt__( self: Feature, other: Any ) -> Feature: - """Checks if another value is less than this feature using - right '<'. - + """Checks if another value is less than this feature using right '<'. + """ return Value(other) >> LessThan(self) @@ -1408,9 +1409,9 @@ def __le__( other: Any ) -> Feature: """Checks if this feature is less than or equal to another using '<='. - + """ - + return self >> LessThanOrEquals(other) def __rle__( @@ -1419,9 +1420,9 @@ def __rle__( ) -> Feature: """Checks if another value is less than or equal to this feature using right '<='. - + """ - + return Value(other) >> LessThanOrEquals(self) def __ge__( @@ -1430,9 +1431,9 @@ def __ge__( ) -> Feature: """Checks if this feature is greater than or equal to another using '>='. - + """ - + return self >> GreaterThanOrEquals(other) def __rge__( @@ -1441,7 +1442,7 @@ def __rge__( ) -> Feature: """Checks if another value is greater than or equal to this feature using right '>='. - + """ return Value(other) >> GreaterThanOrEquals(self) @@ -1461,7 +1462,7 @@ def __and__( other: Any, ) -> Feature: """Stacks this feature with another using '&'. - + """ return self >> Stack(other) From 046b6e2552b9586923768059f5eab9d7acfce4cb Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 10:43:37 +0200 Subject: [PATCH 028/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 036438d41..a6652294d 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -167,7 +167,7 @@ def merge_features( "GreaterThanOrEqual", "Equals", "Equal", - "Stack", # TODO + "Stack", "Arguments", # TODO "Probability", # TODO "Repeat", # TODO From 0c83f7cf7911ad27fdd6fb1a557b0c3077a01607 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 11:25:04 +0200 Subject: [PATCH 029/223] Update features.py --- deeptrack/features.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/deeptrack/features.py b/deeptrack/features.py index a6652294d..321753252 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2996,6 +2996,23 @@ class Stack(Feature): >>> pipeline.resolve() [4, 5, 1, 2, 3] + Note + ---- + If a feature is called directly, its result is cached internally. This can + affect how it behaves when reused in chained pipelines. For exmaple: + >>> stack_feature = dt.Stack(value=2) + >>> _ = stack_feature(1) # Evaluate the feature and cache the output + >>> (1 & stack_feature)() + [1, 1, 2] + + To ensure consistent behavior when reusing a feature after calling it, + reset its state using instead: + >>> stack_feature = dt.Stack(value=2) + >>> _ = stack_feature(1) + >>> stack_feature.update() # clear cached state + >>> (1 & stack_feature)() + [1, 2] + """ __distributed__: bool = False From b2da19640185871e2f4e89c32bca93e93aacb679 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 14:44:08 +0200 Subject: [PATCH 030/223] Update features.py --- deeptrack/features.py | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 321753252..f509fdf59 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3093,11 +3093,11 @@ class Arguments(Feature): Examples -------- >>> import deeptrack as dt - >>> from tempfile import NamedTemporaryFile - >>> from PIL import Image as PIL_Image - >>> import os Create a temporary image: + >>> import numpy as np + >>> import PIL, tempfile + >>> >>> test_image_array = (np.ones((50, 50)) * 128).astype(np.uint8) >>> temp_png = NamedTemporaryFile(suffix=".png", delete=False) >>> PIL_Image.fromarray(test_image_array).save(temp_png.name) @@ -3105,43 +3105,45 @@ class Arguments(Feature): A typical use-case is: >>> arguments = dt.Arguments(is_label=False) >>> image_pipeline = ( - ... dt.LoadImage(path=temp_png.name) >> - ... dt.Gaussian(sigma = (1 - arguments.is_label) * 5) + ... dt.LoadImage(path=temp_png.name) + ... >> dt.Gaussian(sigma=arguments.is_label) # Image with no noise ... ) >>> image_pipeline.bind_arguments(arguments) - - >>> image = image_pipeline() # Image with added noise. - >>> print(image.std()) - 5.041072178933536 + >>> + >>> image = image_pipeline() + >>> image.std() + 0.0 Change the argument: - >>> image = image_pipeline(is_label=True) # Image with no noise. - >>> print(image.std()) - 0.0 + >>> image = image_pipeline(is_label=True) # Image with added noise + >>> image.std() + 1.0104364326447652 Remove the temporary image: + >>> import os + >>> >>> os.remove(temp_png.name) For a non-mathematical dependence, create a local link to the property as follows: >>> arguments = dt.Arguments(is_label=False) >>> image_pipeline = ( - ... dt.LoadImage(path=temp_png.name) >> - ... dt.Gaussian( + ... dt.LoadImage(path=temp_png.name) + ... >> dt.Gaussian( ... is_label=arguments.is_label, - ... sigma=lambda is_label: 0 if is_label else 5 + ... sigma=lambda is_label: 1 if is_label else 0, ... ) ... ) >>> image_pipeline.bind_arguments(arguments) - Keep in mind that, if any dependent property is non-deterministic, they may + Keep in mind that, if any dependent property is non-deterministic, it may permanently change: - >>> arguments = dt.Arguments(noise_max_sigma=5) + >>> arguments = dt.Arguments(noise_max=1) >>> image_pipeline = ( - ... dt.LoadImage(path=temp_png.name) >> - ... dt.Gaussian( - ... noise_max_sigma=arguments.noise_max_sigma, - ... sigma=lambda noise_max_sigma: np.random.rand()*noise_max_sigma + ... dt.LoadImage(path=temp_png.name) + ... >> dt.Gaussian( + ... noise_max=arguments.noise_max, + ... sigma=lambda noise_max: np.random.rand() * noise_max, ... ) ... ) >>> image_pipeline.bind_arguments(arguments) From 573001575497e00d7654957a0df71e663cf0b6bf Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 15:25:16 +0200 Subject: [PATCH 031/223] Update test_features.py --- deeptrack/tests/test_features.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 6466f25e9..d8ab98110 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -906,10 +906,6 @@ def test_Stack(self): feature = features.Stack(value=2) result = feature(1) self.assertEqual(result, [1, 2]) - result = (1 & feature)() - self.assertEqual(result, [1, 1, 2]) - result = (feature & 1)() - self.assertEqual(result, [1, 2, 1]) # Stack scalar with list feature = features.Stack(value=[3, 4]) From 0c16b48a38144711ce9e5407afddf476d53f7586 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 15:47:05 +0200 Subject: [PATCH 032/223] Update features.py --- deeptrack/features.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index f509fdf59..816ac0a5b 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -909,26 +909,33 @@ def bind_arguments( self: Feature, arguments: Feature, ) -> Feature: - """Binds another feature’s properties as arguments to this feature. + """Bind another feature’s properties as arguments to this feature. This method allows properties of `arguments` to be dynamically linked - to this feature, enabling shared configurations across multiple features. - It is commonly used in advanced feature pipelines. + to this feature, enabling shared configurations across multiple + features. It is commonly used in advanced feature pipelines. - See Also - -------- - features.Arguments - A utility that helps manage and propagate feature arguments efficiently. + This method is often used in combination with the `Arguments` Feature, + which provides a utility that helps manage and propagate feature + arguments efficiently. Parameters ---------- arguments: Feature - The feature whose properties will be bound as arguments to this feature. + The feature whose properties will be bound as arguments to this + feature. Returns ------- Feature The current feature instance with bound arguments. + + Examples + -------- + TODO method alone + + TODO use with Arguments + """ self.arguments = arguments From c8319d300289a1a51e3439cf3b9164af7383809a Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 18:39:37 +0200 Subject: [PATCH 033/223] Update features.py --- deeptrack/features.py | 60 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 816ac0a5b..762dabd62 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3085,17 +3085,17 @@ def get( class Arguments(Feature): """A convenience container for pipeline arguments. - The `Arguments` feature allows dynamic control of pipeline behavior by - providing a container for arguments that can be modified or overridden at - runtime. This is particularly useful when working with parameterized - pipelines, such as toggling behaviors based on whether an image is a label + The `Arguments` feature allows dynamic control of pipeline behavior by + providing a container for arguments that can be modified or overridden at + runtime. This is particularly useful when working with parametrized + pipelines, such as toggling behaviors based on whether an image is a label or a raw input. Methods ------- - `get(image: Any, **kwargs: dict[str, Any]) -> Any` - Passes the input image through unchanged, while allowing for property - overrides. + `get(image: Any, **kwargs: Any) -> Any` + It passes the input image through unchanged, while allowing for + property overrides. Examples -------- @@ -3106,8 +3106,8 @@ class Arguments(Feature): >>> import PIL, tempfile >>> >>> test_image_array = (np.ones((50, 50)) * 128).astype(np.uint8) - >>> temp_png = NamedTemporaryFile(suffix=".png", delete=False) - >>> PIL_Image.fromarray(test_image_array).save(temp_png.name) + >>> temp_png = tempfile.NamedTemporaryFile(suffix=".png", delete=False) + >>> PIL.Image.fromarray(test_image_array).save(temp_png.name) A typical use-case is: >>> arguments = dt.Arguments(is_label=False) @@ -3137,8 +3137,8 @@ class Arguments(Feature): >>> image_pipeline = ( ... dt.LoadImage(path=temp_png.name) ... >> dt.Gaussian( - ... is_label=arguments.is_label, - ... sigma=lambda is_label: 1 if is_label else 0, + ... local_is_label=arguments.is_label, + ... sigma=lambda local_is_label: 1 if local_is_label else 0, ... ) ... ) >>> image_pipeline.bind_arguments(arguments) @@ -3154,36 +3154,36 @@ class Arguments(Feature): ... ) ... ) >>> image_pipeline.bind_arguments(arguments) - >>> image_pipeline.store_properties() - + >>> image_pipeline.store_properties() # Store image properties + >>> >>> image = image_pipeline() - >>> print(image.get_property("sigma")) - 1.1838819055669947 + >>> image.std(), image.get_property("sigma") + (0.8464173007136401, 0.8423390304699889) - >>> image = image_pipeline(noise_max_sigma=0) - >>> print(image.get_property("sigma")) - 0.0 + >>> image = image_pipeline(noise_max=0) + >>> image.std(), image.get_property("sigma") + (0.0, 0.0) As with any feature, all arguments can be passed by deconstructing the properties dict: >>> arguments = dt.Arguments(is_label=False, noise_sigma=5) >>> image_pipeline = ( - ... dt.LoadImage(path=temp_png.name) >> - ... dt.Gaussian( + ... dt.LoadImage(path=temp_png.name) + ... >> dt.Gaussian( ... sigma=lambda is_label, noise_sigma: ( ... 0 if is_label else noise_sigma - ... ) - ... **arguments.properties + ... ), + ... **arguments.properties, ... ) ... ) >>> image_pipeline.bind_arguments(arguments) - - >>> image = image_pipeline() # Image with added noise. - >>> print(image.std()) + >>> + >>> image = image_pipeline() # Image with added noise + >>> image.std() 5.002151761964336 - >>> image = image_pipeline(is_label=True) # Raw image with no noise. - >>> print(image.std()) + >>> image = image_pipeline(is_label=True) # Raw image with no noise + >>> image.std() 0.0 """ @@ -3191,10 +3191,10 @@ class Arguments(Feature): def get( self: Feature, image: Any, - **kwargs: dict[str, Any] + **kwargs: Any, ) -> Any: - """Process the input image and allow property overrides. + """Return the input image and allow property overrides. This method does not modify the input image but provides a mechanism for overriding arguments dynamically during pipeline execution. @@ -3318,7 +3318,7 @@ def get( otherwise, it is the unchanged input image. """ - + if random_number < probability: image = self.feature.resolve(image, **kwargs) From 01fe26d9402b320df50aa1f0b86a586c0fc5d3c0 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 18:53:02 +0200 Subject: [PATCH 034/223] Update test_features.py --- deeptrack/tests/test_features.py | 123 ++++++++++++++++--------------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index d8ab98110..50482ac4e 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -965,62 +965,23 @@ def test_Stack(self): self.assertTrue(torch.equal(result[0], t1)) self.assertTrue(torch.equal(result[1], t2)) - def test_Arguments_feature_passing(self): - """Tests that arguments are correctly passed and updated in a feature pipeline.""" - - # Define Arguments with static and dynamic values - arguments = features.Arguments( - a="foo", - b="bar", - c=lambda a, b: a + b, # "foobar" - d=np.random.rand, # Random float in [0, 1] - ) - - # First feature with dependencies on arguments - f1 = features.DummyFeature( - p1=arguments.a, # "foo" - p2=lambda p1: p1 + "baz" # "foobaz" - ) - - # Second feature dependent on the first - f2 = features.DummyFeature( - p1=f1.p2, # Should be "foobaz" - p2=arguments.d, # Random value - ) - - # Assertions - self.assertEqual(f1.properties['p1'](), "foo") # Check that p1 is set correctly - self.assertEqual(f1.properties['p2'](), "foobaz") # Check lambda evaluation - - self.assertEqual(f2.properties['p1'](), "foobaz") # Check dependency resolution - - # Ensure p2 in f2 is a valid float between 0 and 1 - self.assertTrue(0 <= f2.properties['p2']() <= 1) - - # Ensure `c` was computed correctly - self.assertEqual(arguments.c(), "foobar") # Should concatenate "foo" + "bar" - - # Test that d is dynamic (generates new values) - first_d = arguments.d.update()() - second_d = arguments.d.update()() - self.assertNotEqual(first_d, second_d) # Check that values change def test_Arguments(self): from tempfile import NamedTemporaryFile from PIL import Image as PIL_Image import os - """Creates a temporary test image.""" + # Create a temporary test image. test_image_array = (np.ones((50, 50)) * 128).astype(np.uint8) with NamedTemporaryFile(suffix=".png", delete=False) as temp_png: PIL_Image.fromarray(test_image_array).save(temp_png.name) - try: - """Tests pipeline behavior when toggling `is_label`.""" + try: # Ensure removal of test image. + # Test pipeline behavior when toggling `is_label`. arguments = features.Arguments(is_label=False) image_pipeline = ( - features.LoadImage(path=temp_png.name) >> - Gaussian(sigma=(1 - arguments.is_label) * 5) + features.LoadImage(path=temp_png.name) + >> Gaussian(sigma=(1 - arguments.is_label) * 5) ) image_pipeline.bind_arguments(arguments) @@ -1030,15 +991,15 @@ def test_Arguments(self): # Test raw image with `is_label=True` image = image_pipeline(is_label=True) - self.assertAlmostEqual(image.std(), 0.0, places=3) # No noise expected + self.assertAlmostEqual(image.std(), 0.0, places=3) # No noise - """Tests pipeline behavior with dynamically computed sigma.""" + # Test pipeline behavior with dynamically computed sigma. arguments = features.Arguments(is_label=False) image_pipeline = ( - features.LoadImage(path=temp_png.name) >> - Gaussian( + features.LoadImage(path=temp_png.name) + >> Gaussian( is_label=arguments.is_label, - sigma=lambda is_label: 0 if is_label else 5 + sigma=lambda is_label: 0 if is_label else 5, ) ) image_pipeline.bind_arguments(arguments) @@ -1049,15 +1010,16 @@ def test_Arguments(self): # Test raw image with `is_label=True` image = image_pipeline(is_label=True) - self.assertAlmostEqual(image.std(), 0.0, places=3) # No noise expected + self.assertAlmostEqual(image.std(), 0.0, places=3) # No noise - """Tests property storage and modification in the pipeline.""" + # Test property storage and modification in the pipeline. arguments = features.Arguments(noise_max_sigma=5) image_pipeline = ( - features.LoadImage(path=temp_png.name) >> - Gaussian( + features.LoadImage(path=temp_png.name) + >> Gaussian( noise_max_sigma=arguments.noise_max_sigma, - sigma=lambda noise_max_sigma: np.random.rand() * noise_max_sigma + sigma=lambda noise_max_sigma: + np.random.rand() * noise_max_sigma, ) ) image_pipeline.bind_arguments(arguments) @@ -1072,13 +1034,14 @@ def test_Arguments(self): image = image_pipeline(noise_max_sigma=0) self.assertEqual(image.get_property("sigma"), 0.0) - """Tests passing arguments dynamically using `**arguments.properties`.""" + # Test passing arguments dynamically using **arguments.properties. arguments = features.Arguments(is_label=False, noise_sigma=5) image_pipeline = ( features.LoadImage(path=temp_png.name) >> Gaussian( - sigma=lambda is_label, noise_sigma: 0 if is_label else noise_sigma, - **arguments.properties + sigma=lambda is_label, noise_sigma: + 0 if is_label else noise_sigma, + **arguments.properties, ) ) image_pipeline.bind_arguments(arguments) @@ -1089,12 +1052,54 @@ def test_Arguments(self): # Test raw image with `is_label=True` image = image_pipeline(is_label=True) - self.assertAlmostEqual(image.std(), 0.0, places=3) # No noise expected - + self.assertAlmostEqual(image.std(), 0.0, places=3) # No noise + + except Exception: + raise finally: if os.path.exists(temp_png.name): os.remove(temp_png.name) + def test_Arguments_feature_passing(self): + """Tests that arguments are correctly passed and updated in a feature pipeline.""" + + # Define Arguments with static and dynamic values + arguments = features.Arguments( + a="foo", + b="bar", + c=lambda a, b: a + b, # "foobar" + d=np.random.rand, # Random float in [0, 1] + ) + + # First feature with dependencies on arguments + f1 = features.DummyFeature( + p1=arguments.a, # "foo" + p2=lambda p1: p1 + "baz" # "foobaz" + ) + + # Second feature dependent on the first + f2 = features.DummyFeature( + p1=f1.p2, # Should be "foobaz" + p2=arguments.d, # Random value + ) + + # Assertions + self.assertEqual(f1.properties['p1'](), "foo") # Check that p1 is set correctly + self.assertEqual(f1.properties['p2'](), "foobaz") # Check lambda evaluation + + self.assertEqual(f2.properties['p1'](), "foobaz") # Check dependency resolution + + # Ensure p2 in f2 is a valid float between 0 and 1 + self.assertTrue(0 <= f2.properties['p2']() <= 1) + + # Ensure `c` was computed correctly + self.assertEqual(arguments.c(), "foobar") # Should concatenate "foo" + "bar" + + # Test that d is dynamic (generates new values) + first_d = arguments.d.update()() + second_d = arguments.d.update()() + self.assertNotEqual(first_d, second_d) # Check that values change + def test_Probability(self): np.random.seed(42) # Set seed for reproducibility From c9c418af04af5b5e0d04987fc0bc75de00fef3bc Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 19:11:00 +0200 Subject: [PATCH 035/223] Update test_features.py --- deeptrack/tests/test_features.py | 45 ++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 50482ac4e..c0a71bf6b 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1061,7 +1061,8 @@ def test_Arguments(self): os.remove(temp_png.name) def test_Arguments_feature_passing(self): - """Tests that arguments are correctly passed and updated in a feature pipeline.""" + # Tests that arguments are correctly passed and updated. + # # Define Arguments with static and dynamic values arguments = features.Arguments( @@ -1074,7 +1075,7 @@ def test_Arguments_feature_passing(self): # First feature with dependencies on arguments f1 = features.DummyFeature( p1=arguments.a, # "foo" - p2=lambda p1: p1 + "baz" # "foobaz" + p2=lambda p1: p1 + "baz", # "foobaz" ) # Second feature dependent on the first @@ -1084,22 +1085,50 @@ def test_Arguments_feature_passing(self): ) # Assertions - self.assertEqual(f1.properties['p1'](), "foo") # Check that p1 is set correctly - self.assertEqual(f1.properties['p2'](), "foobaz") # Check lambda evaluation - - self.assertEqual(f2.properties['p1'](), "foobaz") # Check dependency resolution + self.assertEqual(f1.properties["p1"](), "foo") # Check that p1 is set + # correctly + self.assertEqual(f1.properties["p2"](), "foobaz") # Check lambda + # evaluation + self.assertEqual(f2.properties["p1"](), "foobaz") # Check dependency + # resolution # Ensure p2 in f2 is a valid float between 0 and 1 - self.assertTrue(0 <= f2.properties['p2']() <= 1) + self.assertTrue(0 <= f2.properties["p2"]() <= 1) # Ensure `c` was computed correctly - self.assertEqual(arguments.c(), "foobar") # Should concatenate "foo" + "bar" + self.assertEqual(arguments.c(), "foobar") # Should concatenate + # "foo" + "bar" # Test that d is dynamic (generates new values) first_d = arguments.d.update()() second_d = arguments.d.update()() self.assertNotEqual(first_d, second_d) # Check that values change + def test_Arguments_binding(self): + # Create a dynamic argument container + arguments = features.Arguments(x=10) + + # Create a simple pipeline: Value(100) + x + 1 + pipeline = ( + features.Value(100) + >> features.Add(value=arguments.x) + >> features.Add(1) + ) + + # Evaluate pipeline with default x=10 + result = pipeline() + self.assertEqual(result, 111) # 100 + 10 + 1 + + result_no_binding = pipeline(x=20) + self.assertEqual(result_no_binding, 111) # 100 + 10 + 1 + + # Bind the arguments to the pipeline + pipeline.bind_arguments(arguments) + + # Override x at runtime to 20 + result_binding = pipeline(x=20) + self.assertEqual(result_binding, 121) # 100 + 20 + 1 + def test_Probability(self): np.random.seed(42) # Set seed for reproducibility From d414d409e313a3cdb4997ef3e027133f1e5cb1c9 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 19:28:33 +0200 Subject: [PATCH 036/223] Update features.py --- deeptrack/features.py | 45 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 762dabd62..b41443fc3 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -168,7 +168,7 @@ def merge_features( "Equals", "Equal", "Stack", - "Arguments", # TODO + "Arguments", "Probability", # TODO "Repeat", # TODO "Combine", # TODO @@ -1709,36 +1709,35 @@ def propagate_data_to_dependencies(feature: Feature, **kwargs: dict[str, Any]) - class StructuralFeature(Feature): - """Provides the structure of a feature set without input transformations. + """ + Provides the structure of a feature set without input transformations. + + A `StructuralFeature` does not modify the input data or introduce new + properties. Instead, it serves as a logical and organizational tool for + grouping, chaining, or structuring pipelines. - A `StructuralFeature` does not directly transform the input data or add new - properties. Instead, it is commonly used as a logical or organizational - tool to structure and manage feature sets within a pipeline. + This feature is typically used to: + - group or chain sub-features (e.g., `Chain`) + - apply conditional or sequential logic (e.g., `Probability`) + - organize pipelines without affecting data flow (e.g., `Combine`) - Since `StructuralFeature` does not override the `__init__` or `get` - methods, it inherits the behavior of the base `Feature` class. + `StructuralFeature` inherits all behavior from `Feature`, without + overriding `__init__` or `get`. Attributes ---------- - __property_verbosity__: int - Controls whether this feature’s properties are included in the output - image’s property list. A value of `2` means that this feature’s - properties are not included. - __distributed__: bool - Determines whether the feature’s `get` method is applied to each - element in the input list (`__distributed__ = True`) or to the entire - list as a whole (`__distributed__ = False`). - - Notes - ----- - Structural features are typically used for tasks like grouping or chaining - features, applying sequential or conditional logic, or structuring - pipelines without directly modifying the data. + __property_verbosity__ : int + Controls whether this feature's properties appear in the output image's + property list. A value of `2` hides them from output. + __distributed__ : bool + If `True`, applies `get` to each element in a list individually. + If `False`, processes the entire list as a single unit. Defaults to + `False`. """ - __property_verbosity__: int = 2 # Hide properties from logs or output. - __distributed__: bool = False # Process the entire image list in one call. + __property_verbosity__: int = 2 # Hide properties from logs or output + __distributed__: bool = False # Process the entire image list in one call class Chain(StructuralFeature): From 5179396109c78c644d1d59973ae69f28a7521775 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 19:46:24 +0200 Subject: [PATCH 037/223] Update features.py --- deeptrack/features.py | 86 +++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index b41443fc3..080487632 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -147,9 +147,9 @@ def merge_features( __all__ = [ "Feature", # TODO - "StructuralFeature", # TODO - "Chain", # TODO - "Branch", # TODO + "StructuralFeature", + "Chain", + "Branch", "DummyFeature", "Value", "ArithmeticOperationFeature", @@ -1709,8 +1709,7 @@ def propagate_data_to_dependencies(feature: Feature, **kwargs: dict[str, Any]) - class StructuralFeature(Feature): - """ - Provides the structure of a feature set without input transformations. + """Provide the structure of a feature set without input transformations. A `StructuralFeature` does not modify the input data or introduce new properties. Instead, it serves as a logical and organizational tool for @@ -1743,9 +1742,13 @@ class StructuralFeature(Feature): class Chain(StructuralFeature): """Resolve two features sequentially. - This feature applies two features sequentially, passing the output of the - first feature as the input to the second. It enables building feature - chains that execute complex transformations by combining simple operations. + Applies two features sequentially: the output of `feature_1` is passed as + input to `feature_2`. This allows combining simple operations into complex + pipelines. + + This is equivalent to using the `>>` operator: + + >>> dt.Chain(A, B) ≡ A >> B Parameters ---------- @@ -1760,45 +1763,37 @@ class Chain(StructuralFeature): Methods ------- - `get(image: np.ndarray | list[np.ndarray] | Image | list[Image], _ID: tuple[int, ...], **kwargs: dict[str, Any]) -> Image | list[Image]` + `get(image: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any | list[Any]` Apply the two features in sequence on the given input image. - Notes - ----- - This feature is used to combine simple operations into a pipeline without the - need for explicit function chaining. It is syntactic sugar for creating - sequential feature pipelines. - Examples -------- >>> import deeptrack as dt - >>> import numpy as np Create a feature chain where the first feature adds a constant offset, and the second feature multiplies the result by a constant: - >>> A = dt.Add(value=10) >>> M = dt.Multiply(value=0.5) - - Chain the features: - >>> chain = A >> M + >>> + >>> chain = A >> M Equivalent to: >>> chain = dt.Chain(A, M) Create a dummy image: - >>> dummy_image = np.ones((2, 4)) + >>> import numpy as np + >>> + >>> dummy_image = np.zeros((2, 4)) Apply the chained features: - >>> transformed_image = chain(dummy_image) - >>> print(transformed_image) - [[5.5 5.5 5.5 5.5] - [5.5 5.5 5.5 5.5]] + >>> chain(dummy_image) + array([[5., 5., 5., 5.], + [5., 5., 5., 5.]]) """ def __init__( - self: Feature, + self: Chain, feature_1: Feature, feature_2: Feature, **kwargs: Any, @@ -1815,10 +1810,10 @@ def __init__( feature_1: Feature The first feature to be applied. feature_2: Feature - The second feature, applied after `feature_1`. - **kwargs: Any, optional - Additional keyword arguments passed to the parent constructor (e.g., - name, properties). + The second feature, applied to the result of `feature_1`. + **kwargs: Any + Additional keyword arguments passed to the parent constructor + (e.g., name, properties). """ @@ -1829,33 +1824,34 @@ def __init__( def get( self: Feature, - image: np.ndarray | list[np.ndarray] | Image | list[Image], + image: Any | list[Any], _ID: tuple[int, ...] = (), **kwargs: Any, - ) -> Image | list[Image]: + ) -> Any | list[Any]: """Apply the two features sequentially to the given input image(s). - This method first applies `feature_1` to the input image(s) and then passes - the output through `feature_2`. + This method first applies `feature_1` to the input image(s) and then + passes the output through `feature_2`. Parameters ---------- - image: np.ndarray or Image or list[np.ndarray or Image] - The input data, which can be an `Image` or a list of `Image` objects, - to transform sequentially. - _ID: tuple of int, optional + image: Any or list[Any] + The input data to transform sequentially. Most typically, this is + a NumPy array, a PyTorch tensor, or an Image, or a list of the + same. + _ID: tuple[int, ...], optional A unique identifier for caching or parallel execution. It defaults to an empty tuple. **kwargs: Any - Additional parameters passed to or sampled by the features. These are - generally unused here, as each sub-feature fetches its required properties - internally. + Additional parameters passed to or sampled by the features. These + are generally unused here, as each sub-feature fetches its required + properties internally. Returns ------- - Image or list[Image] - The final output after `feature_1` and then `feature_2` have processed - the input. + Any or list[Any] + The final output after `feature_1` and then `feature_2` have + processed the input. """ @@ -2961,7 +2957,7 @@ class Stack(Feature): it is automatically converted into a list to maintain consistency in the output format. - If B is a feature, `Stack` can be visualized as:: + If B is a feature, `Stack` can be visualized as: >>> A >> Stack(B) = [*A(), *B()] From 62a450759dd5cbd7916d754e85e933d7778c87ee Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 24 Jun 2025 19:46:26 +0200 Subject: [PATCH 038/223] Update test_features.py --- deeptrack/tests/test_features.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index c0a71bf6b..e70c96576 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -564,7 +564,8 @@ def get(self, image, **kwargs): class Multiplication(features.Feature): """Simple feature that multiplies by a constant.""" def get(self, image, **kwargs): - # 'multiplier' is a property set via self.properties (default: 1). + # 'multiplier' is a property set via self.properties + # (default: 1). return image * self.properties.get("multiplier", 1)() A = Addition(addend=10) From 1b8a24c4f969959ff1216f8435063fecd903f4c0 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 12:43:18 +0200 Subject: [PATCH 039/223] Update features.py --- deeptrack/features.py | 92 +++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 080487632..91649d1b9 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -169,7 +169,7 @@ def merge_features( "Equal", "Stack", "Arguments", - "Probability", # TODO + "Probability", "Repeat", # TODO "Combine", # TODO "Slice", # TODO @@ -3184,7 +3184,7 @@ class Arguments(Feature): """ def get( - self: Feature, + self: Arguments, image: Any, **kwargs: Any, ) -> Any: @@ -3214,10 +3214,12 @@ def get( class Probability(StructuralFeature): """Resolve a feature with a certain probability. - This feature conditionally applies a given feature to an input image based - on a specified probability. A random number is sampled, and if it is less - than `probability`, the feature is resolved; otherwise, the input image - remains unchanged. + This feature conditionally applies a given feature to an input based on a + sampled uniform random number. If the sampled number is less than the + specified probability, the feature is resolved; otherwise, the input is + returned unchanged. + + To resample the decision, call `.update()` before evaluating the feature. Parameters ---------- @@ -3225,7 +3227,7 @@ class Probability(StructuralFeature): The feature to resolve conditionally. probability: PropertyLike[float] The probability (between 0 and 1) of resolving the feature. - *args: list[Any], optional + *args: Any, optional Positional arguments passed to the parent `StructuralFeature` class. **kwargs: Any, optional Additional keyword arguments passed to the parent `StructuralFeature` @@ -3233,87 +3235,107 @@ class Probability(StructuralFeature): Methods ------- - `get(image: np.ndarray, probability: float, random_number: float, **kwargs: dict[str, Any]) -> np.ndarray` + `get(image: Any, probability: float, random_number: float, **kwargs: Any) -> Any` Resolves the feature if the sampled random number is less than the specified probability. Examples -------- >>> import deeptrack as dt - >>> import numpy as np - In this example, the `Add` feature is applied to the input image with - a 70% chance. Define a feature and wrap it with `Probability`: + In this example, the `Add` feature is applied to the input image with a 70% + chance. + + Define a feature and wrap it with `Probability`: >>> add_feature = dt.Add(value=2) >>> probabilistic_feature = dt.Probability(add_feature, probability=0.7) Define an input image: - >>> input_image = np.ones((5, 5)) + >>> import numpy as np + >>> + >>> input_image = np.zeros((2, 3)) Apply the feature: + >>> probabilistic_feature.update() # Update the random number >>> output_image = probabilistic_feature(input_image) + With 70% probability, the output is: + >>> output_image + array([[2., 2., 2.], + [2., 2., 2.]]) + + With 30% probability, it remains: + >>> output_image + array([[0., 0., 0.], + [0., 0., 0.]]) + """ def __init__( - self: Feature, + self: Probability, feature: Feature, probability: PropertyLike[float], - *args: list[Any], + *args: Any, **kwargs: Any, ): """Initialize the Probability feature. + The random number is initialized when this feature is initialized. + It can be updated using the `update()` method. + Parameters ---------- feature: Feature The feature to resolve conditionally. probability: PropertyLike[float] The probability (between 0 and 1) of resolving the feature. - *args: list[Any], optional - Positional arguments passed to the parent `StructuralFeature` class. + *args: Any, optional + Positional arguments passed to the parent `StructuralFeature` + class. **kwargs: Any, optional - Additional keyword arguments passed to the parent `StructuralFeature` class. + Additional keyword arguments passed to the parent + `StructuralFeature` class. """ - + super().__init__( - *args, - probability=probability, - random_number=np.random.rand, + *args, + probability=probability, + random_number=np.random.rand, **kwargs, ) - self.feature = self.add_feature(feature) + self.feature = self.add_feature(feature) def get( - self: Feature, - image: np.ndarray, + self: Probability, + image: Any, probability: float, random_number: float, **kwargs: Any, - ) -> np.ndarray: - """Resolve the feature if a random number is less than the probability. + ) -> Any: + """Resolve the feature if random number is less than probability. Parameters ---------- - image: np.ndarray - The input image to process. + image: Any or list[Any] + The input to process. probability: float The probability (between 0 and 1) of resolving the feature. random_number: float - A random number sampled to determine whether to resolve the - feature. + A random number sampled to determine whether to resolve the + feature. It is initialized when this feature is initialized. + It can be updated using the `update()` method. **kwargs: Any - Additional arguments passed to the feature's `resolve` method. + Additional arguments passed to the feature's `resolve()` method. Returns ------- - np.ndarray - The processed image. If the feature is resolved, this is the output of the feature; - otherwise, it is the unchanged input image. + Any + The processed image. If the feature is resolved, this is the output + of the feature; otherwise, it is the unchanged input image. """ - + if random_number < probability: image = self.feature.resolve(image, **kwargs) From 66108d877df577aa994555d25ff05d1c27fd3eb9 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 12:44:43 +0200 Subject: [PATCH 040/223] Update test_features.py --- deeptrack/tests/test_features.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index e70c96576..b14cca7b0 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1139,7 +1139,7 @@ def test_Probability(self): feature = add_feature, probability=0.7 ) - + input_image = np.ones((5, 5)) applied_count = 0 @@ -1153,7 +1153,8 @@ def test_Probability(self): self.assertTrue(np.array_equal(output_image, input_image + 2)) observed_probability = applied_count / total_runs - self.assertTrue(0.65 <= observed_probability <= 0.75, f"Observed probability: {observed_probability}") + self.assertTrue(0.65 <= observed_probability <= 0.75, + f"Observed probability: {observed_probability}") def test_Repeat(self): From 5fbb7cd10225e5504192c80e05770eb763de69b4 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 12:52:10 +0200 Subject: [PATCH 041/223] Update test_features.py --- deeptrack/tests/test_features.py | 50 +++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index b14cca7b0..c230a9bac 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1132,30 +1132,66 @@ def test_Arguments_binding(self): def test_Probability(self): - np.random.seed(42) # Set seed for reproducibility + # Set seed for reproducibility of random trials + np.random.seed(42) + input_image = np.ones((5, 5)) add_feature = features.Add(value=2) + + # Helper: Check if feature was applied + def is_transformed(output): + return np.array_equal(output, input_image + 2) + + # 1. Test probabilistic application over many runs probabilistic_feature = features.Probability( - feature = add_feature, + feature=add_feature, probability=0.7 ) - input_image = np.ones((5, 5)) - applied_count = 0 total_runs = 300 for _ in range(total_runs): output_image = probabilistic_feature.update().resolve(input_image) - - if not np.array_equal(output_image, input_image): + if is_transformed(output_image): applied_count += 1 - self.assertTrue(np.array_equal(output_image, input_image + 2)) + else: + self.assertTrue(np.array_equal(output_image, input_image)) observed_probability = applied_count / total_runs self.assertTrue(0.65 <= observed_probability <= 0.75, f"Observed probability: {observed_probability}") + # 2. Edge case: probability = 0 (feature should never apply) + never_applied = features.Probability(feature=add_feature, + probability=0.0) + output = never_applied.update().resolve(input_image) + self.assertTrue(np.array_equal(output, input_image)) + + # 3. Edge case: probability = 1 (feature should always apply) + always_applied = features.Probability(feature=add_feature, + probability=1.0) + output = always_applied.update().resolve(input_image) + self.assertTrue(is_transformed(output)) + + # 4. Cached behavior: result is the same without update() + cached_feature = features.Probability(feature=add_feature, + probability=1.0) + output_1 = cached_feature.update().resolve(input_image) + output_2 = cached_feature.resolve(input_image) # same random number + self.assertTrue(np.array_equal(output_1, output_2)) + + # 5. Manual override: force behavior using random_number + manual = features.Probability(feature=add_feature, probability=0.5) + + # Should NOT apply (0.9 > 0.5) + output = manual.resolve(input_image, random_number=0.9) + self.assertTrue(np.array_equal(output, input_image)) + + # Should apply (0.1 < 0.5) + output = manual.resolve(input_image, random_number=0.1) + self.assertTrue(is_transformed(output)) + def test_Repeat(self): add_ten = features.Add(value=10) From 6c744fb1a300fac84302bc6d622e2bb035159d12 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 12:56:11 +0200 Subject: [PATCH 042/223] Update features.py --- deeptrack/features.py | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 91649d1b9..137627d33 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3227,9 +3227,9 @@ class Probability(StructuralFeature): The feature to resolve conditionally. probability: PropertyLike[float] The probability (between 0 and 1) of resolving the feature. - *args: Any, optional + *args: Any Positional arguments passed to the parent `StructuralFeature` class. - **kwargs: Any, optional + **kwargs: Any Additional keyword arguments passed to the parent `StructuralFeature` class. @@ -3289,10 +3289,10 @@ def __init__( The feature to resolve conditionally. probability: PropertyLike[float] The probability (between 0 and 1) of resolving the feature. - *args: Any, optional + *args: Any Positional arguments passed to the parent `StructuralFeature` class. - **kwargs: Any, optional + **kwargs: Any Additional keyword arguments passed to the parent `StructuralFeature` class. @@ -3382,21 +3382,16 @@ class Repeat(Feature): Define an `Add` feature that adds `10` to its input: >>> add_ten = dt.Add(value=10) - Apply this feature **3 times** using `Repeat`: + Apply this feature 3 times using `Repeat`: >>> pipeline = dt.Repeat(add_ten, N=3) Process an input list: - >>> print(pipeline.resolve([1, 2, 3])) + >>> pipeline.resolve([1, 2, 3]) [31, 32, 33] - Step-by-step breakdown: - - Iteration 1: `[1, 2, 3] + 10 → [11, 12, 13]` - - Iteration 2: `[11, 12, 13] + 10 → [21, 22, 23]` - - Iteration 3: `[21, 22, 23] + 10 → [31, 32, 33]` - Alternative shorthand using `^` operator: >>> pipeline = dt.Add(value=10) ^ 3 - >>> print(pipeline.resolve([1, 2, 3])) + >>> pipeline.resolve([1, 2, 3]) [31, 32, 33] """ @@ -3404,9 +3399,9 @@ class Repeat(Feature): __distributed__: bool = False def __init__( - self: Feature, - feature: Feature, - N: int, + self: Repeat, + feature: Feature, + N: int, **kwargs: Any, ): """Initialize the Repeat feature. @@ -3433,13 +3428,13 @@ def __init__( self.feature = self.add_feature(feature) def get( - self: Feature, + self: Repeat, image: Any, N: int, _ID: tuple[int, ...] = (), **kwargs: Any, ) -> Any: - """Sequentially apply the feature `N` times. + """Sequentially apply the feature N times. This method applies the feature `N` times, passing the output of each iteration as the input to the next. The `_ID` tuple is updated at @@ -3465,15 +3460,15 @@ def get( of the feature. """ - + for n in range(N): - index = _ID + (n,) # Track iteration index. + index = _ID + (n,) # Track iteration index image = self.feature( image, _ID=index, - replicate_index=index, # Pass replicate_index for legacy. + replicate_index=index, # Pass replicate_index for legacy ) return image From 5224632a532918d9f2dabdee03aa61e74f48784c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 12:56:39 +0200 Subject: [PATCH 043/223] Update test_features.py --- deeptrack/tests/test_features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index c230a9bac..5f430c8ed 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1203,8 +1203,8 @@ def test_Repeat(self): output_data = pipeline.resolve(input_data) - self.assertTrue(np.array_equal(output_data, expected_output), \ - f"Expected {expected_output}, got {output_data}") + self.assertTrue(np.array_equal(output_data, expected_output), + f"Expected {expected_output}, got {output_data}") pipeline_shorthand = features.Add(value=10) ^ 3 output_data_shorthand = pipeline_shorthand.resolve(input_data) From 4b127efaa39f988359a358c4b4c920daf9b6c299 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 15:08:10 +0200 Subject: [PATCH 044/223] Update features.py --- deeptrack/features.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 137627d33..65f365c38 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3342,8 +3342,8 @@ def get( return image -class Repeat(Feature): - """Applies a feature multiple times in sequence. +class Repeat(StructuralFeature): + """Apply a feature multiple times in sequence. The `Repeat` feature iteratively applies another feature, passing the output of each iteration as the input to the next. This enables chained @@ -3351,9 +3351,12 @@ class Repeat(Feature): number of repetitions is defined by `N`. Each iteration operates with its own set of properties, and the index of - the current iteration is accessible via `_ID` or `replicate_index`. - `_ID` is extended to include the current iteration index, ensuring - deterministic behavior when needed. + the current iteration is accessible via `_ID`. `_ID` is extended to include + the current iteration index, ensuring deterministic behavior when needed. + + This is equivalent to using the `^` operator: + + >>> dt.Repeat(A, 3) ≡ A ^ 3 Parameters ---------- @@ -3363,17 +3366,11 @@ class Repeat(Feature): The number of times to apply the feature in sequence. **kwargs: Any - Attributes - ---------- - __distributed__: bool - Always `False` for `Repeat`, since it processes sequentially rather - than distributing computation across inputs. - Methods ------- - `get(image: Any, N: int, _ID: tuple[int, ...], **kwargs: dict[str, Any]) -> Any` - Applies the feature `N` times in sequence, passing the output of each - iteration as the input to the next. + `get(image: Any, N: int, _ID: tuple[int, ...], **kwargs: Any) -> Any` + It applies the feature `N` times in sequence, passing the output of + each iteration as the input to the next. Examples -------- @@ -3396,8 +3393,6 @@ class Repeat(Feature): """ - __distributed__: bool = False - def __init__( self: Repeat, feature: Feature, @@ -3440,6 +3435,10 @@ def get( iteration as the input to the next. The `_ID` tuple is updated at each iteration, ensuring dynamic property updates and reproducibility. + Each iteration uses the output of the previous one. This makes `Repeat` + suitable for building recursive, cumulative, or progressive + transformations. + Parameters ---------- image: Any @@ -3461,6 +3460,9 @@ def get( """ + if not isinstance(N, int) or N < 0: + raise ValueError("N must be a non-negative integer.") + for n in range(N): index = _ID + (n,) # Track iteration index @@ -3468,7 +3470,7 @@ def get( image = self.feature( image, _ID=index, - replicate_index=index, # Pass replicate_index for legacy + replicate_index=index, # Legacy property ) return image From 0edf8fd305acb000ab9fc22e032dcde9b8d30dc5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 15:21:12 +0200 Subject: [PATCH 045/223] Update test_features.py --- deeptrack/tests/test_features.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 5f430c8ed..ad032d007 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1194,24 +1194,25 @@ def is_transformed(output): def test_Repeat(self): + # Define a simple feature and pipeline add_ten = features.Add(value=10) - pipeline = features.Repeat(add_ten, N=3) input_data = [1, 2, 3] expected_output = [31, 32, 33] + # Test standard Repeat behavior output_data = pipeline.resolve(input_data) + self.assertEqual(output_data, expected_output) - self.assertTrue(np.array_equal(output_data, expected_output), - f"Expected {expected_output}, got {output_data}") - + # Test shorthand syntax (^) produces same result pipeline_shorthand = features.Add(value=10) ^ 3 output_data_shorthand = pipeline_shorthand.resolve(input_data) + self.assertEqual(output_data_shorthand, expected_output) - self.assertTrue(np.array_equal(output_data_shorthand, expected_output), \ - f"Shorthand failed. Expected {expected_output}, \ - got {output_data_shorthand}") + # Test dynamic override of N + output_override = pipeline(input_data, N=2) + self.assertEqual(output_override, [21, 22, 23]) def test_Combine(self): From c8279355c562eaa08d652c1774a3292a6d710c2c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 15:21:14 +0200 Subject: [PATCH 046/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 65f365c38..13d56c41c 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -170,7 +170,7 @@ def merge_features( "Stack", "Arguments", "Probability", - "Repeat", # TODO + "Repeat", "Combine", # TODO "Slice", # TODO "Bind", # TODO From 6f65d74c567560d944d35f3eaa048d69e3989e70 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 15:22:28 +0200 Subject: [PATCH 047/223] Update features.py --- deeptrack/features.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deeptrack/features.py b/deeptrack/features.py index 13d56c41c..536a1b574 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -532,6 +532,8 @@ def __call__( | list[torch.Tensor] | Image | list[Image] + | Any + | list[Any] | None ) = None, _ID: tuple[int, ...] = (), From 63093f91e66d5fb4f35e320e1f46c6437e35b3ba Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 15:23:13 +0200 Subject: [PATCH 048/223] Update test_features.py --- deeptrack/tests/test_features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index ad032d007..6d1fa15f3 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1807,7 +1807,7 @@ def test_OneOfDict(self): output_image = controlled_feature.resolve(input_image) expected_output = input_image * 2 # The "multiply" feature should be applied self.assertTrue(np.array_equal(output_image, expected_output)) - + def test_LoadImage(self): from tempfile import NamedTemporaryFile From 3f8a9cf90650d82224cc89f9a72a5980d789f292 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 15:49:14 +0200 Subject: [PATCH 049/223] Update features.py --- deeptrack/features.py | 46 +++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 536a1b574..1fd389454 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3482,50 +3482,57 @@ class Combine(StructuralFeature): """Combine multiple features into a single feature. This feature sequentially resolves a list of features and returns their - results as a list. Each feature in the `features` parameter operates on - the same input, and their outputs are aggregated into a single list. + results as a list. Parameters ---------- features: list[Feature] A list of features to combine. Each feature will be resolved in the - order they appear in the list. - **kwargs: Any, optional + order they appear in the list and their outputs aggregated into a + single list to be returned. + **kwargs: Any Additional keyword arguments passed to the parent `StructuralFeature` class. Methods ------- - `get(image_list: Any, **kwargs: dict[str, Any]) -> list[Any]` + `get(image_list: Any, **kwargs: Any) -> list[Any]` Resolves each feature in the `features` list on the input image and returns their results as a list. Examples -------- >>> import deeptrack as dt - >>> import numpy as np - Define a list of features to combine `GaussianBlur` and `Add`: - >>> blur_feature = dt.GaussianBlur(sigma=2) - >>> add_feature = dt.Add(value=10) + Define a list of features: + >>> add_1 = dt.Add(value=1) + >>> add_2 = dt.Add(value=2) + >>> add_3 = dt.Add(value=3) Combine the features: - >>> combined_feature = dt.Combine([blur_feature, add_feature]) + >>> combined_feature = dt.Combine([add_1, add_2, add_3]) Define an input image: - >>> input_image = np.ones((10, 10)) + >>> import numpy as np + >>> + >>> input_image = np.zeros((2, 3)) Apply the combined feature: >>> output_list = combined_feature(input_image) + >>> output_list + [array([[1., 1., 1.], + [1., 1., 1.]]), + array([[2., 2., 2.], + [2., 2., 2.]]), + array([[3., 3., 3.], + [3., 3., 3.]])] """ - __distributed__: bool = False - def __init__( - self: Feature, - features: list[Feature], - **kwargs: dict[str, Any] + self: Feature, + features: list[Feature], + **kwargs: Any, ): """Initialize the Combine feature. @@ -3534,19 +3541,20 @@ def __init__( features: list[Feature] A list of features to combine. Each feature is added as a dependency to ensure proper execution in the computation graph. - **kwargs: Any, optional + **kwargs: Any Additional keyword arguments passed to the parent `StructuralFeature` class. """ super().__init__(**kwargs) + self.features = [self.add_feature(f) for f in features] def get( - self: Feature, + self: Feature, image_list: Any, - **kwargs: dict[str, Any] + **kwargs: Any, ) -> list[Any]: """Resolve each feature in the `features` list on the input image. From 3af492b03a7c430b10262eb0a3fbb69aaa3fa3db Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 19:20:24 +0200 Subject: [PATCH 050/223] Update features.py --- deeptrack/features.py | 75 +++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 1fd389454..5d5c50c3e 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -171,7 +171,7 @@ def merge_features( "Arguments", "Probability", "Repeat", - "Combine", # TODO + "Combine", "Slice", # TODO "Bind", # TODO "BindResolve", # TODO @@ -1493,7 +1493,7 @@ def __getitem__( """Allows direct slicing of the feature's output. """ - + if not isinstance(slices, tuple): slices = (slices,) @@ -3481,22 +3481,22 @@ def get( class Combine(StructuralFeature): """Combine multiple features into a single feature. - This feature sequentially resolves a list of features and returns their - results as a list. + This feature applies a list of features to the same input and returns their + outputs as a list. It is useful for computing multiple parallel outputs + from the same data (e.g., branches in a feature graph). Parameters ---------- features: list[Feature] - A list of features to combine. Each feature will be resolved in the - order they appear in the list and their outputs aggregated into a - single list to be returned. + A list of features to combine. Each feature will be applied in order, + and their outputs collected into a list. **kwargs: Any Additional keyword arguments passed to the parent `StructuralFeature` class. Methods ------- - `get(image_list: Any, **kwargs: Any) -> list[Any]` + `get(image: Any, **kwargs: Any) -> list[Any]` Resolves each feature in the `features` list on the input image and returns their results as a list. @@ -3530,7 +3530,7 @@ class Combine(StructuralFeature): """ def __init__( - self: Feature, + self: Combine, features: list[Feature], **kwargs: Any, ): @@ -3552,15 +3552,15 @@ def __init__( self.features = [self.add_feature(f) for f in features] def get( - self: Feature, - image_list: Any, + self: Combine, + image: Any, **kwargs: Any, ) -> list[Any]: """Resolve each feature in the `features` list on the input image. Parameters ---------- - image_list: Any + image: Any The input image or list of images to process. **kwargs: Any Additional arguments passed to each feature's `resolve` method. @@ -3572,16 +3572,16 @@ def get( """ - return [f(image_list, **kwargs) for f in self.features] + return [f(image, **kwargs) for f in self.features] class Slice(Feature): - """Dynamically applies array indexing to input Image(s). + """Dynamically applies array indexing to inputs. - This feature allows **dynamic slicing** of an image using integer indices, - slice objects, or ellipses (`...`). While normal array indexing is preferred - for static cases, `Slice` is useful when the slicing parameters **must be - computed dynamically** based on other properties. + This feature allows dynamic slicing of an image using integer indices, + slice objects, or ellipses (`...`). While normal array indexing is + preferred for static cases, `Slice` is useful when the slicing parameters + *must be computed dynamically based on other properties. Parameters ---------- @@ -3599,22 +3599,31 @@ class Slice(Feature): Examples -------- >>> import deeptrack as dt - >>> import numpy as np - **Recommended Approach: Use Normal Indexing for Static Slicing** + Recommended approach: Use normal indexing for static slicing: + >>> import numpy as np + >>> >>> feature = dt.DummyFeature() - >>> static_slicing = feature[:, 1:2, ::-2] + >>> static_slicing = feature[0:2, ::2, :] >>> result = static_slicing.resolve(np.arange(27).reshape((3, 3, 3))) - >>> print(result) - - **Using `Slice` for Dynamic Slicing (when necessary)** - If slices depend on computed properties, use `Slice`: + >>> result + array([[[ 0, 1, 2], + [ 6, 7, 8]], + [[ 9, 10, 11], + [15, 16, 17]]]) + + Using `Slice` for dynamic slicing (when necessary when slices depend on + computed properties): >>> feature = dt.DummyFeature() >>> dynamic_slicing = feature >> dt.Slice( - ... slices=(slice(None), slice(1, 2), slice(None, None, -2)) + ... slices=(slice(0, 2), slice(None, None, 2), slice(None)) ... ) >>> result = dynamic_slicing.resolve(np.arange(27).reshape((3, 3, 3))) - >>> print(result) + >>> result + array([[[ 0, 1, 2], + [ 6, 7, 8]], + [[ 9, 10, 11], + [15, 16, 17]]]) In both cases, slices can be defined dynamically based on feature properties. @@ -3622,10 +3631,12 @@ class Slice(Feature): """ def __init__( - self: Feature, + self: Slice, slices: PropertyLike[ Iterable[ - PropertyLike[int] | PropertyLike[slice] | PropertyLike[...] + PropertyLike[int] + | PropertyLike[slice] + | PropertyLike[...] ] ], **kwargs: Any, @@ -3645,7 +3656,7 @@ def __init__( super().__init__(slices=slices, **kwargs) def get( - self: Feature, + self: Slice, image: np.ndarray, slices: tuple[Any, ...] | Any, **kwargs: Any, @@ -3671,10 +3682,10 @@ def get( """ try: - # Convert slices to a tuple if possible. + # Convert slices to a tuple if possible slices = tuple(slices) except ValueError: - # Leave slices as is if conversion fails. + # Leave slices as is if conversion fails pass return image[slices] From ae805eec566faa6cc45631a288d72a1dbbf061b5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 25 Jun 2025 19:20:27 +0200 Subject: [PATCH 051/223] Update test_features.py --- deeptrack/tests/test_features.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 6d1fa15f3..18f463de0 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1224,17 +1224,17 @@ def test_Combine(self): input_image = np.ones((10, 10)) output_list = combined_feature.resolve(input_image) - self.assertTrue(isinstance(output_list, list), "Output should be a list") - self.assertTrue(len(output_list) == 2, "Output list should contain results of both features") + self.assertTrue(isinstance(output_list, list)) + self.assertTrue(len(output_list) == 2) for output in output_list: - self.assertTrue(output.shape == input_image.shape, "Output shape mismatch") + self.assertTrue(output.shape == input_image.shape) noisy_image = output_list[0] added_image = output_list[1] - self.assertFalse(np.all(noisy_image == 1), "Gaussian noise was not applied") - self.assertTrue(np.allclose(added_image, input_image + 10), "Add operation failed") + self.assertFalse(np.all(noisy_image == 1)) + self.assertTrue(np.allclose(added_image, input_image + 10)) def test_Slice_constant(self): From 7a5b83fe01364b2a5874bdd545f1b7836b96ac7c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 11:21:11 +0200 Subject: [PATCH 052/223] Update features.py --- deeptrack/features.py | 52 ++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 5d5c50c3e..455aafb55 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1491,14 +1491,13 @@ def __getitem__( slices: Any, ) -> 'Feature': """Allows direct slicing of the feature's output. - + """ if not isinstance(slices, tuple): slices = (slices,) - # We make it a list to ensure that each element is sampled - # independently. + # Make it a list to ensure that each element is sampled independently. slices = list(slices) return self >> Slice(slices) @@ -3577,15 +3576,17 @@ def get( class Slice(Feature): """Dynamically applies array indexing to inputs. - + This feature allows dynamic slicing of an image using integer indices, - slice objects, or ellipses (`...`). While normal array indexing is - preferred for static cases, `Slice` is useful when the slicing parameters - *must be computed dynamically based on other properties. + slice objects, or ellipses (`...`). + + While normal array indexing is preferred for static cases, `Slice` is + useful when the slicing parameters must be computed dynamically based on + other properties. Parameters ---------- - slices: Iterable[int | slice | ...] + slices: tuple[int or slice or ellipsis] or list[int or slice or ellipsis] The slicing instructions for each dimension. Each element corresponds to a dimension in the input image. **kwargs: Any @@ -3593,7 +3594,7 @@ class Slice(Feature): Methods ------- - `get(image: np.ndarray, slices: tuple[int | slice | ...], **kwargs: dict[str, Any]) -> np.ndarray` + `get(image: array or list[array], slices: Iterable[int or slice or ellipsis], **kwargs: Any) -> array or list[array]` Applies the specified slices to the input image. Examples @@ -3632,20 +3633,14 @@ class Slice(Feature): def __init__( self: Slice, - slices: PropertyLike[ - Iterable[ - PropertyLike[int] - | PropertyLike[slice] - | PropertyLike[...] - ] - ], + slices: PropertyLike[Iterable[int | slice | Ellipsis]], **kwargs: Any, ): """Initialize the Slice feature. Parameters ---------- - slices: list[int | slice | ...] or tuple[int | slice | ...] + slices: Iterable[int or slice or ellipsis] The slicing instructions for each dimension, specified as a list or tuple of integers, slice objects, or ellipses (`...`). **kwargs: Any @@ -3657,27 +3652,28 @@ def __init__( def get( self: Slice, - image: np.ndarray, - slices: tuple[Any, ...] | Any, + image: ArrayLike[Any] | list[ArrayLike[Any]], + slices: slice | tuple[int | slice | Ellipsis, ...], **kwargs: Any, - ): + ) -> ArrayLike[Any] | list[ArrayLike[Any]]: """Apply the specified slices to the input image. Parameters ---------- - image: np.ndarray - The input image to be sliced. - slices: tuple[int | slice | ellipsis, ...] | int | slice | ellipsis - The slicing instructions for the input image. Each element in the - tuple corresponds to a dimension in the input image. If a single - element is provided, it is converted to a tuple. + image: array or list[array] + The input image(s) to be sliced. + slices: slice ellipsis or tuple[int or slice or ellipsis, ...] + The slicing instructions for the input image. Typically it is a + tuple. Each element in the tuple corresponds to a dimension in the + input image. If a single element is provided, it is converted to a + tuple. **kwargs: Any Additional keyword arguments (unused in this implementation). Returns ------- - np.ndarray - The sliced image. + array or list[array] + The sliced image(s). """ From 1cf1973736544e9585678e0945161fe43c4e7b41 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 11:21:13 +0200 Subject: [PATCH 053/223] Update test_features.py --- deeptrack/tests/test_features.py | 59 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 18f463de0..ec4002094 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1238,43 +1238,45 @@ def test_Combine(self): def test_Slice_constant(self): - input = np.arange(9).reshape((3, 3)) + image = np.arange(9).reshape((3, 3)) A = features.DummyFeature() + A0 = A[0] + a0 = A0.resolve(image) + self.assertEqual(a0.tolist(), image[0].tolist()) + A1 = A[1] - A22 = A[2, 2] - A12 = A[1, lambda: -1] + a1 = A1.resolve(image) + self.assertEqual(a1.tolist(), image[1].tolist()) - a0 = A0.resolve(input) - a1 = A1.resolve(input) - a22 = A22.resolve(input) - a12 = A12.resolve(input) + A22 = A[2, 2] + a22 = A22.resolve(image) + self.assertEqual(a22, image[2, 2]) - self.assertEqual(a0.tolist(), input[0].tolist()) - self.assertEqual(a1.tolist(), input[1].tolist()) - self.assertEqual(a22, input[2, 2]) - self.assertEqual(a12, input[1, -1]) + A12 = A[1, lambda: -1] + a12 = A12.resolve(image) + self.assertEqual(a12, image[1, -1]) def test_Slice_colon(self): - input = np.arange(16).reshape((4, 4)) A = features.DummyFeature() A0 = A[0, :1] - A1 = A[1, lambda: 0 : lambda: 4 : lambda: 2] - A2 = A[lambda: slice(0, 4, 1), 2] - A3 = A[lambda: 0 : lambda: 2, :] - a0 = A0.resolve(input) - a1 = A1.resolve(input) - a2 = A2.resolve(input) - a3 = A3.resolve(input) - self.assertEqual(a0.tolist(), input[0, :1].tolist()) + + A1 = A[1, lambda: 0 : lambda: 4 : lambda: 2] + a1 = A1.resolve(input) self.assertEqual(a1.tolist(), input[1, 0:4:2].tolist()) + + A2 = A[lambda: slice(0, 4, 1), 2] + a2 = A2.resolve(input) self.assertEqual(a2.tolist(), input[:, 2].tolist()) + + A3 = A[lambda: 0 : lambda: 2, :] + a3 = A3.resolve(input) self.assertEqual(a3.tolist(), input[0:2, :].tolist()) def test_Slice_ellipse(self): @@ -1284,18 +1286,19 @@ def test_Slice_ellipse(self): A = features.DummyFeature() A0 = A[..., :1] - A1 = A[..., lambda: 0 : lambda: 4 : lambda: 2] - A2 = A[lambda: slice(0, 4, 1), ...] - A3 = A[lambda: 0 : lambda: 2, lambda: ...] - a0 = A0.resolve(input) - a1 = A1.resolve(input) - a2 = A2.resolve(input) - a3 = A3.resolve(input) - self.assertEqual(a0.tolist(), input[..., :1].tolist()) + + A1 = A[..., lambda: 0 : lambda: 4 : lambda: 2] + a1 = A1.resolve(input) self.assertEqual(a1.tolist(), input[..., 0:4:2].tolist()) + + A2 = A[lambda: slice(0, 4, 1), ...] + a2 = A2.resolve(input) self.assertEqual(a2.tolist(), input[:, ...].tolist()) + + A3 = A[lambda: 0 : lambda: 2, lambda: ...] + a3 = A3.resolve(input) self.assertEqual(a3.tolist(), input[0:2, ...].tolist()) def test_Slice_static_dynamic(self): From 27a76b13113993038fd7c838f063f5bba65d20f6 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 11:21:42 +0200 Subject: [PATCH 054/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 455aafb55..bc4cb4dc1 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -172,7 +172,7 @@ def merge_features( "Probability", "Repeat", "Combine", - "Slice", # TODO + "Slice", "Bind", # TODO "BindResolve", # TODO "BindUpdate", # TODO From 927f6ab3f3f0d1fd1797caebb1170c61e8a3f3ed Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 11:28:07 +0200 Subject: [PATCH 055/223] Update features.py --- deeptrack/features.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index bc4cb4dc1..10b7b3670 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -6689,34 +6689,35 @@ class Transpose(Feature): axes: tuple[int, ...], optional A tuple specifying the permutation of the axes. If `None`, the axes are reversed by default. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get(image: np.ndarray, axes: tuple[int, ...] | None, **kwargs: dict[str, Any]) -> np.ndarray` + `get(image: array or list[array], axes: tuple[int, ...] | None, **kwargs: Any) -> array or list[array]` Transpose the axes of the input image Examples -------- >>> import deeptrack as dt - >>> import numpy as np Create an input array: + >>> import numpy as np + >>> >>> input_image = np.random.rand(2, 3, 4) - >>> print(input_image.shape) + >>> input_image.shape (2, 3, 4) Apply a Transpose feature: >>> transpose_feature = dt.Transpose(axes=(1, 2, 0)) >>> output_image = transpose_feature(input_image) - >>> print(output_image.shape) + >>> output_image.shape (3, 4, 2) Without specifying axes: >>> transpose_feature = dt.Transpose() >>> output_image = transpose_feature(input_image) - >>> print(output_image.shape) + >>> output_image.shape (4, 3, 2) """ @@ -6733,7 +6734,7 @@ def __init__( axes: tuple[int, ...], optional A tuple specifying the permutation of the axes. If `None`, the axes are reversed by default. - **kwargs:: dict of str to Any + **kwargs:: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -6742,15 +6743,15 @@ def __init__( def get( self: Transpose, - image: np.ndarray, + image: ArrayLike[Any] | list[ArrayLike[Any]], axes: tuple[int, ...] | None = None, **kwargs: Any, - ) -> np.ndarray: + ) -> ArrayLike[Any] | list[ArrayLike[Any]]: """Transpose the axes of the input image. Parameters ---------- - image: np.ndarray + image: array or list[array] The input image to process. axes: tuple[int, ...], optional A tuple specifying the permutation of the axes. If `None`, the @@ -6760,12 +6761,12 @@ def get( Returns ------- - np.ndarray + array or list[array] The transposed image with rearranged axes. """ - return np.transpose(image, axes) + return xp.transpose(image, axes) Permute = Transpose From 7040b1c223cdccc2a9af7f52cf20ec57ae1652cd Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 11:47:26 +0200 Subject: [PATCH 056/223] Update test_features.py --- deeptrack/tests/test_features.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index ec4002094..7225fa338 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -23,6 +23,9 @@ from deeptrack.image import Image from deeptrack.noises import Gaussian +if TORCH_AVAILABLE: + import torch + def grid_test_features( tester, feature_a, @@ -629,8 +632,6 @@ def test_DummyFeature(self): # Test with PyTorch if TORCH_AVAILABLE: - import torch - # Test with PyTorch tensor tensor = torch.ones(4, 4) self.assertIs(feature.get(tensor), tensor) @@ -702,8 +703,6 @@ def test_Value(self): # PyTorch tensor value tests if TORCH_AVAILABLE: - import torch - tensor = torch.tensor([1., 2., 3.]) value_tensor = features.Value(value=tensor) self.assertTrue(torch.equal(value_tensor(), tensor)) @@ -776,8 +775,6 @@ def test_ArithmeticOperationFeature(self): # PyTorch tensor input (if available) if TORCH_AVAILABLE: - import torch - addition_feature = features.ArithmeticOperationFeature( lambda a, b: a + b, value=5, ) @@ -956,8 +953,6 @@ def test_Stack(self): # Stack PyTorch tensors if TORCH_AVAILABLE: - import torch - t1 = torch.tensor([1, 2]) t2 = torch.tensor([3, 4]) feature = features.Stack(value=t2) @@ -2401,6 +2396,9 @@ def test_Transpose(self): output_image = transpose_feature(input_image) self.assertEqual(output_image.shape, (4, 3, 2)) + if TORCH_AVAILABLE: + pass + def test_OneHot(self): From bfd68521b48e0181a2b931513002082d40193011 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 11:47:28 +0200 Subject: [PATCH 057/223] Update features.py --- deeptrack/features.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 10b7b3670..d63c837b5 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -6694,8 +6694,8 @@ class Transpose(Feature): Methods ------- - `get(image: array or list[array], axes: tuple[int, ...] | None, **kwargs: Any) -> array or list[array]` - Transpose the axes of the input image + `get(image: array, axes: tuple[int, ...] | None, **kwargs: Any) -> array` + Transpose the axes of the input image(s). Examples -------- @@ -6743,15 +6743,15 @@ def __init__( def get( self: Transpose, - image: ArrayLike[Any] | list[ArrayLike[Any]], + image: ArrayLike[Any], axes: tuple[int, ...] | None = None, **kwargs: Any, - ) -> ArrayLike[Any] | list[ArrayLike[Any]]: + ) -> ArrayLike[Any]: """Transpose the axes of the input image. Parameters ---------- - image: array or list[array] + image: array The input image to process. axes: tuple[int, ...], optional A tuple specifying the permutation of the axes. If `None`, the @@ -6761,7 +6761,7 @@ def get( Returns ------- - array or list[array] + array The transposed image with rearranged axes. """ From f90f2c113afcf65adea8b4e66a5b3dcf20218212 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 12:32:57 +0200 Subject: [PATCH 058/223] Update features.py --- deeptrack/features.py | 87 +++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index d63c837b5..76f0fe6b0 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -123,6 +123,7 @@ def merge_features( from __future__ import annotations +import array_api_compat as apc import itertools import operator import random @@ -193,8 +194,8 @@ def merge_features( "Unsqueeze", # TODO "ExpandDims", # TODO "MoveAxis", # TODO - "Transpose", # TODO - "Permute", # TODO + "Transpose", + "Permute", "OneHot", # TODO "TakeProperties", # TODO ] @@ -1764,7 +1765,7 @@ class Chain(StructuralFeature): Methods ------- - `get(image: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any | list[Any]` + `get(image: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` Apply the two features in sequence on the given input image. Examples @@ -1825,10 +1826,10 @@ def __init__( def get( self: Feature, - image: Any | list[Any], + image: Any, _ID: tuple[int, ...] = (), **kwargs: Any, - ) -> Any | list[Any]: + ) -> Any: """Apply the two features sequentially to the given input image(s). This method first applies `feature_1` to the input image(s) and then @@ -1836,10 +1837,9 @@ def get( Parameters ---------- - image: Any or list[Any] + image: Any The input data to transform sequentially. Most typically, this is - a NumPy array, a PyTorch tensor, or an Image, or a list of the - same. + a NumPy array, a PyTorch tensor, or an Image. _ID: tuple[int, ...], optional A unique identifier for caching or parallel execution. It defaults to an empty tuple. @@ -1850,7 +1850,7 @@ def get( Returns ------- - Any or list[Any] + Any The final output after `feature_1` and then `feature_2` have processed the input. @@ -1973,7 +1973,7 @@ class Value(Feature): Methods ------- - `get(image: Any, value: float, **kwargs: Any) -> float` + `get(image: Any, value: float, **kwargs: Any) -> float or array` Returns the stored value, ignoring the input image. Examples @@ -2054,9 +2054,9 @@ def __init__( def get( self: Value, image: Any, - value: float | ArrayLike, + value: float | ArrayLike[Any], **kwargs: Any, - ) -> float | ArrayLike: + ) -> float | ArrayLike[Any]: """Return the stored value, ignoring the input image. The `get` method simply returns the stored numerical value, allowing @@ -2114,7 +2114,7 @@ class ArithmeticOperationFeature(Feature): Methods ------- - `get(image: Any or list[Any], value: float or int or list[float or int], **kwargs: Any) -> list[Any]` + `get(image: Any, value: float or int or list[float or int], **kwargs: Any) -> list[Any]` Apply the arithmetic operation element-wise to the input data. Examples @@ -2171,7 +2171,7 @@ def __init__( def get( self: ArithmeticOperationFeature, - image: Any | list[Any], + image: Any, value: float | int | ArrayLike | list[float | int | ArrayLike], **kwargs: Any, ) -> list[Any]: @@ -6598,31 +6598,33 @@ class MoveAxis(Feature): Parameters ---------- source: int - The axis to move. + The source position of the axis to move. destination: int The destination position of the axis. - **kwargs:: dict of str to Any + **kwargs:: Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get(image: np.ndarray, source: int, destination: int, **kwargs: dict[str, Any]) -> np.ndarray` - Move the specified axis of the input image to a new position. + `get(image: array, source: int, destination: int, **kwargs: Any) -> array` + Move the specified axis of the input image to a new position. The input + and output array can be a NumPy array, a PyTorch tensor, or an Image. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Create an input array: + >>> import numpy as np + >>> >>> input_image = np.random.rand(2, 3, 4) - >>> print(input_image.shape) + >>> input_image.shape (2, 3, 4) Apply a MoveAxis feature: >>> move_axis_feature = dt.MoveAxis(source=0, destination=2) >>> output_image = move_axis_feature(input_image) - >>> print(output_image.shape) + >>> output_image.shape (3, 4, 2) """ @@ -6641,7 +6643,7 @@ def __init__( The axis to move. destination: int The destination position of the axis. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -6650,31 +6652,41 @@ def __init__( def get( self: MoveAxis, - image: np.ndarray, + image: NDArray | torch.Tensor | Image, source: int, - destination: int, + destination: int, **kwargs: Any, - ) -> np.ndarray: + ) -> NDArray | torch.Tensor | Image: """Move the specified axis of the input image to a new position. Parameters ---------- - image: np.ndarray - The input image to process. + image: array + The input image to process. The input array can be a NumPy array, a + PyTorch tensor, or an Image. source: int The axis to move. destination: int The destination position of the axis. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments (unused here). Returns ------- - np.ndarray + array The input image with the specified axis moved to the destination. + The output array can be a NumPy array, a PyTorch tensor, or an + Image. + """ - return np.moveaxis(image, source, destination) + if apc.is_torch_array(image): + axes = list(range(image.ndim)) + axis = axes.pop(source) + axes.insert(destination, axis) + return image.permute(*axes) + + return xp.moveaxis(image, source, destination) class Transpose(Feature): @@ -6695,7 +6707,8 @@ class Transpose(Feature): Methods ------- `get(image: array, axes: tuple[int, ...] | None, **kwargs: Any) -> array` - Transpose the axes of the input image(s). + Transpose the axes of the input image(s). The input and output array + can be a NumPy array, a PyTorch tensor, or an Image. Examples -------- @@ -6734,7 +6747,7 @@ def __init__( axes: tuple[int, ...], optional A tuple specifying the permutation of the axes. If `None`, the axes are reversed by default. - **kwargs:: Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -6743,16 +6756,17 @@ def __init__( def get( self: Transpose, - image: ArrayLike[Any], + image: NDArray | torch.Tensor | Image, axes: tuple[int, ...] | None = None, **kwargs: Any, - ) -> ArrayLike[Any]: + ) -> NDArray | torch.Tensor | Image: """Transpose the axes of the input image. Parameters ---------- image: array - The input image to process. + The input image to process. The input array can be a NumPy array, a + PyTorch tensor, or an Image. axes: tuple[int, ...], optional A tuple specifying the permutation of the axes. If `None`, the axes are reversed by default. @@ -6762,7 +6776,8 @@ def get( Returns ------- array - The transposed image with rearranged axes. + The transposed image with rearranged axes. The output array can be + a NumPy array, a PyTorch tensor, or an Image. """ From f25fc1ceed8a1f912d637bb65975dd16e07c5d46 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 12:32:59 +0200 Subject: [PATCH 059/223] Update test_features.py --- deeptrack/tests/test_features.py | 52 ++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 7225fa338..467495800 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2376,28 +2376,74 @@ def test_Unsqueeze(self): def test_MoveAxis(self): - + ### Test with NumPy array input_image = np.random.rand(2, 3, 4) move_axis_feature = features.MoveAxis(source=0, destination=2) output_image = move_axis_feature(input_image) self.assertEqual(output_image.shape, (3, 4, 2)) + ### Test with Image + input_data = np.random.rand(2, 3, 4) + input_image = features.Image(input_data) - def test_Transpose(self): + move_axis_feature = features.MoveAxis(source=0, destination=2) + output_image = move_axis_feature(input_image) + self.assertEqual(output_image.shape, (3, 4, 2)) + + ### Test with PyTorch tensor (if available) + if TORCH_AVAILABLE: + input_tensor = torch.rand(2, 3, 4) + + move_axis_feature = features.MoveAxis(source=0, destination=2) + output_tensor = move_axis_feature(input_tensor) + print(output_tensor.shape) + self.assertEqual(output_tensor.shape, (3, 4, 2)) + + def test_Transpose(self): + ### Test with NumPy array input_image = np.random.rand(2, 3, 4) + # Explicit axes transpose_feature = features.Transpose(axes=(1, 2, 0)) output_image = transpose_feature(input_image) self.assertEqual(output_image.shape, (3, 4, 2)) + expected_output = np.transpose(input_image, (1, 2, 0)) + self.assertTrue(np.allclose(output_image, expected_output)) + # Reversed axes transpose_feature = features.Transpose() output_image = transpose_feature(input_image) self.assertEqual(output_image.shape, (4, 3, 2)) + expected_output = np.transpose(input_image) + self.assertTrue(np.allclose(output_image, expected_output)) + + ### Test with Image + input_data = np.random.rand(2, 3, 4) + input_image = features.Image(input_data) + + transpose_feature = features.Transpose(axes=(1, 2, 0)) + output_image = transpose_feature(input_image) + self.assertEqual(output_image.shape, (3, 4, 2)) + ### Test with PyTorch tensor (if available) if TORCH_AVAILABLE: - pass + input_tensor = torch.rand(2, 3, 4) + + # Explicit axes + transpose_feature = features.Transpose(axes=(1, 2, 0)) + output_tensor = transpose_feature(input_tensor) + self.assertEqual(output_tensor.shape, (3, 4, 2)) + expected_tensor = input_tensor.permute(1, 2, 0) + self.assertTrue(torch.allclose(output_tensor, expected_tensor)) + + # Reversed axes + transpose_feature = features.Transpose() + output_tensor = transpose_feature(input_tensor) + self.assertEqual(output_tensor.shape, (4, 3, 2)) + expected_tensor = input_tensor.permute(2, 1, 0) + self.assertTrue(torch.allclose(output_tensor, expected_tensor)) def test_OneHot(self): From 65773d0ee2e03373e5faf130f76643a6a32388f7 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 12:39:49 +0200 Subject: [PATCH 060/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 76f0fe6b0..8f0dc58fa 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -193,7 +193,7 @@ def merge_features( "Squeeze", # TODO "Unsqueeze", # TODO "ExpandDims", # TODO - "MoveAxis", # TODO + "MoveAxis", "Transpose", "Permute", "OneHot", # TODO From 155dcf35dd8bdba5bd58db8313355bf5f23d0616 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 12:48:49 +0200 Subject: [PATCH 061/223] Update features.py --- deeptrack/features.py | 59 +++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 8f0dc58fa..0d69d4488 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -190,9 +190,9 @@ def merge_features( "Upscale", # TODO "NonOverlapping", # TODO "Store", # TODO - "Squeeze", # TODO - "Unsqueeze", # TODO - "ExpandDims", # TODO + "Squeeze", + "Unsqueeze", + "ExpandDims", "MoveAxis", "Transpose", "Permute", @@ -6494,7 +6494,7 @@ def get( class Unsqueeze(Feature): - """Unsqueezes the input image to the smallest possible dimension. + """Unsqueeze the input image to the smallest possible dimension. This feature adds new singleton dimensions to the input image at the specified axis or axes. If no axis is specified, it defaults to adding @@ -6503,36 +6503,38 @@ class Unsqueeze(Feature): Parameters ---------- axis: int or tuple[int, ...], optional - The axis or axes where new singleton dimensions should be added. - It defaults to `None`, which adds a singleton dimension at the last axis. - **kwargs:: dict of str to Any + The axis or axes where new singleton dimensions should be added. It + defaults to `None`, which adds a singleton dimension at the last axis. + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get(image: np.ndarray, axis: int | tuple[int, ...] | None, **kwargs: dict[str, Any]) -> np.ndarray` - Add singleton dimensions to the input image. + `get(image: array, axis: int | tuple[int, ...] | None, **kwargs: Any) -> array` + Add singleton dimensions to the input image. The input and output + arrays can be a NumPy array, a PyTorch tensor, or an Image. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Create an input array: + >>> import numpy as np + >>> >>> input_image = np.array([1, 2, 3]) - >>> print(input_image.shape) + >>> input_image.shape (3,) - Apply an Unsqueeze feature: + Apply Unsqueeze feature: >>> unsqueeze_feature = dt.Unsqueeze(axis=0) >>> output_image = unsqueeze_feature(input_image) - >>> print(output_image.shape) + >>> output_image.shape (1, 3) - Without specifying an axis: + Without specifying an axis, in unsqueezes the last dimension: >>> unsqueeze_feature = dt.Unsqueeze() >>> output_image = unsqueeze_feature(input_image) - >>> print(output_image.shape) + >>> output_image.shape (3, 1) """ @@ -6547,9 +6549,9 @@ def __init__( Parameters ---------- axis: int or tuple[int, ...], optional - The axis or axes where new singleton dimensions should be added. - It defaults to -1, which adds a singleton dimension at the last axis. - **kwargs:: dict of str to Any + The axis or axes where new singleton dimensions should be added. It + defaults to -1, which adds a singleton dimension at the last axis. + **kwargs:: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -6558,31 +6560,34 @@ def __init__( def get( self: Unsqueeze, - image: np.ndarray, + image: np.ndarray | torch.Tensor | Image, axis: int | tuple[int, ...] | None = -1, **kwargs: Any, - ) -> np.ndarray: + ) -> np.ndarray | torch.Tensor | Image: """Add singleton dimensions to the input image. Parameters ---------- - image: np.ndarray - The input image to process. + image: array + The input image to process. The input array can be a NumPy array, a + PyTorch tensor, or an Image. axis: int or tuple[int, ...], optional The axis or axes where new singleton dimensions should be added. - It defaults to -1, which adds a singleton dimension at the last axis. - **kwargs:: dict of str to Any + It defaults to -1, which adds a singleton dimension at the last + axis. + **kwargs: Any Additional keyword arguments (unused here). Returns ------- - np.ndarray - The input image with the specified singleton dimensions added. + array + The input image with the specified singleton dimensions added. The + output array can be a NumPy array, a PyTorch tensor, or an Image. """ - return np.expand_dims(image, axis=axis) + return xp.expand_dims(image, axis=axis) ExpandDims = Unsqueeze From 12a88e19f15a97db3d9e7b0b24a96dd5458243d1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 12:58:12 +0200 Subject: [PATCH 062/223] Update features.py --- deeptrack/features.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/deeptrack/features.py b/deeptrack/features.py index 0d69d4488..84ae0ae43 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -6587,6 +6587,13 @@ def get( """ + if apc.is_torch_array(image): + if isinstance(axis, int): + axis = (axis,) + for ax in sorted(axis): + image = image.unsqueeze(ax) + return image + return xp.expand_dims(image, axis=axis) From 4eb169c44dff4a449bf4842242862d67616232a5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 12:58:14 +0200 Subject: [PATCH 063/223] Update test_features.py --- deeptrack/tests/test_features.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 467495800..72c93339a 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2363,8 +2363,20 @@ def test_Squeeze(self): def test_Unsqueeze(self): + ### Test with NumPy array + input_image = np.array([1, 2, 3]) + + unsqueeze_feature = features.Unsqueeze(axis=0) + output_image = unsqueeze_feature(input_image) + self.assertEqual(output_image.shape, (1, 3)) + + unsqueeze_feature = features.Unsqueeze() + output_image = unsqueeze_feature(input_image) + self.assertEqual(output_image.shape, (3, 1)) - input_image = np.array([1, 2, 3]) # shape (3,) + ### Test with Image + input_data = np.array([1, 2, 3]) + input_image = features.Image(input_data) unsqueeze_feature = features.Unsqueeze(axis=0) output_image = unsqueeze_feature(input_image) @@ -2374,6 +2386,21 @@ def test_Unsqueeze(self): output_image = unsqueeze_feature(input_image) self.assertEqual(output_image.shape, (3, 1)) + ### Test with PyTorch tensor (if available) + if TORCH_AVAILABLE: + input_tensor = torch.tensor([1, 2, 3]) + + unsqueeze_feature = features.Unsqueeze(axis=0) + output_tensor = unsqueeze_feature(input_tensor) + self.assertEqual(output_tensor.shape, (1, 3)) + torch.testing.assert_close(output_tensor, + input_tensor.unsqueeze(0)) + + unsqueeze_feature = features.Unsqueeze() + output_tensor = unsqueeze_feature(input_tensor) + self.assertEqual(output_tensor.shape, (3, 1)) + torch.testing.assert_close(output_tensor, + input_tensor.unsqueeze(-1)) def test_MoveAxis(self): ### Test with NumPy array From e6734f091cb28566029e9171f1ca4cbd20652c8f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 13:01:10 +0200 Subject: [PATCH 064/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 84ae0ae43..8008735d3 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -190,7 +190,7 @@ def merge_features( "Upscale", # TODO "NonOverlapping", # TODO "Store", # TODO - "Squeeze", + "Squeeze", # TODO "Unsqueeze", "ExpandDims", "MoveAxis", From 3dee646394ff484156165899eb6ad53fe1c80c74 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 13:01:13 +0200 Subject: [PATCH 065/223] Update test_features.py --- deeptrack/tests/test_features.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 72c93339a..ebf46c3c0 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2374,6 +2374,11 @@ def test_Unsqueeze(self): output_image = unsqueeze_feature(input_image) self.assertEqual(output_image.shape, (3, 1)) + # Multiple axes + unsqueeze_feature = features.Unsqueeze(axis=(0, 2)) + output_image = unsqueeze_feature(input_image) + self.assertEqual(output_image.shape, (1, 3, 1)) + ### Test with Image input_data = np.array([1, 2, 3]) input_image = features.Image(input_data) @@ -2386,6 +2391,11 @@ def test_Unsqueeze(self): output_image = unsqueeze_feature(input_image) self.assertEqual(output_image.shape, (3, 1)) + # Multiple axes + unsqueeze_feature = features.Unsqueeze(axis=(0, 2)) + output_image = unsqueeze_feature(input_image) + self.assertEqual(output_image.shape, (1, 3, 1)) + ### Test with PyTorch tensor (if available) if TORCH_AVAILABLE: input_tensor = torch.tensor([1, 2, 3]) @@ -2402,6 +2412,13 @@ def test_Unsqueeze(self): torch.testing.assert_close(output_tensor, input_tensor.unsqueeze(-1)) + # Multiple axes + unsqueeze_feature = features.Unsqueeze(axis=(0, 2)) + output_tensor = unsqueeze_feature(input_tensor) + self.assertEqual(output_tensor.shape, (1, 3, 1)) + expected_tensor = input_tensor.unsqueeze(0).unsqueeze(2) + torch.testing.assert_close(output_tensor, expected_tensor) + def test_MoveAxis(self): ### Test with NumPy array input_image = np.random.rand(2, 3, 4) From a5a9736225fbe07c23edfcac28257a5b96bd43ac Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 19:35:13 +0200 Subject: [PATCH 066/223] Update test_features.py --- deeptrack/tests/test_features.py | 64 +++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index ebf46c3c0..6dcb47d05 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2350,16 +2350,75 @@ def test_Store(self): def test_Squeeze(self): + ### Test with NumPy array + input_image = np.array([[[[3], [2], [1]]], [[[1], [2], [3]]]]) + # shape: (2, 1, 3, 1) + + # Squeeze axis 1 + squeeze_feature = features.Squeeze(axis=1) + output_image = squeeze_feature(input_image) + self.assertEqual(output_image.shape, (2, 3, 1)) + expected_output = np.squeeze(input_image, axis=1) + np.testing.assert_array_equal(output_image, expected_output) + + # Squeeze all singleton dimensions + squeeze_feature = features.Squeeze() + output_image = squeeze_feature(input_image) + self.assertEqual(output_image.shape, (2, 3)) + expected_output = np.squeeze(input_image) + np.testing.assert_array_equal(output_image, expected_output) + + # Squeeze multiple axes + squeeze_feature = features.Squeeze(axis=(1, 3)) + output_image = squeeze_feature(input_image) + self.assertEqual(output_image.shape, (2, 3)) + expected_output = np.squeeze(np.squeeze(input_image, axis=3), axis=1) + np.testing.assert_array_equal(output_image, expected_output) - input_image = np.array([[[[3], [2], [1]]],[[[1], [2], [3]]]]) + ### Test with Image + input_data = np.array([[[[3], [2], [1]]], [[[1], [2], [3]]]]) + input_image = features.Image(input_data) squeeze_feature = features.Squeeze(axis=1) output_image = squeeze_feature(input_image) self.assertEqual(output_image.shape, (2, 3, 1)) + expected_output = np.squeeze(input_data, axis=1) + np.testing.assert_array_equal(output_image, expected_output) squeeze_feature = features.Squeeze() output_image = squeeze_feature(input_image) - self.assertEqual(output_image.shape, (2,3)) + self.assertEqual(output_image.shape, (2, 3)) + expected_output = np.squeeze(input_data) + np.testing.assert_array_equal(output_image, expected_output) + + squeeze_feature = features.Squeeze(axis=(1, 3)) + output_image = squeeze_feature(input_image) + self.assertEqual(output_image.shape, (2, 3)) + expected_output = np.squeeze(np.squeeze(input_data, axis=3), axis=1) + np.testing.assert_array_equal(output_image, expected_output) + + ### Test with PyTorch tensor (if available) + if TORCH_AVAILABLE: + input_tensor = torch.tensor([[[[3], [2], [1]]], [[[1], [2], [3]]]]) + # shape: (2, 1, 3, 1) + + squeeze_feature = features.Squeeze(axis=1) + output_tensor = squeeze_feature(input_tensor) + self.assertEqual(output_tensor.shape, (2, 3, 1)) + expected_tensor = input_tensor.squeeze(1) + torch.testing.assert_close(output_tensor, expected_tensor) + + squeeze_feature = features.Squeeze() + output_tensor = squeeze_feature(input_tensor) + self.assertEqual(output_tensor.shape, (2, 3)) + expected_tensor = input_tensor.squeeze() + torch.testing.assert_close(output_tensor, expected_tensor) + + squeeze_feature = features.Squeeze(axis=(1, 3)) + output_tensor = squeeze_feature(input_tensor) + self.assertEqual(output_tensor.shape, (2, 3)) + expected_tensor = input_tensor.squeeze(3).squeeze(1) + torch.testing.assert_close(output_tensor, expected_tensor) def test_Unsqueeze(self): @@ -2419,6 +2478,7 @@ def test_Unsqueeze(self): expected_tensor = input_tensor.unsqueeze(0).unsqueeze(2) torch.testing.assert_close(output_tensor, expected_tensor) + def test_MoveAxis(self): ### Test with NumPy array input_image = np.random.rand(2, 3, 4) From dcae336debd665fbc2a553cb38aaa4306ae4df76 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 20:30:08 +0200 Subject: [PATCH 067/223] Update features.py --- deeptrack/features.py | 62 +++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 8008735d3..b1ab4e03f 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -190,7 +190,7 @@ def merge_features( "Upscale", # TODO "NonOverlapping", # TODO "Store", # TODO - "Squeeze", # TODO + "Squeeze", "Unsqueeze", "ExpandDims", "MoveAxis", @@ -6414,34 +6414,36 @@ class Squeeze(Feature): ---------- axis: int or tuple[int, ...], optional The axis or axes to squeeze. It defaults to `None`, squeezing all axes. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get(image: np.ndarray, axis: int | tuple[int, ...], **kwargs: dict[str, Any]) -> np.ndarray` - Squeeze the input image by removing singleton dimensions. + `get(image: array, axis: int | tuple[int, ...], **kwargs: Any) -> array` + Squeeze the input image by removing singleton dimensions. The input and + output arrays can be a NumPy array, a PyTorch tensor, or an Image. Examples -------- - >>> import numpy as np - >>> from deeptrack.features import Squeeze + >>> import deeptrack as dt Create an input array with extra dimensions: + >>> import numpy as np + >>> >>> input_image = np.array([[[[1], [2], [3]]]]) - >>> print(input_image.shape) + >>> input_image.shape (1, 1, 3, 1) Create a Squeeze feature: - >>> squeeze_feature = Squeeze(axis=0) + >>> squeeze_feature = dt.Squeeze(axis=0) >>> output_image = squeeze_feature(input_image) - >>> print(output_image.shape) + >>> output_image.shape (1, 3, 1) Without specifying an axis: - >>> squeeze_feature = Squeeze() + >>> squeeze_feature = dt.Squeeze() >>> output_image = squeeze_feature(input_image) - >>> print(output_image.shape) + >>> output_image.shape (3,) """ @@ -6457,8 +6459,8 @@ def __init__( ---------- axis: int or tuple[int, ...], optional The axis or axes to squeeze. It defaults to `None`, which squeezes - all axes. - **kwargs:: dict of str to Any + all singleton axes. + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -6467,30 +6469,41 @@ def __init__( def get( self: Squeeze, - image: np.ndarray, + image: NDArray | torch.Tensor | Image, axis: int | tuple[int, ...] | None = None, **kwargs: Any, - ) -> np.ndarray: + ) -> NDArray | torch.Tensor | Image: """Squeeze the input image by removing singleton dimensions. Parameters ---------- - image: np.ndarray - The input image to process. + image: array + The input image to process. The input array can be a NumPy array, a + PyTorch tensor, or an Image. axis: int or tuple[int, ...], optional The axis or axes to squeeze. It defaults to `None`, which squeezes - all axes. - **kwargs:: dict of str to Any + all singleton axes. + **kwargs: Any Additional keyword arguments (unused here). Returns ------- - np.ndarray - The squeezed image with reduced dimensions. + array + The squeezed image with reduced dimensions. The output array can be + a NumPy array, a PyTorch tensor, or an Image. """ - return np.squeeze(image, axis=axis) + if apc.is_torch_array(image): + if axis is None: + return image.squeeze() + if isinstance(axis, int): + return image.squeeze(axis) + for ax in sorted(axis, reverse=True): + image = image.squeeze(ax) + return image + + return xp.squeeze(image, axis=axis) class Unsqueeze(Feature): @@ -6885,8 +6898,11 @@ def get( if image.shape[-1] == 1: image = image[..., 0] + if apc.is_torch_array(image): + return torch.nn.functional.one_hot(image, num_classes=num_classes) + # Create the one-hot encoded array. - return np.eye(num_classes)[image] + return xp.eye(num_classes)[image] class TakeProperties(Feature): From c7985bfb9122931dfa43a30e81fe653a180d3800 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 27 Jun 2025 20:30:10 +0200 Subject: [PATCH 068/223] Update test_features.py --- deeptrack/tests/test_features.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 6dcb47d05..25e2e5633 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2377,6 +2377,7 @@ def test_Squeeze(self): ### Test with Image input_data = np.array([[[[3], [2], [1]]], [[[1], [2], [3]]]]) + # shape: (2, 1, 3, 1) input_image = features.Image(input_data) squeeze_feature = features.Squeeze(axis=1) From 0128f220267ced242bd2d838f019319e679a1d7b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 28 Jun 2025 00:18:05 +0200 Subject: [PATCH 069/223] Update features.py --- deeptrack/features.py | 47 ++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index b1ab4e03f..3c53232c0 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -137,7 +137,7 @@ def merge_features( from scipy.spatial.distance import cdist from deeptrack import units -from deeptrack.backend import config, xp +from deeptrack.backend import config, TORCH_AVAILABLE, xp from deeptrack.backend.core import DeepTrackNode from deeptrack.backend.units import ConversionTable, create_context from deeptrack.image import Image @@ -145,6 +145,8 @@ def merge_features( from deeptrack.sources import SourceItem from deeptrack.types import ArrayLike, PropertyLike +if TORCH_AVAILABLE: + import torch __all__ = [ "Feature", # TODO @@ -196,7 +198,7 @@ def merge_features( "MoveAxis", "Transpose", "Permute", - "OneHot", # TODO + "OneHot", "TakeProperties", # TODO ] @@ -6823,30 +6825,32 @@ class OneHot(Feature): ---------- num_classes: int The total number of classes for the one-hot encoding. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get(image: np.ndarray, num_classes: int, **kwargs: dict[str, Any]) -> np.ndarray` + `get(image: array, num_classes: int, **kwargs: Any) -> array` Convert the input array of class labels into a one-hot encoded array. + The input and output arrays can be a NumPy array, a PyTorch tensor, or + an Image. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Create an input array of class labels: + >>> import numpy as np + >>> >>> input_data = np.array([0, 1, 2]) Apply a OneHot feature: >>> one_hot_feature = dt.OneHot(num_classes=3) - >>> one_hot_feature = dt.OneHot(num_classes=3) >>> one_hot_encoded = one_hot_feature.get(input_data, num_classes=3) - >>> print(one_hot_encoded) - [[1. 0. 0.] - [0. 1. 0.] - [0. 0. 1.]] + >>> one_hot_encoded + array([[1., 0., 0.], + [0., 1., 0.], + [0., 0., 1.]]) """ @@ -6861,7 +6865,7 @@ def __init__( ---------- num_classes: int The total number of classes for the one-hot encoding. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -6870,17 +6874,18 @@ def __init__( def get( self: OneHot, - image: np.ndarray, + image: NDArray | torch.Tensor | Image, num_classes: int, **kwargs: Any, - ) -> np.ndarray: + ) -> NDArray | torch.Tensor | Image: """Convert the input array of labels into a one-hot encoded array. Parameters ---------- - image: np.ndarray + image: array The input array of class labels. The last dimension should contain - integers representing class indices. + integers representing class indices. The input array can be a NumPy + array, a PyTorch tensor, or an Image. num_classes: int The total number of classes for the one-hot encoding. **kwargs: Any @@ -6888,9 +6893,11 @@ def get( Returns ------- - np.ndarray + array The one-hot encoded array. The last dimension is replaced with - one-hot vectors of length `num_classes`. + one-hot vectors of length `num_classes`. The output array can be a + NumPy array, a PyTorch tensor, or an Image. In all cases, it is of + data type float32 (e.g., np.float32 or torch.float32). """ @@ -6899,10 +6906,12 @@ def get( image = image[..., 0] if apc.is_torch_array(image): - return torch.nn.functional.one_hot(image, num_classes=num_classes) + return (torch.nn.functional + .one_hot(image, num_classes=num_classes) + .to(dtype=torch.float32)) # Create the one-hot encoded array. - return xp.eye(num_classes)[image] + return xp.eye(num_classes, dtype=np.float32)[image] class TakeProperties(Feature): From 2234f8dc34a3f1bd868e7e3135d08e43bc115175 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 28 Jun 2025 00:18:07 +0200 Subject: [PATCH 070/223] Update test_features.py --- deeptrack/tests/test_features.py | 45 ++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 25e2e5633..760be1b28 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2552,17 +2552,52 @@ def test_Transpose(self): def test_OneHot(self): - + ### Test with NumPy array input_image = np.array([0, 1, 2]) - one_hot_feature = features.OneHot(num_classes=3) - output_image = one_hot_feature.get(input_image, num_classes=3) + output_image = one_hot_feature(input_image) + expected_output = np.array([ [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0] - ]) - self.assertTrue(np.array_equal(output_image, expected_output)) + ], dtype=np.float32) + + self.assertEqual(output_image.shape, (3, 3)) + np.testing.assert_array_equal(output_image, expected_output) + + ### Test with singleton last dimension + input_image = np.array([[0], [1], [2]]) # shape (3, 1) + output_image = one_hot_feature(input_image) + self.assertEqual(output_image.shape, (3, 3)) + np.testing.assert_array_equal(output_image, expected_output) + + ### Test with Image + input_data = np.array([0, 1, 2]) + input_image = features.Image(input_data) + output_image = one_hot_feature(input_image) + self.assertEqual(output_image.shape, (3, 3)) + np.testing.assert_array_equal(output_image, expected_output) + + ### Test with PyTorch tensor (if available) + if TORCH_AVAILABLE: + input_tensor = torch.tensor([0, 1, 2]) + output_tensor = one_hot_feature(input_tensor) + + expected_tensor = torch.tensor([ + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0] + ], dtype=torch.float32) + + self.assertEqual(output_tensor.shape, (3, 3)) + torch.testing.assert_close(output_tensor, expected_tensor) + + # Test with singleton dimension + input_tensor = torch.tensor([[0], [1], [2]]) + output_tensor = one_hot_feature(input_tensor) + self.assertEqual(output_tensor.shape, (3, 3)) + torch.testing.assert_close(output_tensor, expected_tensor) def test_TakeProperties(self): From a23fa7cdbd888352d2f99bae08fa536b08e38378 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 09:08:40 +0200 Subject: [PATCH 071/223] Update features.py --- deeptrack/features.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 3c53232c0..ec60f6bd8 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3706,33 +3706,35 @@ class Bind(StructuralFeature): Methods ------- - `get(image: Any, **kwargs: dict[str, Any]) -> Any` - Resolves the child feature with the provided arguments. + `get(image: Any, **kwargs: Any) -> Any` + It resolves the child feature with the provided arguments. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Start by creating a `Gaussian` feature: >>> gaussian_noise = dt.Gaussian() - Dynamically modify the behavior of the feature using `Bind`: - >>> bound_feature = dt.Bind(gaussian_noise, mu = -5, sigma=2) - + Create a test image: + >>> import numpy as np + >>> >>> input_image = np.zeros((512, 512)) + + Bind fixed values to the parameters: + >>> bound_feature = dt.Bind(gaussian_noise, mu=-5, sigma=2) + + Resolve the bound feature: >>> output_image = bound_feature.resolve(input_image) - >>> print(np.mean(output_image), np.std(output_image)) - -4.9954959040123152 1.9975296489398942 + >>> round(np.mean(output_image), 1), round(np.std(output_image), 1) + (-5.0, 2.0) """ - __distributed__: bool = False - def __init__( - self: Feature, - feature: Feature, - **kwargs: dict[str, Any] + self: Bind, + feature: Feature, + **kwargs: Any, ): """Initialize the Bind feature. @@ -3746,12 +3748,13 @@ def __init__( """ super().__init__(**kwargs) + self.feature = self.add_feature(feature) def get( - self: Feature, - image: Any, - **kwargs: dict[str, Any] + self: Bind, + image: Any, + **kwargs: Any, ) -> Any: """Resolve the child feature with the dynamically provided arguments. From 5c67108c92f2168e723dbefe3d7a078f062210d9 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 09:08:43 +0200 Subject: [PATCH 072/223] Update test_features.py --- deeptrack/tests/test_features.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 760be1b28..c6482f6f6 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1315,21 +1315,15 @@ def test_Slice_static_dynamic(self): def test_Bind(self): - value = features.Value( - value=lambda input_value: input_value, - input_value=10, - ) value = features.Value( value=lambda input_value: input_value, input_value=10, ) pipeline = (value + 10) / value - - pipeline_with_small_input = features.Bind(pipeline, input_value=1) - res = pipeline.update().resolve() self.assertEqual(res, 2) + pipeline_with_small_input = features.Bind(pipeline, input_value=1) res = pipeline_with_small_input.update().resolve() self.assertEqual(res, 11) @@ -1342,7 +1336,7 @@ def test_Bind_gaussian_noise(self): bound_feature = features.Bind(gaussian_noise, mu=-5, sigma=2) # Create the input image - input_image = np.zeros((512, 512)) + input_image = np.zeros((128, 128)) # Resolve the feature to get the output image output_image = bound_feature.resolve(input_image) @@ -1352,10 +1346,8 @@ def test_Bind_gaussian_noise(self): output_std = np.std(output_image) # Assert that the mean and standard deviation are close to the bound values - self.assertAlmostEqual(output_mean, -5, delta=0.2, \ - msg="Mean is not within the expected range") - self.assertAlmostEqual(output_std, 2, delta=0.2, \ - msg="Standard deviation is not within the expected range") + self.assertAlmostEqual(output_mean, -5, delta=0.2) + self.assertAlmostEqual(output_std, 2, delta=0.2) def test_BindResolve(self): From fd5fc9fbebc280ab88a46f1d62782c8ca76f58f6 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 11:39:54 +0200 Subject: [PATCH 073/223] Update test_features.py --- deeptrack/tests/test_features.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index c6482f6f6..7265790af 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1426,11 +1426,9 @@ def test_BindUpdate_gaussian_noise(self): output_mean = np.mean(output_image) output_std = np.std(output_image) - # Assert that the mean and standard deviation are close to the bound values - self.assertAlmostEqual(output_mean, 5, \ - delta=0.2, msg="Mean is not within the expected range") - self.assertAlmostEqual(output_std, 3, \ - delta=0.2, msg="Standard deviation is not within the expected range") + # Assert mean and standard deviation close to the bound values + self.assertAlmostEqual(output_mean, 5) + self.assertAlmostEqual(output_std, 3) def test_ConditionalSetProperty(self): From 1714d5cec3d13b565edfa32aa0dc2656b3df0fe1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 11:39:56 +0200 Subject: [PATCH 074/223] Update features.py --- deeptrack/features.py | 120 ++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 63 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index ec60f6bd8..9be3601d7 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -176,11 +176,11 @@ def merge_features( "Repeat", "Combine", "Slice", - "Bind", # TODO - "BindResolve", # TODO - "BindUpdate", # TODO - "ConditionalSetProperty", # TODO - "ConditionalSetFeature", # TODO + "Bind", + "BindResolve", + "BindUpdate", + "ConditionalSetProperty", + "ConditionalSetFeature", "Lambda", # TODO "Merge", # TODO "OneOf", # TODO @@ -2048,7 +2048,8 @@ def __init__( "Passing an Image object as the value to dt.Value may lead to " "performance deterioration. Consider converting the Image to " "a NumPy array with np.array(image), or to a PyTorch tensor " - "with torch.tensor(np.array(image))." + "with torch.tensor(np.array(image)).", + DeprecationWarning, ) super().__init__(value=value, **kwargs) @@ -3796,23 +3797,19 @@ class BindUpdate(StructuralFeature): Methods ------- - `get(image: Any, **kwargs: dict[str, Any]) -> Any` - Resolves the child feature with the provided arguments. + `get(image: Any, **kwargs: Any) -> Any` + It resolves the child feature with the provided arguments. Warnings -------- - This feature is deprecated and may be removed in a future release. - It is recommended to use `Bind` instead for equivalent functionality. - - Notes - ----- - The current implementation is not guaranteed to be exactly equivalent to - prior implementations. + Deprecation: This feature is deprecated and may be removed in a future + release. It is recommended to use `Bind` instead for equivalent + functionality. Further, the current implementation is not guaranteed to be + exactly equivalent to prior implementations. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Start by creating a `Gaussian` feature: >>> gaussian_noise = dt.Gaussian() @@ -3820,19 +3817,19 @@ class BindUpdate(StructuralFeature): Dynamically modify the behavior of the feature using `BindUpdate`: >>> bound_feature = dt.BindUpdate(gaussian_noise, mu = 5, sigma=3) + >>> import numpy as np + >>> >>> input_image = np.zeros((512, 512)) >>> output_image = bound_feature.resolve(input_image) - >>> print(np.mean(output_image), np.std(output_image)) - 4.998501486851294 3.0020269383538176 + >>> round(np.mean(output_image), 1), round(np.std(output_image), 1) + (5.0, 3.0) """ - __distributed__: bool = False - def __init__( self: Feature, feature: Feature, - **kwargs: dict[str, Any] + **kwargs: Any, ): """Initialize the BindUpdate feature. @@ -3845,7 +3842,7 @@ def __init__( Warnings -------- - Emits a deprecation warning, encouraging the use of `Bind` instead. + It emits a deprecation warning, encouraging the use of `Bind` instead. """ @@ -3860,12 +3857,13 @@ def __init__( ) super().__init__(**kwargs) + self.feature = self.add_feature(feature) def get( - self: Feature, - image: Any, - **kwargs: dict[str, Any] + self: Feature, + image: Any, + **kwargs: Any, ) -> Any: """Resolve the child feature with the provided arguments. @@ -3896,64 +3894,60 @@ class ConditionalSetProperty(StructuralFeature): the given properties are applied; otherwise, the child feature remains unchanged. - **Note**: It is advisable to use `dt.Arguments` instead when possible, - since this feature **overwrites** properties, which may affect future - calls to the feature. + It is advisable to use `dt.Arguments` instead when possible, since this + feature overwrites properties, which may affect future calls to the + feature. + + If `condition` is a string, the condition must be explicitly passed when + resolving. + + The properties applied do not persist unless explicitly stored. Parameters ---------- feature: Feature The child feature whose properties will be modified conditionally. - condition: PropertyLike[str] or PropertyLike[bool] - Either a boolean value (`True`/`False`) or the name of a boolean + condition: PropertyLike[str or bool] or None + Either a boolean value (`True`, `False`) or the name of a boolean property in the feature’s property dictionary. If the condition evaluates to `True`, the specified properties are applied. - **kwargs: dict[str, Any] + **kwargs: Any The properties to be applied to the child feature if `condition` is `True`. - Attributes - ---------- - __distributed__: bool - Indicates whether this feature distributes computation across inputs. - Methods ------- - `get(image: Any, condition: str | bool, **kwargs: dict[str, Any]) -> Any` + `get(image: Any, condition: str or bool, **kwargs: Any) -> Any` Resolves the child feature, conditionally applying the specified properties. - Notes - ----- - - If `condition` is a string, the condition must be explicitly passed when - resolving. - - The properties applied **do not persist** unless explicitly stored. - Examples -------- >>> import deeptrack as dt + + Define an image: >>> import numpy as np + >>> + >>> image = np.ones((512, 512)) Define a `Gaussian` noise feature: >>> gaussian_noise = dt.Gaussian(sigma=0) --- Using a boolean condition --- - Apply `sigma=5` **only if** `condition=True`: + Apply `sigma=5` only if `condition=True`: >>> conditional_feature = dt.ConditionalSetProperty( - ... gaussian_noise, sigma=5 + ... gaussian_noise, sigma=5, ... ) - Define an image: - >>> image = np.ones((512, 512)) - Resolve with condition met: - >>> noisy_image = conditional_feature.update(image, condition=True) - >>> print(noisy_image.std()) # Should be ~5 - 4.987707046984823 + >>> noisy_image = conditional_feature(image, condition=True) + >>> round(noisy_image.std(), 1) + 5.0 Resolve without condition: - >>> clean_image = conditional_feature.update(image, condition=False) - >>> print(clean_image.std()) # Should be 0 + >>> conditional_feature.update() # Essential to reset the property + >>> clean_image = conditional_feature(image, condition=False) + >>> round(clean_image.std(), 1) 0.0 --- Using a string-based condition --- @@ -3963,18 +3957,17 @@ class ConditionalSetProperty(StructuralFeature): ... ) Resolve with condition met: - >>> noisy_image = conditional_feature.update(image, is_noisy=True) - >>> print(noisy_image.std()) # Should be ~5 - 5.006310381139811 + >>> noisy_image = conditional_feature(image, is_noisy=True) + >>> round(noisy_image.std(), 1) + 5.0 Resolve without condition: - >>> clean_image = conditional_feature.update(image, is_noisy=False) - >>> print(clean_image.std()) # Should be 0 + >>> conditional_feature.update() + >>> clean_image = conditional_feature(image, is_noisy=False) + >>> round(clean_image.std(), 1) 0.0 - - """ - __distributed__: bool = False + """ def __init__( self: Feature, @@ -3988,7 +3981,7 @@ def __init__( ---------- feature: Feature The child feature to conditionally modify. - condition: PropertyLike[str or bool] + condition: PropertyLike[str or bool] or None A boolean value or the name of a boolean property in the feature's property dictionary. If the condition evaluates to `True`, the specified properties are applied. @@ -4002,6 +3995,7 @@ def __init__( kwargs.setdefault(condition, True) super().__init__(condition=condition, **kwargs) + self.feature = self.add_feature(feature) def get( @@ -4020,7 +4014,7 @@ def get( A boolean value or the name of a boolean property in the feature's property dictionary. If the condition evaluates to `True`, the specified properties are applied. - **kwargs:: dict of str to Any + **kwargs:: Any Additional properties to apply to the child feature if the condition is `True`. From 3ef47fde874c0f726f1923707584651028872272 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 11:45:15 +0200 Subject: [PATCH 075/223] Update test_features.py --- deeptrack/tests/test_features.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 7265790af..af1c71679 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1417,7 +1417,7 @@ def test_BindUpdate_gaussian_noise(self): bound_feature = features.BindUpdate(gaussian_noise, mu=5, sigma=3) # Create the input image - input_image = np.zeros((512, 512)) + input_image = np.zeros((128, 128)) # Resolve the feature to get the output image output_image = bound_feature.resolve(input_image) @@ -1432,13 +1432,12 @@ def test_BindUpdate_gaussian_noise(self): def test_ConditionalSetProperty(self): - """Test that ConditionalSetProperty correctly modifies properties based on condition.""" - """Set up a Gaussian feature and a test image before each test.""" + # Set up a Gaussian feature and a test image before each test. gaussian_noise = Gaussian(sigma=0) image = np.ones((128, 128)) - """Test that sigma is correctly applied when condition is a boolean.""" + # Test that sigma is correctly applied when condition is a boolean. conditional_feature = features.ConditionalSetProperty( gaussian_noise, sigma=5, ) @@ -1451,9 +1450,9 @@ def test_ConditionalSetProperty(self): clean_image = conditional_feature.update()(image, condition=False) self.assertEqual(clean_image.std(), 0) - """Test that sigma is correctly applied when condition is a string property.""" + # Test sigma is correctly applied when condition is string property. conditional_feature = features.ConditionalSetProperty( - gaussian_noise, sigma=5, condition="is_noisy" + gaussian_noise, sigma=5, condition="is_noisy", ) # Test with condition met (should apply sigma=5) @@ -1464,17 +1463,17 @@ def test_ConditionalSetProperty(self): clean_image = conditional_feature.update()(image, is_noisy=False) self.assertEqual(clean_image.std(), 0) - def test_ConditionalSetFeature(self): - """Set up Gaussian noise features and test image before each test.""" + def test_ConditionalSetFeature(self): + # Set up Gaussian noise features and test image before each test. true_feature = Gaussian(sigma=0) # Clean image (no noise) false_feature = Gaussian(sigma=5) # Noisy image (sigma=5) image = np.ones((512, 512)) - """Test using a direct boolean condition.""" + # Test using a direct boolean condition. conditional_feature = features.ConditionalSetFeature( on_true=true_feature, - on_false=false_feature + on_false=false_feature, ) # Default condition is True (no noise) @@ -1489,11 +1488,11 @@ def test_ConditionalSetFeature(self): clean_image = conditional_feature(image, condition=True) self.assertEqual(clean_image.std(), 0) - """Test using a string-based condition.""" + # Test using a string-based condition. conditional_feature = features.ConditionalSetFeature( on_true=true_feature, on_false=false_feature, - condition="is_noisy" + condition="is_noisy", ) # Condition is False (sigma=5) From 9f1f019c4c10bd54d43e8f44c7e4751a8bbe4a21 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 12:47:26 +0200 Subject: [PATCH 076/223] Update features.py --- deeptrack/features.py | 65 ++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 9be3601d7..c3b7b604a 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -4046,7 +4046,7 @@ class ConditionalSetFeature(StructuralFeature): The `condition` parameter specifies either: - A boolean value (default is `True`). - - The name of a property to listen to. For example, if + - The name of a property to listen to. For example, if `condition="is_label"`, the selected feature can be toggled as follows: >>> feature.resolve(is_label=True) # Resolves `on_true` @@ -4071,20 +4071,19 @@ class ConditionalSetFeature(StructuralFeature): **kwargs: Any Additional keyword arguments passed to the parent `StructuralFeature`. - Attributes - ---------- - __distributed__: bool - Indicates whether this feature distributes computation across inputs. - Methods ------- - `get(image: Any, condition: str | bool, **kwargs: dict[str, Any]) -> Any` + `get(image: Any, condition: str or bool, **kwargs: Any) -> Any` Resolves the appropriate feature based on the condition. Examples -------- >>> import deeptrack as dt + + Define an image: >>> import numpy as np + >>> + >>> image = np.ones((512, 512)) Define two `Gaussian` noise features: >>> true_feature = dt.Gaussian(sigma=0) @@ -4092,26 +4091,23 @@ class ConditionalSetFeature(StructuralFeature): --- Using a boolean condition --- Combine the features into a conditional set feature. - If not provided explicitely, condition is assumed to be True: + If not provided explicitely, the condition is assumed to be True: >>> conditional_feature = dt.ConditionalSetFeature( - ... on_true=true_feature, - ... on_false=false_feature, + ... on_true=true_feature, + ... on_false=false_feature, ... ) - Define an image: - >>> image = np.ones((512, 512)) - - Resolve based on the condition: - >>> clean_image = conditional_feature(image) # If not specified, default is True - >>> print(clean_image.std()) # Should be 0 + Resolve based on the condition. If not specified, default is True: + >>> clean_image = conditional_feature(image) + >>> round(clean_image.std(), 1) 0.0 >>> noisy_image = conditional_feature(image, condition=False) - >>> print(noisy_image.std()) # Should be ~5 - 4.987707046984823 + >>> round(noisy_image.std(), 1) + 5.0 >>> clean_image = conditional_feature(image, condition=True) - >>> print(clean_image.std()) # Should be 0 + >>> round(clean_image.std(), 1) 0.0 --- Using a string-based condition --- @@ -4124,17 +4120,15 @@ class ConditionalSetFeature(StructuralFeature): Resolve based on the conditions: >>> noisy_image = conditional_feature(image, is_noisy=False) - >>> print(noisy_image.std()) # Should be ~5 - 5.006310381139811 + >>> round(noisy_image.std(), 1) + 5.0 >>> clean_image = conditional_feature(image, is_noisy=True) - >>> print(clean_image.std()) # Should be 0 + >>> round(clean_image.std(), 1) 0.0 """ - __distributed__: bool = False - def __init__( self: Feature, on_false: Feature | None = None, @@ -4152,8 +4146,8 @@ def __init__( The feature to resolve if the condition evaluates to `True`. condition: str or bool, optional The name of the property to listen to, or a boolean value. It - defaults to `"is_label"`. - **kwargs:: dict of str to Any + defaults to `True`. + **kwargs:: Any Additional keyword arguments for the parent `StructuralFeature`. """ @@ -4162,7 +4156,7 @@ def __init__( kwargs.setdefault(condition, True) super().__init__(condition=condition, **kwargs) - + # Add the child features to the dependency graph if provided. if on_true: self.add_feature(on_true) @@ -4189,7 +4183,7 @@ def get( The name of the conditional property or a boolean value. If a string is provided, it is looked up in `kwargs` to get the actual boolean value. - **kwargs:: dict of str to Any + **kwargs:: Any Additional keyword arguments to pass to the resolved feature. Returns @@ -4207,16 +4201,11 @@ def get( _condition = kwargs.get(condition, False) # Resolve the appropriate feature. - if _condition: - if self.on_true: - return self.on_true(image) - else: - return image - else: - if self.on_false: - return self.on_false(image) - else: - return image + if _condition and self.on_true: + return self.on_true(image) + if not _condition and self.on_false: + return self.on_false(image) + return image class Lambda(Feature): From ca3e44388f8dbb3c1cd7bdcf6a1ea42aa03af9d1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 12:47:28 +0200 Subject: [PATCH 077/223] Update test_features.py --- deeptrack/tests/test_features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index af1c71679..12c9d76de 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1427,8 +1427,8 @@ def test_BindUpdate_gaussian_noise(self): output_std = np.std(output_image) # Assert mean and standard deviation close to the bound values - self.assertAlmostEqual(output_mean, 5) - self.assertAlmostEqual(output_std, 3) + self.assertAlmostEqual(output_mean, 5, delta=0.5) + self.assertAlmostEqual(output_std, 3, delta=0.5) def test_ConditionalSetProperty(self): From b5ea8732a9aef440c6511bd9dc910882826a6068 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 18:20:51 +0200 Subject: [PATCH 078/223] Update features.py --- deeptrack/features.py | 76 +++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index c3b7b604a..40d8d12c8 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -181,7 +181,7 @@ def merge_features( "BindUpdate", "ConditionalSetProperty", "ConditionalSetFeature", - "Lambda", # TODO + "Lambda", "Merge", # TODO "OneOf", # TODO "OneOfDict", # TODO @@ -4209,12 +4209,12 @@ def get( class Lambda(Feature): - """Apply a user-defined function to each image in the input. + """Apply a user-defined function to the input. - This feature allows applying a custom function to individual images in the - input pipeline. The `function` parameter must be wrapped in an - **outer function** that can depend on other properties of the pipeline. - The **inner function** processes a single image. + This feature allows applying a custom function to individual inputs in the + input pipeline. The `function` parameter must be wrapped in an **outer + function** that can depend on other properties of the pipeline. + The **inner function** processes a single input. Parameters ---------- @@ -4227,7 +4227,7 @@ class Lambda(Feature): Methods ------- - `get(image: np.ndarray | Image, function: Callable[[Image], Image], **kwargs: dict[str, Any]) -> Image` + `get(image: Any, function: Callable[[Any], Any], **kwargs: Any) -> Any` Applies the custom function to the input image. Examples @@ -4244,36 +4244,40 @@ class Lambda(Feature): Create a `Lambda` feature that scales images by a factor of 5: >>> lambda_feature = dt.Lambda(function=scale_function_factory, scale=5) - Apply the feature to an image: - >>> input_image = np.ones((5, 5)) + Create an image: + >>> import numpy as np + >>> + >>> input_image = np.ones((2, 3)) + >>> input_image + array([[1., 1., 1.], + [1., 1., 1.]]) + + Apply the feature to the image: >>> output_image = lambda_feature(input_image) - >>> print(output_image) - [[5. 5. 5. 5. 5.] - [5. 5. 5. 5. 5.] - [5. 5. 5. 5. 5.] - [5. 5. 5. 5. 5.] - [5. 5. 5. 5. 5.]] - + >>> output_image + array([[5., 5., 5.], + [5., 5., 5.]]) + """ def __init__( self: Feature, - function: Callable[..., Callable[[Image], Image]], + function: Callable[..., Callable[[Any], Any]], **kwargs: Any, ): """Initialize the Lambda feature. - This feature applies a user-defined function to process an image. The + This feature applies a user-defined function to process an input. The `function` parameter must be a callable that returns another function, - where the inner function operates on the image. + where the inner function operates on the input. Parameters ---------- - function: Callable[..., Callable[[Image], Image]] + function: Callable[..., Callable[[Any], Any]] A callable that produces a function. The outer function can accept additional arguments from the pipeline, while the inner function - processes a single image. - **kwargs: dict[str, Any] + processes a single input. + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -4282,30 +4286,30 @@ def __init__( def get( self: Feature, - image: np.ndarray | Image, - function: Callable[[Image], Image], + image: Any, + function: Callable[[Any], Any], **kwargs: Any, - ) -> Image: - """Apply the custom function to the input image. + ) -> Any: + """Apply the custom function to the input. - This method applies a user-defined function to transform the input - image. The function should be a callable that takes an image as input - and returns a modified version of it. + This method applies a user-defined function to transform the input. The + function should be a callable that takes an input and returns a + modified version of it. Parameters ---------- - image: np.ndarray or Image - The input image to be processed. - function: Callable[[Image], Image] - A callable function that takes an image and returns a transformed - image. + image: Any + The input to be processed. + function: Callable[[Any], Any] + A callable function that takes an input and returns a transformed + output. **kwargs: Any Additional keyword arguments (unused in this implementation). Returns ------- - Image - The transformed image after applying the function. + Any + The transformed output after applying the function. """ From 9c7bb2939d4caec1171f86e911dd2b3683e1bbf2 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 18:20:53 +0200 Subject: [PATCH 079/223] Update test_features.py --- deeptrack/tests/test_features.py | 57 ++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 12c9d76de..887a2675c 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1509,80 +1509,93 @@ def test_Lambda_dependence(self): B = features.DummyFeature( key="a", - prop=lambda key: A.a() if key == "a" - else (A.b() if key == "b" else A.c()), + prop=lambda key: A.a() if key == "a" + else (A.b() if key == "b" + else A.c()), ) B.update() self.assertEqual(B.prop(), 1) - B.key.set_value("a") - self.assertEqual(B.prop(), 1) + B.key.set_value("b") self.assertEqual(B.prop(), 2) + B.key.set_value("c") self.assertEqual(B.prop(), 3) + B.key.set_value("a") + self.assertEqual(B.prop(), 1) + def test_Lambda_dependence_twice(self): A = features.DummyFeature(a=1, b=2, c=3) B = features.DummyFeature( key="a", - prop=lambda key: A.a() if key == "a" - else (A.b() if key == "b" else A.c()), + prop=lambda key: A.a() if key == "a" + else (A.b() if key == "b" + else A.c()), prop2=lambda prop: prop * 2, ) B.update() self.assertEqual(B.prop2(), 2) - B.key.set_value("a") - self.assertEqual(B.prop2(), 2) + B.key.set_value("b") self.assertEqual(B.prop2(), 4) + B.key.set_value("c") self.assertEqual(B.prop2(), 6) + B.key.set_value("a") + self.assertEqual(B.prop2(), 2) + def test_Lambda_dependence_other_feature(self): A = features.DummyFeature(a=1, b=2, c=3) B = features.DummyFeature( key="a", - prop=lambda key: A.a() if key == "a" - else (A.b() if key == "b" else A.c()), + prop=lambda key: A.a() if key == "a" + else (A.b() if key == "b" + else A.c()), prop2=lambda prop: prop * 2, ) - C = features.DummyFeature(B_prop=B.prop2, + C = features.DummyFeature(B_prop=B.prop2, prop=lambda B_prop: B_prop * 2) C.update() self.assertEqual(C.prop(), 4) - B.key.set_value("a") - self.assertEqual(C.prop(), 4) + B.key.set_value("b") self.assertEqual(C.prop(), 8) + B.key.set_value("c") self.assertEqual(C.prop(), 12) + B.key.set_value("a") + self.assertEqual(C.prop(), 4) + def test_Lambda_scaling(self): def scale_function_factory(scale=2): def scale_function(image): return image * scale return scale_function - lambda_feature = features.Lambda(function=scale_function_factory, scale=5) + lambda_feature = features.Lambda( + function=scale_function_factory, + scale=5, + ) input_image = np.ones((5, 5)) - output_image = lambda_feature.resolve(input_image) + self.assertTrue(np.array_equal(output_image, np.ones((5, 5)) * 5)) - expected_output = np.ones((5, 5)) * 5 - self.assertTrue(np.array_equal(output_image, expected_output), "Arrays are not equal") - - lambda_feature = features.Lambda(function=scale_function_factory, scale=3) + lambda_feature = features.Lambda( + function=scale_function_factory, + scale=3, + ) output_image = lambda_feature.resolve(input_image) - - expected_output = np.ones((5, 5)) * 3 - self.assertTrue(np.array_equal(output_image, expected_output), "Arrays are not equal") + self.assertTrue(np.array_equal(output_image, np.ones((5, 5)) * 3)) def test_Merge(self): From ee903ab1807169e99d4ce76e8ab270b69f804e69 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 21:27:39 +0200 Subject: [PATCH 080/223] Update features.py --- deeptrack/features.py | 121 +++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 55 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 40d8d12c8..58c866dc7 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -182,8 +182,8 @@ def merge_features( "ConditionalSetProperty", "ConditionalSetFeature", "Lambda", - "Merge", # TODO - "OneOf", # TODO + "Merge", + "OneOf", "OneOfDict", # TODO "LoadImage", # TODO "SampleToMasks", # TODO @@ -1734,7 +1734,7 @@ class StructuralFeature(Feature): property list. A value of `2` hides them from output. __distributed__ : bool If `True`, applies `get` to each element in a list individually. - If `False`, processes the entire list as a single unit. Defaults to + If `False`, processes the entire list as a single unit. It defaults to `False`. """ @@ -4317,42 +4317,41 @@ def get( class Merge(Feature): - """Apply a custom function to a list of images. + """Apply a custom function to a list of inputs. - This feature allows applying a user-defined function to a list of images. + This feature allows applying a user-defined function to a list of inputs. The `function` parameter must be a callable that returns another function, where: - The **outer function** can depend on other properties in the pipeline. - - The **inner function** takes a list of images and returns a single - image or a list of images. + - The **inner function** takes a list of inputs and returns a single + outputs or a list of outputs. - **Note:** The function must be wrapped in an **outer layer** to enable - dependencies on other properties while ensuring correct execution. + The function must be wrapped in an outer layer to enable dependencies on + other properties while ensuring correct execution. Parameters ---------- - function: Callable[..., Callable[[list[np.ndarray] | list[Image]], np.ndarray | list[np.ndarray] | Image | list[Image]]] - A callable that produces a function. The **outer function** can depend - on other properties of the pipeline, while the **inner function** - processes a list of images and returns either a single image or a list - of images. - **kwargs: dict[str, Any] + function: Callable[..., Callable[[list[Any]], Any or list[Any]] + A callable that produces a function. The outer function can depend on + other properties of the pipeline, while the inner function processes a + list of inputs and returns either a single output or a list of outputs. + **kwargs: Any Additional parameters passed to the parent `Feature` class. Attributes ---------- __distributed__: bool Indicates whether this feature distributes computation across inputs. + It defaults to `False`. Methods ------- - `get(list_of_images: list[np.ndarray] | list[Image], function: Callable[[list[np.ndarray] | list[Image]], np.ndarray | list[np.ndarray] | Image | list[Image]], **kwargs: dict[str, Any]) -> Image | list[Image]` - Applies the custom function to the list of images. + `get(list_of_images: list[Any], function: Callable[[list[Any]], Any or list[Any]], **kwargs: Any) -> Any or list[Any]` + Applies the custom function to the list of inputs. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Define a merge function that averages multiple images: >>> def merge_function_factory(): @@ -4363,16 +4362,17 @@ class Merge(Feature): Create a Merge feature: >>> merge_feature = dt.Merge(function=merge_function_factory) + Create some images: + >>> import numpy as np + >>> + >>> image_1 = np.ones((2, 3)) * 2 + >>> image_2 = np.ones((2, 3)) * 4 + Apply the feature to a list of images: - >>> image_1 = np.ones((5, 5)) * 2 - >>> image_2 = np.ones((5, 5)) * 4 >>> output_image = merge_feature([image_1, image_2]) - >>> print(output_image) - [[3. 3. 3. 3. 3.] - [3. 3. 3. 3. 3.] - [3. 3. 3. 3. 3.] - [3. 3. 3. 3. 3.] - [3. 3. 3. 3. 3.]] + >>> output_image + array([[3., 3., 3.], + [3., 3., 3.]]) """ @@ -4388,12 +4388,12 @@ def __init__( Parameters ---------- - function: Callable[..., Callable[list[np.ndarray] | [list[Image]], np.ndarray | list[np.ndarray] | Image | list[Image]]] + function: Callable[..., Callable[list[Any]], Any or list[Any]] A callable that returns a function for processing a list of images. - - The **outer function** can depend on other properties in the pipeline. - - The **inner function** takes a list of images as input and - returns either a single image or a list of images. - **kwargs: dict[str, Any] + The outer function can depend on other properties in the pipeline. + The inner function takes a list of inputs and returns either a + single output or a list of outputs. + **kwargs: Any Additional parameters passed to the parent `Feature` class. """ @@ -4406,17 +4406,16 @@ def get( function: Callable[[list[np.ndarray] | list[Image]], np.ndarray | list[np.ndarray] | Image | list[Image]], **kwargs: Any, ) -> Image | list[Image]: - """Apply the custom function to a list of images. + """Apply the custom function to a list of inputs. Parameters ---------- - list_of_images: list[np.ndarray or Image] - A list of images to be processed by the function. - function: Callable[[list[np.ndarray] | list[Image]], np.ndarray | list[np.ndarray] | Image | list[Image]] - The function that processes the list of images and returns either: - - A single transformed image (`Image`) - - A list of transformed images (`list[Image]`) - **kwargs: dict[str, Any] + list_of_images: list[Any] + A list of inputs to be processed by the function. + function: Callable[[list[Any]], Any | list[Any]] + The function that processes the list of images and returns either a + single transformed input or a list of transformed inputs. + **kwargs: Any Additional arguments (unused in this implementation). Returns @@ -4430,7 +4429,7 @@ def get( class OneOf(Feature): - """Resolves one feature from a given collection. + """Resolve one feature from a given collection. This feature selects and applies one of multiple features from a given collection. The default behavior selects a feature randomly, but this @@ -4454,18 +4453,18 @@ class OneOf(Feature): ---------- __distributed__: bool Indicates whether this feature distributes computation across inputs. + It defaults to `False`. Methods ------- - `_process_properties(propertydict: dict) -> dict` + `_process_properties(propertydict: dict[Property]) -> dict[Property]` Processes the properties to determine the selected feature index. - `get(image: Any, key: int, _ID: tuple[int, ...], **kwargs: dict[str, Any]) -> Any` - Applies the selected feature to the input image. + `get(image: Any, key: int, _ID: tuple[int, ...], **kwargs: Any) -> Any` + Applies the selected feature to the input. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Define multiple features: >>> feature_1 = dt.Add(value=10) @@ -4474,15 +4473,25 @@ class OneOf(Feature): Create a `OneOf` feature that randomly selects a transformation: >>> one_of_feature = dt.OneOf([feature_1, feature_2]) - Apply it to an input image: + Create an input image: + >>> import numpy as np + >>> >>> input_image = np.array([1, 2, 3]) + + Apply the `OneOf` feature to the input image: >>> output_image = one_of_feature(input_image) - >>> print(output_image) # The output depends on the randomly selected feature. + >>> output_image # The output depends on the randomly selected feature. - Use a `key` to apply a specific feature: + Use `key` to apply a specific feature: >>> controlled_feature = dt.OneOf([feature_1, feature_2], key=0) >>> output_image = controlled_feature(input_image) - >>> print(output_image) # Adds 10 to each element. + >>> output_image + array([11, 12, 13]) + + >>> controlled_feature.key.set_value(1) + >>> output_image = controlled_feature(input_image) + >>> output_image + array([2, 4, 6]) """ @@ -4499,7 +4508,8 @@ def __init__( Parameters ---------- collection: Iterable[Feature] - A collection of features to choose from. It will be stored as a tuple. + A collection of features to choose from. It will be stored as a + tuple. key: int | None, optional The index of the feature to resolve from the collection. If not provided, a feature is selected randomly at execution. @@ -4509,28 +4519,29 @@ def __init__( """ super().__init__(key=key, **kwargs) + self.collection = tuple(collection) - + # Add all features in the collection as dependencies. for feature in self.collection: self.add_feature(feature) def _process_properties( - self: Feature, - propertydict: dict, - ) -> dict: + self: Feature, + propertydict: dict[Property], + ) -> dict[Property]: """Process the properties to determine the feature index. If `key` is not provided, a random feature index is assigned. Parameters ---------- - propertydict: dict + propertydict: dict[Property] The dictionary containing properties of the feature. Returns ------- - dict + dict[Property] The updated property dictionary with the `key` property set. """ From 4d94ed0b5a883dd892448b1cf32aec352770bb94 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 21:27:42 +0200 Subject: [PATCH 081/223] Update test_features.py --- deeptrack/tests/test_features.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 887a2675c..615923583 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1609,9 +1609,12 @@ def merge_function(images): image_1 = np.ones((5, 5)) * 2 image_2 = np.ones((5, 5)) * 4 - expected_output = np.ones((5, 5)) * 3 output_image = merge_feature.resolve([image_1, image_2]) - self.assertIsNone(np.testing.assert_array_almost_equal(output_image, expected_output)) + self.assertIsNone( + np.testing.assert_array_almost_equal( + output_image, np.ones((5, 5)) * 3, + ) + ) image_1 = np.ones((5, 5)) * 2 image_2 = np.ones((3, 3)) * 4 @@ -1620,16 +1623,20 @@ def merge_function(images): image_1 = np.ones((5, 5)) * 2 output_image = merge_feature.resolve([image_1]) - self.assertIsNone(np.testing.assert_array_almost_equal(output_image, image_1)) + self.assertIsNone( + np.testing.assert_array_almost_equal( + output_image, image_1, + ) + ) def test_OneOf(self): - """Set up the features and input image for testing.""" + # Set up the features and input image for testing. feature_1 = features.Add(value=10) feature_2 = features.Multiply(value=2) input_image = np.array([1, 2, 3]) - """Test that OneOf applies one of the features randomly.""" + # Test that OneOf applies one of the features randomly. one_of_feature = features.OneOf([feature_1, feature_2]) output_image = one_of_feature.resolve(input_image) @@ -1638,14 +1645,16 @@ def test_OneOf(self): # - self.input_image * 2 (if feature_2 is chosen) expected_outputs = [ input_image + 10, - input_image * 2 + input_image * 2, ] self.assertTrue( - any(np.array_equal(output_image, expected) for expected in expected_outputs), - f"Output {output_image} did not match any expected transformations." + any( + np.array_equal(output_image, expected) + for expected in expected_outputs + ) ) - """Test that OneOf applies the selected feature when `key` is provided.""" + # Test that OneOf applies the selected feature when `key` is provided. controlled_feature = features.OneOf([feature_1, feature_2], key=0) output_image = controlled_feature.resolve(input_image) expected_output = input_image + 10 From a58bdc8340995a8498149f931ccf5b23c49f638b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 21:58:00 +0200 Subject: [PATCH 082/223] Update features.py --- deeptrack/features.py | 54 ++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 58c866dc7..95535556e 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -184,7 +184,7 @@ def merge_features( "Lambda", "Merge", "OneOf", - "OneOfDict", # TODO + "OneOfDict", "LoadImage", # TODO "SampleToMasks", # TODO "AsType", # TODO @@ -4457,10 +4457,10 @@ class OneOf(Feature): Methods ------- - `_process_properties(propertydict: dict[Property]) -> dict[Property]` - Processes the properties to determine the selected feature index. + `_process_properties(propertydict: dict) -> dict` + It processes the properties to determine the selected feature index. `get(image: Any, key: int, _ID: tuple[int, ...], **kwargs: Any) -> Any` - Applies the selected feature to the input. + It applies the selected feature to the input. Examples -------- @@ -4480,7 +4480,7 @@ class OneOf(Feature): Apply the `OneOf` feature to the input image: >>> output_image = one_of_feature(input_image) - >>> output_image # The output depends on the randomly selected feature. + >>> output_image # The output depends on the randomly selected feature. Use `key` to apply a specific feature: >>> controlled_feature = dt.OneOf([feature_1, feature_2], key=0) @@ -4528,20 +4528,20 @@ def __init__( def _process_properties( self: Feature, - propertydict: dict[Property], - ) -> dict[Property]: + propertydict: dict, + ) -> dict: """Process the properties to determine the feature index. If `key` is not provided, a random feature index is assigned. Parameters ---------- - propertydict: dict[Property] + propertydict: dict The dictionary containing properties of the feature. Returns ------- - dict[Property] + dict The updated property dictionary with the `key` property set. """ @@ -4587,9 +4587,9 @@ def get( class OneOfDict(Feature): """Resolve one feature from a dictionary and apply it to an input. - This feature selects a feature from a dictionary and applies it to an input. - The selection is made randomly by default, but it can be controlled using - the `key` argument. + This feature selects a feature from a dictionary and applies it to an + input. The selection is made randomly by default, but it can be controlled + using the `key` argument. If `key` is not specified, a random key from the dictionary is selected, and the corresponding feature is applied. Otherwise, the feature mapped to @@ -4609,35 +4609,46 @@ class OneOfDict(Feature): ---------- __distributed__: bool Indicates whether this feature distributes computation across inputs. + It defaults to `False`. Methods ------- `_process_properties(propertydict: dict) -> dict` - Determines which feature to use based on `key`. - `get(image: Any, key: Any, _ID: tuple[int, ...], **kwargs: dict[str, Any]) -> Any` - Resolves the selected feature and applies it to the input image. + It determines which feature to use based on `key`. + `get(image: Any, key: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` + It resolves the selected feature and applies it to the input image. Examples -------- >>> import deeptrack as dt - >>> import numpy as np Define a dictionary of features: >>> features_dict = { ... "add": dt.Add(value=10), ... "multiply": dt.Multiply(value=2), ... } + + Create a `OneOfDict` feature that randomly selects a transformation: >>> one_of_dict_feature = dt.OneOfDict(features_dict) - Apply a randomly selected feature: + Creare an image: + >>> import numpy as np + >>> >>> input_image = np.array([1, 2, 3]) + + Apply a randomly selected feature to the image: >>> output_image = one_of_dict_feature(input_image) - >>> print(output_image) + >>> output_image # The output depends on the randomly selected feature. + + Potentially select a different feature: + >>> output_image = one_of_dict_feature.update()(input_image) + >>> output_image Use a specific key to apply a predefined feature: >>> controlled_feature = dt.OneOfDict(features_dict, key="add") >>> output_image = controlled_feature(input_image) - >>> print(output_image) # Adds 10 to each element. + >>> output_image + array([11, 12, 13]) """ @@ -4664,6 +4675,7 @@ def __init__( """ super().__init__(key=key, **kwargs) + self.collection = collection # Add all features in the dictionary as dependencies. @@ -4671,8 +4683,8 @@ def __init__( self.add_feature(feature) def _process_properties( - self: Feature, - propertydict: dict + self: Feature, + propertydict: dict, ) -> dict: """Determine which feature to apply based on the selected key. From 485fd1df8e598f431a674a1b326eec95d964cc5b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 21:58:02 +0200 Subject: [PATCH 083/223] Update test_features.py --- deeptrack/tests/test_features.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 615923583..0f631d999 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1727,7 +1727,6 @@ def test_OneOf_tuple(self): self.assertRaises(IndexError, lambda: values.update().resolve(key=3)) - def test_OneOf_set(self): values = features.OneOf( @@ -1795,26 +1794,24 @@ def test_OneOfDict(self): input_image = np.array([1, 2, 3]) - """Test that OneOfDict selects a feature randomly and applies it correctly.""" + # Test OneOfDict selects a feature randomly and applies it correctly. output_image = one_of_dict_feature.resolve(input_image) expected_outputs = [ input_image + 10, # "add" - input_image * 2, # "multiply" + input_image * 2, # "multiply" ] - self.assertTrue( - any(np.array_equal(output_image, expected) for expected in expected_outputs), - f"Output {output_image} did not match any expected transformations." - ) + self.assertTrue(any(np.array_equal(output_image, expected) + for expected in expected_outputs)) - """Test that OneOfDict selects the correct feature when a key is specified.""" + # Test OneOfDict selects the correct feature when a key is specified. controlled_feature = features.OneOfDict(features_dict, key="add") output_image = controlled_feature.resolve(input_image) - expected_output = input_image + 10 # The "add" feature should be applied + expected_output = input_image + 10 self.assertTrue(np.array_equal(output_image, expected_output)) controlled_feature = features.OneOfDict(features_dict, key="multiply") output_image = controlled_feature.resolve(input_image) - expected_output = input_image * 2 # The "multiply" feature should be applied + expected_output = input_image * 2 self.assertTrue(np.array_equal(output_image, expected_output)) From ad358a34f609da66668b84cb2c597b1159ea9b52 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 22:28:21 +0200 Subject: [PATCH 084/223] Update test_features.py --- deeptrack/tests/test_features.py | 36 +++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 0f631d999..ee44ccfcd 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1820,7 +1820,7 @@ def test_LoadImage(self): from PIL import Image as PIL_Image import os - """Create temporary image files in multiple formats for testing.""" + # Create temporary image files in multiple formats for testing. test_image_array = (np.random.rand(50, 50) * 255).astype(np.uint8) try: @@ -1833,39 +1833,45 @@ def test_LoadImage(self): # png_filename = temp_png.name with NamedTemporaryFile(suffix=".jpg", delete=False) as temp_jpg: - PIL_Image.fromarray(test_image_array).convert("RGB").save(temp_jpg.name) + PIL_Image.fromarray(test_image_array).convert("RGB") \ + .save(temp_jpg.name) # jpg_filename = temp_jpg.name - - """Test loading a .npy file.""" + # Test loading a .npy file. load_feature = features.LoadImage(path=temp_npy.name) loaded_image = load_feature.resolve() - self.assertEqual(loaded_image.shape[:2], test_image_array.shape[:2]) + self.assertEqual(loaded_image.shape[:2], + test_image_array.shape[:2]) - """Test loading a .png file.""" + # Test loading a .png file. load_feature = features.LoadImage(path=temp_png.name) loaded_image = load_feature.resolve() - self.assertEqual(loaded_image.shape[:2], test_image_array.shape[:2]) + self.assertEqual(loaded_image.shape[:2], + test_image_array.shape[:2]) - """Test loading a .jpg file.""" + # Test loading a .jpg file. load_feature = features.LoadImage(path=temp_jpg.name) loaded_image = load_feature.resolve() - self.assertEqual(loaded_image.shape[:2], test_image_array.shape[:2]) - - """Test loading an image and converting it to grayscale.""" - load_feature = features.LoadImage(path=temp_png.name, to_grayscale=True) + self.assertEqual(loaded_image.shape[:2], + test_image_array.shape[:2]) + + # Test loading an image and converting it to grayscale. + load_feature = features.LoadImage(path=temp_png.name, + to_grayscale=True) loaded_image = load_feature.resolve() - self.assertEqual(loaded_image.shape[-1], 1) + self.assertEqual(loaded_image.shape[-1], 1) - """Test ensuring a minimum number of dimensions.""" + # Test ensuring a minimum number of dimensions. load_feature = features.LoadImage(path=temp_png.name, ndim=4) loaded_image = load_feature.resolve() - self.assertGreaterEqual(len(loaded_image.shape), 4) + self.assertGreaterEqual(len(loaded_image.shape), 4) finally: for file in [temp_npy.name, temp_png.name, temp_jpg.name]: os.remove(file) + #TODO: Add a test for loading a list of images. + def test_SampleToMasks(self): # Parameters From 5610d400a4003c7925a0e677a20f5568233f5b21 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 22:28:23 +0200 Subject: [PATCH 085/223] Update features.py --- deeptrack/features.py | 79 +++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 95535556e..8ba399951 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3100,7 +3100,7 @@ class Arguments(Feature): -------- >>> import deeptrack as dt - Create a temporary image: + Create a temporary image file: >>> import numpy as np >>> import PIL, tempfile >>> @@ -4772,10 +4772,11 @@ class LoadImage(Feature): ---------- __distributed__: bool Indicates whether this feature distributes computation across inputs. + It defaults to `False`. Methods ------- - `get(image: Any, path: str | list[str], load_options: dict[str, Any] | None, ndim: int, to_grayscale: bool, as_list: bool, get_one_random: bool, **kwargs: dict[str, Any]) -> np.ndarray` + `get(image: Any, path: str or list[str], load_options: dict[str, Any] | None, ndim: int, to_grayscale: bool, as_list: bool, get_one_random: bool, **kwargs: Any) -> array` Load the image(s) from disk and process them. Raises @@ -4786,27 +4787,43 @@ class LoadImage(Feature): Examples -------- >>> import deeptrack as dt - >>> import numpy as np - >>> from tempfile import NamedTemporaryFile Create a temporary image file: - >>> temp_file = NamedTemporaryFile(suffix=".npy", delete=False) - >>> np.save(temp_file.name, np.random.rand(100, 100)) + >>> import numpy as np + >>> import os, tempfile + >>> + >>> temp_file = tempfile.NamedTemporaryFile(suffix=".npy", delete=False) + >>> np.save(temp_file.name, np.random.rand(100, 100, 3)) Load the image using `LoadImage`: - >>> load_image_feature = dt.LoadImage(path=temp_file.name, to_grayscale=True) + >>> load_image_feature = dt.LoadImage(path=temp_file.name) >>> loaded_image = load_image_feature.resolve() Print image shape: - >>> print(loaded_image.shape) + >>> loaded_image.shape + (100, 100, 3) - If `to_grayscale=True`, the image is converted to grayscale (single channel). - If `ndim=4`, additional dimensions are added if necessary. + If `to_grayscale=True`, the image is converted to single channel: + >>> load_image_feature = dt.LoadImage( + ... path=temp_file.name, + ... to_grayscale=True, + ... ) + >>> loaded_image = load_image_feature.resolve() + >>> loaded_image.shape + (100, 100, 1) + + If `ndim=4`, additional dimensions are added if necessary: + >>> load_image_feature = dt.LoadImage( + ... path=temp_file.name, + ... ndim=4, + ... ) + >>> loaded_image = load_image_feature.resolve() + >>> loaded_image.shape + (2, 2, 3, 1) Cleanup the temporary file: - >>> import os >>> os.remove(temp_file.name) - + """ __distributed__: bool = False @@ -4826,23 +4843,23 @@ def __init__( Parameters ---------- path: PropertyLike[str or list[str]] - The path(s) to the image(s) to load. Can be a single string or a list - of strings. + The path(s) to the image(s) to load. Can be a single string or a + list of strings. load_options: PropertyLike[dict[str, Any]], optional - Additional options passed to the file reader (e.g., `mode` for OpenCV, - `allow_pickle` for NumPy). It defaults to `None`. + Additional options passed to the file reader (e.g., `mode` for + OpenCV, `allow_pickle` for NumPy). It defaults to `None`. as_list: PropertyLike[bool], optional - If `True`, treats the first dimension of the image as a list of images. - It defaults to `False`. + If `True`, treats the first dimension of the image as a list of + images. It defaults to `False`. ndim: PropertyLike[int], optional - Ensures the image has at least this many dimensions. If the loaded image - has fewer dimensions, extra dimensions are added. It defaults to - `3`. + Ensures the image has at least this many dimensions. If the loaded + image has fewer dimensions, extra dimensions are added. It defaults + to `3`. to_grayscale: PropertyLike[bool], optional If `True`, converts the image to grayscale. It defaults to `False`. get_one_random: PropertyLike[bool], optional - If `True`, selects a single random image from a stack when `as_list=True`. - It defaults to `False`. + If `True`, selects a single random image from a stack when + `as_list=True`. It defaults to `False`. **kwargs: Any Additional keyword arguments passed to the parent `Feature` class, allowing further customization. @@ -4869,7 +4886,7 @@ def get( as_list: bool, get_one_random: bool, **kwargs: Any, - ) -> np.ndarray: + ) -> NDArray | torch.Tensor: """Load and process an image or a list of images from disk. This method attempts to load an image using multiple file readers @@ -4897,14 +4914,15 @@ def get( get_one_random: bool If `True`, selects a single random image from a multi-frame stack when `as_list=True`. It defaults to `False`. - **kwargs: dict[str, Any] + **kwargs: Any Additional keyword arguments. Returns ------- - np.ndarray + array The loaded and processed image(s). If `as_list=True`, returns a - list of images; otherwise, returns a single NumPy array. + list of images; otherwise, returns a single NumPy array or PyTorch + tensor. Raises ------ @@ -4932,7 +4950,7 @@ def get( try: import PIL.Image - image = [PIL.Image.open(file, **load_options) + image = [PIL.Image.open(file, **load_options) for file in path] except (IOError, ImportError): import cv2 @@ -4959,7 +4977,7 @@ def get( try: import skimage - skimage.color.rgb2gray(image) + image = skimage.color.rgb2gray(image) except ValueError: import warnings @@ -4969,6 +4987,9 @@ def get( while ndim and image.ndim < ndim: image = np.expand_dims(image, axis=-1) + # Convert to PyTorch tensor if needed. + #TODO + return image From eb17babe7ef09e3e66019e28b263123ea9c147f1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Mon, 30 Jun 2025 22:41:17 +0200 Subject: [PATCH 086/223] Update features.py --- deeptrack/features.py | 55 +++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 8ba399951..97f564048 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -5043,16 +5043,16 @@ class SampleToMasks(Feature): Examples ------- >>> import deeptrack as dt - >>> import matplotlib.pyplot as plt - >>> import numpy as np Define number of particles: >>> n_particles = 12 Define optics and particles: + >>> import numpy as np + >>> >>> optics = dt.Fluorescence(output_region=(0, 0, 64, 64)) >>> particle = dt.PointParticle( - >>> position=lambda: np.random.uniform(5, 55, size=2) + >>> position=lambda: np.random.uniform(5, 55, size=2), >>> ) >>> particles = particle ^ n_particles @@ -5061,7 +5061,7 @@ class SampleToMasks(Feature): >>> sim_mask_pip = particles >> dt.SampleToMasks( ... lambda: lambda particles: particles > 0, ... output_region=optics.output_region, - ... merge_method="or" + ... merge_method="or", ... ) >>> pipeline = sim_im_pip & sim_mask_pip >>> pipeline.store_properties() @@ -5073,12 +5073,14 @@ class SampleToMasks(Feature): >>> positions = np.array(image.get_property("position", get_one=False)) Visualize results: + >>> import matplotlib.pyplot as plt + >>> >>> plt.subplot(1, 2, 1) >>> plt.imshow(image, cmap="gray") >>> plt.title("Original Image") >>> plt.subplot(1, 2, 2) >>> plt.imshow(mask, cmap="gray") - >>> plt.scatter(positions[:,1], positions[:,0], c="r", marker="x", s = 10) + >>> plt.scatter(positions[:,1], positions[:,0], c="y", marker="x", s = 50) >>> plt.title("Mask") >>> plt.show() @@ -5300,39 +5302,40 @@ class AsType(Feature): Parameters ---------- - dtype: PropertyLike[Any], optional + dtype: PropertyLike[str], optional The desired data type for the image. It defaults to `"float64"`. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get(image: np.ndarray, dtype: str, **kwargs: dict[str, Any]) -> np.ndarray` + `get(image: array, dtype: str, **kwargs: Any) -> array` Convert the data type of the input image. Examples -------- - >>> import numpy as np - >>> from deeptrack.features import AsType + >>> import deeptrack as dt Create an input array: + >>> import numpy as np + >>> >>> input_image = np.array([1.5, 2.5, 3.5]) Apply an AsType feature to convert to `int32`: - >>> astype_feature = AsType(dtype="int32") + >>> astype_feature = dt.AsType(dtype="int32") >>> output_image = astype_feature.get(input_image, dtype="int32") - >>> print(output_image) - [1 2 3] + >>> output_image + array([1, 2, 3], dtype=int32) Verify the data type: - >>> print(output_image.dtype) - int32 + >>> output_image.dtype + dtype('int32') """ def __init__( self: Feature, - dtype: PropertyLike[Any] = "float64", + dtype: PropertyLike[str] = "float64", **kwargs: Any, ): """ @@ -5340,9 +5343,9 @@ def __init__( Parameters ---------- - dtype: PropertyLike[Any], optional + dtype: PropertyLike[str], optional The desired data type for the image. It defaults to `"float64"`. - **kwargs:: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ @@ -5351,16 +5354,17 @@ def __init__( def get( self: Feature, - image: np.ndarray, + image: NDArray | torch.Tensor | Image, dtype: str, **kwargs: Any, - ) -> np.ndarray: + ) -> NDArray | torch.Tensor | Image: """Convert the data type of the input image. Parameters ---------- - image: np.ndarray - The input image to process. + image: array + The input image to process. It can be a NumPy array, a PyTorch + tensor, or an Image. dtype: str The desired data type for the image. **kwargs: Any @@ -5368,8 +5372,9 @@ def get( Returns ------- - np.ndarray - The input image converted to the specified data type. + array + The input image converted to the specified data type. It can be a + NumPy array, a PyTorch tensor, or an Image. """ @@ -5642,7 +5647,7 @@ def get( # Downscale the result to the original resolution. import skimage.measure - + image = skimage.measure.block_reduce( image, (factor[0], factor[1]) + (1,) * (image.ndim - 2), np.mean ) From 6ccdfac3d7919fd3fcff586abd7b01bed4ccb1bb Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 08:05:30 +0200 Subject: [PATCH 087/223] Update test_features.py --- deeptrack/tests/test_features.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index ee44ccfcd..4fb75ba42 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1922,6 +1922,7 @@ def test_SampleToMasks(self): def test_AsType(self): + # Test for Numpy arrays. input_image = np.array([1.5, 2.5, 3.5]) data_types = ["float64", "int32", "uint16", "int16", "uint8", "int8"] @@ -1937,6 +1938,12 @@ def test_AsType(self): np.all(output_image == np.array([1, 2, 3], dtype=dtype)) ) + # Test for Image. + #TODO + + # Test for PyTorch tensors. + #TODO + def test_ChannelFirst2d(self): From 4b4d5476d3628582c8c06125cfb33f1c4d44636b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 18:46:20 +0200 Subject: [PATCH 088/223] Update features.py --- deeptrack/features.py | 164 ++++++++++++++++++++++++++++++------------ 1 file changed, 119 insertions(+), 45 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 97f564048..015453279 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -214,31 +214,56 @@ def merge_features( class Feature(DeepTrackNode): """Base feature class. - Features define the image generation process. All features operate on lists - of images. Most features, such as noise, apply some tranformation to all - images in the list. This transformation can be additive, such as adding - some Gaussian noise or a background illumination, or non-additive, such as - introducing Poisson noise or performing a low-pass filter. This - transformation is defined by the method `get(image, **kwargs)`, which all - implementations of the class `Feature` need to define. - - Whenever a Feature is initiated, all keyword arguments passed to the - constructor will be wrapped as a `Property`, and stored in the `properties` - attribute as a `PropertyDict`. When a Feature is resolved, the current - value of each property is sent as input to the get method. + Features define the image generation process. + + All features operate on lists of images. Most features, such as noise, + apply a tranformation to all images in the list. This transformation can be + additive, such as adding some Gaussian noise or a background illumination, + or non-additive, such as introducing Poisson noise or performing a low-pass + filter. This transformation is defined by the `get(image, **kwargs)` + method, which all implementations of the class `Feature` need to define. + This method operates on a single image at a time. + + Whenever a Feature is initialized, it wraps all keyword arguments passed to + the constructor as `Property` objects, and stored in the `properties` + attribute as a `PropertyDict`. + + When a Feature is resolved, the current value of each property is sent as + input to the get method. + + **Computational Backends and Data Types** + + This class also provides mechanisms for managing numerical types and + computational backends. + + Supported backends include NumPy and PyTorch. The active backend is + determined at initialization and stored in the `_backend` attribute, which + is used internally to control how computations are executed. The backend + can be switched using the `.numpy()` and `.torch()` methods. + + Numerical types used in computation (float, int, complex, and bool) can be + configured using the `.dtype()` method. The chosen types are retrieved + via the properties `float_dtype`, `int_dtype`, `complex_dtype`, and + `bool_dtype`. These are resolved dynamically using the backend's internal + type resolution system and are used in downstream computations. + + The computational device (e.g., "cpu" or a specific GPU) is managed through + the `.to()` method and accessed via the `device` property. This is + especially relevant for PyTorch backends, which support GPU acceleration. Parameters ---------- - _input: np.ndarray or Image or list[np.ndarray or Image], optional. - A list of np.ndarray or `DeepTrackNode` objects or a single np.ndarray - or an `Image` object representing the input data for the feature. This - parameter specifies what the feature will process. If left empty, no - initial input is set. - **kwargs: dict of str and Any + _input: Any, optional. + The input data for the feature. If left empty, no initial input is set. + It is most commonly a NumPy array, PyTorch tensor, or Image object, or + a list of NumPy arrays, PyTorch tensors, or Image objects; however, it + can be anything. + **kwargs: Any Keyword arguments to configure the feature. Each keyword argument is wrapped as a `Property` and added to the `properties` attribute, allowing dynamic sampling and parameterization during the feature's - execution. + execution. These properties are passed to the `get()` method when a + feature is resolved. Attributes ---------- @@ -248,6 +273,37 @@ class Feature(DeepTrackNode): dynamically sample values during pipeline execution. A sampled copy of this dictionary is passed to the `get` function and appended to the properties of the output image. + _input: DeepTrackNode + A node representing the input data for the feature. It is most commonly + a NumPy array, PyTorch tensor, or Image object, or a list of NumPy + arrays, PyTorch tensors, or Image objects; however, it can be anything. + It supports lazy evaluation and graph traversal. + _random_seed: DeepTrackNode + A node representing the feature’s random seed. This allows for + deterministic behavior when generating random elements, and ensures + reproducibility during evaluation. + arguments: Feature | None + An optional `Feature` whose properties are bound to this feature. This + allows dynamic property sharing and centralized parameter management + in complex pipelines. + __list_merge_strategy__: int + Specifies how the output of `.get(image, **kwargs)` is merged with the + current `_input`. Options include: + - `MERGE_STRATEGY_OVERRIDE` (0, default): `_input` is replaced by the + new output. + - `MERGE_STRATEGY_APPEND` (1): The output is appended to the end of + `_input`. + __distributed__: bool + Determines whether `.get(image, **kwargs)` is applied to each element + of the input list independently (`__distributed__ = True`) or to the + list as a whole (`__distributed__ = False`). + __conversion_table__: ConversionTable + Defines the unit conversions used by the feature to convert its + properties into the desired units. + _wrap_array_with_image: bool + Internal flag that determines whether arrays are wrapped as `Image` + instances during evaluation. When `True`, image metadata and properties + are preserved and propagated. It defaults to `False`. float_dtype: np.dtype The data type of the float numbers. int_dtype: np.dtype @@ -260,29 +316,14 @@ class Feature(DeepTrackNode): The device on which the feature is executed. _backend: Config The computational backend. - __list_merge_strategy__: int - Specifies how the output of `.get(image, **kwargs)` is merged with the - input list. Options include: - - `MERGE_STRATEGY_OVERRIDE` (0, default): The input list is replaced by - the new list. - - `MERGE_STRATEGY_APPEND` (1): The new list is appended to the end of - the input list. - __distributed__: bool - Determines whether `.get(image, **kwargs)` is applied to each element - of the input list independently (`__distributed__ = True`) or to the - list as a whole (`__distributed__ = False`). - __property_memorability__: int - Specifies whether to store the feature’s properties in the output - image. Properties with a memorability value of `1` or lower are stored - by default. - __conversion_table__: ConversionTable - Defines the unit conversions used by the feature to convert its - properties into the desired units. Methods ------- - `get(image: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> Image | list[Image]` - Abstract method that defines how the feature transforms the input. + `get(image: Any, **kwargs: Any) -> Any` + Abstract method that defines how the feature transforms the input. The + input is most commonly a NumPy array, PyTorch tensor, or Image object, + or a list of NumPy arrays, PyTorch tensors, or Image objects; however, + it can be anything. `__call__(image_list: np.ndarray | list[np.ndarray] | Image | list[Image] | None = None, _ID: tuple[int, ...] = (), **kwargs: Any) -> Any` Executes the feature or pipeline on the input and applies property overrides from `kwargs`. @@ -387,6 +428,10 @@ class Feature(DeepTrackNode): `_no_wrap_process_output(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> None` Processes the output of the feature. + Examples + -------- + TODO + """ properties: PropertyDict @@ -396,14 +441,15 @@ class Feature(DeepTrackNode): __list_merge_strategy__ = MERGE_STRATEGY_OVERRIDE __distributed__ = True - __property_memorability__ = 1 __conversion_table__ = ConversionTable() _wrap_array_with_image: bool = False + _float_dtype: str _int_dtype: str _complex_dtype: str _device: str | torch.device + _backend: Config @property def float_dtype(self) -> np.dtype | torch.dtype: @@ -3894,7 +3940,7 @@ class ConditionalSetProperty(StructuralFeature): the given properties are applied; otherwise, the child feature remains unchanged. - It is advisable to use `dt.Arguments` instead when possible, since this + It is advisable to use `Arguments` instead when possible, since this feature overwrites properties, which may affect future calls to the feature. @@ -3921,6 +3967,11 @@ class ConditionalSetProperty(StructuralFeature): Resolves the child feature, conditionally applying the specified properties. + Warnings + -------- + Deprecation: This feature is deprecated and may be removed in a future + release. It is recommended to use `Arguments` instead. + Examples -------- >>> import deeptrack as dt @@ -3970,7 +4021,7 @@ class ConditionalSetProperty(StructuralFeature): """ def __init__( - self: Feature, + self: ConditionalSetProperty, feature: Feature, condition: PropertyLike[str | bool] | None = None, **kwargs: Any, @@ -3991,6 +4042,14 @@ def __init__( """ + import warnings + + warnings.warn( + "ConditionalSetFeature is deprecated and may be removed in a " + "future release. Please use Arguments instead when possible.", + DeprecationWarning, + ) + if isinstance(condition, str): kwargs.setdefault(condition, True) @@ -3999,7 +4058,7 @@ def __init__( self.feature = self.add_feature(feature) def get( - self: Feature, + self: ConditionalSetProperty, image: Any, condition: str | bool, **kwargs: Any, @@ -4056,6 +4115,8 @@ class ConditionalSetFeature(StructuralFeature): Both `on_true` and `on_false` are updated during each call, even if only one is resolved. + It is advisable to use `Arguments` instead when possible. + Parameters ---------- on_false: Feature, optional @@ -4076,6 +4137,11 @@ class ConditionalSetFeature(StructuralFeature): `get(image: Any, condition: str or bool, **kwargs: Any) -> Any` Resolves the appropriate feature based on the condition. + Warnings + -------- + Deprecation: This feature is deprecated and may be removed in a future + release. It is recommended to use `Arguments` instead. + Examples -------- >>> import deeptrack as dt @@ -4130,7 +4196,7 @@ class ConditionalSetFeature(StructuralFeature): """ def __init__( - self: Feature, + self: ConditionalSetFeature, on_false: Feature | None = None, on_true: Feature | None = None, condition: PropertyLike[str | bool] = True, @@ -4152,6 +4218,14 @@ def __init__( """ + import warnings + + warnings.warn( + "ConditionalSetFeature is deprecated and may be removed in a " + "future release. Please use Arguments instead when possible.", + DeprecationWarning, + ) + if isinstance(condition, str): kwargs.setdefault(condition, True) @@ -4167,7 +4241,7 @@ def __init__( self.on_false = on_false def get( - self: Feature, + self: ConditionalSetFeature, image: Any, *, condition: str | bool, From 0cc01329c5f47f684328dc2898597ffbd1d06f0b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 18:56:48 +0200 Subject: [PATCH 089/223] Update features.py --- deeptrack/features.py | 67 ++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 015453279..18dd17e1b 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -322,8 +322,7 @@ class Feature(DeepTrackNode): `get(image: Any, **kwargs: Any) -> Any` Abstract method that defines how the feature transforms the input. The input is most commonly a NumPy array, PyTorch tensor, or Image object, - or a list of NumPy arrays, PyTorch tensors, or Image objects; however, - it can be anything. + but it can be anything. `__call__(image_list: np.ndarray | list[np.ndarray] | Image | list[Image] | None = None, _ID: tuple[int, ...] = (), **kwargs: Any) -> Any` Executes the feature or pipeline on the input and applies property overrides from `kwargs`. @@ -478,28 +477,22 @@ def device(self) -> str | torch.device: def __init__( self: Feature, - _input: ( - NDArray - | list[NDArray] - | torch.Tensor - | list[torch.Tensor] - | Image - | list[Image] - ) = [], + _input: Any = [], **kwargs: Any, - ) -> None: + ): """Initialize a new Feature instance. Parameters ---------- - _input: np.ndarray or list[np.ndarray] or torch.Tensor or list[torch.Tensor] or Image or list[Images], optional - The initial input(s) for the feature, often images or other data. - If not provided, defaults to an empty list. + _input: Any, optional + The initial input(s) for the feature. It is most commonly a NumPy + array, PyTorch tensor, or Image object, or a list of NumPy arrays, + PyTorch tensors, or Image objects; however, it can be anything. If + not provided, defaults to an empty list. **kwargs: Any Keyword arguments that are wrapped into `Property` instances and stored in `self.properties`, allowing for dynamic or parameterized - behavior. - If not provided, defaults to an empty list. + behavior. If not provided, it defaults to an empty list. """ @@ -540,25 +533,26 @@ def __init__( def get( self: Feature, - image: np.ndarray | list[np.ndarray] | Image | list[Image], + image: Any, **kwargs: Any, - ) -> Image | list[Image]: - """Transform an image [abstract method]. + ) -> Any: + """Transform an input (abstract method). Abstract method that defines how the feature transforms the input. The current value of all properties will be passed as keyword arguments. Parameters ---------- - image: np.ndarray or Image or list[np.ndarray or Image] - The image or list of images to transform. + image: Any + The input to transform. It is most commonly a NumPy array, PyTorch + tensor, or Image object, but it can be anything. **kwargs: Any The current value of all properties in `properties`, as well as any global arguments passed to the feature. Returns ------- - Image or list[Image] + Any The transformed image or list of images. Raises @@ -572,19 +566,7 @@ def get( def __call__( self: Feature, - image_list: ( - Feature - | list[Feature] - | NDArray[Any] - | list[NDArray[Any]] - | torch.Tensor - | list[torch.Tensor] - | Image - | list[Image] - | Any - | list[Any] - | None - ) = None, + image_list: Any = None, _ID: tuple[int, ...] = (), **kwargs: Any, ) -> Any: @@ -600,9 +582,12 @@ def __call__( Parameters ---------- - image_list: np.ndarrray or Image or list[np.ndarrray or Image], optional - The input to the feature or pipeline. If `None`, the feature uses - previously set input values or propagates properties. + image_list: Any, optional + The input to the feature or pipeline. It is most commonly a NumPy + array, PyTorch tensor, or Image object, or a list of NumPy arrays, + PyTorch tensors, or Image objects; however, it can be anything. It + defaults to `None`, in which case the feature uses the previous set + input values or propagates properties. **kwargs: Any Additional parameters passed to the pipeline. These override properties with matching names. For example, calling @@ -614,7 +599,11 @@ def __call__( ------- Any The output of the feature or pipeline after execution. - + + Examples + -------- + TODO: basic examples + examples with overwriting of features + """ with config.with_backend(self._backend): From 362ad856a5ddd3ebbebe771e1c8c5ac38c6b4845 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 18:57:59 +0200 Subject: [PATCH 090/223] Update features.py --- deeptrack/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 18dd17e1b..f914499b6 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -323,8 +323,8 @@ class Feature(DeepTrackNode): Abstract method that defines how the feature transforms the input. The input is most commonly a NumPy array, PyTorch tensor, or Image object, but it can be anything. - `__call__(image_list: np.ndarray | list[np.ndarray] | Image | list[Image] | None = None, _ID: tuple[int, ...] = (), **kwargs: Any) -> Any` - Executes the feature or pipeline on the input and applies property + `__call__(image_list: Any = None, _ID: tuple[int, ...] = (), **kwargs: Any) -> Any` + It executes the feature or pipeline on the input and applies property overrides from `kwargs`. `store_properties(x: bool = True, recursive: bool = True) -> None` Controls whether the properties are stored in the output `Image` object. From 07383ce697c81194b7d97f8fe6fb482ce95f5577 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 18:58:50 +0200 Subject: [PATCH 091/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index f914499b6..9444a9d5e 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -137,7 +137,7 @@ def merge_features( from scipy.spatial.distance import cdist from deeptrack import units -from deeptrack.backend import config, TORCH_AVAILABLE, xp +from deeptrack.backend import config, Config, TORCH_AVAILABLE, xp from deeptrack.backend.core import DeepTrackNode from deeptrack.backend.units import ConversionTable, create_context from deeptrack.image import Image From 0be5371c2161c226bc7bec093dcb07fd39bcc076 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 19:14:31 +0200 Subject: [PATCH 092/223] Update _config.py --- deeptrack/backend/_config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeptrack/backend/_config.py b/deeptrack/backend/_config.py index 4a578f149..3441e358b 100644 --- a/deeptrack/backend/_config.py +++ b/deeptrack/backend/_config.py @@ -144,6 +144,7 @@ __all__ = [ "config", + "Config", "OPENCV_AVAILABLE", "TORCH_AVAILABLE", "xp", From fffdf6d1fbd1b6cc164ed634ab92c8b6026d5170 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 19:14:33 +0200 Subject: [PATCH 093/223] Update features.py --- deeptrack/features.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 9444a9d5e..81ac8f616 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -598,11 +598,30 @@ def __call__( Returns ------- Any - The output of the feature or pipeline after execution. + The output of the feature or pipeline after execution. This is + typically a NumPy array, PyTorch tensor, or Image object, or a list + of NumPy arrays, PyTorch tensors, or Image objects. Examples -------- - TODO: basic examples + examples with overwriting of features + >>> import deeptrack as dt + + Deafine a feature: + >>> feature = dt.Add(value=2) + + Call this feature with an input: + >>> import numpy as np + >>> + >>> feature(np.array([1, 2, 3])) + array([3, 4, 5]) + + Execute the feature with previously set input: + >>> feature() # Uses stored input + array([3, 4, 5]) + + Override a property: + >>> feature(np.array([1, 2, 3]), value=10) + array([11, 12, 13]) """ From df8330f6735b7854939653cd9102295deddd0af3 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 19:19:37 +0200 Subject: [PATCH 094/223] Update features.py --- deeptrack/features.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 81ac8f616..25b60a168 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -629,7 +629,8 @@ def __call__( # If image_list is as Source, activate it. self._activate_sources(image_list) - # Potentially fragile. Maybe a special variable dt._last_input instead? + # Potentially fragile. + # Maybe a special variable dt._last_input instead? # If the input is not empty, set the value of the input. if ( image_list is not None @@ -639,29 +640,31 @@ def __call__( ): self._input.set_value(image_list, _ID=_ID) - # A dict to store the values of self.arguments before updating them. + # A dict to store values of self.arguments before updating them. original_values = {} - # If there are no self.arguments, instead propagate the values of the - # kwargs to all properties in the computation graph. + # If there are no self.arguments, instead propagate the values of + # the kwargs to all properties in the computation graph. if kwargs and self.arguments is None: propagate_data_to_dependencies(self, **kwargs) - # If there are self.arguments, update the values of self.arguments to - # match kwargs. + # If there are self.arguments, update the values of self.arguments + # to match kwargs. if isinstance(self.arguments, Feature): for key, value in kwargs.items(): if key in self.arguments.properties: original_values[key] = \ self.arguments.properties[key](_ID=_ID) - self.arguments.properties[key].set_value(value, _ID=_ID) + self.arguments.properties[key]\ + .set_value(value, _ID=_ID) - # This executes the feature. DeepTrackNode will determine if it needs - # to be recalculated. If it does, it will call the `action` method. + # This executes the feature. DeepTrackNode will determine if it + # needs to be recalculated. If it does, it will call the `action` + # method. output = super().__call__(_ID=_ID) - # If there are self.arguments, reset the values of self.arguments to - # their original values. + # If there are self.arguments, reset the values of self.arguments + # to their original values. for key, value in original_values.items(): self.arguments.properties[key].set_value(value, _ID=_ID) From 57e42ec8f7664b8f6987f7a01bfcfd3815f973bb Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 19:43:23 +0200 Subject: [PATCH 095/223] Update features.py --- deeptrack/features.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 25b60a168..38b8bff42 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -323,7 +323,7 @@ class Feature(DeepTrackNode): Abstract method that defines how the feature transforms the input. The input is most commonly a NumPy array, PyTorch tensor, or Image object, but it can be anything. - `__call__(image_list: Any = None, _ID: tuple[int, ...] = (), **kwargs: Any) -> Any` + `__call__(image_list: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` It executes the feature or pipeline on the input and applies property overrides from `kwargs`. `store_properties(x: bool = True, recursive: bool = True) -> None` @@ -685,9 +685,44 @@ def store_properties( Parameters ---------- toggle: bool - If `True`, store properties. If `False`, do not store. + If `True` (default), store properties. If `False`, do not store. recursive: bool - If `True`, also set the same behavior for all dependent features. + If `True` (default), also set the same behavior for all dependent + features. If `False`, it does not. + + Examples + -------- + >>> import deeptrack as dt + + Create a feature and enable property storage: + >>> feature = dt.Add(value=2) + >>> feature.store_properties(True) + + Evaluate the feature and inspect the stored properties: + >>> import numpy as np + >>> + >>> output = feature(np.array([1, 2, 3])) + >>> isinstance(output, dt.Image) + True + >>> output.get_property("value") + 2 + + Disable property storage: + >>> feature.store_properties(False) + >>> output = feature(np.array([1, 2, 3])) + >>> isinstance(output, dt.Image) + False + + Apply recursively to a pipeline: + >>> feature1 = dt.Add(value=1) + >>> feature2 = dt.Multiply(value=2) + >>> pipeline = feature1 >> feature2 + >>> pipeline.store_properties(True, recursive=True) + >>> output = pipeline(np.array([1, 2])) + >>> output.get_property("value") + 1 + >>> output.get_property("value", get_one=False) + [1, 2] """ @@ -1562,7 +1597,7 @@ def __getitem__( return self >> Slice(slices) - # private properties to dispatch based on config + # Private properties to dispatch based on config. @property def _format_input(self): """Selects the appropriate input formatting function based on From e4e9750f61cbb3f412db84dc5ab3c528d890b6d3 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 1 Jul 2025 22:16:42 +0200 Subject: [PATCH 096/223] Update features.py --- deeptrack/features.py | 149 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 135 insertions(+), 14 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 38b8bff42..82c186825 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -743,15 +743,58 @@ def torch( Parameters ---------- device: torch.device, optional - The target device of the output (e.g., cpu or cuda). + The target device of the output (e.g., cpu or cuda). It defaults to + `None`. recursive: bool, optional - If `True`, also convert all dependent features. + If `True` (default), it also convert all dependent features. If + `False`, it does not. Returns ------- Feature self + Examples + -------- + >>> import deeptrack as dt + >>> import torch + + Create a feature and switch to the PyTorch backend: + >>> feature = dt.Multiply(value=2) + >>> feature.torch() + + Call the feature on a torch tensor: + >>> input_tensor = torch.tensor([1.0, 2.0, 3.0]) + >>> output = feature(input_tensor) + >>> output + tensor([2., 4., 6.]) + + Switch to GPU if available (CUDA): + >>> if torch.cuda.is_available(): + ... device = torch.device("cuda") + ... feature.torch(device=device) + ... output = feature(torch.tensor([1.0, 2.0, 3.0], device=device)) + ... output.device.type + 'cuda' + + Switch to GPU if available (MPS): + >>> if (torch.backends.mps.is_available() + ... and torch.backends.mps.is_built()): + ... device = torch.device("mps") + ... feature.torch(device=device) + ... output = feature(torch.tensor([1.0, 2.0, 3.0], device=device)) + ... output.device.type + 'mps' + + Apply recursively in a pipeline: + >>> f1 = dt.Add(value=1) + >>> f2 = dt.Multiply(value=2) + >>> pipeline = f1 >> f2 + >>> pipeline.torch() + >>> output = pipeline(torch.tensor([1.0, 2.0])) + >>> output + tensor([4., 6.]) + """ self._backend = "torch" @@ -763,19 +806,45 @@ def torch( self.invalidate() return self - def numpy(self: Feature, recursive: bool = True) -> Feature: + def numpy( + self: Feature, + recursive: bool = True, + ) -> Feature: """Set the backend to numpy. Parameters ---------- recursive: bool, optional - If `True`, also convert all dependent features. + If `True` (default), also convert all dependent features. Returns ------- Feature self + Examples + -------- + >>> import deeptrack as dt + >>> import numpy as np + + Create a feature and ensure it uses the NumPy backend: + >>> feature = dt.Add(value=5) + >>> feature.numpy() + + Evaluate the feature on a NumPy array: + >>> output = feature(np.array([1, 2, 3])) + >>> output + array([6, 7, 8]) + + Apply recursively in a pipeline: + >>> f1 = dt.Multiply(value=2) + >>> f2 = dt.Subtract(value=1) + >>> pipeline = f1 >> f2 + >>> pipeline.numpy() + >>> output = pipeline(np.array([1, 2, 3])) + >>> output + array([1, 3, 5]) + """ self._backend = "numpy" @@ -795,19 +864,45 @@ def dtype( ) -> None: """Set the dtype to be used during evaluation. - This alters the dtype used for array creation, but does not - automatically cast the type. + It alters the dtype used for array creation, but does not automatically + cast the type. Parameters ---------- float: str, optional - The float dtype to set. + The float dtype to set. It can be `"float32"`, `"float64"`, + `"default"`, or `None`. It defaults to `None`. int: str, optional - The int dtype to set. + The int dtype to set. It can be `"int16"`, `"int32"`, `"int64"`, + `"default"`, or `None`. It defaults to `None`. complex: str, optional - The complex dtype to set. + The complex dtype to set. It can be `"complex64"`, `"complex128"`, + `"default"`, or `None`. It defaults to `None`. bool: str, optional - The bool dtype to set. + The bool dtype to set. It cna be `"bool"`, `"default"`, or `None`. + It defaults to `None`. + + Examples + -------- + >>> import deeptrack as dt + + Set float and int data types for a feature: + >>> feature = dt.Multiply(value=2) + >>> feature.dtype(float="float32", int="int16") + >>> feature.float_dtype + dtype('float32') + >>> feature.int_dtype + dtype('int16') + + Use complex numbers in the feature: + >>> feature.dtype(complex="complex128") + >>> feature.complex_dtype + dtype('complex128') + + Reset float dtype to default: + >>> feature.dtype(float="default") + >>> feature.float_dtype # resolved from config + dtype('float64') # depending on backend config """ @@ -820,15 +915,41 @@ def dtype( if bool is not None: self._bool_dtype = bool - def to(self: Feature, device: str | torch.device): + def to( + self: Feature, + device: str | torch.device, + ) -> None: """Set the device to be used during evaluation. - If the backend is numpy, this can only be "cpu". - Parameters ---------- device: str or torch.device - The device to use. + The device to use. If the backend is numpy, this can only be "cpu". + + Examples + -------- + >>> import deeptrack as dt + >>> import torch + + Create a feature and assign a device (for torch backend): + >>> feature = dt.Add(value=1) + >>> feature.torch() + >>> feature.to(torch.device("cpu")) + >>> feature.device + device(type='cpu') + + Move the feature to GPU (if available): + >>> if torch.cuda.is_available(): + ... feature.to(torch.device("cuda")) + ... feature.device + device(type='cuda') + + Use Apple MPS device on Apple Silicon (if supported): + >>> if (torch.backends.mps.is_available() + ... and torch.backends.mps.is_built()): + ... feature.to(torch.device("mps")) + ... feature.device + device(type='mps') """ From 4355a71d929e7edf5740bc07a88135f9b7ae989a Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 05:19:58 +0200 Subject: [PATCH 097/223] Update features.py --- deeptrack/features.py | 56 +++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 82c186825..6affc9ef9 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -955,7 +955,10 @@ def to( self._device = device - def batch(self: Feature, batch_size: int = 32) -> tuple | list[Image]: + def batch( + self: Feature, + batch_size: int = 32, + ) -> tuple: """Batch the feature. This method produces a batch of outputs by repeatedly calling @@ -964,14 +967,38 @@ def batch(self: Feature, batch_size: int = 32) -> tuple | list[Image]: Parameters ---------- batch_size: int - The number of times to sample or generate data. + The number of times to sample or generate data. It defaults to 32. Returns ------- - tuple or list[Image] - A tuple of stacked arrays (if the outputs are NumPy arrays or - torch tensors) or a list of images if the outputs are not - stackable. + tuple + A tuple where each element corresponds to one component of the + output. If the outputs are NumPy arrays or PyTorch tensors, each + element is a stacked array. + + Examples + -------- + >>> import deeptrack as dt + + Define a feature that adds a random value to a fixed array: + >>> import numpy as np + >>> + >>> feature = ( + ... dt.Value(value=np.array([[-1, 1]])) + ... >> dt.Add(value=lambda: np.random.rand()) + ... ) + + Evaluate the feature once: + >>> output = feature() + >>> output + array([[-0.77378939, 1.22621061]]) + + Generate a batch of outputs: + >>> batch = feature.batch(batch_size=3) + >>> batch + (array([[-0.2375814 , 1.7624186 ], + [-0.65764878, 1.34235122], + [-0.87449525, 1.12550475]]),) """ @@ -979,21 +1006,14 @@ def batch(self: Feature, batch_size: int = 32) -> tuple | list[Image]: results = list(zip(*results)) for idx, r in enumerate(results): - - if isinstance(r[0], np.ndarray): - results[idx] = np.stack(r) - else: - import torch - - if isinstance(r[0], torch.Tensor): - results[idx] = torch.stack(r) + results[idx] = xp.stack(r) return tuple(results) def action( self: Feature, _ID: tuple[int, ...] = (), - ) -> Image | list[Image]: + ) -> Any | list[Any]: """Core logic to create or transform the image. This method creates or transforms the input image by calling the @@ -1002,11 +1022,11 @@ def action( Parameters ---------- _ID: tuple of int - The unique identifier for the current execution. + The unique identifier for the current execution. It defaults to (). Returns ------- - Image or list[Image] + Any or list[Any] The resolved image or list of resolved images. """ @@ -1018,7 +1038,7 @@ def action( feature_input = self.properties(_ID=_ID).copy() # Call the _process_properties hook, default does nothing. - # For example, it can be used to ensure properties are formatted + # For example, it can be used to ensure properties are formatted # correctly or to rescale properties. feature_input = self._process_properties(feature_input) if _ID != (): From 55d678887b337287cd2792062a3be666e804a484 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 05:22:10 +0200 Subject: [PATCH 098/223] Update features.py --- deeptrack/features.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 6affc9ef9..5f4a0150f 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1014,20 +1014,24 @@ def action( self: Feature, _ID: tuple[int, ...] = (), ) -> Any | list[Any]: - """Core logic to create or transform the image. + """Core logic to create or transform the input. - This method creates or transforms the input image by calling the - `get()` method with the correct inputs. + This method creates or transforms the input by calling the `get()` + method with the correct inputs. Parameters ---------- - _ID: tuple of int + _ID: tuple[int], optional The unique identifier for the current execution. It defaults to (). Returns ------- Any or list[Any] - The resolved image or list of resolved images. + The resolved output or list of resolved outputs. + + Examples + -------- + TODO """ From a55e95f374655a7d8a493afa5c436212035bef9f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 05:45:57 +0200 Subject: [PATCH 099/223] Update features.py --- deeptrack/features.py | 155 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 125 insertions(+), 30 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 5f4a0150f..46d3d0fec 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -326,14 +326,21 @@ class Feature(DeepTrackNode): `__call__(image_list: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` It executes the feature or pipeline on the input and applies property overrides from `kwargs`. - `store_properties(x: bool = True, recursive: bool = True) -> None` - Controls whether the properties are stored in the output `Image` object. - `torch(dtype: torch.dtype | None = None, device: torch.device | None = None, permute_mode: str = "never") -> 'Feature'` - Converts the feature into a PyTorch-compatible feature. - `batch(batch_size: int = 32) -> tuple | list[Image]` - Batches the feature for repeated execution. - `action(_ID: tuple[int, ...] = ()) -> Image | list[Image]` - Core logic to create or transform the image. + `store_properties(toggle: bool, recursive: bool) -> Feature` + It controls whether the properties are stored in the output `Image` + object. + `torch(device: torch.device or None, recursive: bool) -> 'Feature'` + It sets the backend to torch. + `numpy(recursice: bool) -> Feature` + It set the backend to numpy. + `dtype(float: Literal["float32", "float64", "default"] or None, int: Literal["int16", "int32", "int64", "default"] or None, complex: Literal["complex64", "complex128", "default"] or None, bool: Literal["bool", "default"] or None) -> Feature` + It set the dtype to be used during evaluation. + `to(device: str or torch.device) -> Feature` + It set the device to be used during evaluation. + `batch(batch_size: int) -> tuple` + It batches the feature for repeated execution. + `action(_ID: tuple[int, ...]) -> Any | list[Any]` + Implement the core logic to create or transform the input(s). `update(**global_arguments: Any) -> Feature` Refreshes the feature to create a new image. `add_feature(feature: Feature) -> Feature` @@ -676,7 +683,7 @@ def store_properties( self: Feature, toggle: bool = True, recursive: bool = True, - ) -> None: + ) -> Feature: """Control whether to return an Image object. If selected `True`, the output of the evaluation of the feature is an @@ -690,6 +697,11 @@ def store_properties( If `True` (default), also set the same behavior for all dependent features. If `False`, it does not. + Returns + ------- + Feature + self + Examples -------- >>> import deeptrack as dt @@ -733,9 +745,11 @@ def store_properties( if isinstance(dependency, Feature): dependency.store_properties(toggle, recursive=False) + return self + def torch( self: Feature, - device: torch.device = None, + device: torch.device | None = None, recursive: bool = True, ) -> Feature: """Set the backend to torch. @@ -861,7 +875,7 @@ def dtype( int: Literal["int16", "int32", "int64", "default"] | None = None, complex: Literal["complex64", "complex128", "default"] | None = None, bool: Literal["bool", "default"] | None = None, - ) -> None: + ) -> Feature: """Set the dtype to be used during evaluation. It alters the dtype used for array creation, but does not automatically @@ -882,6 +896,11 @@ def dtype( The bool dtype to set. It cna be `"bool"`, `"default"`, or `None`. It defaults to `None`. + Returns + ------- + Feature + self + Examples -------- >>> import deeptrack as dt @@ -915,10 +934,12 @@ def dtype( if bool is not None: self._bool_dtype = bool + return self + def to( self: Feature, device: str | torch.device, - ) -> None: + ) -> Feature: """Set the device to be used during evaluation. Parameters @@ -926,6 +947,11 @@ def to( device: str or torch.device The device to use. If the backend is numpy, this can only be "cpu". + Returns + ------- + Feature + self + Examples -------- >>> import deeptrack as dt @@ -955,6 +981,8 @@ def to( self._device = device + return self + def batch( self: Feature, batch_size: int = 32, @@ -1016,10 +1044,43 @@ def action( ) -> Any | list[Any]: """Core logic to create or transform the input. - This method creates or transforms the input by calling the `get()` - method with the correct inputs. + This method is the central point where the feature's transformation is + actually executed. It retrieves the input data, evaluates the current + values of all properties, formats the input into a list of `Image` + objects, and applies the `get()` method to perform the desired + transformation. + + Depending on the configuration, the transformation can be applied to + each element of the input independently or to the full list at once. + The outputs are optionally post-processed, and then merged back into + the input according to the configured merge strategy. Parameters + + The behavior of this method is influenced by several class attributes: + + - `__distributed__`: If `True` (default), the `get()` method is applied + independently to each input in the input list. If `False`, the + `get()` method is applied to the entire list at once. + + - `__list_merge_strategy__`: Determines how the outputs returned by + `get()` are combined with the original inputs: + * `MERGE_STRATEGY_OVERRIDE` (default): The output replaces the + input. + * `MERGE_STRATEGY_APPEND`: The output is appended to the input + list. + + - `_wrap_array_with_image`: If `True`, input arrays are wrapped as + `Image` instances and their properties are preserved. Otherwise, + they are treated as raw arrays. + + - `_process_properties()`: This hook can be overridden to pre-process + properties before they are passed to `get()` (e.g., for unit + normalization). + + - `_process_output()`: Handles post-processing of the output images, + including appending feature properties and binding argument features. + ---------- _ID: tuple[int], optional The unique identifier for the current execution. It defaults to (). @@ -1027,11 +1088,37 @@ def action( Returns ------- Any or list[Any] - The resolved output or list of resolved outputs. + The resolved output or list of resolved outputs. If only a single + output is generated, the result is unwrapped for convenience. Examples -------- - TODO + >>> import deeptrack as dt + + Define a feature that adds a sampled value: + >>> import numpy as np + >>> + >>> feature = ( + ... dt.Value(value=np.array([1, 2, 3])) + ... >> dt.Add(value=0.5) + ... ) + + Execute core logic manually: + >>> output = feature.action() + >>> output + array([1.5, 2.5, 3.5]) + + Use a list of inputs: + >>> feature = ( + ... dt.Value(value=[ + ... np.array([1, 2, 3]), + ... np.array([4, 5, 6]), + ... ]) + ... >> dt.Add(value=0.5) + ... ) + >>> output = feature.action() + >>> output + [array([1.5, 2.5, 3.5]), array([4.5, 5.5, 6.5])] """ @@ -1188,23 +1275,27 @@ def _normalize( self: Feature, **properties: dict[str, Any], ) -> dict[str, Any]: - """Normalizes the properties. + """Normalize the properties. - This method handles all unit normalizations and conversions. For each class in - the method resolution order (MRO), it checks if the class has a - `__conversion_table__` attribute. If found, it calls the `convert` method of - the conversion table using the properties as arguments. + This method handles all unit normalizations and conversions. For each + class in the method resolution order (MRO), it checks if the class has + a `__conversion_table__` attribute. If found, it calls the `convert` + method of the conversion table using the properties as arguments. Parameters ---------- - **properties: dict of str to Any + **properties: dict[str, Any] The properties to be normalized and converted. Returns ------- - dict of str to Any + dict[str, Any] The normalized and converted properties. + Examples + -------- + TODO + """ for cl in type(self).mro(): @@ -1313,12 +1404,17 @@ def _process_properties( self: Feature, propertydict: dict[str, Any], ) -> dict[str, Any]: - """Preprocesses the input properties before calling `.get()`. + """Preprocess the input properties before calling `.get()`. This method acts as a preprocessing hook for subclasses, allowing them to modify or normalize input properties before the feature's main computation. + Notes: + - Calls `_normalize()` internally to standardize input properties. + - Subclasses may override this method to implement additional + preprocessing steps. + Parameters ---------- propertydict: dict[str, Any] @@ -1330,15 +1426,14 @@ def _process_properties( dict[str, Any] The processed property dictionary after normalization. - Notes - ----- - - Calls `_normalize()` internally to standardize input properties. - - Subclasses may override this method to implement additional - preprocessing steps. - + Examples + -------- + TODO + """ propertydict = self._normalize(**propertydict) + return propertydict def _activate_sources( From b247a6554684c7a3c4a621f62d1eea0c9775ddad Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 05:53:47 +0200 Subject: [PATCH 100/223] Update features.py --- deeptrack/features.py | 66 +++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 46d3d0fec..a68a4103f 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1840,38 +1840,36 @@ def __getitem__( # Private properties to dispatch based on config. @property def _format_input(self): - """Selects the appropriate input formatting function based on - configuration. + """Select the appropriate input formatting function for configuration. """ if self._wrap_array_with_image: return self._image_wrapped_format_input - else: - return self._no_wrap_format_input + + return self._no_wrap_format_input @property def _process_and_get(self): - """Selects the appropriate processing function based on configuration. - + """Select the appropriate processing function based on configuration. + """ if self._wrap_array_with_image: return self._image_wrapped_process_and_get - else: - return self._no_wrap_process_and_get + + return self._no_wrap_process_and_get @property def _process_output(self): - """Selects the appropriate output processing function based on - configuration. - + """Select the appropriate output processing function for configuration. + """ if self._wrap_array_with_image: return self._image_wrapped_process_output - else: - return self._no_wrap_process_output + + return self._no_wrap_process_output def _image_wrapped_format_input( self: Feature, @@ -1907,20 +1905,30 @@ def _no_wrap_format_input( return image_list - def _no_wrap_process_and_get( + def _image_wrapped_process_and_get( self: Feature, image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **feature_input: dict[str, Any], ) -> list[Image]: - """Processes input data without additional wrapping and retrieves - results. + """Processes input data while maintaining Image properties. """ if self.__distributed__: # Call get on each image in list, and merge properties from # corresponding image - return [self.get(x, **feature_input) for x in image_list] + + results = [] + + for image in image_list: + output = self.get(image, **feature_input) + if not isinstance(output, Image): + output = Image(output) + + output.merge_properties_from(image) + results.append(output) + + return results else: # Call get on entire list. @@ -1929,32 +1937,25 @@ def _no_wrap_process_and_get( if not isinstance(new_list, list): new_list = [new_list] + for idx, image in enumerate(new_list): + if not isinstance(image, Image): + new_list[idx] = Image(image) return new_list - def _image_wrapped_process_and_get( + def _no_wrap_process_and_get( self: Feature, image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **feature_input: dict[str, Any], ) -> list[Image]: - """Processes input data while maintaining Image properties. + """Processes input data without additional wrapping and retrieves + results. """ if self.__distributed__: # Call get on each image in list, and merge properties from # corresponding image - - results = [] - - for image in image_list: - output = self.get(image, **feature_input) - if not isinstance(output, Image): - output = Image(output) - - output.merge_properties_from(image) - results.append(output) - - return results + return [self.get(x, **feature_input) for x in image_list] else: # Call get on entire list. @@ -1963,9 +1964,6 @@ def _image_wrapped_process_and_get( if not isinstance(new_list, list): new_list = [new_list] - for idx, image in enumerate(new_list): - if not isinstance(image, Image): - new_list[idx] = Image(image) return new_list def _image_wrapped_process_output( From 2c61e9f14434858a95052c35e998f0227df93302 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 06:28:34 +0200 Subject: [PATCH 101/223] Update features.py --- deeptrack/features.py | 222 +++++++++++++++++++++++++++++++++--------- 1 file changed, 176 insertions(+), 46 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index a68a4103f..6786e976a 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1839,9 +1839,21 @@ def __getitem__( # Private properties to dispatch based on config. @property - def _format_input(self): + def _format_input(self: Feature) -> Callable[[Any], list[Any or Image]]: """Select the appropriate input formatting function for configuration. - + + Returns either `_image_wrapped_format_input` or + `_no_wrap_format_input`, depending on whether image metadata + (properties) should be preserved and processed downstream. + + This selection is controlled by the `_wrap_array_with_image` flag. + + Returns + ------- + Callable + A function that formats the input into a list of Image objects or + raw arrays, depending on the configuration. + """ if self._wrap_array_with_image: @@ -1850,9 +1862,23 @@ def _format_input(self): return self._no_wrap_format_input @property - def _process_and_get(self): + def _process_and_get(self: Feature) -> Callable[[Any], list[Any or Image]]: """Select the appropriate processing function based on configuration. + Returns a method that applies the feature’s transformation (`get`) to + the input data, either with or without wrapping and preserving `Image` + metadata. + + The decision is based on the `_wrap_array_with_image` flag: + - If `True`, returns `_image_wrapped_process_and_get` + - If `False`, returns `_no_wrap_process_and_get` + + Returns + ------- + Callable + A function that applies `.get()` to the input, either preserving + or ignoring metadata depending on configuration. + """ if self._wrap_array_with_image: @@ -1861,9 +1887,24 @@ def _process_and_get(self): return self._no_wrap_process_and_get @property - def _process_output(self): + def _process_output(self: Feature) -> Callable[[Any], None]: """Select the appropriate output processing function for configuration. + Returns a method that post-processes the outputs of the feature, + typically after the `get()` method has been called. The selected method + depends on whether the feature is configured to wrap outputs in `Image` + objects (`_wrap_array_with_image = True`). + + - If `True`, returns `_image_wrapped_process_output`, which appends + feature properties to each `Image`. + - If `False`, returns `_no_wrap_process_output`, which extracts raw + array values from any `Image` instances. + + Returns + ------- + Callable + A post-processing function for the feature output. + """ if self._wrap_array_with_image: @@ -1873,11 +1914,27 @@ def _process_output(self): def _image_wrapped_format_input( self: Feature, - image_list: np.ndarray | list[np.ndarray] | Image | list[Image], + image_list: np.ndarray | list[np.ndarray] | Image | list[Image] | None, **kwargs: Any, ) -> list[Image]: - """Wraps input data as Image instances before processing. - + """Wrap input data as Image instances before processing. + + This method ensures that all elements in the input are `Image` + objects. If any raw arrays are provided, they are wrapped in `Image`. + This allows features to propagate metadata and store properties in the + output. + + Parameters + ---------- + image_list: np.ndarray or list[np.ndarray] or Image or list[Image] or None + The input to the feature. If not a list, it is converted into a + single-element list. If `None`, it returns an empty list. + + Returns + ------- + list[Image] + A list where all items are instances of `Image`. + """ if image_list is None: @@ -1889,12 +1946,26 @@ def _image_wrapped_format_input( return [(Image(image)) for image in image_list] def _no_wrap_format_input( - self: Feature, - image_list: np.ndarray | list[np.ndarray] | Image | list[Image], + self: Feature, + image_list: Any, **kwargs: Any, - ) -> list[Image]: - """Processes input data without wrapping it as Image instances. - + ) -> list[Any]: + """Process input data without wrapping it as Image instances. + + This method returns the input list as-is (after ensuring it is a list). + It is used when metadata is not needed or performance is a concern. + + Parameters + ---------- + image_list: Any + The input to the feature. If not already a list, it is wrapped in + one. If `None`, it returns an empty list. + + Returns + ------- + list[Any] + A list of raw input elements, without any transformation. + """ if image_list is None: @@ -1907,16 +1978,36 @@ def _no_wrap_format_input( def _image_wrapped_process_and_get( self: Feature, - image_list: np.ndarray | list[np.ndarray] | Image | list[Image], + image_list: Image | list[Image] | Any | list[Any], **feature_input: dict[str, Any], ) -> list[Image]: """Processes input data while maintaining Image properties. - + + This method applies the `get()` method to the input while ensuring that + output values are wrapped as `Image` instances and preserve the + properties of the corresponding input images. + + If `__distributed__ = True`, `get()` is called separately for each + input image. If `False`, the full list is passed to `get()` at once. + + Parameters + ---------- + image_list: Image or list[Image] or Any or list[Any] + The input data to be processed. + **feature_input: dict[str, Any] + The keyword arguments containing the sampled properties to pass + to the `get()` method. + + Returns + ------- + list[Image] + The list of processed images, with properties preserved. + """ if self.__distributed__: - # Call get on each image in list, and merge properties from - # corresponding image + # Call get on each image in list, and merge properties from + # corresponding image. results = [] @@ -1930,69 +2021,108 @@ def _image_wrapped_process_and_get( return results - else: - # Call get on entire list. - new_list = self.get(image_list, **feature_input) + # ELse, call get on entire list. + new_list = self.get(image_list, **feature_input) - if not isinstance(new_list, list): - new_list = [new_list] + if not isinstance(new_list, list): + new_list = [new_list] - for idx, image in enumerate(new_list): - if not isinstance(image, Image): - new_list[idx] = Image(image) - return new_list + for idx, image in enumerate(new_list): + if not isinstance(image, Image): + new_list[idx] = Image(image) + return new_list def _no_wrap_process_and_get( self: Feature, - image_list: np.ndarray | list[np.ndarray] | Image | list[Image], + image_list: Any | list[Any], **feature_input: dict[str, Any], - ) -> list[Image]: - """Processes input data without additional wrapping and retrieves - results. - + ) -> list[Any]: + """Process input data without additional wrapping and retrieve results. + + This method applies the `get()` method to the input without wrapping + results in `Image` objects, and without propagating or merging metadata. + + If `__distributed__ = True`, `get()` is called separately for each + element in the input list. If `False`, the full list is passed to + `get()` at once. + + Parameters + ---------- + image_list: Any or list[Any] + The input data to be processed. + **feature_input: dict + The keyword arguments containing the sampled properties to pass + to the `get()` method. + + Returns + ------- + list[Any] + The list of processed outputs (raw arrays, tensors, etc.). + """ if self.__distributed__: - # Call get on each image in list, and merge properties from + # Call get on each image in list, and merge properties from # corresponding image + return [self.get(x, **feature_input) for x in image_list] - else: - # Call get on entire list. - new_list = self.get(image_list, **feature_input) + # Else, call get on entire list. + new_list = self.get(image_list, **feature_input) - if not isinstance(new_list, list): - new_list = [new_list] + if not isinstance(new_list, list): + new_list = [new_list] - return new_list + return new_list def _image_wrapped_process_output( self: Feature, - image_list: np.ndarray | list[np.ndarray] | Image | list[Image], + image_list: Image | list[Image] | Any | list[Any], feature_input: dict[str, Any], ) -> None: - """Appends feature properties and input data to each Image. - + """Append feature properties and input data to each Image. + + This method is called after `get()` when the feature is set to wrap + its outputs in `Image` instances. It appends the sampled properties + (from `feature_input`) to the metadata of each `Image`. If the feature + is bound to an `arguments` object, those properties are also appended. + + Parameters + ---------- + image_list: list[Image] + The output images from the feature. + feature_input: dict[str, Any] + The resolved property values used during this evaluation. + """ for index, image in enumerate(image_list): - if self.arguments: image.append(self.arguments.properties()) - image.append(feature_input) def _no_wrap_process_output( self: Feature, - image_list: np.ndarray | list[np.ndarray] | Image | list[Image], + image_list: Any | list[Any], feature_input: dict[str, Any], ) -> None: - """Extracts and updates raw values from Image instances. - + """Extract and update raw values from Image instances. + + This method is called after `get()` when the feature is not configured + to wrap outputs as `Image` instances. If any `Image` objects are + present in the output list, their underlying array values are extracted + using `.value` (i.e., `image._value`). + + Parameters + ---------- + image_list: list[Any] + The list of outputs returned by the feature. + feature_input: dict[str, Any] + The resolved property values used during this evaluation (unused). + """ for index, image in enumerate(image_list): - if isinstance(image, Image): image_list[index] = image._value From 434d661cd30e7788871d28f3667718c7c18a00d1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 06:29:53 +0200 Subject: [PATCH 102/223] Update features.py --- deeptrack/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 6786e976a..d11b16d02 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -425,10 +425,10 @@ class Feature(DeepTrackNode): Ensures the input is a list of Image. `_no_wrap_format_input(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` Ensures the input is a list of Image. - `_no_wrap_process_and_get(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` - Calls the `get` method according to the `__distributed__` attribute. `_image_wrapped_process_and_get(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` Calls the `get` method according to the `__distributed__` attribute. + `_no_wrap_process_and_get(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` + Calls the `get` method according to the `__distributed__` attribute. `_image_wrapped_process_output(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> None` Processes the output of the feature. `_no_wrap_process_output(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> None` From c356091dc4ff787cd043eed2163047c1f5f0dacd Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 08:35:42 +0200 Subject: [PATCH 103/223] Update features.py --- deeptrack/features.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index d11b16d02..aa5913ea4 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -185,13 +185,13 @@ def merge_features( "Merge", "OneOf", "OneOfDict", - "LoadImage", # TODO - "SampleToMasks", # TODO - "AsType", # TODO - "ChannelFirst2d", # TODO - "Upscale", # TODO - "NonOverlapping", # TODO - "Store", # TODO + "LoadImage", # TODO **MG** + "SampleToMasks", # TODO **MG** + "AsType", # TODO **MG** + "ChannelFirst2d", # TODO **AL** + "Upscale", # TODO **AL** + "NonOverlapping", # TODO **AL** + "Store", # TODO **JH** "Squeeze", "Unsqueeze", "ExpandDims", @@ -199,7 +199,7 @@ def merge_features( "Transpose", "Permute", "OneHot", - "TakeProperties", # TODO + "TakeProperties", # TODO **JH** ] @@ -1708,8 +1708,8 @@ def __rpow__( return Value(other) >> Power(self) def __gt__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: """Checks if this feature is greater than another using '>'. From f790513be17f7d340a813194d4125bc8f928eecd Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 08:50:48 +0200 Subject: [PATCH 104/223] Update features.py --- deeptrack/features.py | 132 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index aa5913ea4..43e873c6f 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1588,25 +1588,127 @@ def __rrshift__( return NotImplemented def __add__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: """Adds another value or feature using '+'. - + + This operator is shorthand for chaining with `dt.Add`. The expression: + + >>> feature + other + + is equivalent to: + + >>> feature >> dt.Add(value=other) + + Internally, this method constructs a new `Add` feature and uses the + right-shift operator (`>>`) to chain the current feature into it. + + Parameters + ---------- + other: Any + The value or `Feature` to be added. It is passed to `dt.Add` as + the `value` argument. + + Returns + ------- + Feature + A new feature that adds `other` to the output of `self`. + + Examples + -------- + >>> import deeptrack as dt + + Add a constant value to a static input: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature + 5 + >>> result = pipeline() + >>> result + [6, 7, 8] + + This is equivalent to: + >>> pipeline = f >> dt.Add(value=5) + + Add a dynamic feature that samples values at each call: + >>> import numpy as np + >>> + >>> noise = dt.Value(value=lambda: np.random.rand()) + >>> pipeline = feature + noise + >>> result = pipeline.update()() + >>> result + [1.325563919290048, 2.325563919290048, 3.325563919290048] + + This is equivalent to: + >>> pipeline = feature >> dt.Add(value=noise) + """ return self >> Add(other) def __radd__( - self: Feature, + self: Feature, other: Any ) -> Feature: """Adds this feature to another value using right '+'. - + + This operator is the right-hand version of `+`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other + feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.Add(value=feature) + + Internally, this method constructs a `Value` feature from `other` and + chains it into an `Add` feature that adds the current feature as a + dynamic value. + + Parameters + ---------- + other: Any + A constant or `Feature` to which `self` will be added. It is + passed as the input to `Value`. + + Returns + ------- + Feature + A new feature that adds `self` to `other`. + + Examples + -------- + >>> import deeptrack as dt + + Add a feature to a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 5 + feature + >>> result = pipeline() + >>> result + [6, 7, 8] + + This is equivalent to: + >>> pipeline = dt.Value(value=5) >> dt.Add(value=feature) + + Add a feature to a dynamic value: + >>> import numpy as np + >>> + >>> noise = dt.Value(value=lambda: np.random.rand()) + >>> pipeline = noise + feature + >>> result = pipeline.update()() + >>> result + [1.5254613210875014, 2.5254613210875014, 3.5254613210875014] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=lambda: np.random.rand()) + ... >> dt.Add(value=feature) + ... ) + """ return Value(other) >> Add(self) + #TODO **MG** def __sub__( self: Feature, other: Any @@ -1617,6 +1719,7 @@ def __sub__( return self >> Subtract(other) + #TODO **MG** def __rsub__( self: Feature, other: Any @@ -1627,6 +1730,7 @@ def __rsub__( return Value(other) >> Subtract(self) + #TODO **MG** def __mul__( self: Feature, other: Any @@ -1637,6 +1741,7 @@ def __mul__( return self >> Multiply(other) + #TODO **MG** def __rmul__( self: Feature, other: Any @@ -1647,6 +1752,7 @@ def __rmul__( return Value(other) >> Multiply(self) + #TODO **AL** def __truediv__( self: Feature, other: Any @@ -1657,6 +1763,7 @@ def __truediv__( return self >> Divide(other) + #TODO **AL** def __rtruediv__( self: Feature, other: Any @@ -1667,6 +1774,7 @@ def __rtruediv__( return Value(other) >> Divide(self) + #TODO **AL** def __floordiv__( self: Feature, other: Any @@ -1677,6 +1785,7 @@ def __floordiv__( return self >> FloorDivide(other) + #TODO **AL** def __rfloordiv__( self: Feature, other: Any @@ -1687,6 +1796,7 @@ def __rfloordiv__( return Value(other) >> FloorDivide(self) + #TODO **JH** def __pow__( self: Feature, other: Any @@ -1697,6 +1807,7 @@ def __pow__( return self >> Power(other) + #TODO **JH** def __rpow__( self: Feature, other: Any @@ -1707,6 +1818,7 @@ def __rpow__( return Value(other) >> Power(self) + #TODO **JH** def __gt__( self: Feature, other: Any, @@ -1717,6 +1829,7 @@ def __gt__( return self >> GreaterThan(other) + #TODO **JH** def __rgt__( self: Feature, other: Any @@ -1728,6 +1841,7 @@ def __rgt__( return Value(other) >> GreaterThan(self) + #TODO **JH** def __lt__( self: Feature, other: Any @@ -1738,6 +1852,7 @@ def __lt__( return self >> LessThan(other) + #TODO **JH** def __rlt__( self: Feature, other: Any @@ -1748,6 +1863,7 @@ def __rlt__( return Value(other) >> LessThan(self) + #TODO **JH** def __le__( self: Feature, other: Any @@ -1758,6 +1874,7 @@ def __le__( return self >> LessThanOrEquals(other) + #TODO **JH** def __rle__( self: Feature, other: Any @@ -1769,6 +1886,7 @@ def __rle__( return Value(other) >> LessThanOrEquals(self) + #TODO **JH** def __ge__( self: Feature, other: Any @@ -1780,6 +1898,7 @@ def __ge__( return self >> GreaterThanOrEquals(other) + #TODO **JH** def __rge__( self: Feature, other: Any @@ -1791,6 +1910,7 @@ def __rge__( return Value(other) >> GreaterThanOrEquals(self) + #TODO **JH** def __xor__( self: Feature, other: Any, @@ -1801,6 +1921,7 @@ def __xor__( return Repeat(self, other) + #TODO **JH** def __and__( self: Feature, other: Any, @@ -1811,6 +1932,7 @@ def __and__( return self >> Stack(other) + #TODO **JH** def __rand__( self: Feature, other: Any, From c05ca7a147e5c22d9c7c23ca75983f802d315fa2 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 09:10:00 +0200 Subject: [PATCH 105/223] Update features.py --- deeptrack/features.py | 65 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 43e873c6f..c6368a765 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1557,7 +1557,70 @@ def __rshift__( other: Any, ) -> Feature: """Chains this feature with another feature or function using '>>'. - + + This operator enables pipeline-style chaining. The expression: + + >>> feature >> other + + creates a new pipeline where the output of `feature` is passed as + input to `other`. + + If `other` is a `Feature` or `DeepTrackNode`, this returns a + `Chain(feature, other)`. If `other` is a callable (e.g., a function), + it is wrapped using `dt.Lambda(lambda: other)` and chained + similarly. + + If `other` is neither a `DeepTrackNode` nor a callable, the operator + is not implemented and returns `NotImplemented`, which may lead to a + `TypeError` if no matching reverse operator is defined. + + Parameters + ---------- + other: Any + The feature, node, or callable to chain after `self`. + + Returns + ------- + Feature + A new chained feature combining `self` and `other`. + + Raises + ------ + TypeError + If `other` is not a `DeepTrackNode` or callable, the operator + returns `NotImplemented`, which may raise a `TypeError` if no + matching reverse operator is defined. + + Examples + -------- + >>> import deeptrack as dt + + Chain two features: + >>> feature1 = dt.Value(value=[1, 2, 3]) + >>> feature2 = dt.Add(value=1) + >>> pipeline = feature1 >> feature2 + >>> result = pipeline() + >>> result + [2, 3, 4] + + Chain with a callable (e.g., NumPy function): + >>> import numpy as np + >>> + >>> feature = dt.Value(value=np.array([1, 2, 3])) + >>> function = np.mean + >>> pipeline = feature >> function + >>> result = pipeline() + >>> result + 2.0 + + This is equivalent to: + >>> pipeline = feature >> dt.Lambda(lambda: function) + + Attempting to chain with an unsupported object raise a TypeError: + >>> feature >> "invalid" + ... + TypeError: unsupported operand type(s) for >>: 'Value' and 'str' + """ if isinstance(other, DeepTrackNode): From 9b42746c38e19f19520b6b4e7b582ec6e1f04305 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 09:24:01 +0200 Subject: [PATCH 106/223] Update features.py --- deeptrack/features.py | 75 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index c6368a765..db5376de1 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1568,7 +1568,9 @@ def __rshift__( If `other` is a `Feature` or `DeepTrackNode`, this returns a `Chain(feature, other)`. If `other` is a callable (e.g., a function), it is wrapped using `dt.Lambda(lambda: other)` and chained - similarly. + similarly. The lambda returns the function itself, which is then + automatically called with the upstream feature’s output during + evaluation. If `other` is neither a `DeepTrackNode` nor a callable, the operator is not implemented and returns `NotImplemented`, which may lead to a @@ -1616,7 +1618,10 @@ def __rshift__( This is equivalent to: >>> pipeline = feature >> dt.Lambda(lambda: function) - Attempting to chain with an unsupported object raise a TypeError: + The lambda returns the function object. During evaluation, DeepTrack + internally calls that function with the resolved output of `feature`. + + Attempting to chain with an unsupported object raises a TypeError: >>> feature >> "invalid" ... TypeError: unsupported operand type(s) for >>: 'Value' and 'str' @@ -1634,12 +1639,74 @@ def __rshift__( # The operator is not implemented for other inputs. return NotImplemented + #TODO **BM** TBE? note that it's never actually accessed def __rrshift__( self: Feature, other: Any, ) -> Feature: - """Chains another feature or function with this feature using '<<'. - + """Chains another feature or value with this feature using '>>'. + + This operator supports chaining when the `Feature` appears on the + right-hand side of a pipeline. The expression: + + >>> other >> feature + + triggers `feature.__rrshift__(other)` if `other` does not implement + `__rshift__`, or if its implementation returns `NotImplemented`. + + If `other` is a `Feature`, this is equivalent to: + + >>> dt.Chain(other, feature) + + If `other` is a raw value (e.g., a list or array), it is wrapped using + `dt.Value(value=other)` before chaining: + + >>> dt.Chain(dt.Value(value=other), feature) + + Parameters + ---------- + other: Any + The value or feature to be evaluated before this feature. + + Returns + ------- + Feature + A new chained feature where `other` is evaluated first. + + Raises + ------ + TypeError + If `other` is not a supported type, this method returns + `NotImplemented`, which may raise a `TypeError` if no matching + forward operator is defined. + + Examples + -------- + >>> import deeptrack as dt + + Chain a constant value into a feature: + >>> feature = dt.Add(value=1) + >>> pipeline = [1, 2, 3] >> feature + >>> result = pipeline() + >>> result + [2, 3, 4] + + This is equivalent to: + >>> pipeline = dt.Value(value=[1, 2, 3]) >> feature + + Chain two features (normally handled by __rshift__): + >>> feature1 = dt.Value(value=[1, 2, 3]) + >>> feature2 = dt.Add(value=1) + >>> pipeline = feature1 >> feature2 + >>> result = pipeline() + >>> result + [2, 3, 4] + + Attempting to chain an unsupported object raises a TypeError: + >>> object() >> feature + ... + TypeError: unsupported operand type(s) for >>: 'object' and 'Add' + """ if isinstance(other, Feature): From bb709db4074e9a4382a2a077c08768e7fd25d095 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 10:03:29 +0200 Subject: [PATCH 107/223] Update features.py --- deeptrack/features.py | 53 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index db5376de1..6bc3e613d 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1639,7 +1639,6 @@ def __rshift__( # The operator is not implemented for other inputs. return NotImplemented - #TODO **BM** TBE? note that it's never actually accessed def __rrshift__( self: Feature, other: Any, @@ -1680,32 +1679,40 @@ def __rrshift__( `NotImplemented`, which may raise a `TypeError` if no matching forward operator is defined. - Examples - -------- - >>> import deeptrack as dt + Notes + ----- + This method enables chaining where a `Feature` appears on the + right-hand side of the `>>` operator. It is triggered when the + left-hand operand does not implement `__rshift__`, or when its + implementation returns `NotImplemented`. - Chain a constant value into a feature: - >>> feature = dt.Add(value=1) - >>> pipeline = [1, 2, 3] >> feature - >>> result = pipeline() - >>> result - [2, 3, 4] + This is particularly useful when chaining two `Feature` instances or + when the left-hand operand is a custom class designed to delegate + chaining behavior. For example: - This is equivalent to: - >>> pipeline = dt.Value(value=[1, 2, 3]) >> feature + >>> pipeline = dt.Value(value=[1, 2, 3]) >> dt.Add(value=1) - Chain two features (normally handled by __rshift__): - >>> feature1 = dt.Value(value=[1, 2, 3]) - >>> feature2 = dt.Add(value=1) - >>> pipeline = feature1 >> feature2 - >>> result = pipeline() - >>> result - [2, 3, 4] + In this case, if `dt.Value` does not handle `__rshift__`, Python will + fall back to calling `Add.__rrshift__(...)`, which constructs the + chain. - Attempting to chain an unsupported object raises a TypeError: - >>> object() >> feature - ... - TypeError: unsupported operand type(s) for >>: 'object' and 'Add' + However, this mechanism does **not** apply to built-in types like + `int`, `float`, or `list`. Due to limitations in Python's operator + overloading, expressions like: + + >>> 1 >> dt.Add(value=1) + >>> [1, 2, 3] >> dt.Add(value=1) + + will raise `TypeError`, because Python does not delegate to the + right-hand operand’s `__rrshift__` method for built-in types. + + To chain a raw value into a feature, wrap it explicitly using + `dt.Value`: + + >>> dt.Value(1) >> dt.Add(value=1) + + This is functionally equivalent and avoids the need for fallback + behavior. """ From d5920c86c4c260fffe5ea10d4a417235d2788221 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 10:20:53 +0200 Subject: [PATCH 108/223] Update features.py --- deeptrack/features.py | 130 +++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 6bc3e613d..7ea07a5ed 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -349,10 +349,12 @@ class Feature(DeepTrackNode): Sets the random seed for the feature, ensuring deterministic behavior. `bind_arguments(arguments: Feature) -> Feature` Binds another feature’s properties as arguments to this feature. - `_normalize(**properties: dict[str, Any]) -> dict[str, Any]` - Normalizes the properties of the feature. `plot(input_image: np.ndarray | list[np.ndarray] | Image | list[Image] | None = None, resolve_kwargs: dict | None = None, interval: float | None = None, **kwargs) -> Any` Visualizes the output of the feature. + + **Private and internal methods.** + `_normalize(**properties: dict[str, Any]) -> dict[str, Any]` + Normalizes the properties of the feature. `_process_properties(propertydict: dict[str, Any]) -> dict[str, Any]` Preprocesses the input properties before calling the `get` method. `_activate_sources(x: Any) -> None` @@ -364,57 +366,57 @@ class Feature(DeepTrackNode): `__next__() -> Any` Returns the next element in the feature. `__rshift__(other: Any) -> Feature` - Allows chaining of features. + It allows chaining of features. `__rrshift__(other: Any) -> Feature` - Allows right chaining of features. + It allows right chaining of features. `__add__(other: Any) -> Feature` - Overrides add operator. + It overrides add operator. `__radd__(other: Any) -> Feature` - Overrides right add operator. + It overrides right add operator. `__sub__(other: Any) -> Feature` - Overrides subtraction operator. + It overrides subtraction operator. `__rsub__(other: Any) -> Feature` - Overrides right subtraction operator. + It overrides right subtraction operator. `__mul__(other: Any) -> Feature` - Overrides multiplication operator. + It overrides multiplication operator. `__rmul__(other: Any) -> Feature` - Overrides right multiplication operator. + It overrides right multiplication operator. `__truediv__(other: Any) -> Feature` - Overrides division operator. + It overrides division operator. `__rtruediv__(other: Any) -> Feature` - Overrides right division operator. + It overrides right division operator. `__floordiv__(other: Any) -> Feature` - Overrides floor division operator. + It overrides floor division operator. `__rfloordiv__(other: Any) -> Feature` - Overrides right floor division operator. + It overrides right floor division operator. `__pow__(other: Any) -> Feature` - Overrides power operator. + It overrides power operator. `__rpow__(other: Any) -> Feature` - Overrides right power operator. + It overrides right power operator. `__gt__(other: Any) -> Feature` - Overrides greater than operator. + It overrides greater than operator. `__rgt__(other: Any) -> Feature` - Overrides right greater than operator. + It overrides right greater than operator. `__lt__(other: Any) -> Feature` - Overrides less than operator. + It overrides less than operator. `__rlt__(other: Any) -> Feature` - Overrides right less than operator. + It overrides right less than operator. `__le__(other: Any) -> Feature` - Overrides less than or equal to operator. + It overrides less than or equal to operator. `__rle__(other: Any) -> Feature` - Overrides right less than or equal to operator. + It overrides right less than or equal to operator. `__ge__(other: Any) -> Feature` - Overrides greater than or equal to operator. + It overrides greater than or equal to operator. `__rge__(other: Any) -> Feature` - Overrides right greater than or equal to operator. + It overrides right greater than or equal to operator. `__xor__(other: Any) -> Feature` - Overrides XOR operator. + It overrides XOR operator. `__and__(other: Feature) -> Feature` - Overrides AND operator. + It overrides AND operator. `__rand__(other: Feature) -> Feature` - Overrides right AND operator. + It overrides right AND operator. `__getitem__(key: Any) -> Feature` - Allows direct slicing of the data. + It allows direct slicing of the data. `_format_input(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` Formats the input data for the feature. `_process_and_get(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` @@ -1271,42 +1273,6 @@ def bind_arguments( return self - def _normalize( - self: Feature, - **properties: dict[str, Any], - ) -> dict[str, Any]: - """Normalize the properties. - - This method handles all unit normalizations and conversions. For each - class in the method resolution order (MRO), it checks if the class has - a `__conversion_table__` attribute. If found, it calls the `convert` - method of the conversion table using the properties as arguments. - - Parameters - ---------- - **properties: dict[str, Any] - The properties to be normalized and converted. - - Returns - ------- - dict[str, Any] - The normalized and converted properties. - - Examples - -------- - TODO - - """ - - for cl in type(self).mro(): - if hasattr(cl, "__conversion_table__"): - properties = cl.__conversion_table__.convert(**properties) - - for key, val in properties.items(): - if isinstance(val, Quantity): - properties[key] = val.magnitude - return properties - def plot( self: Feature, input_image: np.ndarray | list[np.ndarray] | Image | list[Image] = None, @@ -1400,6 +1366,42 @@ def plotter(frame=0): ), ) + def _normalize( + self: Feature, + **properties: dict[str, Any], + ) -> dict[str, Any]: + """Normalize the properties. + + This method handles all unit normalizations and conversions. For each + class in the method resolution order (MRO), it checks if the class has + a `__conversion_table__` attribute. If found, it calls the `convert` + method of the conversion table using the properties as arguments. + + Parameters + ---------- + **properties: dict[str, Any] + The properties to be normalized and converted. + + Returns + ------- + dict[str, Any] + The normalized and converted properties. + + Examples + -------- + TODO + + """ + + for cl in type(self).mro(): + if hasattr(cl, "__conversion_table__"): + properties = cl.__conversion_table__.convert(**properties) + + for key, val in properties.items(): + if isinstance(val, Quantity): + properties[key] = val.magnitude + return properties + def _process_properties( self: Feature, propertydict: dict[str, Any], From d28a82991729087aab1bdd470d74bfc9d127a37e Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 10:28:21 +0200 Subject: [PATCH 109/223] Update features.py --- deeptrack/features.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 7ea07a5ed..4aff2f321 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -149,7 +149,7 @@ def merge_features( import torch __all__ = [ - "Feature", # TODO + "Feature", # TODO **GV** "StructuralFeature", "Chain", "Branch", @@ -1161,6 +1161,7 @@ def action( else: return image_list + # **GV** def update( self: Feature, **global_arguments: Any, @@ -1198,6 +1199,7 @@ def update( return self + # **GV** def add_feature( self: Feature, feature: Feature, @@ -1221,6 +1223,7 @@ def add_feature( return feature + # **GV** def seed( self: Feature, _ID: tuple[int, ...] = (), @@ -1236,6 +1239,7 @@ def seed( np.random.seed(self._random_seed(_ID=_ID)) + # **GV** def bind_arguments( self: Feature, arguments: Feature, @@ -1273,6 +1277,7 @@ def bind_arguments( return self + # **GV** def plot( self: Feature, input_image: np.ndarray | list[np.ndarray] | Image | list[Image] = None, @@ -1366,6 +1371,7 @@ def plotter(frame=0): ), ) + # **GV** def _normalize( self: Feature, **properties: dict[str, Any], @@ -1402,6 +1408,7 @@ class in the method resolution order (MRO), it checks if the class has properties[key] = val.magnitude return properties + # **GV** def _process_properties( self: Feature, propertydict: dict[str, Any], @@ -1438,6 +1445,7 @@ def _process_properties( return propertydict + # **GV** def _activate_sources( self: Feature, x: Any, @@ -1470,6 +1478,7 @@ def _activate_sources( if isinstance(source, SourceItem): source() + # **GV** def __getattr__( self: Feature, key: str, @@ -1534,6 +1543,7 @@ def __getattr__( raise AttributeError(f"'{self.__class__.__name__}' object has " "no attribute '{key}'") + # **GV** def __iter__( self: Feature, ) -> Iterable: @@ -1545,6 +1555,7 @@ def __iter__( while True: yield from next(self) + # **GV** def __next__( self: Feature, ) -> Any: @@ -2082,6 +2093,7 @@ def __rand__( return Value(other) >> Stack(self) + # **GV** def __getitem__( self: Feature, slices: Any, From 50491da873fedac10ab58e0e2d3eadcc249abbac Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 14:32:05 +0200 Subject: [PATCH 110/223] Update features.py --- deeptrack/features.py | 74 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 12 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 4aff2f321..59c610e05 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -361,10 +361,10 @@ class Feature(DeepTrackNode): Activates sources in the input data. `__getattr__(key: str) -> Any` Custom attribute access for the Feature class. - `__iter__() -> Iterable` - Iterates over the feature. - `__next__() -> Any` - Returns the next element in the feature. + `__iter__() -> Any` + It return the next element iterating over the feature. + `__next__() -> Iterable` + It returns an iterator for the feature. `__rshift__(other: Any) -> Feature` It allows chaining of features. `__rrshift__(other: Any) -> Feature` @@ -1543,24 +1543,74 @@ def __getattr__( raise AttributeError(f"'{self.__class__.__name__}' object has " "no attribute '{key}'") - # **GV** def __iter__( self: Feature, - ) -> Iterable: - """ Returns an infinite iterator that continuously yields feature - values. + ) -> Any: + """ Return infinite iterator that continuously yields feature values. + + This method allows a `Feature` instance to be used in a `for` loop or + as a generator. It repeatedly calls `__next__()` to resolve and yield + new values. + + Each iteration generates a fresh output by calling + `self.update().resolve()`, meaning it resamples all properties before + evaluation. + + Returns + ------- + Any + A newly generated output from the feature. + + Examples + -------- + >>> import deeptrack as dt + + Create a feature: + >>> import numpy as np + >>> + >>> feature = dt.Value(value=lambda: np.random.rand()) + + Use the feature in a loop: + >>> for i, sample in enumerate(feature): + ... print(sample) + ... if i == 2: + ... break + 0.43126475134786546 + 0.3270413736199965 + 0.6734339603677173 """ while True: yield from next(self) - # **GV** def __next__( self: Feature, - ) -> Any: - """Returns the next resolved feature in the sequence. - + ) -> Iterator: + """Return the next resolved feature in the sequence. + + This method is called by `next(feature)` and yields one new sample by + first resampling all properties via `update()` and then evaluating the + feature using `resolve()`. + + Returns + ------- + Iterable + An infinite generator that yields evaluated feature outputs. + + Examples + -------- + >>> import deeptrack as dt + + Create a feature: + >>> import numpy as np + >>> + >>> feature = dt.Value(value=lambda: np.random.rand()) + + Get a single sample multiple times: + >>> for _ in enumerate(3): + ... next(feature) + """ yield self.update().resolve() From a96969237a41e8149334ab65bc879b06812383f2 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 15:00:01 +0200 Subject: [PATCH 111/223] Update features.py --- deeptrack/features.py | 128 ++++++++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 31 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 59c610e05..1dc45d278 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -361,10 +361,10 @@ class Feature(DeepTrackNode): Activates sources in the input data. `__getattr__(key: str) -> Any` Custom attribute access for the Feature class. - `__iter__() -> Any` - It return the next element iterating over the feature. - `__next__() -> Iterable` + `__iter__() -> Feature` It returns an iterator for the feature. + `__next__() -> Any` + It return the next element iterating over the feature. `__rshift__(other: Any) -> Feature` It allows chaining of features. `__rrshift__(other: Any) -> Feature` @@ -1545,35 +1545,31 @@ def __getattr__( def __iter__( self: Feature, - ) -> Any: - """ Return infinite iterator that continuously yields feature values. - - This method allows a `Feature` instance to be used in a `for` loop or - as a generator. It repeatedly calls `__next__()` to resolve and yield - new values. + ) -> Feature: + """Return self as an iterator over feature values. - Each iteration generates a fresh output by calling - `self.update().resolve()`, meaning it resamples all properties before - evaluation. + This makes the `Feature` object compatible with Python's iterator + protocol. Each call to `next(feature)` generates a new output by + resampling its properties and resolving the pipeline. Returns ------- - Any - A newly generated output from the feature. + Feature + Returns self, which defines `__next__()` to yield outputs. Examples -------- >>> import deeptrack as dt - Create a feature: + Create feature: >>> import numpy as np >>> >>> feature = dt.Value(value=lambda: np.random.rand()) Use the feature in a loop: - >>> for i, sample in enumerate(feature): + >>> for sample in feature: ... print(sample) - ... if i == 2: + ... if sample > 0.5: ... break 0.43126475134786546 0.3270413736199965 @@ -1581,22 +1577,28 @@ def __iter__( """ - while True: - yield from next(self) + return self + + #TODO **BM** TBE? Previous implementation, not standard in Python + # while True: + # yield from next(self) def __next__( self: Feature, - ) -> Iterator: + ) -> Any: """Return the next resolved feature in the sequence. - This method is called by `next(feature)` and yields one new sample by - first resampling all properties via `update()` and then evaluating the - feature using `resolve()`. + This method allows a `Feature` to be used as an iterator that yields + a new result at each step. It is called automatically by `next(feature)` + or when used in iteration. + + Each call to `__next__()` triggers a resampling of all properties and + evaluation of the pipeline using `self.update().resolve()`. Returns ------- - Iterable - An infinite generator that yields evaluated feature outputs. + Any + A newly generated output from the feature. Examples -------- @@ -1607,13 +1609,16 @@ def __next__( >>> >>> feature = dt.Value(value=lambda: np.random.rand()) - Get a single sample multiple times: - >>> for _ in enumerate(3): - ... next(feature) + Get a single sample: + >>> next(feature) + 0.41251758103924216 """ - yield self.update().resolve() + return self.update().resolve() + + #TODO **BM** TBE? Previous implementation, not standard in Python + # yield self.update().resolve() def __rshift__( self: Feature, @@ -2143,13 +2148,74 @@ def __rand__( return Value(other) >> Stack(self) - # **GV** def __getitem__( self: Feature, slices: Any, - ) -> 'Feature': + ) -> Feature: """Allows direct slicing of the feature's output. + This operator enables syntax like: + + >>> feature[:, 0] + + to extract a slice from the output of the feature, just as you would + with a NumPy array or PyTorch tensor. + + Internally, this is equivalent to chaining with `dt.Slice`, and the + expression: + + >>> feature[slices] + + is equivalent to: + + >>> feature >> dt.Slice(slices) + + If the slice is not already a tuple (i.e., a single index or slice), + it is wrapped in one. The resulting tuple is converted to a list to + allow sampling of dynamic slices at runtime. + + Parameters + ---------- + slices: Any + The slice or index to apply to the feature output. Can be an int, + slice object, or a tuple of them. + + Returns + ------- + Feature + A new feature that applies slicing to the output of the current + feature. + + Examples + -------- + >>> import deeptrack as dt + + Create a feature: + >>> import numpy as np + >>> + >>> feature = dt.Value(value=np.arange(9).reshape(3, 3)) + >>> feature() + array([[0, 1, 2], + [3, 4, 5], + [6, 7, 8]]) + + Slice a row: + >>> sliced = feature[1] + >>> sliced() + array([3, 4, 5]) + + This is equivalent to: + >>> sliced = feature >> dt.Slice([1]) + + Slice with multiple axes: + >>> sliced = feature[1:, 1:] + >>> sliced() + array([[4, 5], + [7, 8]]) + + This is equivalent to: + >>> sliced = feature >> dt.Slice([slice(1, None), slice(1, None)]) + """ if not isinstance(slices, tuple): From 13205239809458cfeba00fc1a44cc21be98fb0bc Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 2 Jul 2025 15:09:09 +0200 Subject: [PATCH 112/223] Update features.py --- deeptrack/features.py | 102 +++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 1dc45d278..72de58575 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -417,24 +417,26 @@ class Feature(DeepTrackNode): It overrides right AND operator. `__getitem__(key: Any) -> Feature` It allows direct slicing of the data. - `_format_input(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` - Formats the input data for the feature. - `_process_and_get(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` - Calls the `get` method according to the `__distributed__` attribute. - `_process_output(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> None` - Processes the output of the feature. + `_format_input(image_list: Any, **kwargs: Any) -> list[Any or Image]` + It formats the input data for the feature. + `_process_and_get(image_list: Any, **kwargs: Any) -> list[Any or Image]` + It calls the `get` method according to the `__distributed__` attribute. + `_process_output(image_list: Any, **kwargs: Any) -> None` + It processes the output of the feature. `_image_wrapped_format_input(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` - Ensures the input is a list of Image. - `_no_wrap_format_input(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` - Ensures the input is a list of Image. + It ensures the input is a list of Image. + `_no_wrap_format_input(image_list: Any, **kwargs: Any) -> list[Any]` + It ensures the input is a list of Image. `_image_wrapped_process_and_get(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` - Calls the `get` method according to the `__distributed__` attribute. - `_no_wrap_process_and_get(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> list[Image]` - Calls the `get` method according to the `__distributed__` attribute. + It calls the `get()` method according to the `__distributed__` + attribute. + `_no_wrap_process_and_get(image_list: Any | list[Any], **kwargs: Any) -> list[Any]` + It calls the `get()` method according to the `__distributed__` + attribute. `_image_wrapped_process_output(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> None` - Processes the output of the feature. - `_no_wrap_process_output(image_list: np.ndarray | list[np.ndarray] | Image | list[Image], **kwargs: Any) -> None` - Processes the output of the feature. + It processes the output of the feature. + `_no_wrap_process_output(image_list: Any | list[Any], **kwargs: Any) -> None` + It processes the output of the feature. Examples -------- @@ -1328,48 +1330,46 @@ def plot( plt.imshow(output_image, **kwargs) return plt.gca() - else: - # Assume video - fig = plt.figure() - images = [] - plt.axis("off") - for image in output_image: - images.append([plt.imshow(image, **kwargs)]) - + # Assume video + fig = plt.figure() + images = [] + plt.axis("off") + for image in output_image: + images.append([plt.imshow(image, **kwargs)]) - if not interval: - if isinstance(output_image[0], Image): - interval = output_image[0].get_property("interval") or (1 / 30 * 1000) - else: - interval = (1 / 30 * 1000) + if not interval: + if isinstance(output_image[0], Image): + interval = output_image[0].get_property("interval") or (1 / 30 * 1000) + else: + interval = 1 / 30 * 1000 - anim = animation.ArtistAnimation( - fig, images, interval=interval, blit=True, repeat_delay=0 - ) + anim = animation.ArtistAnimation( + fig, images, interval=interval, blit=True, repeat_delay=0 + ) - try: - get_ipython # Throws NameError if not in Notebook - display(HTML(anim.to_jshtml())) - return anim + try: + get_ipython # Throws NameError if not in Notebook + display(HTML(anim.to_jshtml())) + return anim - except NameError: - # Not in an notebook - plt.show() + except NameError: + # Not in an notebook + plt.show() - except RuntimeError: - # In notebook, but animation failed - import ipywidgets as widgets + except RuntimeError: + # In notebook, but animation failed + import ipywidgets as widgets - def plotter(frame=0): - plt.imshow(output_image[frame][:, :, 0], **kwargs) - plt.show() + def plotter(frame=0): + plt.imshow(output_image[frame][:, :, 0], **kwargs) + plt.show() - return widgets.interact( - plotter, - frame=widgets.IntSlider( - value=0, min=0, max=len(images) - 1, step=1 - ), - ) + return widgets.interact( + plotter, + frame=widgets.IntSlider( + value=0, min=0, max=len(images) - 1, step=1 + ), + ) # **GV** def _normalize( @@ -2158,7 +2158,7 @@ def __getitem__( >>> feature[:, 0] - to extract a slice from the output of the feature, just as you would + to extract a slice from the output of the feature, just as one would with a NumPy array or PyTorch tensor. Internally, this is equivalent to chaining with `dt.Slice`, and the From adaa4262ef31e15de32269a82ee9202edb481c5d Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 3 Jul 2025 09:13:50 +0200 Subject: [PATCH 113/223] Update features.py --- deeptrack/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 72de58575..456ced4de 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -137,7 +137,7 @@ def merge_features( from scipy.spatial.distance import cdist from deeptrack import units -from deeptrack.backend import config, Config, TORCH_AVAILABLE, xp +from deeptrack.backend import config, TORCH_AVAILABLE, xp from deeptrack.backend.core import DeepTrackNode from deeptrack.backend.units import ConversionTable, create_context from deeptrack.image import Image @@ -459,7 +459,7 @@ class Feature(DeepTrackNode): _int_dtype: str _complex_dtype: str _device: str | torch.device - _backend: Config + _backend: Literal["numpy", "torch"] @property def float_dtype(self) -> np.dtype | torch.dtype: From 7d2379feb173ffae4865dc1f78fecebcdb82192c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 3 Jul 2025 09:13:52 +0200 Subject: [PATCH 114/223] Update _config.py --- deeptrack/backend/_config.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deeptrack/backend/_config.py b/deeptrack/backend/_config.py index 3441e358b..4a578f149 100644 --- a/deeptrack/backend/_config.py +++ b/deeptrack/backend/_config.py @@ -144,7 +144,6 @@ __all__ = [ "config", - "Config", "OPENCV_AVAILABLE", "TORCH_AVAILABLE", "xp", From 2c0edc61dc42bd8d5814257d3c5c8112459bd91c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 3 Jul 2025 15:55:03 +0200 Subject: [PATCH 115/223] Update features.py --- deeptrack/features.py | 98 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 456ced4de..7ff49c087 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -345,7 +345,7 @@ class Feature(DeepTrackNode): Refreshes the feature to create a new image. `add_feature(feature: Feature) -> Feature` Adds a feature to the dependency graph of this one. - `seed(_ID: tuple[int, ...] = ()) -> None` + `seed(updated_seed: int, _ID: tuple[int, ...]) -> int` Sets the random seed for the feature, ensuring deterministic behavior. `bind_arguments(arguments: Feature) -> Feature` Binds another feature’s properties as arguments to this feature. @@ -1228,18 +1228,106 @@ def add_feature( # **GV** def seed( self: Feature, + updated_seed: int | None = None, _ID: tuple[int, ...] = (), - ) -> None: - """Seed the random number generator. + ) -> int: + """Seed all random number generators for reproducibility. + + This method sets the global random seed for Python's `random` module, + NumPy, and (if available) PyTorch. If `updated_seed` is provided, it + replaces the value of the internal `_random_seed` node before + resolution. + + This method sets the following: + - `random.seed(seed)` for Python's RNG + - `np.random.seed(seed)` for NumPy + - `torch.manual_seed(seed)` and `torch.cuda.manual_seed_all(seed)` + + The same seed will lead to deterministic behavior within each backend + (e.g., `random`, NumPy or PyTorch), but not **across** them. NumPy and + PyTorch use different RNG algorithms, so identical seeds will not + generate the same random numbers across backends. Parameters ---------- + updated_seed: int or None, optional + If provided, sets a fixed value for the internal `_random_seed`. _ID: tuple[int, ...], optional - Unique identifier for parallel evaluations. + Unique identifier used to resolve the seed value. It defaults to + `()`. + + Returns + ------- + int + The resolved seed value used for all RNGs. + + Examples + -------- + >>> import deeptrack as dt + + **Using `random`** + Define a feature that samples a random integer from 0 to 10 using the + Python standard library's `random` module: + >>> import random + >>> + >>> feature = dt.Value(lambda: random.randint(0, 10)) + >>> + >>> for _ in range(3): + ... print(f"output={feature.update()()} seed={feature.seed()}") + output=3 seed=355549663 + output=5 seed=119234165 + output=9 seed=1956541335 + + Each time `.update()` is called, the internal `_random_seed` is + re-sampled and used to reseed the Python `random` module. This + produces a new deterministic seed, but different output values. + + Fix the seed to reuse it later for reproducibility: + >>> seed = feature.seed() + >>> seed + 1956541335 + + Now reseed the feature with the same value before each update, + to make the output deterministic and repeatable. + >>> for _ in range(3): + ... feature.seed(seed) + ... print(f"output={feature.update()()} seed={feature.seed()}") + output=5 seed=1933964715 + output=5 seed=1933964715 + output=5 seed=1933964715 + + Since the random seed is fixed before each sample, the output is + the same every time. Note: the seed reported after sampling may + differ if it's re-sampled internally, but the output remains stable. + + **Using NumPy** + Similar observations can be made with NumPy: + >>> import numpy as np + >>> + >>> feature = dt.Value(lambda: np.random.randint(0, 10)) + + **Using PyTorch** + Similar observations can be made with PyTorch: + >>> import torch + >>> + >>> feature = dt.Value(lambda: torch.randint(0, 10, (1,)).item()) """ - np.random.seed(self._random_seed(_ID=_ID)) + if updated_seed: + self._random_seed.set_value(updated_seed) + + seed = self._random_seed(_ID=_ID) + + random.seed(seed) + np.random.seed(seed) + + if TORCH_AVAILABLE: + torch.manual_seed(seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) + + return seed # **GV** def bind_arguments( From 38319bf5bfd4b0d0b57604c560855283d23821e9 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 3 Jul 2025 15:55:22 +0200 Subject: [PATCH 116/223] Update features.py --- deeptrack/features.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 7ff49c087..344d919eb 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1225,7 +1225,6 @@ def add_feature( return feature - # **GV** def seed( self: Feature, updated_seed: int | None = None, From edb5c5bac16c13219106e2788800ea52b7136544 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 4 Jul 2025 10:08:18 +0200 Subject: [PATCH 117/223] Update features.py --- deeptrack/features.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 344d919eb..b45f2c7b3 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1225,7 +1225,7 @@ def add_feature( return feature - def seed( + def seed( self: Feature, updated_seed: int | None = None, _ID: tuple[int, ...] = (), @@ -1899,8 +1899,8 @@ def __add__( Parameters ---------- other: Any - The value or `Feature` to be added. It is passed to `dt.Add` as - the `value` argument. + The value or `Feature` to be added. It is passed to `Add` as the + `value` argument. Returns ------- From 91633c676c553d27792da0b1587583c1b1f33bb9 Mon Sep 17 00:00:00 2001 From: Mirja Granfors <95694095+mirjagranfors@users.noreply.github.com> Date: Fri, 4 Jul 2025 10:14:23 +0200 Subject: [PATCH 118/223] Updated the documentation of features/__sub__ (#356) * updated documentation of features/__sub__ * minor changes * remove blank --- deeptrack/features.py | 56 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index b45f2c7b3..9c219090f 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2000,12 +2000,60 @@ def __radd__( return Value(other) >> Add(self) - #TODO **MG** def __sub__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Subtracts another value or feature using '-'. + """Subtract another value or feature using '-'. + + This operator is shorthand for chaining with `Subtract`. + The expression: + + >>> feature - other + + is equivalent to: + + >>> feature >> dt.Subtract(value=other) + + Internally, this method constructs a new `Subtract` feature and uses + the right-shift operator (`>>`) to chain the current feature into it. + + Parameters + ---------- + other: Any + The value or `Feature` to be subtracted. It is passed to + `Subtract` as the `value` argument. + + Returns + ------- + Feature + A new feature that subtracts `other` from the output of `self`. + + Examples + -------- + >>> import deeptrack as dt + + Subtract a constant value from a static input: + >>> feature = dt.Value(value=[5, 6, 7]) + >>> pipeline = feature - 2 + >>> result = pipeline() + >>> result + [3, 4, 5] + + This is equivalent to: + >>> pipeline = feature >> dt.Subtract(value=2) + + Subtract a dynamic feature that samples a value at each call: + >>> import numpy as np + >>> + >>> noise = dt.Value(value=lambda: np.random.rand()) + >>> pipeline = feature - noise + >>> result = pipeline.update()() + >>> result + [4.524072925059197, 5.524072925059197, 6.524072925059197] + + This is equivalent to: + >>> pipeline = feature >> dt.Subtract(value=noise) """ From 9c7e4c48a0b89810b3ec5b544b34a46a56888f0b Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 4 Jul 2025 10:18:19 +0200 Subject: [PATCH 119/223] Update documentation of features/__pow__ (#359) * Update documentation of features/__pow__ * Update * u --------- Co-authored-by: Jiacheng Huang --- deeptrack/features.py | 57 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 9c219090f..ae9645046 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1919,7 +1919,7 @@ def __add__( [6, 7, 8] This is equivalent to: - >>> pipeline = f >> dt.Add(value=5) + >>> pipeline = feature >> dt.Add(value=5) Add a dynamic feature that samples values at each call: >>> import numpy as np @@ -2136,13 +2136,60 @@ def __rfloordiv__( return Value(other) >> FloorDivide(self) - #TODO **JH** def __pow__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Raises this feature to a power using '**'. + """Raise this feature to a power using '**'. + + This operator is shorthand for chaining with `Power`. The expression: + + >>> feature ** other + + is equivalent to: + + >>> feature >> dt.Power(value=other) + + Internally, this method constructs a new `Power` feature and uses the + right-shift operator (`>>`) to chain the current feature into it. + + Parameters + ---------- + other: Any + The value or `Feature` representing the exponent. It is passed to `Power` + as the `value` argument. + + Returns + ------- + Feature + A new feature representing `self` to the power of `other`. + + Examples + -------- + >>> import deeptrack as dt + + Raise a static base to a constant exponent: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature ** 3 + >>> result = pipeline() + >>> result + [1, 8, 27] + + This is equivalent to: + >>> pipeline = feature >> dt.Power(value=3) + Raise to a dynamic exponent that samples values at each call: + >>> import numpy as np + >>> + >>> noise = dt.Value(value=lambda: np.random.randint(10)) + >>> pipeline = feature ** noise + >>> result = pipeline.update()() + >>> result + [1, 64, 729] + + This is equivalent to: + >>> pipeline = feature >> dt.Power(value=noise) + """ return self >> Power(other) From c6934daf022706f9d6e38acb16d8a53495eb48b9 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 4 Jul 2025 11:08:52 +0200 Subject: [PATCH 120/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index ae9645046..cc16307c6 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -314,7 +314,7 @@ class Feature(DeepTrackNode): The data type of the boolean numbers. device: str or torch.device The device on which the feature is executed. - _backend: Config + _backend: "numpy" or "torch" The computational backend. Methods From 673b98c6200c1e2fbb7512e70cf9b8d74f0c76e9 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 4 Jul 2025 11:23:28 +0200 Subject: [PATCH 121/223] Update features.py --- deeptrack/features.py | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index cc16307c6..1b6f18b83 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -344,7 +344,7 @@ class Feature(DeepTrackNode): `update(**global_arguments: Any) -> Feature` Refreshes the feature to create a new image. `add_feature(feature: Feature) -> Feature` - Adds a feature to the dependency graph of this one. + It adds a feature to the dependency graph of this one. `seed(updated_seed: int, _ID: tuple[int, ...]) -> int` Sets the random seed for the feature, ensuring deterministic behavior. `bind_arguments(arguments: Feature) -> Feature` @@ -1201,12 +1201,22 @@ def update( return self - # **GV** def add_feature( self: Feature, feature: Feature, ) -> Feature: - """Adds a feature to the dependecy graph of this one. + """Add a feature to the dependecy graph of this one. + + This method establishes a dependency relationship by registering the + provided `feature` as a child node of the current feature. This ensures + that its evaluation and property resolution are included in the current + feature’s computation graph. + + Internally, it calls `feature.add_child(self)`, which automatically + handles graph integration and triggers recomputation if necessary. + + This is often used to define explicit data dependencies or to ensure + side-effect features are computed when this feature is resolved. Parameters ---------- @@ -1218,6 +1228,31 @@ def add_feature( Feature The newly added feature (for chaining). + Examples + -------- + >>> import deeptrack as dt + + Define the main feature that adds a constant to the input: + >>> feature = dt.Add(value=2) + + Define a side-effect feature: + >>> dependency = dt.Value(value=42) + + Register the dependency so its state becomes part of the graph: + >>> feature.add_feature(dependency) + + Execute the main feature on an input array: + >>> import numpy as np + >>> + >>> result = feature(np.array([1, 2, 3])) + >>> result + array([3, 4, 5]) + + Note that the `dependency` does not affect the result directly, but it + will be tracked and updated as part of the pipeline's evaluation graph. + This can be useful if the dependency affects any parameters of the main + feature. + """ feature.add_child(self) From 263bd577dab44d340eba244cb4b2dd26d447a149 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 4 Jul 2025 17:55:41 +0200 Subject: [PATCH 122/223] Update features.py --- deeptrack/features.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 1b6f18b83..48813e182 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1530,7 +1530,6 @@ class in the method resolution order (MRO), it checks if the class has properties[key] = val.magnitude return properties - # **GV** def _process_properties( self: Feature, propertydict: dict[str, Any], @@ -1600,7 +1599,6 @@ def _activate_sources( if isinstance(source, SourceItem): source() - # **GV** def __getattr__( self: Feature, key: str, @@ -1642,7 +1640,7 @@ def __getattr__( Examples -------- - >>> import deptrack as dt + >>> import deeptrack as dt Accessing an attribute as if it were a property: >>> feature = dt.DummyFeature(value=42) @@ -1652,6 +1650,7 @@ def __getattr__( If the `properties` attribute is not defined for the instance or if the `key` does not exist in `properties`, an `AttributeError` is raised: >>> feature.nonexistent_property + ... AttributeError: 'MyFeature' object has no attribute 'nonexistent_property' @@ -1662,8 +1661,9 @@ def __getattr__( if key in properties: return properties[key] - raise AttributeError(f"'{self.__class__.__name__}' object has " - "no attribute '{key}'") + raise AttributeError( + f"'{self.__class__.__name__}' object has no attribute '{key}'" + ) def __iter__( self: Feature, From dac7c79d55eb007a74a93ace4dabcb16ec139c99 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 4 Jul 2025 18:00:29 +0200 Subject: [PATCH 123/223] Update features.py --- deeptrack/features.py | 55 +++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 48813e182..5ab144422 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -149,7 +149,7 @@ def merge_features( import torch __all__ = [ - "Feature", # TODO **GV** + "Feature", #TODO ***GV*** "StructuralFeature", "Chain", "Branch", @@ -1163,7 +1163,7 @@ def action( else: return image_list - # **GV** + #TODO ***GV*** def update( self: Feature, **global_arguments: Any, @@ -1363,7 +1363,7 @@ def seed( return seed - # **GV** + #TODO ***GV*** def bind_arguments( self: Feature, arguments: Feature, @@ -1401,7 +1401,7 @@ def bind_arguments( return self - # **GV** + #TODO ***GV*** def plot( self: Feature, input_image: np.ndarray | list[np.ndarray] | Image | list[Image] = None, @@ -1493,7 +1493,7 @@ def plotter(frame=0): ), ) - # **GV** + #TODO ***GV*** def _normalize( self: Feature, **properties: dict[str, Any], @@ -1566,7 +1566,7 @@ def _process_properties( return propertydict - # **GV** + #TODO ***GV*** def _activate_sources( self: Feature, x: Any, @@ -1603,23 +1603,18 @@ def __getattr__( self: Feature, key: str, ) -> Any: - """Custom attribute access for the Feature class. + """Access properties of the feature as if they were attributes. - This method allows the properties of the `Feature` instance to be - accessed as if they were attributes. For example, `feature.my_property` - is equivalent to `feature.properties["my_property"]`. + This method allows dynamic access to the feature's properties via + standard attribute syntax. For example, `feature.my_property` is + equivalent to: - If the requested attribute (`key`) exists in the `properties` - dictionary, the corresponding value is returned. If the attribute does - not exist, or if the `properties` attribute is not set, an - This method allows the properties of the `Feature` instance to be - accessed as if they were attributes. For example, `feature.my_property` - is equivalent to `feature.properties["my_property"]`. + >>> feature.properties["my_property"]`() - If the requested attribute (`key`) exists in the `properties` - dictionary, the corresponding value is returned. If the attribute does - not exist, or if the `properties` attribute is not set, an - `AttributeError` is raised. + This is only called if the attribute is not found via the normal lookup + process (i.e., it's not a real attribute or method). It checks whether + `key` exists in the `properties` dictionary, and if so, returns the + corresponding `Property` instance. Parameters ---------- @@ -1629,31 +1624,29 @@ def __getattr__( Returns ------- Any - The value of the property corresponding to the given `key` in the - `properties` dictionary. + The corresponding property if it exists in `self.properties`. Raises ------ AttributeError - If the `properties` attribute is not defined for the instance or - if the `key` does not exist in `properties`. + If `properties` is not set, or if `key` does not exist in it. Examples -------- >>> import deeptrack as dt - Accessing an attribute as if it were a property: + Create a feature with a property: >>> feature = dt.DummyFeature(value=42) + + Access the property as an attribute: >>> feature.value() 42 - If the `properties` attribute is not defined for the instance or if the - `key` does not exist in `properties`, an `AttributeError` is raised: - >>> feature.nonexistent_property + Attempting to access a non-existent property raises an `AttributeError`: + >>> feature.nonexistent() ... - AttributeError: 'MyFeature' object has no attribute - 'nonexistent_property' - + AttributeError: 'DummyFeature' object has no attribute 'nonexistent' + """ if "properties" in self.__dict__: From fb865d5c35d3f803995aaee24b045a7214c2a63b Mon Sep 17 00:00:00 2001 From: Mirja Granfors <95694095+mirjagranfors@users.noreply.github.com> Date: Sat, 5 Jul 2025 06:06:14 +0200 Subject: [PATCH 124/223] added get_backend() as a feature method + unittesting to switch and get backend (#360) --- deeptrack/features.py | 27 +++++++++++++++++++++++++++ deeptrack/tests/test_features.py | 10 ++++++++++ 2 files changed, 37 insertions(+) diff --git a/deeptrack/features.py b/deeptrack/features.py index 5ab144422..7ba37da59 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -333,6 +333,8 @@ class Feature(DeepTrackNode): It sets the backend to torch. `numpy(recursice: bool) -> Feature` It set the backend to numpy. + `get_backend() -> Literal["numpy", "torch"]` + It returns the current backend of the feature. `dtype(float: Literal["float32", "float64", "default"] or None, int: Literal["int16", "int32", "int64", "default"] or None, complex: Literal["complex64", "complex128", "default"] or None, bool: Literal["bool", "default"] or None) -> Feature` It set the dtype to be used during evaluation. `to(device: str or torch.device) -> Feature` @@ -872,6 +874,31 @@ def numpy( dependency.numpy(recursive=False) self.invalidate() return self + + def get_backend( + self: Feature + ) -> Literal["numpy", "torch"]: + """Get the current backend of the feature. + + Returns + ------- + Literal["numpy", "torch"] + The backend of this feature + + Examples + -------- + >>> import deeptrack as dt + >>> feature = dt.Add(value=5) + >>> feature.numpy() + >>> feature.get_backend() + 'numpy' + + >>> feature.torch() + >>> feature.get_backend() + 'torch' + + """ + return self._backend def dtype( self: Feature, diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 4fb75ba42..576f303b3 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -131,6 +131,16 @@ def test_operator(self, operator, emulated_operator=None): operator, ) +def test_backend_switching(self): + f = features.Add(value=5) + + f.numpy() + self.assertEqual(f.get_backend(), "numpy") + + if TORCH_AVAILABLE: + f.torch() + self.assertEqual(f.get_backend(), "torch") + class TestFeatures(unittest.TestCase): From e6dc8b8f7ac8e20b4a26f317e9a805428d22c51b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 06:08:19 +0200 Subject: [PATCH 125/223] Update features.py --- deeptrack/features.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deeptrack/features.py b/deeptrack/features.py index 7ba37da59..ee9601a16 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -888,11 +888,16 @@ def get_backend( Examples -------- >>> import deeptrack as dt + + Create a feature: >>> feature = dt.Add(value=5) + + Set the feature's backend to NumPy and check it: >>> feature.numpy() >>> feature.get_backend() 'numpy' + Set the feature's backend to PyTorch and check it: >>> feature.torch() >>> feature.get_backend() 'torch' From 26ea2b2122eafe24affa4bdfc5acf46d7a05f2fe Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 06:13:32 +0200 Subject: [PATCH 126/223] Update test_features.py --- deeptrack/tests/test_features.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 576f303b3..410ca1621 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -131,16 +131,6 @@ def test_operator(self, operator, emulated_operator=None): operator, ) -def test_backend_switching(self): - f = features.Add(value=5) - - f.numpy() - self.assertEqual(f.get_backend(), "numpy") - - if TORCH_AVAILABLE: - f.torch() - self.assertEqual(f.get_backend(), "torch") - class TestFeatures(unittest.TestCase): @@ -566,6 +556,17 @@ def test_Feature_outside_dependence(self): self.assertGreaterEqual(b - a, 0) + def test_backend_switching(self): + f = features.Add(value=5) + + f.numpy() + self.assertEqual(f.get_backend(), "numpy") + + if TORCH_AVAILABLE: + f.torch() + self.assertEqual(f.get_backend(), "torch") + + def test_Chain(self): class Addition(features.Feature): From cf2fce74efead49640b52fe2e7647aaf0e87cba9 Mon Sep 17 00:00:00 2001 From: Alex <95913221+Pwhsky@users.noreply.github.com> Date: Sat, 5 Jul 2025 10:50:59 +0200 Subject: [PATCH 127/223] Sequences restructuring (#345) * features: added to_sequential() * refactoring: SequentialProperty * typo in type hint * added dependency support for the set methods. * docs for to_sequential() * Update features.py * Update features.py * Add files via upload * edits Update sequences.py Update properties.py Update sequences.py Update sequences.py Update DTAT341_sequences.ipynb * Update test_properties.py * Update properties.py * u * Update properties.py * Update test_properties.py * Update properties.py * Update __init__.py * Update properties.py * Update properties.py * Update features.py * Update features.py * Update sequences.py * documentation and type hints * import Optional, Dict * missing comma... * current attributes * fix: typo in docstring. * documentation for sequences * typo * Update sequences.py * : * future import * documentation * DTAT306: Added SequentialProperty Example. (#350) * Added SequentialProperty example * typo * set length & index in seqprop __init__ * variable rename: sequence_step <- sequence_index * variable rename: sequence_step <- sequence_index * Update test_properties.py with new variable name * update documentation of SequentialProperty * variable name change: current and initialization * Update test_properties.py * Update features.py * Update sequences.py * sampling rule in init. * attributes * Update properties.py * Update features.py * Update properties.py * Update test_sequences.py * Revert "Update test_sequences.py" This reverts commit 503ea57bf4fa3f3937a601145094383d60e7e819. * Update properties.py * Update test_sequences.py * sequentialproperty docs * markov chain example in properties tutorial * Update seq_prop names * Update features.py * Update properties.py * Update properties.py * Update properties.py * Update properties.py * Update properties.py * Update properties.py * removed duplicate method * added example in docs * Updated tutorial with new names and examples * Update unit test * Update test_properties.py * Variable name changes * Update test_sequences.py * sequentialproperty docs * markov chain example in properties tutorial * new sequentialproperty attribute names * removed duplicate method * added example in docs * Updated tutorial with new names and examples * Update unit test * Update test_properties.py * docs: example of to_sequential() * create_action in __init__ * Added Previous() to SequentialProperty * Removed previous from backend.core * removed upsample from unit tests * Specific imports * u * sequences type hints * Updated SequentialProperty example * comma * Update __init__.py * Update features.py * Update DTAT306_properties.ipynb * Update DTAT306_properties.ipynb * Update DTAT341_sequences.ipynb * Update DTAT306_properties.ipynb * Update properties.py * Update test_properties.py * Update test_properties.py * Update features.py * Update features.py * Update optics.py * Update sequences.py * Update properties.py * Update test_sequences.py * Update features.py --------- Co-authored-by: Giovanni Volpe Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/__init__.py | 2 + deeptrack/backend/core.py | 24 - deeptrack/features.py | 171 +- deeptrack/optics.py | 5 +- deeptrack/properties.py | 757 +- deeptrack/sequences.py | 332 +- deeptrack/tests/test_properties.py | 37 +- deeptrack/tests/test_sequences.py | 104 +- .../DTAT306_properties.ipynb | 636 +- .../3-advanced-topics/DTAT341_sequences.ipynb | 101288 ++++++++++++++- 10 files changed, 102820 insertions(+), 536 deletions(-) diff --git a/deeptrack/__init__.py b/deeptrack/__init__.py index 0c32be3b2..c7aeea1fd 100644 --- a/deeptrack/__init__.py +++ b/deeptrack/__init__.py @@ -26,8 +26,10 @@ # Create a unit registry with custom pixel-related units. units = UnitRegistry(pint_definitions.split("\n")) + from deeptrack.backend import * +from deeptrack.properties import * from deeptrack.features import * from deeptrack.aberrations import * from deeptrack.augmentations import * diff --git a/deeptrack/backend/core.py b/deeptrack/backend/core.py index 38e883b57..ca345fb79 100644 --- a/deeptrack/backend/core.py +++ b/deeptrack/backend/core.py @@ -1175,30 +1175,6 @@ def set_value( return self - # TODO: The previous() method should be moved into SequentialProperty - def previous( - self: DeepTrackNode, - _ID: tuple[int, ...] = (), - ) -> Any: - """Retrieve the previously stored value at _ID without recomputing. - - Parameters - ---------- - _ID: tuple[int, ...], optional - The _ID for which to retrieve the previous value. - - Returns - ------- - Any - The previously stored value if `_ID` is valid. - Returns `[]` if `_ID` is not a valid index. - - """ - - if self.data.valid_index(_ID): - return self.data[_ID].current_value() - - return [] # If `_ID` is not a valid index def recurse_children( self: DeepTrackNode, diff --git a/deeptrack/features.py b/deeptrack/features.py index ee9601a16..3055be7c0 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -141,7 +141,7 @@ def merge_features( from deeptrack.backend.core import DeepTrackNode from deeptrack.backend.units import ConversionTable, create_context from deeptrack.image import Image -from deeptrack.properties import PropertyDict +from deeptrack.properties import PropertyDict, SequentialProperty from deeptrack.sources import SourceItem from deeptrack.types import ArrayLike, PropertyLike @@ -326,6 +326,8 @@ class Feature(DeepTrackNode): `__call__(image_list: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` It executes the feature or pipeline on the input and applies property overrides from `kwargs`. + `to_sequential(**kwargs: Any) -> Feature` + It convert a feature to be resolved as a sequence. `store_properties(toggle: bool, recursive: bool) -> Feature` It controls whether the properties are stored in the output `Image` object. @@ -685,6 +687,125 @@ def __call__( resolve = __call__ + def to_sequential( + self: Feature, + **kwargs: Any, + ) -> Feature: + """Convert a feature to be resolved as a sequence. + + Should be called on individual features, not combinations of features. + All keyword arguments will be treated as sequential properties and will + be passed to the parent feature. + + If a property from the keyword argument already exists in the feature, + the existing property will be used to initialize the passed property + (that is, it will be used for the first timestep). + + Parameters + ---------- + self: Feature + Feature to make sequential. + kwargs: Any + Keyword arguments to pass on as sequential properties of `feature`. + + Returns + ------- + Feature + The input feature evolved as a sequence + + Examples + -------- + >>> import deeptrack as dt + + Sequentially evaluate a rotating ellipse. + + Create the optics: + >>> optics = dt.Fluorescence( + ... NA=0.6, + ... magnification=10, + ... resolution=1e-6, + ... wavelength=633e-9, + ... output_region=(0, 0, 32, 32), + ... ) + + Create the scatterer: + >>> ellipse = Ellipse( + ... position_unit="pixel", + ... position=(16, 16), + ... intensity=1, + ... radius=(1.5e-6, 1e-6), + ... rotation=0, # Initial rotation at time step 0 + ... ) + + Implement a function to increment the rotation: + >>> from numpy import pi + >>> + >>> def get_rotation(sequence_length, previous_value): + ... delta = 2 * pi / sequence_length + ... return previous_value + delta + + Call `to_sequential()` to resolve the feature sequentially: + >>> rotating_ellipse = ellipse.to_sequential(rotation=get_rotation) + + Image the scatterer with the optics: + >>> imaged_rotating_ellipse = optics(rotating_ellipse) + + Encapsulate as a `Sequence` object and specify the sequence length: + >>> imaged_rotating_ellipse_sequence = Sequence( + ... imaged_rotating_ellipse, + ... sequence_length=10 + ... ) + + Finally observe the scatterer rotate: + >>> imaged_rotating_ellipse_sequence.update().plot(); + + """ + + for property_name in kwargs.keys(): + if property_name in self.properties: + # Insert sequential property with initialized value taken from + # the already available property. + self.properties[property_name] = SequentialProperty( + self.properties[property_name], **self.properties + ) + else: + # Insert empty sequential property. + self.properties[property_name] = SequentialProperty() + + self.properties.add_dependency(self.properties[property_name]) + # self.properties[property_name].add_child(self.properties) + + for property_name, sampling_rule in kwargs.items(): + prop = self.properties[property_name] + + all_kwargs = dict( + previous_value=prop.previous_value, + previous_values=prop.previous_values, + sequence_length=prop.sequence_length, + sequence_index=prop.sequence_index, + ) + + for key, value in self.properties.items(): + if key == property_name: + continue + + if isinstance(value, SequentialProperty): + all_kwargs[key] = value + all_kwargs["previous_" + key] = value.previous_values + else: + all_kwargs[key] = value + + if not prop.initial_sampling_rule: + prop.initial_sampling_rule = prop.create_action( + sampling_rule, + **{k:all_kwargs[k] for k in all_kwargs + if k != "previous_value"}, + ) + + prop.sample = prop.create_action(sampling_rule, **all_kwargs) + + return self + def store_properties( self: Feature, toggle: bool = True, @@ -1217,10 +1338,12 @@ def update( Feature The updated feature instance, ensuring the next evaluation produces a fresh result. + """ if global_arguments: import warnings + # Deprecated, but not necessary to raise hard error. warnings.warn( "Passing information through .update is no longer supported. " @@ -3135,6 +3258,7 @@ def __init__( if isinstance(value, Image): import warnings + warnings.warn( "Passing an Image object as the value to dt.Value may lead to " "performance deterioration. Consider converting the Image to " @@ -4872,9 +4996,15 @@ def get( BindResolve = Bind -class BindUpdate(StructuralFeature): +class BindUpdate(StructuralFeature): # DEPRECATED """Bind a feature with certain arguments. + .. deprecated:: 2.0 + This feature is deprecated and may be removed in a future release. It + is recommended to use `Bind` instead for equivalent functionality. + Further, the current implementation is not guaranteed to be exactly + equivalent to prior implementations. + This feature binds a child feature with specific properties (`kwargs`) that are passed to it when it is updated. It is similar to the `Bind` feature but is marked as deprecated in favor of `Bind`. @@ -4891,13 +5021,6 @@ class BindUpdate(StructuralFeature): `get(image: Any, **kwargs: Any) -> Any` It resolves the child feature with the provided arguments. - Warnings - -------- - Deprecation: This feature is deprecated and may be removed in a future - release. It is recommended to use `Bind` instead for equivalent - functionality. Further, the current implementation is not guaranteed to be - exactly equivalent to prior implementations. - Examples -------- >>> import deeptrack as dt @@ -4977,9 +5100,13 @@ def get( return self.feature.resolve(image, **kwargs) -class ConditionalSetProperty(StructuralFeature): +class ConditionalSetProperty(StructuralFeature): # DEPRECATED """Conditionally override the properties of a child feature. + .. deprecated:: 2.0 + This feature is deprecated and may be removed in a future release. It + is recommended to use `Arguments` instead. + This feature modifies the properties of a child feature only when a specified condition is met. If the condition evaluates to `True`, the given properties are applied; otherwise, the child feature remains @@ -5012,11 +5139,6 @@ class ConditionalSetProperty(StructuralFeature): Resolves the child feature, conditionally applying the specified properties. - Warnings - -------- - Deprecation: This feature is deprecated and may be removed in a future - release. It is recommended to use `Arguments` instead. - Examples -------- >>> import deeptrack as dt @@ -5141,9 +5263,13 @@ def get( return self.feature(image) -class ConditionalSetFeature(StructuralFeature): +class ConditionalSetFeature(StructuralFeature): # DEPRECATED """Conditionally resolves one of two features based on a condition. + .. deprecated:: 2.0 + This feature is deprecated and may be removed in a future release. It + is recommended to use `Arguments` instead. + This feature allows dynamically selecting and resolving one of two child features depending on whether a specified condition evaluates to `True` or `False`. @@ -5182,11 +5308,6 @@ class ConditionalSetFeature(StructuralFeature): `get(image: Any, condition: str or bool, **kwargs: Any) -> Any` Resolves the appropriate feature based on the condition. - Warnings - -------- - Deprecation: This feature is deprecated and may be removed in a future - release. It is recommended to use `Arguments` instead. - Examples -------- >>> import deeptrack as dt @@ -6100,7 +6221,10 @@ def get( except ValueError: import warnings - warnings.warn("Non-rgb image, ignoring to_grayscale") + warnings.warn( + "Non-rgb image, ignoring to_grayscale", + UserWarning, + ) # Ensure the image has at least `ndim` dimensions. while ndim and image.ndim < ndim: @@ -7023,11 +7147,12 @@ def get( self.feature.update() import warnings + warnings.warn( "Non-overlapping placement could not be achieved. Consider " "adjusting parameters: reduce object radius, increase FOV, " "or decrease min_distance.", - UserWarning + UserWarning, ) return list_of_volumes diff --git a/deeptrack/optics.py b/deeptrack/optics.py index b014855fd..b213be7c3 100644 --- a/deeptrack/optics.py +++ b/deeptrack/optics.py @@ -674,7 +674,8 @@ def _process_properties( To fix, set magnification to {required_upscale}, and downsample the resulting image with dt.AveragePooling(({required_upscale}, {required_upscale}, 1)) - """ + """, + UserWarning, ) return propertydict @@ -921,7 +922,7 @@ def __call__( f"{type(self).__name__} optics must be used with Mie scatterers " f"to produce a {type(self).__name__} image. " f"Got sample of type {type(sample).__name__}.", - UserWarning + UserWarning, ) return Microscope(sample, self, **kwargs) diff --git a/deeptrack/properties.py b/deeptrack/properties.py index d2763d467..eb54e0870 100644 --- a/deeptrack/properties.py +++ b/deeptrack/properties.py @@ -5,83 +5,106 @@ and handling properties with various data types, dependencies, and sampling rules. -Main Features -------------- +Key Features +------------ - **Property Management** - Classes like `Property` and `PropertyDict` provide tools - for defining, sampling, and evaluating properties. These properties can be - constants, functions, lists, dictionaries, iterators, or slices, allowing - for dynamic and context-dependent evaluations. + The `Property` and `PropertyDict` classes provide tools for defining, + sampling, and evaluating properties. These properties can be constants, + functions, lists, dictionaries, iterators, or slices, allowing for dynamic + and context-dependent evaluations. -**Sequential Sampling** +- **Sequential Sampling** The `SequentialProperty` class enables the creation of properties that evolve over a sequence, useful for applications like creating dynamic features in videos or time-series data. -Model Structure ------------------ -Property Classes: +Module Structure +---------------- +Classes: - `Property`: Property of a feature. Defines a single property of a feature, supporting various data types and dynamic evaluations. -- `SequentialProperty`: Property for sequential sampling. - - Extends `Property` to support sequential sampling across steps. - - `PropertyDict`: Property dictionary. A dictionary of properties with utilities for dependency management and sampling. -Example -------- -Create and use a constant property: +- `SequentialProperty`: Property for sequential sampling. + Extends `Property` to support sequential sampling across steps. + +Examples +-------- >>> import deeptrack as dt +Create and use a constant property: >>> const_prop = dt.Property(42) ->>> const_prop() # Returns 42 +>>> const_prop() +42 Define a dynamic property dependent on another: - >>> const_prop = dt.Property(5) >>> dynamic_prop = dt.Property(lambda x: x * 2, x=const_prop) ->>> dynamic_prop() # Returns 10 +>>> dynamic_prop() +10 Create a dictionary of properties: - +>>> import numpy as np +>>> >>> prop_dict = dt.PropertyDict( ... constant=42, ... dependent=lambda constant: constant + 10, ... random=lambda dependent: np.random.rand() + dependent, ... ) ->>> print(prop_dict["constant"]()) # Returns 42 ->>> print(prop_dict["dependent"]()) # Returns 52 ->>> print(prop_dict["random"]()) +>>> prop_dict["constant"]() +42 +>>> prop_dict["dependent"]() +52 +>>> prop_dict["random"]() +52.35065943710633 Handle sequential properties: - ->>> seq_prop = dt.SequentialProperty() ->>> seq_prop.sequence_length.store(5) ->>> seq_prop.current = lambda _ID=(): seq_prop.sequence_step() + 1 +>>> seq_prop = dt.SequentialProperty( +... sampling_rule=lambda: np.random.randint(10, 20), +... ) +>>> seq_prop.set_sequence_length(5) >>> for step in range(seq_prop.sequence_length()): -... seq_prop.sequence_step.store(step) -... seq_prop.store(seq_prop.current()) -... print(seq_prop.data[()].current_value()) +... seq_prop.set_current_index(step) +... current_value = seq_prop.sample() +... seq_prop.store(current_value) +... print(f"{step}: {seq_prop.previous()}") +0: [16] +1: [16, 19] +2: [16, 19, 18] +3: [16, 19, 18, 15] +4: [16, 19, 18, 15, 19] """ -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from __future__ import annotations -import numpy as np +from typing import Any, Callable, TYPE_CHECKING -from .utils import get_kwarg_names -from .backend.core import DeepTrackNode +from numpy.typing import NDArray + +from deeptrack.backend.core import DeepTrackNode +from deeptrack.utils import get_kwarg_names + + +__all__ = [ + "Property", + "PropertyDict", + "SequentialProperty", +] + + +if TYPE_CHECKING: + import torch class Property(DeepTrackNode): @@ -89,20 +112,20 @@ class Property(DeepTrackNode): A `Property` defines a rule for sampling values used to evaluate features. It supports various data types and structures, such as constants, - functions, lists, iterators, dictionaries, tuples, NumPy arrays, slices, - and DeepTrackNodes. + functions, lists, iterators, dictionaries, tuples, NumPy arrays, PyTorch + tensors, slices, and `DeepTrackNode` objects. The behavior of a `Property` depends on the type of the sampling rule: - - **Constant values** (including tuples and NumPy arrays): Always returns the - same value. - - **Functions**: Evaluates dynamically, potentially using other properties as - arguments. - - **Lists or dictionaries**: Evaluates and samples each member individually. - - **Iterators**: Returns the next value in the sequence, repeating the final - value indefinitely. - - **Slices**: Samples the `start`, `stop`, and `step` values individually. - - **DeepTrackNodes** (e.g., other properties or features): Uses the value + - **Constant values** (including tuples, NumPy arrays, and PyTorch + tensors) always return the same value. + - **Functions** are evaluated dynamically, potentially using other + properties as arguments. + - **Lists or dictionaries** evaluate and sample each member individually. + - **Iterators** return the next value in the sequence, repeating the final + value indefinitely. + - **Slices** sample the `start`, `stop`, and `step` values individually. + - **DeepTrackNode's** (e.g., other properties or features) use the value computed by the node. Dependencies between properties are tracked automatically, enabling @@ -110,103 +133,236 @@ class Property(DeepTrackNode): Parameters ---------- - sampling_rule : Any + sampling_rule: Any The rule for sampling values. Can be a constant, function, list, - dictionary, iterator, tuple, NumPy array, slice, or DeepTrackNode. - **kwargs : Dict['Property'] + dictionary, iterator, tuple, NumPy array, PyTorch tensor, slice, + or DeepTrackNode. + **dependencies: Property Additional dependencies passed as named arguments. These dependencies can be used as inputs to functions or other dynamic components of the sampling rule. Methods ------- - create_action(sampling_rule: Any, **dependencies: Dict[str, Property]) -> Callable[..., Any] + create_action(sampling_rule: Any, **dependencies: Property) -> Callable[..., Any] Creates an action that defines how the property is evaluated. The behavior of the action depends on the type of `sampling_rule`. Examples -------- - Constant property: - >>> import deeptrack as dt - - >>> const_prop = dt.Property(42) - >>> const_prop() # Returns 42 - Dynamic property using a function: + Constant properties are returned forever: + + >>> const_prop = dt.Property(42) # Number + >>> const_prop() + 42 + + >>> const_prop = dt.Property([1, 2, 3]) # List + >>> const_prop() + [1, 2, 3] + + >>> const_prop = dt.Property((1, 2, 3)) # Tuple + >>> const_prop() + (1, 2, 3) + + >>> import numpy as np + >>> + >>> const_prop = dt.Property(np.array([1, 2, 3])) # NumPy array + >>> const_prop() + array([1, 2, 3]) + + >>> import torch + >>> + >>> const_prop = dt.Property(torch.Tensor([1, 2, 3])) # PyTorch tensor + >>> const_prop() + tensor([1., 2., 3.]) + + Dynamic property using functions, which can also depend on other + properties: + >>> dynamic_prop = dt.Property(lambda: np.random.rand()) + >>> dynamic_prop() # Returns random value + 0.37700241766131415 + >>> dynamic_prop() # Returns same random value + 0.37700241766131415 + >>> dynamic_prop.update() # Updates the value + >>> dynamic_prop() # Returns different random value + 0.5862725216547282 + >>> const_prop = dt.Property(5) - >>> dynamic_prop = dt.Property(lambda x: x * 2, x=const_prop) - >>> dynamic_prop() # Returns 10 + >>> dynamic_prop = dt.Property(lambda x: 2 * x, x=const_prop) + >>> dynamic_prop() + 10 - Property with a dictionary rule: - - >>> dict_prop = dt.Property({"a": Property(1), "b": lambda: 2}) - >>> dict_prop() # Returns {"a": 1, "b": 2} + >>> def func(x): + ... return 2 * x + >>> + >>> const_prop = dt.Property(5) + >>> dynamic_prop = dt.Property(func, x=const_prop) + >>> dynamic_prop() + 10 + + Slices can be constructed from dynamic or static components: + + >>> slice_prop = dt.Property(slice(1, lambda: 10, dt.Property(2))) + >>> s = slice_prop() + >>> s.start, s.stop, s.step + (1, 10, 2) + + Iterators return their next value each time, repeating the last + indefinitely: + + >>> iter_prop = dt.Property(iter([1, 2, 3])) + >>> iter_prop() + 1 + >>> iter_prop.update() + >>> iter_prop() + 2 + >>> iter_prop.update() + >>> iter_prop() + 3 + >>> iter_prop.update() + >>> iter_prop() # Last value repeats + 3 + + Lists and dictionaries can contain properties, functions, or constants: + + >>> list_prop = dt.Property([ + ... 1, + ... lambda: 2, + ... dt.Property(3), + ... ]) + >>> list_prop() + [1, 2, 3] + + >>> dict_prop = dt.Property({ + ... "a": 1, + ... "b": lambda: 2, + ... "c": dt.Property(3), + ... }) + >>> dict_prop() + {'a': 1, 'b': 2, 'c': 3} + + Property can wrap a DeepTrackNode, such as another feature node: + + >>> node = dt.DeepTrackNode(100) + >>> node_prop = dt.Property(node) + >>> node_prop() + 100 + + >>> node = dt.DeepTrackNode(lambda _ID=(): np.random.rand()) + >>> node_prop = Property(node) + >>> node_prop() + 0.5065650298607408 + + The ID mechanism allows parameterizing evaluation: + + >>> id_prop0 = dt.Property(lambda _ID: _ID) + >>> id_prop0() + () + >>> id_prop0((1,)) + () + >>> id_prop0((1, 2, 3)) + () + + >>> id_prop1 = dt.Property(lambda _ID: _ID) + >>> id_prop1((1,)) + (1,) + >>> id_prop1((1, 2, 3)) + (1,) + + >>> id_prop2 = dt.Property(lambda _ID: _ID) + >>> id_prop2((1, 2, 3)) + (1, 2, 3) + + Properties can be combined in complex nested structures: + + >>> P = dt.Property( + ... { + ... "constant": 42, + ... "list": [1, lambda: 2, dt.Property(3)], + ... "dict": {"a": dt.Property(1), "b": lambda: 2}, + ... "function": lambda x, y: x * y, + ... "slice": slice(1, lambda: 10, dt.Property(2)), + ... }, + ... x=dt.Property(5), + ... y=dt.Property(3), + ... ) + >>> result = P() + >>> result["constant"] + 42 + >>> result["list"] + [1, 2, 3] + >>> result["dict"] + {'a': 1, 'b': 2} + >>> result["function"] + 15 + >>> result["slice"].start + 1 + >>> result["slice"].stop + 10 + >>> result["slice"].step + 2 - Property with an iterable: - - >>> gen = (i for i in range(3)) - >>> gen_prop = dt.Property(gen) - >>> gen_prop() # Returns the next value from the generator - >>> gen_prop.update() - >>> gen_prop() # Returns the next value - """ def __init__( - self, - sampling_rule: Union[ - Callable[..., Any], - List[Any], - Dict[str, Any], - tuple, - np.ndarray, - slice, - DeepTrackNode, + self: Property, + sampling_rule: ( + Callable[..., Any] | + list[Any] | + dict[Any, Any] | + tuple[Any, ...] | + NDArray[Any] | + torch.Tensor | + slice | + DeepTrackNode | Any - ], - **kwargs: 'Property', + ), + **dependencies: Property, ): - """Initializes a Property object with a given sampling rule. + """Initialize a `Property` object with a given sampling rule. Parameters ---------- - sampling_rule : Callable[..., Any] or List[Any] or Dict[str, Any] or - tuple or np.ndarray or slice or DeepTrackNode or Any + sampling_rule: Callable[..., Any] or list[Any] or dict[Any, Any] + or tuple or NumPy array or PyTorch tensor or slice + or DeepTrackNode or Any The rule to sample values for the property. - **kwargs : Property + **dependencies: Property Additional named dependencies used in the sampling rule. """ super().__init__() - self.action = self.create_action(sampling_rule, **kwargs) + self.action = self.create_action(sampling_rule, **dependencies) def create_action( - self, - sampling_rule: Union[ - Callable[..., Any], - List[Any], - Dict[str, Any], - tuple, - np.ndarray, - slice, - DeepTrackNode, + self: Property, + sampling_rule: ( + Callable[..., Any] | + list[Any] | + dict[Any, Any] | + tuple[Any, ...] | + NDArray[Any] | + torch.Tensor | + slice | + DeepTrackNode | Any - ], - **dependencies: Dict[str, 'Property'], + ), + **dependencies: Property, ) -> Callable[..., Any]: - """Creates an action defining how the property is evaluated. + """Create an action defining how the property is evaluated. Parameters ---------- - sampling_rule : Union[Callable[..., Any], List[Any], Dict[str, Any], - tuple, np.ndarray, slice, Generator, - DeepTrackNode, Any] + sampling_rule: Callable[..., Any] or list[Any] or dict[Any] + or tuple or np.ndarray or torch.Tensor or slice + or DeepTrackNode or Any The rule to sample values for the property. - **dependencies : Dict[str, Property] + **dependencies: dict[str, Property] Dependencies to be used in the sampling rule. Returns @@ -216,14 +372,14 @@ def create_action( """ - # DeepTrackNode (e.g., another property or feature). + # DeepTrackNode (e.g., another property or feature) # Return the value sampled by the DeepTrackNode. if isinstance(sampling_rule, DeepTrackNode): sampling_rule.add_child(self) # self.add_dependency(sampling_rule) # Already done by add_child. return sampling_rule - # Dictionary. + # Dictionary # Return a dictionary with each each member sampled individually. if isinstance(sampling_rule, dict): dict_of_actions = dict( @@ -234,7 +390,7 @@ def create_action( (key, value(_ID=_ID)) for key, value in dict_of_actions.items() ) - # List. + # List # Return a list with each each member sampled individually. if isinstance(sampling_rule, list): list_of_actions = [ @@ -243,8 +399,8 @@ def create_action( ] return lambda _ID=(): [value(_ID=_ID) for value in list_of_actions] - # Iterable. - # Return the next value. The last value is returned indefinetely. + # Iterable + # Return the next value. The last value is returned indefinitely. if hasattr(sampling_rule, "__next__"): def wrapped_iterator(): @@ -262,7 +418,7 @@ def action(_ID=()): return action - # Slice. + # Slice # Sample individually the start, stop and step. if isinstance(sampling_rule, slice): @@ -276,7 +432,7 @@ def action(_ID=()): step(_ID=_ID), ) - # Function. + # Function # Return the result of the function. It accepts the names of other # properties of the same feature as arguments. if callable(sampling_rule): @@ -301,7 +457,7 @@ def action(_ID=()): **({"_ID": _ID} if "_ID" in knames else {}), ) - # Constant, tuple or numpy array. + # Constant, tuple, numpy array, or torch Tensor # Return always the same constant value. return lambda _ID=(): sampling_rule @@ -316,24 +472,23 @@ class PropertyDict(DeepTrackNode, dict): Parameters ---------- - **kwargs : Dict[str, Any] + **kwargs: Any Key-value pairs used to initialize the dictionary, where values are either directly used to create `Property` instances or are dependent on other `Property` values. Methods ------- - __init__(**kwargs: Dict[str, Any]) - Initializes the `PropertyDict`, resolving `Property` dependencies. __getitem__(key: str) -> Any Retrieves a value from the dictionary using a key. Examples -------- - Initialize a `PropertyDict` with different types of properties: - >>> import deeptrack as dt + Initialize a `PropertyDict` with different types of properties: + >>> import numpy as np + >>> >>> prop_dict = dt.PropertyDict( ... constant=42, ... dependent=lambda constant: constant + 10, @@ -341,14 +496,19 @@ class PropertyDict(DeepTrackNode, dict): ... ) Access the properties: - - >>> print(prop_dict["constant"]()) # Returns 42 - >>> print(prop_dict["dependent"]()) # Returns 52 - >>> print(prop_dict["random"]()) + >>> prop_dict["constant"]() + 42 + >>> prop_dict["dependent"]() + 52 + >>> prop_dict["random"]() + 0.33112452108057056 """ - def __init__(self, **kwargs: Dict[str, Any]): + def __init__( + self: PropertyDict, + **kwargs: Any, + ): """Initialize a PropertyDict with properties and dependencies. Iteratively converts the input dictionary's values into `Property` @@ -361,7 +521,7 @@ def __init__(self, **kwargs: Dict[str, Any]): Parameters ---------- - **kwargs : Dict[str, Any] + **kwargs: Any Key-value pairs used to initialize the dictionary. Values can be constants, functions, or other `Property`-compatible types. @@ -370,7 +530,7 @@ def __init__(self, **kwargs: Dict[str, Any]): dependencies = {} # To store the resolved Property instances. while kwargs: - # Multiple passes over the data until everything that can be + # Multiple passes over the data until everything that can be # resolved is resolved. for key, value in list(kwargs.items()): try: @@ -384,17 +544,19 @@ def __init__(self, **kwargs: Dict[str, Any]): # Catch unresolved dependencies and continue iterating. pass - def action(_ID: Tuple[int, ...] = ()) -> Dict[str, Any]: + def action( + _ID: tuple[int, ...] = (), + ) -> dict[str, Any]: """Evaluate and return the dictionary with sampled Property values. Parameters ---------- - _ID : Tuple[int, ...], optional + _ID: tuple[int, ...], optional A unique identifier for sampling properties. Returns ------- - Dict[str, Any] + dict[str, Any] A dictionary where each value is sampled from its respective `Property`. @@ -408,14 +570,17 @@ def action(_ID: Tuple[int, ...] = ()) -> Dict[str, Any]: value.add_child(self) # self.add_dependency(value) # Already executed by add_child. - def __getitem__(self, key: str) -> Any: + def __getitem__( + self: PropertyDict, + key: str, + ) -> Any: """Retrieve a value from the dictionary. Overrides the default `__getitem__` to ensure dictionary functionality. Parameters ---------- - key : str + key: str The key to retrieve the value for. Returns @@ -425,7 +590,7 @@ def __getitem__(self, key: str) -> Any: Notes ----- - This method explicitly calls the `__getitem__` method of the built-in + This method directly calls the `__getitem__()` method of the built-in `dict` class. This ensures that the standard dictionary behavior is used to retrieve values, bypassing any custom logic in `PropertyDict` that might otherwise cause infinite recursion or unexpected results. @@ -441,191 +606,270 @@ def __getitem__(self, key: str) -> Any: class SequentialProperty(Property): """Property that yields different values for sequential steps. - The `SequentialProperty` extends the standard `Property` to handle + SequentialProperty lets the user encapsulate feature sampling rules and + iterator logic in a single object to evaluate them sequentially. + + The `SequentialProperty` class extends the standard `Property` to handle scenarios where the property’s value evolves over discrete steps, such as frames in a video, time-series data, or any sequential process. At each step, it selects whether to use the `initialization` function (step = 0) or the `current` function (steps >= 1). It also keeps track of all previously generated values, allowing to refer back to them if needed. + Parameters ---------- - initialization : Any, optional + initial_sampling_rule: Any, optional A sampling rule for the first step of the sequence (step=0). Can be any value or callable that is acceptable to `Property`. If not provided, the initial value is `None`. - **kwargs : Dict[str, Property] + + current_value: Any, optional + The sampling rule (value or callable) for steps > 0. Defaults to None. + sequence_length: int, optional + The length of the sequence. + sequence_index: int, optional + The current index of the sequence. + + **kwargs: dict[str, Property] Additional dependencies that might be required if `initialization` is a callable. These dependencies are injected when evaluating `initialization`. Attributes ---------- - sequence_length : Property + sequence_length: Property A `Property` holding the total number of steps in the sequence. Initialized to 0 by default. - sequence_step : Property + sequence_index: Property A `Property` holding the index of the current step (starting at 0). - previous_values : Property + previous_values: Property A `Property` returning all previously stored values up to, but not including, the current value and the previous value. - previous_value : Property + previous_value: Property A `Property` returning the most recently stored value, or `None` if there is no history yet. - initialization : Callable[..., Any], optional + initial_sampling_rule: Callable[..., Any], optional A function to compute the value at step=0. If `None`, the property returns `None` at the first step. - current : Callable[..., Any] - A function to compute the value at steps >= 1. By default, it returns - `None`. - action : Callable[..., Any] + sample: Callable[..., Any] + Computes the value at steps >= 1 with the given sampling rule. + By default, it returns `None`. + action: Callable[..., Any] Overrides the default `Property.action` to select between - `initialization` (if `sequence_step` is 0) or `current` (otherwise). + `initial_sampling_rule` (if `sequence_index` is 0) or `sampling_rule` (otherwise). Methods ------- - _action_override(_ID: Tuple[int, ...]) -> Any + _action_override(_ID: tuple[int, ...]) -> Any Internal logic to pick which function (`initialization` or `current`) - to call based on the `sequence_step`. - store(value: Any, _ID: Tuple[int, ...] = ()) -> None + to call based on the `sequence_index`. + store(value: Any, _ID: tuple[int, ...] = ()) -> None Store a newly computed `value` in the property’s internal list of previously generated values. - current_value(_ID: Tuple[int, ...] = ()) -> Any - Retrieve the value associated with the current step index. - __call__(_ID: Tuple[int, ...] = ()) -> Any + sampling_rule(_ID: tuple[int, ...] = ()) -> Any + Retrieve the sampling_rule associated with the current step index. + __call__(_ID: tuple[int, ...] = ()) -> Any Evaluate the property at the current step, returning either the - initialization (if step=0) or current value (if step>0). - + initialization (if index = 0) or current value (if index > 0). + set_sequence_length(self, value, ID) -> None: + Stores the value for the length of the sequence, + analagous to SequentialProperty.sequence_length.store() + set_current_index(self, value, ID) -> None: + Stores the value for the current step of the sequence, + analagous to SequentialProperty.current_step.store() + Examples -------- >>> import deeptrack as dt + + To illustrate the use of `SequentialProperty`, we will implement a + one-dimensional Brownian walker. + + Define the `SequentialProperty`: >>> import numpy as np + >>> + >>> seq_prop = dt.SequentialProperty( + ... initial_sampling_rule=0, # Sampling rule for first time step + ... sampling_rule= np.random.randn, # Sampl. rule for subsequent steps + ... sequence_length=10, # Number of steps + ... sequence_index=0, # Initial step + ... ) - >>> seq_prop = dt.SequentialProperty() - >>> seq_prop.sequence_length.store(5) - >>> seq_prop.current = lambda _ID=(): seq_prop.sequence_step() + 1 - >>> for step in range(seq_prop.sequence_length()): - ... seq_prop.sequence_step.store(step) - ... current_value = seq_prop.current() - ... seq_prop.store(current_value) - ... print(seq_prop.data[()].current_value()) - [1] - [1, 2] - [1, 2, 3] - [1, 2, 3, 4] - [1, 2, 3, 4, 5] - - """ + Sample and store initial position: + >>> start_position = seq_prop.initial_sampling_rule() + >>> seq_prop.store(start_position) + + Iteratively update and store position: + >>> for step in range(1, seq_prop.sequence_length()): + ... seq_prop.set_current_index(step) + ... previous_position = seq_prop.previous()[-1] # Previous value + ... new_position = previous_position + seq_prop.sample() + ... seq_prop.store(new_position) + + Print all stored values: + >>> seq_prop.previous() + [0, + -0.38200070551587934, + 0.4107493780458869, + 0.4168147820083061, + -0.37943277485427523, + -0.24658839362797394, + 0.6200008820895946, + 0.7763449126000742, + 1.9552313612982135, + 1.8016703270391572] + """ - # Attributes. sequence_length: Property - sequence_step: Property + sequence_index: Property previous_values: Property previous_value: Property - initialization: Optional[Callable[..., Any]] - current: Callable[..., Any] + initial_sampling_rule: Callable[..., Any] + sample: Callable[..., Any] action: Callable[..., Any] def __init__( - self, - initialization: Optional[Any] = None, - **kwargs: Dict[str, 'Property'], - ): - """Create a SequentialProperty with optional initialization. + self: SequentialProperty, + initial_sampling_rule: Any = None, + sampling_rule: Any = None, + sequence_length: int | None = None, + sequence_index: int | None = None, + **kwargs: Property, + ) -> None: + """Create SequentialProperty. Parameters ---------- - initialization : Any, optional - The sampling rule (value or callable) for step=0. Defaults to None. - **kwargs : Dict[str, Property] - Additional named dependencies for `initialization`. + initial_sampling_rule: Any, optional + The sampling rule (value or callable) for step = 0. It defaults to + `None`. + sampling_rule: Any, optional + The sampling rule (value or callable) for the current step. It + defaults to `None`. + sequence_length: int, optional + The length of the sequence. It defaults to `None`. + sequence_index: int, optional + The current index of the sequence. It defaults to `None`. + **kwargs: Property + Additional named dependencies for `initialization` and `current`. """ - # Set sampling_rule=None to the base constructor, as it overrides - # action below with _action_override. + # Set sampling_rule=None to the base constructor. + # It overrides action below with _action_override(). super().__init__(sampling_rule=None) - # 1) Initialize sequence length to 0. - self.sequence_length = Property(0) + # 1) Initialize sequence length. + if isinstance(sequence_length, int): + self.sequence_length = Property(sequence_length) + else: + self.sequence_length = Property(0) self.sequence_length.add_child(self) # self.add_dependency(self.sequence_length) # Done by add_child. - # 2) Current index of the sequence (0). - self.sequence_step = Property(0) - self.sequence_step.add_child(self) - # self.add_dependency(self.sequence_step) # Done by add_child. + # 2) Initialize sequence index. + if isinstance(sequence_index, int): + self.sequence_index = Property(sequence_index) + else: + self.sequence_index = Property(0) + self.sequence_index.add_child(self) + # self.add_dependency(self.sequence_index) # Done by add_child. - # 3) Store all previous values. + # 3) Store all previous values if sequence step > 0. self.previous_values = Property( - lambda _ID=(): self.previous(_ID=_ID)[: self.sequence_step() - 1] - if self.sequence_step(_ID=_ID) + lambda _ID=(): self.previous(_ID=_ID)[: self.sequence_index() - 1] + if self.sequence_index(_ID=_ID) else [] ) self.previous_values.add_child(self) - # self.add_dependency(self.previous_values) # Done by add_child. - self.sequence_step.add_child(self.previous_values) - # self.previous_values.add_dependency(self.sequence_step) # Done. + # self.add_dependency(self.previous_values) # Done by add_child + + self.sequence_index.add_child(self.previous_values) + # self.previous_values.add_dependency(self.sequence_index) # Done # 4) Store the previous value. self.previous_value = Property( - lambda _ID=(): self.previous(_ID=_ID)[self.sequence_step() - 1] + lambda _ID=(): self.previous(_ID=_ID)[self.sequence_index() - 1] if self.previous(_ID=_ID) else None ) self.previous_value.add_child(self) - # self.add_dependency(self.previous_value) # Done by add_child. - self.sequence_step.add_child(self.previous_value) - # self.previous_value.add_dependency(self.sequence_step) # Done. + # self.add_dependency(self.previous_value) # Done by add_child + + self.sequence_index.add_child(self.previous_value) + # self.previous_value.add_dependency(self.sequence_index) # Done # 5) Create an action for initializing the sequence. - if initialization is not None: - self.initialization = self.create_action(initialization, **kwargs) + if initial_sampling_rule is not None: + self.initial_sampling_rule = self.create_action( + initial_sampling_rule, + **kwargs, + ) else: - self.initialization = None + self.initial_sampling_rule = None # 6) Define a default current function for steps >= 1. - self.current = lambda _ID=(): None + if sampling_rule is not None: + self.sample = self.create_action( + sampling_rule, + sequence_index=self.sequence_index, + sequence_length=self.sequence_length, + previous_values=self.previous_values, + previous_value=self.previous_value, + **kwargs, + ) + else: + self.sample = lambda _ID=(): None # 7) Override the default action with our custom logic. self.action = self._action_override - def _action_override(self, _ID: Tuple[int, ...] = ()) -> Any: + def _action_override( + self: SequentialProperty, + _ID: tuple[int, ...] = (), + ) -> Any: """Decide which function to call based on the current step. - For step=0, call `initialization`. Otherwise, call `self.current`. + For step=0, it calls `self.initial_sampling_rule`. Otherwise, it calls + `self.sampling_rule`. Parameters ---------- - _ID : Tuple[int, ...], optional + _ID: tuple[int, ...], optional A unique identifier that differentiates parallel evaluations. Returns ------- Any - The result of the `initialization` (step=0) or the `current` - function (step>0). + Result of the `self.initial_sampling_rule` function (if step == 0) + or result of the `self.sampling_rule` function (if step > 0). """ - if self.sequence_step(_ID=_ID) == 0: - return (self.initialization(_ID=_ID) - if self.initialization else None) - else: - return self.current(_ID=_ID) + if self.sequence_index(_ID=_ID) == 0: + if self.initial_sampling_rule: + return self.initial_sampling_rule(_ID=_ID) + return None + + return self.sample(_ID=_ID) - def store(self, value: Any, _ID: Tuple[int, ...] = ()) -> None: + def store( + self: SequentialProperty, + value: Any, + _ID: tuple[int, ...] = (), + ) -> None: """Append value to the internal list of previously generated values. It retrieves the existing list of values for this _ID. If this _ID has - never been used, it starts an empty list + never been used, it starts an empty list. Parameters ---------- - value : Any + value: Any The value to store, e.g., the output from calling `self()`. - _ID : Tuple[int, ...], optional + _ID: tuple[int, ...], optional A unique identifier that allows the property to keep separate histories for different parallel evaluations. @@ -643,42 +887,101 @@ def store(self, value: Any, _ID: Tuple[int, ...] = ()) -> None: super().store(current_data + [value], _ID=_ID) - def current_value(self, _ID: Tuple[int, ...] = ()) -> Any: - """Retrieve the value corresponding to the current step. + def current_value( + self: SequentialProperty, + _ID: tuple[int, ...] = (), + ) -> Any: + """Retrieve the value corresponding to the current sequence step. It expects that each step's value has been stored. If no value has been stored for this step, it thorws an IndexError. Parameters ---------- - _ID : Tuple[int, ...], optional + _ID: tuple[int, ...], optional A unique identifier for separate parallel evaluations. Returns ------- Any - The value stored at the index = `self.sequence_step(_ID=_ID)`. - + The value stored at the index = `self.sequence_index(_ID=_ID)`. + + Raises + ------ + IndexError + If no value has been stored for this step, it thorws an IndexError. + """ - return super().current_value(_ID=_ID)[self.sequence_step(_ID=_ID)] + return super().current_value(_ID=_ID)[self.sequence_index(_ID=_ID)] - def __call__(self, _ID: Tuple[int, ...] = ()) -> Any: - """Evaluate the property at the current step. - - It returns either the initialization (if step=0) or the result of - `self.current`. + def previous(self, _ID: tuple[int, ...] = ()) -> Any: + """Retrieve the previously stored value at ID without recomputing. Parameters ---------- _ID : Tuple[int, ...], optional - A unique identifier for parallel evaluations. + The ID for which to retrieve the previous value. Returns ------- Any - The computed value for this step. + The previously stored value if `_ID` is valid. + Returns `[]` if `_ID` is not a valid index. """ - return super().__call__(_ID=_ID) + if self.data.valid_index(_ID): + return self.data[_ID].current_value() + else: + return [] + + def set_sequence_length( + self: SequentialProperty, + value: Any, + _ID: tuple[int, ...] = (), + ) -> None: + """Sets the `sequence_length` attribute of a sequence to be resolved. + + It supports dependencies if `value` is a `Property`. + + Parameters + ---------- + value: Any + The value to store in `self.sequence_length`. + _ID: tuple[int, ...], optional + A unique identifier that allows the property to keep separate + histories for different parallel evaluations. + + """ + + if isinstance(value, Property): # For dependencies + self.sequence_length = Property(lambda _ID: value(_ID)) + self.sequence_length.add_dependency(value) + else: + self.sequence_length = Property(value, _ID=_ID) + + def set_current_index( + self: SequentialProperty, + value: Any, + _ID: tuple[int, ...] = (), + ) -> None: + """Set the `sequence_index` attribute of a sequence to be resolved. + + It supports dependencies if `value` is a `Property`. + + Parameters + ---------- + value: Any + The value to store in `sequence_index`. + _ID: tuple[int, ...], optional + A unique identifier that allows the property to keep separate + histories for different parallel evaluations. + + """ + + if isinstance(value, Property): # For dependencies + self.sequence_index = Property(lambda _ID: value(_ID)) + self.sequence_index.add_dependency(value) + else: + self.sequence_index = Property(value, _ID=_ID) diff --git a/deeptrack/sequences.py b/deeptrack/sequences.py index 3fb1d9680..650b6793b 100644 --- a/deeptrack/sequences.py +++ b/deeptrack/sequences.py @@ -1,62 +1,250 @@ -"""Features and tools for resolving sequences of images. +"""Tools for evaluating and propagating sequences of features. -Classes -------- -Sequence - Resolves a feature as a sequence. +This module enables sequential evaluation of DeepTrack2 features by +resolving them over multiple time steps. It provides tools for propagating +values like `sequence_index` and `sequence_length` to all dependent +`SequentialProperty` attributes, allowing simulation of dynamic behaviors +(e.g., microsocpy videos). + +Key Features +------------ +- **Temporal Simulation via SequentialProperty** + + Features can be annotated with sampling rules that evolve over a sequence + of time steps, enabling animations and simulations of time-dependent + systems. + +- **Graph-wide Sequential Data Propagation** + + Sequential information is passed to all relevant nodes in the feature + graph. + +Module Structure +---------------- +Classes: + +- `Sequence` + + It resolves a feature over multiple time steps, using a defined + `sequence_length`. Injects sequential arguments into all dependent + `SequentialProperty` attributes before each evaluation. + +Functions: + +- `Sequential(feature, **kwargs)` + + .. deprecated:: 2.0 + + def Sequential( + feature: Feature, + **kwargs: Any, + ) -> Feature + + Converts a feature to be resolved as a sequence. Replaced by + `Feature.to_sequence()` and will be removed in a future release. + +- `_propagate_sequential_data(feature, **kwargs)` + + def _propagate_sequential_data( + feature: Feature, + **kwargs: Any, + ) -> None + + Recursively propagates keyword arguments like `sequence_index` and + `sequence_length` to all `SequentialProperty` nodes in a feature graph. + +Examples +-------- +>>> import deeptrack as dt + +Simulating a spinning ellipsoid. + +Define imaging system: +>>> optics = dt.optics.Fluorescence(output_region=(0, 0, 32, 32)) + +Define a static ellipse: +>>> ellipse = dt.scatterers.Ellipse( +... radius=(1e-6,0.5e-6), +... position=(16, 16), +... rotation=0.78, # Initial rotation +... ) + +Define a rotation function that increments the previous angle: +>>> def rotate(sequence_length, previous_value): +... return previous_value + 6.28 / sequence_length + +Convert the ellipse to a sequential feature: +>>> rotating_ellipse = ellipse.to_sequential(rotation=rotate) + +Compose with the optics: +>>> imaged_rotating_ellipse = optics(rotating_ellipse) + +Wrap the full feature in a Sequence: +>>> imaged_rotating_ellipse_sequence = dt.Sequence( +... imaged_rotating_ellipse, +... sequence_length=50, +... ) + +Generate and display the result +>>> imaged_rotating_ellipse_sequence.update().plot(); -Functions ---------- -Sequential - Converts a feature to be resolved as a sequence. """ -from .features import Feature -from .properties import SequentialProperty -from .types import PropertyLike -import random -import numpy as np +from __future__ import annotations + +from typing import Any + +from deeptrack.features import Feature +from deeptrack.properties import SequentialProperty +from deeptrack.types import PropertyLike + + +__all__ = ["Sequence"] + class Sequence(Feature): """Resolves a feature as a sequence. - The input feature is resolved `sequence_length` times, with the kwarg - arguments `sequene_length` and `sequence_step` passed to all properties - of the feature set. + The `Sequence` class repeatedly evaluates a given feature + `sequence_length` times. During each evaluation, the keyword arguments + `sequence_length` and `sequence_index` are propagated to all + `SequentialProperty` attributes of the feature, enabling dynamic updates at + each timestep. + + This allows for temporal simulations or animations, where the same feature + (e.g., a rotating particle or moving object) evolves over time with + properties defined as sequential functions. Parameters ---------- - feature : Feature + feature: Feature The feature to resolve as a sequence. - sequence_length : int - The number of times to resolve the feature. + sequence_length: int + The number of times to evaluate the feature. It defaults to 1. + kwargs: Any + Additional keyword arguments to be passed to the base `Feature`. Attributes ---------- - feature : Feature - The feature to resolve as a sequence. + feature: Feature + The feature that is resolved multiple times to generate the sequence. + __distributed__: bool + This feature is not distributed across processes or devices. + Always set to False. + + Methods + ------- + `get(input_list: list[Feature], sequence_length: int, **kwargs: Any) -> list[Any] or tuple[list[Any], ...]` + Resolves the wrapped feature `sequence_length` times. It returns a list + (or tuple of lists) of resolved outputs. + + Examples + -------- + >>> import deeptrack as dt + + Simulating a spinning ellipsoid. + + Define imaging system: + >>> optics = dt.Fluorescence(output_region=(0, 0, 32, 32)) + + Define a static ellipse: + >>> ellipse = dt.Ellipse( + ... radius=(1e-6,0.5e-6), + ... position=(16, 16), + ... rotation=0.78, # Initial rotation + ... ) + + Define a rotation function that increments the previous angle: + >>> def rotate(sequence_length, previous_value): + ... return previous_value + 6.28 / sequence_length + + Convert the ellipse to a sequential feature: + >>> rotating_ellipse = ellipse.to_sequential(rotation=rotate) + + Compose with the optics: + >>> imaged_rotating_ellipse = optics(rotating_ellipse) + + Wrap the full feature in a Sequence: + >>> imaged_rotating_ellipse_sequence = dt.Sequence( + ... imaged_rotating_ellipse, + ... sequence_length=50, + ... ) + + Generate and display the result + >>> imaged_rotating_ellipse_sequence.update().plot(); + """ __distributed__ = False + feature: Feature + def __init__( - self, feature: Feature, sequence_length: PropertyLike[int] = 1, **kwargs - ): - + self: Sequence, + feature: Feature, + sequence_length: PropertyLike[int] = 1, + **kwargs: Any, + ) -> None: + """Initialize a Sequence object. + + This constructor wraps a feature to be resolved multiple times, + propagating sequential information to any `SequentialProperty` + attributes. + + Parameters + ---------- + feature: Feature + The feature to be resolved as a sequence. + sequence_length: PropertyLike[int], optional + Number of steps in the sequence. It defaults to 1. + **kwargs: Any + Additional keyword arguments passed to the base `Feature`. + + """ + super().__init__(sequence_length=sequence_length, **kwargs) self.feature = self.add_feature(feature) - # Require update - # self.update() - def get(self, input_list, sequence_length=None, **kwargs): + def get( + self: Sequence, + input_list: list[Feature], + sequence_length: int | None = None, + **kwargs: Any, + ) -> list[Any] | tuple[list[Any], ...]: + """Resolve the wrapped feature as a sequence of outputs. + + The method evaluates the feature `sequence_length` times, each time + updating the `sequence_index` and propagating it to all dependent + `SequentialProperty` attributes. The results are collected into a list. + + Parameters + ---------- + input_list: list[Feature] + A list of previously resolved outputs to extend. If empty, a new + list is initialized. + sequence_length: int, optional + Number of times to evaluate the feature. If None, it is assumed + to be handled externally or will raise an error. + **kwargs: Any + Unused, included for compatibility. + + Returns + ------- + list[Any] or tuple[list[Any], ...] + The sequence of resolved feature outputs. If the output of the + feature is a tuple or list, the return is transposed into a tuple + of lists. + + """ outputs = input_list or [] - for sequence_step in range(sequence_length): - np.random.seed(random.randint(0, 1000000)) + for sequence_index in range(sequence_length): + #TODO ***BM*** ***AL*** Can this be erased? + # np.random.seed(random.randint(0, 1000000)) - propagate_sequential_data( + _propagate_sequential_data( self.feature, - sequence_step=sequence_step, + sequence_index=sequence_index, sequence_length=sequence_length, ) out = self.feature() @@ -69,26 +257,74 @@ def get(self, input_list, sequence_length=None, **kwargs): return outputs -def Sequential(feature: Feature, **kwargs): +def _propagate_sequential_data( + feature: Feature, + **kwargs: Any, +) -> None: + """Propagate sequential data through the computational graph. + + This function updates the attributes of all `SequentialProperty` instances + in the computational graph rooted at the given feature. It works by + recursively traversing the feature's dependencies and setting the values + of matching attributes using the provided keyword arguments. + + Parameters + ---------- + feature: Feature + The root feature whose dependent sequential properties will be updated. + **kwargs: Any + Attribute-value pairs to assign to matching fields in each + `SequentialProperty`. + + """ + + for dep in feature.recurse_dependencies(): + if isinstance(dep, SequentialProperty): + for key, value in kwargs.items(): + if hasattr(dep, key): + getattr(dep, key).set_value(value) + + +def Sequential( + feature: Feature, + **kwargs: Any, +) -> Feature: # DEPRECATED """Converts a feature to be resolved as a sequence. + .. deprecated:: 2.0 + This function has been substituted by the `Feature.to_sequence()` + method and will be removed in a future release. + Should be called on individual features, not combinations of features. All - keyword arguments will be trated as sequential properties and will be + keyword arguments will be treated as sequential properties and will be passed to the parent feature. If a property from the keyword argument already exists on the feature, the - existing property will be used to initilize the passed property (that is, + existing property will be used to initialize the passed property (that is, it will be used for the first timestep). Parameters ---------- - feature : Feature + feature: Feature Feature to make sequential. - kwargs + kwargs: Any Keyword arguments to pass on as sequential properties of `feature`. + Returns + ------- + Feature + The modified feature with sequential behavior. + """ + import warnings + + warnings.warn( + "The `Sequential()` function is deprecated and will be removed in a " + "future release. Please use `Feature.to_sequence()` instead.", + category=DeprecationWarning, + ) + for property_name in kwargs.keys(): if property_name in feature.properties: @@ -111,7 +347,7 @@ def Sequential(feature: Feature, **kwargs): previous_value=prop.previous_value, previous_values=prop.previous_values, sequence_length=prop.sequence_length, - sequence_step=prop.sequence_step, + sequence_index=prop.sequence_index, ) for key, val in feature.properties.items(): @@ -123,17 +359,17 @@ def Sequential(feature: Feature, **kwargs): all_kwargs["previous_" + key] = val.previous_values else: all_kwargs[key] = val - if not prop.initialization: - prop.initialization = prop.create_action(sampling_rule, **{k:all_kwargs[k] for k in all_kwargs if k != "previous_value"}) + if not prop.initial_sampling_rule: + prop.initial_sampling_rule = prop.create_action( + sampling_rule, + **{ + k:all_kwargs[k] + for k + in all_kwargs + if k != "previous_value" + } + ) prop.current = prop.create_action(sampling_rule, **all_kwargs) return feature - - -def propagate_sequential_data(X, **kwargs): - for dep in X.recurse_dependencies(): - if isinstance(dep, SequentialProperty): - for key, value in kwargs.items(): - if hasattr(dep, key): - getattr(dep, key).set_value(value) diff --git a/deeptrack/tests/test_properties.py b/deeptrack/tests/test_properties.py index 340800ebb..2bd7e6c40 100644 --- a/deeptrack/tests/test_properties.py +++ b/deeptrack/tests/test_properties.py @@ -8,16 +8,17 @@ import unittest -from deeptrack.backend.core import DeepTrackNode -from deeptrack.utils import get_kwarg_names import numpy as np -from deeptrack import properties +from deeptrack import properties, TORCH_AVAILABLE +from deeptrack.backend.core import DeepTrackNode +if TORCH_AVAILABLE: + import torch class TestProperties(unittest.TestCase): - def test_Property_constant_list_nparray(self): + def test_Property_constant_list_nparray_tensor(self): P = properties.Property(42) self.assertEqual(P(), 42) P.update() @@ -33,6 +34,11 @@ def test_Property_constant_list_nparray(self): P.update() np.testing.assert_array_equal(P(), np.array([1, 2, 3])) + if TORCH_AVAILABLE: + P = properties.Property(torch.Tensor([1, 2, 3])) + self.assertTrue(torch.equal(P(), torch.tensor([1, 2, 3]))) + P.update() + self.assertTrue(torch.equal(P(), torch.tensor([1, 2, 3]))) def test_Property_function(self): @@ -71,7 +77,6 @@ def func2(x): self.assertEqual(P(), P()) self.assertTrue(P() >= 0 and P() <= 2) - def test_Property_slice(self): P = properties.Property(slice(1, lambda: 10, properties.Property(2))) result = P() @@ -83,7 +88,6 @@ def test_Property_slice(self): self.assertEqual(result.stop, 10) self.assertEqual(result.step, 2) - def test_Property_iterable(self): P = properties.Property(iter([1, 2, 3])) @@ -95,7 +99,6 @@ def test_Property_iterable(self): P.update() self.assertEqual(P(), 3) # Last value repeats indefinitely - def test_Property_list(self): P = properties.Property([1, lambda: 2, properties.Property(3)]) self.assertEqual(P(), [1, 2, 3]) @@ -116,7 +119,6 @@ def test_Property_list(self): self.assertTrue(P()[1] >= 0 and P()[1] <= 2) self.assertTrue(P()[2] >= 0 and P()[2] <= 3) - def test_Property_dict(self): P = properties.Property( { @@ -143,7 +145,6 @@ def test_Property_dict(self): self.assertTrue(P()["b"] >= 0 and P()["b"] <= 2) self.assertTrue(P()["c"] >= 0 and P()["c"] <= 3) - def test_Property_DeepTrackNode(self): node = DeepTrackNode(100) P = properties.Property(node) @@ -158,7 +159,6 @@ def test_Property_DeepTrackNode(self): self.assertEqual(P(), P()) self.assertTrue(P() >= 0 and P() <= 1) - def test_Property_ID(self): P = properties.Property(lambda _ID: _ID) self.assertEqual(P(), ()) @@ -169,7 +169,6 @@ def test_Property_ID(self): P = properties.Property(lambda _ID: _ID) self.assertEqual(P((1, 2, 3)), (1, 2, 3)) - def test_Property_combined(self): P = properties.Property( { @@ -192,7 +191,6 @@ def test_Property_combined(self): self.assertEqual(result["slice"].stop, 10) self.assertEqual(result["slice"].step, 2) - def test_PropertyDict(self): PD = properties.PropertyDict( @@ -220,19 +218,22 @@ def test_PropertyDict(self): self.assertEqual(PD["dependent"](), 43) self.assertEqual(PD()["dependent"], 43) - def test_SequentialProperty(self): SP = properties.SequentialProperty() SP.sequence_length.store(5) - SP.current = lambda _ID=(): SP.sequence_step() + 1 + SP.sample = lambda _ID=(): SP.sequence_index() + 1 for step in range(SP.sequence_length()): - SP.sequence_step.store(step) - current_value = SP.current() + SP.sequence_index.store(step) + current_value = SP.sample() SP.store(current_value) - self.assertEqual(SP.data[()].current_value(), - list(range(1, step + 2))) + self.assertEqual( + SP.data[()].current_value(), list(range(1, step + 2)), + ) + self.assertEqual( + SP.previous(), list(range(1, step + 2)), + ) SP.previous_value.invalidate() # print(SP.previous_value()) diff --git a/deeptrack/tests/test_sequences.py b/deeptrack/tests/test_sequences.py index 2248aaa55..1c50678c0 100644 --- a/deeptrack/tests/test_sequences.py +++ b/deeptrack/tests/test_sequences.py @@ -1,19 +1,22 @@ -import sys +# pylint: disable=C0115:missing-class-docstring +# pylint: disable=C0116:missing-function-docstring +# pylint: disable=C0103:invalid-name -# sys.path.append(".") # Adds the module to path +# Use this only when running the test locally. +# import sys +# sys.path.append(".") # Adds the module to path. import unittest -from matplotlib import pyplot +from numpy import pi +from numpy.random import randn from deeptrack import sequences - from deeptrack.optics import Fluorescence from deeptrack.scatterers import Ellipse -import numpy as np - class TestSequences(unittest.TestCase): + def test_Sequence(self): optics = Fluorescence( output_region=(0, 0, 32, 32), @@ -23,27 +26,29 @@ def test_Sequence(self): position=(16, 16), intensity=1, radius=(1.5e-6, 1e-6), - rotation=0, # This will be the value at time 0. - upsample=2, + rotation=0, # This will be the value at time 0 + #upsample=2, ) def get_rotation(sequence_length, previous_value): - return previous_value + 2 * np.pi / sequence_length + return previous_value + 2 * pi / sequence_length - rotating_ellipse = sequences.Sequential(ellipse, rotation=get_rotation) + rotating_ellipse = ellipse.to_sequential(rotation=get_rotation) imaged_rotating_ellipse = optics(rotating_ellipse) imaged_rotating_ellipse_sequence = sequences.Sequence( imaged_rotating_ellipse, sequence_length=5 ) imaged_rotating_ellipse_sequence.store_properties() - self.assertIsInstance(imaged_rotating_ellipse_sequence, sequences.Sequence) + self.assertIsInstance(imaged_rotating_ellipse_sequence, + sequences.Sequence) outputs = imaged_rotating_ellipse_sequence() for i, out in enumerate(outputs): - self.assertAlmostEqual(out.get_property("rotation"), 2 * i * np.pi / 5) + self.assertAlmostEqual(out.get_property("rotation"), + 2 * i * pi / 5) def test_Dependent_Sequential(self): @@ -54,32 +59,35 @@ def test_Dependent_Sequential(self): position_unit="pixel", position=(16, 16), radius=(1.5e-6, 1e-6), - rotation=0, # This will be the value at time 0. - upsample=2, + rotation=0, # This will be the value at time 0 + #upsample=2, ) def get_rotation(sequence_length, previous_value): - return previous_value + 2 * np.pi / sequence_length + return previous_value + 2 * pi / sequence_length def get_intensity(rotation): return rotation * 2 - rotating_ellipse = sequences.Sequential( - ellipse, rotation=get_rotation, intensity=get_intensity - ) + rotating_ellipse = ellipse.to_sequential(rotation=get_rotation, + intensity=get_intensity) + imaged_rotating_ellipse = optics(rotating_ellipse) imaged_rotating_ellipse_sequence = sequences.Sequence( imaged_rotating_ellipse, sequence_length=5 ) imaged_rotating_ellipse_sequence.store_properties() - self.assertIsInstance(imaged_rotating_ellipse_sequence, sequences.Sequence) + self.assertIsInstance(imaged_rotating_ellipse_sequence, + sequences.Sequence) outputs = imaged_rotating_ellipse_sequence() for i, out in enumerate(outputs): - self.assertAlmostEqual(out.get_property("rotation"), 2 * i * np.pi / 5) - self.assertAlmostEqual(out.get_property("intensity"), 4 * i * np.pi / 5) + self.assertAlmostEqual(out.get_property("rotation"), + 2 * i * pi / 5) + self.assertAlmostEqual(out.get_property("intensity"), + 4 * i * pi / 5) def test_RepeatedParticle(self): @@ -88,28 +96,29 @@ def test_RepeatedParticle(self): ) ellipse = Ellipse( position_unit="pixel", - position=lambda: np.random.randn(2) * 4 + (16, 16), + position=lambda: randn(2) * 4 + (16, 16), radius=(1.5e-6, 1e-6), rotation=0, # This will be the value at time 0. - upsample=2, + #upsample=2, ) def get_rotation(sequence_length, previous_value): - return previous_value + 2 * np.pi / sequence_length + return previous_value + 2 * pi / sequence_length def get_intensity(rotation): return rotation * 2 - rotating_ellipse = sequences.Sequential( - ellipse, rotation=get_rotation, intensity=get_intensity - ) + rotating_ellipse = ellipse.to_sequential(rotation=get_rotation, + intensity=get_intensity) + imaged_rotating_ellipse = optics(rotating_ellipse ^ 2) imaged_rotating_ellipse_sequence = sequences.Sequence( imaged_rotating_ellipse, sequence_length=5 ) imaged_rotating_ellipse_sequence.store_properties() - self.assertIsInstance(imaged_rotating_ellipse_sequence, sequences.Sequence) + self.assertIsInstance(imaged_rotating_ellipse_sequence, + sequences.Sequence) imaged_rotating_ellipse_sequence.update() outputs = imaged_rotating_ellipse_sequence() @@ -120,10 +129,10 @@ def get_intensity(rotation): self.assertEqual(len(rotations), 2) self.assertEqual(len(intensity), 2) self.assertEqual(len(positions), 2) - self.assertAlmostEqual(rotations[0], 2 * i * np.pi / 5) - self.assertAlmostEqual(rotations[1], 2 * i * np.pi / 5) - self.assertAlmostEqual(intensity[0], 4 * i * np.pi / 5) - self.assertAlmostEqual(intensity[1], 4 * i * np.pi / 5) + self.assertAlmostEqual(rotations[0], 2 * i * pi / 5) + self.assertAlmostEqual(rotations[1], 2 * i * pi / 5) + self.assertAlmostEqual(intensity[0], 4 * i * pi / 5) + self.assertAlmostEqual(intensity[1], 4 * i * pi / 5) self.assertNotEqual(positions[0][0], positions[1][0]) self.assertNotEqual(positions[0][1], positions[1][1]) @@ -138,26 +147,27 @@ def test_DistributedRepeatedParticle(self): position_unit="pixel", position=lambda _ID: positions[_ID[-1]], radius=(1.5e-6, 1e-6), - rotation=0, # This will be the value at time 0. - upsample=2, + rotation=0, # This will be the value at time 0 + #upsample=2, ) def get_rotation(sequence_length, previous_value): - return previous_value + 2 * np.pi / sequence_length + return previous_value + 2 * pi / sequence_length def get_intensity(rotation): return rotation * 2 - rotating_ellipse = sequences.Sequential( - ellipse, rotation=get_rotation, intensity=get_intensity - ) + rotating_ellipse = ellipse.to_sequential(rotation=get_rotation, + intensity=get_intensity) + imaged_rotating_ellipse = optics(rotating_ellipse ^ 2) imaged_rotating_ellipse_sequence = sequences.Sequence( imaged_rotating_ellipse, sequence_length=5 ) imaged_rotating_ellipse_sequence.store_properties() - self.assertIsInstance(imaged_rotating_ellipse_sequence, sequences.Sequence) + self.assertIsInstance(imaged_rotating_ellipse_sequence, + sequences.Sequence) imaged_rotating_ellipse_sequence.update() outputs = imaged_rotating_ellipse_sequence() @@ -168,13 +178,15 @@ def get_intensity(rotation): self.assertEqual(len(rotations), 2) self.assertEqual(len(intensity), 2) self.assertEqual(len(positions), 2) - self.assertAlmostEqual(rotations[0], 2 * i * np.pi / 5) - self.assertAlmostEqual(rotations[1], 2 * i * np.pi / 5) - self.assertAlmostEqual(intensity[0], 4 * i * np.pi / 5) - self.assertAlmostEqual(intensity[1], 4 * i * np.pi / 5) - - self.assertSequenceEqual(list(p_positions[0]), list(positions[0])) - self.assertSequenceEqual(list(p_positions[1]), list(positions[1])) + self.assertAlmostEqual(rotations[0], 2 * i * pi / 5) + self.assertAlmostEqual(rotations[1], 2 * i * pi / 5) + self.assertAlmostEqual(intensity[0], 4 * i * pi / 5) + self.assertAlmostEqual(intensity[1], 4 * i * pi / 5) + + self.assertSequenceEqual(list(p_positions[0]), + list(positions[0])) + self.assertSequenceEqual(list(p_positions[1]), + list(positions[1])) if __name__ == "__main__": diff --git a/tutorials/3-advanced-topics/DTAT306_properties.ipynb b/tutorials/3-advanced-topics/DTAT306_properties.ipynb index 5f57d9629..bea99b655 100644 --- a/tutorials/3-advanced-topics/DTAT306_properties.ipynb +++ b/tutorials/3-advanced-topics/DTAT306_properties.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# deeptrack.properties\n", + "# DTAT306. deeptrack.properties\n", "\n", "\"Open" ] @@ -29,23 +29,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is a property?\n", + "## 1. What is a Property?\n", "\n", - "Each feature (instance of the class `Feature`, see [features_example](DTAT301_features.ipynb)) can have several properties (instances of the class `Property`). These properties can be constants, functions, lists, dictionaries, iterators, or slices, providing flexibility in defining and controlling different aspects of the system being modelled.\n", + "Each feature (instance of the class `Feature`, see [DTAT301_features.ipynb](DTAT301_features.ipynb)) can have several properties (instances of the class `Property`). These properties can be constants, functions, lists, dictionaries, iterators, or slices, providing flexibility in defining and controlling different aspects of the system being modelled.\n", "\n", "A propety has a value accessible through the field `current_value`, whose data type is not restricted. \n", "This value is updated through a sampling rule (method `.update()`), which is passed to the class constructor on initialization. " ] }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -56,7 +47,7 @@ "A sampling rule is defined when an instance of the class `Property` is created and can be of any type. \n", "When calling `.update()`, the value of the property is updated according to the first of the following that applies:\n", " \n", - "1. If the sampling rule has a method `.sample()`, call `.sample()` and return the output.\n", + "1. If the sampling rule is a `DeepTrackNode` (e.g., another property or feature), it returns the value sampled.\n", "\n", "2. If the sampling rule is a ``dict``, sample each value and combine the result into a new ``dict`` using the original keys. \n", "\n", @@ -69,15 +60,6 @@ "6. If none of the above apply, return the sampling rule itself." ] }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "from deeptrack.properties import Property, PropertyDict, SequentialProperty" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -91,7 +73,16 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.properties import Property" + ] + }, + { + "cell_type": "code", + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -104,7 +95,7 @@ } ], "source": [ - "# Number.\n", + "# Number\n", "\n", "P = Property(1)\n", "print(f\"The current value of the property is {P()}\")\n", @@ -115,7 +106,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -128,7 +119,7 @@ } ], "source": [ - "# Tuple.\n", + "# Tuple\n", "\n", "P = Property((1, [2, 3], None))\n", "print(f\"The current value of the property is {P()}\")\n", @@ -139,20 +130,22 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The current value of the property is [, 1, {}]\n", - "The current value of the property is [, 1, {}]\n" + "The current value of the property is [, 1, {}]\n", + "The current value of the property is [, 1, {}]\n" ] } ], "source": [ - "# Wrapped list.\n", + "# Wrapped list\n", + "\n", + "import numpy as np\n", "\n", "P = Property(lambda: [np.random.rand, 1, {}])\n", "print(f\"The current value of the property is {P()}\")\n", @@ -172,23 +165,23 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The current value of the property is 9\n", - "The current value of the property is 0\n", + "The current value of the property is 1\n", "The current value of the property is 0\n", - "The current value of the property is 2\n", - "The current value of the property is 3\n" + "The current value of the property is 4\n", + "The current value of the property is 4\n", + "The current value of the property is 6\n" ] } ], "source": [ - "# Function.\n", + "# Function\n", "\n", "P = Property(lambda: np.random.randint(0, 10))\n", "\n", @@ -199,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -207,15 +200,15 @@ "output_type": "stream", "text": [ "The current value of the property is 0\n", + "The current value of the property is 1\n", "The current value of the property is 0\n", "The current value of the property is 0\n", - "The current value of the property is 1\n", "The current value of the property is 0\n" ] } ], "source": [ - "# Binary choice.\n", + "# Binary choice\n", "\n", "P = Property(lambda: 1 if np.random.rand() > 0.75 else 0)\n", "\n", @@ -235,23 +228,23 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The current value of the property is 0.6058058486216974\n", - "The current value of the property is 0.4388130582114753\n", - "The current value of the property is 0.4492343759700439\n", - "The current value of the property is 0.43076786487677377\n", - "The current value of the property is 0.5294829032012791\n" + "The current value of the property is 0.24884174295246797\n", + "The current value of the property is 0.2730654583052251\n", + "The current value of the property is 0.1139662711281052\n", + "The current value of the property is 0.9253983785419675\n", + "The current value of the property is 0.35316904315837794\n" ] } ], "source": [ - "# Function with no input.\n", + "# Function with no input\n", "\n", "P = Property(np.random.rand)\n", "\n", @@ -262,23 +255,23 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The current value of the property is -2.7800167909302074\n", - "The current value of the property is -4.99826898249571\n", - "The current value of the property is 9.239257264425211\n", - "The current value of the property is 10.638607727647866\n", - "The current value of the property is 7.443379615451991\n" + "The current value of the property is 1.3976162321540424\n", + "The current value of the property is -9.259918689939429\n", + "The current value of the property is 9.1451360378318\n", + "The current value of the property is -2.5319257907450745\n", + "The current value of the property is -5.395141874900193\n" ] } ], "source": [ - "# Wrapped function.\n", + "# Wrapped function\n", "\n", "P = Property(lambda: np.random.normal(1, 5))\n", "\n", @@ -298,7 +291,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -319,7 +312,7 @@ } ], "source": [ - "# Iterator.\n", + "# Iterator\n", "\n", "P = Property(iter([1, 2, 3, 4, 5]))\n", "\n", @@ -330,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -351,7 +344,7 @@ } ], "source": [ - "# Function.\n", + "# Function\n", "\n", "fibbonacci = [1, 1]\n", "\n", @@ -377,27 +370,27 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The current independent property is 0.6047967671299797\n", - "The current dependent property is 1.6047967671299797\n", + "The current independent property is 0.13063704539164633\n", + "The current dependent property is 1.1306370453916463\n", "\n", - "The current independent property is 0.9632829097510287\n", - "The current dependent property is 1.9632829097510287\n", + "The current independent property is 0.12109753963721859\n", + "The current dependent property is 1.1210975396372187\n", "\n", - "The current independent property is 0.5707308224509307\n", - "The current dependent property is 1.5707308224509307\n", + "The current independent property is 0.2239520167702148\n", + "The current dependent property is 1.2239520167702147\n", "\n", - "The current independent property is 0.17336784973310793\n", - "The current dependent property is 1.173367849733108\n", + "The current independent property is 0.5431591185980041\n", + "The current dependent property is 1.543159118598004\n", "\n", - "The current independent property is 0.2129111133514252\n", - "The current dependent property is 1.2129111133514252\n", + "The current independent property is 0.0774493433606398\n", + "The current dependent property is 1.0774493433606398\n", "\n" ] } @@ -412,22 +405,21 @@ "\n", "# Link the properties with add_dependency() or add_child().\n", "# PropertyDict (see below) automatically links the properties.\n", - "dependent_number.add_dependency(random_number) # Alternative 1.\n", - "# random_number.add_child(dependent_number) # Alternative 2.\n", + "dependent_number.add_dependency(random_number) # Alternative 1\n", + "# random_number.add_child(dependent_number) # Alternative 2\n", "\n", "for _ in range(5):\n", " dependent_number.update()\n", " \n", " print(f\"The current independent property is {random_number()}\")\n", - " print(f\"The current dependent property is {dependent_number()}\\n\")\n", - " \n" + " print(f\"The current dependent property is {dependent_number()}\\n\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. What is a PropertyDict?\n", + "## 2. What is a PropertyDict?\n", "\n", "Another class contained in the module deeptrack.properties is `PropertyDict`. This is a dictionary of properties (keys: name of properties; values: properties) complemented by utility methods to manage collections of properties. These include:\n", "\n", @@ -442,32 +434,514 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The current properties in property_dict are {'foo': 'foo', 'barorbaz': 'baz', 'foobarorbaz': 'foobaz'}\n", - "The current properties in property_dict are {'foo': 'foo', 'barorbaz': 'baz', 'foobarorbaz': 'foobaz'}\n", - "The current properties in property_dict are {'foo': 'foo', 'barorbaz': 'bar', 'foobarorbaz': 'foobar'}\n", - "The current properties in property_dict are {'foo': 'foo', 'barorbaz': 'bar', 'foobarorbaz': 'foobar'}\n", - "The current properties in property_dict are {'foo': 'foo', 'barorbaz': 'baz', 'foobarorbaz': 'foobaz'}\n" + "The current properties in property_dict are {'foo': 'foo', 'bar_or_baz': 'baz', 'foo_bar_or_baz': 'foobaz'}\n", + "The current properties in property_dict are {'foo': 'foo', 'bar_or_baz': 'baz', 'foo_bar_or_baz': 'foobaz'}\n", + "The current properties in property_dict are {'foo': 'foo', 'bar_or_baz': 'bar', 'foo_bar_or_baz': 'foobar'}\n", + "The current properties in property_dict are {'foo': 'foo', 'bar_or_baz': 'baz', 'foo_bar_or_baz': 'foobaz'}\n", + "The current properties in property_dict are {'foo': 'foo', 'bar_or_baz': 'baz', 'foo_bar_or_baz': 'foobaz'}\n" ] } ], "source": [ + "from deeptrack.properties import PropertyDict\n", + "\n", "property_dict = PropertyDict(\n", " foo=\"foo\",\n", - " barorbaz=lambda:np.random.choice([\"bar\", \"baz\"]),\n", - " foobarorbaz=lambda foo, barorbaz: foo + barorbaz,\n", + " bar_or_baz=lambda: np.random.choice([\"bar\", \"baz\"]),\n", + " foo_bar_or_baz=lambda foo, bar_or_baz: foo + bar_or_baz,\n", ")\n", "\n", "for _ in range(5):\n", " property_dict.update()\n", " print(f\"The current properties in property_dict are {property_dict()}\")" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. What is a SequentialProperty?\n", + "`SequentialProperty` is a class that extends `Property` with iterative utilities. It lets the user encapsulate sampling rules and iterator logic in a single object with the methods:\n", + "* `set_sequence_length()`, which specifies the sequence length.\n", + "* `set_current_step()`, which specifies the current index in the sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sequence length = 5\n", + "Current sample = 0.6119682128127352\n" + ] + } + ], + "source": [ + "from deeptrack.properties import SequentialProperty\n", + "\n", + "# Set the SequentialProperty sampling rule to be a random number generator.\n", + "seq_prop = SequentialProperty(sampling_rule=np.random.rand)\n", + "\n", + "# Specify length of sequence.\n", + "seq_prop.set_sequence_length(5)\n", + "print(f\"Sequence length = {seq_prop.sequence_length()}\")\n", + "\n", + "# Evaluate sampling rule.\n", + "print(f\"Current sample = {seq_prop.sample()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.1. Evaluating the Sequence\n", + "\n", + "To demostrate the workings of `SequentialProperty`, iterate it by setting explicitly the index in the sequence, evaluating it, storing the currently evaluated value, and printing the sequence evaluated at each step." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: [0.838772215753703]\n", + "1: [0.838772215753703, 0.31826186152196734]\n", + "2: [0.838772215753703, 0.31826186152196734, 0.35805642344740995]\n", + "3: [0.838772215753703, 0.31826186152196734, 0.35805642344740995, 0.3820729835113815]\n", + "4: [0.838772215753703, 0.31826186152196734, 0.35805642344740995, 0.3820729835113815, 0.10595747296519764]\n" + ] + } + ], + "source": [ + "for index in range(seq_prop.sequence_length()):\n", + " # 1. Set the current index of the sequence\n", + " seq_prop.set_current_index(index)\n", + "\n", + " # 2. Evaluate feature\n", + " current_value = seq_prop.sample()\n", + "\n", + " # 3. Store the current value\n", + " seq_prop.store(current_value)\n", + "\n", + " # 4. Print all the stored values so far\n", + " print(f\"{index}: {seq_prop.previous()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2. Simulating a Random Walker with a Markov Chain\n", + "\n", + "We will now simulate a random walker in one dimension by iterating the `SequentialProperty` with random number sampling rule as the displacements in each time step and updating the walkers position as a cummulative sum of the sampled displacements." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "random_walker = SequentialProperty(\n", + " initial_sampling_rule=lambda: 0,\n", + " sampling_rule=lambda previous_value: previous_value + np.random.rand(),\n", + " sequence_index=0,\n", + " sequence_length=100,\n", + ")\n", + "\n", + "trajectory = []\n", + "for step in range(random_walker.sequence_length()):\n", + " random_walker.sequence_index.set_value(step)\n", + " trajectory.append(random_walker())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plot the resulting trajectory." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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nExcXFy9sbbb7axcuVJj3UBHjhzBaq3/MeTn54fL5QI01i3pRMY2LS2N0tJSsrKy6s75+uuvcblcDB8+vGkBmjRd1UPMmzfPsNlsxptvvmls3brVuO6664yIiAgjPz/f7Gge7cYbbzTCw8ONb7/91jh48GDd48iRI3Xn3HDDDUZSUpLx9ddfG2vWrDHS0tKMtLQ0E1N7j1+udjEMjXVzWbVqleHv72889thjRk5OjvHee+8Zbdq0Md599926c5544gkjIiLC+PTTT42NGzcao0eP1vLPRpg0aZLRsWPHuqW2H3/8sdG+fXvjrrvuqjtHY9145eXlxrp164x169YZgPHMM88Y69atM/bu3WsYxomN7XnnnWcMGjTIWLlypfH9998b3bt311LbhnjhhReMpKQkIzAw0Bg2bJixYsUKsyN5POC4jzfeeKPunKNHjxo33XST0a5dO6NNmzbGxRdfbBw8eNC80F7kf8uHxrr5LFy40Ojbt69hs9mMlJQU4x//+Ee9510ul3H//fcbsbGxhs1mM84++2wjOzvbpLSey263G7fddpuRlJRkBAUFGV26dDHuvfdew+Fw1J2jsW68b7755ri/oydNmmQYxomN7aFDh4wJEyYYISEhRlhYmDF58mSjvLy8ydkshvGLreREREREWpjXz/kQERGR1kXlQ0RERNxK5UNERETcSuVDRERE3ErlQ0RERNxK5UNERETcSuVDRERE3ErlQ0RERNxK5UNERETcSuVDRERE3ErlQ0RERNxK5UNERETc6v8B9ByTSP7BxKYAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from matplotlib import pyplot as plt \n", + "\n", + "plt.plot(trajectory);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.3. Accessing the Stored Values\n", + "\n", + "This section will show different methods of accessing stored values in a `SequentialProperty`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3.3.1. Accessing All Stored Values\n", + "\n", + "This can be done with two methods, yielding equivalent results. One involves using `previous()`." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0,\n", + " 0.9311194313810336,\n", + " 1.8024140678124319,\n", + " 2.501844350699782,\n", + " 2.9094661734110816,\n", + " 3.8476539422946585,\n", + " 4.086416719412897,\n", + " 4.6830635933223785,\n", + " 5.037148494561957,\n", + " 5.474577708838074,\n", + " 5.8955226659302475,\n", + " 6.755603111108819,\n", + " 6.914792736964355,\n", + " 7.293093194719748,\n", + " 7.826151931769779,\n", + " 7.82811031937069,\n", + " 8.656926665663386,\n", + " 8.781438101063287,\n", + " 9.617230678371058,\n", + " 9.89655673445487,\n", + " 10.32384450932856,\n", + " 11.158858312240758,\n", + " 11.91325377946318,\n", + " 11.995083365075992,\n", + " 12.058317667244356,\n", + " 12.837276716168308,\n", + " 13.3568770548243,\n", + " 13.918587281996215,\n", + " 14.74329087903931,\n", + " 15.125940146827933,\n", + " 15.834527185043934,\n", + " 16.37462354208352,\n", + " 16.540156213125226,\n", + " 16.739393211854406,\n", + " 17.232377456876627,\n", + " 18.02023746298754,\n", + " 18.476154468162115,\n", + " 18.935217696302526,\n", + " 19.369648527315956,\n", + " 20.361454531135276,\n", + " 20.92505733707207,\n", + " 21.152663559969177,\n", + " 21.878007843462647,\n", + " 22.796971328373143,\n", + " 23.499190226607215,\n", + " 24.011294213975553,\n", + " 24.573926741320708,\n", + " 24.833060373484848,\n", + " 24.96298540519224,\n", + " 24.976714080599717,\n", + " 25.85933139887143,\n", + " 25.932221172645306,\n", + " 25.971867436447095,\n", + " 26.329879737826367,\n", + " 26.839318947055236,\n", + " 27.251669836722765,\n", + " 28.223447168492342,\n", + " 28.91194441519757,\n", + " 29.13999314400922,\n", + " 29.81449024553546,\n", + " 30.541900298258724,\n", + " 30.86550794069003,\n", + " 31.62551765220428,\n", + " 31.787181579268985,\n", + " 32.700073340155754,\n", + " 33.31226383481802,\n", + " 34.048902974137135,\n", + " 34.31530159469335,\n", + " 34.90555283543322,\n", + " 35.112864904116414,\n", + " 35.93413385078996,\n", + " 36.84467257197482,\n", + " 37.19772257530309,\n", + " 38.034877573318624,\n", + " 38.14732987568684,\n", + " 38.72354989148179,\n", + " 39.02898656691594,\n", + " 39.281663167042765,\n", + " 39.38367556414773,\n", + " 39.567328863437346,\n", + " 40.51797535587875,\n", + " 40.94702211715974,\n", + " 41.49349504795441,\n", + " 42.087122500352535,\n", + " 42.54817618367076,\n", + " 43.13608744393932,\n", + " 43.899456977950116,\n", + " 44.52524888116425,\n", + " 44.879165981103014,\n", + " 45.57967527131823,\n", + " 46.16149566742987,\n", + " 46.357926475455834,\n", + " 46.92267721456783,\n", + " 47.54748958784514,\n", + " 47.93924772036247,\n", + " 47.955798989729736,\n", + " 48.87184928951471,\n", + " 49.646712793666396,\n", + " 49.65440488154153,\n", + " 49.840497386945884]" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_walker.previous()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, it is possible to access the `DeepTrackDataDict` attribute of the class." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0,\n", + " 0.9311194313810336,\n", + " 1.8024140678124319,\n", + " 2.501844350699782,\n", + " 2.9094661734110816,\n", + " 3.8476539422946585,\n", + " 4.086416719412897,\n", + " 4.6830635933223785,\n", + " 5.037148494561957,\n", + " 5.474577708838074,\n", + " 5.8955226659302475,\n", + " 6.755603111108819,\n", + " 6.914792736964355,\n", + " 7.293093194719748,\n", + " 7.826151931769779,\n", + " 7.82811031937069,\n", + " 8.656926665663386,\n", + " 8.781438101063287,\n", + " 9.617230678371058,\n", + " 9.89655673445487,\n", + " 10.32384450932856,\n", + " 11.158858312240758,\n", + " 11.91325377946318,\n", + " 11.995083365075992,\n", + " 12.058317667244356,\n", + " 12.837276716168308,\n", + " 13.3568770548243,\n", + " 13.918587281996215,\n", + " 14.74329087903931,\n", + " 15.125940146827933,\n", + " 15.834527185043934,\n", + " 16.37462354208352,\n", + " 16.540156213125226,\n", + " 16.739393211854406,\n", + " 17.232377456876627,\n", + " 18.02023746298754,\n", + " 18.476154468162115,\n", + " 18.935217696302526,\n", + " 19.369648527315956,\n", + " 20.361454531135276,\n", + " 20.92505733707207,\n", + " 21.152663559969177,\n", + " 21.878007843462647,\n", + " 22.796971328373143,\n", + " 23.499190226607215,\n", + " 24.011294213975553,\n", + " 24.573926741320708,\n", + " 24.833060373484848,\n", + " 24.96298540519224,\n", + " 24.976714080599717,\n", + " 25.85933139887143,\n", + " 25.932221172645306,\n", + " 25.971867436447095,\n", + " 26.329879737826367,\n", + " 26.839318947055236,\n", + " 27.251669836722765,\n", + " 28.223447168492342,\n", + " 28.91194441519757,\n", + " 29.13999314400922,\n", + " 29.81449024553546,\n", + " 30.541900298258724,\n", + " 30.86550794069003,\n", + " 31.62551765220428,\n", + " 31.787181579268985,\n", + " 32.700073340155754,\n", + " 33.31226383481802,\n", + " 34.048902974137135,\n", + " 34.31530159469335,\n", + " 34.90555283543322,\n", + " 35.112864904116414,\n", + " 35.93413385078996,\n", + " 36.84467257197482,\n", + " 37.19772257530309,\n", + " 38.034877573318624,\n", + " 38.14732987568684,\n", + " 38.72354989148179,\n", + " 39.02898656691594,\n", + " 39.281663167042765,\n", + " 39.38367556414773,\n", + " 39.567328863437346,\n", + " 40.51797535587875,\n", + " 40.94702211715974,\n", + " 41.49349504795441,\n", + " 42.087122500352535,\n", + " 42.54817618367076,\n", + " 43.13608744393932,\n", + " 43.899456977950116,\n", + " 44.52524888116425,\n", + " 44.879165981103014,\n", + " 45.57967527131823,\n", + " 46.16149566742987,\n", + " 46.357926475455834,\n", + " 46.92267721456783,\n", + " 47.54748958784514,\n", + " 47.93924772036247,\n", + " 47.955798989729736,\n", + " 48.87184928951471,\n", + " 49.646712793666396,\n", + " 49.65440488154153,\n", + " 49.840497386945884]" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_walker.data[()].current_value()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3.3.2. Accessing the Last Element" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "49.840497386945884" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_walker.current_value()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3.3.3. Accessing the Second-to-Last Element" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "49.65440488154153" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_walker.previous_value()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3.3.4 Accessing the Third-to-Last Element" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "49.646712793666396" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "random_walker.previous_values()[-1]" + ] } ], "metadata": { diff --git a/tutorials/3-advanced-topics/DTAT341_sequences.ipynb b/tutorials/3-advanced-topics/DTAT341_sequences.ipynb index 70adce1d1..6c4712351 100644 --- a/tutorials/3-advanced-topics/DTAT341_sequences.ipynb +++ b/tutorials/3-advanced-topics/DTAT341_sequences.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# deeptrack.sequences\n", + "# DTAT341. deeptrack.sequences\n", "\n", "\"Open" ] @@ -25,48 +25,175 @@ "This advanced tutorial introduces the module deeptrack.sequences." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. What is a Sequence?\n", + "\n", + "Sequences are lists of (typically) images, where any image in the series may depend on previous images. They can be used to create videos. They can also be used to resolve the same feature several times, changing only a subset of the properties of the feature. An example is imaging a sample at several focal planes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To resolve a sequence, you create an instance of the feature `Sequence` with the feature you want to resolve and `sequence_length` as parameters.\n", + "\n", + "By default, properties remain constant so so that each step in the sequence is by default the same.\n", + "\n", + "To make the properties change, you should call the method `myfeature.to_sequential()` for the feature you want to change during the sequence, and a set of functions as keyword arguments. These functions will be converted to instances of `SequentialProperty`, which behave similarly to regular properties.\n", + "\n", + "For more information about how sequential properties work, see [DTAT306_properties.ipynb](DTAT306_properties.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Example - A Rotating Ellipse\n", + "\n", + "To demonstrate the concept of `sequences`, we will implement a simple case of a rotating ellipse." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the optical device ..." + ] + }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "import numpy as np" + "from deeptrack.optics import Fluorescence\n", + "\n", + "optics = Fluorescence(\n", + " NA=0.6,\n", + " magnification=10,\n", + " resolution=1e-6,\n", + " wavelength=633e-9,\n", + " output_region=(0, 0, 32, 32),\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is a sequence?\n", - "\n", - "Sequences are lists of images, where any image in the series may depend on previous images. They can be used to create videos. They can also be used to resolve the same feature-set several times, changing only a subset of the properties of the features; an example would be imaging a sample at several focal planes.\n", + "... define the elliptical scatterer with initial rotation value ..." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.scatterers import Ellipse\n", "\n", - "To resolve a sequence, you create an instance of the feature `Sequence`, with the feature you want to resolve as the first input, and `sequence_length` as a second input. \n", + "ellipse = Ellipse(\n", + " position_unit=\"pixel\",\n", + " position=(16, 16),\n", + " intensity=1,\n", + " radius=(1.5e-6, 1e-6),\n", + " rotation=0, # Rotation at time 0\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... define the function to get the next rotation value ..." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", "\n", - "By default, properties remain constant in a series. This means that each step in the sequence is by default the same.\n", + "def update_rotation(sequence_length, previous_value):\n", + " delta = 2 * np.pi / sequence_length\n", + " next_value = previous_value + delta\n", + " return next_value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... make the scatterer sequential by specifying the properties to be sequentially evaluated as arguments in `ellipse.to_sequential()` (for this case, it is the rotation of the ellipse) ..." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "rotating_ellipse = ellipse.to_sequential(rotation=update_rotation)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... image the sequential ellipse with the microscope ..." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "imaged_rotating_ellipse = optics(rotating_ellipse)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... encapsulate as a `Sequence` object and specify the sequence length ..." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sequences import Sequence\n", "\n", - "To make the properties change, you should call the function `Sequential` with the feature you want to change during the sequence as the first input, and a set of functions as keyword arguments. These functions will be converted to instances of `SequentialProperty`, which behave similarly to regular properties. For more information about how sequential properties work, see [properties_example.ipynb](properties_example.ipynb)." + "imaged_rotating_ellipse_sequence = Sequence(\n", + " imaged_rotating_ellipse,\n", + " sequence_length=50,\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Example - A rotating ellipse" + "... and finally image the rotating ellipse." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/Users/giovannivolpe/Documents/GitHub/DeepTrack2/deeptrack/scatterers.py:245: UserWarning: Setting upsample != 1 is deprecated. Please, instead use dt.Upscale(f, factor=2)\n", - " warnings.warn(\n", "INFO:matplotlib.animation:Animation.save using \n" ] }, @@ -258,42 +385,42 @@ "\n", "\n", "
\n", - " \n", + " \n", "
\n", - " \n", + " oninput=\"anim00c9cfc0cfae42b09ca3337395ca97a3.set_frame(parseInt(this.value));\">\n", "
\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", "
\n", - "
\n", - " \n", - " \n", - " Once\n", + " \n", - " \n", - " Loop\n", + " \n", - " \n", + " \n", "
\n", "
\n", "
\n", @@ -303,9 +430,9 @@ " /* Instantiate the Animation class. */\n", " /* The IDs given should match those used in the template above. */\n", " (function() {\n", - " var img_id = \"_anim_img6dd719d0effd4db5ad594b7e79251c2e\";\n", - " var slider_id = \"_anim_slider6dd719d0effd4db5ad594b7e79251c2e\";\n", - " var loop_select_id = \"_anim_loop_select6dd719d0effd4db5ad594b7e79251c2e\";\n", + " var img_id = \"_anim_img00c9cfc0cfae42b09ca3337395ca97a3\";\n", + " var slider_id = \"_anim_slider00c9cfc0cfae42b09ca3337395ca97a3\";\n", + " var loop_select_id = \"_anim_loop_select00c9cfc0cfae42b09ca3337395ca97a3\";\n", " var frames = new Array(50);\n", " \n", " frames[0] = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoAAAAHgCAYAAAA10dzkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90\\\n", @@ -5955,7 +6082,7 @@ " /* set a timeout to make sure all the above elements are created before\n", " the object is initialized. */\n", " setTimeout(function() {\n", - " anim6dd719d0effd4db5ad594b7e79251c2e = new Animation(frames, img_id, slider_id, 33.0,\n", + " anim00c9cfc0cfae42b09ca3337395ca97a3 = new Animation(frames, img_id, slider_id, 33.0,\n", " loop_select_id);\n", " }, 0);\n", " })()\n", @@ -5968,16 +6095,6 @@ "metadata": {}, "output_type": "display_data" }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, { "data": { "image/png": "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", @@ -5990,39 +6107,101076 @@ } ], "source": [ - "from deeptrack.optics import Fluorescence\n", - "from deeptrack.scatterers import Ellipse\n", - "from deeptrack.sequences import Sequential, Sequence\n", + "imaged_rotating_ellipse_sequence.update().plot();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Example - Brownian Motion\n", "\n", + "A more advanced application of `sequences` is to simulate a video of the Brownian motion of a microscopic particle." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define the optical device ..." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ "optics = Fluorescence(\n", " NA=0.6,\n", " magnification=10,\n", " resolution=1e-6,\n", " wavelength=633e-9,\n", " output_region=(0, 0, 32, 32),\n", - ")\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... define a scatterer (in this case, a microscopic sphere) with its initial position ...\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.scatterers import Sphere\n", "\n", - "ellipse = Ellipse(\n", + "particle = Sphere(\n", " position_unit=\"pixel\",\n", - " position=(16, 16),\n", + " position=(16, 16), # Position at time 0\n", + " radius=(.1e-6),\n", " intensity=1,\n", - " radius=(1.5e-6, 1e-6),\n", - " rotation=0, # This will be the value at time 0.\n", - " upsample=2\n", - ")\n", - "\n", - "def get_rotation(sequence_length, previous_value):\n", - " return previous_value + 2 * np.pi / sequence_length\n", - "\n", - "\n", - "rotating_ellipse = Sequential(ellipse, rotation=get_rotation) \n", - "\n", - "imaged_rotating_ellipse = optics(rotating_ellipse)\n", - "\n", - "imaged_rotating_ellipse_sequence = Sequence(imaged_rotating_ellipse,\n", - " sequence_length=50)\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... define a function to update the scatterer's position property with normally distributed perturbations ..." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "def update_position(sequence_length, previous_value):\n", + " delta = np.random.standard_normal(2)\n", + " next_value = previous_value + delta\n", + " return next_value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... make the scatterer sequential by specifying the properties to be sequentially evaluated as arguments in `particle.to_sequential()` (for this case, it is the position of the scatterer) ..." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "brownian_particle = particle.to_sequential(position=update_position)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... image the Brownian particle with the microscope ..." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "imaged_brownian_particle = optics(brownian_particle)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... encapsulate as a `Sequence` object and specify the sequence length ..." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "imaged_brownian_particle_sequence = Sequence(\n", + " imaged_brownian_particle,\n", + " sequence_length=100,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and finally image the Brownian particle." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:matplotlib.animation:Animation.save using \n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "imaged_brownian_particle_sequence.update().plot(cmap=\"gray\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.1 Simulating Multiple Brownian Particles\n", + "\n", + "You can also use the chain operator when imaging the brownian particle as a sequence. This will produce a set of scatterers that are updated independently of each other." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Chain multiple particles with `^` ..." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "imaged_brownian_particles = optics(brownian_particle ^ 10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... encapsulate as a `Sequence` object and specify the sequence length ..." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "imaged_brownian_particles_sequence = Sequence(\n", + " imaged_brownian_particles,\n", + " sequence_length=100,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and finally image the ensemble of Brownian particles." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:matplotlib.animation:Animation.save using \n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "imaged_brownian_particles_sequence.update().plot(cmap=\"gray\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Example - A Microscope Sequence\n", + "\n", + "Sequences are not exclusive to scatterers. You can evaluate sequentially the properties of any feature in DeepTrack2, including microscopes. This example will show how you can vary the wavelength property." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Deafine a microscope ..." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'Brightfield' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[19], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m optics \u001b[38;5;241m=\u001b[39m \u001b[43mBrightfield\u001b[49m(\n\u001b[1;32m 2\u001b[0m NA\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0.6\u001b[39m,\n\u001b[1;32m 3\u001b[0m magnification\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m10\u001b[39m,\n\u001b[1;32m 4\u001b[0m resolution\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1e-6\u001b[39m,\n\u001b[1;32m 5\u001b[0m wavelength\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m400e-9\u001b[39m, \u001b[38;5;66;03m# Initial wavelength at time step 0\u001b[39;00m\n\u001b[1;32m 6\u001b[0m output_region\u001b[38;5;241m=\u001b[39m(\u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m128\u001b[39m, \u001b[38;5;241m128\u001b[39m),\n\u001b[1;32m 7\u001b[0m )\n", + "\u001b[0;31mNameError\u001b[0m: name 'Brightfield' is not defined" + ] + } + ], + "source": [ + "optics = Brightfield(\n", + " NA=0.6,\n", + " magnification=10,\n", + " resolution=1e-6,\n", + " wavelength=400e-9, # Initial wavelength at time step 0\n", + " output_region=(0, 0, 128, 128),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... define a scatterer ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "particle = Sphere(\n", + " position_unit=\"pixel\",\n", + " position=(64, 64),\n", + " radius=(1e-6),\n", + " intensity=1,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... define function to update the microscope wavelength with constant increments ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def update_wavelength(sequence_length, previous_value):\n", + " delta = 5e-9 # nm\n", + " next_value = previous_value + delta\n", + " return next_value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... make the microscope sequential by specifying the properties to be sequentially evaluated as arguments in `optics.to_sequential()` (for this case, it is the wavelength of the illuminating light) ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "microscope_sequence = optics.to_sequential(wavelength=update_wavelength)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... image the particle with the microscope ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "variable_microscope = optics(particle)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... encapsulate as a `Sequence` object and specify the sequence length ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "imaged_microscope_sequence= Sequence(\n", + " variable_microscope,\n", + " sequence_length=100,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "... and finally observe the image of the scatterer as the wavelegth varies." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:matplotlib.animation:Animation.save using \n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + " \n", + "
\n", + " \n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "
\n", + "
\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ "\n", - "imaged_rotating_ellipse_sequence.update().plot()" + "imaged_microscope_sequence.update().plot(cmap=\"gray\");" ] } ], From 35727644e270e2e76a79e351896aa2ffd72a25e4 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 13:43:52 +0200 Subject: [PATCH 128/223] Update features.py --- deeptrack/features.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 3055be7c0..cb14c8a6b 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1316,22 +1316,23 @@ def action( else: return image_list - #TODO ***GV*** def update( self: Feature, **global_arguments: Any, ) -> Feature: - """Refreshes the feature to generate a new output. + """Refresh the feature to generate a new output. By default, when a feature is called multiple times, it returns the - same value. Calling `update()` forces the feature to recompute and + same value. + + Calling `update()` forces the feature to recompute and return a new value the next time it is evaluated. Parameters ---------- **global_arguments: Any - Optional global arguments that can be passed to modify the - feature update behavior. + Deprecated. Has no effect. Previously used to inject values + during update. Use `Arguments` or call-time overrides instead. Returns ------- @@ -1339,6 +1340,26 @@ def update( The updated feature instance, ensuring the next evaluation produces a fresh result. + Examples + ------- + >>> import deeptrack as dt + + >>> import numpy as np + >>> + >>> feature = dt.Value(value=lambda: np.random.rand()) + >>> output1 = feature() + >>> output1 + 0.9173610765203623 + + >>> output2 = feature() + >>> output2 # Same as before + 0.9173610765203623 + + >>> feature.update() # Feature updated + >>> output3 = feature() + >>> output3 + 0.13917950359184617 + """ if global_arguments: From cafb1e08a788ab319f64e6732c88989924224952 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 14:01:11 +0200 Subject: [PATCH 129/223] Update features.py --- deeptrack/features.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index cb14c8a6b..7f7e36126 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -149,7 +149,7 @@ def merge_features( import torch __all__ = [ - "Feature", #TODO ***GV*** + "Feature", "StructuralFeature", "Chain", "Branch", @@ -1539,10 +1539,9 @@ def seed( return seed - #TODO ***GV*** def bind_arguments( self: Feature, - arguments: Feature, + arguments: Arguments | Feature, ) -> Feature: """Bind another feature’s properties as arguments to this feature. @@ -1550,13 +1549,16 @@ def bind_arguments( to this feature, enabling shared configurations across multiple features. It is commonly used in advanced feature pipelines. - This method is often used in combination with the `Arguments` Feature, + This method is often used in combination with the `Arguments` feature, which provides a utility that helps manage and propagate feature arguments efficiently. + The values from `arguments` override the corresponding feature’s own + properties at call-time, but do not modify them permanently. + Parameters ---------- - arguments: Feature + arguments: Arguments or Feature The feature whose properties will be bound as arguments to this feature. @@ -1567,9 +1569,25 @@ def bind_arguments( Examples -------- - TODO method alone + >>> import deeptrack as dt - TODO use with Arguments + Create an `Arguments` feature: + >>> arguments = dt.Arguments(scale=2.0) + + Bind it with a pipeline: + >>> pipeline = dt.Value(value=3) >> dt.Add(value=1 * arguments.scale) + >>> pipeline.bind_arguments(arguments) + >>> result = pipeline() + >>> result + 5.0 + + Override the argument dynamically: + >>> result = pipeline(scale=1.0) + >>> result + 4.0 + + Without binding, the result would be still 5.0 as `scale` would still + be the original one. """ @@ -1577,13 +1595,13 @@ def bind_arguments( return self - #TODO ***GV*** + #TODO ***MG*** def plot( self: Feature, input_image: np.ndarray | list[np.ndarray] | Image | list[Image] = None, resolve_kwargs: dict = None, interval: float = None, - **kwargs + **kwargs: Any, ) -> Any: """Visualizes the output of the feature. From 1cf97ffc54bebb2f363ddbb86f688c4102871667 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 14:17:06 +0200 Subject: [PATCH 130/223] Update features.py --- deeptrack/features.py | 92 ++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 7f7e36126..fd985a853 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -185,13 +185,13 @@ def merge_features( "Merge", "OneOf", "OneOfDict", - "LoadImage", # TODO **MG** - "SampleToMasks", # TODO **MG** - "AsType", # TODO **MG** - "ChannelFirst2d", # TODO **AL** - "Upscale", # TODO **AL** - "NonOverlapping", # TODO **AL** - "Store", # TODO **JH** + "LoadImage", #TODO ***MG*** + "SampleToMasks", #TODO ***MG*** + "AsType", #TODO ***MG*** + "Upscale", #TODO ***AL*** + "ChannelFirst2d", #TODO ***AL*** + "NonOverlapping", #TODO ***AL*** + "Store", #TODO ***JH*** "Squeeze", "Unsqueeze", "ExpandDims", @@ -199,7 +199,7 @@ def merge_features( "Transpose", "Permute", "OneHot", - "TakeProperties", # TODO **JH** + "TakeProperties", #TODO ***JH*** ] @@ -314,7 +314,7 @@ class Feature(DeepTrackNode): The data type of the boolean numbers. device: str or torch.device The device on which the feature is executed. - _backend: "numpy" or "torch" + _backend: Literal["numpy", "torch"] The computational backend. Methods @@ -326,6 +326,8 @@ class Feature(DeepTrackNode): `__call__(image_list: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` It executes the feature or pipeline on the input and applies property overrides from `kwargs`. + `resolve(image_list: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` + A shadow of the `__call__()` method. `to_sequential(**kwargs: Any) -> Feature` It convert a feature to be resolved as a sequence. `store_properties(toggle: bool, recursive: bool) -> Feature` @@ -344,27 +346,28 @@ class Feature(DeepTrackNode): `batch(batch_size: int) -> tuple` It batches the feature for repeated execution. `action(_ID: tuple[int, ...]) -> Any | list[Any]` - Implement the core logic to create or transform the input(s). + It implements the core logic to create or transform the input(s). `update(**global_arguments: Any) -> Feature` - Refreshes the feature to create a new image. + It refreshes the feature to create a new image. `add_feature(feature: Feature) -> Feature` It adds a feature to the dependency graph of this one. `seed(updated_seed: int, _ID: tuple[int, ...]) -> int` - Sets the random seed for the feature, ensuring deterministic behavior. + It sets the random seed for the feature, ensuring deterministic + behavior. `bind_arguments(arguments: Feature) -> Feature` - Binds another feature’s properties as arguments to this feature. - `plot(input_image: np.ndarray | list[np.ndarray] | Image | list[Image] | None = None, resolve_kwargs: dict | None = None, interval: float | None = None, **kwargs) -> Any` - Visualizes the output of the feature. + It binds another feature’s properties as arguments to this feature. + `plot(input_image: np.ndarray | list[np.ndarray] | Image | list[Image] | None = None, resolve_kwargs: dict | None = None, interval: float | None = None, **kwargs: Any) -> Any` + It visualizes the output of the feature. **Private and internal methods.** - `_normalize(**properties: dict[str, Any]) -> dict[str, Any]` - Normalizes the properties of the feature. + `_normalize(**properties: Any) -> dict[str, Any]` + It normalizes the properties of the feature. `_process_properties(propertydict: dict[str, Any]) -> dict[str, Any]` - Preprocesses the input properties before calling the `get` method. + It preprocesses the input properties before calling the `get` method. `_activate_sources(x: Any) -> None` - Activates sources in the input data. + It activates sources in the input data. `__getattr__(key: str) -> Any` - Custom attribute access for the Feature class. + It provides custom attribute access for the Feature class. `__iter__() -> Feature` It returns an iterator for the feature. `__next__() -> Any` @@ -507,7 +510,7 @@ def __init__( **kwargs: Any Keyword arguments that are wrapped into `Property` instances and stored in `self.properties`, allowing for dynamic or parameterized - behavior. If not provided, it defaults to an empty list. + behavior. """ @@ -995,7 +998,7 @@ def numpy( dependency.numpy(recursive=False) self.invalidate() return self - + def get_backend( self: Feature ) -> Literal["numpy", "torch"]: @@ -1050,7 +1053,7 @@ def dtype( The complex dtype to set. It can be `"complex64"`, `"complex128"`, `"default"`, or `None`. It defaults to `None`. bool: str, optional - The bool dtype to set. It cna be `"bool"`, `"default"`, or `None`. + The bool dtype to set. It can be `"bool"`, `"default"`, or `None`. It defaults to `None`. Returns @@ -1687,7 +1690,7 @@ def plotter(frame=0): ), ) - #TODO ***GV*** + #TODO ***AL*** def _normalize( self: Feature, **properties: dict[str, Any], @@ -1722,6 +1725,7 @@ class in the method resolution order (MRO), it checks if the class has for key, val in properties.items(): if isinstance(val, Quantity): properties[key] = val.magnitude + return properties def _process_properties( @@ -1888,7 +1892,7 @@ def __iter__( return self - #TODO **BM** TBE? Previous implementation, not standard in Python + #TODO ***BM*** TBE? Previous implementation, not standard in Python # while True: # yield from next(self) @@ -1926,7 +1930,7 @@ def __next__( return self.update().resolve() - #TODO **BM** TBE? Previous implementation, not standard in Python + #TODO ***BM*** TBE? Previous implementation, not standard in Python # yield self.update().resolve() def __rshift__( @@ -2281,7 +2285,7 @@ def __sub__( return self >> Subtract(other) - #TODO **MG** + #TODO ***MG*** def __rsub__( self: Feature, other: Any @@ -2292,7 +2296,7 @@ def __rsub__( return Value(other) >> Subtract(self) - #TODO **MG** + #TODO ***MG*** def __mul__( self: Feature, other: Any @@ -2314,7 +2318,7 @@ def __rmul__( return Value(other) >> Multiply(self) - #TODO **AL** + #TODO ***AL*** def __truediv__( self: Feature, other: Any @@ -2325,7 +2329,7 @@ def __truediv__( return self >> Divide(other) - #TODO **AL** + #TODO ***AL*** def __rtruediv__( self: Feature, other: Any @@ -2336,7 +2340,7 @@ def __rtruediv__( return Value(other) >> Divide(self) - #TODO **AL** + #TODO ***AL*** def __floordiv__( self: Feature, other: Any @@ -2347,7 +2351,7 @@ def __floordiv__( return self >> FloorDivide(other) - #TODO **AL** + #TODO ***AL*** def __rfloordiv__( self: Feature, other: Any @@ -2416,7 +2420,7 @@ def __pow__( return self >> Power(other) - #TODO **JH** + #TODO ***JH*** def __rpow__( self: Feature, other: Any @@ -2427,7 +2431,7 @@ def __rpow__( return Value(other) >> Power(self) - #TODO **JH** + #TODO ***JH*** def __gt__( self: Feature, other: Any, @@ -2438,7 +2442,7 @@ def __gt__( return self >> GreaterThan(other) - #TODO **JH** + #TODO ***JH*** def __rgt__( self: Feature, other: Any @@ -2450,7 +2454,7 @@ def __rgt__( return Value(other) >> GreaterThan(self) - #TODO **JH** + #TODO ***JH*** def __lt__( self: Feature, other: Any @@ -2461,7 +2465,7 @@ def __lt__( return self >> LessThan(other) - #TODO **JH** + #TODO ***JH*** def __rlt__( self: Feature, other: Any @@ -2472,7 +2476,7 @@ def __rlt__( return Value(other) >> LessThan(self) - #TODO **JH** + #TODO ***JH*** def __le__( self: Feature, other: Any @@ -2483,7 +2487,7 @@ def __le__( return self >> LessThanOrEquals(other) - #TODO **JH** + #TODO ***JH*** def __rle__( self: Feature, other: Any @@ -2495,7 +2499,7 @@ def __rle__( return Value(other) >> LessThanOrEquals(self) - #TODO **JH** + #TODO ***JH*** def __ge__( self: Feature, other: Any @@ -2507,7 +2511,7 @@ def __ge__( return self >> GreaterThanOrEquals(other) - #TODO **JH** + #TODO ***JH*** def __rge__( self: Feature, other: Any @@ -2519,7 +2523,7 @@ def __rge__( return Value(other) >> GreaterThanOrEquals(self) - #TODO **JH** + #TODO ***JH*** def __xor__( self: Feature, other: Any, @@ -2530,7 +2534,7 @@ def __xor__( return Repeat(self, other) - #TODO **JH** + #TODO ***JH*** def __and__( self: Feature, other: Any, @@ -2541,7 +2545,7 @@ def __and__( return self >> Stack(other) - #TODO **JH** + #TODO ***JH*** def __rand__( self: Feature, other: Any, From 6d67783dc32d7e97395f9b554681e381cbfd99c3 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 16:14:08 +0200 Subject: [PATCH 131/223] Update features.py --- deeptrack/features.py | 127 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index fd985a853..c11c34e16 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -447,7 +447,123 @@ class Feature(DeepTrackNode): Examples -------- - TODO + >>> import deeptrack as dt + + **Define and evaluate a simple feature** + + >>> import numpy as np + >>> + >>> feature = dt.Value(value=np.array([1, 2, 3])) + >>> result = feature() + >>> result + array([1, 2, 3]) + + **Chain features using '>>'** + + >>> pipeline = dt.Value(value=np.array([1, 2, 3])) >> dt.Add(value=2) + >>> pipeline() + array([3, 4, 5]) + + **Use arithmetic operators for syntactic sugar** + + >>> feature = dt.Value(value=np.array([1, 2, 3])) + >>> result = (feature + 1) * 2 - 1 + >>> result() + array([3, 5, 7]) + + This is equivalent to chaining with `Add`, `Multiply`, and `Subtract`. + + **Evaluate a dynamic feature using `.update()`** + + >>> feature = dt.Value(value=lambda: np.random.rand()) + >>> output1 = feature() + >>> output1 + 0.9938966963707441 + + >>> output2 = feature() # Cached result + >>> output2 + 0.9938966963707441 + + >>> feature.update() + >>> output3 = feature() # New sample + >>> output3 + 0.3874078815170007 + + **Generate a batch of outputs** + + >>> feature = dt.Value(lambda: np.random.rand()) + 1 + >>> batch = feature.batch(batch_size=3) + >>> batch + (array([1.6888222 , 1.88422131, 1.90027316]),) + + **Store and retrieve properties from outputs** + + >>> feature = dt.Value(value=3).store_properties(True) + >>> output = feature(np.array([1, 2])) + >>> output.get_property("value") + 3 + + **Switch computational backend to torch** + + >>> import torch + >>> + >>> feature = dt.Add(value=5).torch() + >>> input_tensor = torch.tensor([1.0, 2.0]) + >>> feature(input_tensor) + tensor([6., 7.]) + + **Use `.seed()` for reproducibility** + + >>> feature = dt.Value(lambda: np.random.randint(0, 100)) + >>> seed = feature.seed() + >>> v1 = feature.update()() + >>> v1 + 76 + + >>> feature.seed(seed) + >>> v2 = feature.update()() + >>> v2 + 76 + + **Sequential feature with evolving property** + + >>> def rotate(sequence_length, previous_value): + ... return previous_value + 2 * np.pi / sequence_length + + >>> rotating = dt.Ellipse( + ... position=(16, 16), + ... radius=(1.5, 1), + ... rotation=0, + ... ).to_sequential(rotation=rotate) + + >>> frames = dt.Sequence(rotating, sequence_length=5).update() + >>> images = frames() + >>> len(images) + 5 + + **Bind dynamic arguments across multiple features** + + >>> arguments = dt.Arguments(frequency=1, amplitude=2) + >>> wave = ( + ... dt.Value( + ... value=lambda frequency: np.linspace(0, 2 * np.pi * frequency, 100), + ... frequency=arguments.frequency, + ... ) + ... >> np.sin + ... >> dt.Multiply( + ... value=lambda amplitude: amplitude, + ... amplitude=arguments.amplitude, + ... ) + ... ) + >>> wave.bind_arguments(arguments) + + >>> from matplotlib import pyplot as plt + >>> + >>> plt.plot(wave()) + >>> plt.show() + + >>> plt.plot(wave(frequency=2, amplitude=1)) # Raw image with no noise + >>> plt.show() """ @@ -1191,6 +1307,15 @@ def batch( """ results = [self.update()() for _ in range(batch_size)] + + try: + # Attempt to unzip results + results = [(r,) for r in results] + except TypeError: + # If outputs are scalar (not iterable), wrap each in a tuple + results = [(r,) for r in results] + results = [(r,) for r in results] + results = list(zip(*results)) for idx, r in enumerate(results): From 5cf559be41b2ec110cb590327689b4d142eaa6e7 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 16:51:26 +0200 Subject: [PATCH 132/223] Update features.py --- deeptrack/features.py | 109 +++++++++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 39 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index c11c34e16..60110e364 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -41,34 +41,75 @@ ---------------- Key Classes: -- `Feature`: +- `Feature`: Base class for all features in DeepTrack2. - Base class for all features in DeepTrack2. Represents a modular data - transformation with properties and methods for customization. + It represents a modular data transformation with properties and methods for + customization. -- `StructuralFeature`: +- `StructuralFeature`: Provide structure without input transformations. A specialized feature for organizing and managing hierarchical or logical structures in the pipeline. -- `Value`: - - Stores a constant value as a feature. Useful for passing parameters through - the pipeline. - -- `Chain`: - - Sequentially applies multiple features to the input data (>>). - -- `DummyFeature`: - - A no-op feature that passes the input data unchanged. - -- `ArithmeticOperationFeature`: +- `ArithmeticOperationFeature`: Apply arithmetic operation element-wise. A parent class for features performing arithmetic operations like addition, subtraction, multiplication, and division. +Structural Feature Classes: +- `Chain`: Sequentially apply multiple features to the input data (>>). +- `Branch`: Alias of `Chain`. +- `Probability`: Resolve a feature with a certain probability. +- `Repeat`: Apply a feature multiple times in sequence (^). +- `Combine`: Combine multiple features into a single feature. +- `Bind`: Bind a feature with property arguments. +- `BindResolve`: Alias of `Bind`. +- `BindUpdate`: DEPRECATED Bind a feature with certain arguments. +- `ConditionalSetProperty`: DEPRECATED Conditionally override child properties. +- `ConditionalSetFeature`: DEPRECATED Conditionally resolve features. + +Other Feature Classes: +- `DummyFeature`: A no-op feature that simply returns the input unchanged. +- `Value`: Store a constant value as a feature. +- `Stack`: Stack the input and the value. +- `Arguments`: A convenience container for pipeline arguments. +- `Slice`: Dynamically applies array indexing to inputs. +- `Lambda`: Apply a user-defined function to the input. +- `Merge`: Apply a custom function to a list of inputs. +- `OneOf`: Resolve one feature from a given collection. +- `OneOfDict`: Resolve one feature from a dictionary and apply it to an input. +- `LoadImage`: Load an image from disk and preprocess it. +- `SampleToMasks`: Create a mask from a list of images. +- `AsType`: Convert the data type of images. +- `ChannelFirst2d`: DEPRECATED Convert an image to a channel-first format. +- `Upscale`: Simulate a pipeline at a higher resolution. +- `NonOverlapping`: Ensure volumes are placed non-overlapping in a 3D space. +- `Store`: Store the output of a feature for reuse. +- `Squeeze`: Squeeze the input image to the smallest possible dimension. +- `Unsqueeze`: Unsqueeze the input image to the smallest possible dimension. +- `ExpandDims`: Alias of `Unsqueeze`. +- `MoveAxis`: Moves the axis of the input image. +- `Transpose`: Transpose the input image. +- `Permute`: Alias of `Transpose`. +- `OneHot`: Convert the input to a one-hot encoded array. +- `TakeProperties`: Extract all instances of properties from a pipeline. + +Arithmetic Feature Classes: +- `Add`: Add a value to the input. +- `Subtract`: Subtract a value from the input. +- `Multiply`: Multiply the input by a value. +- `Divide`: Divide the input with a value. +- `FloorDivide`: Divide the input with a value. +- `Power`: Raise the input to a power. +- `LessThan`: Determine if input is less than value. +- `LessThanOrEquals`: Determine if input is less than or equal to value. +- `LessThanOrEqual`: Alias for `LessThanOrEquals`. +- `GreaterThan`: Determine if input is greater than value. +- `GreaterThanOrEquals`: Determine if input is greater than or equal to value. +- `GreaterThanOrEqual`: Alias for `GreaterThanOrEquals`. +- `Equals`: Determine if input is equal to value. +- `Equal`: Alias for `Equals`. + Functions: - `propagate_data_to_dependencies`: @@ -81,16 +122,6 @@ def propagate_data_to_dependencies( Propagates data to all dependencies of a feature, updating their properties with the provided values. -- `merge_features`: - - def merge_features( - features: list[Feature], - merge_strategy: int = MERGE_STRATEGY_OVERRIDE, - ) -> Feature - - Merges multiple features into a single feature using the specified merge - strategy. - Examples -------- Define a simple pipeline with features: @@ -123,12 +154,12 @@ def merge_features( from __future__ import annotations -import array_api_compat as apc import itertools import operator import random from typing import Any, Callable, Iterable, Literal, TYPE_CHECKING +import array_api_compat as apc import numpy as np from numpy.typing import NDArray import matplotlib.pyplot as plt @@ -3331,7 +3362,7 @@ def get( class Value(Feature): - """Represents a constant (per evaluation) value in a DeepTrack pipeline. + """Represent a constant (per evaluation) value in a DeepTrack pipeline. This feature holds a constant value (e.g., a scalar or array) and supplies it on demand to other parts of the pipeline. @@ -3471,7 +3502,7 @@ def get( class ArithmeticOperationFeature(Feature): - """Applies an arithmetic operation element-wise to inputs. + """Apply an arithmetic operation element-wise to inputs. This feature performs an arithmetic operation (e.g., addition, subtraction, multiplication) on the input data. The inputs can be single values or lists @@ -4334,7 +4365,7 @@ def __init__( class Stack(Feature): - """Stacks the input and the value. + """Stack the input and the value. This feature combines the output of the input data (`image`) and the value produced by the specified feature (`value`). The resulting output @@ -4961,7 +4992,7 @@ def get( class Slice(Feature): - """Dynamically applies array indexing to inputs. + """Dynamically apply array indexing to inputs. This feature allows dynamic slicing of an image using integer indices, slice objects, or ellipses (`...`). @@ -5432,7 +5463,7 @@ def get( class ConditionalSetFeature(StructuralFeature): # DEPRECATED - """Conditionally resolves one of two features based on a condition. + """Conditionally resolve one of two features. .. deprecated:: 2.0 This feature is deprecated and may be removed in a future release. It @@ -6405,7 +6436,7 @@ def get( class SampleToMasks(Feature): - """Creates a mask from a list of images. + """Create a mask from a list of images. This feature applies a transformation function to each input image and merges the resulting masks into a single multi-layer image. Each input @@ -6792,7 +6823,7 @@ def get( return image.astype(dtype) -class ChannelFirst2d(Feature): +class ChannelFirst2d(Feature): # DEPRECATED """Convert an image to a channel-first format. This feature rearranges the axes of a 3D image so that the specified axis @@ -7730,7 +7761,7 @@ def _resample_volume_position( class Store(Feature): - """Stores the output of a feature for reuse. + """Store the output of a feature for reuse. The `Store` feature evaluates a given feature and stores its output in an internal dictionary. Subsequent calls with the same key will return the @@ -8266,7 +8297,7 @@ def get( class OneHot(Feature): - """Converts the input to a one-hot encoded array. + """Convert the input to a one-hot encoded array. This feature takes an input array of integer class labels and converts it into a one-hot encoded array. The last dimension of the input is replaced @@ -8366,7 +8397,7 @@ def get( class TakeProperties(Feature): - """Extracts all instances of a set of properties from a pipeline. + """Extract all instances of a set of properties from a pipeline. Only extracts the properties if the feature contains all given property-names. The order of the properties is not guaranteed to be the From 7945439bffceebba9c0ab527c03328db90c9a1e5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 16:52:13 +0200 Subject: [PATCH 133/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 60110e364..14c6f53b8 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -358,7 +358,7 @@ class Feature(DeepTrackNode): It executes the feature or pipeline on the input and applies property overrides from `kwargs`. `resolve(image_list: Any, _ID: tuple[int, ...], **kwargs: Any) -> Any` - A shadow of the `__call__()` method. + Alias of `__call__()`. `to_sequential(**kwargs: Any) -> Feature` It convert a feature to be resolved as a sequence. `store_properties(toggle: bool, recursive: bool) -> Feature` From 1ba87cd9ddc12e14a0fe6798e21106087acfdc3b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 17:40:23 +0200 Subject: [PATCH 134/223] Update augmentations.py --- deeptrack/augmentations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/augmentations.py b/deeptrack/augmentations.py index 0b37d5992..ddc496900 100644 --- a/deeptrack/augmentations.py +++ b/deeptrack/augmentations.py @@ -107,7 +107,7 @@ from deeptrack import utils from deeptrack.features import Feature from deeptrack.image import Image -from deeptrack.types import ArrayLike, PropertyLike +from deeptrack.types import PropertyLike class Augmentation(Feature): From 4d512584b7ecc65af5049976223a93ed83345e7c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 17:40:26 +0200 Subject: [PATCH 135/223] Update types.py --- deeptrack/types.py | 130 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 106 insertions(+), 24 deletions(-) diff --git a/deeptrack/types.py b/deeptrack/types.py index 3d4df8470..40b8746b8 100644 --- a/deeptrack/types.py +++ b/deeptrack/types.py @@ -10,46 +10,113 @@ ------------- - `PropertyLike` A type alias representing a value of type `T` or a callable returning `T`. +- `DTImageLike` + A type alias for array-like structures, namely, NumPy arrays, PyTorch + tensors, and `Image` objects. - `ArrayLike` - A type alias for array-like structures (e.g., tuples, lists, numpy arrays). + A type alias for array-like structures, namely, tuples, lists, NumPy + arrays, PyTorch tensors, and `Image` objects. - `NumberLike` - A type alias for numeric types, including scalars and arrays (e.g., numpy - arrays, GPU tensors). + A type alias for numeric types, including scalars and arrays, namely, NumPy + arrays, PyTorch tensors, bool, int, float, and complex Examples -------- -Using `PropertyLike`: +>>> import deeptrack as dt +**Using `PropertyLike`** >>> def scale(value: PropertyLike[float]) -> float: ... if callable(value): ... return value() ... return value ->>> scale(3.14) # 3.14 ->>> scale(lambda: 2.71) # 2.71 -Using `ArrayLike`: +It works for a given type (in this case, a `float`): +>>> scale(3.14) +It also works for function returning the same type (in this case, a function +returning a `float`): +>>> scale(lambda: 2.71) + +`PropertyLike[Type]` is generally used for typing arguments passed to a feature +that are then passed to the constructor of the feature parent, because these +can be intrisically either `Type` or `Callable[..., Type]`. + +**Using `ImageLike`** +>>> def print_imagelike(image: dt.ArrayLike[float]) -> None: +... print(image) + +- NumPy arrays: >>> import numpy as np ->>> def compute_mean(array: ArrayLike[float]) -> float: -... return np.mean(array) ->>> compute_mean([1.0, 2.0, 3.0]) # 2.0 ->>> compute_mean((4.0, 5.0, 6.0)) # 5.0 ->>> compute_mean(np.array([7.0, 8.0, 9.0])) # 8.0 +>>> +>>> print_imagelike(np.array([7.0, 8.0, 9.0])) + +- PyTorch tensors: +>>> import torch +>>> +>>> print_imagelike(torch.Tensor([1.0, 2.0, 3.0])) + +- `Image` objects: +>>> print_imagelike(dt.Image([1.0, 2.0, 3.0])) + +**Using `ArrayLike`** +>>> def print_arraylike(array: dt.ArrayLike[float]) -> None: +... print(array) + +It works for: -Using `NumberLike`: +- Lists: +>>> print_arraylike([1.0, 2.0, 3.0]) +- Tuples: +>>> print_arraylike((4.0, 5.0, 6.0)) + +- NumPy arrays: +>>> import numpy as np +>>> +>>> print_arraylike(np.array([7.0, 8.0, 9.0])) + +- PyTorch tensors: +>>> import torch +>>> +>>> print_arraylike(torch.Tensor([1.0, 2.0, 3.0])) + +- `Image` objects: +>>> print_arraylike(dt.Image([1.0, 2.0, 3.0])) + +**Using `NumberLike`** >>> def add_numbers(a: NumberLike, b: NumberLike) -> NumberLike: ... return a + b ->>> add_numbers(5, 3.2) # 8.2 ->>> add_numbers(np.array([1, 2, 3]), 4) # array([5, 6, 7]) + +It works for: + +- Scalars (bool, int, float, complex): +>>> add_numbers(5, 3.2) + +- NumPy arrays: +>>> import numpy as np +>>> +>>> add_numbers(np.array([1, 2, 3]), 4) + +- PyTorch tensors: +>>> import torch +>>> +>>> add_numbers(torch.Tensor([1, 2, 3]), 4) """ from __future__ import annotations -from typing import Callable, List, Tuple, TypeVar, Union, TYPE_CHECKING +from typing import Any, Callable, TypeVar, TYPE_CHECKING, Union -import numpy as np +from numpy.typing import NDArray + + +__all__ = [ + "PropertyLike", + "ImageLike", + "ArrayLike", + "NumberLike", +] if TYPE_CHECKING: @@ -58,21 +125,36 @@ # T is a generic type variable defining generic types for reusability. -_T = TypeVar("T") +_T: TypeVar = TypeVar("T") # PropertyLike is a type alias representing a value of type T # or a callable returning type T. -PropertyLike = Union[_T, Callable[..., _T]] +PropertyLike = _T | Callable[..., _T] + +# ImageLike is a type alias representing any +ImageLike = Union[ + NDArray[Any], + "torch.Tensor", + "Image", +] # ArrayLike is a type alias representing any array-like structure. -# It supports tuples, lists, and numpy arrays containing elements of type T. +# It supports tuples, lists, and NumPy arrays containing elements of type T, +# as well as PyTorch tensors and Image objects. ArrayLike = Union[ - np.ndarray, + NDArray[Any], "torch.Tensor", "Image", - List[_T], - Tuple[_T, ...], + list[_T], + tuple[_T, ...], ] # NumberLike is a type alias representing any numeric type including arrays. -NumberLike = Union[np.ndarray, "torch.Tensor", int, float, bool, complex] +NumberLike = Union[ + NDArray[Any], + "torch.Tensor", + bool, + int, + float, + complex, +] From 352bcb2808e121d6ebd921c9fc7c8d48b6ad600b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 17:40:27 +0200 Subject: [PATCH 136/223] Update utils.py --- deeptrack/utils.py | 80 +++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/deeptrack/utils.py b/deeptrack/utils.py index 10051b8e3..1a50dfe63 100644 --- a/deeptrack/utils.py +++ b/deeptrack/utils.py @@ -69,50 +69,50 @@ def safe_call( Examples -------- -Check if a method exists in an object: +>>> import deeptrack as dt ->>> from deeptrack.utils import hasmethod +Check if a method exists in an object: >>> class Example: ... def foo(self): pass ->>> hasmethod(Example(), "foo") + +>>> dt.utils.hasmethod(Example(), "foo") True ->>> hasmethod(Example(), "bar") + +>>> dt.utils.hasmethod(Example(), "bar") False Convert various objects to lists: - ->>> from deeptrack.utils import as_list ->>> as_list(42) +>>> dt.utils.as_list(42) [42] ->>> as_list((1, 2)) + +>>> dt.utils.as_list((1, 2)) [1, 2] ->>> as_list("abc") + +>>> dt.utils.as_list("abc") ['abc'] Retrieve keyword argument names from a function: - ->>> from deeptrack.utils import get_kwarg_names >>> def func(x, y=1, z=2): ... pass ->>> get_kwarg_names(func) + +>>> dt.utils.get_kwarg_names(func) ['x', 'y', 'z'] Check if a function argument has a default value: - ->>> from deeptrack.utils import kwarg_has_default >>> def func(x, y=1): ... pass ->>> kwarg_has_default(func, "x") + +>>> dt.utils.kwarg_has_default(func, "x") False ->>> kwarg_has_default(func, "y") + +>>> dt.utils.kwarg_has_default(func, "y") True Safely call a function with extra arguments: - ->>> from deeptrack.utils import safe_call >>> def f(a, b=2, c=3): ... return a + b + c ->>> safe_call(f, positional_args=[1], b=5, x=100) + +>>> dt.utils.safe_call(f, positional_args=[1], b=5, x=100) 9 """ @@ -159,37 +159,39 @@ def hasmethod( >>> from deeptrack.utils import hasmethod Check if an object has a method called 'foo': - >>> class MyClass: ... def foo(self): ... return 42 + >>> obj = MyClass() >>> hasmethod(obj, "foo") True + >>> hasmethod(obj, "bar") False Built-in types: - >>> hasmethod([1, 2, 3], "append") True + >>> hasmethod([1, 2, 3], "not_a_method") False Modules: - >>> import math >>> hasmethod(math, "sqrt") True + >>> hasmethod(math, "not_existing") False Edge cases: - >>> hasmethod(42, "bit_length") True + >>> hasmethod(42, "foo") False + >>> hasmethod(None, "foo") False @@ -302,41 +304,39 @@ def get_kwarg_names(function: Callable[..., Any]) -> list[str]: from deeptrack.utils import get_kwarg_names Basic usage: - >>> def f(a, b=1, c=2): ... pass + >>> get_kwarg_names(f) ['a', 'b', 'c'] Functions with only positional arguments: - >>> def g(x, y): ... pass + >>> get_kwarg_names(g) ['x', 'y'] Functions with *args and **kwargs (note: **kwargs are not listed): - >>> def k(*args, alpha=0.1, beta=0.2, **kwargs): ... pass + >>> get_kwarg_names(k) ['alpha', 'beta'] Built-in functions (may return an empty list): - >>> get_kwarg_names(len) ['obj'] Lambda functions: - >>> get_kwarg_names(lambda x, y=5: x + y) ['x', 'y'] Methods (including 'self'): - >>> class MyClass: ... def method(self, a, b=2): ... pass + >>> get_kwarg_names(MyClass.method) ['self', 'a', 'b'] @@ -376,37 +376,40 @@ def kwarg_has_default( from deeptrack.utils import kwarg_has_default Check default values for positional and keyword-only arguments: - >>> def f(a, b=2, c=3): ... pass + >>> kwarg_has_default(f, "a") False + >>> kwarg_has_default(f, "b") True + >>> kwarg_has_default(f, "c") True Missing argument: - >>> kwarg_has_default(f, "not_present") False Keyword-only arguments without defaults: - >>> def g(*, flag): ... pass + >>> kwarg_has_default(g, "flag") False Method example: - >>> class MyClass: ... def method(self, x, y=42): ... pass + >>> kwarg_has_default(MyClass.method, "self") False + >>> kwarg_has_default(MyClass.method, "x") False + >>> kwarg_has_default(MyClass.method, "y") True @@ -452,47 +455,44 @@ def safe_call( from deeptrack.utils import safe_call Basic usage with positional and keyword arguments: - >>> def f(a, b=2, c=3): ... return a + b + c + >>> safe_call(f, positional_args=[1], b=4, x=100) 8 All keyword arguments: - >>> safe_call(f, a=1, b=2, c=3) 6 Extra keyword arguments (ignored if not accepted by the function): - >>> safe_call(f, a=2, extra=42) 7 Missing required argument (raises TypeError): - >>> safe_call(f, b=2, c=3) Traceback (most recent call last): ... TypeError: ... Function with *args and **kwargs (the kwargs are not passed): - >>> def g(a, *args, b=5, **kwargs): ... return a, args, b, kwargs + >>> safe_call(g, positional_args=[1, 10], b=7, x=3, y=2) (1, (10,), 7, {}) Function with only *args (positional): - >>> def h(*args): ... return args + >>> safe_call(h, positional_args=[1, 2, 3]) (1, 2, 3) Function with only **kwargs (the kwargs are not passed): - >>> def i(**kwargs): ... return sorted(kwargs.items()) + >>> safe_call(i, foo=1, bar=2) [] From 396b954bb5b29e8e8417f62720f19bb916f75a13 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 17:41:31 +0200 Subject: [PATCH 137/223] Update types.py --- deeptrack/types.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deeptrack/types.py b/deeptrack/types.py index 40b8746b8..ba1d2e2d5 100644 --- a/deeptrack/types.py +++ b/deeptrack/types.py @@ -42,7 +42,7 @@ can be intrisically either `Type` or `Callable[..., Type]`. **Using `ImageLike`** ->>> def print_imagelike(image: dt.ArrayLike[float]) -> None: +>>> def print_imagelike(image: dt.types.ArrayLike[float]) -> None: ... print(image) - NumPy arrays: @@ -56,10 +56,10 @@ >>> print_imagelike(torch.Tensor([1.0, 2.0, 3.0])) - `Image` objects: ->>> print_imagelike(dt.Image([1.0, 2.0, 3.0])) +>>> print_imagelike(dt.types.Image([1.0, 2.0, 3.0])) **Using `ArrayLike`** ->>> def print_arraylike(array: dt.ArrayLike[float]) -> None: +>>> def print_arraylike(array: dt.types.ArrayLike[float]) -> None: ... print(array) It works for: @@ -81,7 +81,7 @@ >>> print_arraylike(torch.Tensor([1.0, 2.0, 3.0])) - `Image` objects: ->>> print_arraylike(dt.Image([1.0, 2.0, 3.0])) +>>> print_arraylike(dt.types.Image([1.0, 2.0, 3.0])) **Using `NumberLike`** >>> def add_numbers(a: NumberLike, b: NumberLike) -> NumberLike: From 6ce4109b7e45a48aff755a5c5aed9bdaf3acb32f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 17:43:58 +0200 Subject: [PATCH 138/223] Update test_features.py --- deeptrack/tests/test_features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 410ca1621..5a5469e28 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -14,14 +14,14 @@ from deeptrack import ( features, + Image, + Gaussian, optics, properties, scatterers, TORCH_AVAILABLE, units, ) -from deeptrack.image import Image -from deeptrack.noises import Gaussian if TORCH_AVAILABLE: import torch From 43b7f377e2016e5e82549ac494a8a1211e7ec818 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 17:44:00 +0200 Subject: [PATCH 139/223] Update test_utils.py --- deeptrack/tests/test_utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/deeptrack/tests/test_utils.py b/deeptrack/tests/test_utils.py index ba2deff9b..712dcddb6 100644 --- a/deeptrack/tests/test_utils.py +++ b/deeptrack/tests/test_utils.py @@ -8,11 +8,12 @@ import unittest -import deeptrack as dt import numpy as np -from deeptrack import utils +from deeptrack import TORCH_AVAILABLE, utils +if TORCH_AVAILABLE: + import torch class DummyClass: def method(self): pass @@ -59,9 +60,7 @@ def test_as_list(self): self.assertTrue(isinstance(result, list)) self.assertTrue(all(isinstance(x, (int, np.generic)) for x in result)) - if dt.TORCH_AVAILABLE: - import torch - + if TORCH_AVAILABLE: tensor = torch.tensor([[1, 2], [3, 4]]) result = utils.as_list(tensor) From f6383ff48e353b0fca5c36f31cbeb99d8f1ff7da Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 17:50:26 +0200 Subject: [PATCH 140/223] Update DTAT387_types.ipynb --- .../3-advanced-topics/DTAT387_types.ipynb | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT387_types.ipynb b/tutorials/3-advanced-topics/DTAT387_types.ipynb index 2cb2a14ff..e8d49858f 100644 --- a/tutorials/3-advanced-topics/DTAT387_types.ipynb +++ b/tutorials/3-advanced-topics/DTAT387_types.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# deeptrack.types\n", + "# DTAT387. deeptrack.types\n", "\n", "\"Open" ] @@ -31,22 +31,22 @@ "source": [ "## 1. What is `types`?\n", "\n", - "The `types` module introduces custom datatype aliases for type hints throughout the DeepTrack2 codebase.\n", + "The `types` module introduces custom data type aliases for type hints throughout the DeepTrack2 codebase.\n", "Type hints are used when declaring a function or class to improve readability and maintanability by specifying expected parameters and attribute types.\n", "\n", "Currently there are three type aliases in DeepTrack2:\n", "\n", - "- `PropertyLike` for values or callables, can contain a generic datatype or a callable function which returns a generic datatype.\n", - "- `ArrayLike` for array-like structures (e.g., tuples, lists, numpy arrays).\n", - "- `NumberLike` for numeric types, including scalars and arrays (e.g., numpy \n", - " arrays, GPU tensors)." + "- `PropertyLike` represents a generic data type or a callable function which returns that generic datatype.\n", + "- `ImageLike` represents an image-liek structure, namely a NumPy array, a PyTorch tensor, or an `Image` obeject.\n", + "- `ArrayLike` represents an array-like structure, namley a tuple, a list, a NumPy array, a PyTorch tensor, or an `Image` obeject.\n", + "- `NumberLike` represents a numeric type, including scalars (a bool, int, float, or complex) and arrays (a NumPy array or a PyTorch tensor)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Declare a function with type hints\n", + "## 2. Declaring a Function with Type Hints\n", "\n", "An elementary example of the usage of a type hint can be seen below:" ] @@ -75,11 +75,12 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from __future__ import annotations\n", + "\n", "import deeptrack as dt\n", "from deeptrack.types import PropertyLike\n", "\n", @@ -87,15 +88,15 @@ "\n", " def __init__(\n", " self: Augmentation,\n", - " time_consistent: bool = False,\n", - " **kwargs\n", + " time_consistent: PropertyLike[bool] = False,\n", + " **kwargs: Any,\n", " ) -> None:\n", " super().__init__(time_consistent=time_consistent, **kwargs)\n", "\n", " def _image_wrapped_process_and_get(\n", " self: Augmentation,\n", " image_list: list[Image],\n", - " time_consistent: PropertyLike[bool],\n", + " time_consistent: bool,\n", " **kwargs\n", " ) -> list[Image]:\n", " pass" From 582d60e51f05273f8a83601527a7d7656f71b13c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sat, 5 Jul 2025 18:45:03 +0200 Subject: [PATCH 141/223] u --- .../3-advanced-topics/DTAT301_features.ipynb | 200 ++++++++++++++---- .../DTAT306_properties.ipynb | 58 +++-- .../3-advanced-topics/DTAT341_sequences.ipynb | 36 +++- .../3-advanced-topics/DTAT383_utils.ipynb | 4 +- .../3-advanced-topics/DTAT387_types.ipynb | 24 ++- 5 files changed, 254 insertions(+), 68 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT301_features.ipynb b/tutorials/3-advanced-topics/DTAT301_features.ipynb index 4fc261e02..f8143f257 100644 --- a/tutorials/3-advanced-topics/DTAT301_features.ipynb +++ b/tutorials/3-advanced-topics/DTAT301_features.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# deeptrack.features\n", + "# DTAT301. deeptrack.features\n", "\n", "\"Open" ] @@ -25,6 +25,15 @@ "This advanced tutorial introduces the module deeptrack.features." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. What is `features.py`?\n", + "\n", + "#TODO ***GV*** Add global paragraph" + ] + }, { "cell_type": "code", "execution_count": 2, @@ -39,19 +48,57 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is a Feature?\n", + "#TODO ***GV*** itemized outline\n", + "\n", + "The key roles of `features.py`are:\n", + "\n", + "- **First Point:**\n", + " xxx\n", + "\n", + "- **Second Point:**\n", + " xxx\n", + "\n", + "..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. What is a Feature?\n", "\n", - "Features are instances of the abstract class `Feature` and are responsible for creating and altering images. For example, a feature might add a particle or add some salt-and-pepper noise to an image.\n", + "Features are instances of the abstract class `Feature` and are responsible for creating and altering inputs (typically images in the context of microscopy). For example, a feature might add a particle or some salt-and-pepper noise to an image.\n", "\n", - "All features operate on lists of images (which can be either `Image` objects or numerical arrays). Most features, such as noise, apply some tranformation to all images in the list. This transformation can be additive, such as adding some Gaussian noise or a background illumination, or non-additive, such as introducing Poisson noise or performing a low-pass filter. This transformation is defined by the method `get(image, **kwargs)`, which all implementations of the class `Feature` need to define.\n", + "All features operate on lists of images (which can be NumPy arrays, PyTorch tensors, or `Image` objects). Most features, such as noise, apply some tranformation to all images in the list. This transformation can be additive, such as adding some Gaussian noise or a background illumination, or non-additive, such as introducing Poisson noise or performing a low-pass filter.\n", + "\n", + "This transformation is defined by the method `get(image, **kwargs)`, which all implementations of the class `Feature` need to define. Importantly, this method typically operates on a single image.\n", "\n", "Some features, for example scatterers, instead, append a new image to the list. This behavior is controlled by the class attributes `__distributed__` and `__list_merge_strategy__`:\n", "\n", - "- `__distributed__` controls whether `.get(image, **kwargs)` is called on each element in the list separately (`__distributed__ = True`), or it is called on the list as a whole (`__distributed__ = False`). \n", + "- `__distributed__` controls whether `.get(image, **kwargs)` is called on each element in the list separately (`__distributed__ = True`, so that it operates on a single image at a time), or it is called on the list as a whole (`__distributed__ = False`, so that it operates on the whole input list at once).\n", "\n", "- `__list_merge_strategy__` controls how the output of `.get(image, **kwargs)` is merged with the input list. It can be `MERGE_STRATEGY_OVERRIDE` (`0`, default), where the new list is output without merging with the input, or `MERGE_STRATEGY_APPEND` (`1`), where the new list is appended to the end of the input list.\n", "\n", - "When a feature is created, keyword arguments can be passed to its constructor. These arguments are stored as properties (as a `PropertyDict`) of the feature (see also [properties_example](DTAT306_properties.ipynb)), which determine how the feature is resolved. When a feature is updated using the method `.update()`, all properties are updated by calling their respective methods `.update()`. When a feature is called, the current values of all properties are sent through the method `get(image, **kwargs)` as keyword arguments." + "When a feature is created, keyword arguments can be passed to its constructor. These arguments are stored as properties (as a `PropertyDict`) of the feature (see also [DTAT306_properties.ipynb](DTAT306_properties.ipynb)), which determine how the feature is resolved. When a feature is updated using the `.update()` method, all properties are updated by calling their respective `.update()` methods.\n", + "\n", + "When a feature is called, the current values of all properties are sent through the method `get(image, **kwargs)` as keyword arguments." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 Implementing a Simple Feature" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "The `__distributed__` atrribut controls what is passed to the method `get()`: \n", + "- `False`: The input list is passed without modification as the `image` parameter.\n", + "- `True`: Elements along the first axis of the inpur list are passed ndivisually as the `image` parameter.\n" ] }, { @@ -77,9 +124,6 @@ "\n", " \"\"\"\n", "\n", - " # Controls what is passed to the method get(): \n", - " # False: everything is passed without modification.\n", - " # True: elements along the first axis are passed separately to the images.\n", " __distributed__ = False\n", "\n", " def get(self, image, my_property=None, **kwargs):\n", @@ -96,16 +140,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is 1\n", - "[]\n" + "The current value of my_property is 1\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "foo1 = Foo(my_property=1)\n", "foo1.update()\n", "output_image = foo1()\n", - "print(output_image)" + "output_image" ] }, { @@ -117,16 +170,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is bar\n", - "[]\n" + "The current value of my_property is bar\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "foo2 = Foo(my_property=\"bar\")\n", "foo2.update()\n", "output_image = foo2()\n", - "print(output_image)" + "output_image" ] }, { @@ -138,16 +200,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is None\n", - "[]\n" + "The current value of my_property is None\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "foo3 = Foo()\n", "foo3.update()\n", "output_image = foo3()\n", - "print(output_image)" + "output_image" ] }, { @@ -173,16 +244,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is 0.1209843758525152\n", - "[]\n" + "The current value of my_property is -0.3781153265650975\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ "foo4 = Foo(my_property=lambda: np.random.rand() - 0.5)\n", "foo4.update()\n", "output_image = foo4()\n", - "print(output_image)" + "output_image" ] }, { @@ -194,9 +274,18 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is bar\n", - "[]\n" + "The current value of my_property is baz\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -204,14 +293,14 @@ "foo5 = Foo(my_property=lambda: np.random.choice([\"bar\", \"baz\"]))\n", "foo5.update()\n", "output_image = foo5()\n", - "print(output_image)" + "output_image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Operations Defined on Features\n", + "## 3. Operations Defined on Features\n", "\n", "Features can be combined to create a feature series, which is a series of features that are evaluated sequentially." ] @@ -244,9 +333,18 @@ "output_type": "stream", "text": [ "The current value of my_property is foo\n", - "The current value of my_property is bar\n", - "[]\n" + "The current value of my_property is bar\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -254,7 +352,7 @@ "\n", "foobar.update()\n", "output_image = foobar()\n", - "print(output_image)" + "output_image" ] }, { @@ -284,13 +382,22 @@ "name": "stdout", "output_type": "stream", "text": [ + "The current value of my_property is qux\n", + "The current value of my_property is baz\n", "The current value of my_property is bar\n", - "The current value of my_property is bar\n", - "The current value of my_property is quux\n", - "The current value of my_property is quux\n", - "The current value of my_property is bar\n", - "[]\n" + "The current value of my_property is baz\n", + "The current value of my_property is baz\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -299,7 +406,7 @@ "five_foos = foo ^ 5\n", "five_foos.update()\n", "output_image = five_foos()\n", - "print(output_image)" + "output_image" ] }, { @@ -318,8 +425,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "[]\n" + "The current value of my_property is qux\n", + "The current value of my_property is bar\n", + "The current value of my_property is baz\n", + "The current value of my_property is quux\n" ] + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -328,14 +448,14 @@ "\n", "random_foos.update()\n", "output_image = random_foos()\n", - "print(output_image)" + "output_image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Example - Image with Random Circles" + "## 4. Example - Image with Random Circles" ] }, { @@ -411,7 +531,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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Vaqr/Px8OXjwoBw8eFAAyBtvvCEHDx6Us2fPisivp8MHBATIunXr5Mcff5T+/fvf8HT4jh07yp49e2Tnzp3SsmXLanM6/NixY8Xf31+2bdtW4ZTdgoIC6zhjxoyR0NBQ2bJli+zfv1+io6MlOjraOvz6Kbu9evWStLQ02bRpk9SvX79anM783HPPyfbt2+X06dPy448/ynPPPSc6nU42b94sIjV729zMb88qFKnZ22jKlCmybds2OX36tHz//fcSExMjgYGBkpOTIyKu3Taaa1wiIu+8846EhoaKt7e33HXXXbJ7927VkVxi69atAuAPNXz4cBH59ZT4GTNmSHBwsBgMBunZs6ccO3aswjwuXbokQ4cOFT8/PzEajTJixAjJz89XsDaOd6NtA0CWLFliHaewsFDGjRsnderUEV9fXxk4cKBcuHChwnzOnDkjsbGx4uPjI4GBgTJlyhQpLS118do43siRI6VJkybi7e0t9evXl549e1qblkjN3jY38/vGVZO3UVxcnDRo0EC8vb3l9ttvl7i4ODlx4oR1uCu3DR9rQkREmqKpv3ERERGxcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkab8P2wMWPKm9LfQAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -429,7 +549,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 4. Example - Image with Stacked Circles\n", + "## 5. Example - Image with Stacked Circles\n", "\n", "And now a more complex example. In this case, we have a repeated feature that depends on another feature." ] @@ -519,7 +639,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] diff --git a/tutorials/3-advanced-topics/DTAT306_properties.ipynb b/tutorials/3-advanced-topics/DTAT306_properties.ipynb index bea99b655..83426f599 100644 --- a/tutorials/3-advanced-topics/DTAT306_properties.ipynb +++ b/tutorials/3-advanced-topics/DTAT306_properties.ipynb @@ -29,7 +29,33 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is a Property?\n", + "## 1. What is `properties.py`?\n", + "\n", + "#TODO ***GV*** Add global paragraph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#TODO ***GV*** itemized outline\n", + "\n", + "The key roles of `properties.py`are:\n", + "\n", + "- **First Point:**\n", + " xxx\n", + "\n", + "- **Second Point:**\n", + " xxx\n", + "\n", + "..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. What is a Property?\n", "\n", "Each feature (instance of the class `Feature`, see [DTAT301_features.ipynb](DTAT301_features.ipynb)) can have several properties (instances of the class `Property`). These properties can be constants, functions, lists, dictionaries, iterators, or slices, providing flexibility in defining and controlling different aspects of the system being modelled.\n", "\n", @@ -41,7 +67,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.1. What is a sampling rule?\n", + "### 2.1. What is a sampling rule?\n", "\n", "The sampling rule determines how the value of a property is updated upon calling `.update()`.\n", "A sampling rule is defined when an instance of the class `Property` is created and can be of any type. \n", @@ -64,7 +90,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.2. Property with a Constant Value\n", + "### 2.2. Property with a Constant Value\n", "\n", "The simplest example of a property is one that does not change during an update call.\n", "This is commonly either a number or a tuple, but can be any data type that will be evaluated by case 6 above.\n", @@ -158,7 +184,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.3. Property with a Discrete Random Value \n", + "### 2.3. Property with a Discrete Random Value \n", "\n", "Discrete randomness can be achieved by a function (case 5)." ] @@ -221,7 +247,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.4. Property with a Continuous Random Value\n", + "### 2.4. Property with a Continuous Random Value\n", "\n", "Continuous randomness is typically achieved by passing a function that returns a continuous random value. This function should take no input, as noted in case 5. To use a function that needs arguments, wrap it in a function that calls it with the correct arguments." ] @@ -284,7 +310,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.5. Property with a Deterministically Changing Value\n", + "### 2.5. Property with a Deterministically Changing Value\n", "\n", "Deterministically changing properties can be achieved using either an iterator (case 4) or a function (case 5). For the output of a function to change deterministically between calls, it should reference some variable outside its definition. Once an iterator has been exhausted, it will always return its last value." ] @@ -363,7 +389,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.6. Property with Dependent Value\n", + "### 2.6. Property with Dependent Value\n", "\n", "The value of a property can be dependent on the value on some other property. It does this by accepting some keyword argument corresponding to the name of the independent property. Instances of `Feature` will handle this automatically." ] @@ -419,7 +445,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. What is a PropertyDict?\n", + "## 3. What is a PropertyDict?\n", "\n", "Another class contained in the module deeptrack.properties is `PropertyDict`. This is a dictionary of properties (keys: name of properties; values: properties) complemented by utility methods to manage collections of properties. These include:\n", "\n", @@ -467,7 +493,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. What is a SequentialProperty?\n", + "## 4. What is a SequentialProperty?\n", "`SequentialProperty` is a class that extends `Property` with iterative utilities. It lets the user encapsulate sampling rules and iterator logic in a single object with the methods:\n", "* `set_sequence_length()`, which specifies the sequence length.\n", "* `set_current_step()`, which specifies the current index in the sequence." @@ -505,7 +531,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.1. Evaluating the Sequence\n", + "### 4.1. Evaluating the Sequence\n", "\n", "To demostrate the workings of `SequentialProperty`, iterate it by setting explicitly the index in the sequence, evaluating it, storing the currently evaluated value, and printing the sequence evaluated at each step." ] @@ -546,7 +572,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.2. Simulating a Random Walker with a Markov Chain\n", + "### 4.2. Simulating a Random Walker with a Markov Chain\n", "\n", "We will now simulate a random walker in one dimension by iterating the `SequentialProperty` with random number sampling rule as the displacements in each time step and updating the walkers position as a cummulative sum of the sampled displacements." ] @@ -603,7 +629,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.3. Accessing the Stored Values\n", + "### 4.3. Accessing the Stored Values\n", "\n", "This section will show different methods of accessing stored values in a `SequentialProperty`." ] @@ -612,7 +638,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 3.3.1. Accessing All Stored Values\n", + "#### 4.3.1. Accessing All Stored Values\n", "\n", "This can be done with two methods, yielding equivalent results. One involves using `previous()`." ] @@ -866,7 +892,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 3.3.2. Accessing the Last Element" + "#### 4.3.2. Accessing the Last Element" ] }, { @@ -893,7 +919,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 3.3.3. Accessing the Second-to-Last Element" + "#### 4.3.3. Accessing the Second-to-Last Element" ] }, { @@ -920,7 +946,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 3.3.4 Accessing the Third-to-Last Element" + "#### 4.3.4 Accessing the Third-to-Last Element" ] }, { diff --git a/tutorials/3-advanced-topics/DTAT341_sequences.ipynb b/tutorials/3-advanced-topics/DTAT341_sequences.ipynb index 6c4712351..32aa0ac93 100644 --- a/tutorials/3-advanced-topics/DTAT341_sequences.ipynb +++ b/tutorials/3-advanced-topics/DTAT341_sequences.ipynb @@ -29,7 +29,33 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is a Sequence?\n", + "## 1. What is `sequences.py`?\n", + "\n", + "#TODO ***GV*** Add global paragraph" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#TODO ***GV*** itemized outline\n", + "\n", + "The key roles of `sequences.py`are:\n", + "\n", + "- **First Point:**\n", + " xxx\n", + "\n", + "- **Second Point:**\n", + " xxx\n", + "\n", + "..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. What is a Sequence?\n", "\n", "Sequences are lists of (typically) images, where any image in the series may depend on previous images. They can be used to create videos. They can also be used to resolve the same feature several times, changing only a subset of the properties of the feature. An example is imaging a sample at several focal planes." ] @@ -51,7 +77,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Example - A Rotating Ellipse\n", + "## 3. Example - A Rotating Ellipse\n", "\n", "To demonstrate the concept of `sequences`, we will implement a simple case of a rotating ellipse." ] @@ -6114,7 +6140,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Example - Brownian Motion\n", + "## 4. Example - Brownian Motion\n", "\n", "A more advanced application of `sequences` is to simulate a video of the Brownian motion of a microscopic particle." ] @@ -13071,7 +13097,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 3.1 Simulating Multiple Brownian Particles\n", + "### 4.1 Simulating Multiple Brownian Particles\n", "\n", "You can also use the chain operator when imaging the brownian particle as a sequence. This will produce a set of scatterers that are updated independently of each other." ] @@ -22635,7 +22661,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 4. Example - A Microscope Sequence\n", + "## 5. Example - A Microscope Sequence\n", "\n", "Sequences are not exclusive to scatterers. You can evaluate sequentially the properties of any feature in DeepTrack2, including microscopes. This example will show how you can vary the wavelength property." ] diff --git a/tutorials/3-advanced-topics/DTAT383_utils.ipynb b/tutorials/3-advanced-topics/DTAT383_utils.ipynb index 8ee1eba4c..f1d453f26 100644 --- a/tutorials/3-advanced-topics/DTAT383_utils.ipynb +++ b/tutorials/3-advanced-topics/DTAT383_utils.ipynb @@ -27,11 +27,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "import deeptrack.utils as utils" + "from deeptrack import utils" ] }, { diff --git a/tutorials/3-advanced-topics/DTAT387_types.ipynb b/tutorials/3-advanced-topics/DTAT387_types.ipynb index e8d49858f..e81d7dcc3 100644 --- a/tutorials/3-advanced-topics/DTAT387_types.ipynb +++ b/tutorials/3-advanced-topics/DTAT387_types.ipynb @@ -29,11 +29,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is `types`?\n", + "## 1. What is `types.py`?\n", "\n", "The `types` module introduces custom data type aliases for type hints throughout the DeepTrack2 codebase.\n", - "Type hints are used when declaring a function or class to improve readability and maintanability by specifying expected parameters and attribute types.\n", - "\n", + "Type hints are used when declaring a function or class to improve readability and maintanability by specifying expected parameters and attribute types." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Currently there are three type aliases in DeepTrack2:\n", "\n", "- `PropertyLike` represents a generic data type or a callable function which returns that generic datatype.\n", @@ -46,7 +51,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Declaring a Function with Type Hints\n", + "## 2. Using Correctly `PropertyLike`\n", + "\n", + "#TODO ***GV*** add section explaining the correct use of PropertyLike based on the issue." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Declaring a Function with Type Hints\n", "\n", "An elementary example of the usage of a type hint can be seen below:" ] @@ -106,7 +120,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Declare a new type hint\n", + "## 4. Declaring a New Type Hint\n", "\n", "We will now declare a new type hint of our own which will represent non-integer numeric datatypes with higher precision than the standard float datatype. We will use `Union` for this, which lets us gather datatypes into a single set." ] From 3c848b5e4da2992c1886a792be50da20aaba9539 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:02:12 +0100 Subject: [PATCH 142/223] Update types.py --- deeptrack/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/types.py b/deeptrack/types.py index ba1d2e2d5..5b1983266 100644 --- a/deeptrack/types.py +++ b/deeptrack/types.py @@ -129,7 +129,7 @@ # PropertyLike is a type alias representing a value of type T # or a callable returning type T. -PropertyLike = _T | Callable[..., _T] +PropertyLike = Union[_T, Callable[..., _T]] # ImageLike is a type alias representing any ImageLike = Union[ From 2434e7e74a93c99f3fc3853cab77c31f643720e2 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:26:18 +0100 Subject: [PATCH 143/223] Update DTAT301_features.ipynb --- .../3-advanced-topics/DTAT301_features.ipynb | 117 ++++++++++-------- 1 file changed, 62 insertions(+), 55 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT301_features.ipynb b/tutorials/3-advanced-topics/DTAT301_features.ipynb index f8143f257..16eb76d12 100644 --- a/tutorials/3-advanced-topics/DTAT301_features.ipynb +++ b/tutorials/3-advanced-topics/DTAT301_features.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ @@ -31,34 +31,38 @@ "source": [ "## 1. What is `features.py`?\n", "\n", - "#TODO ***GV*** Add global paragraph" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import pyplot as plt\n", - "import numpy as np" + "The `features.py` module is the core engine of the DeepTrack2 framework. It defines the structure and behavior of features, the fundamental building blocks of any data generation pipeline in DeepTrack. Each feature represents a transformation or a data source—such as an optical element, a noise model, or a synthetic particle—and can be flexibly combined with other features to form complex generative models. The design is fully modular and supports both deterministic and stochastic behaviors, enabling rich and dynamic data generation workflows. This tutorial will explore how to define, combine, and manipulate features using this module." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#TODO ***GV*** itemized outline\n", + "The key roles of `features.py` are:\n", "\n", - "The key roles of `features.py`are:\n", + "- **Encapsulation of functionality:** \n", + " Each feature encapsulates a specific operation, such as adding noise, \n", + " applying optical effects, or generating synthetic objects. This modular \n", + " design makes it easy to compose complex pipelines from simple parts.\n", "\n", - "- **First Point:**\n", - " xxx\n", + "- **Lazy evaluation and dependency management:** \n", + " Features are evaluated only when needed, and they automatically manage \n", + " dependencies between operations. This enables efficient and reproducible \n", + " generation of data on demand.\n", "\n", - "- **Second Point:**\n", - " xxx\n", + "- **Support for both static and dynamic behavior:** \n", + " Features can be defined using fixed values or as functions of parameters, \n", + " enabling the creation of dynamic, parameterized, and randomized data \n", + " pipelines.\n", "\n", - "..." + "- **Operator overloading for intuitive composition:** \n", + " Features can be combined using standard Python operators (e.g., `+`, \n", + " `>>`, `*`), allowing users to build processing chains in a natural and \n", + " readable syntax.\n", + "\n", + "- **Transparent data propagation and metadata tracking:** \n", + " Features automatically propagate input data and track associated metadata, \n", + " making them especially suited for building reproducible and traceable datasets." ] }, { @@ -103,7 +107,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 25, "metadata": { "execution": { "iopub.execute_input": "2022-06-29T20:32:41.429206Z", @@ -133,7 +137,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 26, "metadata": {}, "outputs": [ { @@ -149,7 +153,7 @@ "[]" ] }, - "execution_count": 4, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -163,7 +167,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -179,7 +183,7 @@ "[]" ] }, - "execution_count": 5, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -193,7 +197,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -209,7 +213,7 @@ "[]" ] }, - "execution_count": 6, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -230,7 +234,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 29, "metadata": { "execution": { "iopub.execute_input": "2022-06-29T20:32:44.941652Z", @@ -244,7 +248,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is -0.3781153265650975\n" + "The current value of my_property is 0.4128902212580223\n" ] }, { @@ -253,7 +257,7 @@ "[]" ] }, - "execution_count": 7, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -267,14 +271,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is baz\n" + "The current value of my_property is bar\n" ] }, { @@ -283,7 +287,7 @@ "[]" ] }, - "execution_count": 8, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -318,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 31, "metadata": { "execution": { "iopub.execute_input": "2022-06-29T20:32:44.946650Z", @@ -342,7 +346,7 @@ "[]" ] }, - "execution_count": 9, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -368,7 +372,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 32, "metadata": { "execution": { "iopub.execute_input": "2022-06-29T20:32:44.952150Z", @@ -382,10 +386,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is qux\n", - "The current value of my_property is baz\n", "The current value of my_property is bar\n", "The current value of my_property is baz\n", + "The current value of my_property is qux\n", + "The current value of my_property is quux\n", "The current value of my_property is baz\n" ] }, @@ -395,7 +399,7 @@ "[]" ] }, - "execution_count": 10, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -411,7 +415,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 33, "metadata": { "execution": { "iopub.execute_input": "2022-06-29T20:32:44.957150Z", @@ -425,7 +429,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "The current value of my_property is qux\n", + "The current value of my_property is bar\n", + "The current value of my_property is bar\n", + "The current value of my_property is baz\n", + "The current value of my_property is quux\n", "The current value of my_property is bar\n", "The current value of my_property is baz\n", "The current value of my_property is quux\n" @@ -437,7 +444,7 @@ "[]" ] }, - "execution_count": 11, + "execution_count": 33, "metadata": {}, "output_type": "execute_result" } @@ -467,7 +474,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 34, "metadata": { "execution": { "iopub.execute_input": "2022-06-29T20:32:44.963649Z", @@ -491,7 +498,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 35, "metadata": {}, "outputs": [], "source": [ @@ -504,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 36, "metadata": {}, "outputs": [], "source": [ @@ -513,7 +520,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -526,12 +533,12 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 38, "metadata": {}, "outputs": [ { "data": { - "image/png": "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Vaqr/Px8OXjwoBw8eFAAyBtvvCEHDx6Us2fPisivp8MHBATIunXr5Mcff5T+/fvf8HT4jh07yp49e2Tnzp3SsmXLanM6/NixY8Xf31+2bdtW4ZTdgoIC6zhjxoyR0NBQ2bJli+zfv1+io6MlOjraOvz6Kbu9evWStLQ02bRpk9SvX79anM783HPPyfbt2+X06dPy448/ynPPPSc6nU42b94sIjV729zMb88qFKnZ22jKlCmybds2OX36tHz//fcSExMjgYGBkpOTIyKu3Taaa1wiIu+8846EhoaKt7e33HXXXbJ7927VkVxi69atAuAPNXz4cBH59ZT4GTNmSHBwsBgMBunZs6ccO3aswjwuXbokQ4cOFT8/PzEajTJixAjJz89XsDaOd6NtA0CWLFliHaewsFDGjRsnderUEV9fXxk4cKBcuHChwnzOnDkjsbGx4uPjI4GBgTJlyhQpLS118do43siRI6VJkybi7e0t9evXl549e1qblkjN3jY38/vGVZO3UVxcnDRo0EC8vb3l9ttvl7i4ODlx4oR1uCu3DR9rQkREmqKpv3ERERGxcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkaawcRERkab8P2wMWPKm9LfQAAAAAElFTkSuQmCC", + "image/png": "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", "text/plain": [ "
" ] @@ -556,7 +563,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 39, "metadata": { "execution": { "iopub.execute_input": "2022-06-29T20:32:45.107150Z", @@ -576,7 +583,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -590,7 +597,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 41, "metadata": {}, "outputs": [], "source": [ @@ -604,7 +611,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 42, "metadata": {}, "outputs": [], "source": [ @@ -613,7 +620,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 43, "metadata": {}, "outputs": [], "source": [ @@ -623,7 +630,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 44, "metadata": {}, "outputs": [], "source": [ @@ -634,12 +641,12 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 45, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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fxsfffQRPvjgA5w/fx6Dg4P46le/6nu9x+PBsWPH4HQ6UVtbi5/+9Kd477338MYbb4T2XRER0eYlVikhIUH8wz/8g5iYmBARERHigw8+8G27ffu2ACDq6uqEEEL85je/ESqVSlgsFl+bd955RxgMBuFwOGQf02q1CgB88MEHH3wo/GG1WoOOOyuunOHxePD+++9jenoaZrMZLS0tcLlcOHz4sK/Nrl27kJ2djbq6OgBAXV0dSktLF6xLdPToUdhsNt+obSkOhwM2m23Bg4iItqagkzNaW1thNptht9uh1+vx4YcfoqioCFevXoVWq11Ut81oNPomS1oslkWL6c3/39+EypMnT+Ktt94Ktqt+aTQaSJKE6upqREREyHrNyMgI2traIIRgtQIionUSdOAqKCjA1atXYbVa8W//9m944YUXcP78+XD0zef111/Hq6++6vu/zWZDVlZW0PvR6XRISkqCVqtFRUUFAEClkj/oTEpKwq5duzA9PY3r169jdHQ0bEtUEBHR0oIOXFqtFvn5+QCAiooKNDU14a/+6q/wta99DU6nExMTEwtGXcPDwzCZTADmSuI0NjYu2N981uF8m6XodDrodLpgu7qAJEmoqKhARkbGqvYhSRJiY2Oxf/9+DAwMYHh4GHfv3l1V34iISL5VV4f3er1wOByoqKhAREQEzpw549vW1taG3t5emM1mAIDZbEZraytGRkZ8bU6fPg2DwYCioqLVdmVJ0dHRKCgowO/93u8hPT09pPvOyMhAWVkZnnrqKSQmJoZ030REtLSgRlyvv/46nnrqKWRnZ2NychI/+9nPcO7cOXz66aeIi4vDSy+9hFdffRWJiYkwGAz45je/CbPZjJqaGgDAkSNHUFRUhOeffx4//OEPYbFY8N3vfhcnTpxY9YhqKampqTCbzWGt2K3RaKDX63HgwAHcunULg4ODmJmZCdvxiIi2uqAC18jICL7+9a9jaGgIcXFxKCsrw6efforf+Z3fAQD8+Mc/hkqlwvHjx+FwOHD06FH83d/9ne/1arUap06dwje+8Q2YzWbExMTghRdewPe///3QvisAKSkpqKysXJNlJoC5y5nl5eXIysrCxYsXOamaiChMNmWR3eTkZBw8eNBvMdNwGh0dRX19PWZnZ9fl+ERESsEiu5i7PFhVVRV00FKr1SgqKlqw/ITX68WtW7eCrsqd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", "text/plain": [ "
" ] From cdafe079ef4102e4364410ca7777e90f1ce9da2a Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:26:20 +0100 Subject: [PATCH 144/223] Update DTAT387_types.ipynb --- tutorials/3-advanced-topics/DTAT387_types.ipynb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT387_types.ipynb b/tutorials/3-advanced-topics/DTAT387_types.ipynb index e81d7dcc3..9bf2311d2 100644 --- a/tutorials/3-advanced-topics/DTAT387_types.ipynb +++ b/tutorials/3-advanced-topics/DTAT387_types.ipynb @@ -53,7 +53,9 @@ "source": [ "## 2. Using Correctly `PropertyLike`\n", "\n", - "#TODO ***GV*** add section explaining the correct use of PropertyLike based on the issue." + "In general, anything that is passed to `Feature.init()` (usually though `super().init()` in the inizialization of a concrete feature) should be `PropertyLike`.\n", + "\n", + "Nothing else should be `PropertyLike`." ] }, { @@ -89,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ From e8d797f29e2d5fb8de5b9b2b9708db52cce49197 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:29:11 +0100 Subject: [PATCH 145/223] Update DTAT306_properties.ipynb --- .../DTAT306_properties.ipynb | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT306_properties.ipynb b/tutorials/3-advanced-topics/DTAT306_properties.ipynb index 83426f599..3ff10e3c8 100644 --- a/tutorials/3-advanced-topics/DTAT306_properties.ipynb +++ b/tutorials/3-advanced-topics/DTAT306_properties.ipynb @@ -31,24 +31,51 @@ "source": [ "## 1. What is `properties.py`?\n", "\n", - "#TODO ***GV*** Add global paragraph" + "The `properties.py` module in DeepTrack2 defines the mechanisms for \n", + "managing the dynamic and hierarchical properties of features. Unlike \n", + "standard Python attributes, these properties support lazy evaluation, \n", + "inheritance, dependency tracking, and value caching. This makes them \n", + "ideal for defining complex, data-driven feature graphs where values \n", + "may depend on other values—either statically or dynamically. The \n", + "properties' system is tightly integrated with the `Feature` class, \n", + "serving as the foundation for the flexible and declarative interface \n", + "used to build DeepTrack pipelines." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#TODO ***GV*** itemized outline\n", + "The key roles of `properties.py` are:\n", "\n", - "The key roles of `properties.py`are:\n", + "- **Defining lazy-evaluated properties:**\n", + " Properties can be set as static values or as callables, allowing for \n", + " on-demand evaluation. This is critical for dynamic data pipelines \n", + " where some values depend on runtime conditions.\n", "\n", - "- **First Point:**\n", - " xxx\n", + "- **Tracking dependencies between properties:**\n", + " Properties can depend on other properties. When a dependency is \n", + " updated, dependent properties are automatically re-evaluated. This \n", + " enables consistent and efficient parameter propagation.\n", "\n", - "- **Second Point:**\n", - " xxx\n", + "- **Supporting hierarchical property inheritance:**\n", + " Sub-features can inherit and override properties from their parent \n", + " features, enabling modular and composable configurations.\n", "\n", - "..." + "- **Allowing introspection and documentation:**\n", + " Properties can be introspected to extract their default values, \n", + " types, and documentation, which supports automated documentation \n", + " generation and better developer experience.\n", + "\n", + "- **Caching evaluated values:**\n", + " Once evaluated, a property's value is cached until its dependencies \n", + " change. This prevents unnecessary recomputation while ensuring \n", + " correctness.\n", + "\n", + "- **Providing a declarative syntax for pipeline specification:**\n", + " The use of properties allows DeepTrack users to define image \n", + " generation pipelines in a high-level, declarative manner while \n", + " retaining low-level control when needed." ] }, { From 9067d2d38306720f72b51e4b1b45856cda6529f5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:35:17 +0100 Subject: [PATCH 146/223] Update DTAT301_features.ipynb --- tutorials/3-advanced-topics/DTAT301_features.ipynb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT301_features.ipynb b/tutorials/3-advanced-topics/DTAT301_features.ipynb index 16eb76d12..584ccc961 100644 --- a/tutorials/3-advanced-topics/DTAT301_features.ipynb +++ b/tutorials/3-advanced-topics/DTAT301_features.ipynb @@ -40,29 +40,28 @@ "source": [ "The key roles of `features.py` are:\n", "\n", - "- **Encapsulation of functionality:** \n", + "- **Functionality Encapsulation:**\n", " Each feature encapsulates a specific operation, such as adding noise, \n", " applying optical effects, or generating synthetic objects. This modular \n", " design makes it easy to compose complex pipelines from simple parts.\n", "\n", - "- **Lazy evaluation and dependency management:** \n", + "- **Lazy Evaluation and Dependency Management:**\n", " Features are evaluated only when needed, and they automatically manage \n", " dependencies between operations. This enables efficient and reproducible \n", " generation of data on demand.\n", "\n", - "- **Support for both static and dynamic behavior:** \n", + "- **Support for Static and Dynamic Behavior:**\n", " Features can be defined using fixed values or as functions of parameters, \n", " enabling the creation of dynamic, parameterized, and randomized data \n", " pipelines.\n", "\n", - "- **Operator overloading for intuitive composition:** \n", + "- **Operator Overloading for Intuitive Composition:**\n", " Features can be combined using standard Python operators (e.g., `+`, \n", " `>>`, `*`), allowing users to build processing chains in a natural and \n", " readable syntax.\n", "\n", - "- **Transparent data propagation and metadata tracking:** \n", - " Features automatically propagate input data and track associated metadata, \n", - " making them especially suited for building reproducible and traceable datasets." + "- **Transparent Data Propagation and Metadata Tracking:**\n", + " Features automatically propagate input data and track associated metadata, making them especially suited for building reproducible and traceable datasets." ] }, { From 19cb53e1d51ca9521981ebba45fc6cdc7cf96296 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:35:19 +0100 Subject: [PATCH 147/223] Update DTAT306_properties.ipynb --- tutorials/3-advanced-topics/DTAT306_properties.ipynb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT306_properties.ipynb b/tutorials/3-advanced-topics/DTAT306_properties.ipynb index 3ff10e3c8..03ab37a46 100644 --- a/tutorials/3-advanced-topics/DTAT306_properties.ipynb +++ b/tutorials/3-advanced-topics/DTAT306_properties.ipynb @@ -48,31 +48,31 @@ "source": [ "The key roles of `properties.py` are:\n", "\n", - "- **Defining lazy-evaluated properties:**\n", + "- **Lazy-Evaluated Properties:**\n", " Properties can be set as static values or as callables, allowing for \n", " on-demand evaluation. This is critical for dynamic data pipelines \n", " where some values depend on runtime conditions.\n", "\n", - "- **Tracking dependencies between properties:**\n", + "- **Tracking of Dependencies Between Properties:**\n", " Properties can depend on other properties. When a dependency is \n", " updated, dependent properties are automatically re-evaluated. This \n", " enables consistent and efficient parameter propagation.\n", "\n", - "- **Supporting hierarchical property inheritance:**\n", + "- **Hierarchical Property Inheritance:**\n", " Sub-features can inherit and override properties from their parent \n", " features, enabling modular and composable configurations.\n", "\n", - "- **Allowing introspection and documentation:**\n", + "- **Introspection and Documentation:**\n", " Properties can be introspected to extract their default values, \n", " types, and documentation, which supports automated documentation \n", " generation and better developer experience.\n", "\n", - "- **Caching evaluated values:**\n", + "- **Evaluated Values Caching:**\n", " Once evaluated, a property's value is cached until its dependencies \n", " change. This prevents unnecessary recomputation while ensuring \n", " correctness.\n", "\n", - "- **Providing a declarative syntax for pipeline specification:**\n", + "- **Declarative Syntax for Pipeline Specification:**\n", " The use of properties allows DeepTrack users to define image \n", " generation pipelines in a high-level, declarative manner while \n", " retaining low-level control when needed." From 1cfa3aed22c398bce353e742415d6c8fcc62a043 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:35:22 +0100 Subject: [PATCH 148/223] Update DTAT341_sequences.ipynb --- .../3-advanced-topics/DTAT341_sequences.ipynb | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT341_sequences.ipynb b/tutorials/3-advanced-topics/DTAT341_sequences.ipynb index 32aa0ac93..4b4cbde2f 100644 --- a/tutorials/3-advanced-topics/DTAT341_sequences.ipynb +++ b/tutorials/3-advanced-topics/DTAT341_sequences.ipynb @@ -31,24 +31,52 @@ "source": [ "## 1. What is `sequences.py`?\n", "\n", - "#TODO ***GV*** Add global paragraph" + "The `sequences.py` module in DeepTrack2 provides tools for constructing \n", + "and manipulating sequential data, where each frame or element may \n", + "depend on or interact with the previous ones. It introduces the `Sequence` \n", + "class, which represents an ordered series of feature evaluations, and \n", + "provides utilities for propagating and managing stateful or time-dependent \n", + "information across frames. This is essential for simulating and analyzing \n", + "dynamic systems—such as particle trajectories, evolving microscopy scenes, \n", + "or time-series data—in a structured and reusable manner. The module is \n", + "designed to integrate seamlessly with the rest of the DeepTrack feature \n", + "graph architecture, while adding support for temporal logic and memory." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#TODO ***GV*** itemized outline\n", + "The key roles of `sequences.py` are:\n", "\n", - "The key roles of `sequences.py`are:\n", + "- **Definition and Management of Temporal Sequences of Features:**\n", + " The `Sequence` class allows users to construct an ordered list of \n", + " feature evaluations, where each frame can be independently generated \n", + " or depend on the previous ones.\n", "\n", - "- **First Point:**\n", - " xxx\n", + "- **Propagation of Sequential Data:**\n", + " With `propagate_sequential_data`, state variables such as positions, \n", + " labels, or metadata can be passed forward through the sequence, \n", + " enabling coherent modeling of temporal dynamics.\n", "\n", - "- **Second Point:**\n", - " xxx\n", + "- **Handling of Memory Across Time Steps:**\n", + " Features can retain and update state across frames, making it possible \n", + " to model behaviors like drift, motion persistence, or object continuity \n", + " over time.\n", "\n", - "..." + "- **Generation of Synthetic Videos or Time Series:**\n", + " Sequences provide a straightforward interface for generating batches \n", + " of temporally structured data, which is critical for training and \n", + " evaluating deep learning models on dynamic datasets.\n", + "\n", + "- **Integration with the Core Feature Pipeline:**\n", + " Sequence elements are just standard DeepTrack features, allowing \n", + " seamless integration with optical models, noise processes, and \n", + " detectors.\n", + "\n", + "- **Customized Update Logic:**\n", + " Users can inject custom logic to update state between frames, making \n", + " `sequences.py` a flexible tool for simulating complex evolving systems." ] }, { From 3bda8220ce029f06a99bbb7f7d2e2336e99c0533 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Sun, 6 Jul 2025 17:44:59 +0100 Subject: [PATCH 149/223] Update features.py --- deeptrack/features.py | 55 +++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 14c6f53b8..49c08d9fc 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -1920,38 +1920,57 @@ def _process_properties( return propertydict - #TODO ***GV*** def _activate_sources( self: Feature, - x: Any, + x: SourceItem | list[SourceItem] | Any, ) -> None: """Activates source items within the given input. - This method checks if `x` or its elements (if `x` is a list) are - instances of `SourceItem`, and if so, calls them to trigger their - behavior. + This method checks whether the input `x` or its elements (if `x` is a + list) are instances of `SourceItem`. If so, the source is called to + trigger its behavior—typically to update or emit a new value. This is + necessary to ensure source-driven features (e.g., time-dependent or + externally updated values) are evaluated when the pipeline is run. + + Non-`SourceItem` elements in `x` are ignored. + + This method is typically invoked at the beginning of `__call__()` to + activate all relevant sources before resolving a feature. Parameters ---------- - x: Any + x: SourceItem or list[SourceItem] or Any The input to process. If `x` is a `SourceItem`, it is activated. If `x` is a list, each `SourceItem` within the list is activated. + If `x` is `None` or contains no sources, the method has no effect. + + Examples + -------- + >>> import deeptrack as dt + + Create a dummy source that prints when called: + >>> class MySource(dt.sources.SourceItem): + ... def __call__(self): + ... print("Source activated") + + Instantiate a feature and manually activate a source: + >>> feature = dt.Value(value=1) + >>> source = MySource(callbacks=[]) + >>> feature._activate_sources(source) + Source activated + + Use a list of sources: + >>> feature._activate_sources([source, 42, "text"]) + Source activated - Notes - ----- - - Non-`SourceItem` elements in `x` are ignored. - - This method is used to ensure that all source-dependent computations - are properly triggered when required. - """ - + if isinstance(x, SourceItem): x() - else: - if isinstance(x, list): - for source in x: - if isinstance(source, SourceItem): - source() + elif isinstance(x, list): + for source in x: + if isinstance(source, SourceItem): + source() def __getattr__( self: Feature, From 445abd867d3c6cb45852b4e84aba3510620a05c3 Mon Sep 17 00:00:00 2001 From: Mirja Granfors <95694095+mirjagranfors@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:03:28 +0200 Subject: [PATCH 150/223] Added more documentation for features/__rsub__ (#363) * Added more documentation for features/__rsub__ * Update features.py --------- Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 64 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 49c08d9fc..d789dd4ab 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2460,14 +2460,66 @@ def __sub__( return self >> Subtract(other) - #TODO ***MG*** def __rsub__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Subtracts this feature from another value using right '-'. - - """ + """Subtract this feature from another value using right '-'. + + This operator is the right-hand version of `-`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other - feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.Subtract(value=feature) + + Internally, this method constructs a `Value` feature from `other` and + chains it into a `Subtract` feature that subtracts the current feature + as a dynamic value. + + Parameters + ---------- + other: Any + A constant or `Feature` to which `self` will be subtracted. It is + passed as the input to `Value`. + + Returns + ------- + Feature + A new feature that subtracts `self` from `other`. + + Examples + -------- + >>> import deeptrack as dt + + Subtract a feature from a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 5 - feature + >>> result = pipeline() + >>> result + [4, 3, 2] + + This is equivalent to: + >>> pipeline = dt.Value(value=5) >> dt.Subtract(value=feature) + + Subtract a feature from a dynamic value: + >>> import numpy as np + >>> + >>> noise = dt.Value(value=lambda: np.random.rand()) + >>> pipeline = noise - feature + >>> result = pipeline.update()() + >>> result + [-0.18761746914784516, -1.1876174691478452, -2.1876174691478454] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=lambda: np.random.rand()) + ... >> dt.Subtract(value=feature) + ... ) + + """ return Value(other) >> Subtract(self) From df7fb8147d7b770b403fe6a8ea78a1d33c4e74aa Mon Sep 17 00:00:00 2001 From: Mirja Granfors <95694095+mirjagranfors@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:09:06 +0200 Subject: [PATCH 151/223] Added more documentation for features/__mul__ (#364) * Added more documentation for features/__mul__ * Update features.py --------- Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 58 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index d789dd4ab..c99f16290 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2523,13 +2523,61 @@ def __rsub__( return Value(other) >> Subtract(self) - #TODO ***MG*** def __mul__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Multiplies this feature with another value using '*'. - + """Multiply this feature with another value using '*'. + + This operator is shorthand for chaining with `Multiply`. + The expression: + + >>> feature * other + + is equivalent to: + + >>> feature >> dt.Multiply(value=other) + + Internally, this method constructs a new `Multiply` feature and uses + the right-shift operator (`>>`) to chain the current feature into it. + + Parameters + ---------- + other: Any + The value or `Feature` to be multiplied. It is passed to + `dt.Multiply` as the `value` argument. + + Returns + ------- + Feature + A new feature that multiplies `other` to the output of `self`. + + Examples + -------- + >>> import deeptrack as dt + + Multiply a constant value to a static input: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature * 2 + >>> result = pipeline() + >>> result + [2, 4, 6] + + This is equivalent to: + >>> pipeline = feature >> dt.Multiply(value=2) + + Multiply with a dynamic feature that samples a value at each call: + >>> import numpy as np + >>> + >>> noise = dt.Value(value=lambda: np.random.rand()) + >>> pipeline = feature * noise + >>> result = pipeline.update()() + >>> result + [0.2809370704818722, 0.5618741409637444, 0.8428112114456167] + + This is equivalent to: + >>> pipeline = feature >> dt.Multiply(value=noise) + """ return self >> Multiply(other) From 69ae21a0f9e84d27120d1079f4949be53a956c63 Mon Sep 17 00:00:00 2001 From: Mirja Granfors <95694095+mirjagranfors@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:17:20 +0200 Subject: [PATCH 152/223] Added more documentation to features/__rmul__ (#365) --- deeptrack/features.py | 63 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index c99f16290..8aa5b60e0 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2582,13 +2582,66 @@ def __mul__( return self >> Multiply(other) - #TODO **MG** + def __rmul__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Multiplies another value with this feature using right '*'. - + """Multiply another value with this feature using right '*'. + + This operator is the right-hand version of `*`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other * feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.Multiply(value=feature) + + Internally, this method constructs a `Value` feature from `other` and + chains it into a `Multiply` feature that multiplies the current feature + as a dynamic value. + + Parameters + ---------- + other: Any + A constant or `Feature` that will be multiplied by `self`. It is + passed as the input to `Value`. + + Returns + ------- + Feature + A new feature that muliplies `self` by `other`. + + Examples + -------- + >>> import deeptrack as dt + + Multiply a feature to a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 2 * feature + >>> result = pipeline() + >>> result + [2, 4, 6] + + This is equivalent to: + >>> pipeline = dt.Value(value=2) >> dt.Multiply(value=feature) + + Multiply a feature to a dynamic value: + >>> import numpy as np + >>> + >>> noise = dt.Value(value=lambda: np.random.rand()) + >>> pipeline = noise * feature + >>> result = pipeline.update()() + >>> result + [0.8784860790329121, 1.7569721580658242, 2.635458237098736] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=lambda: np.random.rand()) + ... >> dt.Multiply(value=feature) + ... ) + """ return Value(other) >> Multiply(self) From c35e5be21af86d125bca8df24df58b0f5eba0602 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 14:18:20 +0100 Subject: [PATCH 153/223] Update features.py --- deeptrack/features.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 8aa5b60e0..28269dd85 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2582,7 +2582,6 @@ def __mul__( return self >> Multiply(other) - def __rmul__( self: Feature, other: Any, From 1bb3541f14da23e661095c0098de5389166f9a7c Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:22:50 +0200 Subject: [PATCH 154/223] Polish documentationof feature/__pow__ (#366) * Update documentation of features/__pow__ * Update * u * Polish 1. remove >>> 2. remove blank * u * u * Update features.py --------- Co-authored-by: Jiacheng Huang Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 28269dd85..a1ba25205 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2693,7 +2693,7 @@ def __pow__( self: Feature, other: Any, ) -> Feature: - """Raise this feature to a power using '**'. + """Raise this feature (base) to a power (exponent) using '**'. This operator is shorthand for chaining with `Power`. The expression: @@ -2703,14 +2703,14 @@ def __pow__( >>> feature >> dt.Power(value=other) - Internally, this method constructs a new `Power` feature and uses the + Internally, this method constructs a new `Power` feature and uses the right-shift operator (`>>`) to chain the current feature into it. Parameters ---------- other: Any - The value or `Feature` representing the exponent. It is passed to `Power` - as the `value` argument. + The value or `Feature` representing the exponent. It is passed to + `Power` as the `value` argument. Returns ------- @@ -2734,14 +2734,14 @@ def __pow__( Raise to a dynamic exponent that samples values at each call: >>> import numpy as np >>> - >>> noise = dt.Value(value=lambda: np.random.randint(10)) - >>> pipeline = feature ** noise + >>> random_exponent = dt.Value(value=lambda: np.random.randint(10)) + >>> pipeline = feature ** random_exponent >>> result = pipeline.update()() >>> result [1, 64, 729] This is equivalent to: - >>> pipeline = feature >> dt.Power(value=noise) + >>> pipeline = feature >> dt.Power(value=random_exponent) """ From d9d93eae8b59309982e2abc73acaa0b58d419b55 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Tue, 8 Jul 2025 15:28:30 +0200 Subject: [PATCH 155/223] Add documentation of features/__rpow__ (#367) * Add documentation of features/__rpow__ * u * u * u * u * Update features.py --------- Co-authored-by: Jiacheng Huang Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index a1ba25205..89d3ab74e 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2747,12 +2747,65 @@ def __pow__( return self >> Power(other) - #TODO ***JH*** def __rpow__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Raises another value to this feature as a power using right '**'. + """Raise another value (base) to this feature (exponent) as a power + using right '**'. + + This operator is the right-hand version of `**`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other ** feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.Power(value=feature) + + Internally, this method constructs a `Value` feature from `other` + (base) and chains it into a `Power` feature (exponent). + + Parameters + ---------- + other: Any + A constant or `Feature` representing the base. It is passed as the + `value` argument to `Value`. + + Returns + ------- + Feature + A new feature representing `other` to the power of `self`. + + Examples + -------- + >>> import deeptrack as dt + + Raise a static base to a constant exponent: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 5 ** feature + >>> result = pipeline() + >>> result + [5, 25, 125] + + This is equivalent to: + >>> pipeline = dt.Value(value=5) >> dt.Power(value=feature) + + Raise a dynamic base that samples values at each call to the static + exponent: + >>> import numpy as np + >>> + >>> random_base = dt.Value(value=lambda: np.random.randint(10)) + >>> pipeline = random_base ** feature + >>> result = pipeline.update()() + >>> result + [9, 81, 729] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=lambda: np.random.randint(10)) + ... >> dt.Power(value=feature) + ... ) """ From f54dd7bccfdb1af650e6b6070483805cb21abe35 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 14:57:57 +0100 Subject: [PATCH 156/223] polishing of sources initial polishing of sources --- deeptrack/sources/__init__.py | 19 +- deeptrack/sources/base.py | 18 +- deeptrack/sources/folder.py | 7 +- deeptrack/sources/rng.py | 8 +- .../DTAT391A_sources.base.ipynb | 224 +++++++++++++++--- 5 files changed, 227 insertions(+), 49 deletions(-) diff --git a/deeptrack/sources/__init__.py b/deeptrack/sources/__init__.py index 431a212f7..390e3ba7b 100644 --- a/deeptrack/sources/__init__.py +++ b/deeptrack/sources/__init__.py @@ -1,3 +1,16 @@ -from .base import Source, SourceItem, Product, Subset, Sources, Join, random_split -from .folder import ImageFolder -from .rng import NumpyRNG, PythonRNG +from deeptrack.sources.base import * +from deeptrack.sources.folder import * +from deeptrack.sources.rng import * + +__all__ = [ + "Source", # deeptrack.sources.base + "SourceItem", # deeptrack.sources.base + "Product", # deeptrack.sources.base + "Subset", # deeptrack.sources.base + "Sources", # deeptrack.sources.base + "Join", # deeptrack.sources.base + "random_split", # deeptrack.sources.base + "ImageFolder", # deeptrack.sources.folder + "NumpyRNG", # deeptrack.sources.rng + "PythonRNG", # deeptrack.sources.rng +] diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 31115f676..e877bde07 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -103,6 +103,7 @@ def random_split( """ from __future__ import annotations + from typing import Any, Callable, List, Dict, Union, Generator import functools import itertools @@ -115,6 +116,18 @@ def random_split( from deeptrack.backend.core import DeepTrackNode + +__all__ [ + "Source", + "SourceItem", + "Product", + "Subset", + "Sources", + "Join", + "random_split", +] + + class SourceDeepTrackNode(DeepTrackNode): """A node that creates child nodes when attributes are accessed. @@ -422,7 +435,6 @@ def __init__( super().__init__(**dict_of_lists) - class Subset(Source): def __init__( @@ -458,8 +470,6 @@ def __getattr__( ) -> Any: return getattr(self.source, name) - - class Sources: """Joins multiple sources into a single access point. @@ -563,8 +573,6 @@ def random_split( return [Subset(source, indices[offset - length : offset])\ for offset, length in zip(_accumulate(lengths), lengths)] - - def _accumulate( iterable: List[int], fn: Callable [[int, int], int]=lambda x, y: x + y diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index 0afaccd4d..bd365d1a3 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -52,6 +52,12 @@ from deeptrack.sources.base import Source + +__all__ = [ + "ImageFolder", +] + + known_extensions = ["png", "jpg", "jpeg", "tif", "tiff", "bmp", "gif"] class ImageFolder(Source): @@ -262,4 +268,3 @@ def update_root_source( output.append(subfolder) return tuple(output) - diff --git a/deeptrack/sources/rng.py b/deeptrack/sources/rng.py index 81a849541..660e954cf 100644 --- a/deeptrack/sources/rng.py +++ b/deeptrack/sources/rng.py @@ -39,6 +39,12 @@ from deeptrack.backend.core import DeepTrackNode +__all__ = [ + "NumpyRNG", + "PythonRNG", +] + + class NumpyRNG(Source, np.random.RandomState): """Class that generates multiple numpy random number generators. @@ -147,7 +153,7 @@ def set_index( ) -> Callable: self.reset() return super().set_index(index) - + class PythonRNG(Source, random.Random): """Class that generates multiple random.Random number generators. diff --git a/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb b/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb index 4667af033..16bb14c77 100644 --- a/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb +++ b/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb @@ -29,16 +29,40 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is `base`?\n", + "## 1. What is `base.py`?\n", "\n", - "The `base` module provides utilities for manipulating data sources, primarily when data needs to be dynamically manipulated, filtered or combined. This guide explains how to use each component in the module with examples." + "The `base` module provides utilities for manipulating data sources, primarily when data needs to be dynamically manipulated, filtered, or combined. This guide explains how to use each component in the module with examples." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Dynamically generate child nodes when attributes are accessed with `SourceDeepTrackNode`." + "#TODO ***GV***\n", + "\n", + "The key roles of `base.py` are:\n", + "\n", + "- **First Key Role:**\n", + " Description ...\n", + "\n", + "- **Second Key Role:**\n", + " Description ...\n", + "\n", + "Description ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Dynamically Generating Child Nodes with `SourceDeepTrackNode`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define parent node with `SourceDeepTrackNode`." ] }, { @@ -57,52 +81,135 @@ "source": [ "from deeptrack.sources.base import SourceDeepTrackNode\n", "\n", - "# Define parent node.\n", - "node = SourceDeepTrackNode(lambda: {\"a\": 10, \"b\": 20})\n", - "\n", - "# Create child nodes.\n", + "node = SourceDeepTrackNode(lambda: {\"a\": 10, \"b\": 20})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create some child nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ "child_a = node.a\n", - "child_b = node.b\n", - "\n", - "# Call child nodes.\n", - "print(child_a() + child_b())" + "child_b = node.b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Generate a source item that allows callbacks when accessed" + "Call the child nodes." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, + "outputs": [], + "source": [ + "result = child_a() + child_b()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Generating a Source Item with Callbacks\n", + "\n", + "You will generate a source item that allows callbacks when accessed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a callback." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def callback(item):\n", + " print(f\"CALLBACK - Item accessed: {item}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a `SourceItem` registering this callback." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sources.base import SourceItem\n", + "\n", + "item = SourceItem([callback], a=5, b=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the item to trigger a callback." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Item accessed: SourceItem({'a': 5, 'b': 10})\n", - "5\n" + "CALLBACK - Item accessed: SourceItem({'a': 5, 'b': 10})\n" ] } ], "source": [ - "from deeptrack.sources.base import SourceItem\n", - "\n", - "def callback(item):\n", - " print(\"Item accessed:\", item)\n", - "\n", - "# Create a SourceItem with a callback.\n", - "item = SourceItem([callback], a=5, b=10)\n", - "\n", - "# Call the item to trigger a callback.\n", - "item() \n", - "\n", - "# Access values directly\n", - "print(item[\"a\"])" + "item();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Access values directly." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item[\"a\"]" ] }, { @@ -112,9 +219,34 @@ "## 4. Generate a dataset of multiple `SourceItem` objects." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a source with multiple attributes." + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sources.base import Source\n", + "\n", + "dataset = Source(a=[1, 2, 3], b=[4, 5, 6])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Access elements by index." + ] + }, + { + "cell_type": "code", + "execution_count": 25, "metadata": {}, "outputs": [ { @@ -122,7 +254,31 @@ "output_type": "stream", "text": [ "SourceItem({'a': 1, 'b': 4})\n", - "SourceItem({'a': 2, 'b': 5})\n", + "SourceItem({'a': 2, 'b': 5})\n" + ] + } + ], + "source": [ + "print(dataset[0])\n", + "print(dataset[1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the items in the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ "SourceItem({'a': 1, 'b': 4})\n", "SourceItem({'a': 2, 'b': 5})\n", "SourceItem({'a': 3, 'b': 6})\n" @@ -130,16 +286,6 @@ } ], "source": [ - "from deeptrack.sources.base import Source\n", - "\n", - "# Define a source with multiple attributes.\n", - "dataset = Source(a=[1, 2, 3], b=[4, 5, 6])\n", - "\n", - "# Access elements by index.\n", - "print(dataset[0])\n", - "print(dataset[1]()) \n", - "\n", - "# Print the items in the dataset.\n", "for item in dataset:\n", " print(item)" ] From 3dcfbee877abc94e1283e5f137a2d5c681fabda1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 15:10:38 +0100 Subject: [PATCH 157/223] Update base.py --- deeptrack/sources/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index e877bde07..273a6993f 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -117,7 +117,7 @@ def random_split( from deeptrack.backend.core import DeepTrackNode -__all__ [ +__all__ = [ "Source", "SourceItem", "Product", From 08c3cb9d9e0961fa016b904d7dfb97cfd2b487db Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 16:19:21 +0100 Subject: [PATCH 158/223] Update base.py --- deeptrack/sources/base.py | 97 +++++++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 273a6993f..7920751a1 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -1,23 +1,22 @@ """Utility classes for data sources. -This module provides a set of utility classes -designed for managing and manipulating data sources. +This module provides a set of utility classes designed for managing and +manipulating data sources. -These tools are primarily used in scenarios where -data needs to be dynamically manipulated, filtered, or combined -for feature generation in machine learning pipelines. +These tools are primarily used in scenarios where data needs to be dynamically +manipulated, filtered, or combined for feature generation in machine learning +pipelines. Key Features ------------ - **Node Hierarchy** - Extends `DeepTrackNode` with utilities to create + `SourceDeepTrackNode` extends `DeepTrackNode` with utilities to create nested nodes, and structured data access. - **Dynamic Data Access** - Retrieve data items as callable objects, supporting - custom callbacks and dependency tracking. + It retrieves data items as callable objects, supporting custom callbacks and dependency tracking. - **Randomized Splitting** @@ -131,25 +130,86 @@ def random_split( class SourceDeepTrackNode(DeepTrackNode): """A node that creates child nodes when attributes are accessed. - This class is used to create a node that creates child nodes when - attributes are accessed. Assumes the value of the node is dict-like - (i.e. has a __getitem__ method that takes a string). + `SourceDeepTrackNode` is a subclass of `DeepTrackNode` designed to + facilitate structured data access. When an attribute is accessed, it + creates a new child node that retrieves the corresponding key from the + underlying dictionary-like data. + + This is particularly useful when working with hierarchical or nested + data sources, allowing intuitive access via attribute syntax (e.g. + `source.position.x`) and automatic dependency tracking between nodes. + + It assumes the value of the node is dict-like (i.e., that it has a + `__getitem__()` method that takes a string). Parameters ---------- - action: callable - The action that returns the value of the node. + action: Callable[[...], Any] + A callable that returns the value of the node. The return value + must be a dictionary-like object supporting string-key indexing. + + Examples + -------- + >>> from deeptrack.sources.base import SourceDeepTrackNode + + Basic usage with a dictionary-like source: + >>> data = {"x": 42, "y": {"z": 3.14}} + >>> source = SourceDeepTrackNode(lambda: data) + >>> source.x() + 42 + + >>> source.y() + {'z': 3.14} + + >>> source.y.z() + 3.14 + """ def __getattr__( - self, + self: SourceDeepTrackNode, name: str ) -> SourceDeepTrackNode: + """Return a child node corresponding to a key in the underlying data. + + This method is triggered when an attribute is accessed and no + explicitly defined attribute is found. It constructs a new + `SourceDeepTrackNode` that retrieves the value associated with the + given key from the parent node's dictionary-like output. + + The new node is registered as a dependent of the current node to ensure + correct dependency tracking during evaluation. + + Parameters + ---------- + name : str + The key to retrieve from the dictionary-like data returned by `self()`. + + Returns + ------- + SourceDeepTrackNode + A new node that resolves to `self()[name]` when evaluated. + + Examples + -------- + >>> from deeptrack.sources.base import SourceDeepTrackNode + + Basic usage with a dictionary-like source: + >>> source = SourceDeepTrackNode(lambda: {"a": {"b": 1}}) + >>> source.a() + {'b': 1} + + >>> source.a.b() + 1 + + """ + node = SourceDeepTrackNode(lambda: self()[name]) node.add_dependency(self) - self.add_child(node) + # self.add_child(node) return node + class SourceItem(dict): """A dict-like object that calls a list of callbacks when called. @@ -185,6 +245,7 @@ def __repr__( ) -> str: return f"SourceItem({super().__repr__()})" + class Source: """A class that represents one or more sources of data. @@ -396,6 +457,7 @@ def on_activate( ) -> None: self._callbacks.add(callback) + class Product(Source): """Class that represents the product of a source with one or more sources. @@ -435,6 +497,7 @@ def __init__( super().__init__(**dict_of_lists) + class Subset(Source): def __init__( @@ -470,6 +533,7 @@ def __getattr__( ) -> Any: return getattr(self.source, name) + class Sources: """Joins multiple sources into a single access point. @@ -515,8 +579,10 @@ def _callback( getattr(self, key).invalidate() getattr(self, key).set_value(item[key]) + Join = Sources + def random_split( source: Source, lengths: List[Union[int, float]], @@ -573,6 +639,7 @@ def random_split( return [Subset(source, indices[offset - length : offset])\ for offset, length in zip(_accumulate(lengths), lengths)] + def _accumulate( iterable: List[int], fn: Callable [[int, int], int]=lambda x, y: x + y From 3c5a439fd1a843351c5d8aa78478ed08e6a82f4b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 16:22:28 +0100 Subject: [PATCH 159/223] Update base.py --- deeptrack/sources/base.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 7920751a1..b6bf9eacb 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -182,8 +182,9 @@ def __getattr__( Parameters ---------- - name : str - The key to retrieve from the dictionary-like data returned by `self()`. + name: str + The key to retrieve from the dictionary-like data returned by + `self()`. Returns ------- From c34f3daa5f7eeeede809c252854d3252e111953c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 20:00:34 +0100 Subject: [PATCH 160/223] Update base.py --- deeptrack/sources/base.py | 118 +++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index b6bf9eacb..2f7a8a9cf 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -52,9 +52,9 @@ def random_split( source: Source, - lengths: List[Union[int, float]], + lengths: list[int or float], generator: np.random.Generator = np.random.default_rng() - ) -> List[Subset]: + ) -> list[Subset]: Randomly split source into non-overlapping new sources of given lengths. Examples @@ -103,7 +103,7 @@ def random_split( from __future__ import annotations -from typing import Any, Callable, List, Dict, Union, Generator +from typing import Any, Callable, Generator import functools import itertools import math @@ -227,22 +227,22 @@ class SourceItem(dict): """ def __init__( - self, - callbacks: List[Callable[[Any], None]], + self: SourceItem, + callbacks: list[Callable[[Any], None]], **kwargs: Any ) -> None: self._callbacks = callbacks super().__init__(**kwargs) def __call__( - self + self: SourceItem, ) -> SourceItem: for callback in self._callbacks: callback(self) return self def __repr__( - self + self: SourceItem, ) -> str: return f"SourceItem({super().__repr__()})" @@ -275,9 +275,9 @@ class Source: A dictionary of lists or arrays. The keys of the dictionary are the names of the sources, and the values are the sources themselves. """ - + def __init__( - self, + self: Source, **kwargs ) -> None: self.validate_all_same_length(kwargs) @@ -290,21 +290,21 @@ def __init__( setattr(self, k, self._wrap(k)) def __len__( - self + self: Source, ) -> int: return self._length def __getitem__( - self, + self: Source, index: int - ) -> Union[SourceItem, List[SourceItem]]: + ) -> SourceItem | list[SourceItem]: if isinstance(index, slice): return self._get_slice(index) else: return self._get_item(index) def product( - self, + self: Source, **kwargs ) -> Product: """Return the product of the source with the given sources. @@ -335,7 +335,7 @@ def product( return Product(self, **kwargs) def constants( - self, + self: Source, **kwargs ) -> Product: """Return a new source where the given values are constant. @@ -360,7 +360,7 @@ def constants( return Product(self, **{k: [v] for k, v in kwargs.items()}) def filter( - self, + self: Source, predicate: Callable[..., bool] ) -> Subset: """Return a new source with only the items that satisfy the predicate. @@ -380,16 +380,16 @@ def filter( return Subset(self, indices) def validate_all_same_length( - self, - kwargs: Dict[str, List[Any]] + self: Source, + kwargs: list[Any], ) -> None: lengths = [len(v) for v in kwargs.values()] if not all([l == lengths[0] for l in lengths]): raise ValueError("All sources must have the same length.") def _wrap_indexable( - self, - key: str + self: Source, + key: str, ) -> SourceDeepTrackNode: value_getter = SourceDeepTrackNode( lambda: self._dict[key][self._current_index()] @@ -397,20 +397,20 @@ def _wrap_indexable( value_getter.add_dependency(self._current_index) self._current_index.add_child(value_getter) return value_getter - + def _wrap( - self, - key: str + self: Source, + key: str, ) -> SourceDeepTrackNode: value = self._dict[key] if hasattr(value, "__getitem__"): return self._wrap_indexable(key) - + return self._wrap_iterable(key) - + def _wrap_iterable( - self, - key: str + self: Source, + key: str, ) -> SourceDeepTrackNode: value_getter = SourceDeepTrackNode( lambda: list(self._dict[key])[self._current_index()] @@ -420,20 +420,20 @@ def _wrap_iterable( return value_getter def __iter__( - self + self: Source, ) -> Generator[SourceItem, None, None]: for i in range(len(self)): yield self[i] - + def set_index( - self, + self: Source, index: int ) -> Source: self._current_index.set_value(index) return self def _get_item( - self, + self: Source, index: int ) -> SourceItem: values = {k: v[index] for k, v in self._dict.items()} @@ -442,19 +442,19 @@ def _get_item( return SourceItem(callbacks, **values) def _get_slice( - self, - slice: List[SourceItem] - ) -> List[SourceItem]: + self: Source, + slice: list[SourceItem], + ) -> list[SourceItem]: # Convert slice to list of indices. indices = list(range(*slice.indices(len(self)))) # Get values for each index. return [self[i] for i in indices] - + def on_activate( - self, - callback: Callable[[Any], None] + self: Source, + callback: Callable[[Any], None], ) -> None: self._callbacks.add(callback) @@ -471,16 +471,16 @@ class Product(Source): """ def __init__( - self, + self: Product, __source: Source = [{}], - **kwargs: List[Any] + **kwargs: list[Any], ) -> None: product = itertools.product(__source, *kwargs.values()) dict_of_lists = {k: [] for k in kwargs.keys()} source_dict = {k: [] for k in __source[0].keys()} - + # If overlapping keys, error. if set(kwargs.keys()).intersection(set(source_dict.keys())): raise ValueError( @@ -502,9 +502,9 @@ def __init__( class Subset(Source): def __init__( - self, + self: Subset, source: Source, - indices: List[int] + indices: list[int], ) -> None: self.source = source self.indices = indices @@ -512,25 +512,25 @@ def __init__( for k, v in source._dict.items()} def __iter__( - self + self: Subset, ) -> Generator[SourceItem, None, None]: for i in self.indices: yield self.source[i] - + def __getitem__( - self, + self: Subset, index: int ) -> SourceItem: return self.source[self.indices[index]] def __len__( - self + self: Subset, ) -> int: return len(self.indices) - + def __getattr__( - self, - name: str + self: Subset, + name: str, ) -> Any: return getattr(self.source, name) @@ -551,15 +551,15 @@ class Sources: """ def __init__( - self, - *sources: Source + self: Sources, + *sources: Source, ) -> None: self.sources = sources keys = set() for source in sources: keys.update(source._dict.keys()) - + self._dict = dict.fromkeys(keys) for key in keys: @@ -573,8 +573,8 @@ def __init__( source.on_activate(self._callback) def _callback( - self, - item: SourceItem + self: Sources, + item: SourceItem, ) -> None: for key in item: getattr(self, key).invalidate() @@ -586,9 +586,9 @@ def _callback( def random_split( source: Source, - lengths: List[Union[int, float]], + lengths: list[int | float], generator: np.random.Generator = np.random.default_rng() -) -> List[Subset]: +) -> list[Subset]: """Randomly split source into non-overlapping new sources of given lengths. Parameters @@ -618,7 +618,7 @@ def random_split( ) subset_lengths.append(n_items_in_split) remainder = len(source) - sum(subset_lengths) # type: ignore[arg-type] - + # Add 1 to all the lengths in round-robin fashion # until the remainder is 0. for i in range(remainder): @@ -629,7 +629,7 @@ def random_split( if length == 0: warnings.warn(f"Length of split at index {i} is 0. " f"This might result in an empty source.") - + # Cannot verify that dataset is Sized. if sum(lengths) != len(source): # type: ignore[arg-type] raise ValueError("Sum of input lengths does not\ @@ -642,8 +642,8 @@ def random_split( def _accumulate( - iterable: List[int], - fn: Callable [[int, int], int]=lambda x, y: x + y + iterable: list[int], + fn: Callable [[int, int], int]=lambda x, y: x + y, ) -> Generator[int, None, None]: """Returns running totals with user specified operator. From a0c16927373bdc38de8b94af307f05d810cc5eb1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 20:02:09 +0100 Subject: [PATCH 161/223] Update base.py --- deeptrack/sources/base.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 2f7a8a9cf..040a615f8 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -103,13 +103,10 @@ def random_split( from __future__ import annotations -from typing import Any, Callable, Generator import functools import itertools import math -import random -import warnings -import weakref +from typing import Any, Callable, Generator import numpy as np @@ -627,8 +624,12 @@ def random_split( lengths = subset_lengths for i, length in enumerate(lengths): if length == 0: - warnings.warn(f"Length of split at index {i} is 0. " - f"This might result in an empty source.") + import warnings + + warnings.warn( + f"Length of split at index {i} is 0. " + "This might result in an empty source." + ) # Cannot verify that dataset is Sized. if sum(lengths) != len(source): # type: ignore[arg-type] From b715bda0dece4b0f7f5810f20e95320420c10ae6 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 21:06:00 +0100 Subject: [PATCH 162/223] Update base.py --- deeptrack/sources/base.py | 104 +++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 19 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 040a615f8..e9fc8cdf8 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -34,6 +34,8 @@ - `Source`: Represents one or more sources of data. +- `Join`: Alias of `Source`. + - `Product`: Represents the product of the source with the given sources. This class is used to represent the product of a source with @@ -209,31 +211,84 @@ def __getattr__( class SourceItem(dict): - """A dict-like object that calls a list of callbacks when called. - - Used in conjunction with the Source class to call a list of callbacks - when called. These callbacks are used to activate a certain item - in the source, ensuring all DeepTrackNodes are updated. + """A dict-like object that triggers a list of callbacks when called. + `SourceItem` is used within the `Source` framework to wrap a dictionary + entry that activates one or more callbacks when accessed via calling. + This mechanism ensures that all dependent `DeepTrackNode`s are updated + when a particular item in the source is selected. Parameters ---------- - callbacks: list - A list of callables that are called when the SourceItem is called. - + callbacks: list[Callable[[Any], None]] + A list of callback functions that are executed when the item is called. + Each function receives the `SourceItem` itself as argument. + + Attributes + ---------- + _callbacks : list[Callable[[SourceItem], None]] + Internal list of callbacks that are triggered on call. + + Methods + ------- + __call__() -> SourceItem + Executes all callbacks and returns the item. + + __repr__() -> str + Returns a string representation including the dictionary content + and number of callbacks. + + Examples + -------- + >>> from deeptrack.sources.base import SourceItem + + Implement a callback function: + >>> def log_callback(item): + ... print(f"CALLBACK - Accessed item: {item}") + + Create a SourceItem with dictionary contents and callbacks: + >>> item = dt.SourceItem(callbacks=[log_callback], a=1, b=2) + + Call the item to trigger the callbacks: + >>> item(); + CALLBACK - Accessed item: SourceItem({'a': 1, 'b': 2}, 1 callback(s)) + """ + _callbacks: list[Callable[[Any], None]] + def __init__( self: SourceItem, callbacks: list[Callable[[Any], None]], + **kwargs: Any, + ): + """Initialize a SourceItem. + + Parameters + ---------- + callbacks: list[Callable[[SourceItem], None]] + The list of callbacks to trigger when the item is called. **kwargs: Any - ) -> None: + Additional key-value pairs stored in the dictionary. + + """ + self._callbacks = callbacks + super().__init__(**kwargs) def __call__( self: SourceItem, ) -> SourceItem: + """Call the item, triggering all associated callbacks. + + Returns + ------- + SourceItem + The item itself, after invoking all callbacks. + + """ + for callback in self._callbacks: callback(self) return self @@ -241,7 +296,17 @@ def __call__( def __repr__( self: SourceItem, ) -> str: - return f"SourceItem({super().__repr__()})" + """Return a string representation of the item. + + Returns + ------- + str + The string representation of the dictionary contents. + + """ + + return (f"SourceItem({super().__repr__()}, " + f"{len(self._callbacks)} callback(s))") class Source: @@ -254,6 +319,12 @@ class Source: The feature can then be called with an item from the source to get the value of the feature for that item. + Parameters + ---------- + kwargs: dict + A dictionary of lists or arrays. The keys of the dictionary are + the names of the sources, and the values are the sources themselves. + Example ------- >>> import deeptrack as dt @@ -266,17 +337,12 @@ class Source: >>> sum_feature(source[0]) # returns 4 >>> sum_feature(source[1]) # returns 6 - Parameters - ---------- - kwargs: dict - A dictionary of lists or arrays. The keys of the dictionary are - the names of the sources, and the values are the sources themselves. """ def __init__( self: Source, **kwargs - ) -> None: + ): self.validate_all_same_length(kwargs) self._length = len(kwargs[list(kwargs.keys())[0]]) self._current_index = DeepTrackNode(0) @@ -471,7 +537,7 @@ def __init__( self: Product, __source: Source = [{}], **kwargs: list[Any], - ) -> None: + ): product = itertools.product(__source, *kwargs.values()) @@ -502,7 +568,7 @@ def __init__( self: Subset, source: Source, indices: list[int], - ) -> None: + ): self.source = source self.indices = indices self._dict = {k: [v[i] for i in indices] @@ -550,7 +616,7 @@ class Sources: def __init__( self: Sources, *sources: Source, - ) -> None: + ): self.sources = sources keys = set() From 2b36344c22c4f7fe9913cea89b955826b8abd2ae Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 21:25:10 +0100 Subject: [PATCH 163/223] Update base.py --- deeptrack/sources/base.py | 115 +++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 26 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index e9fc8cdf8..c41b79921 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -107,7 +107,9 @@ def random_split( import functools import itertools -import math +import math + +from collections.abc import Sequence from typing import Any, Callable, Generator import numpy as np @@ -149,7 +151,7 @@ class SourceDeepTrackNode(DeepTrackNode): Examples -------- - >>> from deeptrack.sources.base import SourceDeepTrackNode + >>> from deeptrack.sources import SourceDeepTrackNode Basic usage with a dictionary-like source: >>> data = {"x": 42, "y": {"z": 3.14}} @@ -240,7 +242,7 @@ class SourceItem(dict): Examples -------- - >>> from deeptrack.sources.base import SourceItem + >>> from deeptrack.sources import SourceItem Implement a callback function: >>> def log_callback(item): @@ -312,46 +314,107 @@ def __repr__( class Source: """A class that represents one or more sources of data. - This class is used to represent one or more sources of data. - When accessed, it returns a deeptrack object that can be passed - as properties to features. + `Source` holds one or more named sequences (e.g., lists, arrays) and + makes them accessible by index. It returns `SourceItem` objects that + activate registered callbacks (e.g., for dependency tracking) when called. - The feature can then be called with an item from the source to get the - value of the feature for that item. + Each named field is accessible as an attribute (e.g., `source.a`) and + can be passed directly to DeepTrack features such as `Value`. Features + can then be evaluated on specific items by indexing the source (e.g., + `feature(source[i])`). Parameters ---------- - kwargs: dict - A dictionary of lists or arrays. The keys of the dictionary are - the names of the sources, and the values are the sources themselves. + *kwargs: Sequence[Any] + Named data sources, where each key is the name of a source (e.g., "x", + "label") and each value is an indexable sequence (e.g., list, NumPy + array, PyTorch tensor). All sequences must have the same length and + support integer indexing. + + Attributes + ---------- + _dict: dict[str, Sequence[Any]] + Internal mapping of source names to their corresponding data sequences. - Example + _length: int + Number of items in the source. All fields must have the same length. + + _current_index: DeepTrackNode + A node that holds the current active index. Used for dynamic access + when a source attribute (e.g., `source.a`) is passed to a feature. + + _callbacks: set[Callable[[Any], None]] + A set of callback functions triggered when a `SourceItem` is called. + + Methods #TODO ***GV*** check and complete list of methods ------- + __len__() -> int + It returns the number of items in the source. + + __getitem__(index) -> SourceItem or list[SourceItem] + It retrieves one or more items by index or slice. + + __iter__() -> Generator[SourceItem, None, None] + Iterates over all items in the source. + + product(**kwargs: Sequence[Any]) -> Product + It returns a new source representing the cartesian product of the + current source with the given sequences. + + constants(**kwargs: Sequence[Any]) -> Product + It returns a new source where the given values are treated as + constants. + + filter(predicate: Callable[..., bool]) -> Subset + It returns a new source containing only the items for which the + predicate returns `True`. + + on_activate(callback) -> None + Registers a callback to be called when any item is activated. + + set_index(index) -> Source + Sets the active index used when evaluating attributes like `source.a`. + + Examples + -------- >>> import deeptrack as dt >>> from deeptrack.sources import Source - - >>> source = Source(a=[1, 2], b=[3, 4]) + + Define a source with two fields: + >>> source = Source(a=[1, 2], b=[10, 20]) + + Create features from the source: >>> feature_a = dt.Value(source.a) >>> feature_b = dt.Value(source.b) >>> sum_feature = feature_a + feature_b - >>> sum_feature(source[0]) # returns 4 - >>> sum_feature(source[1]) # returns 6 + + Evaluate features on individual items: + >>> sum_feature(source[0]) + 11 + >>> sum_feature(source[1]) + 22 """ + _dict: dict[str, Sequence[Any]] + _length: int + _current_index: DeepTrackNode + _callbacks: set[Callable[[Any], None]] + def __init__( self: Source, - **kwargs + **kwargs: Sequence[Any], ): self.validate_all_same_length(kwargs) + + self._dict = kwargs self._length = len(kwargs[list(kwargs.keys())[0]]) self._current_index = DeepTrackNode(0) - self._dict = kwargs self._callbacks = set() for k in kwargs: setattr(self, k, self._wrap(k)) - + def __len__( self: Source, ) -> int: @@ -359,7 +422,7 @@ def __len__( def __getitem__( self: Source, - index: int + index: int, ) -> SourceItem | list[SourceItem]: if isinstance(index, slice): return self._get_slice(index) @@ -368,7 +431,7 @@ def __getitem__( def product( self: Source, - **kwargs + **kwargs: Sequence[Any], ) -> Product: """Return the product of the source with the given sources. @@ -396,10 +459,10 @@ def product( """ return Product(self, **kwargs) - + def constants( self: Source, - **kwargs + **kwargs: Sequence[Any], ) -> Product: """Return a new source where the given values are constant. @@ -420,11 +483,11 @@ def constants( names of the sources, and the values are the values themselves. """ - return Product(self, **{k: [v] for k, v in kwargs.items()}) - + return Product(self, **{k: [v] for k, v in kwargs.items()}) + def filter( self: Source, - predicate: Callable[..., bool] + predicate: Callable[..., bool], ) -> Subset: """Return a new source with only the items that satisfy the predicate. From ab4223519480d5d4d62c59f7835487441db0e22b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Tue, 8 Jul 2025 21:43:39 +0100 Subject: [PATCH 164/223] Update base.py --- deeptrack/sources/base.py | 101 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index c41b79921..a544a8190 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -381,7 +381,10 @@ class Source: >>> from deeptrack.sources import Source Define a source with two fields: - >>> source = Source(a=[1, 2], b=[10, 20]) + >>> source = Source( + ... a=[1, 2, 3, 4, 5, 6, 7, 8, 9], + ... b=[10, 20, 30, 40, 50, 60, 70, 80, 90], + >>> ) Create features from the source: >>> feature_a = dt.Value(source.a) @@ -391,8 +394,54 @@ class Source: Evaluate features on individual items: >>> sum_feature(source[0]) 11 - >>> sum_feature(source[1]) - 22 + >>> sum_feature(source[8]) + 99 + + Filter items using a predicate: + >>> filtered = source.filter(lambda a, b: a > 5 and b < 80) + >>> list(filtered) + [SourceItem({'a': 6, 'b': 60}, 1 callback(s)), + SourceItem({'a': 7, 'b': 70}, 1 callback(s))] + + Slice the source: + >>> subset = source[3:5] + >>> subset + [SourceItem({'a': 4, 'b': 40}, 1 callback(s)), + SourceItem({'a': 5, 'b': 50}, 1 callback(s))] + + Add a constant field to the source: + >>> augmented = source.constants(label="train") + >>> augmented[0]["label"] + 'train' + + Take a Cartesian product with a new field: + >>> extended = source.product(c=[100, 200]) + >>> len(extended) + 18 # 9 original items × 2 values in "c" + >>> extended[0]["c"] + 100 + >>> extended[17]["c"] + 200 + + Use set_index to manually select the active item: + >>> source.set_index(1) + >>> source.a() + 2 + >>> source.b() + 20 + + Iterate over items in the source: + >>> for item in source: + ... print(item["a"], item["b"]) + 1 10 + 2 20 + 3 30 + 4 40 + 5 50 + 6 60 + 7 70 + 8 80 + 9 90 """ @@ -405,6 +454,52 @@ def __init__( self: Source, **kwargs: Sequence[Any], ): + """Initialize a Source with one or more named data sequences. + + The input sequences must all have the same length and support integer + indexing (i.e., implement both `__getitem__` and `__len__`). Each key + becomes an attribute of the source and can be passed to DeepTrack + features for dynamic evaluation. + + Parameters + ---------- + **kwargs : Sequence[Any] + Named data sources, where each key is the name of a field (e.g., + "x", "label") and each value is an indexable sequence (e.g., list, + NumPy array, PyTorch tensor). All sequences must have the same + length. + + Raises + ------ + ValueError + If the input sequences do not all have the same length. + + Examples + -------- + >>> from deeptrack.sources import Source + + Create a source with two named sequences (note that they are of the + same length): + >>> source = Source( + ... a=[1, 2, 3, 4, 5, 6, 7, 8, 9], + ... b=[10, 20, 30, 40, 50, 60, 70, 80, 90], + >>> ) + + Iterate over items in the source: + >>> for item in source: + ... print(item["a"], item["b"]) + 1 10 + 2 20 + 3 30 + 4 40 + 5 50 + 6 60 + 7 70 + 8 80 + 9 90 + + """ + self.validate_all_same_length(kwargs) self._dict = kwargs From 88c44585ae12685e2a40e255e090e1395a958f32 Mon Sep 17 00:00:00 2001 From: Alex <95913221+Pwhsky@users.noreply.github.com> Date: Wed, 9 Jul 2025 09:55:22 +0200 Subject: [PATCH 165/223] features: __truediv__ (#362) * implemented truediv and rtruediv * docstring clarification * removed white spaces and updated example * example update * Implemented feedback * Update features.py --------- Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 112 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 9 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 89d3ab74e..d323053c2 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2645,23 +2645,117 @@ def __rmul__( return Value(other) >> Multiply(self) - #TODO ***AL*** def __truediv__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Divides this feature by another value using '/'. + """Divide a feature (nominator) using `/` with another + value (denominator). + + This operator is shorthand for chaining with `dt.Divide`. + The expression: + + >>> feature / other + + is equivalent to: + + >>> feature >> dt.Divide(value=other) + + Internally, this method constructs a new `Divide` feature and uses the + right-shift operator (`>>`) to chain the current feature into it. + + Parameters + ---------- + other: Any + The value or `Feature` to divide feature with. It is passed to + `Divide` as the `value` argument. + + Returns + ------- + Feature + A new feature that is `self` divided by `other`. + + Examples + -------- + >>> import deeptrack as dt + + Divide a feature with a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature / 5 + >>> result = pipeline() + >>> result + [0.2, 0.4, 0.6] + + This is equivalent to: + >>> pipeline = feature >> dt.Divide(value=5) + + Implement feature that normalizes the features values: + >>> feature = dt.Value(value=[1, 25, 20]) + >>> magnitude = dt.Value(value=lambda: max(feature())) + >>> pipeline = feature / magnitude + >>> result = pipeline() + >>> result + [0.04, 1.0, 0.8] + + This is equivalent to: + >>> pipeline = ( + ... feature + ... >> dt.Divide(value=lambda: max(feature()) + ... ) """ return self >> Divide(other) - #TODO ***AL*** def __rtruediv__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Divides another value by this feature using right '/'. + """Divide `other` value (nominator) by this feature (denominator) + using right '/'. + + This operator is shorthand for chaining with `dt.Divide`, and is the + right-hand side version of `__truediv__`. + + The expression: + + >>> other / feature + + is equivalent to: + + >>> other >> dt.Divide(value=feature) + + Internally, this method constructs a new `Value` feature from `other` + and uses the right-shift operator (`>>`) to chain it into a `Divide` + feature. + + Parameters + ---------- + other: Any + The constant or `Feature` to be divided by `self`. It is passed to + `Divide` as the input to `Value`. + + Returns + ------- + Feature + A new feature that is `other` divided by `self`. + + Examples + -------- + >>> import deeptrack as dt + + Divide a constant with a feature. + >>> feature = dt.Value(value=[-1, 2, 2]) + >>> pipeline = 5 / feature + >>> result = pipeline() + >>> result + [-5.0, 2.5, 2.5] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=5) + ... >> dt.Divide(value=feature) + ... ) """ @@ -2684,7 +2778,7 @@ def __rfloordiv__( other: Any ) -> Feature: """Performs right floor division using '//'. - + """ return Value(other) >> FloorDivide(self) From dcd33149eba1a5a982af03c3549413b7dccc0540 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 08:56:21 +0100 Subject: [PATCH 166/223] Update base.py --- deeptrack/sources/base.py | 49 ++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index a544a8190..626c358cf 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -276,7 +276,7 @@ def __init__( """ self._callbacks = callbacks - + super().__init__(**kwargs) def __call__( @@ -369,12 +369,12 @@ class Source: It returns a new source containing only the items for which the predicate returns `True`. - on_activate(callback) -> None - Registers a callback to be called when any item is activated. - set_index(index) -> Source Sets the active index used when evaluating attributes like `source.a`. + on_activate(callback: Callable[[SourceItem], None]) -> None + Registers a callback to be called when any item is activated. + Examples -------- >>> import deeptrack as dt @@ -648,18 +648,19 @@ def __iter__( def set_index( self: Source, - index: int + index: int, ) -> Source: + self._current_index.set_value(index) return self def _get_item( self: Source, - index: int + index: int, ) -> SourceItem: values = {k: v[index] for k, v in self._dict.items()} callbacks = list(self._callbacks) - callbacks.append(lambda _: self.set_index(index)) + [lambda _: self.set_index(index)] + list(self._callbacks) # callbacks.append(lambda _: self.set_index(index)) return SourceItem(callbacks, **values) def _get_slice( @@ -675,8 +676,40 @@ def _get_slice( def on_activate( self: Source, - callback: Callable[[Any], None], + callback: Callable[[SourceItem], None], ) -> None: + """Register a callback to be triggered when a SourceItem is activated. + + The callback will be executed every time a `SourceItem` produced by + this `Source` is called (i.e., when `item()` is invoked). The callback + receives the `SourceItem` as its argument, allowing access or mutation + of its contents. + + Parameters + ---------- + callback : Callable[[SourceItem], None] + A function that takes a `SourceItem` and performs a side-effect + (e.g., logging, modifying metadata, triggering updates). The + function must return None. + + Examples + -------- + >>> from deeptrack.sources import Source + + Define a callback function: + >>> def log_access(item): + ... print(f"CALLBACK - Item accessed: {item}") + + Create a source and register the callback: + >>> source = Source(a=[1, 2], b=[10, 20]) + >>> source.on_activate(log_access) + + >>> item = source[0] + >>> item(); + CALLBACK - Item accessed: SourceItem({'a': 1, 'b': 10}, 2 callback(s)) + + """ + self._callbacks.add(callback) From ca587ea07672a31c7f15778e4d3df877e625e00c Mon Sep 17 00:00:00 2001 From: Alex <95913221+Pwhsky@users.noreply.github.com> Date: Wed, 9 Jul 2025 10:31:32 +0200 Subject: [PATCH 167/223] al/features/__rtruediv__ polishing (#369) * removed update(), added dynamic example for rtruediv * Update features.py * Update features.py --------- Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index d323053c2..3d1958d4a 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2689,7 +2689,7 @@ def __truediv__( This is equivalent to: >>> pipeline = feature >> dt.Divide(value=5) - Implement feature that normalizes the features values: + Implement a normalization pipeline: >>> feature = dt.Value(value=[1, 25, 20]) >>> magnitude = dt.Value(value=lambda: max(feature())) >>> pipeline = feature / magnitude @@ -2727,7 +2727,7 @@ def __rtruediv__( Internally, this method constructs a new `Value` feature from `other` and uses the right-shift operator (`>>`) to chain it into a `Divide` - feature. + feature that divides the current feature as a dynamic value. Parameters ---------- @@ -2757,6 +2757,22 @@ def __rtruediv__( ... >> dt.Divide(value=feature) ... ) + Divide a dynamic value with a feature: + >>> import numpy as np + >>> + >>> scale_factor = dt.Value(value=5) + >>> noise = dt.Value(value=lambda: np.random.rand()) + >>> pipeline = noise / scale_factor + >>> result = pipeline() + >>> result + 0.13736078990870043 + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=lambda: np.random.rand()) + ... >> dt.Divide(value=scale_factor) + ... ) + """ return Value(other) >> Divide(self) From 0f7ab6b10bcb0925771e20caeb08b8bd6c4da990 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 13:11:38 +0100 Subject: [PATCH 168/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 3d1958d4a..26184de6a 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2763,7 +2763,7 @@ def __rtruediv__( >>> scale_factor = dt.Value(value=5) >>> noise = dt.Value(value=lambda: np.random.rand()) >>> pipeline = noise / scale_factor - >>> result = pipeline() + >>> result = pipeline.update()() >>> result 0.13736078990870043 From 5b8db3ee1a2369a78bfa37e5f0e412fa01e65cda Mon Sep 17 00:00:00 2001 From: Alex <95913221+Pwhsky@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:00:22 +0200 Subject: [PATCH 169/223] features: __floordiv__ (#368) * added __floordiv__ * docs * Implemented feedback from Mirja * minor adjustments * updated example * Update features.py --------- Co-authored-by: mirjagranfors Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 125 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 26184de6a..96f463961 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2777,23 +2777,130 @@ def __rtruediv__( return Value(other) >> Divide(self) - #TODO ***AL*** def __floordiv__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Performs floor division using '//'. - + """Perform floor division of `feature` (numerator) with + `other` (denominator) using `//`. + + This operator is shorthand for chaining with `FloorDivide`. + The expression: + + >>> feature // other + + is equivalent to: + + >>> feature >> dt.FloorDivide(value=other) + + Internally, this method constructs a new `FloorDivide` feature and uses + the right-shift operator (`>>`) to chain the current feature with it. + + Parameters + ---------- + other: Any + A constant or `Feature` by which `self` will be floor-divided. It + is passed as the input to `value`. + + Returns + ------- + Feature + A new feature that floor divides `self` with `other`. + + Examples + -------- + >>> import deeptrack as dt + + Floor divide a feature with a constant: + >>> feature = dt.Value(value=[5, 9, 12]) + >>> pipeline = feature // 2 + >>> result = pipeline() + >>> result + [2, 4, 6] + + This is equivalent to: + >>> pipeline = feature >> dt.FloorDivide(value=2) + + Floor divide a dynamic feature by another feature: + >>> import numpy as np + >>> + >>> randint = dt.Value(value=lambda: np.random.randint(1, 5)) + >>> feature = dt.Value(value=[20, 30, 40]) + >>> pipeline = feature // randint + >>> result = pipeline.update()() + >>> result + [6, 10, 13] + + This is equivalent to: + >>> pipeline = ( + ... feature + ... >> dt.FloorDivide(value=lambda: np.random.randint(1, 5)) + ... ) + """ return self >> FloorDivide(other) - #TODO ***AL*** def __rfloordiv__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Performs right floor division using '//'. + """Perform floor division of `other` (numerator) with + `feature` (denominator) using '//'. + + This operator is shorthand for chaining with `FloorDivide`. + The expression: + + >>> other // feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.FloorDivide(value=feature) + + Internally, this method constructs a `Value` feature from `other` and + chains it into a `FloorDivide` feature that divides with the current + feature. + + Parameters + ---------- + other: Any + A constant or `Feature` which will be floor divided with `self`. + It is passed as the input to `Value`. + + Returns + ------- + Feature + A new feature that floor divides `other` with `self`. + + Examples + -------- + >>> import deeptrack as dt + + Floor divide a feature with a constant: + >>> feature = dt.Value(value=[5, 9, 12]) + >>> pipeline = 10 // feature + >>> result = pipeline() + >>> result + [2, 1, 0] + + This is equivalent to: + >>> pipeline = dt.Value(value=10) >> dt.FloorDivide(value=feature) + + Floor divide a dynamic feature by another feature: + >>> import numpy as np + >>> + >>> randint = dt.Value(value=lambda: np.random.randint(1, 5)) + >>> feature = dt.Value(value=[2, 3, 4]) + >>> pipeline = randint // feature + >>> result = pipeline.update()() + >>> result + [1, 1, 0] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=lambda: np.random.randint(1, 5)) + ... >> dt.FloorDivide(value=feature) + ... ) """ From e36d948441968a2a31bbf7a7835ddfd56cfae5f0 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 16:03:10 +0100 Subject: [PATCH 170/223] Update features.py --- deeptrack/features.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 96f463961..25984b505 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -2781,16 +2781,18 @@ def __floordiv__( self: Feature, other: Any, ) -> Feature: - """Perform floor division of `feature` (numerator) with - `other` (denominator) using `//`. + """Perform floor division of feature with other using `//`. + + It performs the floor division of `feature` (numerator) with `other` + (denominator) using `//`. This operator is shorthand for chaining with `FloorDivide`. The expression: >>> feature // other - + is equivalent to: - + >>> feature >> dt.FloorDivide(value=other) Internally, this method constructs a new `FloorDivide` feature and uses @@ -2845,7 +2847,9 @@ def __rfloordiv__( self: Feature, other: Any, ) -> Feature: - """Perform floor division of `other` (numerator) with + """Perform floor division of other with feature using '//'. + + This operator performs the floor division of `other` (numerator) with `feature` (denominator) using '//'. This operator is shorthand for chaining with `FloorDivide`. From 1ddce8912f8a28fc7bc1ae9fa7f1137f656bc0ed Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 16:52:03 +0100 Subject: [PATCH 171/223] Update base.py --- deeptrack/sources/base.py | 124 ++++++++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 626c358cf..7d03eeb6c 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -524,6 +524,110 @@ def __getitem__( else: return self._get_item(index) + def _get_item( + self: Source, + index: int, + ) -> SourceItem: + """Retrieve a single SourceItem at a specified index. + + This method extracts the values at the given index from all fields + in the source and wraps them in a `SourceItem`. It also attaches + callbacks that are executed when the item is activated (i.e., called). + + The first callback sets the active index in the source to the given + index. Additional callbacks come from those registered via the + `on_activate()` method. + + Parameters + ---------- + index: int + The index of the item to retrieve. + + Returns + ------- + SourceItem + The item at the specified index, wrapped with activation callbacks. + + Examples + -------- + >>> from deeptrack.sources import Source + + Create a source: + >>> source = Source(a=[1, 2], b=[10, 20]) + + Extract the source item corresponding to index 1: + >>> item = source._get_item(1) + >>> item + SourceItem({'a': 2, 'b': 20}, 1 callback(s)) + + Since the item has not been activated the current index of the source + is still 0: + >>> source._current_index() + 0 + + Activate the item and sets the source's current index to 1: + >>> item() + >>> source._current_index() + 1 + + """ + + # Collect field values at the given index from all source sequences + values = {k: v[index] for k, v in self._dict.items()} + + # Prepend the set_index callback so the active index is updated first + callbacks = [lambda _: self.set_index(index)] + list(self._callbacks) + + return SourceItem(callbacks, **values) + + def _get_slice( + self: Source, + slice_obj: slice, + ) -> list[SourceItem]: + """Retrieve a list of SourceItems corresponding to a slice. + + This method returns a list of `SourceItem`s corresponding to the given + slice object (e.g., `source[1:4]`). It converts the slice to a list + of integer indices and uses the `_get_item()` method to retrieve each + item. + + Parameters + ---------- + slice_obj: slice + A slice object representing the range of indices to retrieve. + + Returns + ------- + list[SourceItem] + A list of SourceItems corresponding to the selected range. + + Examples + -------- + >>> from deeptrack.sources import Source + + Create a source: + >>> source = Source( + ... a=[1, 2, 3, 4, 5, 6, 7, 8, 9], + ... b=[10, 20, 30, 40, 50, 60, 70, 80, 90], + ... ) + + Get a slice of the source: + >>> source[1:4] + [SourceItem({'a': 2, 'b': 20}, 1 callback(s)), + SourceItem({'a': 3, 'b': 30}, 1 callback(s)), + SourceItem({'a': 4, 'b': 40}, 1 callback(s))] + + This is equivalent to: + >>> source._get_slice(slice(1, 4)) + + """ + + # Convert slice to list of indices + indices = list(range(*slice_obj.indices(len(self)))) + + # Get values for each index + return [self[i] for i in indices] + def product( self: Source, **kwargs: Sequence[Any], @@ -654,26 +758,6 @@ def set_index( self._current_index.set_value(index) return self - def _get_item( - self: Source, - index: int, - ) -> SourceItem: - values = {k: v[index] for k, v in self._dict.items()} - callbacks = list(self._callbacks) - [lambda _: self.set_index(index)] + list(self._callbacks) # callbacks.append(lambda _: self.set_index(index)) - return SourceItem(callbacks, **values) - - def _get_slice( - self: Source, - slice: list[SourceItem], - ) -> list[SourceItem]: - - # Convert slice to list of indices. - indices = list(range(*slice.indices(len(self)))) - - # Get values for each index. - return [self[i] for i in indices] - def on_activate( self: Source, callback: Callable[[SourceItem], None], From 6057d6eb3ea7f87c488b024a2c3d6dcad9244a63 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 16:54:19 +0100 Subject: [PATCH 172/223] Update base.py --- deeptrack/sources/base.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 7d03eeb6c..33fbb4bda 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -587,9 +587,8 @@ def _get_slice( """Retrieve a list of SourceItems corresponding to a slice. This method returns a list of `SourceItem`s corresponding to the given - slice object (e.g., `source[1:4]`). It converts the slice to a list - of integer indices and uses the `_get_item()` method to retrieve each - item. + slice object (e.g., `source[1:4]`). It converts the slice into a list + of integer indices and uses `_get_item()` to retrieve each item. Parameters ---------- @@ -619,13 +618,12 @@ def _get_slice( This is equivalent to: >>> source._get_slice(slice(1, 4)) - """ - # Convert slice to list of indices + # Convert the slice to a list of indices indices = list(range(*slice_obj.indices(len(self)))) - # Get values for each index + # Get values for each index using _get_item() return [self[i] for i in indices] def product( From 3638042cafa4b61f4f986f2883e404b1f34140b8 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 17:47:27 +0100 Subject: [PATCH 173/223] Update base.py --- deeptrack/sources/base.py | 206 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 195 insertions(+), 11 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 33fbb4bda..da03f4afb 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -351,11 +351,14 @@ class Source: __len__() -> int It returns the number of items in the source. - __getitem__(index) -> SourceItem or list[SourceItem] + __getitem__(index: int | slice) -> SourceItem or list[SourceItem] It retrieves one or more items by index or slice. - __iter__() -> Generator[SourceItem, None, None] - Iterates over all items in the source. + _get_item(index: int) -> SourceItem: + It retrieves a single SourceItem at a specified index. + + _get_slice(slice_obj: slice) -> list[SourceItem]: + It retrieves a list of SourceItems corresponding to a slice. product(**kwargs: Sequence[Any]) -> Product It returns a new source representing the cartesian product of the @@ -369,11 +372,18 @@ class Source: It returns a new source containing only the items for which the predicate returns `True`. + _validate_all_same_length(kwargs: dict[str, Sequence[Any]]) -> None: + It validates that all input sequences have the same length. + + __iter__() -> Generator[SourceItem, None, None] + It iterates over all items in the source. + set_index(index) -> Source - Sets the active index used when evaluating attributes like `source.a`. + It sets the active index used when evaluating attributes, like in + `source.a()`. on_activate(callback: Callable[[SourceItem], None]) -> None - Registers a callback to be called when any item is activated. + It registers a callback to be called when any item is activated. Examples -------- @@ -500,7 +510,7 @@ def __init__( """ - self.validate_all_same_length(kwargs) + self._validate_all_same_length(kwargs) self._dict = kwargs self._length = len(kwargs[list(kwargs.keys())[0]]) @@ -513,12 +523,84 @@ def __init__( def __len__( self: Source, ) -> int: + """Return the number of items in the source. + + This returns the number of indexed entries available in the source, + which corresponds to the length of any of the underlying sequences. + + Returns + ------- + int + The number of items in the source. + + Examples + -------- + >>> from deeptrack.sources import Source + + Create a source: + >>> source = Source(a=[1, 2, 3], b=[10, 20, 30]) + + Get its length: + >>> len(source) + 3 + + """ + return self._length def __getitem__( self: Source, - index: int, + index: int | slice, ) -> SourceItem | list[SourceItem]: + """Retrieve one or more SourceItems by index or slice. + + If the input is an integer, this returns a single `SourceItem` + at the specified index. If the input is a slice, it returns a list + of `SourceItem`s corresponding to the slice range. + + Parameters + ---------- + index: int or slice + The index or slice specifying which item(s) to retrieve. + + Returns + ------- + SourceItem or list[SourceItem] + The item(s) corresponding to the given index or slice. + + Examples + -------- + >>> from deeptrack.sources import Source + + Create a source: + >>> source = Source( + ... a=[1, 2, 3, 4, 5, 6, 7, 8, 9], + ... b=[10, 20, 30, 40, 50, 60, 70, 80, 90], + ... ) + + Retrieve a single item: + >>> item = source[1] + >>> item + SourceItem({'a': 2, 'b': 20}, 1 callback(s)) + + >>> item["a"] + 20 + + >>> item["b"] + 2 + + Retrieve a slice of items: + >>> items = source[1:4] + >>> items + [SourceItem({'a': 2, 'b': 20}, 1 callback(s)), + SourceItem({'a': 3, 'b': 30}, 1 callback(s)), + SourceItem({'a': 4, 'b': 40}, 1 callback(s))] + + >>> [(item["a"], item["b"]) for item in items] + [(2, 20), (3, 30), (4, 40)] + + """ + if isinstance(index, slice): return self._get_slice(index) else: @@ -702,13 +784,46 @@ def filter( indices = [i for i, item in enumerate(self) if predicate(**item)] return Subset(self, indices) - def validate_all_same_length( + def _validate_all_same_length( self: Source, - kwargs: list[Any], + kwargs: dict[str, Sequence[Any]], ) -> None: + """Validate that all input sequences have the same length. + + This method checks that all sequences provided to the source have equal + length. It is called during initialization to ensure consistent + indexing behavior. + + Parameters + ---------- + kwargs: dict[str, Sequence[Any]] + Dictionary of named sequences to validate. + + Raises + ------ + ValueError + If the sequences do not all have the same length. + + Examples + -------- + >>> from deeptrack.sources import Source + + This works: + >>> source = Source(a=[1, 2, 3], b=[10, 20, 30]) + + This raises a ValueError: + >>> source = Source(a=[1, 2], b=[10, 20, 30]) + + """ + lengths = [len(v) for v in kwargs.values()] - if not all([l == lengths[0] for l in lengths]): - raise ValueError("All sources must have the same length.") + unique_lengths = set(lengths) + + if len(unique_lengths) > 1: + raise ValueError( + "All sources must have the same length, but the following " + f"lengths were found: {lengths}" + ) def _wrap_indexable( self: Source, @@ -745,6 +860,31 @@ def _wrap_iterable( def __iter__( self: Source, ) -> Generator[SourceItem, None, None]: + """Iterate over all items in the source. + + This method allows the source to be used in for-loops and + comprehensions by yielding each `SourceItem` in sequence. Each item is + constructed using `__getitem__()`, which attaches the appropriate + callbacks. + + Yields + ------ + SourceItem + Each item in the source, one at a time. + + Examples + -------- + >>> from deeptrack.sources import Source + + >>> source = Source(a=[1, 2], b=[10, 20]) + + >>> for item in source: + ... print(item["a"], item["b"]) + 1 10 + 2 20 + + """ + for i in range(len(self)): yield self[i] @@ -752,8 +892,52 @@ def set_index( self: Source, index: int, ) -> Source: + """Set the active index of the source for dynamic evaluation. + + This method updates the internal `_current_index` node, which is + used when evaluating attribute-based access such as `source.a()`. + It is typically called automatically when a `SourceItem` is + activated, but can also be called manually to override the index. + + Parameters + ---------- + index: int + The index to set as the current active index. + + Returns + ------- + Source + The source itself, allowing method chaining. + + Examples + -------- + >>> from deeptrack.sources import Source + + Create a source: + >>> source = Source( + ... a=[1, 2, 3, 4, 5, 6, 7, 8, 9], + ... b=[10, 20, 30, 40, 50, 60, 70, 80, 90], + ... ) + + >>> source.a(), source.b() + (1, 10) + + >>> source.set_index(5) + >>> source.a(), source.b() + (6, 60) + + >>> source.set_index(-1) + >>> source.a(), source.b() + (9, 90) + + >>> source.set_index(1) + >>> source.a(), source.b() + (2, 20) + + """ self._current_index.set_value(index) + return self def on_activate( From 7a757e7ad108460630e07b23ddf96ef9ba4ae1a8 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 18:21:10 +0100 Subject: [PATCH 174/223] Update base.py --- deeptrack/sources/base.py | 287 +++++++++++++++++++++++++++++--------- 1 file changed, 218 insertions(+), 69 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index da03f4afb..a298c2a9c 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -335,31 +335,16 @@ class Source: ---------- _dict: dict[str, Sequence[Any]] Internal mapping of source names to their corresponding data sequences. - _length: int Number of items in the source. All fields must have the same length. - _current_index: DeepTrackNode A node that holds the current active index. Used for dynamic access when a source attribute (e.g., `source.a`) is passed to a feature. - _callbacks: set[Callable[[Any], None]] A set of callback functions triggered when a `SourceItem` is called. - Methods #TODO ***GV*** check and complete list of methods + Methods ------- - __len__() -> int - It returns the number of items in the source. - - __getitem__(index: int | slice) -> SourceItem or list[SourceItem] - It retrieves one or more items by index or slice. - - _get_item(index: int) -> SourceItem: - It retrieves a single SourceItem at a specified index. - - _get_slice(slice_obj: slice) -> list[SourceItem]: - It retrieves a list of SourceItems corresponding to a slice. - product(**kwargs: Sequence[Any]) -> Product It returns a new source representing the cartesian product of the current source with the given sequences. @@ -371,13 +356,6 @@ class Source: filter(predicate: Callable[..., bool]) -> Subset It returns a new source containing only the items for which the predicate returns `True`. - - _validate_all_same_length(kwargs: dict[str, Sequence[Any]]) -> None: - It validates that all input sequences have the same length. - - __iter__() -> Generator[SourceItem, None, None] - It iterates over all items in the source. - set_index(index) -> Source It sets the active index used when evaluating attributes, like in `source.a()`. @@ -385,6 +363,28 @@ class Source: on_activate(callback: Callable[[SourceItem], None]) -> None It registers a callback to be called when any item is activated. + **Private and internal methods.** + __len__() -> int + It returns the number of items in the source. + __getitem__(index: int | slice) -> SourceItem or list[SourceItem] + It retrieves one or more items by index or slice. + _get_item(index: int) -> SourceItem: + It retrieves a single SourceItem at a specified index. + _get_slice(slice_obj: slice) -> list[SourceItem]: + It retrieves a list of SourceItems corresponding to a slice. + _validate_all_same_length(kwargs: dict[str, Sequence[Any]]) -> None: + It validates that all input sequences have the same length. + _wrap(key: str) -> SourceDeepTrackNode + It wraps a field from the source into a SourceDeepTrackNode. + _wrap_indexable(key: str) -> SourceDeepTrackNode + It wraps an indexable field as a SourceDeepTrackNode. + _wrap_iterable(key: str) -> SourceDeepTrackNode + It wraps a non-indexable iterable field as a SourceDeepTrackNode. + __iter__() -> Generator[SourceItem, None, None] + It iterates over all items in the source. + __repr__() -> str: + It returns a string representation of the source object. + Examples -------- >>> import deeptrack as dt @@ -712,76 +712,124 @@ def product( self: Source, **kwargs: Sequence[Any], ) -> Product: - """Return the product of the source with the given sources. + """Cartesian product of the current source with additional fields. - Returns a source that is the product of th - source with the given sources. + This method returns a new `Product` source formed by taking the + Cartesian product of the current source with the provided sequences. + The new source will contain one item for every combination of the + original items and the new sequences. - Example + Parameters + ---------- + **kwargs: Sequence[Any] + One or more additional sequences to combine with the current + source. The keys define the names of the new fields, and the + values are indexable sequences (e.g., lists or arrays). + + Returns ------- + Product + A new source representing the Cartesian product of the current + source with the additional sequences. + + Examples + -------- >>> from deeptrack.sources import Source - + + Create an initial source: >>> source = Source(a=[1, 2], b=[3, 4]) + + Take the product with a new sequence: >>> new_source = source.product(c=[5, 6]) - >>> new_source - Source(c=[5, 6, 5, 6], - a=[1, 1, 2, 2], - b=[3, 3, 4, 4] - ) - Parameters - ---------- - kwargs: dict - A dictionary of lists or arrays. - The keys of the dictionary are the names of the sources, - and the values are the sources themselves. - + Result: + >>> new_source + Product(c=[5, 6, 5, 6], a=[1, 1, 2, 2], b=[3, 3, 4, 4]) + """ + return Product(self, **kwargs) def constants( self: Source, **kwargs: Sequence[Any], ) -> Product: - """Return a new source where the given values are constant. + """New source where the given values are treated as constants. + + This method extends the current source with one or more constant + fields. Each value is repeated to match the length of the existing + source. + + Parameters + ---------- + **kwargs: Sequence[Any] + Named constant values to add to the source. Each key defines + the name of a new field, and each value will be broadcasted + as a constant (e.g., scalar, string, etc.). - Example + Returns ------- - from deeptrack.sources import Source - + Product + A new source that includes the constant fields in addition to + the original fields. + + Examples + -------- + >>> from deeptrack.sources import Source + + Create a source: >>> source = Source(a=[1, 2], b=[3, 4]) + + Add a constant field: >>> new_source = source.constants(c=5) + + Result: >>> new_source - Equivalent to: - >>> Source(c=[5, 5], a=[1, 2], b=[3, 4]). + Product(c=[5, 5], a=[1, 2], b=[3, 4]) - Parameters - ---------- - kwargs: dict - A dictionary of values. The keys of the dictionary are the - names of the sources, and the values are the values themselves. - """ + return Product(self, **{k: [v] for k, v in kwargs.items()}) def filter( self: Source, predicate: Callable[..., bool], ) -> Subset: - """Return a new source with only the items that satisfy the predicate. + """New source containing only items that satisfy a predicate. + + This method filters the source based on a boolean-valued predicate + applied to each `SourceItem`. The result is a `Subset` containing + only the items for which the predicate returns `True`. - Example + Parameters + ---------- + predicate: Callable[..., bool] + A function that takes the fields of a `SourceItem` as keyword + arguments and returns `True` if the item should be included. + + Returns ------- + Subset + A new source containing only the filtered items. + + Examples + -------- >>> from deeptrack.sources import Source - + + Create a source: >>> source = Source(a=[1, 2], b=[3, 4]) + + Filter to keep only items where a > 1: >>> new_source = source.filter(lambda a, b: a > 1) + + Result: >>> new_source - Equivalent to: - >>> Source(a=[2], b=[4]). - + Subset(a=[2], b=[4]) + """ + indices = [i for i, item in enumerate(self) if predicate(**item)] + return Subset(self, indices) def _validate_all_same_length( @@ -825,36 +873,103 @@ def _validate_all_same_length( f"lengths were found: {lengths}" ) + def _wrap( + self: Source, + key: str, + ) -> SourceDeepTrackNode: + """Wrap a field from the source into a SourceDeepTrackNode. + + This method is called during source initialization to convert + input sequences into graph-compatible nodes. + + This method checks whether the field associated with the given key + is indexable (i.e., supports `__getitem__()`) and wraps it accordingly + using either `_wrap_indexable()` or `_wrap_iterable()`. + + Parameters + ---------- + key: str + The name of the field in the source dictionary. + + Returns + ------- + SourceDeepTrackNode + A node representing access to the field at the current index. + + """ + + value = self._dict[key] + + # If the value supports __getitem__, treat it as indexable + if hasattr(value, "__getitem__"): + return self._wrap_indexable(key) + + # Otherwise, attempt to convert it into a list and wrap it + return self._wrap_iterable(key) + def _wrap_indexable( self: Source, key: str, ) -> SourceDeepTrackNode: + """Wrap an indexable field as a SourceDeepTrackNode. + + This method creates a node that returns the value at the current + index for a field that supports direct indexing (i.e., implements + `__getitem__`). + + The returned node depends on the `_current_index` node, allowing + dynamic evaluation as the index changes. + + Parameters + ---------- + key: str + The name of the field in the source dictionary. + + Returns + ------- + SourceDeepTrackNode + A node that evaluates to `self._dict[key][self._current_index()]`. + + """ + value_getter = SourceDeepTrackNode( lambda: self._dict[key][self._current_index()] ) value_getter.add_dependency(self._current_index) - self._current_index.add_child(value_getter) + # self._current_index.add_child(value_getter) return value_getter - def _wrap( + def _wrap_iterable( self: Source, key: str, ) -> SourceDeepTrackNode: - value = self._dict[key] - if hasattr(value, "__getitem__"): - return self._wrap_indexable(key) + """Wrap a non-indexable iterable field as a SourceDeepTrackNode. - return self._wrap_iterable(key) + This method converts the iterable to a list and creates a node that + returns the value at the current index. It is used when the field does + not support direct indexing. + + Like `_wrap_indexable`, the resulting node depends on the + `_current_index` node for dynamic evaluation. + + Parameters + ---------- + key: str + The name of the field in the source dictionary. + + Returns + ------- + SourceDeepTrackNode + A node that evaluates to + `list(self._dict[key])[self._current_index()]`. + + """ - def _wrap_iterable( - self: Source, - key: str, - ) -> SourceDeepTrackNode: value_getter = SourceDeepTrackNode( lambda: list(self._dict[key])[self._current_index()] ) value_getter.add_dependency(self._current_index) - self._current_index.add_child(value_getter) + # self._current_index.add_child(value_getter) return value_getter def __iter__( @@ -978,6 +1093,40 @@ def on_activate( self._callbacks.add(callback) + def __repr__( + self: Source, + ) -> str: + """Return a string representation of the source object. + + Shows the class name and a truncated preview of each field, displaying + the first four items followed by an ellipsis if longer. + + Returns + ------- + str + A readable summary of the source fields and their contents. + + """ + + field_summaries = [] + + for k, v in self._dict.items(): + try: + preview = list(v[:4]) if len(v) > 4 else list(v) + except Exception: + preview = "" + + if isinstance(preview, list): + suffix = "..." if len(v) > 4 else "" + summary = f"{k}={preview}{suffix}" + else: + summary = f"{k}={preview}" + + field_summaries.append(summary) + + fields_repr = ", ".join(field_summaries) + return f"{self.__class__.__name__}({fields_repr})" + class Product(Source): """Class that represents the product of a source with one or more sources. From fcc380204a74ab31eede86b63c317d4186fb5c93 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 18:31:12 +0100 Subject: [PATCH 175/223] Update base.py --- deeptrack/sources/base.py | 59 +++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index a298c2a9c..21095b284 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -1129,36 +1129,79 @@ def __repr__( class Product(Source): - """Class that represents the product of a source with one or more sources. + """Cartesian product of a source with one or more additional fields. - This class is used to represent the product of a source with - one or more sources. When accessed, it returns a deeptrack object that - can be passed as properties to features. + `Product` constructs a new source by taking the Cartesian product + between an existing `Source` and one or more sequences passed as + keyword arguments. Each item in the result is a unique combination + of an item from the original source and a value from each added field. + + This is typically used via `Source.product(...)`, and the resulting + `Product` can be passed to DeepTrack features for dynamic evaluation. + + Examples + -------- + >>> from deeptrack.sources import Source + + Using the recommended Source.product() method: + >>> source = Source(a=[1, 2]) + >>> product = source.product(b=[10, 20]) + >>> product + Product(b=[10, 20, 10, 20], a=[1, 1, 2, 2]) + + >>> list(product) + [SourceItem({'b': 10, 'a': 1}, 1 callback(s)), + SourceItem({'b': 20, 'a': 1}, 1 callback(s)), + SourceItem({'b': 10, 'a': 2}, 1 callback(s)), + SourceItem({'b': 20, 'a': 2}, 1 callback(s))] + + Equivalent direct usage of Product (advanced): + >>> from deeptrack.sources.base import Product + >>> + >>> product = Product(source, b=[10, 20]) - The feature can then be called with an item from the source - to get the value of the feature for that item. """ def __init__( self: Product, - __source: Source = [{}], + __source: Source, **kwargs: list[Any], ): + """Initialize the Cartesian product of a source with additional fields. + + Parameters + ---------- + __source : Source + The base source to be expanded via Cartesian product. + + **kwargs : list[Any] + Named sequences to take the product with. Each value must be + a list or array of equal length. + + Raises + ------ + ValueError + If any key in `kwargs` overlaps with a key in the original source. + + """ + # Compute the cartesian product of all items product = itertools.product(__source, *kwargs.values()) dict_of_lists = {k: [] for k in kwargs.keys()} source_dict = {k: [] for k in __source[0].keys()} - # If overlapping keys, error. + # Check for overlapping keys. If overlapping keys, error. if set(kwargs.keys()).intersection(set(source_dict.keys())): raise ValueError( f"Overlapping keys in product. Duplicate keys: " f"{set(kwargs.keys()).intersection(set(source_dict.keys()))}" ) + # Initialize combined dictionary dict_of_lists.update(source_dict) + # Populate each field from the cartesian product for source, *items in product: for k, v in source.items(): dict_of_lists[k].append(v) From bc6a9ca676bbf6482bb55c7890095a8da8be5df5 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 20:13:34 +0100 Subject: [PATCH 176/223] Update base.py --- deeptrack/sources/base.py | 46 +++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 21095b284..1745b00d5 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -1131,14 +1131,36 @@ def __repr__( class Product(Source): """Cartesian product of a source with one or more additional fields. - `Product` constructs a new source by taking the Cartesian product - between an existing `Source` and one or more sequences passed as - keyword arguments. Each item in the result is a unique combination - of an item from the original source and a value from each added field. + `Product` constructs a new source by taking the Cartesian product between + an existing `Source` and one or more sequences passed as keyword arguments. + Each item in the result is a unique combination of an item from the + original source and a value from each added field. This is typically used via `Source.product(...)`, and the resulting `Product` can be passed to DeepTrack features for dynamic evaluation. + If no base source is provided, a dummy source with a single empty item is + used. This allows syntax such as: + + >>> Product(x=[1, 2], y=[3, 4]) + + to create a Cartesian product of just the keyword arguments. + + While a list of dictionaries like `[{}]` would also technically work, this + approach is not type-safe. Internally, `Source(__dummy=[0])` is used and + then cleaned up to preserve correctness and consistency. + + + Parameters + ---------- + __source: Source | None, optional + The base source to be expanded. If None, a default single-item + source is used, allowing `Product` to act on keyword arguments alone. + + **kwargs: list[Any] + Named sequences to take the product with. Each field will be + broadcasted across all items in the base source. + Examples -------- >>> from deeptrack.sources import Source @@ -1160,21 +1182,27 @@ class Product(Source): >>> >>> product = Product(source, b=[10, 20]) + Using Product without a base source: + >>> product = Product(x=[1, 2], y=["a", "b"]) + >>> product + Product(x=[1, 1, 2, 2], y=['a', 'b', 'a', 'b']) + """ def __init__( self: Product, - __source: Source, + __source: Source | None = None, **kwargs: list[Any], ): """Initialize the Cartesian product of a source with additional fields. Parameters ---------- - __source : Source + __source: Source | None The base source to be expanded via Cartesian product. + It defaults to None. - **kwargs : list[Any] + **kwargs: list[Any] Named sequences to take the product with. Each value must be a list or array of equal length. @@ -1185,6 +1213,10 @@ def __init__( """ + # This might be fragile and could be changed to a dummy source + if __source == None: + __source = [{}] + # Compute the cartesian product of all items product = itertools.product(__source, *kwargs.values()) From cbaa5fc851dfce262df45745153e55a73362d04d Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 21:04:12 +0100 Subject: [PATCH 177/223] Update base.py --- deeptrack/sources/base.py | 198 +++++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 1 deletion(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 1745b00d5..4adb794af 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -1244,38 +1244,234 @@ def __init__( class Subset(Source): + """A filtered view of a Source defined by a list of indices. + + `Subset` represents a restricted view of a parent `Source`, exposing + only the items corresponding to the provided list of indices. It + supports indexing, iteration, and can be passed to DeepTrack features. + + The subset preserves all attributes and dynamic behavior of the + original source, but limits iteration and indexing to the selected + indices. + + Parameters + ---------- + source: Source + The original source to take a subset from. + indices: list[int] + A list of indices specifying which items to include. + + Attributes + ---------- + source: Source + The original full source that this subset is derived from. + indices: list[int] + The list of selected indices within the original source. + _dict: dict[str, Sequence[Any]] + Dictionary of sliced field values for compatibility with the + `Source` interface and `__repr__()`. + + Methods + ------- + __iter__() -> Generator[SourceItem, None, None] + It iterates over the items at the specified indices. + __getitem__(index: int) -> SourceItem + It retrieves the item at a given position in the subset. + __len__() -> int + It returns the number of items in the subset. + __getattr__(name: str) -> Any + It delegates attribute access to the parent source. + + Examples + -------- + >>> from deeptrack.sources import Source, Subset + + Create a source: + >>> source = Source(a=[1, 2, 3], b=[10, 20, 30]) + + Extract a subset: + >>> subset = Subset(source, [0, 2]) + >>> subset + Subset(a=[1, 3], b=[10, 30]) + + >>> list[subset] + [SourceItem({'a': 1, 'b': 10}, 1 callback(s)), + SourceItem({'a': 3, 'b': 30}, 1 callback(s))] + + """ + + source: Source + indices: list[int] + _dict: dict[str, Sequence[Any]] def __init__( self: Subset, source: Source, indices: list[int], ): + """Initialize a Subset from a source and a list of indices. + + This constructor extracts a subset of items from the given source + by selecting only the entries corresponding to the provided indices. + + The underlying source is preserved in `self.source`, while `self._dict` + holds the sliced values for compatibility with `Source` methods + such as `__repr__`. The subset supports all dynamic attribute access + via delegation to the original source. + + Parameters + ---------- + source: Source + The original source from which to extract the subset. + indices: list[int] + The indices of the items to include in the subset. + + """ + self.source = source self.indices = indices + + # Build the field dictionary for the subset by slicing each field self._dict = {k: [v[i] for i in indices] for k, v in source._dict.items()} def __iter__( self: Subset, ) -> Generator[SourceItem, None, None]: + """Iterate over the items in the subset. + + This method yields each `SourceItem` corresponding to the indices + stored in the subset. Items are retrieved from the original source. + + Yields + ------ + SourceItem + An item from the original source at one of the selected indices. + + Examples + -------- + >>> from deeptrack.sources import Source, Subset + + Create a source: + >>> source = Source(a=[1, 2, 3], b=[10, 20, 30]) + + Extract a subset: + >>> subset = Subset(source, [0, 2]) + + Iterate ove the items of the subset: + >>> for item in subset: + ... print(item["a"], item["b"]) + 1 10 + 3 30 + + """ + for i in self.indices: yield self.source[i] def __getitem__( self: Subset, - index: int + index: int, ) -> SourceItem: + """Retrieve a SourceItem at a given position in the subset. + + This method returns the item at the specified position in the + subset, mapped to its corresponding index in the original source. + + Parameters + ---------- + index: int + The position within the subset (not the original source). + + Returns + ------- + SourceItem + The item corresponding to `self.indices[index]` in the original + source. + + Examples + -------- + >>> from deeptrack.sources import Source, Subset + + Create a source: + >>> source = Source(a=[1, 2, 3], b=[10, 20, 30]) + + Extract a subset: + >>> subset = Subset(source, [0, 2]) + + >>> item = subset[1] + >>> item["a"], item["b"] + (3, 30) + + """ + return self.source[self.indices[index]] def __len__( self: Subset, ) -> int: + """Return the number of items in the subset. + + This corresponds to the number of selected indices from the + original source. + + Returns + ------- + int + The number of items in the subset. + + Examples + -------- + >>> from deeptrack.sources import Source, Subset + + Create a source: + >>> source = Source(a=[1, 2, 3]) + + Extract a subset: + >>> subset = Subset(source, [0, 2]) + + Get the length of the subset: + >>> len(subset) + 2 + + """ + return len(self.indices) def __getattr__( self: Subset, name: str, ) -> Any: + """Delegate attribute access to the original source. + + This allows the subset to transparently expose dynamic attributes + from the original source, such as fields like `source.a` or methods + defined on the source class. + + Parameters + ---------- + name: str + The name of the attribute to access. + + Returns + ------- + Any + The corresponding attribute from the original source. + + Examples + -------- + >>> from deeptrack.sources import Source, Subset + + Create a source: + >>> source = Source(a=[1, 2, 3]) + + Extract a subset: + >>> subset = Subset(source, [0, 2]) + >>> subset.a() + 1 + + """ + return getattr(self.source, name) From 2afc37dc05e660115dbf203c6e721438ff4925c1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 21:35:28 +0100 Subject: [PATCH 178/223] Update base.py --- deeptrack/sources/base.py | 100 ++++++++++++++++++++++++++++++++++---- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index 4adb794af..f3e094ca3 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -1476,32 +1476,100 @@ def __getattr__( class Sources: - """Joins multiple sources into a single access point. + """Joins multiple sources into a single dynamic access point. - Used when one of multiple sources can be passed to a feature. - For example the sources are split into training and validation sets, - and the user can choose which one to use. + `Sources` is used to combine multiple `Source` objects into one logical + interface. It enables multiple independent sources to share the same + features dynamically. This is particularly useful in cases like training/ + validation/test splits, where features are defined once and evaluated + on different datasets. + + When any item from one of the joined sources is activated (i.e., called), + the corresponding fields in the `Sources` object are updated and + propagated through the computational graph via `SourceDeepTrackNode`. + + Aliased as `Join` for semantic clarity in different contexts. Parameters ---------- - sources: Source - - The sources to join. - + *sources: Source + One or more `Source` instances to join. Each source must have + compatible field names (e.g., all sources used with a common feature + must define that feature’s required fields). + + Attributes + ---------- + sources: tuple[Source, ...] + The tuple of joined source instances. + + _dict: dict[str, Any] + Dictionary used internally to store the currently active values + for each field. + + : SourceDeepTrackNode + Each field key becomes a `SourceDeepTrackNode` that reflects the + currently activated item from any of the joined sources. + + Methods + ------- + _callback(item: SourceItem) -> None + Internal method triggered on activation. Updates dynamic fields + with the activated item values. + + Examples + -------- + >>> import deeptrack as dt + >>> from deeptrack.sources import Source, Sources + + Create two disjoint sources: + >>> train = Source(a=[1, 2], b=[10, 20]) + >>> val = Source(a=[3, 4], b=[30, 40]) + + Join them together: + >>> joined = Sources(train, val) + + Create a shared feature: + >>> feature = dt.Value(joined.a) + dt.Value(joined.b) + + Evaluate on items from different sources: + >>> feature(train[0]) + 11 + >>> feature(train[0]) + 22 + >>> feature(val[1]) + 33 + >>> feature(val[1]) + 44 + """ + sources: tuple[Source, ...] + _dict: dict[str, Any] + def __init__( self: Sources, *sources: Source, ): + """Initialize a joined multi-source access point. + + Parameters + ---------- + *sources : Source + One or more `Source` instances to join. + + """ + self.sources = sources + # Determine all unique keys across all sources keys = set() for source in sources: keys.update(source._dict.keys()) + # Initialize internal storage self._dict = dict.fromkeys(keys) + # Create dynamic nodes for each key for key in keys: node = SourceDeepTrackNode( functools.partial(lambda key: self._dict[key], key) @@ -1509,6 +1577,7 @@ def __init__( setattr(self, key, node) + # Register callback for each source for source in sources: source.on_activate(self._callback) @@ -1516,6 +1585,20 @@ def _callback( self: Sources, item: SourceItem, ) -> None: + """Update dictionary and nodes with values from activated item. + + This method is called when an item is activated from any of the joined + sources. It updates the internal `_dict` with the field values from the + item and sets the corresponding `SourceDeepTrackNode` values to reflect + the active state. + + Parameters + ---------- + item : SourceItem + The activated item whose values will update the joined source nodes. + + """ + for key in item: getattr(self, key).invalidate() getattr(self, key).set_value(item[key]) @@ -1545,7 +1628,6 @@ def random_split( """ - if math.isclose(sum(lengths), 1) and sum(lengths) <= 1: subset_lengths = [] for i, frac in enumerate(lengths): From cb892da1e24931b7fc46937c94b4d8df2b31661c Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 21:49:33 +0100 Subject: [PATCH 179/223] Update base.py --- deeptrack/sources/base.py | 128 +++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 22 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index f3e094ca3..f22d8adb9 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -1610,22 +1610,71 @@ def _callback( def random_split( source: Source, lengths: list[int | float], - generator: np.random.Generator = np.random.default_rng() + generator: np.random.Generator = np.random.default_rng(), ) -> list[Subset]: - """Randomly split source into non-overlapping new sources of given lengths. + """Randomly split a source into non-overlapping subsets of specified sizes. + + This function splits a `Source` into multiple disjoint `Subset`s either + by specifying absolute lengths (integers) or relative proportions (floats). + + If all entries in `lengths` are floats that sum to 1 or less, they are + interpreted as fractions and scaled to match the total size of the source. + Remaining items (due to rounding) are distributed round-robin to ensure + full coverage. Parameters ---------- - source: Source - The source to split. - - lengths: list of int or float - The lengths of the new sources. If the lengths are floats, - they are interpreted as fractions of the source. - - generator: numpy.random.Generator, optional - The random number generator to use. - + source : Source + The input `Source` to split. + lengths : list[int or float] + A list of lengths for the resulting splits. If all values are floats + summing to 1 (or slightly less), they are treated as proportions. + generator : np.random.Generator, optional + A NumPy random generator used for shuffling. Defaults to + `np.random.default_rng()`. + + Returns + ------- + list[Subset] + A list of `Subset` instances corresponding to the split parts. + + Raises + ------ + ValueError + If the sum of provided lengths does not match the length of the source. + + Examples + -------- + >>> from deeptrack.sources import Source, random_split + + Create a source: + >>> source = Source( + ... a=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + ... b=[10, 11, 12, 13, 14, 15, 16, 17, 18, 19], + ... ) + + Split into train (70%) and validation (30%): + >>> train, val, test = random_split(source, [0.4, 0.3, 0.3]) + >>> train + Subset(a=[3, 2, 7, 9], b=[13, 12, 17, 19]) + + >>> val + Subset(a=[5, 6, 1], b=[15, 16, 11]) + + >>> test + Subset(a=[0, 8, 4], b=[10, 18, 14]) + + Split into fixed sizes: + >>> train, val, test = random_split(source, [4, 3, 3]) + >>> train + Subset(a=[3, 2, 7, 9], b=[13, 12, 17, 19]) + + >>> val + Subset(a=[5, 6, 1], b=[15, 16, 11]) + + >>> test + Subset(a=[0, 8, 4], b=[10, 18, 14]) + """ if math.isclose(sum(lengths), 1) and sum(lengths) <= 1: @@ -1656,7 +1705,7 @@ def random_split( "This might result in an empty source." ) - # Cannot verify that dataset is Sized. + # Cannot verify that dataset is Sized. if sum(lengths) != len(source): # type: ignore[arg-type] raise ValueError("Sum of input lengths does not\ equal the length of the input dataset!") @@ -1671,25 +1720,60 @@ def _accumulate( iterable: list[int], fn: Callable [[int, int], int]=lambda x, y: x + y, ) -> Generator[int, None, None]: - """Returns running totals with user specified operator. - - Default is summation. - + """Return running totals using a binary accumulation function. + + This utility function computes cumulative values from a list using a + user-defined binary operator. By default, it performs cumulative summation + (i.e., partial sums), similar to `itertools.accumulate()`. + + Parameters + ---------- + iterable : list[int] + A list of integers to be accumulated. + fn : Callable[[int, int], int], optional + A binary function that takes two integers and returns a new integer. + It defaults to addition. + + Yields + ------ + int + The cumulative value at each step of the accumulation. + Examples -------- - >>> _accumulate([1,2,3,4,5]) - 1 3 6 10 15 - - >>> _accumulate([1,2,3,4,5], operator.mul) - 1 2 6 24 120 + >>> from deeptrack.sources.base import _accumulate + + Default behavior (cumulative sum): + >>> for value in _accumulate([1, 2, 3, 4, 5]): + ... print(value) + 1 + 3 + 6 + 10 + 15 + + Using a custom operator (e.g., multiplication): + >>> import operator + >>> + >>> for value in _accumulate([1, 2, 3, 4, 5], fn=operator.mul): + ... print(value) + 1 + 2 + 6 + 24 + 120 """ + it = iter(iterable) + try: total = next(it) except StopIteration: return + yield total + for element in it: total = fn(total, element) yield total From 28488f6faa2932cefefdaac9f7e41166c10c6fd7 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Wed, 9 Jul 2025 22:06:21 +0100 Subject: [PATCH 180/223] Update base.py --- deeptrack/sources/base.py | 165 +++++++++++++++++++++++++------------- 1 file changed, 108 insertions(+), 57 deletions(-) diff --git a/deeptrack/sources/base.py b/deeptrack/sources/base.py index f22d8adb9..f7d509adb 100644 --- a/deeptrack/sources/base.py +++ b/deeptrack/sources/base.py @@ -1,7 +1,9 @@ -"""Utility classes for data sources. +"""Utility classes and functions for dynamic data sources in DeepTrack. -This module provides a set of utility classes designed for managing and -manipulating data sources. +This module provides core abstractions for representing and manipulating +collections of data in a modular and composable way. It defines the structure +and behavior of dynamic sources that can be indexed, filtered, combined, +and tracked through DeepTrack's computational graph. These tools are primarily used in scenarios where data needs to be dynamically manipulated, filtered, or combined for feature generation in machine learning @@ -9,44 +11,60 @@ Key Features ------------ -- **Node Hierarchy** - - `SourceDeepTrackNode` extends `DeepTrackNode` with utilities to create - nested nodes, and structured data access. - - **Dynamic Data Access** - It retrieves data items as callable objects, supporting custom callbacks and dependency tracking. + Sources return dictionary-like items (`SourceItem`) that activate + custom callbacks when accessed, enabling dynamic behavior such as + dependency tracking and delayed evaluation. -- **Randomized Splitting** +- **Composable Data Structures** - Enables splitting of data sources into non-overlapping - subsets with user-specified length. - + Includes tools like `Product`, `Subset`, and `Sources` to manipulate + and combine data sources for flexible pipeline construction. + +- **Hierarchical Node System** + + The `SourceDeepTrackNode` class extends `DeepTrackNode` to support + hierarchical field access and automatic dependency propagation. + +- **Random Splitting Utilities** + + Provides utilities such as `random_split()` for reproducible partitioning + of sources into disjoint subsets for training/validation/test workflows. Module Structure ---------------- Classes: -- `SourceDeepTrackNode`: Creates child nodes when accessing attributes. +- `Source`: Represents one or more named sequences of data. + + Provides access to items as `SourceItem` and integrates with features + for graph-based computation. + +- `SourceItem`: A dict-like object that triggers callbacks when called. + + Wraps data fields from a `Source` and activates dependency updates + on use. + +- `SourceDeepTrackNode`: A DeepTrack node that supports attribute access. + + Automatically creates child nodes when dictionary-like attributes + are accessed (e.g., `source.a.b`). -- `SourceItem`: Dict-like object that calls a list of callbacks when called. +- `Product`: Cartesian product of a `Source` with additional fields. -- `Source`: Represents one or more sources of data. + Allows combining items with new fields, either with or without a base + source. -- `Join`: Alias of `Source`. +- `Subset`: Represents a filtered view of a `Source` via explicit indices. -- `Product`: Represents the product of the source with the given sources. + Provides indexed access to a restricted set of items. - This class is used to represent the product of a source with - one or more sources. When accessed, it returns a deeptrack object that - can be passed as properties to features. - -- `Subset`: Represents the subset of a `Source`. +- `Sources`: Joins multiple `Source` objects into one dynamic access point. -- `Sources`: Represents multiple sources as a single access point. + Enables field sharing and flexible evaluation across datasets. - Used when one of multiple sources can be passed to a feature. +- `Join`: Alias for `Sources`. Functions: @@ -54,52 +72,85 @@ def random_split( source: Source, - lengths: list[int or float], - generator: np.random.Generator = np.random.default_rng() - ) -> list[Subset]: - Randomly split source into non-overlapping new sources of given lengths. + lengths: list[int | float], + generator: np.random.Generator = np.random.default_rng(), + ) -> list[Subset] + + Randomly splits a `Source` into multiple non-overlapping subsets. Examples -------- -Call a list of callbacks: +import deeptrack as dt ->>> from deeptrack.sources import Source +**Trigger callbacks when a source item is accessed** +>>> from deeptrack.sources import Source +>>> >>> source = Source(a=[1, 2], b=[3, 4]) +>>> >>> @source.on_activate ->>> def callback(item): ->>> print(item) ->>> source[0]() +... def callback(item): +... print("Activated:", item) -Equivalent to: +>>> source[0](); +Activated: SourceItem({'a': 1, 'b': 3}, 2 callback(s)) ->>> SourceItem({'a': 1, 'b': 3}). +**Access nested dictionary-like data with dynamic nodes** -Create a node that creates child nodes when attributes are accessed: - ->>> from deeptrack.sources import SourceDeepTrackNode - ->>> node = SourceDeepTrackNode(lambda: {"a": 1, "b": 2}) ->>> child = node.a ->>> child() +>>> from deeptrack.sources.base import SourceDeepTrackNode +>>> +>>> node = SourceDeepTrackNode(lambda: {"a": 1, "b": {"x": 42}}) +>>> node.a() 1 +>>> node.b.x() +42 + +**Use shared features across multiple sources** + +>>> from deeptrack.sources import Source, Sources +>>> +>>> train = Source(a=[1, 2], b=[3, 4]) +>>> val = Source(a=[5, 6], b=[7, 8]) +>>> joined = Sources(train, val) +>>> feature = dt.Value(joined.a) + dt.Value(joined.b) +>>> feature(train[0]) +4 +>>> feature(val[0]) +12 -Join multiple sources into a single access point: +**Create a Cartesian product of fields** ->>> import deeptrack as dt >>> from deeptrack.sources import Source - ->>> source1 = Source(a=[1, 2], b=[3, 4]) ->>> source2 = Source(a=[5, 6], b=[7, 8]) ->>> joined_source = Sources(source1, source2) ->>> feature_a = dt.Value(joined_source.a) ->>> feature_b = dt.Value(joined_source.b) ->>> sum_feature = feature_a + feature_b - ->>> sum_feature(source1[0]) -4 ->>> sum_feature(source2[0]) -12 +>>> +>>> source = Source(a=[1, 2]) +>>> product = source.product(b=[10, 20]) +>>> list(product) +[SourceItem({'b': 10, 'a': 1}, 1 callback(s)), + SourceItem({'b': 20, 'a': 1}, 1 callback(s)), + SourceItem({'b': 10, 'a': 2}, 1 callback(s)), + SourceItem({'b': 20, 'a': 2}, 1 callback(s))] + +**Extract a subset of selected indices** + +>>> from deeptrack.sources import Source, Subset +>>> +>>> source = Source(a=[1, 2, 3], b=[10, 20, 30]) +>>> subset = Subset(source, [0, 2]) +>>> list(subset) +[SourceItem({'a': 1, 'b': 10}, 1 callback(s)), + SourceItem({'a': 3, 'b': 30}, 1 callback(s))] + +**Split a source randomly into multiple parts** + +>>> from deeptrack.sources import random_split, Source +>>> +>>> source = Source( +... a=list(range(10)), +... b=list(range(10, 20)), +... ) +>>> train, val, test = random_split(source, [0.5, 0.3, 0.2]) +>>> len(train), len(val), len(test) +(5, 3, 2) """ From 6c9dcccfb1b8c578f2e1f0406e40d6ea808540db Mon Sep 17 00:00:00 2001 From: Alex <95913221+Pwhsky@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:33:00 +0200 Subject: [PATCH 181/223] Features: ChannelFirst2d (#361) * documented channel2d func * unit tests: channelfirst2d * type hints * Update features.py * NDArray type hint * included Image type hint and xp usage * unit test checks for torch_available * Update features.py * u * docs * linebreak * docs * Update test_features.py * Update test_features.py * deprecation warning & tests * Added support for Image objects * Tests for image objects * docs * tests and axis compatibility * fix for repeated calls * revert changes to upscale * Implemented feedback --------- Co-authored-by: Giovanni Volpe --- deeptrack/features.py | 103 ++++++++++++++++++++----------- deeptrack/tests/test_features.py | 55 +++++++++++++---- 2 files changed, 111 insertions(+), 47 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 25984b505..ad2bfffc7 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -216,13 +216,14 @@ def propagate_data_to_dependencies( "Merge", "OneOf", "OneOfDict", - "LoadImage", #TODO ***MG*** - "SampleToMasks", #TODO ***MG*** - "AsType", #TODO ***MG*** - "Upscale", #TODO ***AL*** - "ChannelFirst2d", #TODO ***AL*** - "NonOverlapping", #TODO ***AL*** - "Store", #TODO ***JH*** + "LoadImage", # TODO **MG** + "SampleToMasks", # TODO **MG** + "AsType", # TODO **MG** + "ChannelFirst2d", + "Upscale", # TODO **AL** + "NonOverlapping", # TODO **AL** + "Store", # TODO **JH** + "Squeeze", "Unsqueeze", "ExpandDims", @@ -7268,24 +7269,29 @@ def get( return image.astype(dtype) -class ChannelFirst2d(Feature): # DEPRECATED +class ChannelFirst2d(Feature): """Convert an image to a channel-first format. - This feature rearranges the axes of a 3D image so that the specified axis - (e.g., channel axis) is moved to the first position. If the input image is - 2D, it adds a new dimension at the front, effectively treating the 2D + This feature rearranges the axes of a 3D image so that the specified axis + (e.g., channel axis) is moved to the first position. If the input image is + 2D, it adds a new dimension at the first index, effectively treating the 2D image as a single-channel image. Parameters ---------- axis: int, optional - The axis to move to the first position. It defaults to `-1` (last axis). - **kwargs:: dict of str to Any + The axis to move to the first position. It defaults to `-1` + (last axis), which is typically the channel axis for NumPy arrays. + **kwargs: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get(image: np.ndarray, axis: int, **kwargs: dict[str, Any]) -> np.ndarray` + `get( + image: NDArray | torch.Tensor | Image, + axis: int, + **kwargs: Any + ) -> NDArray | torch.Tensor | Image` Rearrange the axes of an image to channel-first format. Examples @@ -7318,46 +7324,55 @@ class ChannelFirst2d(Feature): # DEPRECATED def __init__( self: Feature, - axis: int = -1, + axis: PropertyLike[int] = -1, **kwargs: Any, - ): + ) -> None: """Initialize the ChannelFirst2d feature. Parameters ---------- axis: int, optional - The axis to move to the first position. - It defaults to `-1` (last axis). - **kwargs:: dict of str to Any + The axis to move to the first position, + defaults to `-1` (last axis). + **kwargs: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. """ + import warnings + + warnings.warn( + "ChannelFirst2d is deprecated and may be removed in a " + "future release. The current implementation is not guaranteed " + "to be exactly equivalent to prior implementations. ", + DeprecationWarning, + ) super().__init__(axis=axis, **kwargs) def get( self: Feature, - image: np.ndarray, - axis: int, + image: NDArray | torch.Tensor | Image, + axis: int = -1, **kwargs: Any, - ) -> np.ndarray: + ) -> NDArray | torch.Tensor | Image: """Rearrange the axes of an image to channel-first format. - Rearrange the axes of a 3D image to channel-first format or add a + Rearrange the axes of a 3D image to channel-first format or add a channel dimension to a 2D image. Parameters ---------- - image: np.ndarray + image: NDArray | torch.Tensor | Image The input image to process. Can be 2D or 3D. axis: int The axis to move to the first position (for 3D images). + For 2D images, this argument does nothing. **kwargs: Any Additional keyword arguments (unused here). Returns ------- - np.ndarray + NDArray | torch.Tensor | Image The processed image in channel-first format. Raises @@ -7366,20 +7381,36 @@ def get( If the input image is neither 2D nor 3D. """ + # Pre-processing logic to check for Image objects. + is_image = isinstance(image, Image) + array = image._value if is_image else image - ndim = image.ndim + # Raise error if not 2D or 3D. + ndim = array.ndim + if ndim not in (2, 3): + raise ValueError("ChannelFirst2d only supports 2D or 3D images. " + f"Received {ndim}D image.") # Add a new dimension for 2D images. if ndim == 2: - return image[None] - - # Move the specified axis to the first position for 3D images. - if ndim == 3: - return np.moveaxis(image, axis, 0) + if apc.is_torch_array(array): + array = array.unsqueeze(0) + else: + array[None] + + # Move axis for 3D images. + else: + if apc.is_torch_array(array): + axis = ndim + axis if axis < 0 else axis + dims = [axis] + [i for i in range(ndim) if i != axis] + array = array.permute(*dims) + else: + array = xp.moveaxis(array, axis, 0) - raise ValueError("ChannelFirst2d only supports 2D or 3D images. " - f"Received {ndim}D image.") + if is_image: + return Image(array) + return array class Upscale(Feature): """Simulate a pipeline at a higher resolution. @@ -7413,7 +7444,7 @@ class Upscale(Feature): Methods ------- - `get(image: np.ndarray | Image, factor: int | tuple[int, int, int], **kwargs) -> np.ndarray` + `get(image: np.ndarray | Image, factor: int | tuple[int, int, int], **kwargs) -> np.ndarray | torch.tensor` Simulates the pipeline at a higher resolution and returns the result at the original resolution. @@ -7468,7 +7499,7 @@ def __init__( feature: Feature, factor: int | tuple[int, int, int] = 1, **kwargs: Any, - ): + ) -> None: """Initialize the Upscale feature. Parameters @@ -7493,7 +7524,7 @@ def get( image: np.ndarray, factor: int | tuple[int, int, int], **kwargs: Any, - ) -> np.ndarray: + ) -> np.ndarray | torch.tensor: """Simulate the pipeline at a higher resolution and return result. Parameters diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 5a5469e28..39d4f040d 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1956,17 +1956,50 @@ def test_AsType(self): #TODO - def test_ChannelFirst2d(self): - - channel_first_feature = features.ChannelFirst2d() - - input_image_2d = np.random.rand(10, 20) - output_image = channel_first_feature.get(input_image_2d, axis=-1) - self.assertEqual(output_image.shape, (1, 10, 20)) - - input_image_3d = np.random.rand(10, 20, 3) - output_image = channel_first_feature.get(input_image_3d, axis=-1) - self.assertEqual(output_image.shape, (3, 10, 20)) +def test_ChannelFirst2d(self): + + channel_first_feature = features.ChannelFirst2d() + + # Numpy shapes + input_image = np.zeros((10, 20)) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(output_image.shape, (1, 10, 20)) + + input_image = np.zeros((10, 20, 3)) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(output_image.shape, (3, 10, 20)) + + # Image[Numpy] shape + input_image = Image(np.zeros((10, 20, 3))) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(output_image._value.shape, (3, 10, 20)) + + # Numpy values + input_image = np.array([[[1, 2, 3], [4, 5, 6]]]) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(output_image.shape, (3, 1, 2)) + np.testing.assert_array_equal(output_image, np.moveaxis(input_image, -1, 0)) + + if TORCH_AVAILABLE: + # Torch shapes + input_image = torch.zeros(10, 20) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(tuple(output_image.shape), (1, 10, 20)) + + input_image = torch.zeros(10, 20, 3) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(tuple(output_image.shape), (3, 10, 20)) + + # Image[Torch] shape + input_image = Image(torch.zeros(10, 20, 3)) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(tuple(output_image.shape), (3, 10, 20)) + + # Torch values + input_image = torch.tensor([[[1, 2, 3], [4, 5, 6]]]) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(output_image.shape, (3, 1, 2)) + self.assertTrue(torch.equal(output_image, input_image.permute(2, 0, 1))) def test_Upscale(self): From 1c60d4ea7bef19625d8ae171d36420d3fe26bb55 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 09:35:31 +0100 Subject: [PATCH 182/223] Update features.py --- deeptrack/features.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index ad2bfffc7..f5fec7e03 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -7269,7 +7269,7 @@ def get( return image.astype(dtype) -class ChannelFirst2d(Feature): +class ChannelFirst2d(Feature): # DEPRECATED """Convert an image to a channel-first format. This feature rearranges the axes of a 3D image so that the specified axis @@ -7282,17 +7282,13 @@ class ChannelFirst2d(Feature): axis: int, optional The axis to move to the first position. It defaults to `-1` (last axis), which is typically the channel axis for NumPy arrays. - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. Methods ------- - `get( - image: NDArray | torch.Tensor | Image, - axis: int, - **kwargs: Any - ) -> NDArray | torch.Tensor | Image` - Rearrange the axes of an image to channel-first format. + `get(image: array, axis: int, **kwargs: Any) -> array` + It rearranges the axes of an image to channel-first format. Examples -------- @@ -7334,16 +7330,17 @@ def __init__( axis: int, optional The axis to move to the first position, defaults to `-1` (last axis). - **kwargs: dict of str to Any + **kwargs: Any Additional keyword arguments passed to the parent `Feature` class. """ + import warnings warnings.warn( "ChannelFirst2d is deprecated and may be removed in a " "future release. The current implementation is not guaranteed " - "to be exactly equivalent to prior implementations. ", + "to be exactly equivalent to prior implementations.", DeprecationWarning, ) @@ -7362,7 +7359,7 @@ def get( Parameters ---------- - image: NDArray | torch.Tensor | Image + image: array The input image to process. Can be 2D or 3D. axis: int The axis to move to the first position (for 3D images). @@ -7372,7 +7369,7 @@ def get( Returns ------- - NDArray | torch.Tensor | Image + array The processed image in channel-first format. Raises @@ -7381,6 +7378,7 @@ def get( If the input image is neither 2D nor 3D. """ + # Pre-processing logic to check for Image objects. is_image = isinstance(image, Image) array = image._value if is_image else image @@ -7394,10 +7392,10 @@ def get( # Add a new dimension for 2D images. if ndim == 2: if apc.is_torch_array(array): - array = array.unsqueeze(0) + array = array.unsqueeze(0) else: array[None] - + # Move axis for 3D images. else: if apc.is_torch_array(array): @@ -7407,7 +7405,7 @@ def get( else: array = xp.moveaxis(array, axis, 0) - if is_image: + if is_image: return Image(array) return array From 612e125dfb7dde6fbe6b200e808462be8862a7ba Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 09:35:33 +0100 Subject: [PATCH 183/223] Create __init__.py --- deeptrack/tests/sources/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 deeptrack/tests/sources/__init__.py diff --git a/deeptrack/tests/sources/__init__.py b/deeptrack/tests/sources/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/deeptrack/tests/sources/__init__.py @@ -0,0 +1 @@ + From 0e37b0fd45337d6e5dabca7f6a08d6484bd77d39 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 09:35:35 +0100 Subject: [PATCH 184/223] Create test_base.py --- deeptrack/tests/sources/test_base.py | 140 +++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 deeptrack/tests/sources/test_base.py diff --git a/deeptrack/tests/sources/test_base.py b/deeptrack/tests/sources/test_base.py new file mode 100644 index 000000000..e40392d7b --- /dev/null +++ b/deeptrack/tests/sources/test_base.py @@ -0,0 +1,140 @@ +# 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 numpy as np + +from deeptrack.sources import base +from deeptrack import TORCH_AVAILABLE + +if TORCH_AVAILABLE: + import torch + + +class TestBase(unittest.TestCase): + + def test___all__(self): + from deeptrack.sources import ( + Source, + SourceItem, + Product, + Subset, + Sources, + Join, + random_split, + ) + + def test_SourceItem(self): + + called = [] + + def callback(item): + called.append(item) + + # List input + item_list = base.SourceItem(callbacks=[callback], a=[1, 2], b=[3, 4]) + self.assertEqual(item_list["a"], [1, 2]) + self.assertEqual(item_list["b"], [3, 4]) + returned = item_list() + self.assertIn(item_list, called) + self.assertIs(returned, item_list) + self.assertIn("callback", repr(item_list)) + + # Tuple input + called.clear() + item_tuple = base.SourceItem(callbacks=[callback], a=(1, 2), b=(3, 4)) + self.assertEqual(item_tuple["a"], (1, 2)) + self.assertEqual(item_tuple["b"], (3, 4)) + returned = item_tuple() + self.assertIn(item_tuple, called) + + # NumPy array input + called.clear() + a_np = np.array([1, 2]) + b_np = np.array([3, 4]) + item_np = base.SourceItem(callbacks=[callback], a=a_np, b=b_np) + np.testing.assert_array_equal(item_np["a"], a_np) + np.testing.assert_array_equal(item_np["b"], b_np) + returned = item_np() + self.assertIn(item_np, called) + + if TORCH_AVAILABLE: + called.clear() + a_torch = torch.tensor([1, 2]) + b_torch = torch.tensor([3, 4]) + item_torch = base.SourceItem( + callbacks=[callback], a=a_torch, b=b_torch, + ) + self.assertTrue(torch.equal(item_torch["a"], a_torch)) + self.assertTrue(torch.equal(item_torch["b"], b_torch)) + returned = item_torch() + self.assertIn(item_torch, called) + + def test_Source(self): + # Prepare test data + data_variants = { + "list": ([1, 2, 3], [10, 20, 30]), + "tuple": ((1, 2, 3), (10, 20, 30)), + "numpy": (np.array([1, 2, 3]), np.array([10, 20, 30])), + } + + if TORCH_AVAILABLE: + import torch + data_variants["torch"] = ( + torch.tensor([1, 2, 3]), + torch.tensor([10, 20, 30]), + ) + + for name, (a, b) in data_variants.items(): + with self.subTest(dtype=name): + source = base.Source(a=a, b=b) + + # Test length + self.assertEqual(len(source), 3) + + # Test indexing + item = source[1] + self.assertEqual(item["a"], a[1]) + self.assertEqual(item["b"], b[1]) + + # Test iteration + items = list(source) + self.assertEqual(len(items), 3) + self.assertEqual(items[2]["a"], a[2]) + self.assertEqual(items[2]["b"], b[2]) + + # Test slice + sliced = source[1:3] + self.assertEqual(len(sliced), 2) + self.assertEqual(sliced[0]["a"], a[1]) + self.assertEqual(sliced[1]["b"], b[2]) + + # Test dynamic field + source.set_index(0) + self.assertEqual(source.a(), a[0]) + self.assertEqual(source.b(), b[0]) + + def test_Product(self): + pass + + def test_Subset(self): + pass + + def test_Sources(self): + pass + + def test_Join(self): + pass + + def test_random_split(self): + pass + + +if __name__ == "__main__": + unittest.main() From e93530594309b83c018d5f72bbde19b5843920f4 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 09:43:59 +0100 Subject: [PATCH 185/223] Update test_base.py --- deeptrack/tests/sources/test_base.py | 174 +++++++++++++++++++++++++-- 1 file changed, 167 insertions(+), 7 deletions(-) diff --git a/deeptrack/tests/sources/test_base.py b/deeptrack/tests/sources/test_base.py index e40392d7b..b667add59 100644 --- a/deeptrack/tests/sources/test_base.py +++ b/deeptrack/tests/sources/test_base.py @@ -10,8 +10,9 @@ import numpy as np -from deeptrack.sources import base +import deeptrack as dt from deeptrack import TORCH_AVAILABLE +from deeptrack.sources import base if TORCH_AVAILABLE: import torch @@ -121,19 +122,178 @@ def test_Source(self): self.assertEqual(source.b(), b[0]) def test_Product(self): - pass + data_variants = { + "list": ([1, 2], [10, 20]), + "tuple": ((1, 2), (10, 20)), + "numpy": (np.array([1, 2]), np.array([10, 20])), + } + + if TORCH_AVAILABLE: + import torch + data_variants["torch"] = ( + torch.tensor([1, 2]), + torch.tensor([10, 20]), + ) + + for name, (a, b) in data_variants.items(): + with self.subTest(dtype=name): + source = base.Source(a=a) + product = base.Product(source, b=b) + + # Check length: 2 source × 2 b = 4 + self.assertEqual(len(product), 4) + + # Check content consistency + expected_a = [a[0], a[0], a[1], a[1]] + expected_b = [b[0], b[1], b[0], b[1]] + + for i, item in enumerate(product): + self.assertEqual(item["a"], expected_a[i]) + self.assertEqual(item["b"], expected_b[i]) + self.assertIsInstance(item, base.SourceItem) + + # Test Product without source (i.e., only kwargs) + product = base.Product(x=[1, 2], y=[100, 200]) + self.assertEqual(len(product), 4) + expected_pairs = [(1, 100), (1, 200), (2, 100), (2, 200)] + for i, item in enumerate(product): + self.assertEqual(item["x"], expected_pairs[i][0]) + self.assertEqual(item["y"], expected_pairs[i][1]) + + # Test error on overlapping keys + source = base.Source(x=[1, 2]) + with self.assertRaises(ValueError): + base.Product(source, x=[10, 20]) def test_Subset(self): - pass + data_variants = { + "list": ([1, 2, 3], [10, 20, 30]), + "tuple": ((1, 2, 3), (10, 20, 30)), + "numpy": (np.array([1, 2, 3]), np.array([10, 20, 30])), + } + + if TORCH_AVAILABLE: + import torch + data_variants["torch"] = ( + torch.tensor([1, 2, 3]), + torch.tensor([10, 20, 30]), + ) + + for name, (a, b) in data_variants.items(): + with self.subTest(dtype=name): + source = base.Source(a=a, b=b) + indices = [0, 2] + subset = base.Subset(source, indices) + + # Length + self.assertEqual(len(subset), 2) + + # Items should match corresponding ones from original source + for i, idx in enumerate(indices): + item = subset[i] + self.assertEqual(item["a"], a[idx]) + self.assertEqual(item["b"], b[idx]) + + # Iteration should return correct items + for i, item in enumerate(subset): + self.assertEqual(item["a"], a[indices[i]]) + self.assertEqual(item["b"], b[indices[i]]) + + # Dynamic attribute access + if TORCH_AVAILABLE and isinstance(a, torch.Tensor): + self.assertEqual(subset.a().item(), a[0].item()) + else: + self.assertEqual(subset.a(), a[0]) def test_Sources(self): - pass + data_variants = { + "list": ([1, 2], [10, 20], [3, 4], [30, 40]), + "tuple": ((1, 2), (10, 20), (3, 4), (30, 40)), + "numpy": ( + np.array([1, 2]), np.array([10, 20]), + np.array([3, 4]), np.array([30, 40]) + ), + } + + if TORCH_AVAILABLE: + data_variants["torch"] = ( + torch.tensor([1, 2]), torch.tensor([10, 20]), + torch.tensor([3, 4]), torch.tensor([30, 40]) + ) + + for name, (a1, b1, a2, b2) in data_variants.items(): + with self.subTest(dtype=name): + train = base.Source(a=a1, b=b1) + val = base.Source(a=a2, b=b2) + + joined = base.Sources(train, val) + + # Verify dynamic fields exist and have callable values + self.assertTrue(callable(joined.a)) + self.assertTrue(callable(joined.b)) + + # Trigger update by activating an item + item_train = train[0] + item_val = val[1] - def test_Join(self): - pass + item_train() + self.assertEqual(joined.a(), a1[0]) + self.assertEqual(joined.b(), b1[0]) + + item_val() + self.assertEqual(joined.a(), a2[1]) + self.assertEqual(joined.b(), b2[1]) + + # Feature access + feature = dt.Value(joined.a) + dt.Value(joined.b) + self.assertEqual(feature(train[0]), a1[0] + b1[0]) + self.assertEqual(feature(val[1]), a2[1] + b2[1]) def test_random_split(self): - pass + data_variants = { + "list": ([1, 2, 3, 4, 5], [10, 20, 30, 40, 50]), + "tuple": ((1, 2, 3, 4, 5), (10, 20, 30, 40, 50)), + "numpy": ( + np.array([1, 2, 3, 4, 5]), + np.array([10, 20, 30, 40, 50]), + ), + } + + if TORCH_AVAILABLE: + data_variants["torch"] = ( + torch.tensor([1, 2, 3, 4, 5]), + torch.tensor([10, 20, 30, 40, 50]), + ) + + for dtype, (a, b) in data_variants.items(): + with self.subTest(dtype=dtype): + source = base.Source(a=a, b=b) + + # Test integer split + train, val = base.random_split(source, [3, 2]) + self.assertEqual(len(train), 3) + self.assertEqual(len(val), 2) + + train_indices = {item["a"] for item in train} + val_indices = {item["a"] for item in val} + self.assertTrue(train_indices.isdisjoint(val_indices)) + + combined = sorted(train_indices | val_indices) + expected = sorted(list(a)) + if TORCH_AVAILABLE and isinstance(a, torch.Tensor): + expected = expected # torch.Tensor already sorted and list-like + self.assertEqual(combined, expected) + + # Test fractional split + splits = base.random_split(source, [0.4, 0.6]) + self.assertEqual(sum(len(s) for s in splits), 5) + + # Ensure all indices are unique and complete + all_indices = set() + for subset in splits: + for item in subset: + all_indices.add(item["a"]) + self.assertEqual(len(all_indices), 5) if __name__ == "__main__": From 472d769cda3bb8195ca073a6e1b0c4d2d8097b9b Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 10:56:35 +0100 Subject: [PATCH 186/223] Update DTAT391A_sources.base.ipynb --- .../DTAT391A_sources.base.ipynb | 1077 ++++++++++++++--- 1 file changed, 940 insertions(+), 137 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb b/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb index 16bb14c77..acbf6acec 100644 --- a/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb +++ b/tutorials/3-advanced-topics/DTAT391A_sources.base.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# deeptrack.sources.base\n", + "# DTAT391A. deeptrack.sources.base\n", "\n", "\"Open" ] @@ -31,262 +31,970 @@ "source": [ "## 1. What is `base.py`?\n", "\n", - "The `base` module provides utilities for manipulating data sources, primarily when data needs to be dynamically manipulated, filtered, or combined. This guide explains how to use each component in the module with examples." + "The `base.py` module provides utilities for manipulating data sources, primarily when data needs to be dynamically manipulated, filtered, or combined. This guide explains how to use each component in the module with examples." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#TODO ***GV***\n", + "### The key roles of `base.py` are:\n", "\n", - "The key roles of `base.py` are:\n", + "- **Data Wrapping and Activation:**\n", + " `base.py` introduces the `Source` and `SourceItem` classes to represent structured data streams. Each `SourceItem` triggers a list of callbacks when accessed, enabling dynamic updates and evaluation within DeepTrack2 pipelines.\n", "\n", - "- **First Key Role:**\n", - " Description ...\n", + "- **Field Access via Nodes:**\n", + " The module defines `SourceDeepTrackNode`, a subclass of `DeepTrackNode`, which allows dynamic field access using attribute notation. This makes it possible to write expressions like `source.a()` that automatically track dependencies on the current index.\n", "\n", - "- **Second Key Role:**\n", - " Description ...\n", + "- **Source Manipulation Utilities:**\n", + " It provides tools such as `Product`, `Subset`, `Sources`, and `random_split()` to construct complex data sources by combining, filtering, and partitioning existing ones. These tools allow efficient data manipulation for training/validation/test workflows and augmentation pipelines." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Dynamically Generating Child Nodes with `SourceDeepTrackNode`\n", + "\n", + "The `SourceDeepTrackNode` class extends `DeepTrackNode` by enabling structured, dynamic access to dictionary-like data. When you access an attribute of a `SourceDeepTrackNode`, it automatically generates a child node corresponding to that key. This is particularly useful when you want to track dependencies between data fields and features (such as `source.position.x` or `source.a()`) in a dynamic way.\n", + "\n", + "This node-based approach supports:\n", + "- Automatic dependency tracking in computation graphs.\n", + "- Composable, lazy evaluation of nested or hierarchical data.\n", + "- Dynamic binding to the currently activated data item." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1. Accessing Top-Level Fields" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define parent node with `SourceDeepTrackNode`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sources.base import SourceDeepTrackNode\n", + "\n", + "node = SourceDeepTrackNode(lambda: {\"a\": 10, \"b\": 20})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Dynamically generate child nodes." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "child_a = node.a\n", + "child_b = node.b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Call the child nodes and evaluate them." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "30" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result = child_a() + child_b()\n", + "result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.2. Accessing Nested Fields" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a nested dictionary structure." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "node = SourceDeepTrackNode(lambda: {\"a\": 10, \"b\": {\"c\": 3, \"d\": 7}})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Access nested fields:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "child_c = node.b.c\n", + "child_c()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "7" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "child_d = node.b.d\n", + "child_d()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.3. Integrating with a DeepTrack2 Feature\n", + "\n", + "You can pass a `SourceDeepTrackNode` (or any field from a `Source`) to a DeepTrack2 feature such as `dt.Value`. When the data is activated (e.g., `source[i]()`), the feature dynamically evaluates the current value at that index." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a Source with two fields." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "import deeptrack as dt\n", + "from deeptrack.sources import Source\n", + "\n", + "source = Source(\n", + " a=[1, 2, 3],\n", + " b=[10, 20, 30],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a feature that sums fields `a` and `b`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "feature = dt.Value(source.a) + dt.Value(source.b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Evaluate feature on each item in the source." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "11" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "feature(source[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "22" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "feature(source[1])" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "33" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "feature(source[2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Using a `SourceItem` with Callbacks\n", + "\n", + "A `SourceItem` is a dictionary-like object used internally by `Source`. It holds data fields (e.g., `a`, `b`) and triggers registered callbacks when the item is called.\n", + "\n", + "This behavior enables features like automatic index switching and dependency tracking when a data item is accessed in DeepTrack2." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a callback function:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "def callback(item):\n", + " print(f\"CALLBACK - Item accessed: {item}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a `SourceItem` registering this callback:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sources.base import SourceItem\n", + "\n", + "item = SourceItem([callback], a=5, b=10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Trigger the callback by calling the item:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CALLBACK - Item accessed: SourceItem({'a': 5, 'b': 10}, 1 callback(s))\n" + ] + } + ], + "source": [ + "item();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Access values directly as in a dictionary:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item[\"a\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "item[\"b\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Using `Source` to Generate a Dataset of `SourceItem` Objects\n", + "\n", + "A `Source` organizes multiple named sequences (e.g., `a`, `b`) into a dataset that returns SourceItem objects. These items can be accessed by index or iterated over.\n", + "\n", + "This is the primary interface for constructing datasets in DeepTrack2." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.1. Basic Usage" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a source with multiple fields:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sources.base import Source\n", + "\n", + "dataset = Source(a=[1, 2, 3], b=[4, 5, 6])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Access individual items by index:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SourceItem({'a': 1, 'b': 4}, 1 callback(s))" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dataset[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SourceItem({'a': 2, 'b': 5}, 1 callback(s))" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dataset[1]" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "SourceItem({'a': 3, 'b': 6}, 1 callback(s))" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dataset[2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Iterate over all items in the source:" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SourceItem({'a': 1, 'b': 4}, 1 callback(s))\n", + "SourceItem({'a': 2, 'b': 5}, 1 callback(s))\n", + "SourceItem({'a': 3, 'b': 6}, 1 callback(s))\n" + ] + } + ], + "source": [ + "for item in dataset:\n", + " print(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.2. Dynamically Evaluating Source Attributes\n", + "\n", + "Each field in a `Source` is wrapped in a `SourceDeepTrackNode`, enabling dynamic evaluation based on the currently active index." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Source(a=[1, 2, 3], b=[10, 20, 30])" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "source = Source(a=[1, 2, 3], b=[10, 20, 30])\n", + "source.set_index(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "source.a()" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "20" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "source.b()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Source attributes can be passed directly to features and dynamically evaluated using `SourceItem`." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "33" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "feature = dt.Value(source.a) + dt.Value(source.b)\n", + "feature(source[2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.3. Registering Activation Callbacks with `on_activate()`\n", "\n", - "Description ..." + "You can register callbacks that are triggered when a `SourceItem` is called. This enables features like dependency tracking, logging, or side effects." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. Dynamically Generating Child Nodes with `SourceDeepTrackNode`" + "Define a callback function:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "def log_access(item):\n", + " print(f\"Item activated: {item}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Define parent node with `SourceDeepTrackNode`." + "Define a source and add the callback:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "source = Source(a=[1, 2], b=[3, 4])\n", + "source.on_activate(log_access)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Trigger the callback whern the source is called:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "30\n" + "Item activated: SourceItem({'a': 2, 'b': 4}, 2 callback(s))\n" ] } ], "source": [ - "from deeptrack.sources.base import SourceDeepTrackNode\n", + "source[1]();" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.4. Slicing a Source\n", "\n", - "node = SourceDeepTrackNode(lambda: {\"a\": 10, \"b\": 20})" + "You can retrieve a contiguous subset of a `Source` using slicing. Each item in the result is a `SourceItem`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Create some child nodes." + "Define a source:" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 30, "metadata": {}, "outputs": [], "source": [ - "child_a = node.a\n", - "child_b = node.b" + "source = Source(a=[1, 2, 3], b=[4, 5, 6])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Call the child nodes." + "Slice it:" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ - "result = child_a() + child_b()" + "subset = source[1:3]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Iterate over the sliced source items:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SourceItem({'a': 2, 'b': 5}, 1 callback(s))\n", + "SourceItem({'a': 3, 'b': 6}, 1 callback(s))\n" + ] + } + ], + "source": [ + "for item in subset:\n", + " print(item)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Generating a Source Item with Callbacks\n", + "### 4.5. Filtering a Source with `filter()`\n", "\n", - "You will generate a source item that allows callbacks when accessed." + "The `filter()` method creates a `Subset` based on a predicate function. The predicate receives the fields as keyword arguments." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Create a callback." + "Create a source:" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ - "def callback(item):\n", - " print(f\"CALLBACK - Item accessed: {item}\")" + "source = Source(\n", + " a=[1, 2, 3, 4, 5, 6, 7, 8, 9],\n", + " b=[11, 12, 13, 14, 15, 16, 17, 18, 19],\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Create a `SourceItem` registering this callback." + "Filter it:" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 34, "metadata": {}, "outputs": [], "source": [ - "from deeptrack.sources.base import SourceItem\n", - "\n", - "item = SourceItem([callback], a=5, b=10)" + "filtered = source.filter(lambda a, b: a > 2 and b < 16)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Call the item to trigger a callback." + "And iterate over the filtered items:" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "CALLBACK - Item accessed: SourceItem({'a': 5, 'b': 10})\n" + "SourceItem({'a': 3, 'b': 13}, 1 callback(s))\n", + "SourceItem({'a': 4, 'b': 14}, 1 callback(s))\n", + "SourceItem({'a': 5, 'b': 15}, 1 callback(s))\n" ] } ], "source": [ - "item();" + "for item in filtered:\n", + " print(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 4.6. Adding Constant Metadata with `constants()`\n", + "\n", + "The `constants()` method adds new fields with fixed values to each item in the source. These are repeated to match the dataset length." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Access values directly." + "Create a source:" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 36, + "metadata": {}, + "outputs": [], + "source": [ + "source = Source(a=[1, 2], b=[3, 4])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Add a constant:" + ] + }, + { + "cell_type": "code", + "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "5" + "Product(label=['constant', 'constant'], a=[1, 2], b=[3, 4])" ] }, - "execution_count": 15, + "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "item[\"a\"]" + "labeled_source = source.constants(label=\"constant\")\n", + "labeled_source" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 4. Generate a dataset of multiple `SourceItem` objects." + "### 4.7.Expanding a Source with `product()`\n", + "\n", + "The `product()` method creates a Cartesian product between the original items and new fields. This is useful for parameter sweeps or augmentation." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Define a source with multiple attributes." + "Create a source:" ] }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 38, "metadata": {}, "outputs": [], "source": [ - "from deeptrack.sources.base import Source\n", - "\n", - "dataset = Source(a=[1, 2, 3], b=[4, 5, 6])" + "source = Source(a=[1, 2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Access elements by index." + "Extend the souce with a product:" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 39, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SourceItem({'a': 1, 'b': 4})\n", - "SourceItem({'a': 2, 'b': 5})\n" - ] - } - ], + "outputs": [], "source": [ - "print(dataset[0])\n", - "print(dataset[1])" + "extended = source.product(c=[\"x\", \"y\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Print the items in the dataset." + "And iterate over the resulting source items:" ] }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 40, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "SourceItem({'a': 1, 'b': 4})\n", - "SourceItem({'a': 2, 'b': 5})\n", - "SourceItem({'a': 3, 'b': 6})\n" + "SourceItem({'c': 'x', 'a': 1}, 1 callback(s))\n", + "SourceItem({'c': 'y', 'a': 1}, 1 callback(s))\n", + "SourceItem({'c': 'x', 'a': 2}, 1 callback(s))\n", + "SourceItem({'c': 'y', 'a': 2}, 1 callback(s))\n" ] } ], "source": [ - "for item in dataset:\n", + "for item in extended:\n", " print(item)" ] }, @@ -294,35 +1002,65 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 5. Combine existing attributes with `Product`" + "## 5. Combining Existing Attributes with `Product`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a source:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "source = Source(a=[1, 2], b=[3, 4])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Generate a new source as a product with new attributes." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "new_source = source.product(c=[5, 6])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the resulting combinations." + ] + }, + { + "cell_type": "code", + "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "SourceItem({'c': 5, 'a': 1, 'b': 3})\n", - "SourceItem({'c': 6, 'a': 1, 'b': 3})\n", - "SourceItem({'c': 5, 'a': 2, 'b': 4})\n", - "SourceItem({'c': 6, 'a': 2, 'b': 4})\n" + "SourceItem({'c': 5, 'a': 1, 'b': 3}, 1 callback(s))\n", + "SourceItem({'c': 6, 'a': 1, 'b': 3}, 1 callback(s))\n", + "SourceItem({'c': 5, 'a': 2, 'b': 4}, 1 callback(s))\n", + "SourceItem({'c': 6, 'a': 2, 'b': 4}, 1 callback(s))\n" ] } ], "source": [ - "from deeptrack.sources.base import Source\n", - "\n", - "# Create a source\n", - "source = Source(a=[1, 2], b=[3, 4])\n", - "\n", - "# Generate a new source as a product with new attributes.\n", - "new_source = source.product(c=[5, 6])\n", - "\n", - "# Print the combinations.\n", "for item in new_source:\n", " print(item)" ] @@ -331,95 +1069,160 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 6. Filter dataset items with `Subset`" + "## 6. Filtering Dataset Items with `Subset`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a source:" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 44, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SourceItem({'a': 2, 'b': 2})\n", - "SourceItem({'a': 4, 'b': 4})\n", - "SourceItem({'a': 2, 'b': 7})\n", - "SourceItem({'a': 8, 'b': 9})\n", - "SourceItem({'a': 8, 'b': 11})\n" - ] - } - ], + "outputs": [], "source": [ - "from deeptrack.sources import Source, Subset\n", - "\n", - "# Define a source.\n", "source = Source(\n", " a=[1, 2, 3, 4, 2, 8, 8],\n", - " b=[1, 2, 3, 4, 7, 9, 11 ]\n", - ")\n", - "\n", - "# Create a subset with only even values of 'a'.\n", - "subset = source.filter(lambda a, b: a % 2 == 0)\n", - "\n", - "# Print subset values.\n", - "for item in subset:\n", - " print(item)" + " b=[1, 2, 3, 4, 7, 9, 11],\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 7. Random splitting of sources into multiple subsets\n" + "Create a subset with only even values of `a`:" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 45, + "metadata": {}, + "outputs": [], + "source": [ + "subset = source.filter(lambda a, b: a % 2 == 0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print the subset values:" + ] + }, + { + "cell_type": "code", + "execution_count": 46, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Subset 1:\n", - "SourceItem({'a': 4, 'b': 10})\n", - "SourceItem({'a': 3, 'b': 9})\n", - "Subset 2:\n", - "SourceItem({'a': 6, 'b': 12})\n", - "SourceItem({'a': 5, 'b': 11})\n", - "SourceItem({'a': 2, 'b': 8})\n", - "SourceItem({'a': 1, 'b': 7})\n" + "SourceItem({'a': 2, 'b': 2}, 1 callback(s))\n", + "SourceItem({'a': 4, 'b': 4}, 1 callback(s))\n", + "SourceItem({'a': 2, 'b': 7}, 1 callback(s))\n", + "SourceItem({'a': 8, 'b': 9}, 1 callback(s))\n", + "SourceItem({'a': 8, 'b': 11}, 1 callback(s))\n" ] } ], "source": [ - "from deeptrack.sources import Source, random_split\n", + "for item in subset:\n", + " print(item)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Randomly Splitting Sources into Multiple Subsets" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a source:" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sources import random_split\n", "import numpy as np\n", "\n", - "# Create a source\n", "source = Source(\n", " a=[1, 2, 3, 4, 5, 6],\n", " b=[7, 8, 9, 10, 11, 12],\n", - ")\n", - "\n", - "# Split into two subsets (proportionally to 30% and 70%)\n", - "# commonly used for validation during training.\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Split into two subsets (proportionally to 70% and 30%) commonly used for validation during training." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ "train_subset, test_subset = random_split(\n", " source,\n", - " lengths=[0.3, 0.7],\n", - " generator=np.random.default_rng(42)\n", - ")\n", - "\n", - "print(\"Subset 1:\")\n", - "for item in train_subset:\n", - " print(item)\n", - "\n", - "print(\"Subset 2:\")\n", - "for item in test_subset:\n", - " print(item)" + " lengths=[0.7, 0.3],\n", + " generator=np.random.default_rng(42),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Subset(a=[4, 3, 6, 5]..., b=[10, 9, 12, 11]...)" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "train_subset" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Subset(a=[1], b=[7])" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_subset" ] } ], From ea8be6b843a8397527c367507303aa1b83930200 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 11:06:50 +0100 Subject: [PATCH 187/223] Delete interface_v2.mp4 --- assets/interface_v2.mp4 | Bin 1020199 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 assets/interface_v2.mp4 diff --git a/assets/interface_v2.mp4 b/assets/interface_v2.mp4 deleted file mode 100644 index 9e6f50392737d3ae20a053465eb5422755a4b703..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1020199 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%0Ah$(FVt`}dE}_RkzIui)vWST{e|$c3b%bo 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 z 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

4#LRGpMRxA8mlxJ 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%0D*UIVA`}}znSE3FOL8~X?}n; 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 S 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 z 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�=>dkCq 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

DSwI{DBdG6Y@j*{;uQ&L>kyqN4?3d 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�Q!EB|X<-q3U2Tr!5 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

b;*&=S>H0ir{=yza(B7F-JK~UzSKS8mdcCC0 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 Date: Thu, 10 Jul 2025 11:59:02 +0100 Subject: [PATCH 188/223] Update DTAT391B_sources.folder.ipynb --- .../DTAT391B_sources.folder.ipynb | 304 +++++++++++------- 1 file changed, 190 insertions(+), 114 deletions(-) diff --git a/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb b/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb index f49080b07..e826b5a59 100644 --- a/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb +++ b/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# deeptrack.sources.folder\n", + "# DTAT391B. deeptrack.sources.folder\n", "\n", "\"Open" ] @@ -29,9 +29,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 1. What is `folder`?\n", + "## 1. What is `folder.py`?\n", "\n", - "The `folder` module enables the management of image datasets organized in a directory hierarchy. It contains a single class `ImageFolder` that provides utilities to perform structured naming, organization, and retrieval of image data." + "The `folder.py` module enables the management of image datasets organized in a directory hierarchy. It contains a single class `ImageFolder` that provides utilities to perform structured naming, organization, and retrieval of image data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The key roles of `folder.py` are:\n", + "\n", + "- **Recursively Index Folder-Based Datasets:**\n", + " The module provides utilities to index datasets that are stored on disk in folders,\n", + " typically structured by class labels or categories. It allows automatic discovery\n", + " and registration of files into sources, preserving the folder hierarchy.\n", + "\n", + "- **Associate Metadata with Files:**\n", + " It supports extracting metadata from folder structures (e.g., parent folder as label)\n", + " and saving them as fields in the resulting `Source`, making them directly usable\n", + " in downstream pipelines.\n", + "\n", + "- **Dynamic File Access for Streaming:**\n", + " Instead of loading all file contents at once, `folder.py` allows for lazy evaluation\n", + " by returning file paths as source fields. This enables streaming images or data\n", + " directly into DeepTrack features (e.g., `dt.LoadImage`), supporting scalable workflows.\n", + "\n", + "- **Split and Augment Datasets:**\n", + " It works seamlessly with `random_split`, `Product`, and other tools in `base.py`,\n", + " allowing flexible construction of training, validation, and test sets with dynamic\n", + " or constant augmentation fields." ] }, { @@ -40,205 +67,254 @@ "source": [ "## 2. Creating a Directory Structure\n", "\n", - "Since the `ImageFolder` class expects images to be stored in directories categorized by class names, we will need to create a dummy directory structure for demonstration purposes." + "Since the `ImageFolder` class expects images to be stored in directories categorized by class names, you will need to create a dummy directory structure for demonstration purposes." ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import shutil\n", "\n", - "from deeptrack.sources import folder\n", - "\n", - "\n", - "# Define root directory.\n", + "# Define root directory\n", "dataset_path = \"dummy_dataset\"\n", "\n", - "# Define class names.\n", - "classes = [\"cat\", \"dog\", \"bird\"]\n", - "\n", - "# Remove existing directory if exists.\n", + "# Remove existing directory if exists\n", "if os.path.exists(dataset_path):\n", " shutil.rmtree(dataset_path)\n", "\n", - "# Create directories.\n", - "for class_name in classes:\n", - " os.makedirs(os.path.join(dataset_path, class_name))\n", + "# Define splits and class names\n", + "splits = [\"train\", \"test\"]\n", + "classes = [\"cat\", \"dog\", \"bird\"]\n", "\n", - "# Create some empty dummy files.\n", - "for class_name in classes:\n", - " for i in range(3): \n", - " with open(os.path.join(dataset_path, class_name, f\"image_{i}.jpg\"), \"w\") as f:\n", - " f.write(\"\")\n" + "# Create directory structure and add dummy files\n", + "for split in splits:\n", + " for class_name in classes:\n", + " class_dir = os.path.join(dataset_path, split, class_name)\n", + " os.makedirs(class_dir, exist_ok=True)\n", + " for i in range(3): \n", + " with open(\n", + " os.path.join(class_dir, f\"{split}_image_{i}.jpg\"), \"w\"\n", + " ) as f:\n", + " f.write(\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 3. Initializing an `ImageFolder`.\n", - "Now that the dummy directory is created, we initialize an `ImageFolder` object." + "Print the directory structure." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Total images in dataset: 9\n", - "Classes: ['dog', 'cat', 'bird']\n" + "📂 dummy_dataset\n", + " 📂 test\n", + " 📂 cat\n", + " 📄 image_0.jpg\n", + " 📄 image_1.jpg\n", + " 📄 image_2.jpg\n", + " 📂 dog\n", + " 📄 image_0.jpg\n", + " 📄 image_1.jpg\n", + " 📄 image_2.jpg\n", + " 📂 bird\n", + " 📄 image_0.jpg\n", + " 📄 image_1.jpg\n", + " 📄 image_2.jpg\n", + " 📂 train\n", + " 📂 cat\n", + " 📄 image_0.jpg\n", + " 📄 image_1.jpg\n", + " 📄 image_2.jpg\n", + " 📂 dog\n", + " 📄 image_0.jpg\n", + " 📄 image_1.jpg\n", + " 📄 image_2.jpg\n", + " 📂 bird\n", + " 📄 image_0.jpg\n", + " 📄 image_1.jpg\n", + " 📄 image_2.jpg\n" ] } ], "source": [ - "data_source = folder.ImageFolder(dataset_path)\n", + "for root, dirs, files in os.walk(dataset_path):\n", "\n", - "# Print total number of images.\n", - "print(f\"Total images in dataset: {len(data_source)}\")\n", + " # Get depth of directory for indenting the print text\n", + " depth = root.replace(dataset_path, \"\").count(os.sep)\n", + " indent = \" \" * depth\n", "\n", - "# Print class names.\n", - "print(f\"Classes: {data_source.classes}\")" + " # Directories\n", + " directory_name = os.path.basename(root)\n", + " print(f\"{indent}📂 {directory_name}\")\n", + " \n", + " # Files\n", + " for filename in sorted(files):\n", + " print(f\"{indent} 📄 {filename}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 4. Getting Category Names from File Paths\n" + "## 3. Initializing an `ImageFolder`.\n", + "Now that the dummy directory is created, initialize an `ImageFolder` object." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "from deeptrack.sources.folder import ImageFolder\n", + "\n", + "data_source = ImageFolder(dataset_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Print total number of images:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Category of dummy_dataset/dog/image_1.jpg: dog\n" - ] + "data": { + "text/plain": [ + "9" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "example_path = os.path.join(dataset_path, \"dog\", \"image_1.jpg\")\n", - "category = data_source.get_category_name(example_path, directory_level=0)\n", - "print(f\"Category of {example_path}: {category}\")\n" + "len(data_source)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 5. Dataset Splitting.\n", - "\n", - "If the dataset has subcategories (e.g., train/dog, train/cat), we can split it according to those subcategories." + "Print class names:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 34, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Train set classes: ['cat']\n", - "Test set classes: ['dog']\n" - ] + "data": { + "text/plain": [ + "['dog', 'bird', 'cat']" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "# Create directories if they don't exist.\n", - "train_dir = os.path.join(dataset_path, \"train\")\n", - "test_dir = os.path.join(dataset_path, \"test\")\n", - "os.makedirs(train_dir, exist_ok=True)\n", - "os.makedirs(test_dir, exist_ok=True)\n", - "\n", - "\n", - "# Define source and destination paths\n", - "cat_src = os.path.join(dataset_path, \"cat\")\n", - "cat_dest = os.path.join(train_dir, \"cat\")\n", - "\n", - "dog_src = os.path.join(dataset_path, \"dog\")\n", - "dog_dest = os.path.join(test_dir, \"dog\")\n", - "\n", - "\n", - "# Move only if source exists and destination does not.\n", - "if os.path.exists(cat_src) and not os.path.exists(cat_dest):\n", - " shutil.move(cat_src, train_dir)\n", - "\n", - "if os.path.exists(dog_src) and not os.path.exists(dog_dest):\n", - " shutil.move(dog_src, test_dir)\n", - "\n", - "split_data_source = folder.ImageFolder(dataset_path)\n", - "\n", - "# Split into train and test.\n", - "train, test = split_data_source.split(\"train\", \"test\")\n", - "\n", - "print(f\"Train set classes: {train.classes}\")\n", - "print(f\"Test set classes: {test.classes}\")\n" + "data_source.classes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 6. Print directory structure\n", - "The resulting directory structure from splitting the dataset can be visualized by running the code cell below." + "## 4. Getting Category Names from File Paths\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Prepare an example path to one of the files:" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 35, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "📂 dummy_dataset\n", - " 📂 test\n", - " 📂 dog\n", - " 📄 image_0.jpg\n", - " 📄 image_1.jpg\n", - " 📄 image_2.jpg\n", - " 📂 bird\n", - " 📄 image_0.jpg\n", - " 📄 image_1.jpg\n", - " 📄 image_2.jpg\n", - " 📂 train\n", - " 📂 cat\n", - " 📄 image_0.jpg\n", - " 📄 image_1.jpg\n", - " 📄 image_2.jpg\n" - ] + "data": { + "text/plain": [ + "'dummy_dataset/dog/image_1.jpg'" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "for root, dirs, files in os.walk(dataset_path):\n", - "\n", - " # Get depth of directory for indenting the print text.\n", - " depth = root.replace(dataset_path, \"\").count(os.sep)\n", - " indent = \" \" * depth\n", + "example_path = os.path.join(dataset_path, \"dog\", \"image_1.jpg\")\n", + "example_path" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Get the corresponding category name:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'dog'" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "category = data_source.get_category_name(example_path, directory_level=0)\n", + "category" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Splitting the Dataset\n", "\n", - " # Directories.\n", - " directory_name = os.path.basename(root)\n", - " print(f\"{indent}📂 {directory_name}\")\n", - " \n", - " # Files.\n", - " for filename in sorted(files):\n", - " print(f\"{indent} 📄 {filename}\")" + "If the dataset has subcategories (e.g., dog/cat/bird), you can split it according to those subcategories." ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From a73a799e127fc2198c955f5c94087ff0d1bd9328 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 11:59:04 +0100 Subject: [PATCH 189/223] Update folder.py --- deeptrack/sources/folder.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index bd365d1a3..6b4d75ba3 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -184,26 +184,26 @@ def get_category_name( path: str, directory_level: int ) -> str: - + relative_path = path.replace(self._root, "", 1).lstrip(os.sep) folder = relative_path.split(os.sep)[directory_level] \ if relative_path else "" return folder - + def label_to_name( self, label: int ) -> str: """Gets the category corresponding to a label""" return self._int_to_category[label] - + def name_to_label( self, name: str ) -> int: """Gets the label corresponding to a category""" return self._category_to_int[name] - + def split( self, *splits: str @@ -236,12 +236,12 @@ def split( The names of the categories to split into. """ - - all_splits = set([self.get_category_name(path, 0) + + all_splits = set([self.get_category_name(path, 0) for path in self._paths]) if len(splits) == 0: - + if len(all_splits) == 0: raise ValueError("No categories to split into") return self.split(*all_splits) @@ -260,7 +260,6 @@ def update_root_source( for key in item: getattr(self, key).invalidate() getattr(self, key).set_value(item[key]) - for split in splits: subfolder = ImageFolder(os.path.join(self._root, split)) From 9b5bc20a0cf1cb535000428ed851cd8d722ca632 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 12:08:33 +0100 Subject: [PATCH 190/223] Update test_features.py --- deeptrack/tests/test_features.py | 68 ++++++++++++++++---------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 39d4f040d..ab86f2723 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -1956,50 +1956,50 @@ def test_AsType(self): #TODO -def test_ChannelFirst2d(self): + def test_ChannelFirst2d(self): - channel_first_feature = features.ChannelFirst2d() + channel_first_feature = features.ChannelFirst2d() - # Numpy shapes - input_image = np.zeros((10, 20)) - output_image = channel_first_feature.get(input_image, axis=-1) - self.assertEqual(output_image.shape, (1, 10, 20)) - - input_image = np.zeros((10, 20, 3)) - output_image = channel_first_feature.get(input_image, axis=-1) - self.assertEqual(output_image.shape, (3, 10, 20)) - - # Image[Numpy] shape - input_image = Image(np.zeros((10, 20, 3))) - output_image = channel_first_feature.get(input_image, axis=-1) - self.assertEqual(output_image._value.shape, (3, 10, 20)) - - # Numpy values - input_image = np.array([[[1, 2, 3], [4, 5, 6]]]) - output_image = channel_first_feature.get(input_image, axis=-1) - self.assertEqual(output_image.shape, (3, 1, 2)) - np.testing.assert_array_equal(output_image, np.moveaxis(input_image, -1, 0)) - - if TORCH_AVAILABLE: - # Torch shapes - input_image = torch.zeros(10, 20) + # Numpy shapes + input_image = np.zeros((10, 20, 1)) output_image = channel_first_feature.get(input_image, axis=-1) - self.assertEqual(tuple(output_image.shape), (1, 10, 20)) + self.assertEqual(output_image.shape, (1, 10, 20)) - input_image = torch.zeros(10, 20, 3) + input_image = np.zeros((10, 20, 3)) output_image = channel_first_feature.get(input_image, axis=-1) - self.assertEqual(tuple(output_image.shape), (3, 10, 20)) + self.assertEqual(output_image.shape, (3, 10, 20)) - # Image[Torch] shape - input_image = Image(torch.zeros(10, 20, 3)) + # Image[Numpy] shape + input_image = Image(np.zeros((10, 20, 3))) output_image = channel_first_feature.get(input_image, axis=-1) - self.assertEqual(tuple(output_image.shape), (3, 10, 20)) + self.assertEqual(output_image._value.shape, (3, 10, 20)) - # Torch values - input_image = torch.tensor([[[1, 2, 3], [4, 5, 6]]]) + # Numpy values + input_image = np.array([[[1, 2, 3], [4, 5, 6]]]) output_image = channel_first_feature.get(input_image, axis=-1) self.assertEqual(output_image.shape, (3, 1, 2)) - self.assertTrue(torch.equal(output_image, input_image.permute(2, 0, 1))) + np.testing.assert_array_equal(output_image, np.moveaxis(input_image, -1, 0)) + + if TORCH_AVAILABLE: + # Torch shapes + input_image = torch.zeros(10, 20) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(tuple(output_image.shape), (1, 10, 20)) + + input_image = torch.zeros(10, 20, 3) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(tuple(output_image.shape), (3, 10, 20)) + + # Image[Torch] shape + input_image = Image(torch.zeros(10, 20, 3)) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(tuple(output_image.shape), (3, 10, 20)) + + # Torch values + input_image = torch.tensor([[[1, 2, 3], [4, 5, 6]]]) + output_image = channel_first_feature.get(input_image, axis=-1) + self.assertEqual(output_image.shape, (3, 1, 2)) + self.assertTrue(torch.equal(output_image, input_image.permute(2, 0, 1))) def test_Upscale(self): From 8013e1705d3392f491f0249937243fc44e1fd963 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 12:08:35 +0100 Subject: [PATCH 191/223] Update folder.py --- deeptrack/sources/folder.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index 6b4d75ba3..34c280ce7 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -46,11 +46,12 @@ """ +from __future__ import annotations + import glob import os -from typing import List, Tuple -from deeptrack.sources.base import Source +from deeptrack.sources.base import Source, SourceItem __all__ = [ @@ -134,15 +135,15 @@ class ImageFolder(Source): @property def classes( - self - ) -> List: + self: ImageFolder, + ) -> list[str]: return list(self._category_to_int.keys()) def __init__( - self, - root: str + self: ImageFolder, + root: str, ) -> None: - + self._root = root self._paths = glob.glob(f"{root}/**/*", recursive=True) @@ -175,14 +176,14 @@ def __init__( ) def __len__( - self + self: ImageFolder, ) -> int: return self._length def get_category_name( - self, + self: ImageFolder, path: str, - directory_level: int + directory_level: int, ) -> str: relative_path = path.replace(self._root, "", 1).lstrip(os.sep) @@ -191,23 +192,23 @@ def get_category_name( return folder def label_to_name( - self, - label: int + self: ImageFolder, + label: int, ) -> str: """Gets the category corresponding to a label""" return self._int_to_category[label] def name_to_label( - self, - name: str + self: ImageFolder, + name: str, ) -> int: """Gets the label corresponding to a category""" return self._category_to_int[name] def split( - self, - *splits: str - ) -> Tuple[str]: + self: ImageFolder, + *splits: str, + ) -> tuple[str]: """Split the dataset into subsets. The splits are defined by the names of the first folder @@ -254,7 +255,7 @@ def split( output = [] def update_root_source( - item + item: SourceItem, ) -> None: """Inner function which updates attributes of root source.""" for key in item: From 033d3a0e58f9733ec9dc0fdb54f98c6a260e8d68 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 12:53:20 +0100 Subject: [PATCH 192/223] Update folder.py --- deeptrack/sources/folder.py | 390 ++++++++++++++++++++++++++---------- 1 file changed, 279 insertions(+), 111 deletions(-) diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index 34c280ce7..9f6ddef77 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -1,11 +1,9 @@ """Utility class for data sources in a directory structure. -This module provies the `ImageFolder` DeepTrack2 class -which enables control of image sources organized -in a directory structure. +This module provies the `ImageFolder` DeepTrack2 class which enables control of +image sources organized in a directory structure. -The primary usage is to facilitate naming and -organizing of data sources. +The primary usage is to facilitate naming and organizing of data sources. Key Features ------------ @@ -27,11 +25,12 @@ Module Structure ---------------- +Class: `ImageFolder`: Data source for images organized in a directory structure. - Allows for processing of image sources with `Dict` data strucutres, + It allows for processing of image sources with `Dict` data strucutres, splitting, naming and labeling functions. - + Examples -------- Print some information about a source of data: @@ -56,6 +55,7 @@ __all__ = [ "ImageFolder", + "known_extensions", ] @@ -64,120 +64,229 @@ class ImageFolder(Source): """Data source for images organized in a directory structure. - This class assumes that the images are organized in a - directory structure where: - - ```bash - root/dog/xxx.png - root/dog/xxy.png - root/[...]/xxz.png + `ImageFolder` scans a directory tree where images are stored under + subdirectories that represent their categorical labels. It automatically + assigns integer labels, stores paths and names, and supports operations + such as splitting by folder and category lookup. - root/cat/123.png - root/cat/nsdf3.png - root/[...]/asd932_.png - ``` - - The first level of directories (e.g., `dog`, `cat`) is used as labels - for the images, and the images are expected to have file extensions - included in `known_extensions`. + It behaves like a standard `Source`, returning `SourceItem` objects that + include image file paths, label indices, and label names. This allows + seamless integration with feature pipelines in DeepTrack2. Parameters ---------- - path: list - - List of paths to the image files. - - label: list - - List of corresponding labels for each image. - - label_name: list - - List of category names corresponding to each label. - + root: str + Path to the root directory that contains subfolders of images. The + first-level subfolder names are interpreted as categories. + + Attributes + ---------- + _category_to_int: dict[str, int] + Mapping from category name to numeric label. + _int_to_category: dict[int, str] + Mapping from numeric label to category name. + _paths: list[str] + Internal list of all file paths. + _length: int + Total number of images discovered. + _root: str + Root directory provided by the user. Methods ------- - classes: list - Returns a list of unique class names (category names). - - __init__(root: str) - - Initializes the `ImageFolder` instance by scanning - the directory structure. - - __len__() - Returns the total number of images in the dataset. - - get_category_name(path: str, directory_level: int) - - Retrieves the category name (directory name) for the given image path - at a specific directory level. - - label_to_name(label: int) - - Converts a label index to the corresponding category name. - - name_to_label(name: str) - - Converts a category name to the corresponding label index. - - split(*splits: str) - - Splits the dataset into subsets based on the folder structure. - The first folder name in the path will be used to define the split. - + __len__() -> int + Return the number of image files found. + classes -> list[str] + Return a list of unique class names found in the directory. + get_category_name(path: str, directory_level: int) -> str + Return the category name for a given image path. + label_to_name(label: int) -> str + Convert an integer label back to its string category name. + name_to_label(name: str) -> int + Convert a string category name to its integer label. + split(*splits: str) -> tuple[ImageFolder, ...] + Return one or more subsets of the data based on top-level folder names. + + Examples + -------- + **Create a dummy dataset structure with train/test subfolders** + +import os +import shutil + +# Temporary root directory +root = "tmp_data" + +# Remove existing directory if needed +if os.path.exists(root): + shutil.rmtree(root) + +# Define splits and classes +splits = ["train", "test"] +classes = ["cat", "dog", "bird"] + +# Create directories and dummy files +for split in splits: + for cls in classes: + folder_path = os.path.join(root, split, cls) + os.makedirs(folder_path) + for i in range(2): + file_path = os.path.join(folder_path, f"image_{i}.jpg") + with open(file_path, "w") as f: + f.write("dummy") + + **Load a split of the dataset** + +from deeptrack.sources.folder import ImageFolder + +# Load the training set +train_data = ImageFolder(os.path.join(root, "train")) + +print(len(train_data)) +print(train_data.classes) +print(train_data.path()) + + **Access a source item** + +item = train_data[0] +print(item["path"]) +print(item["label"]) +print(item["label_name"]) + + **Convert between label names and indices** + +train_data.name_to_label("cat") +train_data.label_to_name(0) + + **Split the dataset across top-level folders** + +all_data = ImageFolder(root) +train, test = all_data.split("train", "test") + +print(f"Train size: {len(train)}") +print(f"Test size: {len(test)}") + + **Print paths in each split** + +print("Train files:") +for item in train: + print(item["path"]) + +print("Test files:") +for item in test: + print(item["path"]) + """ - path: str - label: int - label_name: str + _root: str + _paths: list[str] + _length: int + _category_to_int: dict[str, int] + _int_to_category: dict[int, str] @property def classes( self: ImageFolder, ) -> list[str]: + """List of category names in the dataset. + + Returns + ------- + list[str] + A list of unique category names corresponding to the top-level + directories found under the root folder. + + """ + return list(self._category_to_int.keys()) def __init__( self: ImageFolder, root: str, - ) -> None: + ): + """Initialize an `ImageFolder` from a directory structure. + + This constructor scans a given root directory recursively for image + files, assigns labels based on their immediate subfolder names, and + initializes the `Source` base class with the image paths and + associated metadata. + + The directory structure is expected to follow the format: + + root/category_name/image_001.png + root/category_name/image_002.png + ... + All recognized files must have an extension in `known_extensions`. + + Parameters + ---------- + root: str + Path to the root directory containing the categorized images. + + Raises + ------ + ValueError + If no valid image files are found or directory is malformed. + + """ + + # Store the root directory path self._root = root + # Recursively collect all file paths under root self._paths = glob.glob(f"{root}/**/*", recursive=True) + + # Filter for valid image files using known extensions self._paths = [ - path for path in self._paths if os.path.isfile(path) - and path.split(".")[-1] in known_extensions - ] + path for path in self._paths + if os.path.isfile(path) and path.split(".")[-1] in known_extensions + ] + # Ensure consistent order across runs self._paths.sort() + # Store total number of valid image paths self._length = len(self._paths) - # Get category name as 1 directory down from root. - category_per_path = [self.get_category_name(path, 0) - for path in self._paths] + # Extract category name from path (1 level down from root) + category_per_path = [ + self.get_category_name(path, 0) for path in self._paths + ] + # Compute the set of unique category names unique_categories = set(category_per_path) - # Create a dictionary mapping category name to integer. - self._category_to_int = {category: i for i, category - in enumerate(unique_categories)} - self._int_to_category = {i: category for category, i - in self._category_to_int.items()} - - # Create a list of integers corresponding to the category of each path. - categories = [self._category_to_int[category] - for category in category_per_path] - + # Create mapping: category name -> integer label + self._category_to_int = { + category: i for i, category in enumerate(unique_categories) + } + # Create inverse mapping: label index -> category name + self._int_to_category = { + i: category for category, i in self._category_to_int.items() + } + + # Map each image path to its integer label + categories = [ + self._category_to_int[category] for category in category_per_path + ] + + # Initialize the base Source with path, label index, and label name super().__init__( path=self._paths, label=categories, - label_name=category_per_path + label_name=category_per_path, ) def __len__( self: ImageFolder, ) -> int: + """Return the total number of images in the dataset. + + Returns + ------- + int + The number of image paths found in the directory structure. + + """ + return self._length def get_category_name( @@ -185,68 +294,125 @@ def get_category_name( path: str, directory_level: int, ) -> str: + """Extract the category name from file path at given directory level. + + This method determines the category name (i.e., the name of the + directory at the specified `directory_level` relative to the root) + associated with the given file path. + + Parameters + ---------- + path: str + The absolute path to the image file. + directory_level: int + The index of the directory component to extract, relative to the + root. + + Returns + ------- + str + The name of the folder at the given level in the path. + + """ relative_path = path.replace(self._root, "", 1).lstrip(os.sep) - folder = relative_path.split(os.sep)[directory_level] \ - if relative_path else "" + folder = ( + relative_path.split(os.sep)[directory_level] + if relative_path + else "" + ) return folder def label_to_name( self: ImageFolder, label: int, ) -> str: - """Gets the category corresponding to a label""" + """Convert an integer label to its corresponding category name. + + Given a numeric label (e.g., 0, 1, 2), return the associated category + name (e.g., "cat", "dog") that was assigned during initialization. + + Parameters + ---------- + label: int + The integer label representing a category. + + Returns + ------- + str + The name of the category corresponding to the label. + + """ + return self._int_to_category[label] def name_to_label( self: ImageFolder, name: str, ) -> int: - """Gets the label corresponding to a category""" + """Convert a category name to its corresponding integer label. + + Given a category name (e.g., "cat", "dog"), return the integer label + (e.g., 0, 1) assigned to it during initialization. + + Parameters + ---------- + name: str + The name of the category. + + Returns + ------- + int + The integer label corresponding to the category name. + + """ + return self._category_to_int[name] def split( self: ImageFolder, *splits: str, ) -> tuple[str]: - """Split the dataset into subsets. - - The splits are defined by the names of the first folder - in the path of each image. For example, if the dataset - contains images in the following structure: - - ```bash - root/A/dog/xxx.png - root/A/dog/xxy.png - root/A/[...]/xxz.png - - root/B/cat/123.png - root/B/cat/nsdf3.png - root/B/[...]/asd932_.png - ``` - - Then the dataset can be split into two subsets, one containing - all images in the `A` folder and one containing all images - in the `B` folder. + """Split the dataset into subsets by folder name. + + This method splits the dataset into subsets based on the first folder + name in the path of each image. It is useful when datasets are stored + in separate directories (e.g., `train`, `test`, `val`), and you want to + retrieve subsets accordingly. + + If no arguments are given, it returns one `ImageFolder` per top-level + directory found under the root. If specific names are provided, only + those subsets are returned. Parameters ---------- + *splits: str + Names of the subfolders (relative to the root) to split into. + + Returns + ------- + tuple[ImageFolder, ...] + A tuple of `ImageFolder` instances, one per requested split. + + Raises + ------ + ValueError + If an unknown split name is provided or no categories are found. - splits: str - - The names of the categories to split into. - """ + # Get top-level folder names present in image paths all_splits = set([self.get_category_name(path, 0) for path in self._paths]) + # If no specific splits provided, return all available if len(splits) == 0: if len(all_splits) == 0: raise ValueError("No categories to split into") return self.split(*all_splits) + # Validate requested splits if not all(split in all_splits for split in splits): raise ValueError( f"Unknown split. Available splits are {all_splits}" @@ -263,7 +429,9 @@ def update_root_source( getattr(self, key).set_value(item[key]) for split in splits: + # Create ImageFolder pointing to subdirectory subfolder = ImageFolder(os.path.join(self._root, split)) + # Attach update callback to propagate selected item to parent subfolder.on_activate(update_root_source) output.append(subfolder) From a9c8f5d0336dec7d041f8e60b5f386eb8e56e2af Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 12:56:55 +0100 Subject: [PATCH 193/223] Update folder.py --- deeptrack/sources/folder.py | 58 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index 9f6ddef77..1b4fc618d 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -1,47 +1,53 @@ -"""Utility class for data sources in a directory structure. +"""Data sources from images organized in a directory structure. -This module provies the `ImageFolder` DeepTrack2 class which enables control of -image sources organized in a directory structure. +This module provides the `ImageFolder` class, which enables structured access +to images stored in a hierarchical folder layout, such as: -The primary usage is to facilitate naming and organizing of data sources. + root/train/cat/image1.jpg + root/train/dog/image2.jpg + root/test/bird/image3.jpg + +The class supports automatic labeling based on directory names, integration +with DeepTrack data pipelines, and flexible splitting of datasets by folder. Key Features ------------ - **Attribute Access** - Enables accessing attributes tied to a data source such as - paths, directory structure, length etc. - -- **Labeling** + Provides access to common attributes such as image paths, label indices, + and category names. Each entry is returned as a `SourceItem` with fields + `path`, `label`, and `label_name`. + +- **Automatic Labeling** - Allows converting category names of images to integers, - which is more flexible and easy to process in a data pipeline. + Converts directory names into integer labels, supporting direct use in + training pipelines or models that expect categorical inputs. -- **Category Splitting** +- **Flexible Dataset Splitting** - The sources of images can be split into subcategories of which the - user specifies the name of. - + Supports splitting datasets based on the top-level folder structure. + This enables separating data into training, validation, and test sets + using directory naming conventions. Module Structure ---------------- -Class: -`ImageFolder`: Data source for images organized in a directory structure. +Classes: - It allows for processing of image sources with `Dict` data strucutres, - splitting, naming and labeling functions. +- `ImageFolder`: Source of image paths and labels from a structured folder. -Examples --------- -Print some information about a source of data: + Wraps a directory of image files into a DeepTrack `Source`, supporting + standard methods such as iteration, indexing, and filtering. ->>> from deeptrack.sources import folder +Attributes: ->>> root = "data/train" ->>> data_source = folder.ImageFolder(root) +- `known_extensions: list[str]` ->>> print(f"Total images in training data: {len(train_data)}") ->>> print(f"Classes: {train_data.classes}") + List of recognized file extensions used when scanning directories for + valid image files: `["png", "jpg", "jpeg", "tif", "tiff", "bmp", "gif"]` + +Examples +-------- +TODO """ From 6db8a9348b50b99c4b4c97886170b997378c7289 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 17:36:38 +0100 Subject: [PATCH 194/223] Update folder.py --- deeptrack/sources/folder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index 1b4fc618d..dac8ff967 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -67,6 +67,7 @@ known_extensions = ["png", "jpg", "jpeg", "tif", "tiff", "bmp", "gif"] + class ImageFolder(Source): """Data source for images organized in a directory structure. From 4527ff23ddccce3830d727c2299842c022c86ee1 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 17:36:41 +0100 Subject: [PATCH 195/223] Create test_folder.py --- deeptrack/tests/sources/test_folder.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 deeptrack/tests/sources/test_folder.py diff --git a/deeptrack/tests/sources/test_folder.py b/deeptrack/tests/sources/test_folder.py new file mode 100644 index 000000000..e69de29bb From 6412727257794af548cbd6ee201b71d15b139746 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 17:47:42 +0100 Subject: [PATCH 196/223] Update test_folder.py --- deeptrack/tests/sources/test_folder.py | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/deeptrack/tests/sources/test_folder.py b/deeptrack/tests/sources/test_folder.py index e69de29bb..fb0593c22 100644 --- a/deeptrack/tests/sources/test_folder.py +++ b/deeptrack/tests/sources/test_folder.py @@ -0,0 +1,62 @@ +# pylint: disable=C0115:missing-class-docstring +# pylint: disable=C0116:missing-function-docstring +# pylint: disable=C0103:invalid-name + +import unittest +import os +import shutil + +from deeptrack.sources.folder import ImageFolder + + +class TestFolder(unittest.TestCase): + + def setUp(self): + self.root = "temp_test_data" + self.splits = ["train", "val"] + self.classes = ["cat", "dog", "bird"] + + for split in self.splits: + for cls in self.classes: + path = os.path.join(self.root, split, cls) + os.makedirs(path, exist_ok=True) + for i in range(2): + file_path = os.path.join(path, f"img{i}.jpg") + with open(file_path, "w") as f: + f.write("") + + def tearDown(self): + shutil.rmtree(self.root) + + def test_ImageFolder_basic(self): + dataset = ImageFolder(os.path.join(self.root, "train")) + self.assertEqual(len(dataset), 6) + self.assertSetEqual(set(dataset.classes), set(self.classes)) + self.assertTrue(all(os.path.isfile(item["path"]) for item in dataset)) + + def test_ImageFolder_split(self): + combined = ImageFolder(self.root) + train, val = combined.split("train", "val") + + self.assertEqual(len(train), 6) + self.assertEqual(len(val), 6) + + train_paths = [item["path"] for item in train] + val_paths = [item["path"] for item in val] + + self.assertTrue(all("train" in path for path in train_paths)) + self.assertTrue(all("val" in path for path in val_paths)) + + def test_ImageFolder_label_mapping(self): + dataset = ImageFolder(os.path.join(self.root, "train")) + + for label in dataset["label"]: + name = dataset.label_to_name(label) + self.assertIsInstance(name, str) + self.assertIn(name, dataset.classes) + back_label = dataset.name_to_label(name) + self.assertEqual(label, back_label) + + +if __name__ == "__main__": + unittest.main() From f6de929efbd7265915a0f4d35e3b480a21369af9 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 17:47:45 +0100 Subject: [PATCH 197/223] Update folder.py --- deeptrack/sources/folder.py | 79 ++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index dac8ff967..8e650834c 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -118,47 +118,54 @@ class ImageFolder(Source): -------- **Create a dummy dataset structure with train/test subfolders** -import os -import shutil - -# Temporary root directory -root = "tmp_data" - -# Remove existing directory if needed -if os.path.exists(root): - shutil.rmtree(root) - -# Define splits and classes -splits = ["train", "test"] -classes = ["cat", "dog", "bird"] + >>> import os + >>> import shutil + + Temporary root directory: + >>> root = "tmp_data" + + Remove existing directory if needed: + >>> if os.path.exists(root): + ... shutil.rmtree(root) + + Define splits and classes: + >>> splits = ["train", "test"] + >>> classes = ["cat", "dog", "bird"] + + Create directories and dummy files: + >>> for split in splits: + ... for cls in classes: + ... folder_path = os.path.join(root, split, cls) + ... os.makedirs(folder_path) + ... for i in range(2): + ... file_path = os.path.join(folder_path, f"image_{i}.jpg") + ... with open(file_path, "w") as f: + ... f.write("dummy") + + Load a split of the dataset, specifically, the training set: + >>> from deeptrack.sources.folder import ImageFolder + >>> + >>> train_data = ImageFolder(os.path.join(root, "train")) + + >>> len(train_data) + 6 + >>> train_data.classes + ['bird', 'dog', 'cat'] + >>> train_data.path() + 'tmp_data/train/bird/image_0.jpg' -# Create directories and dummy files -for split in splits: - for cls in classes: - folder_path = os.path.join(root, split, cls) - os.makedirs(folder_path) - for i in range(2): - file_path = os.path.join(folder_path, f"image_{i}.jpg") - with open(file_path, "w") as f: - f.write("dummy") - - **Load a split of the dataset** - -from deeptrack.sources.folder import ImageFolder + **Access a source item** -# Load the training set -train_data = ImageFolder(os.path.join(root, "train")) + >>> item = train_data[0] -print(len(train_data)) -print(train_data.classes) -print(train_data.path()) + >>> item["path"] + 'tmp_data/train/bird/image_0.jpg' - **Access a source item** + >>> item["label"] + 0 -item = train_data[0] -print(item["path"]) -print(item["label"]) -print(item["label_name"]) + >>> item["label_name"] + 'bird' **Convert between label names and indices** From 75bf02dc0b530921490b38ac5cc7464e069f59bd Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 17:56:23 +0100 Subject: [PATCH 198/223] Update folder.py --- deeptrack/sources/folder.py | 136 ++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 14 deletions(-) diff --git a/deeptrack/sources/folder.py b/deeptrack/sources/folder.py index 8e650834c..3db6fcd64 100644 --- a/deeptrack/sources/folder.py +++ b/deeptrack/sources/folder.py @@ -47,7 +47,97 @@ Examples -------- -TODO +**Create a dummy dataset structure with train/test subfolders** + +>>> import os +>>> import shutil + +Temporary root directory: +>>> root = "tmp_data" + +Remove existing directory if needed: +>>> if os.path.exists(root): +... shutil.rmtree(root) + +Define splits and classes: +>>> splits = ["train", "test"] +>>> classes = ["cat", "dog", "bird"] + +Create directories and dummy files: +>>> for split in splits: +... for cls in classes: +... folder_path = os.path.join(root, split, cls) +... os.makedirs(folder_path) +... for i in range(2): +... file_path = os.path.join(folder_path, f"image_{i}.jpg") +... with open(file_path, "w") as f: +... f.write("dummy") + +Load a split of the dataset, specifically, the training set: +>>> from deeptrack.sources.folder import ImageFolder +>>> +>>> train_data = ImageFolder(os.path.join(root, "train")) + +>>> len(train_data) +6 +>>> train_data.classes +['bird', 'dog', 'cat'] +>>> train_data.path() +'tmp_data/train/bird/image_0.jpg' + +**Access a source item** + +>>> item = train_data[0] + +>>> item["path"] +'tmp_data/train/bird/image_0.jpg' + +>>> item["label"] +0 + +>>> item["label_name"] +'bird' + +**Convert between label names and indices** + +>>> train_data.name_to_label("cat") +2 + +>>> train_data.label_to_name(0) +'bird' + +**Split the dataset across top-level folders** + +>>> all_data = ImageFolder(root) +>>> train, test = all_data.split("train", "test") + +>>> print(f"Train size: {len(train)}") +Train size: 6 + +>>> print(f"Test size: {len(test)}") +Test size: 6 + +**Print paths in each split** + +Train files: +>>> for item in train: +... print(item["path"]) +tmp_data/train/bird/image_0.jpg +tmp_data/train/bird/image_1.jpg +tmp_data/train/cat/image_0.jpg +tmp_data/train/cat/image_1.jpg +tmp_data/train/dog/image_0.jpg +tmp_data/train/dog/image_1.jpg + +Test files: +>>> for item in test: +... print(item["path"]) +tmp_data/test/bird/image_0.jpg +tmp_data/test/bird/image_1.jpg +tmp_data/test/cat/image_0.jpg +tmp_data/test/cat/image_1.jpg +tmp_data/test/dog/image_0.jpg +tmp_data/test/dog/image_1.jpg """ @@ -169,26 +259,44 @@ class ImageFolder(Source): **Convert between label names and indices** -train_data.name_to_label("cat") -train_data.label_to_name(0) + >>> train_data.name_to_label("cat") + 2 + + >>> train_data.label_to_name(0) + 'bird' **Split the dataset across top-level folders** -all_data = ImageFolder(root) -train, test = all_data.split("train", "test") + >>> all_data = ImageFolder(root) + >>> train, test = all_data.split("train", "test") -print(f"Train size: {len(train)}") -print(f"Test size: {len(test)}") + >>> print(f"Train size: {len(train)}") + Train size: 6 - **Print paths in each split** + >>> print(f"Test size: {len(test)}") + Test size: 6 -print("Train files:") -for item in train: - print(item["path"]) + **Print paths in each split** -print("Test files:") -for item in test: - print(item["path"]) + Train files: + >>> for item in train: + ... print(item["path"]) + tmp_data/train/bird/image_0.jpg + tmp_data/train/bird/image_1.jpg + tmp_data/train/cat/image_0.jpg + tmp_data/train/cat/image_1.jpg + tmp_data/train/dog/image_0.jpg + tmp_data/train/dog/image_1.jpg + + Test files: + >>> for item in test: + ... print(item["path"]) + tmp_data/test/bird/image_0.jpg + tmp_data/test/bird/image_1.jpg + tmp_data/test/cat/image_0.jpg + tmp_data/test/cat/image_1.jpg + tmp_data/test/dog/image_0.jpg + tmp_data/test/dog/image_1.jpg """ From d02d6ab4c8c7804ba855f26ae05ec678e9b2b7ea Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Thu, 10 Jul 2025 17:58:25 +0100 Subject: [PATCH 199/223] Update test_folder.py --- deeptrack/tests/sources/test_folder.py | 77 ++++++++++++++------------ 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/deeptrack/tests/sources/test_folder.py b/deeptrack/tests/sources/test_folder.py index fb0593c22..14ef53ca0 100644 --- a/deeptrack/tests/sources/test_folder.py +++ b/deeptrack/tests/sources/test_folder.py @@ -2,60 +2,69 @@ # pylint: disable=C0116:missing-function-docstring # pylint: disable=C0103:invalid-name -import unittest import os import shutil +import unittest -from deeptrack.sources.folder import ImageFolder +from deeptrack.sources import folder class TestFolder(unittest.TestCase): def setUp(self): - self.root = "temp_test_data" - self.splits = ["train", "val"] + self.root_dir = "temp_test_dir" self.classes = ["cat", "dog", "bird"] - for split in self.splits: - for cls in self.classes: - path = os.path.join(self.root, split, cls) - os.makedirs(path, exist_ok=True) - for i in range(2): - file_path = os.path.join(path, f"img{i}.jpg") - with open(file_path, "w") as f: - f.write("") + os.makedirs(self.root_dir, exist_ok=True) + + for class_name in self.classes: + class_dir = os.path.join(self.root_dir, class_name) + os.makedirs(class_dir, exist_ok=True) + for i in range(3): + file_path = os.path.join(class_dir, f"image_{i}.jpg") + with open(file_path, "w") as f: + f.write("") def tearDown(self): - shutil.rmtree(self.root) + if os.path.exists(self.root_dir): + shutil.rmtree(self.root_dir) def test_ImageFolder_basic(self): - dataset = ImageFolder(os.path.join(self.root, "train")) - self.assertEqual(len(dataset), 6) - self.assertSetEqual(set(dataset.classes), set(self.classes)) - self.assertTrue(all(os.path.isfile(item["path"]) for item in dataset)) - - def test_ImageFolder_split(self): - combined = ImageFolder(self.root) - train, val = combined.split("train", "val") - - self.assertEqual(len(train), 6) - self.assertEqual(len(val), 6) + dataset = folder.ImageFolder(self.root_dir) - train_paths = [item["path"] for item in train] - val_paths = [item["path"] for item in val] + self.assertEqual(len(dataset), 9) + self.assertCountEqual(dataset.classes, self.classes) - self.assertTrue(all("train" in path for path in train_paths)) - self.assertTrue(all("val" in path for path in val_paths)) + def test_ImageFolder_get_category_name(self): + dataset = folder.ImageFolder(self.root_dir) + for item in dataset: + path = item["path"] + name = dataset.get_category_name(path, 0) + self.assertIn(name, self.classes) def test_ImageFolder_label_mapping(self): - dataset = ImageFolder(os.path.join(self.root, "train")) + dataset = folder.ImageFolder(self.root_dir) - for label in dataset["label"]: + for item in dataset: + label = item["label"] name = dataset.label_to_name(label) - self.assertIsInstance(name, str) - self.assertIn(name, dataset.classes) - back_label = dataset.name_to_label(name) - self.assertEqual(label, back_label) + idx = dataset.name_to_label(name) + self.assertEqual(idx, label) + + def test_ImageFolder_split(self): + dataset = folder.ImageFolder(self.root_dir) + + cat_ds, dog_ds = dataset.split("cat", "dog") + + cat_names = set([item["label_name"] for item in cat_ds]) + dog_names = set([item["label_name"] for item in dog_ds]) + + self.assertEqual(cat_names, + {"image_0.jpg", "image_1.jpg", "image_2.jpg"}) + self.assertEqual(dog_names, + {"image_0.jpg", "image_1.jpg", "image_2.jpg"}) + self.assertEqual(len(cat_ds), 3) + self.assertEqual(len(dog_ds), 3) if __name__ == "__main__": From 5cbefb9cbe1c9a2bcf8d9474a112b6bc540f5393 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:22:33 +0200 Subject: [PATCH 200/223] Add docs of /features/__gt__ (#372) * add docs of feature/__gt__ * u * u code example * polish --------- Co-authored-by: Jiacheng Huang --- deeptrack/features.py | 54 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index f5fec7e03..0bfdb7bb8 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3033,12 +3033,62 @@ def __rpow__( return Value(other) >> Power(self) - #TODO ***JH*** def __gt__( self: Feature, other: Any, ) -> Feature: - """Checks if this feature is greater than another using '>'. + """Check if this feature is greater than another using '>'. + + This operator is shorthand for chaining with `GreaterThan`. + The expression: + + >>> feature > other + + is equivalent to: + + >>> feature >> dt.GreaterThan(value=other) + + Internally, this method constructs a new `GreaterThan` feature and + uses the right-shift operator (`>>`) to chain the current feature + into it. + + Parameters + ---------- + other: Any + The value or `Feature` to compare against. It is passed to + `GreaterThan` as the `value` argument. + + Returns + ------- + Feature + A new feature representing the element-wise result of greater-than + comparison between `self` and `other`. + + Examples + -------- + >>> import deeptrack as dt + + Compare each element in a feature to a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature > 2 + >>> result = pipeline() + >>> result + [False, False, True] + + This is equivalent to: + >>> pipeline = feature >> dt.GreaterThan(value=2) + + Compare to a dynamic cutoff that samples values at each call: + >>> import numpy as np + >>> + >>> random_cutoff = dt.Value(value=lambda: np.random.randint(3)) + >>> pipeline = feature > random_cutoff + >>> result = pipeline.update()() + >>> result + [False, True, True] + + This is equivalent to: + >>> pipeline = feature >> dt.GreaterThan(value=random_cutoff) """ From e8d781c8f7c58dc26a3b1cc07ebe7829d8f93147 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:28:41 +0200 Subject: [PATCH 201/223] Add docs of /features/__rgt__ (#373) * Add docs of features/__rgt__ * fix typo * polish * fix error * u * Update features.py --------- Co-authored-by: Jiacheng Huang Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 63 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 0bfdb7bb8..9502175e3 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3094,13 +3094,66 @@ def __gt__( return self >> GreaterThan(other) - #TODO ***JH*** def __rgt__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Checks if another value is greater than this feature using - right '>'. + """Check if another value is greater than feature using right '>'. + + This operator is the right-hand version of `>`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other > feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.GreaterThan(value=feature) + + Internally, this method constructs a `Value` feature from `other` + and chains it into a `GreaterThan` feature. + + Parameters + ---------- + other: Any + A constant or `Feature` to compare against. It is passed as + the `value` argument to `Value`. + + Returns + ------- + Feature + A new feature representing the element-wise result of greater-than + comparison between `other` and `self`. + + Examples + -------- + >>> import deeptrack as dt + + Compare a constant to each element in a feature: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 2 > feature + >>> result = pipeline() + >>> result + [True, False, False] + + This is equivalent to: + >>> pipeline = dt.Value(value=2) >> dt.GreaterThan(value=feature) + + Compare a constant to each element in a dynamic feature that samples + values at each call: + >>> from random import randint + >>> + >>> random = dt.Value(value=lambda: [randint(0, 3) for _ in range(3)]) + >>> pipeline = 2 > random + >>> result = pipeline.update()() + >>> result + [False, False, True] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=2) + ... >> dt.GreaterThan(value=lambda: + ... [randint(0, 3) for _ in range(3)]) + ... ) """ From 3e614c0f72de80abadc0d1ac54c56fc52fac4832 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:31:52 +0200 Subject: [PATCH 202/223] Add docs of /features/__lt__ (#374) * add docs of /features/__lt__ * u --------- Co-authored-by: Jiacheng Huang --- deeptrack/features.py | 58 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 9502175e3..a22287f93 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3159,12 +3159,62 @@ def __rgt__( return Value(other) >> GreaterThan(self) - #TODO ***JH*** def __lt__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Checks if this feature is less than another using '<'. + """Check if this feature is less than another using '<'. + + This operator is shorthand for chaining with `LessThan`. + The expression: + + >>> feature < other + + is equivalent to: + + >>> feature >> dt.LessThan(value=other) + + Internally, this method constructs a new `LessThan` feature and + uses the right-shift operator (`>>`) to chain the current feature + into it. + + Parameters + ---------- + other: Any + The value or `Feature` to compare against. It is passed to + `LessThan` as the `value` argument. + + Returns + ------- + Feature + A new feature representing the element-wise result of less-than + comparison between `self` and `other`. + + Examples + -------- + >>> import deeptrack as dt + + Compare each element in a feature to a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature < 2 + >>> result = pipeline() + >>> result + [True, False, False] + + This is equivalent to: + >>> pipeline = feature >> dt.LessThan(value=2) + + Compare to a dynamic cutoff that samples values at each call: + >>> import numpy as np + >>> + >>> random_cutoff = dt.Value(value=lambda: np.random.randint(3)) + >>> pipeline = feature < random_cutoff + >>> result = pipeline.update()() + >>> result + [False, False, False] + + This is equivalent to: + >>> pipeline = feature >> dt.LessThan(value=random_cutoff) """ From a208162b6cc14a6eff13321712dc2712a08f3810 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:37:04 +0200 Subject: [PATCH 203/223] Add docs of /features/__rlt__ (#375) * add docs of /features/__rlt__ * u * u * Update features.py --------- Co-authored-by: Jiacheng Huang Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 62 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index a22287f93..bfdcd640c 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3220,12 +3220,66 @@ def __lt__( return self >> LessThan(other) - #TODO ***JH*** def __rlt__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Checks if another value is less than this feature using right '<'. + """Check if another value is less than this feature using right '<'. + + This operator is the right-hand version of `<`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other < feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.LessThan(value=feature) + + Internally, this method constructs a `Value` feature from `other` + and chains it into a `LessThan` feature. + + Parameters + ---------- + other: Any + A constant or `Feature` to compare against. It is passed as + the `value` argument to `Value`. + + Returns + ------- + Feature + A new feature representing the element-wise result of less-than + comparison between `other` and `self`. + + Examples + -------- + >>> import deeptrack as dt + + Compare a constant to each element in a feature: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 2 < feature + >>> result = pipeline() + >>> result + [False, False, True] + + This is equivalent to: + >>> pipeline = dt.Value(value=2) >> dt.LessThan(value=feature) + + Compare a constant to each element in a dynamic feature that samples + values at each call: + >>> from random import randint + >>> + >>> random = dt.Value(value=lambda: [randint(0, 3) for _ in range(3)]) + >>> pipeline = 2 < random + >>> result = pipeline.update()() + >>> result + [False, True, False] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=2) + ... >> dt.LessThan(value=lambda: + ... [randint(0, 3) for _ in range(3)]) + ... ) """ From fb34c58ff9f98c4d83f3c3e932ef1e38facdaa03 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 11 Jul 2025 13:37:43 +0100 Subject: [PATCH 204/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index bfdcd640c..e85cc292a 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3282,7 +3282,7 @@ def __rlt__( ... ) """ - + return Value(other) >> LessThan(self) #TODO ***JH*** From cf185f18139710e0efb65bc6df50a53a4d380da8 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:40:00 +0200 Subject: [PATCH 205/223] Add docs of /features/__le__ (#376) * add docs 0f /featuers/__le__ * u --------- Co-authored-by: Jiacheng Huang --- deeptrack/features.py | 58 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index e85cc292a..ffb4e78ec 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3285,12 +3285,62 @@ def __rlt__( return Value(other) >> LessThan(self) - #TODO ***JH*** def __le__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Checks if this feature is less than or equal to another using '<='. + """Check if this feature is less than or equal to another using '<='. + + This operator is shorthand for chaining with `LessThanOrEquals`. + The expression: + + >>> feature <= other + + is equivalent to: + + >>> feature >> dt.LessThanOrEquals(value=other) + + Internally, this method constructs a new `LessThanOrEquals` feature + and uses the right-shift operator (`>>`) to chain the current feature + into it. + + Parameters + ---------- + other: Any + The value or `Feature` to compare against. It is passed to + `LessThanOrEquals` as the `value` argument. + + Returns + ------- + Feature + A new feature representing the element-wise result of + less-than-or-equals comparison between `self` and `other`. + + Examples + -------- + >>> import deeptrack as dt + + Compare each element in a feature to a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature <= 2 + >>> result = pipeline() + >>> result + [True, True, False] + + This is equivalent to: + >>> pipeline = feature >> dt.LessThanOrEquals(value=2) + + Compare to a dynamic cutoff that samples values at each call: + >>> import numpy as np + >>> + >>> random_cutoff = dt.Value(value=lambda: np.random.randint(3)) + >>> pipeline = feature <= random_cutoff + >>> result = pipeline.update()() + >>> result + [False, False, False] + + This is equivalent to: + >>> pipeline = feature >> dt.LessThanOrEquals(value=random_cutoff) """ From 467ea52ca5adc008d2229cd8cf4fddef28ece734 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 11 Jul 2025 13:46:38 +0100 Subject: [PATCH 206/223] Update features.py --- deeptrack/features.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index ffb4e78ec..d938ba5d8 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -216,13 +216,13 @@ def propagate_data_to_dependencies( "Merge", "OneOf", "OneOfDict", - "LoadImage", # TODO **MG** - "SampleToMasks", # TODO **MG** - "AsType", # TODO **MG** + "LoadImage", # TODO ***MG*** + "SampleToMasks", # TODO ***MG*** + "AsType", # TODO ***MG*** "ChannelFirst2d", - "Upscale", # TODO **AL** - "NonOverlapping", # TODO **AL** - "Store", # TODO **JH** + "Upscale", # TODO ***AL*** + "NonOverlapping", # TODO ***AL*** + "Store", # TODO ***JH*** "Squeeze", "Unsqueeze", From 19b0e9cb77713bf9253c31536c80da2b829ef315 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:49:46 +0200 Subject: [PATCH 207/223] Add docs of /features/__rle__ (#377) * add docs of /features/__rle__ * u * u * Update features.py --------- Co-authored-by: Jiacheng Huang Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index d938ba5d8..47d04d7f2 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3346,13 +3346,66 @@ def __le__( return self >> LessThanOrEquals(other) - #TODO ***JH*** def __rle__( self: Feature, - other: Any + other: Any, ) -> Feature: - """Checks if another value is less than or equal to this feature using - right '<='. + """Check if other value is less than or equal to feature using right '<='. + + This operator is the right-hand version of `<=`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other <= feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.LessThanOrEquals(value=feature) + + Internally, this method constructs a `Value` feature from `other` + and chains it into a `LessThanOrEquals` feature. + + Parameters + ---------- + other: Any + A constant or `Feature` to compare against. It is passed as + the `value` argument to `Value`. + + Returns + ------- + Feature + A new feature representing the element-wise result of + less-than-or-equals comparison between `other` and `self`. + + Examples + -------- + >>> import deeptrack as dt + + Compare a constant to each element in a feature: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 2 <= feature + >>> result = pipeline() + >>> result + [False, True, True] + + This is equivalent to: + >>> pipeline = dt.Value(value=2) >> dt.LessThanOrEquals(value=feature) + + Compare a constant to each element in a dynamic feature that samples + values at each call: + >>> from random import randint + >>> + >>> random = dt.Value(value=lambda: [randint(0, 3) for _ in range(3)]) + >>> pipeline = 2 <= random + >>> result = pipeline.update()() + >>> result + [True, False, False] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=2) + ... >> dt.LessThanOrEquals(value=lambda: + ... [randint(0, 3) for _ in range(3)]) + ... ) """ From 128114243672aa2f36acb6ff82a12aa21ff8f984 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 11 Jul 2025 13:50:19 +0100 Subject: [PATCH 208/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 47d04d7f2..fa1dfa4b4 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3350,7 +3350,7 @@ def __rle__( self: Feature, other: Any, ) -> Feature: - """Check if other value is less than or equal to feature using right '<='. + """Check if other is less than or equal to feature using right '<='. This operator is the right-hand version of `<=`, enabling expressions where the `Feature` appears on the right-hand side. The expression: From 21033f6a48a14e43fb2592745cac2290d39e42a6 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 11 Jul 2025 13:50:36 +0100 Subject: [PATCH 209/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index fa1dfa4b4..acbff8f47 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3465,7 +3465,7 @@ def __rand__( """Stacks another value with this feature using right '&'. """ - + return Value(other) >> Stack(self) def __getitem__( From 5418ed5ea849ee919200681f10a52d665327f9eb Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:52:32 +0200 Subject: [PATCH 210/223] add docs of /features/__ge__ (#378) Co-authored-by: Jiacheng Huang --- deeptrack/features.py | 58 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index acbff8f47..ffe2247a9 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3411,14 +3411,64 @@ def __rle__( return Value(other) >> LessThanOrEquals(self) - #TODO ***JH*** def __ge__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Checks if this feature is greater than or equal to another + """Check if this feature is greater than or equal to another using '>='. + This operator is shorthand for chaining with `GreaterThanOrEquals`. + The expression: + + >>> feature >= other + + is equivalent to: + + >>> feature >> dt.GreaterThanOrEquals(value=other) + + Internally, this method constructs a new `GreaterThanOrEquals` feature + and uses the right-shift operator (`>>`) to chain the current feature + into it. + + Parameters + ---------- + other: Any + The value or `Feature` to compare against. It is passed to + `GreaterThanOrEquals` as the `value` argument. + + Returns + ------- + Feature + A new feature representing the element-wise result of + greater-than-or-equals comparison between `self` and `other`. + + Examples + -------- + >>> import deeptrack as dt + + Compare each element in a feature to a constant: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature >= 2 + >>> result = pipeline() + >>> result + [False, True, True] + + This is equivalent to: + >>> pipeline = feature >> dt.GreaterThanOrEquals(value=2) + + Compare to a dynamic cutoff that samples values at each call: + >>> import numpy as np + >>> + >>> random_cutoff = dt.Value(value=lambda: np.random.randint(3)) + >>> pipeline = feature >= random_cutoff + >>> result = pipeline.update()() + >>> result + [True, True, True] + + This is equivalent to: + >>> pipeline = feature >> dt.GreaterThanOrEquals(value=random_cutoff) + """ return self >> GreaterThanOrEquals(other) From bd596df3c9219d789ee22ea1d7291e1d346148d7 Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 11 Jul 2025 13:53:18 +0100 Subject: [PATCH 211/223] Update features.py --- deeptrack/features.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index ffe2247a9..853453a6e 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3415,8 +3415,7 @@ def __ge__( self: Feature, other: Any, ) -> Feature: - """Check if this feature is greater than or equal to another - using '>='. + """Check if this feature is greater than or equal to other using '>='. This operator is shorthand for chaining with `GreaterThanOrEquals`. The expression: From 0a61658f986df2ef654013a179c8ea9765ebccfc Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:54:34 +0200 Subject: [PATCH 212/223] Add docs of /features/__rge__ (#379) * add docs 0f /features/__rge__ * u * u * right , * Update features.py --------- Co-authored-by: Jiacheng Huang Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 66 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 853453a6e..2decc27fc 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3472,13 +3472,69 @@ def __ge__( return self >> GreaterThanOrEquals(other) - #TODO ***JH*** def __rge__( - self: Feature, - other: Any + self: Feature, + other: Any, ) -> Feature: - """Checks if another value is greater than or equal to this feature - using right '>='. + """Check if other is greater than or equal to feature using right '>='. + + This operator is the right-hand version of `>=`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other >= feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.GreaterThanOrEquals(value=feature) + + Internally, this method constructs a `Value` feature from `other` + and chains it into a `GreaterThanOrEquals` feature. + + Parameters + ---------- + other: Any + A constant or `Feature` to compare against. It is passed as + the `value` argument to `Value`. + + Returns + ------- + Feature + A new feature representing the element-wise result of + greater-than-or-equals comparison between `other` and `self`. + + Examples + -------- + >>> import deeptrack as dt + + Compare a constant to each element in a feature: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = 2 >= feature + >>> result = pipeline() + >>> result + [True, True, False] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=2) + ... >> dt.GreaterThanOrEquals(value=feature) + ... ) + + Compare a constant to each element in a dynamic feature that samples + values at each call: + >>> from random import randint + >>> + >>> random = dt.Value(value=lambda: [randint(0, 3) for _ in range(3)]) + >>> pipeline = 2 >= random + >>> result = pipeline.update()() + >>> result + [True, False, True] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=2) + ... >> dt.GreaterThanOrEquals(value=lambda: + ... [randint(0, 3) for _ in range(3)]) + ... ) """ From 838f76636e3536d2e7adfc75a0687e160a9db83d Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 14:57:04 +0200 Subject: [PATCH 213/223] Add docs of /features/__xor__ (#382) * add docs of /features/__xor__ * remove blank spaces --------- Co-authored-by: Jiacheng Huang --- deeptrack/features.py | 55 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 2decc27fc..54e7c2159 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3540,13 +3540,60 @@ def __rge__( return Value(other) >> GreaterThanOrEquals(self) - #TODO ***JH*** def __xor__( self: Feature, - other: Any, + other: int, ) -> Feature: - """Repeats the feature a given number of times using '^'. - + """Repeat the feature a given number of times using '^'. + + This operator is shorthand for chaining with `Repeat`. The expression: + + >>> feature ^ other + + is equivalent to: + + >>> dt.Repeat(feature, N=other) + + Internally, this method constructs a new `Repeat` feature taking + `self` and `other` as argument. + + Parameters + ---------- + other: int + The int value representing the repeat times. It is passed to + `Repeat` as the `N` argument. + + Returns + ------- + Feature + A new feature that applies `self` repeatedly `other` times. + + Examples + -------- + >>> import deeptrack as dt + + Repeat the `Add` feature by 3 times: + >>> add_ten = dt.Add(value=10) + >>> pipeline = add_ten ^ 3 + >>> result = pipeline([1, 2, 3]) + >>> result + [31, 32, 33] + + This is equivalent to: + >>> pipeline = dt.Repeat(add_ten, N=3) + + Repeat by random times that samples values at each call: + >>> import numpy as np + >>> + >>> random_times = dt.Value(value=lambda: np.random.randint(10)) + >>> pipeline = add_ten ^ random_times + >>> result = pipeline.update()([1, 2, 3]) + >>> result + [81, 82, 83] + + This is equivalent to: + >>> pipeline = dt.Repeat(add_ten, N=random_times) + """ return Repeat(self, other) From 7f643b7dbbea8a16c33633c153f5ede4391667ca Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:00:40 +0200 Subject: [PATCH 214/223] Add docs of /features/__rand__ (#381) * add docs of /features/__rand__ * Update features.py --------- Co-authored-by: Jiacheng Huang Co-authored-by: Giovanni Volpe <46021832+giovannivolpe@users.noreply.github.com> --- deeptrack/features.py | 55 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 54e7c2159..635635194 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3609,12 +3609,63 @@ def __and__( return self >> Stack(other) - #TODO ***JH*** def __rand__( self: Feature, other: Any, ) -> Feature: - """Stacks another value with this feature using right '&'. + """Stack another value with this feature using right '&'. + + This operator is the right-hand version of `&`, enabling expressions + where the `Feature` appears on the right-hand side. The expression: + + >>> other & feature + + is equivalent to: + + >>> dt.Value(value=other) >> dt.Stack(value=feature) + + Internally, this method constructs a `Value` feature from `other` + and chains it into a `Stack` feature. + + Parameters + ---------- + other: Any + The value or `Feature` to stack with `self`. + + Returns + ------- + Feature + A new feature containing all elements from `other` and `self`. + + Examples + -------- + >>> import deeptrack as dt + + Stack with the fixed data: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = [4, 5, 6] & feature + >>> result = pipeline() + >>> result + [4, 5, 6, 1, 2, 3] + + This is equivalent to: + >>> pipeline = dt.Value(value=[4, 5, 6]) >> dt.Stack(value=feature) + + Stack with the dynamic data that samples values at each call: + >>> from random import randint + >>> + >>> random = dt.Value(value=lambda: [randint(0, 3) for _ in range(3)]) + >>> pipeline = random & feature + >>> result = pipeline.update()() + >>> result + [0, 3, 1, 1, 2, 3] + + This is equivalent to: + >>> pipeline = ( + ... dt.Value(value=lambda: + ... [randint(0, 3) for _ in range(3)]) + ... >> dt.Stack(value=feature) + ... ) """ From 32e4e904c7c1651ec70adada6c4c9daaa4fd1019 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang <31703396+JChonpca@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:01:12 +0200 Subject: [PATCH 215/223] add docs of /features/__and__ (#380) Co-authored-by: Jiacheng Huang --- deeptrack/features.py | 50 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 635635194..d675e7518 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3598,13 +3598,59 @@ def __xor__( return Repeat(self, other) - #TODO ***JH*** def __and__( self: Feature, other: Any, ) -> Feature: - """Stacks this feature with another using '&'. + """Stack this feature with another using '&'. + This operator is shorthand for chaining with `Stack`. The expression: + + >>> feature & other + + is equivalent to: + + >>> feature >> dt.Stack(value=other) + + Internally, this method constructs a new `Stack` feature and uses the + right-shift operator (`>>`) to chain the current feature into it. + + Parameters + ---------- + other: Any + The value or `Feature` to stack with `self`. + + Returns + ------- + Feature + A new feature containing all elements from `self` and `other`. + + Examples + -------- + >>> import deeptrack as dt + + Stack with the fixed data: + >>> feature = dt.Value(value=[1, 2, 3]) + >>> pipeline = feature & [4, 5, 6] + >>> result = pipeline() + >>> result + [1, 2, 3, 4, 5, 6] + + This is equivalent to: + >>> pipeline = feature >> dt.Stack(value=[4, 5, 6]) + + Stack with the dynamic data that samples values at each call: + >>> from random import randint + >>> + >>> random = dt.Value(value=lambda: [randint(0,3) for _ in range(3)]) + >>> pipeline = feature & random + >>> result = pipeline.update()() + >>> result + [1, 2, 3, 3, 1, 3] + + This is equivalent to: + >>> pipeline = feature >> dt.Stack(value=random) + """ return self >> Stack(other) From d97196c7c628a3766b5188d3af3de641d65aa3df Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 11 Jul 2025 14:02:40 +0100 Subject: [PATCH 216/223] Update features.py --- deeptrack/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index d675e7518..30702a61e 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -3642,7 +3642,7 @@ def __and__( Stack with the dynamic data that samples values at each call: >>> from random import randint >>> - >>> random = dt.Value(value=lambda: [randint(0,3) for _ in range(3)]) + >>> random = dt.Value(value=lambda: [randint(0, 3) for _ in range(3)]) >>> pipeline = feature & random >>> result = pipeline.update()() >>> result From 1503c4b9bb31f0df34cc22cb4c52a399acdaffea Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Mon, 28 Jul 2025 07:56:30 +0200 Subject: [PATCH 217/223] Update /features/Store --- deeptrack/features.py | 25 +++++++++++++++---------- deeptrack/tests/test_features.py | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index a4bf2c709..8813e5cdb 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -222,7 +222,7 @@ def propagate_data_to_dependencies( "ChannelFirst2d", "Upscale", # TODO ***AL*** "NonOverlapping", # TODO ***AL*** - "Store", # TODO ***JH*** + "Store", "Squeeze", "Unsqueeze", "ExpandDims", @@ -8815,9 +8815,9 @@ class Store(Feature): The feature to evaluate and store. key: Any The key used to identify the stored output. - replace: bool, optional - If `True`, replaces the stored value with a new computation. It defaults - to `False`. + replace: PropertyLike[bool], optional + If `True`, replaces the stored value with the current computation. It + defaults to `False`. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. @@ -8852,22 +8852,27 @@ class Store(Feature): >>> cached_output = store_feature(None, key="example", replace=False) >>> print(cached_output == output) True + >>> print(cached_output == value_feature()) + True + Retrieve the stored value recomputing: >>> value_feature.update() >>> cached_output = store_feature(None, key="example", replace=True) >>> print(cached_output == output) False + >>> print(cached_output == value_feature()) + False """ __distributed__: bool = False def __init__( - self: Store, + self: Feature, feature: Feature, key: Any, - replace: bool = False, + replace: PropertyLike[bool] = False, **kwargs: Any, ): """Initialize the Store feature. @@ -8878,7 +8883,7 @@ def __init__( The feature to evaluate and store. key: Any The key used to identify the stored output. - replace: bool, optional + replace: PropertyLike[bool], optional If `True`, replaces the stored value with a new computation. It defaults to `False`. **kwargs:: dict of str to Any @@ -8891,10 +8896,10 @@ def __init__( self._store: dict[Any, Image] = {} def get( - self: Store, + self: Feature, _: Any, key: Any, - replace: bool, + replace: PropertyLike[bool], **kwargs: Any, ) -> Any: """Evaluate and store the feature output, or return the cached result. @@ -8905,7 +8910,7 @@ def get( Placeholder for unused image input. key: Any The key used to identify the stored output. - replace: bool + replace: PropertyLike[bool] If `True`, replaces the stored value with a new computation. **kwargs: Any Additional keyword arguments passed to the feature. diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 591547a68..5d6439b1f 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2408,10 +2408,33 @@ def test_Store(self): value_feature.update() cached_output = store_feature(None, key="example", replace=False) self.assertEqual(cached_output, output) + self.assertNotEqual(cached_output, value_feature()) value_feature.update() cached_output = store_feature(None, key="example", replace=True) self.assertNotEqual(cached_output, output) + self.assertEqual(cached_output, value_feature()) + + if TORCH_AVAILABLE: + + value_feature = features.Value(lambda: torch.rand(1)) + + store_feature = features.Store(feature=value_feature, key="example") + + output = store_feature(None, key="example", replace=False) + + value_feature.update() + cached_output = store_feature(None, key="example", replace=False) + torch.testing.assert_close(cached_output, output) + with self.assertRaises(AssertionError): + torch.testing.assert_close(cached_output, value_feature()) + + value_feature.update() + cached_output = store_feature(None, key="example", replace=True) + with self.assertRaises(AssertionError): + torch.testing.assert_close(cached_output, output) + torch.testing.assert_close(cached_output, value_feature()) + def test_Squeeze(self): From a6edfdcaad0513566abf617f7aff5b90e747722d Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Mon, 28 Jul 2025 09:25:41 +0200 Subject: [PATCH 218/223] u --- deeptrack/features.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 8813e5cdb..db1083743 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -8804,9 +8804,9 @@ def _resample_volume_position( class Store(Feature): """Store the output of a feature for reuse. - The `Store` feature evaluates a given feature and stores its output in an - internal dictionary. Subsequent calls with the same key will return the - stored value unless the `replace` parameter is set to `True`. This enables + The `Store` feature evaluates a given feature and stores its output in an + internal dictionary. Subsequent calls with the same key will return the + stored value unless the `replace` parameter is set to `True`. This enables caching and reuse of computed feature outputs. Parameters @@ -8853,7 +8853,7 @@ class Store(Feature): >>> print(cached_output == output) True >>> print(cached_output == value_feature()) - True + False Retrieve the stored value recomputing: @@ -8862,7 +8862,7 @@ class Store(Feature): >>> print(cached_output == output) False >>> print(cached_output == value_feature()) - False + True """ From caa3268e03ac762529173e840baa51473a0fb091 Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Mon, 28 Jul 2025 09:27:03 +0200 Subject: [PATCH 219/223] uu --- deeptrack/features.py | 1 - 1 file changed, 1 deletion(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index db1083743..017858b15 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -8855,7 +8855,6 @@ class Store(Feature): >>> print(cached_output == value_feature()) False - Retrieve the stored value recomputing: >>> value_feature.update() >>> cached_output = store_feature(None, key="example", replace=True) From f127868166bd9c061755ec5dd29efb5c0b74f126 Mon Sep 17 00:00:00 2001 From: mirjagranfors Date: Tue, 29 Jul 2025 09:15:40 +0200 Subject: [PATCH 220/223] minor typos --- deeptrack/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 017858b15..8ad6d4246 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -8818,7 +8818,7 @@ class Store(Feature): replace: PropertyLike[bool], optional If `True`, replaces the stored value with the current computation. It defaults to `False`. - **kwargs:: dict of str to Any + **kwargs: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. Attributes @@ -8883,7 +8883,7 @@ def __init__( key: Any The key used to identify the stored output. replace: PropertyLike[bool], optional - If `True`, replaces the stored value with a new computation. + If `True`, replaces the stored value with a new computation. It defaults to `False`. **kwargs:: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. From 272f22ac6d2ae5d3b8e6f8c4fe4071acf8becca7 Mon Sep 17 00:00:00 2001 From: mirjagranfors Date: Tue, 29 Jul 2025 09:21:54 +0200 Subject: [PATCH 221/223] formatting --- deeptrack/tests/test_features.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 5d6439b1f..94dd21f2e 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2419,7 +2419,9 @@ def test_Store(self): value_feature = features.Value(lambda: torch.rand(1)) - store_feature = features.Store(feature=value_feature, key="example") + store_feature = features.Store( + feature=value_feature, key="example" + ) output = store_feature(None, key="example", replace=False) From ca4ec8bf0bea27f9ca2612b5c1c6544a8ae0a64c Mon Sep 17 00:00:00 2001 From: Jiacheng Huang Date: Tue, 29 Jul 2025 15:06:34 +0200 Subject: [PATCH 222/223] uuuu --- deeptrack/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index 8ad6d4246..fc6c13dde 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -8898,7 +8898,7 @@ def get( self: Feature, _: Any, key: Any, - replace: PropertyLike[bool], + replace: bool, **kwargs: Any, ) -> Any: """Evaluate and store the feature output, or return the cached result. @@ -8909,7 +8909,7 @@ def get( Placeholder for unused image input. key: Any The key used to identify the stored output. - replace: PropertyLike[bool] + replace: bool If `True`, replaces the stored value with a new computation. **kwargs: Any Additional keyword arguments passed to the feature. From 6b323b57e8cd9cdf2536f5f63dc5074ebd2f8f9f Mon Sep 17 00:00:00 2001 From: Giovanni Volpe Date: Fri, 5 Sep 2025 16:51:26 +0200 Subject: [PATCH 223/223] Update features.py --- deeptrack/features.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deeptrack/features.py b/deeptrack/features.py index fc6c13dde..8d1eab219 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -8868,7 +8868,7 @@ class Store(Feature): __distributed__: bool = False def __init__( - self: Feature, + self: Store, feature: Feature, key: Any, replace: PropertyLike[bool] = False, @@ -8895,7 +8895,7 @@ def __init__( self._store: dict[Any, Image] = {} def get( - self: Feature, + self: Store, _: Any, key: Any, replace: bool, @@ -8922,7 +8922,7 @@ def get( """ # Check if the value should be recomputed or retrieved from the store - if replace or not (key in self._store): + if replace or not key in self._store: self._store[key] = self.feature() # Return the stored or newly computed result

9-SrB#UG4E7N0b|@@_^sLD_X;z` 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 ItM`0T|@& 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