Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] Use the new interface of fileio from mmengine #2468

Merged
merged 6 commits into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
70 changes: 52 additions & 18 deletions mmcv/image/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import numpy as np
from cv2 import (IMREAD_COLOR, IMREAD_GRAYSCALE, IMREAD_IGNORE_ORIENTATION,
IMREAD_UNCHANGED)
from mmengine.fileio import FileClient
from mmengine.fileio import FileClient, get, put
from mmengine.utils import is_filepath, is_str

try:
Expand Down Expand Up @@ -145,12 +145,10 @@ def imread(img_or_path: Union[np.ndarray, str, Path],
flag: str = 'color',
channel_order: str = 'bgr',
backend: Optional[str] = None,
file_client_args: Optional[dict] = None) -> np.ndarray:
file_client_args: Optional[dict] = None,
backend_args: Optional[dict] = None) -> np.ndarray:
"""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
Expand All @@ -170,7 +168,12 @@ def imread(img_or_path: Union[np.ndarray, str, Path],
``mmcv.use_backend()`` will be used. Default: None.
file_client_args (dict | None): Arguments to instantiate a
FileClient. See :class:`mmengine.fileio.FileClient` for details.
Default: None.
Default: None. It will be deprecated in future. Please use
``backend_args`` instead.
Deprecated in version 2.0.0rc4.
backend_args (dict, optional): Arguments to instantiate the
preifx of uri corresponding backend. Defaults to None.
New in version 2.0.0rc4.

Returns:
ndarray: Loaded image array.
Expand All @@ -187,22 +190,35 @@ def imread(img_or_path: Union[np.ndarray, str, Path],
>>> # 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={
>>> img = mmcv.imread(s3_img_path, backend_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={
>>> img = mmcv.imread(http_img_path, backend_args={
... 'backend': 'http'})
"""
if file_client_args is not None:
warnings.warn(
'"file_client_args" will be deprecated in future. '
'Please use "backend_args" instead', DeprecationWarning)
if backend_args is not None:
raise ValueError(
'"file_client_args" and "backend_args" cannot be set at the '
'same time.')

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):
file_client = FileClient.infer_client(file_client_args, img_or_path)
img_bytes = file_client.get(img_or_path)
if file_client_args is not None:
file_client = FileClient.infer_client(file_client_args,
img_or_path)
img_bytes = file_client.get(img_or_path)
else:
img_bytes = get(img_or_path, backend_args=backend_args)

return imfrombytes(img_bytes, flag, channel_order, backend)
else:
raise TypeError('"img" must be a numpy array or a str or '
Expand Down Expand Up @@ -272,12 +288,10 @@ def imwrite(img: np.ndarray,
file_path: str,
params: Optional[list] = None,
auto_mkdir: Optional[bool] = None,
file_client_args: Optional[dict] = None) -> bool:
file_client_args: Optional[dict] = None,
backend_args: Optional[dict] = None) -> bool:
"""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.
Expand All @@ -290,7 +304,12 @@ def imwrite(img: np.ndarray,
whether to create it automatically. It will be deprecated.
file_client_args (dict | None): Arguments to instantiate a
FileClient. See :class:`mmengine.fileio.FileClient` for details.
Default: None.
Default: None. It will be deprecated in future. Please use
``backend_args`` instead.
Deprecated in version 2.0.0rc4.
backend_args (dict, optional): Arguments to instantiate the
preifx of uri corresponding backend. Defaults to None.
New in version 2.0.0rc4.

Returns:
bool: Successful or not.
Expand All @@ -301,20 +320,35 @@ def imwrite(img: np.ndarray,
>>> # 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={
>>> ret = mmcv.imwrite(img, 's3://bucket/img.jpg', backend_args={
... 'backend': 'petrel'})
"""
if file_client_args is not None:
warnings.warn(
'"file_client_args" will be deprecated in future. '
'Please use "backend_args" instead', DeprecationWarning)
if backend_args is not None:
raise ValueError(
'"file_client_args" and "backend_args" cannot be set at the '
'same time.')

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)

if file_client_args is not None:
file_client = FileClient.infer_client(file_client_args, file_path)
file_client.put(img_buff.tobytes(), file_path)
else:
put(img_buff.tobytes(), file_path, backend_args=backend_args)

