-
Notifications
You must be signed in to change notification settings - Fork 57
AL/math/MaxPooling #407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
AL/math/MaxPooling #407
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1249,15 +1249,16 @@ 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 | ||
represents the maximum value within the corresponding block of the | ||
original image. | ||
This class inherits from `Pool` to reduce 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. If the backend is torch, it will return the | ||
output of `torch.nn.functional.max_pool2d` instead. | ||
|
||
This is useful for reducing the size of an image while retaining the | ||
most significant features. | ||
|
||
|
@@ -1267,7 +1268,7 @@ class MaxPooling(Pool): | |
Size of the pooling kernel. | ||
cval: number | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is "cval" used anywhere? |
||
Value to pad edges with if necessary. Default 0. | ||
func_kwargs: dict | ||
**kwargs: dict | ||
Additional parameters sent to the pooling function. | ||
|
||
Examples | ||
|
@@ -1285,10 +1286,12 @@ class MaxPooling(Pool): | |
|
||
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. | ||
Calling this feature returns a pooled image of the input, it will return | ||
either numpy or torch depending on the backend. If | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line break a bit too early, but I don't know if that matters |
||
`store_properties` is set to `True` and the input is a numpy array, | ||
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. | ||
|
||
""" | ||
|
||
|
@@ -1299,7 +1302,8 @@ def __init__( | |
): | ||
"""Initialize the parameters for max pooling. | ||
|
||
This constructor initializes the parameters for max pooling. | ||
This constructor initializes the parameters for max pooling and checks | ||
whether to use the numpy or torch implementation, defaults to numpy. | ||
|
||
Parameters | ||
---------- | ||
|
@@ -1309,9 +1313,103 @@ def __init__( | |
Additional keyword arguments. | ||
|
||
""" | ||
|
||
super().__init__(np.max, ksize=ksize, **kwargs) | ||
|
||
def _get_numpy( | ||
self, | ||
image: NDArray, | ||
ksize: int=3, | ||
**kwargs, | ||
): | ||
"""Method to perform average pooling with the numpy backend enabled. | ||
|
||
Returns the result of the image passed to the scikit image block_reduce | ||
function with `np.max()` as the pooling function. | ||
|
||
Parameters | ||
---------- | ||
image: NDArray | ||
Input image to be pooled. | ||
ksize: int | ||
Kernel size of the pooling operation. | ||
|
||
Returns | ||
------- | ||
NDArray | ||
The pooled image as a `NDArray`. | ||
|
||
""" | ||
return utils.safe_call( | ||
skimage.measure.block_reduce, | ||
image=image, | ||
func=self.pooling, # This will be np.mean for this class. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure about the np.mean? |
||
block_size=ksize, | ||
**kwargs, | ||
) | ||
|
||
def _get_torch( | ||
self, | ||
image: torch.Tensor, | ||
ksize: int=3, | ||
**kwargs, | ||
): | ||
"""Method to perform max pooling with the torch backend enabled. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. empty blank spaces |
||
Returns the result of the image passed to a torch max | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To early line break |
||
pooling layer. | ||
|
||
Parameters | ||
---------- | ||
image: torch.Tensor | ||
Input image to be pooled. | ||
ksize: int | ||
Kernel size of the pooling operation. | ||
|
||
Returns | ||
------- | ||
torch.Tensor | ||
The pooled image as a `torch.Tensor`. | ||
|
||
""" | ||
|
||
return torch.nn.functional.max_pool2d( | ||
image, | ||
kernel_size=ksize, | ||
) | ||
|
||
def get( | ||
self, | ||
image: NDArray | torch.Tensor, | ||
ksize: int=3, | ||
**kwargs, | ||
): | ||
"""Method to perform pooling with either torch or numpy backend. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blank spaces |
||
Checks the current backend and chooses the appropriate function to pool | ||
the input image, either `_get_torch` or `_get_numpy`. | ||
|
||
Parameters | ||
---------- | ||
image: NDArray | torch.Tensor | ||
Input image to be pooled. | ||
ksize: int | ||
Kernel size of the pooling operation. | ||
|
||
Returns | ||
------- | ||
NDArray | torch.Tensor | ||
The pooled image as `NDArray` or `torch.Tensor` depending on | ||
the backend. | ||
|
||
""" | ||
if self.get_backend() == "numpy": | ||
return self._get_numpy(image, ksize, **kwargs,) | ||
elif self.get_backend() == "torch": | ||
return self._get_torch(image, ksize, **kwargs,) | ||
else: | ||
raise NotImplementedError(f"Backend {self.backend} not supported") | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One line too much between the classes I think |
||
|
||
#TODO ***AL*** revise MinPooling - torch, typing, docstring, unit test | ||
class MinPooling(Pool): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,12 +82,26 @@ def test_Blur(self): | |
#blurred_image = feature.resolve(input_image) | ||
#self.assertTrue(xp.all(blurred_image == expected_output)) | ||
|
||
def test_MaxPooling(self): | ||
input_image = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=float) | ||
feature = math.MaxPooling(ksize=2) | ||
pooled_image = feature.resolve(input_image) | ||
self.assertTrue(np.all(pooled_image == [[6.0, 8.0]])) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here you can also add a check for the shape |
||
|
||
# Extending the test and setting the backend to torch | ||
@unittest.skipUnless(TORCH_AVAILABLE, "PyTorch is not installed.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see that you haven't created the layout like this. However, it looks different from how we have done it in test_features.py for example. I personally prefer the style we use in test_feature, as all tests belonging to one class are within the same "def test_....()" |
||
class TestMath_Torch(TestMath_Numpy): | ||
BACKEND = "torch" | ||
pass | ||
|
||
def test_MaxPooling(self): | ||
input_image = torch.tensor([[[ [1.0, 2.0, 3.0, 4.0], | ||
[5.0, 6.0, 7.0, 8.0] ]]]) | ||
feature = math.MaxPooling(ksize=2) | ||
pooled_image = feature(input_image, ksize=2) | ||
expected = torch.tensor([[[[6.0, 8.0]]]]) | ||
self.assertEqual(pooled_image.shape, expected.shape) | ||
self.assertTrue(torch.allclose(pooled_image, expected)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would also add a test to check that the output is still a torch.tensor |
||
|
||
|
||
class TestMath(unittest.TestCase): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that it is clear from this explanation what the difference is between using the class with numpy and using it with torch. Are the first two sentences only valid in the when using numpy? Or also when using torch?