Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 128 additions & 21 deletions deeptrack/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -1249,46 +1249,44 @@ def __init__(
super().__init__(np.mean, ksize=ksize, **kwargs)


#TODO ***AL*** revise MaxPooling - torch, typing, docstring, unit test
class MaxPooling(Pool):
"""Apply max-pooling to images.

This class reduces the resolution of an image by dividing it into
non-overlapping blocks of size `ksize` and applying the max function to
each block. The result is a downsampled image where each pixel value
`MaxPooling` reduces the resolution of an image by dividing it into
non-overlapping blocks of size `ksize` and applying the `max` function
to each block. The result is a downsampled image where each pixel value
represents the maximum value within the corresponding block of the
original image.
This is useful for reducing the size of an image while retaining the
most significant features.
original image. This is useful for reducing the size of an image while
retaining the most significant features.

If the backend is NumPy, the downsampling is performed using
`skimage.measure.block_reduce`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing line space


If the backend is PyTorch, the downsampling is performed using
`torch.nn.functional.max_pool2d`.

Parameters
----------
ksize: int
Size of the pooling kernel.
cval: number
Value to pad edges with if necessary. Default 0.
func_kwargs: dict
**kwargs: Any
Additional parameters sent to the pooling function.

Examples
--------
>>> import deeptrack as dt

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space after this

>>> import numpy as np

Create an input image:
>>> import numpy as np
>>>
>>> input_image = np.random.rand(32, 32)

Define a max-pooling feature:
Define and use a max-pooling feature:

>>> max_pooling = dt.MaxPooling(ksize=8)
>>> output_image = max_pooling(input_image)
>>> print(output_image.shape)
(8, 8)

Notes
-----
Calling this feature returns a `np.ndarray` by default. If
`store_properties` is set to `True`, the returned array will be
automatically wrapped in an `Image` object. This behavior is handled
internally and does not affect the return type of the `get()` method.
>>> output_image.shape
(4, 4)

"""

Expand All @@ -1312,6 +1310,115 @@ def __init__(

super().__init__(np.max, ksize=ksize, **kwargs)

def get(
self: MaxPooling,
image: NDArray[Any] | torch.Tensor,
ksize: int=3,
**kwargs: Any,
) -> NDArray[Any] | torch.Tensor:
"""Max-pooling of input.

Checks the current backend and chooses the appropriate function to pool
the input image, either `._get_torch()` or `._get_numpy()`.

Parameters
----------
image: array or tensor
Input array or tensor be pooled.
ksize: int
Kernel size of the pooling operation.

Returns
-------
array or tensor
The pooled input as `NDArray` or `torch.Tensor` depending on
the backend.

"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add space after """


if self.get_backend() == "numpy":
return self._get_numpy(image, ksize, **kwargs)

if self.get_backend() == "torch":
return self._get_torch(image, ksize, **kwargs)

raise NotImplementedError(f"Backend {self.backend} not supported")

def _get_numpy(
self: MaxPooling,
image: NDArray[Any],
ksize: int=3,
**kwargs: Any,
) -> NDArray[Any]:
"""Max-pooling pooling with the NumPy backend enabled.

Returns the result of the input array passed to the scikit image
`block_reduce()` function with `np.max()` as the pooling function.

Parameters
----------
image: array
Input array to be pooled.
ksize: int
Kernel size of the pooling operation.

Returns
-------

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use array and NumPy array

array
The pooled image as a NumPy array.

"""

return utils.safe_call(
skimage.measure.block_reduce,
image=image,
func=np.max,
block_size=ksize,
**kwargs,
)

def _get_torch(
self: MaxPooling,
image: torch.Tensor,
ksize: int=3,
**kwargs: Any,
) -> torch.Tensor:
"""Max-pooling with the PyTorch backend enabled.


Returns the result of the tensor passed to a PyTorch max
pooling layer.

Parameters
----------
image: torch.Tensor
Input tensor to be pooled.
ksize: int
Kernel size of the pooling operation.

Returns
-------
torch.Tensor
The pooled image as a `torch.Tensor`.

"""

# If input tensor is 2D
if len(image.shape) == 2:
# Add batch dimension for max-pooling
expanded_image = image.unsqueeze(0)

pooled_image = torch.nn.functional.max_pool2d(
expanded_image, kernel_size=ksize,
)
# Remove the expanded dim
return pooled_image.squeeze(0)

return torch.nn.functional.max_pool2d(
image,
kernel_size=ksize,
)


class MinPooling(Pool):
"""Apply min-pooling to images.
Expand Down
15 changes: 13 additions & 2 deletions deeptrack/tests/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,21 @@ def test_Blur(self):
#input_image = xp.asarray(np.array([[1, 2], [3, 4]], dtype=float))
#expected_output = xp.asarray(np.array([[1, 1.5], [2, 2.5]]))

#eature = math.Blur(filter_function=uniform_filter, size=2)
#feature = math.Blur(filter_function=uniform_filter, size=2)
#blurred_image = feature.resolve(input_image)
#self.assertTrue(xp.all(blurred_image == expected_output))


def test_MaxPooling(self):
input_image = xp.asarray([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=float)
feature = math.MaxPooling(ksize=2)
pooled_image = feature.resolve(input_image)

expected = xp.asarray([[6.0, 8.0]], dtype=float)

self.assertTrue(xp.all(pooled_image == expected))
self.assertEqual(pooled_image.shape, (1, 2))

def test_MinPooling(self):
input_image = xp.asarray([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=float)
feature = math.MinPooling(ksize=2)
Expand Down Expand Up @@ -123,7 +134,7 @@ def test_MaxPooling(self):
input_image = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
feature = math.MaxPooling(ksize=2)
pooled_image = feature.resolve(input_image)
self.assertTrue(np.all(pooled_image == [[5, 6], [8, 9]]))
self.assertTrue(xp.all(pooled_image == xp.asarray([[5, 6], [8, 9]]) ) )

def test_MinPooling(self):
input_image = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
Expand Down
Loading