Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Image API #1562

Merged
merged 47 commits into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7feaf52
initial commit for image api
edgarriba Feb 6, 2022
39f00dd
implement normalize/denormalize
edgarriba Feb 27, 2022
76d11ed
add more tests
edgarriba Feb 28, 2022
c9539aa
add docs
edgarriba Feb 28, 2022
f6300ce
remove normalize and polish api
edgarriba Mar 5, 2022
54f1cbe
add docs
edgarriba Mar 5, 2022
6c482de
polishing gray functionality
edgarriba Mar 5, 2022
3f22d91
more polishing
edgarriba Mar 6, 2022
2737f48
implement onxx tests
edgarriba Mar 6, 2022
28d5f06
rename ImageColor, and add contrib.io
edgarriba Mar 20, 2022
4c92b62
image as non-tensor
edgarriba Mar 24, 2022
fe8f6bf
magix functions
edgarriba Mar 26, 2022
8669dcd
rename ImagePrompter to VisualPrompter
edgarriba Apr 29, 2023
8d34619
Merge branch 'master' into feat/image_api
edgarriba May 27, 2023
762ec63
Merge branch 'master' into feat/image_api
edgarriba May 30, 2023
8ae1bea
[pre-commit.ci] pre-commit suggestions (#2394)
pre-commit-ci[bot] May 30, 2023
a5f973a
remove artifacts
edgarriba May 31, 2023
fd60abe
Merge branch 'master' into feat/image_api
edgarriba May 31, 2023
60952ee
remove requirements
edgarriba May 31, 2023
ac3ab33
Merge branch 'master' into feat/image_api
edgarriba Jul 10, 2023
aefdcea
simplify base class
edgarriba Jul 10, 2023
30ab7e9
add TensorWrapper
edgarriba Jul 10, 2023
6c499ee
fix docs generation
edgarriba Jul 10, 2023
3fcaec2
fix test
edgarriba Jul 12, 2023
0c57bb4
recover setup
edgarriba Jul 12, 2023
d211998
more fixes
edgarriba Jul 12, 2023
c7e2a86
implement write
edgarriba Jul 12, 2023
2379134
Merge branch 'master' into feat/image_api
edgarriba Jul 12, 2023
9621d72
add docs
edgarriba Jul 12, 2023
9e9e5ff
fix backend
edgarriba Jul 12, 2023
cf69857
fix backend
edgarriba Jul 12, 2023
009ec77
fix mypy
edgarriba Jul 12, 2023
fbf8daa
Fix write shape
edgarriba Jul 13, 2023
a5aa2cb
apply code review
edgarriba Jul 13, 2023
d631498
artifact
edgarriba Jul 13, 2023
58ba513
fix docstring
edgarriba Jul 13, 2023
af2030b
fix annotations
edgarriba Jul 13, 2023
2a83848
skip test because of dlpack
edgarriba Jul 13, 2023
53f7893
Update kornia/image/image.py
edgarriba Jul 13, 2023
0d9d455
simplify
edgarriba Jul 13, 2023
dd9c050
fix doctest
edgarriba Jul 13, 2023
ed5f92f
fix doctest
edgarriba Jul 13, 2023
c31123c
fix doctest
edgarriba Jul 13, 2023
21728c2
fix build docs
edgarriba Jul 13, 2023
089f648
iterate on the pixel format definition
edgarriba Jul 15, 2023
7e5ff6d
Update kornia/image/base.py
edgarriba Jul 16, 2023
42a1509
adjust test tolerance
edgarriba Jul 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions kornia/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
augmentation,
color,
contrib,
core,
enhance,
feature,
losses,
Expand Down
4 changes: 4 additions & 0 deletions kornia/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from ._backend import Tensor
from .image import Image, ImageColor

__all__ = ["Tensor", "Image", "ImageColor"]
4 changes: 4 additions & 0 deletions kornia/core/_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# File containing the backend that defaults to Pytorch.
import torch

Tensor = torch.Tensor
136 changes: 136 additions & 0 deletions kornia/core/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
from enum import Enum
from typing import List, Tuple, Union

from kornia.color import bgr_to_grayscale, bgr_to_rgb, rgb_to_bgr, rgb_to_grayscale
from kornia.core import Tensor
from kornia.geometry.transform import hflip, vflip
from kornia.utils import image_to_tensor, tensor_to_image


class ImageColor(Enum):
GRAY = 0
RGB = 1
BGR = 2


class Image(Tensor):
edgarriba marked this conversation as resolved.
Show resolved Hide resolved
_color = ImageColor.RGB

@staticmethod
def __new__(cls, data, color, *args, **kwargs):
return Tensor._make_subclass(cls, data)

def __init__(self, data: Tensor, color: ImageColor) -> None:
self._color = color

@property
def valid(self) -> bool:
edgarriba marked this conversation as resolved.
Show resolved Hide resolved
# TODO: find a better way to do this
return self.data.data_ptr is not None

@property
def is_batch(self) -> bool:
return len(self.data.shape) > 3

@property
def channels(self) -> int:
edgarriba marked this conversation as resolved.
Show resolved Hide resolved
return self.data.shape[-3]

@property
def height(self) -> int:
return self.data.shape[-2]

@property
def resolution(self) -> Tuple[int, ...]:
return tuple(self.data.shape[-2:])

@property
def width(self) -> int:
return self.data.shape[-1]

@property
def color(self) -> ImageColor:
return self._color

@classmethod
def from_tensor(cls, data: Tensor, color: ImageColor) -> 'Image':
return cls(data, color)

# TODO: possibly call torch.as_tensor
@classmethod
def from_numpy(cls, data, color: ImageColor = ImageColor.RGB) -> 'Image':
data_t: Tensor = image_to_tensor(data)
return cls(data_t, color)

def to_numpy(self):
return tensor_to_image(self.data, keepdim=True)

# TODO: possibly call torch.as_tensor
@classmethod
def from_list(cls, data: List[List[Union[float, int]]], color: ImageColor) -> 'Image':
return cls(Tensor(data), color)

@classmethod
def from_file(cls, file_path: str) -> 'Image':
raise NotImplementedError("not implemented yet.")

# TODO: implement with another logic
def _to_grayscale(self, data: Tensor) -> Tensor:
if self.color == ImageColor.GRAY:
out = data
elif self.color == ImageColor.RGB:
out = rgb_to_grayscale(data)
elif self.color == ImageColor.BGR:
out = bgr_to_grayscale(data)
else:
raise NotImplementedError(f"Unsupported color: {self.color}.")
return out

def grayscale(self) -> 'Image':
gray = self._to_grayscale(self.data)
return Image(gray, ImageColor.GRAY)

def grayscale_(self) -> 'Image':
self.data = self._to_grayscale(self.data)
self._color = ImageColor.GRAY
return self

def _convert(self, color: ImageColor) -> Tuple[Tensor, ImageColor]:
if color == ImageColor.RGB:
if self.color == ImageColor.BGR:
data_out = bgr_to_rgb(self.data)
color_out = ImageColor.RGB
elif color == ImageColor.BGR:
if self.color == ImageColor.RGB:
data_out = rgb_to_bgr(self.data)
color_out = ImageColor.BGR
elif color == ImageColor.GRAY:
if self.color == ImageColor.BGR:
data_out = bgr_to_grayscale(self.data)
color_out = ImageColor.GRAY
elif self.color == ImageColor.RGB:
data_out = rgb_to_grayscale(self.data)
color_out = ImageColor.GRAY
else:
raise NotImplementedError(f"Unsupported color: {self.color}.")
return data_out, color_out

def convert(self, color: ImageColor) -> 'Image':
data, color = self._convert(color)
return Image(data, color)

def convert_(self, color: ImageColor) -> 'Image':
self.data, self._color = self._convert(color)
return self

def hflip(self) -> 'Image':
edgarriba marked this conversation as resolved.
Show resolved Hide resolved
data = hflip(self.data)
return Image(data, self.color)

def vflip(self) -> 'Image':
data = vflip(self.data)
return Image(data, self.color)

# TODO: add the methods we need
# - lab, hsv, ...
# - erode, dilate, ...
75 changes: 75 additions & 0 deletions test/core/test_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import numpy as np
import pytest
import torch

from kornia.color.gray import rgb_to_grayscale
from kornia.core import Image, ImageColor
from kornia.testing import assert_close


class TestImage:
@pytest.mark.parametrize("dtype", [torch.uint8, torch.float32, torch.float64])
def test_from_tensor(self, device, dtype):
data = torch.ones(3, 4, 5, device=device, dtype=dtype)
img = Image.from_tensor(data, color=ImageColor.RGB)
assert isinstance(img, Image)
assert img.channels == 3
assert img.height == 4
assert img.width == 5
assert img.color == ImageColor.RGB
assert not img.is_batch
assert img.shape == (3, 4, 5)
assert img.dtype == dtype
assert img.device == device

def test_from_numpy(self, device, dtype):
data = np.ones((4, 5, 3))
img = Image.from_numpy(data, color=ImageColor.RGB)
assert isinstance(img, Image)
assert img.channels == 3
assert img.height == 4
assert img.width == 5
assert img.color == ImageColor.RGB
assert not img.is_batch
# check clone
img2 = img.clone()
assert isinstance(img2, Image)
img2 = img2.to(device, dtype)
assert img2.dtype == dtype
assert img2.device == device
img3 = img2.to(torch.uint8)
assert isinstance(img3, Image)
assert img3.dtype == torch.uint8
assert img3.device == device

def test_from_list(self):
data = [[[1.0, 2.0], [3.0, 4.0]]]
img = Image.from_list(data, color=ImageColor.GRAY)
assert isinstance(img, Image)
assert img.channels == 1
assert img.height == 2
assert img.width == 2
assert img.color == ImageColor.GRAY
assert not img.is_batch

def test_grayscale(self):
data = torch.rand(3, 4, 5)
img = Image.from_tensor(data, color=ImageColor.RGB)
img_gray = img.grayscale()
assert isinstance(img, Image)
assert isinstance(img_gray, Image)
assert img_gray.channels == 1
assert img_gray.height == 4
assert img_gray.width == 5
assert img_gray.color == ImageColor.GRAY
assert not img.is_batch
assert_close(img_gray, rgb_to_grayscale(img))
# inplace
img = img.grayscale_()
assert isinstance(img, Image)
assert img.channels == 1
assert img.height == 4
assert img.width == 5
assert img.color == ImageColor.GRAY
assert not img.is_batch
assert_close(img_gray, img)