return flag
86 changes: 73 additions & 13 deletions mmcv/transforms/loading.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Copyright (c) OpenMMLab. All rights reserved.
from typing import Optional
import warnings
from typing import Optional, Union

import mmengine
import numpy as np
from mmengine.fileio import BaseStorageBackend, FileClient, get_file_backend

import mmcv
from .base import BaseTransform
Expand Down Expand Up @@ -35,23 +36,50 @@ class LoadImageFromFile(BaseTransform):
Defaults to 'cv2'.
file_client_args (dict): Arguments to instantiate a FileClient.
See :class:`mmengine.fileio.FileClient` for details.
Defaults to ``dict(backend='disk')``.
Defaults to None. It will be deprecated in future. Please use
``backend_args`` instead.
Deprecated in version 2.0.0rc4.
ignore_empty (bool): Whether to allow loading empty image or file path
not existent. Defaults to False.
backend_args (dict, optional): Arguments to instantiate the
preifx of uri corresponding backend. Defaults to None.
New in version 2.0.0rc4.
"""

def __init__(self,
to_float32: bool = False,
color_type: str = 'color',
imdecode_backend: str = 'cv2',
file_client_args: dict = dict(backend='disk'),
ignore_empty: bool = False) -> None:
file_client_args: Optional[dict] = None,
ignore_empty: bool = False,
backend_args: Optional[dict] = None) -> None:
self.ignore_empty = ignore_empty
self.to_float32 = to_float32
self.color_type = color_type
self.imdecode_backend = imdecode_backend

if file_client_args is not None:
warnings.warn(
'"file_client_args" will be deprecated in future. '
'Please use "backend_args" instead', DeprecationWarning)
if backend_args is not None:
raise ValueError(
'"file_client_args" and "backend_args" cannot be set '
'at the same time.')
else:
file_client_args = dict(backend='disk')
if backend_args is None:
backend_args = dict(backend='disk')
zhouzaida marked this conversation as resolved.
Show resolved Hide resolved
self.file_client_args = file_client_args.copy()
self.file_client = mmengine.FileClient(**self.file_client_args)
self.file_client = FileClient(**self.file_client_args)
self.backend_args = backend_args.copy()

self.file_backend: Union[FileClient, BaseStorageBackend]
if self.file_client_args is None:
self.file_backend = get_file_backend(
backend_args=self.backend_args)
else:
self.file_backend = self.file_client

def transform(self, results: dict) -> Optional[dict]:
"""Functions to load image.
Expand All @@ -66,7 +94,7 @@ def transform(self, results: dict) -> Optional[dict]:

filename = results['img_path']
try:
img_bytes = self.file_client.get(filename)
img_bytes = self.file_backend.get(filename)
img = mmcv.imfrombytes(
img_bytes, flag=self.color_type, backend=self.imdecode_backend)
except Exception as e:
Expand All @@ -87,8 +115,13 @@ def __repr__(self):
f'ignore_empty={self.ignore_empty}, '
f'to_float32={self.to_float32}, '
f"color_type='{self.color_type}', "
f"imdecode_backend='{self.imdecode_backend}', "
f'file_client_args={self.file_client_args})')
f"imdecode_backend='{self.imdecode_backend}', ")

if self.file_client_args is not None:
repr_str += f'file_client_args={self.file_client_args})'
else:
repr_str += f'backend_args={self.backend_args})'

return repr_str


Expand Down Expand Up @@ -181,16 +214,38 @@ def __init__(
with_seg: bool = False,
with_keypoints: bool = False,
imdecode_backend: str = 'cv2',
file_client_args: dict = dict(backend='disk')
file_client_args: Optional[dict] = None,
backend_args: Optional[dict] = None,
) -> None:
super().__init__()
self.with_bbox = with_bbox
self.with_label = with_label
self.with_seg = with_seg
self.with_keypoints = with_keypoints
self.imdecode_backend = imdecode_backend

