Skip to content
Snippets Groups Projects
Commit 821d7e81 authored by André Anjos's avatar André Anjos :speech_balloon:
Browse files

[data.transforms] Remove bob.core dependence; Test 16-bit auto-level transform

parent cce6be2a
No related branches found
No related tags found
1 merge request!12Streamlining
include README.rst buildout.cfg COPYING version.txt requirements.txt include README.rst buildout.cfg COPYING version.txt requirements.txt
recursive-include doc *.rst *.png *.ico *.txt recursive-include doc *.rst *.png *.ico *.txt
recursive-include bob *.json recursive-include bob *.json *.png
...@@ -18,8 +18,6 @@ import PIL.Image ...@@ -18,8 +18,6 @@ import PIL.Image
import torchvision.transforms import torchvision.transforms
import torchvision.transforms.functional import torchvision.transforms.functional
import bob.core
class TupleMixin: class TupleMixin:
"""Adds support to work with tuples of objects to torchvision transforms""" """Adds support to work with tuples of objects to torchvision transforms"""
...@@ -104,12 +102,17 @@ class SingleAutoLevel16to8: ...@@ -104,12 +102,17 @@ class SingleAutoLevel16to8:
To auto-level, we calculate the maximum and the minimum of the image, and To auto-level, we calculate the maximum and the minimum of the image, and
consider such a range should be mapped to the [0,255] range of the consider such a range should be mapped to the [0,255] range of the
destination image. destination image.
""" """
def __call__(self, img): def __call__(self, img):
imin, imax = img.getextrema()
irange = imax - imin
return PIL.Image.fromarray( return PIL.Image.fromarray(
bob.core.convert(img, "uint8", (0, 255), img.getextrema()) numpy.round(
) 255.0 * (numpy.array(img).astype(float) - imin) / irange
).astype("uint8"),
).convert("L")
class AutoLevel16to8(TupleMixin, SingleAutoLevel16to8): class AutoLevel16to8(TupleMixin, SingleAutoLevel16to8):
...@@ -121,6 +124,7 @@ class AutoLevel16to8(TupleMixin, SingleAutoLevel16to8): ...@@ -121,6 +124,7 @@ class AutoLevel16to8(TupleMixin, SingleAutoLevel16to8):
consider such a range should be mapped to the [0,255] range of the consider such a range should be mapped to the [0,255] range of the
destination image. destination image.
""" """
pass pass
...@@ -132,6 +136,7 @@ class SingleToRGB: ...@@ -132,6 +136,7 @@ class SingleToRGB:
defaults. This may be aggressive if applied to 16-bit images without defaults. This may be aggressive if applied to 16-bit images without
further considerations. further considerations.
""" """
def __call__(self, img): def __call__(self, img):
return img.convert(mode="RGB") return img.convert(mode="RGB")
...@@ -195,8 +200,8 @@ class RandomRotation(torchvision.transforms.RandomRotation): ...@@ -195,8 +200,8 @@ class RandomRotation(torchvision.transforms.RandomRotation):
""" """
def __init__(self, p=0.5, **kwargs): def __init__(self, p=0.5, **kwargs):
kwargs.setdefault('degrees', 15) kwargs.setdefault("degrees", 15)
kwargs.setdefault('resample', PIL.Image.BILINEAR) kwargs.setdefault("resample", PIL.Image.BILINEAR)
super(RandomRotation, self).__init__(**kwargs) super(RandomRotation, self).__init__(**kwargs)
self.p = p self.p = p
...@@ -205,16 +210,17 @@ class RandomRotation(torchvision.transforms.RandomRotation): ...@@ -205,16 +210,17 @@ class RandomRotation(torchvision.transforms.RandomRotation):
if random.random() < self.p: if random.random() < self.p:
angle = self.get_params(self.degrees) angle = self.get_params(self.degrees)
return [ return [
torchvision.transforms.functional.rotate(img, angle, torchvision.transforms.functional.rotate(
self.resample, self.expand, self.center) img, angle, self.resample, self.expand, self.center
)
for img in args for img in args
] ]
else: else:
return args return args
def __repr__(self): def __repr__(self):
retval = super(RandomRotation, self).__repr__() retval = super(RandomRotation, self).__repr__()
return retval.replace('(', f'(p={self.p},', 1) return retval.replace("(", f"(p={self.p},", 1)
class ColorJitter(torchvision.transforms.ColorJitter): class ColorJitter(torchvision.transforms.ColorJitter):
...@@ -243,10 +249,10 @@ class ColorJitter(torchvision.transforms.ColorJitter): ...@@ -243,10 +249,10 @@ class ColorJitter(torchvision.transforms.ColorJitter):
""" """
def __init__(self, p=0.5, **kwargs): def __init__(self, p=0.5, **kwargs):
kwargs.setdefault('brightness', 0.3) kwargs.setdefault("brightness", 0.3)
kwargs.setdefault('contrast', 0.3) kwargs.setdefault("contrast", 0.3)
kwargs.setdefault('saturation', 0.02) kwargs.setdefault("saturation", 0.02)
kwargs.setdefault('hue', 0.02) kwargs.setdefault("hue", 0.02)
super(ColorJitter, self).__init__(**kwargs) super(ColorJitter, self).__init__(**kwargs)
self.p = p self.p = p
...@@ -259,4 +265,4 @@ class ColorJitter(torchvision.transforms.ColorJitter): ...@@ -259,4 +265,4 @@ class ColorJitter(torchvision.transforms.ColorJitter):
def __repr__(self): def __repr__(self):
retval = super(ColorJitter, self).__repr__() retval = super(ColorJitter, self).__repr__()
return retval.replace('(', f'(p={self.p},', 1) return retval.replace("(", f"(p={self.p},", 1)
...@@ -4,7 +4,10 @@ ...@@ -4,7 +4,10 @@
import random import random
import nose.tools import nose.tools
import pkg_resources
import numpy import numpy
import PIL.Image
import torch import torch
import torchvision.transforms.functional import torchvision.transforms.functional
...@@ -93,7 +96,7 @@ def test_pad_default(): ...@@ -93,7 +96,7 @@ def test_pad_default():
# checks that the border introduced with padding is all about "fill" # checks that the border introduced with padding is all about "fill"
img_t = numpy.array(img_t) img_t = numpy.array(img_t)
img_t[idx] = 0 img_t[idx] = 0
border_size_plane = (img_t[:,:,0].size - numpy.array(img)[:,:,0].size) border_size_plane = img_t[:, :, 0].size - numpy.array(img)[:, :, 0].size
nose.tools.eq_(img_t.sum(), 0) nose.tools.eq_(img_t.sum(), 0)
gt_t = numpy.array(gt_t) gt_t = numpy.array(gt_t)
...@@ -131,8 +134,8 @@ def test_pad_2tuple(): ...@@ -131,8 +134,8 @@ def test_pad_2tuple():
# checks that the border introduced with padding is all about "fill" # checks that the border introduced with padding is all about "fill"
img_t = numpy.array(img_t) img_t = numpy.array(img_t)
img_t[idx] = 0 img_t[idx] = 0
border_size_plane = (img_t[:,:,0].size - numpy.array(img)[:,:,0].size) border_size_plane = img_t[:, :, 0].size - numpy.array(img)[:, :, 0].size
expected_sum = sum((fill[k]*border_size_plane) for k in range(3)) expected_sum = sum((fill[k] * border_size_plane) for k in range(3))
nose.tools.eq_(img_t.sum(), expected_sum) nose.tools.eq_(img_t.sum(), expected_sum)
gt_t = numpy.array(gt_t) gt_t = numpy.array(gt_t)
...@@ -170,8 +173,8 @@ def test_pad_4tuple(): ...@@ -170,8 +173,8 @@ def test_pad_4tuple():
# checks that the border introduced with padding is all about "fill" # checks that the border introduced with padding is all about "fill"
img_t = numpy.array(img_t) img_t = numpy.array(img_t)
img_t[idx] = 0 img_t[idx] = 0
border_size_plane = (img_t[:,:,0].size - numpy.array(img)[:,:,0].size) border_size_plane = img_t[:, :, 0].size - numpy.array(img)[:, :, 0].size
expected_sum = sum((fill[k]*border_size_plane) for k in range(3)) expected_sum = sum((fill[k] * border_size_plane) for k in range(3))
nose.tools.eq_(img_t.sum(), expected_sum) nose.tools.eq_(img_t.sum(), expected_sum)
gt_t = numpy.array(gt_t) gt_t = numpy.array(gt_t)
...@@ -194,7 +197,7 @@ def test_resize_downscale_w(): ...@@ -194,7 +197,7 @@ def test_resize_downscale_w():
img, gt, mask = [_create_img(im_size) for i in range(3)] img, gt, mask = [_create_img(im_size) for i in range(3)]
nose.tools.eq_(img.size, (im_size[2], im_size[1])) # confirms the above nose.tools.eq_(img.size, (im_size[2], im_size[1])) # confirms the above
img_t, gt_t, mask_t = transforms(img, gt, mask) img_t, gt_t, mask_t = transforms(img, gt, mask)
new_size = (new_size, (new_size*im_size[1])/im_size[2]) new_size = (new_size, (new_size * im_size[1]) / im_size[2])
nose.tools.eq_(img_t.size, new_size) nose.tools.eq_(img_t.size, new_size)
nose.tools.eq_(gt_t.size, new_size) nose.tools.eq_(gt_t.size, new_size)
nose.tools.eq_(mask_t.size, new_size) nose.tools.eq_(mask_t.size, new_size)
...@@ -224,8 +227,8 @@ def test_crop(): ...@@ -224,8 +227,8 @@ def test_crop():
# test # test
idx = ( idx = (
slice(crop_size[0], crop_size[0]+crop_size[2]), slice(crop_size[0], crop_size[0] + crop_size[2]),
slice(crop_size[1], crop_size[1]+crop_size[3]), slice(crop_size[1], crop_size[1] + crop_size[3]),
slice(0, im_size[0]), slice(0, im_size[0]),
) )
transforms = Crop(*crop_size) transforms = Crop(*crop_size)
...@@ -297,7 +300,7 @@ def test_rotation(): ...@@ -297,7 +300,7 @@ def test_rotation():
assert numpy.any(numpy.array(img1_t) != numpy.array(img)) assert numpy.any(numpy.array(img1_t) != numpy.array(img))
# asserts two random transforms are not the same # asserts two random transforms are not the same
img_t2, = transforms(img) (img_t2,) = transforms(img)
assert numpy.any(numpy.array(img_t2) != numpy.array(img1_t)) assert numpy.any(numpy.array(img_t2) != numpy.array(img1_t))
...@@ -327,15 +330,40 @@ def test_color_jitter(): ...@@ -327,15 +330,40 @@ def test_color_jitter():
def test_compose(): def test_compose():
transforms = Compose([ transforms = Compose(
RandomVerticalFlip(p=1), [
RandomHorizontalFlip(p=1), RandomVerticalFlip(p=1),
RandomVerticalFlip(p=1), RandomHorizontalFlip(p=1),
RandomHorizontalFlip(p=1), RandomVerticalFlip(p=1),
]) RandomHorizontalFlip(p=1),
]
)
img, gt, mask = [_create_img((3, 24, 42)) for i in range(3)] img, gt, mask = [_create_img((3, 24, 42)) for i in range(3)]
img_t, gt_t, mask_t = transforms(img, gt, mask) img_t, gt_t, mask_t = transforms(img, gt, mask)
assert numpy.all(numpy.array(img_t) == numpy.array(img)) assert numpy.all(numpy.array(img_t) == numpy.array(img))
assert numpy.all(numpy.array(gt_t) == numpy.array(gt)) assert numpy.all(numpy.array(gt_t) == numpy.array(gt))
assert numpy.all(numpy.array(mask_t) == numpy.array(mask)) assert numpy.all(numpy.array(mask_t) == numpy.array(mask))
def test_16bit_autolevel():
test_image_path = pkg_resources.resource_filename(
__name__, "testimg-16bit.png"
)
# the way to load a 16-bit PNG image correctly, according to:
# https://stackoverflow.com/questions/32622658/read-16-bit-png-image-file-using-python
# https://github.com/python-pillow/Pillow/issues/3011
img = PIL.Image.fromarray(
numpy.array(
PIL.Image.open("bob/ip/binseg/test/testimg-16bit.png")
).astype("uint16")
)
nose.tools.eq_(img.mode, "I;16")
nose.tools.eq_(img.getextrema(), (0, 65281))
timg = SingleAutoLevel16to8()(img)
nose.tools.eq_(timg.mode, "L")
nose.tools.eq_(timg.getextrema(), (0, 255))
#timg.show()
#import ipdb; ipdb.set_trace()
bob/ip/binseg/test/testimg-16bit.png

