Skip to content
This repository was archived by the owner on Sep 18, 2024. It is now read-only.

Add functionality to read 32-bit images #242

Merged
merged 8 commits into from
Sep 26, 2019
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
24 changes: 16 additions & 8 deletions keras_preprocessing/image/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,18 @@ def load_img(path, grayscale=False, color_mode='rgb', target_size=None,
# Arguments
path: Path to image file.
grayscale: DEPRECATED use `color_mode="grayscale"`.
color_mode: One of "grayscale", "rgb", "rgba". Default: "rgb".
The desired image format.
color_mode: The desired image format. One of "grayscale", "rgb", "rgba".
"grayscale" supports 8-bit images and 32-bit signed integer images.
Default: "rgb".
target_size: Either `None` (default to original size)
or tuple of ints `(img_height, img_width)`.
interpolation: Interpolation method used to resample the image if the
target size is different from that of the loaded image.
Supported methods are "nearest", "bilinear", and "bicubic".
If PIL version 1.1.3 or newer is installed, "lanczos" is also
supported. If PIL version 3.4.0 or newer is installed, "box" and
"hamming" are also supported. By default, "nearest" is used.
"hamming" are also supported.
Default: "nearest".

# Returns
A PIL Image instance.
Expand All @@ -109,7 +111,9 @@ def load_img(path, grayscale=False, color_mode='rgb', target_size=None,
'The use of `load_img` requires PIL.')
img = pil_image.open(path)
if color_mode == 'grayscale':
if img.mode != 'L':
# if image is not already an 8-bit, 16-bit or 32-bit grayscale image
# convert it to an 8-bit grayscale image.
if img.mode not in ('L', 'I;16', 'I'):
img = img.convert('L')
elif color_mode == 'rgba':
if img.mode != 'RGBA':
Expand Down Expand Up @@ -228,11 +232,12 @@ def array_to_img(x, data_format='channels_last', scale=True, dtype='float32'):

# Arguments
x: Input Numpy array.
data_format: Image data format.
either "channels_first" or "channels_last".
scale: Whether to rescale image values
to be within `[0, 255]`.
data_format: Image data format, either "channels_first" or "channels_last".
Default: "channels_last".
scale: Whether to rescale image values to be within `[0, 255]`.
Default: True.
dtype: Dtype to use.
Default: "float32".

# Returns
A PIL Image instance.
Expand Down Expand Up @@ -271,6 +276,9 @@ def array_to_img(x, data_format='channels_last', scale=True, dtype='float32'):
return pil_image.fromarray(x.astype('uint8'), 'RGB')
elif x.shape[2] == 1:
# grayscale
if np.max(x) > 255:
# 32-bit signed integer grayscale image. PIL mode "I"
return pil_image.fromarray(x[:, :, 0].astype('int32'), 'I')
return pil_image.fromarray(x[:, :, 0].astype('uint8'), 'L')
else:
raise ValueError('Unsupported channel number: %s' % (x.shape[2],))
Expand Down
32 changes: 22 additions & 10 deletions tests/image/directory_iterator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,35 @@ def all_test_images():
rgb_images = []
rgba_images = []
gray_images = []
gray_images_16bit = []
gray_images_32bit = []
for n in range(8):
bias = np.random.rand(img_w, img_h, 1) * 64
variance = np.random.rand(img_w, img_h, 1) * (255 - 64)
# RGB
imarray = np.random.rand(img_w, img_h, 3) * variance + bias
im = Image.fromarray(imarray.astype('uint8')).convert('RGB')
rgb_images.append(im)

# RGBA
imarray = np.random.rand(img_w, img_h, 4) * variance + bias
im = Image.fromarray(imarray.astype('uint8')).convert('RGBA')
rgba_images.append(im)

# 8-bit grayscale
imarray = np.random.rand(img_w, img_h, 1) * variance + bias
im = Image.fromarray(
imarray.astype('uint8').squeeze()).convert('L')
im = Image.fromarray(imarray.astype('uint8').squeeze()).convert('L')
gray_images.append(im)
# 16-bit grayscale
imarray = np.array(
np.random.randint(-2147483648, 2147483647, (img_w, img_h))
)
im = Image.fromarray(imarray.astype('uint16'))
gray_images_16bit.append(im)
# 32-bit grayscale
im = Image.fromarray(imarray.astype('uint32'))
gray_images_32bit.append(im)

return [rgb_images, rgba_images, gray_images]
return [rgb_images, rgba_images,
gray_images, gray_images_16bit, gray_images_32bit]


def test_directory_iterator(all_test_images, tmpdir):
Expand Down Expand Up @@ -101,15 +113,15 @@ def preprocessing_function(x):
color_mode='rgb',
batch_size=3,
class_mode='categorical')
assert len(dir_seq) == np.ceil(count / 3)
assert len(dir_seq) == np.ceil(count / 3.)
x1, y1 = dir_seq[1]
assert x1.shape == (3, 26, 26, 3)
assert y1.shape == (3, num_classes)
x1, y1 = dir_seq[5]
assert (x1 == 0).all()

with pytest.raises(ValueError):
x1, y1 = dir_seq[9]
x1, y1 = dir_seq[14] # there are 40 images and batch size is 3


def test_directory_iterator_class_mode_input(all_test_images, tmpdir):
Expand Down Expand Up @@ -140,9 +152,9 @@ def test_directory_iterator_class_mode_input(all_test_images, tmpdir):


@pytest.mark.parametrize('validation_split,num_training', [
(0.25, 18),
(0.50, 12),
(0.75, 6),
(0.25, 30),
(0.50, 20),
(0.75, 10),
])
def test_directory_iterator_with_validation_split(all_test_images,
validation_split,
Expand Down
125 changes: 125 additions & 0 deletions tests/image/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def test_validate_filename(tmpdir):
def test_load_img(tmpdir):
filename_rgb = str(tmpdir / 'rgb_utils.png')
filename_rgba = str(tmpdir / 'rgba_utils.png')
filename_grayscale_8bit = str(tmpdir / 'grayscale_8bit_utils.png')
filename_grayscale_16bit = str(tmpdir / 'grayscale_16bit_utils.tiff')
filename_grayscale_32bit = str(tmpdir / 'grayscale_32bit_utils.tiff')

original_rgb_array = np.array(255 * np.random.rand(100, 100, 3),
dtype=np.uint8)
Expand All @@ -31,6 +34,26 @@ def test_load_img(tmpdir):
original_rgba = utils.array_to_img(original_rgba_array, scale=False)
original_rgba.save(filename_rgba)

original_grayscale_8bit_array = np.array(255 * np.random.rand(100, 100, 1),
dtype=np.uint8)
original_grayscale_8bit = utils.array_to_img(original_grayscale_8bit_array,
scale=False)
original_grayscale_8bit.save(filename_grayscale_8bit)

original_grayscale_16bit_array = np.array(
np.random.randint(-2147483648, 2147483647, (100, 100, 1)), dtype=np.int16
)
original_grayscale_16bit = utils.array_to_img(original_grayscale_16bit_array,
scale=False, dtype='int16')
original_grayscale_16bit.save(filename_grayscale_16bit)

original_grayscale_32bit_array = np.array(
np.random.randint(-2147483648, 2147483647, (100, 100, 1)), dtype=np.int32
)
original_grayscale_32bit = utils.array_to_img(original_grayscale_32bit_array,
scale=False, dtype='int32')
original_grayscale_32bit.save(filename_grayscale_32bit)

# Test that loaded image is exactly equal to original.

loaded_im = utils.load_img(filename_rgb)
Expand All @@ -48,6 +71,27 @@ def test_load_img(tmpdir):
assert loaded_im_array.shape == (original_rgb_array.shape[0],
original_rgb_array.shape[1], 1)

loaded_im = utils.load_img(filename_grayscale_8bit, color_mode='grayscale')
loaded_im_array = utils.img_to_array(loaded_im)
assert loaded_im_array.shape == original_grayscale_8bit_array.shape
assert np.all(loaded_im_array == original_grayscale_8bit_array)

loaded_im = utils.load_img(filename_grayscale_16bit, color_mode='grayscale')
loaded_im_array = utils.img_to_array(loaded_im, dtype='int16')
assert loaded_im_array.shape == original_grayscale_16bit_array.shape
assert np.all(loaded_im_array == original_grayscale_16bit_array)
# test casting int16 image to float32
loaded_im_array = utils.img_to_array(loaded_im)
assert np.allclose(loaded_im_array, original_grayscale_16bit_array)

loaded_im = utils.load_img(filename_grayscale_32bit, color_mode='grayscale')
loaded_im_array = utils.img_to_array(loaded_im, dtype='int32')
assert loaded_im_array.shape == original_grayscale_32bit_array.shape
assert np.all(loaded_im_array == original_grayscale_32bit_array)
# test casting int32 image to float32
loaded_im_array = utils.img_to_array(loaded_im)
assert np.allclose(loaded_im_array, original_grayscale_32bit_array)

# Test that nothing is changed when target size is equal to original.

loaded_im = utils.load_img(filename_rgb, target_size=(100, 100))
Expand All @@ -67,6 +111,24 @@ def test_load_img(tmpdir):
assert loaded_im_array.shape == (original_rgba_array.shape[0],
original_rgba_array.shape[1], 1)

loaded_im = utils.load_img(filename_grayscale_8bit, color_mode='grayscale',
target_size=(100, 100))
loaded_im_array = utils.img_to_array(loaded_im)
assert loaded_im_array.shape == original_grayscale_8bit_array.shape
assert np.all(loaded_im_array == original_grayscale_8bit_array)

loaded_im = utils.load_img(filename_grayscale_16bit, color_mode='grayscale',
target_size=(100, 100))
loaded_im_array = utils.img_to_array(loaded_im, dtype='int16')
assert loaded_im_array.shape == original_grayscale_16bit_array.shape
assert np.all(loaded_im_array == original_grayscale_16bit_array)

loaded_im = utils.load_img(filename_grayscale_32bit, color_mode='grayscale',
target_size=(100, 100))
loaded_im_array = utils.img_to_array(loaded_im, dtype='int32')
assert loaded_im_array.shape == original_grayscale_32bit_array.shape
assert np.all(loaded_im_array == original_grayscale_32bit_array)

# Test down-sampling with bilinear interpolation.

loaded_im = utils.load_img(filename_rgb, target_size=(25, 25))
Expand All @@ -83,6 +145,21 @@ def test_load_img(tmpdir):
loaded_im_array = utils.img_to_array(loaded_im)
assert loaded_im_array.shape == (25, 25, 1)

loaded_im = utils.load_img(filename_grayscale_8bit, color_mode='grayscale',
target_size=(25, 25))
loaded_im_array = utils.img_to_array(loaded_im)
assert loaded_im_array.shape == (25, 25, 1)

loaded_im = utils.load_img(filename_grayscale_16bit, color_mode='grayscale',
target_size=(25, 25))
loaded_im_array = utils.img_to_array(loaded_im, dtype='int16')
assert loaded_im_array.shape == (25, 25, 1)

loaded_im = utils.load_img(filename_grayscale_32bit, color_mode='grayscale',
target_size=(25, 25))
loaded_im_array = utils.img_to_array(loaded_im, dtype='int32')
assert loaded_im_array.shape == (25, 25, 1)

# Test down-sampling with nearest neighbor interpolation.

loaded_im_nearest = utils.load_img(filename_rgb, target_size=(25, 25),
Expand All @@ -98,6 +175,21 @@ def test_load_img(tmpdir):
assert loaded_im_array_nearest.shape == (25, 25, 4)
assert np.any(loaded_im_array_nearest != loaded_im_array)

loaded_im = utils.load_img(filename_grayscale_8bit, color_mode='grayscale',
target_size=(25, 25), interpolation="nearest")
loaded_im_array = utils.img_to_array(loaded_im)
assert loaded_im_array.shape == (25, 25, 1)

loaded_im = utils.load_img(filename_grayscale_16bit, color_mode='grayscale',
target_size=(25, 25), interpolation="nearest")
loaded_im_array = utils.img_to_array(loaded_im, dtype='int16')
assert loaded_im_array.shape == (25, 25, 1)

loaded_im = utils.load_img(filename_grayscale_32bit, color_mode='grayscale',
target_size=(25, 25), interpolation="nearest")
loaded_im_array = utils.img_to_array(loaded_im, dtype='int32')
assert loaded_im_array.shape == (25, 25, 1)

# Check that exception is raised if interpolation not supported.

loaded_im = utils.load_img(filename_rgb, interpolation="unsupported")
Expand Down Expand Up @@ -150,6 +242,17 @@ def test_array_to_img_and_img_to_array():
x = utils.img_to_array(img, data_format='channels_first')
assert x.shape == (1, height, width)

# grayscale 32-bit signed integer
x = np.array(
np.random.randint(-2147483648, 2147483647, (1, height, width)),
dtype=np.int32
)
img = utils.array_to_img(x, data_format='channels_first')
assert img.size == (width, height)

x = utils.img_to_array(img, data_format='channels_first')
assert x.shape == (1, height, width)

# Test tf data format
# Test RGB 3D
x = np.random.random((height, width, 3))
Expand All @@ -175,6 +278,28 @@ def test_array_to_img_and_img_to_array():
x = utils.img_to_array(img, data_format='channels_last')
assert x.shape == (height, width, 1)

# grayscale 16-bit signed integer
x = np.array(
np.random.randint(-2147483648, 2147483647, (height, width, 1)),
dtype=np.int16
)
img = utils.array_to_img(x, data_format='channels_last')
assert img.size == (width, height)

x = utils.img_to_array(img, data_format='channels_last')
assert x.shape == (height, width, 1)

# grayscale 32-bit signed integer
x = np.array(
np.random.randint(-2147483648, 2147483647, (height, width, 1)),
dtype=np.int32
)
img = utils.array_to_img(x, data_format='channels_last')
assert img.size == (width, height)

x = utils.img_to_array(img, data_format='channels_last')
assert x.shape == (height, width, 1)

# Test invalid use case
with pytest.raises(ValueError):
x = np.random.random((height, width)) # not 3D
Expand Down