Unverified Commit 66bff139 authored by wuwencheng's avatar wuwencheng Committed by GitHub
Browse files

[Feature] Add multi file backends to imread/imwrite. (#1527)

* Add file client to image io

* Fix petrel_client imwrite error

* Add examples to the docstring and delete the file check of imread

* modify docstring v1.3.19->v1.4.1

* Deprecate auto_mkdir parameter and complete test_io.py

* Fix error caused by deleting the mock package in test_io.py

* Add annotation to imencode

* modify imread input assert and delete the judgement of file client 'put' method

* Delete try except in imwrite.

* Add a error file extension unit test.
parent 81f032ed
# Copyright (c) OpenMMLab. All rights reserved.
import io
import os.path as osp
import warnings
from pathlib import Path
import cv2
......@@ -8,7 +9,8 @@ import numpy as np
from cv2 import (IMREAD_COLOR, IMREAD_GRAYSCALE, IMREAD_IGNORE_ORIENTATION,
IMREAD_UNCHANGED)
from mmcv.utils import check_file_exist, is_str, mkdir_or_exist
from mmcv.fileio import FileClient
from mmcv.utils import is_filepath, is_str
try:
from turbojpeg import TJCS_RGB, TJPF_BGR, TJPF_GRAY, TurboJPEG
......@@ -137,9 +139,16 @@ def _pillow2array(img, flag='color', channel_order='bgr'):
return array
def imread(img_or_path, flag='color', channel_order='bgr', backend=None):
def imread(img_or_path,
flag='color',
channel_order='bgr',
backend=None,
file_client_args=None):
"""Read an image.
Note:
In v1.4.1 and later, add `file_client_args` parameters.
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
......@@ -157,44 +166,42 @@ def imread(img_or_path, flag='color', channel_order='bgr', backend=None):
`cv2`, `pillow`, `turbojpeg`, `tifffile`, `None`.
If backend is None, the global imread_backend specified by
``mmcv.use_backend()`` will be used. Default: None.
file_client_args (dict | None): Arguments to instantiate a
FileClient. See :class:`mmcv.fileio.FileClient` for details.
Default: None.
Returns:
ndarray: Loaded image array.
Examples:
>>> import mmcv
>>> img_path = '/path/to/img.jpg'
>>> img = mmcv.imread(img_path)
>>> img = mmcv.imread(img_path, flag='color', channel_order='rgb',
... backend='cv2')
>>> img = mmcv.imread(img_path, flag='color', channel_order='bgr',
... backend='pillow')
>>> s3_img_path = 's3://bucket/img.jpg'
>>> # infer the file backend by the prefix s3
>>> img = mmcv.imread(s3_img_path)
>>> # manually set the file backend petrel
>>> img = mmcv.imread(s3_img_path, file_client_args={
... 'backend': 'petrel'})
>>> http_img_path = 'http://path/to/img.jpg'
>>> img = mmcv.imread(http_img_path)
>>> img = mmcv.imread(http_img_path, file_client_args={
... 'backend': 'http'})
"""
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
file_client = FileClient.infer_client(file_client_args, img_or_path)
img_bytes = file_client.get(img_or_path)
return imfrombytes(img_bytes, flag, channel_order, backend)
else:
raise TypeError('"img" must be a numpy array or a str or '
'a pathlib.Path object')
......@@ -207,28 +214,42 @@ def imfrombytes(content, flag='color', channel_order='bgr', backend=None):
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.
`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.
Examples:
>>> img_path = '/path/to/img.jpg'
>>> with open(img_path, 'rb') as f:
>>> img_buff = f.read()
>>> img = mmcv.imfrombytes(img_buff)
>>> img = mmcv.imfrombytes(img_buff, flag='color', channel_order='rgb')
>>> img = mmcv.imfrombytes(img_buff, backend='pillow')
>>> img = mmcv.imfrombytes(img_buff, backend='cv2')
"""
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'")
raise ValueError(
f'backend: {backend} is not supported. Supported '
"backends are 'cv2', 'turbojpeg', 'pillow', 'tifffile'")
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)
with io.BytesIO(content) as buff:
img = Image.open(buff)
img = _pillow2array(img, flag, channel_order)
return img
elif backend == 'tifffile':
with io.BytesIO(content) as buff:
img = tifffile.imread(buff)
return img
else:
img_np = np.frombuffer(content, np.uint8)
......@@ -239,20 +260,53 @@ def imfrombytes(content, flag='color', channel_order='bgr', backend=None):
return img
def imwrite(img, file_path, params=None, auto_mkdir=True):
def imwrite(img,
file_path,
params=None,
auto_mkdir=None,
file_client_args=None):
"""Write image to file.
Note:
In v1.4.1 and later, add `file_client_args` parameters.
Warning:
The parameter `auto_mkdir` will be deprecated in the future and every
file clients will make directory automatically.
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.
whether to create it automatically. It will be deprecated.
file_client_args (dict | None): Arguments to instantiate a
FileClient. See :class:`mmcv.fileio.FileClient` for details.
Default: None.
Returns:
bool: Successful or not.
Examples:
>>> # write to hard disk client
>>> ret = mmcv.imwrite(img, '/path/to/img.jpg')
>>> # infer the file backend by the prefix s3
>>> ret = mmcv.imwrite(img, 's3://bucket/img.jpg')
>>> # manually set the file backend petrel
>>> ret = mmcv.imwrite(img, 's3://bucket/img.jpg', file_client_args={
... 'backend': 'petrel'})
"""
if auto_mkdir:
dir_name = osp.abspath(osp.dirname(file_path))
mkdir_or_exist(dir_name)
return cv2.imwrite(file_path, img, params)
assert is_filepath(file_path)
file_path = str(file_path)
if auto_mkdir is not None:
warnings.warn(
'The parameter `auto_mkdir` will be deprecated in the future and '
'every file clients will make directory automatically.')
file_client = FileClient.infer_client(file_client_args, file_path)
img_ext = osp.splitext(file_path)[-1]
# Encode image according to image suffix.
# For example, if image path is '/path/your/img.jpg', the encode
# format is '.jpg'.
flag, img_buff = cv2.imencode(img_ext, img, params)
file_client.put(img_buff.tobytes(), file_path)
return flag
# Copyright (c) OpenMMLab. All rights reserved.
import os
import os.path as osp
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch
from unittest.mock import MagicMock, patch
import cv2
import numpy as np
......@@ -11,6 +12,7 @@ import pytest
from numpy.testing import assert_allclose, assert_array_equal
import mmcv
from mmcv.fileio.file_client import HTTPBackend, PetrelBackend
class TestIO:
......@@ -29,6 +31,18 @@ class TestIO:
cls.exif_img_path = osp.join(cls.data_dir, 'color_exif.jpg')
cls.img = cv2.imread(cls.img_path)
cls.tiff_path = osp.join(cls.data_dir, 'uint16-5channel.tif')
# petrel s3 path
cls.s3_path = 's3://path/of/your/file.jpg'
# http path
cls.http_path = 'http://path/of/your/file.jpg'
# add mock package
sys.modules['petrel_client'] = MagicMock()
sys.modules['petrel_client.client'] = MagicMock()
@classmethod
def teardown_class(cls):
# clean instances avoid to influence other unittest
mmcv.FileClient._instances = {}
def assert_img_equal(self, img, ref_img, ratio_thr=0.999):
assert img.shape == ref_img.shape
......@@ -41,6 +55,7 @@ class TestIO:
# backend cv2
mmcv.use_backend('cv2')
# HardDiskBackend
img_cv2_color_bgr = mmcv.imread(self.img_path)
assert img_cv2_color_bgr.shape == (300, 400, 3)
img_cv2_color_rgb = mmcv.imread(self.img_path, channel_order='rgb')
......@@ -69,6 +84,37 @@ class TestIO:
with pytest.raises(TypeError):
mmcv.imread(1)
# PetrelBackend
img_cv2_color_bgr = mmcv.imread(self.img_path)
with patch.object(
PetrelBackend, 'get',
return_value=img_cv2_color_bgr) as mock_method:
img_cv2_color_bgr_petrel = mmcv.imread(self.s3_path, backend='cv2')
img_cv2_color_bgr_petrel_with_args = mmcv.imread(
self.s3_path,
backend='cv2',
file_client_args={'backend': 'petrel'})
mock_method.assert_called()
assert_array_equal(img_cv2_color_bgr_petrel,
img_cv2_color_bgr_petrel_with_args)
# HTTPBackend
img_cv2_color_bgr = mmcv.imread(self.img_path)
with patch.object(
HTTPBackend, 'get',
return_value=img_cv2_color_bgr) as mock_method:
img_cv2_color_bgr_http = mmcv.imread(self.http_path, backend='cv2')
img_cv2_color_bgr_http_with_args = mmcv.imread(
self.http_path,
backend='cv2',
file_client_args={'backend': 'http'})
mock_method.assert_called()
assert_array_equal(img_cv2_color_bgr_http,
img_cv2_color_bgr_http_with_args)
with pytest.raises(FileNotFoundError):
mmcv.imread('/not/exists/' + self.img_path)
# test arg backend pillow
img_pil_gray_alpha = mmcv.imread(
self.gray_alpha_img_path, 'grayscale', backend='pillow')
......@@ -311,9 +357,18 @@ class TestIO:
os.remove(out_file)
self.assert_img_equal(img, rewrite_img)
ret = mmcv.imwrite(
img, './non_exist_path/mmcv_test.jpg', auto_mkdir=False)
assert ret is False
# test petrel client
with patch.object(
PetrelBackend, 'put', return_value=None) as mock_method:
ret = mmcv.imwrite(img, self.s3_path)
ret_with_args = mmcv.imwrite(
img, self.s3_path, file_client_args={'backend': 'petrel'})
assert ret
assert ret_with_args
mock_method.assert_called()
with pytest.raises(cv2.error):
mmcv.imwrite(img, 'error_file.jppg')
@patch('mmcv.image.io.TurboJPEG', None)
def test_no_turbojpeg(self):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment