diff --git a/deeptrack/features.py b/deeptrack/features.py index a4bf2c70..f1acc5be 100644 --- a/deeptrack/features.py +++ b/deeptrack/features.py @@ -230,7 +230,7 @@ def propagate_data_to_dependencies( "Transpose", "Permute", "OneHot", - "TakeProperties", #TODO ***JH*** + "TakeProperties", ] @@ -9440,11 +9440,11 @@ def get( class TakeProperties(Feature): """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 + Only extracts the properties if the feature contains all given + property-names. The order of the properties is not guaranteed to be the same as the evaluation order. - If there is only a single property name, this will return a list of the + If there is only a single property name, this will return a list of the property values. Parameters @@ -9453,7 +9453,7 @@ class TakeProperties(Feature): The feature from which to extract properties. names: list[str] The names of the properties to extract - **kwargs:: dict of str to Any + **kwargs: dict of str to Any Additional keyword arguments passed to the parent `Feature` class. Attributes @@ -9462,18 +9462,18 @@ class TakeProperties(Feature): Indicates whether this feature distributes computation across inputs. Always `False` for `TakeProperties`, as it processes sequentially. __list_merge_strategy__: int - Specifies how lists of properties are merged. Set to + Specifies how lists of properties are merged. Set to `MERGE_STRATEGY_APPEND` to append values to the result list. Methods ------- `get(image: Any, names: tuple[str, ...], **kwargs: dict[str, Any]) -> np.ndarray | tuple[np.ndarray, ...]` Extract the specified properties from the feature pipeline. - + Examples -------- >>> import deeptrack as dt - + >>> class ExampleFeature(Feature): ... def __init__(self, my_property, **kwargs): ... super().__init__(my_property=my_property, **kwargs) @@ -9489,7 +9489,7 @@ class TakeProperties(Feature): Create a `Gaussian` feature: >>> noise_feature = dt.Gaussian(mu=7, sigma=12) - + Use `TakeProperties` to extract the property: >>> take_properties = dt.TakeProperties(noise_feature) >>> output = take_properties.get(image=None, names=["mu"]) @@ -9502,9 +9502,9 @@ class TakeProperties(Feature): __list_merge_strategy__: int = MERGE_STRATEGY_APPEND def __init__( - self: TakeProperties, + self: Feature, feature: Feature, - *names: str, + *names: PropertyLike[str], **kwargs: Any, ): """Initialize the TakeProperties feature. @@ -9513,23 +9513,23 @@ def __init__( ---------- feature: Feature The feature from which to extract properties. - *names: str + *names: PropertyLike[str] One or more names of the properties to extract. -= **kwargs: Any, optional + **kwargs: Any, optional Additional keyword arguments passed to the parent `Feature` class. - + """ super().__init__(names=names, **kwargs) self.feature = self.add_feature(feature) def get( - self: TakeProperties, + self: Feature, image: Any, names: tuple[str, ...], _ID: tuple[int, ...] = (), **kwargs: Any, - ) -> np.ndarray | tuple[np.ndarray, ...]: + ) -> NDArray | tuple[NDArray, torch.Tensor, ...]: """Extract the specified properties from the feature pipeline. This method retrieves the values of the specified properties from the @@ -9549,7 +9549,7 @@ def get( Returns ------- - np.ndarray or tuple[np.ndarray, ...] + NDArray or tuple[NDArray, torch.Tensor, ...] If a single property name is provided, a NumPy array containing the property values is returned. If multiple property names are provided, a tuple of NumPy arrays is returned, where each array @@ -9578,8 +9578,8 @@ def get( if key[:len(_ID)] == _ID: res[name].append(value.current_value()) - # Convert the results to NumPy arrays. - res = tuple([np.array(res[name]) for name in names]) + # Convert the results to tuple. + res = tuple([res[name] for name in names]) # Return a single array if only one property name is specified. if len(res) == 1: diff --git a/deeptrack/tests/test_features.py b/deeptrack/tests/test_features.py index 591547a6..96de58e7 100644 --- a/deeptrack/tests/test_features.py +++ b/deeptrack/tests/test_features.py @@ -2678,23 +2678,59 @@ def __init__(self, my_property, **kwargs): output = take_properties.get(image=None, names=["my_property"]) self.assertEqual(output, [42]) - # with `Gaussian` feature + # with `Gaussian` feature noise_feature = Gaussian(mu=7, sigma=12) - + take_properties = features.TakeProperties(noise_feature) output = take_properties.get(image=None, names=["mu"]) self.assertEqual(output, [7]) output = take_properties.get(image=None, names=["sigma"]) self.assertEqual(output, [12]) - # with `Gaussian` feature - noise_feature = Gaussian(mu=7, sigma=12) - + # with `Gaussian` feature with float properties + noise_feature = Gaussian(mu=7.123, sigma=12.123) + take_properties = features.TakeProperties(noise_feature) - output = take_properties.get(image=None, names=["mu"]) - self.assertEqual(output, [7]) - output = take_properties.get(image=None, names=["sigma"]) - self.assertEqual(output, [12]) + output = take_properties.get(image=None, names=["mu", "sigma"]) + self.assertEqual(output, ([7.123], [12.123])) + self.assertEqual(output[0][0], 7.123) + self.assertEqual(output[1][0], 12.123) + + ### Test with PyTorch tensor (if available) + if TORCH_AVAILABLE: + class ExampleFeature(features.Feature): + def __init__(self, my_property, **kwargs): + super().__init__(my_property=my_property, **kwargs) + + feature = ExampleFeature(my_property= + properties.Property(torch.tensor(42.123))) + + take_properties = features.TakeProperties(feature) + take_properties = features.TakeProperties(feature) + output = take_properties.get(image=None, names=["my_property"]) + torch.testing.assert_close(output[0], torch.tensor(42.123)) + + # with `Gaussian` feature + noise_feature = Gaussian( + mu=torch.tensor(7), sigma=torch.tensor(12) + ) + + take_properties = features.TakeProperties(noise_feature) + output = take_properties.get(image=None, names=["mu"]) + torch.testing.assert_close(output[0], torch.tensor(7)) + output = take_properties.get(image=None, names=["sigma"]) + torch.testing.assert_close(output[0], torch.tensor(12)) + + # with `Gaussian` feature with float properties + random_mu = torch.rand(1) + random_sigma = torch.rand(1) + noise_feature = Gaussian(mu=random_mu, sigma=random_sigma) + + take_properties = features.TakeProperties(noise_feature) + output = take_properties.get(image=None, names=["mu", "sigma"]) + torch.testing.assert_close(output, ([random_mu], [random_sigma])) + torch.testing.assert_close(output[0][0], random_mu) + torch.testing.assert_close(output[1][0], random_sigma) if __name__ == "__main__": diff --git a/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb b/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb index ef9e7d2d..464b37c5 100644 --- a/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb +++ b/tutorials/3-advanced-topics/DTAT391B_sources.folder.ipynb @@ -72,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -174,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 32, "metadata": {}, "outputs": [], "source": [ @@ -430,6 +430,13 @@ "List all image paths in train:" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Prepare an example path to one of the files:" + ] + }, { "cell_type": "code", "execution_count": 15,