1.38 KiB

...@@ -25,21 +25,21 @@ requirements: ...@@ -25,21 +25,21 @@ requirements:
host: host:
- python {{ python }} - python {{ python }}
- setuptools {{ setuptools }} - setuptools {{ setuptools }}
- torchvision {{ torchvision }} # [linux]
- pytorch {{ pytorch }} # [linux]
- numpy {{ numpy }} - numpy {{ numpy }}
- h5py {{ h5py }}
- pytorch {{ pytorch }} # [linux]
- torchvision {{ torchvision }} # [linux]
- bob.extension - bob.extension
- bob.core
- bob.io.base
run: run:
- python - python
- setuptools - setuptools
- {{ pin_compatible('numpy') }}
- {{ pin_compatible('pillow') }}
- {{ pin_compatible('pandas') }}
- {{ pin_compatible('matplotlib') }}
- {{ pin_compatible('pytorch') }} # [linux] - {{ pin_compatible('pytorch') }} # [linux]
- {{ pin_compatible('torchvision') }} # [linux] - {{ pin_compatible('torchvision') }} # [linux]
- {{ pin_compatible('numpy') }} - {{ pin_compatible('h5py') }}
- pandas
- pillow
- matplotlib
- tqdm - tqdm
- tabulate - tabulate
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment