Spaces:
Runtime error
Runtime error
# Copyright 2023 The TensorFlow Authors. All Rights Reserved. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
"""Image-related utilities that are useful to prepare dataset.""" | |
import dataclasses | |
import imghdr | |
import io | |
from typing import Optional, Tuple | |
import numpy as np | |
from PIL import Image | |
class ImageFormat: | |
"""Supported image formats. | |
For model development, this library should support the same image formats as | |
`tf.io.decode_image`[1]. | |
[1]: https://www.tensorflow.org/api_docs/python/tf/io/decode_image | |
""" | |
bmp: str = 'BMP' | |
png: str = 'PNG' | |
jpeg: str = 'JPEG' | |
raw: str = 'RAW' | |
def validate_image_format(format_str: str) -> str: | |
"""Validates `format_str` and returns canonical format. | |
This function accepts image format in lower case and will returns the upper | |
case string as canonical format. | |
Args: | |
format_str: Image format string. | |
Returns: | |
Canonical image format string. | |
Raises: | |
ValueError: If the canonical format is not listed in `ImageFormat`. | |
""" | |
canonical_format = format_str.upper() | |
if canonical_format in dataclasses.asdict(ImageFormat()).values(): | |
return canonical_format | |
raise ValueError(f'Image format is invalid: {format_str}') | |
def encode_image(image_np: np.ndarray, image_format: str) -> bytes: | |
"""Encodes `image_np` specified by `image_format`. | |
Args: | |
image_np: Numpy image array. | |
image_format: An enum specifying the format of the generated image. | |
Returns: | |
Encoded image string. | |
""" | |
if image_format == 'RAW': | |
return image_np.tobytes() | |
if len(image_np.shape) > 2 and image_np.shape[2] == 1: | |
image_pil = Image.fromarray(np.squeeze(image_np), 'L') | |
else: | |
image_pil = Image.fromarray(image_np) | |
with io.BytesIO() as output: | |
image_pil.save(output, format=validate_image_format(image_format)) | |
return output.getvalue() | |
def decode_image(image_bytes: bytes, | |
image_format: Optional[str] = None, | |
image_dtype: str = 'uint8') -> np.ndarray: | |
"""Decodes image_bytes into numpy array.""" | |
if image_format == 'RAW': | |
return np.frombuffer(image_bytes, dtype=image_dtype) | |
image_pil = Image.open(io.BytesIO(image_bytes)) | |
image_np = np.array(image_pil) | |
if len(image_np.shape) < 3: | |
image_np = image_np[..., np.newaxis] | |
return image_np | |
def decode_image_metadata(image_bytes: bytes) -> Tuple[int, int, int, str]: | |
"""Decodes image metadata from encoded image string. | |
Note that if the image is encoded in RAW format, the metadata cannot be | |
inferred from the image bytes. | |
Args: | |
image_bytes: Encoded image string. | |
Returns: | |
A tuple of height, width, number of channels, and encoding format. | |
""" | |
image_np = decode_image(image_bytes) | |
# https://pillow.readthedocs.io/en/stable/reference/Image.html#image-attributes | |
height, width, num_channels = image_np.shape | |
image_format = imghdr.what(file=None, h=image_bytes) | |
return height, width, num_channels, validate_image_format(image_format) | |