| | |
| | import io |
| | import os.path as osp |
| | from pathlib import Path |
| |
|
| | import cv2 |
| | import numpy as np |
| | from cv2 import (IMREAD_COLOR, IMREAD_GRAYSCALE, IMREAD_IGNORE_ORIENTATION, |
| | IMREAD_UNCHANGED) |
| |
|
| | from annotator.mmpkg.mmcv.utils import check_file_exist, is_str, mkdir_or_exist |
| |
|
| | try: |
| | from turbojpeg import TJCS_RGB, TJPF_BGR, TJPF_GRAY, TurboJPEG |
| | except ImportError: |
| | TJCS_RGB = TJPF_GRAY = TJPF_BGR = TurboJPEG = None |
| |
|
| | try: |
| | from PIL import Image, ImageOps |
| | except ImportError: |
| | Image = None |
| |
|
| | try: |
| | import tifffile |
| | except ImportError: |
| | tifffile = None |
| |
|
| | jpeg = None |
| | supported_backends = ['cv2', 'turbojpeg', 'pillow', 'tifffile'] |
| |
|
| | imread_flags = { |
| | 'color': IMREAD_COLOR, |
| | 'grayscale': IMREAD_GRAYSCALE, |
| | 'unchanged': IMREAD_UNCHANGED, |
| | 'color_ignore_orientation': IMREAD_IGNORE_ORIENTATION | IMREAD_COLOR, |
| | 'grayscale_ignore_orientation': |
| | IMREAD_IGNORE_ORIENTATION | IMREAD_GRAYSCALE |
| | } |
| |
|
| | imread_backend = 'cv2' |
| |
|
| |
|
| | def use_backend(backend): |
| | """Select a backend for image decoding. |
| | |
| | Args: |
| | backend (str): The image decoding backend type. Options are `cv2`, |
| | `pillow`, `turbojpeg` (see https://github.com/lilohuang/PyTurboJPEG) |
| | and `tifffile`. `turbojpeg` is faster but it only supports `.jpeg` |
| | file format. |
| | """ |
| | assert backend in supported_backends |
| | global imread_backend |
| | imread_backend = backend |
| | if imread_backend == 'turbojpeg': |
| | if TurboJPEG is None: |
| | raise ImportError('`PyTurboJPEG` is not installed') |
| | global jpeg |
| | if jpeg is None: |
| | jpeg = TurboJPEG() |
| | elif imread_backend == 'pillow': |
| | if Image is None: |
| | raise ImportError('`Pillow` is not installed') |
| | elif imread_backend == 'tifffile': |
| | if tifffile is None: |
| | raise ImportError('`tifffile` is not installed') |
| |
|
| |
|
| | def _jpegflag(flag='color', channel_order='bgr'): |
| | channel_order = channel_order.lower() |
| | if channel_order not in ['rgb', 'bgr']: |
| | raise ValueError('channel order must be either "rgb" or "bgr"') |
| |
|
| | if flag == 'color': |
| | if channel_order == 'bgr': |
| | return TJPF_BGR |
| | elif channel_order == 'rgb': |
| | return TJCS_RGB |
| | elif flag == 'grayscale': |
| | return TJPF_GRAY |
| | else: |
| | raise ValueError('flag must be "color" or "grayscale"') |
| |
|
| |
|
| | def _pillow2array(img, flag='color', channel_order='bgr'): |
| | """Convert a pillow image to numpy array. |
| | |
| | Args: |
| | img (:obj:`PIL.Image.Image`): The image loaded using PIL |
| | flag (str): Flags specifying the color type of a loaded image, |
| | candidates are 'color', 'grayscale' and 'unchanged'. |
| | Default to 'color'. |
| | channel_order (str): The channel order of the output image array, |
| | candidates are 'bgr' and 'rgb'. Default to 'bgr'. |
| | |
| | Returns: |
| | np.ndarray: The converted numpy array |
| | """ |
| | channel_order = channel_order.lower() |
| | if channel_order not in ['rgb', 'bgr']: |
| | raise ValueError('channel order must be either "rgb" or "bgr"') |
| |
|
| | if flag == 'unchanged': |
| | array = np.array(img) |
| | if array.ndim >= 3 and array.shape[2] >= 3: |
| | array[:, :, :3] = array[:, :, (2, 1, 0)] |
| | else: |
| | |
| | if flag in ['color', 'grayscale']: |
| | img = ImageOps.exif_transpose(img) |
| | |
| | if img.mode != 'RGB': |
| | if img.mode != 'LA': |
| | |
| | img = img.convert('RGB') |
| | else: |
| | |
| | |
| | |
| | |
| | |
| | img_rgba = img.convert('RGBA') |
| | img = Image.new('RGB', img_rgba.size, (124, 117, 104)) |
| | img.paste(img_rgba, mask=img_rgba.split()[3]) |
| | if flag in ['color', 'color_ignore_orientation']: |
| | array = np.array(img) |
| | if channel_order != 'rgb': |
| | array = array[:, :, ::-1] |
| | elif flag in ['grayscale', 'grayscale_ignore_orientation']: |
| | img = img.convert('L') |
| | array = np.array(img) |
| | else: |
| | raise ValueError( |
| | 'flag must be "color", "grayscale", "unchanged", ' |
| | f'"color_ignore_orientation" or "grayscale_ignore_orientation"' |
| | f' but got {flag}') |
| | return array |
| |
|
| |
|
| | def imread(img_or_path, flag='color', channel_order='bgr', backend=None): |
| | """Read an image. |
| | |
| | Args: |
| | img_or_path (ndarray or str or Path): Either a numpy array or str or |
| | pathlib.Path. If it is a numpy array (loaded image), then |
| | it will be returned as is. |
| | flag (str): Flags specifying the color type of a loaded image, |
| | candidates are `color`, `grayscale`, `unchanged`, |
| | `color_ignore_orientation` and `grayscale_ignore_orientation`. |
| | By default, `cv2` and `pillow` backend would rotate the image |
| | according to its EXIF info unless called with `unchanged` or |
| | `*_ignore_orientation` flags. `turbojpeg` and `tifffile` backend |
| | always ignore image's EXIF info regardless of the flag. |
| | The `turbojpeg` backend only supports `color` and `grayscale`. |
| | channel_order (str): Order of channel, candidates are `bgr` and `rgb`. |
| | backend (str | None): The image decoding backend type. Options are |
| | `cv2`, `pillow`, `turbojpeg`, `tifffile`, `None`. |
| | If backend is None, the global imread_backend specified by |
| | ``mmcv.use_backend()`` will be used. Default: None. |
| | |
| | Returns: |
| | ndarray: Loaded image array. |
| | """ |
| |
|
| | if backend is None: |
| | backend = imread_backend |
| | if backend not in supported_backends: |
| | raise ValueError(f'backend: {backend} is not supported. Supported ' |
| | "backends are 'cv2', 'turbojpeg', 'pillow'") |
| | if isinstance(img_or_path, Path): |
| | img_or_path = str(img_or_path) |
| |
|
| | if isinstance(img_or_path, np.ndarray): |
| | return img_or_path |
| | elif is_str(img_or_path): |
| | check_file_exist(img_or_path, |
| | f'img file does not exist: {img_or_path}') |
| | if backend == 'turbojpeg': |
| | with open(img_or_path, 'rb') as in_file: |
| | img = jpeg.decode(in_file.read(), |
| | _jpegflag(flag, channel_order)) |
| | if img.shape[-1] == 1: |
| | img = img[:, :, 0] |
| | return img |
| | elif backend == 'pillow': |
| | img = Image.open(img_or_path) |
| | img = _pillow2array(img, flag, channel_order) |
| | return img |
| | elif backend == 'tifffile': |
| | img = tifffile.imread(img_or_path) |
| | return img |
| | else: |
| | flag = imread_flags[flag] if is_str(flag) else flag |
| | img = cv2.imread(img_or_path, flag) |
| | if flag == IMREAD_COLOR and channel_order == 'rgb': |
| | cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) |
| | return img |
| | else: |
| | raise TypeError('"img" must be a numpy array or a str or ' |
| | 'a pathlib.Path object') |
| |
|
| |
|
| | def imfrombytes(content, flag='color', channel_order='bgr', backend=None): |
| | """Read an image from bytes. |
| | |
| | Args: |
| | content (bytes): Image bytes got from files or other streams. |
| | flag (str): Same as :func:`imread`. |
| | backend (str | None): The image decoding backend type. Options are |
| | `cv2`, `pillow`, `turbojpeg`, `None`. If backend is None, the |
| | global imread_backend specified by ``mmcv.use_backend()`` will be |
| | used. Default: None. |
| | |
| | Returns: |
| | ndarray: Loaded image array. |
| | """ |
| |
|
| | if backend is None: |
| | backend = imread_backend |
| | if backend not in supported_backends: |
| | raise ValueError(f'backend: {backend} is not supported. Supported ' |
| | "backends are 'cv2', 'turbojpeg', 'pillow'") |
| | if backend == 'turbojpeg': |
| | img = jpeg.decode(content, _jpegflag(flag, channel_order)) |
| | if img.shape[-1] == 1: |
| | img = img[:, :, 0] |
| | return img |
| | elif backend == 'pillow': |
| | buff = io.BytesIO(content) |
| | img = Image.open(buff) |
| | img = _pillow2array(img, flag, channel_order) |
| | return img |
| | else: |
| | img_np = np.frombuffer(content, np.uint8) |
| | flag = imread_flags[flag] if is_str(flag) else flag |
| | img = cv2.imdecode(img_np, flag) |
| | if flag == IMREAD_COLOR and channel_order == 'rgb': |
| | cv2.cvtColor(img, cv2.COLOR_BGR2RGB, img) |
| | return img |
| |
|
| |
|
| | def imwrite(img, file_path, params=None, auto_mkdir=True): |
| | """Write image to file. |
| | |
| | Args: |
| | img (ndarray): Image array to be written. |
| | file_path (str): Image file path. |
| | params (None or list): Same as opencv :func:`imwrite` interface. |
| | auto_mkdir (bool): If the parent folder of `file_path` does not exist, |
| | whether to create it automatically. |
| | |
| | Returns: |
| | bool: Successful or not. |
| | """ |
| | if auto_mkdir: |
| | dir_name = osp.abspath(osp.dirname(file_path)) |
| | mkdir_or_exist(dir_name) |
| | return cv2.imwrite(file_path, img, params) |
| |
|