if file_client_args is not None:
warnings.warn(
'"file_client_args" will be deprecated in future. '
'Please use "backend_args" instead', DeprecationWarning)
if backend_args is not None:
raise ValueError(
'"file_client_args" and "backend_args" cannot be set '
'at the same time.')
else:
file_client_args = dict(backend='disk')
if backend_args is None:
backend_args = dict(backend='disk')
self.file_client_args = file_client_args.copy()
self.file_client = mmengine.FileClient(**self.file_client_args)
self.file_client = FileClient(**self.file_client_args)
self.backend_args = backend_args.copy()

self.file_backend: Union[FileClient, BaseStorageBackend]
if self.file_client_args is None:
self.file_backend = get_file_backend(
backend_args=self.backend_args)
else:
self.file_backend = self.file_client

def _load_bboxes(self, results: dict) -> None:
"""Private function to load bounding box annotations.
Expand Down Expand Up @@ -235,7 +290,7 @@ def _load_seg_map(self, results: dict) -> None:
dict: The dict contains loaded semantic segmentation annotations.
"""

img_bytes = self.file_client.get(results['seg_map_path'])
img_bytes = self.file_backend.get(results['seg_map_path'])
results['gt_seg_map'] = mmcv.imfrombytes(
img_bytes, flag='unchanged',
backend=self.imdecode_backend).squeeze()
Expand Down Expand Up @@ -285,5 +340,10 @@ def __repr__(self) -> str:
repr_str += f'with_seg={self.with_seg}, '
repr_str += f'with_keypoints={self.with_keypoints}, '
repr_str += f"imdecode_backend='{self.imdecode_backend}', "
repr_str += f'file_client_args={self.file_client_args})'

if self.file_client_args is not None:
repr_str += f'file_client_args={self.file_client_args})'
else:
repr_str += f'backend_args={self.backend_args})'

return repr_str
2 changes: 1 addition & 1 deletion requirements/runtime.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
addict
mmengine
mmengine>=0.2.0
numpy
packaging
Pillow
Expand Down
47 changes: 47 additions & 0 deletions tests/test_image/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ def test_imread(self):
# backend cv2
mmcv.use_backend('cv2')

# file_client_args and backend_args can not be both set
with pytest.raises(
ValueError,
match='"file_client_args" and "backend_args" cannot be set'):
mmcv.imread(
self.img_path,
file_client_args={'backend': 'disk'},
backend_args={'backend': 'disk'})

# HardDiskBackend
img_cv2_color_bgr = mmcv.imread(self.img_path)
assert img_cv2_color_bgr.shape == (300, 400, 3)
Expand Down Expand Up @@ -103,6 +112,16 @@ def test_imread(self):
assert_array_equal(img_cv2_color_bgr_petrel,
img_cv2_color_bgr_petrel_with_args)

mock_method.reset_mock()

img_cv2_color_bgr_petrel_with_args = mmcv.imread(
self.s3_path,
backend='cv2',
backend_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(
Expand All @@ -117,6 +136,16 @@ def test_imread(self):
assert_array_equal(img_cv2_color_bgr_http,
img_cv2_color_bgr_http_with_args)

mock_method.reset_mock()

img_cv2_color_bgr_http_with_args = mmcv.imread(
self.http_path,
backend='cv2',
backend_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)

Expand Down Expand Up @@ -357,6 +386,17 @@ def test_imfrombytes(self):
def test_imwrite(self):
img = mmcv.imread(self.img_path)
out_file = osp.join(tempfile.gettempdir(), 'mmcv_test.jpg')

# file_client_args and backend_args can not be both set
with pytest.raises(
ValueError,
match='"file_client_args" and "backend_args" cannot be set'):
mmcv.imwrite(
img,
out_file,
file_client_args={'backend': 'disk'},
backend_args={'backend': 'disk'})

mmcv.imwrite(img, out_file)
rewrite_img = mmcv.imread(out_file)
os.remove(out_file)
Expand All @@ -372,6 +412,13 @@ def test_imwrite(self):
assert ret_with_args
mock_method.assert_called()

mock_method.reset_mock()

ret_with_args = mmcv.imwrite(
img, self.s3_path, backend_args={'backend': 'petrel'})
assert ret_with_args
mock_method.assert_called()

with pytest.raises(cv2.error):
mmcv.imwrite(img, 'error_file.jppg')

Expand Down