Skip to content

Commit

Permalink
feat: Add find_edges method in Image (#531)
Browse files Browse the repository at this point in the history
Closes #523 

### Summary of Changes

Added find_edges method for the new image class. Works just like the
find_edges method from the old Image class with pillow.
Fixed images with one channel for both repr and both to_file methods.
Added grayscale images that only contain one channel each.
Re-enabled Image.adjust_color_balance & Image.find_edges in the Image
tutorial.
Added warning for adjust_color_balance if it is used on a grayscale
image.

---------

Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
  • Loading branch information
Marsmaennchen221 and megalinter-bot committed Jan 21, 2024
1 parent dba23f9 commit d728eb6
Show file tree
Hide file tree
Showing 99 changed files with 183 additions and 28 deletions.
4 changes: 2 additions & 2 deletions docs/tutorials/image_processing.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
"execution_count": null,
"outputs": [],
"source": [
"# plane.adjust_color_balance(0.5)"
"plane.adjust_color_balance(0.5)"
],
"metadata": {
"collapsed": false
Expand Down Expand Up @@ -280,7 +280,7 @@
"execution_count": null,
"outputs": [],
"source": [
"# plane.find_edges()\n"
"plane.find_edges()\n"
],
"metadata": {
"collapsed": false
Expand Down
67 changes: 63 additions & 4 deletions src/safeds/data/image/containers/_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class Image:

_pil_to_tensor = PILToTensor()
_default_device = _get_device()
_FILTER_EDGES_KERNEL = (
torch.tensor([[-1.0, -1.0, -1.0], [-1.0, 8.0, -1.0], [-1.0, -1.0, -1.0]])
.unsqueeze(dim=0)
.unsqueeze(dim=0)
.to(_default_device)
)

@staticmethod
def from_file(path: str | Path, device: Device = _default_device) -> Image:
Expand Down Expand Up @@ -116,7 +122,10 @@ def _repr_jpeg_(self) -> bytes | None:
if self.channel == 4:
return None
buffer = io.BytesIO()
save_image(self._image_tensor.to(torch.float32) / 255, buffer, format="jpeg")
if self.channel == 1:
func2.to_pil_image(self._image_tensor, mode="L").save(buffer, format="jpeg")
else:
save_image(self._image_tensor.to(torch.float32) / 255, buffer, format="jpeg")
buffer.seek(0)
return buffer.read()

Expand All @@ -130,7 +139,10 @@ def _repr_png_(self) -> bytes:
The image as PNG.
"""
buffer = io.BytesIO()
save_image(self._image_tensor.to(torch.float32) / 255, buffer, format="png")
if self.channel == 1:
func2.to_pil_image(self._image_tensor, mode="L").save(buffer, format="png")
else:
save_image(self._image_tensor.to(torch.float32) / 255, buffer, format="png")
buffer.seek(0)
return buffer.read()

Expand Down Expand Up @@ -213,7 +225,10 @@ def to_jpeg_file(self, path: str | Path) -> None:
if self.channel == 4:
raise IllegalFormatError("png")
Path(path).parent.mkdir(parents=True, exist_ok=True)
save_image(self._image_tensor.to(torch.float32) / 255, path, format="jpeg")
if self.channel == 1:
func2.to_pil_image(self._image_tensor, mode="L").save(path, format="jpeg")
else:
save_image(self._image_tensor.to(torch.float32) / 255, path, format="jpeg")

def to_png_file(self, path: str | Path) -> None:
"""
Expand All @@ -225,7 +240,10 @@ def to_png_file(self, path: str | Path) -> None:
The path to the PNG file.
"""
Path(path).parent.mkdir(parents=True, exist_ok=True)
save_image(self._image_tensor.to(torch.float32) / 255, path, format="png")
if self.channel == 1:
func2.to_pil_image(self._image_tensor, mode="L").save(path, format="png")
else:
save_image(self._image_tensor.to(torch.float32) / 255, path, format="png")

# ------------------------------------------------------------------------------------------------------------------
# Transformations
Expand Down Expand Up @@ -457,6 +475,12 @@ def adjust_color_balance(self, factor: float) -> Image:
UserWarning,
stacklevel=2,
)
elif self.channel == 1:
warnings.warn(
"Color adjustment will not have an affect on grayscale images with only one channel.",
UserWarning,
stacklevel=2,
)
return Image(
self.convert_to_grayscale()._image_tensor * (1.0 - factor * 1.0) + self._image_tensor * (factor * 1.0),
device=self.device,
Expand Down Expand Up @@ -568,3 +592,38 @@ def rotate_left(self) -> Image:
The image rotated 90 degrees counter-clockwise.
"""
return Image(func2.rotate(self._image_tensor, 90, expand=True), device=self.device)

def find_edges(self) -> Image:
"""
Return a grayscale version of the image with the edges highlighted.
The original image is not modified.
Returns
-------
result : Image
The image with edges found.
"""
kernel = (
Image._FILTER_EDGES_KERNEL
if self.device.type == Image._default_device
else Image._FILTER_EDGES_KERNEL.to(self.device)
)
edges_tensor = torch.clamp(
torch.nn.functional.conv2d(
self.convert_to_grayscale()._image_tensor.float()[0].unsqueeze(dim=0),
kernel,
padding="same",
).squeeze(dim=1),
0,
255,
).to(torch.uint8)
if self.channel == 3:
return Image(edges_tensor.repeat(3, 1, 1), device=self.device)
elif self.channel == 4:
return Image(
torch.cat([edges_tensor.repeat(3, 1, 1), self._image_tensor[3].unsqueeze(dim=0)]),
device=self.device,
)
else:
return Image(edges_tensor, device=self.device)
Binary file added tests/resources/image/grayscale.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/resources/image/grayscale.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d728eb6

Please sign in to comment.