From 32f21dadc4828524015d5350bf1cb4641a292f72 Mon Sep 17 00:00:00 2001 From: Quentin Duval Date: Mon, 22 Jun 2020 16:18:03 +0200 Subject: [PATCH 01/51] Fix: PyAV does not support floating point numbers with decimals as FPS when writing and will throw in case this constraint is not satisfied. (#2334) --- torchvision/io/video.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/torchvision/io/video.py b/torchvision/io/video.py index f822cbd3343..2959024b5dd 100644 --- a/torchvision/io/video.py +++ b/torchvision/io/video.py @@ -2,7 +2,7 @@ import math import re import warnings -from typing import Tuple, List +from typing import List, Tuple, Union import numpy as np import torch @@ -49,7 +49,7 @@ def _av_available(): _GC_COLLECTION_INTERVAL = 10 -def write_video(filename, video_array, fps, video_codec="libx264", options=None): +def write_video(filename, video_array, fps: Union[int, float], video_codec="libx264", options=None): """ Writes a 4d tensor in [T, H, W, C] format in a video file @@ -65,6 +65,11 @@ def write_video(filename, video_array, fps, video_codec="libx264", options=None) _check_av_available() video_array = torch.as_tensor(video_array, dtype=torch.uint8).numpy() + # PyAV does not support floating point numbers with decimal point + # and will throw OverflowException in case this is not the case + if isinstance(fps, float): + fps = np.round(fps) + container = av.open(filename, mode="w") stream = container.add_stream(video_codec, rate=fps) From 42aa9b263eaea3553369ff39268cfe295c7a57b0 Mon Sep 17 00:00:00 2001 From: Quentin Duval Date: Mon, 22 Jun 2020 16:59:54 +0200 Subject: [PATCH 02/51] Refactoring to use contexts managers, list comprehensions when more idiomatic, and minor renaming to help reader clarity (#2335) * Refactoring to use contexts managers, list comprehensions when more idiomatic, and minor renaming to help reader clarity. * Fix flake8 warning in video_utils.py --- torchvision/datasets/utils.py | 18 +--- torchvision/datasets/video_utils.py | 20 +++-- torchvision/io/video.py | 122 +++++++++++++--------------- 3 files changed, 72 insertions(+), 88 deletions(-) diff --git a/torchvision/datasets/utils.py b/torchvision/datasets/utils.py index 6689eef649b..995162871fc 100644 --- a/torchvision/datasets/utils.py +++ b/torchvision/datasets/utils.py @@ -95,16 +95,9 @@ def list_dir(root, prefix=False): only returns the name of the directories found """ root = os.path.expanduser(root) - directories = list( - filter( - lambda p: os.path.isdir(os.path.join(root, p)), - os.listdir(root) - ) - ) - + directories = [p for p in os.listdir(root) if os.path.isdir(os.path.join(root, p))] if prefix is True: directories = [os.path.join(root, d) for d in directories] - return directories @@ -119,16 +112,9 @@ def list_files(root, suffix, prefix=False): only returns the name of the files found """ root = os.path.expanduser(root) - files = list( - filter( - lambda p: os.path.isfile(os.path.join(root, p)) and p.endswith(suffix), - os.listdir(root) - ) - ) - + files = [p for p in os.listdir(root) if os.path.isfile(os.path.join(root, p)) and p.endswith(suffix)] if prefix is True: files = [os.path.join(root, d) for d in files] - return files diff --git a/torchvision/datasets/video_utils.py b/torchvision/datasets/video_utils.py index 5c9244e5450..91b858d6b91 100644 --- a/torchvision/datasets/video_utils.py +++ b/torchvision/datasets/video_utils.py @@ -1,6 +1,7 @@ import bisect import math from fractions import Fraction +from typing import List import torch from torchvision.io import ( @@ -45,20 +46,23 @@ def unfold(tensor, size, step, dilation=1): return torch.as_strided(tensor, new_size, new_stride) -class _DummyDataset(object): +class _VideoTimestampsDataset(object): """ - Dummy dataset used for DataLoader in VideoClips. - Defined at top level so it can be pickled when forking. + Dataset used to parallelize the reading of the timestamps + of a list of videos, given their paths in the filesystem. + + Used in VideoClips and defined at top level so it can be + pickled when forking. """ - def __init__(self, x): - self.x = x + def __init__(self, video_paths: List[str]): + self.video_paths = video_paths def __len__(self): - return len(self.x) + return len(self.video_paths) def __getitem__(self, idx): - return read_video_timestamps(self.x[idx]) + return read_video_timestamps(self.video_paths[idx]) class VideoClips(object): @@ -132,7 +136,7 @@ def _compute_frame_pts(self): import torch.utils.data dl = torch.utils.data.DataLoader( - _DummyDataset(self.video_paths), + _VideoTimestampsDataset(self.video_paths), batch_size=16, num_workers=self.num_workers, collate_fn=self._collate_fn, diff --git a/torchvision/io/video.py b/torchvision/io/video.py index 2959024b5dd..5c8529a7b5d 100644 --- a/torchvision/io/video.py +++ b/torchvision/io/video.py @@ -70,27 +70,23 @@ def write_video(filename, video_array, fps: Union[int, float], video_codec="libx if isinstance(fps, float): fps = np.round(fps) - container = av.open(filename, mode="w") - - stream = container.add_stream(video_codec, rate=fps) - stream.width = video_array.shape[2] - stream.height = video_array.shape[1] - stream.pix_fmt = "yuv420p" if video_codec != "libx264rgb" else "rgb24" - stream.options = options or {} - - for img in video_array: - frame = av.VideoFrame.from_ndarray(img, format="rgb24") - frame.pict_type = "NONE" - for packet in stream.encode(frame): + with av.open(filename, mode="w") as container: + stream = container.add_stream(video_codec, rate=fps) + stream.width = video_array.shape[2] + stream.height = video_array.shape[1] + stream.pix_fmt = "yuv420p" if video_codec != "libx264rgb" else "rgb24" + stream.options = options or {} + + for img in video_array: + frame = av.VideoFrame.from_ndarray(img, format="rgb24") + frame.pict_type = "NONE" + for packet in stream.encode(frame): + container.mux(packet) + + # Flush stream + for packet in stream.encode(): container.mux(packet) - # Flush stream - for packet in stream.encode(): - container.mux(packet) - - # Close the file - container.close() - def _read_from_stream( container, start_offset, end_offset, pts_unit, stream, stream_name @@ -234,37 +230,35 @@ def read_video(filename, start_pts=0, end_pts=None, pts_unit="pts"): audio_frames = [] try: - container = av.open(filename, metadata_errors="ignore") + with av.open(filename, metadata_errors="ignore") as container: + if container.streams.video: + video_frames = _read_from_stream( + container, + start_pts, + end_pts, + pts_unit, + container.streams.video[0], + {"video": 0}, + ) + video_fps = container.streams.video[0].average_rate + # guard against potentially corrupted files + if video_fps is not None: + info["video_fps"] = float(video_fps) + + if container.streams.audio: + audio_frames = _read_from_stream( + container, + start_pts, + end_pts, + pts_unit, + container.streams.audio[0], + {"audio": 0}, + ) + info["audio_fps"] = container.streams.audio[0].rate + except av.AVError: # TODO raise a warning? pass - else: - if container.streams.video: - video_frames = _read_from_stream( - container, - start_pts, - end_pts, - pts_unit, - container.streams.video[0], - {"video": 0}, - ) - video_fps = container.streams.video[0].average_rate - # guard against potentially corrupted files - if video_fps is not None: - info["video_fps"] = float(video_fps) - - if container.streams.audio: - audio_frames = _read_from_stream( - container, - start_pts, - end_pts, - pts_unit, - container.streams.audio[0], - {"audio": 0}, - ) - info["audio_fps"] = container.streams.audio[0].rate - - container.close() vframes = [frame.to_rgb().to_ndarray() for frame in video_frames] aframes = [frame.to_ndarray() for frame in audio_frames] @@ -293,6 +287,14 @@ def _can_read_timestamps_from_packets(container): return False +def _decode_video_timestamps(container): + if _can_read_timestamps_from_packets(container): + # fast path + return [x.pts for x in container.demux(video=0) if x.pts is not None] + else: + return [x.pts for x in container.decode(video=0) if x.pts is not None] + + def read_video_timestamps(filename, pts_unit="pts"): """ List the video frames timestamps. @@ -326,26 +328,18 @@ def read_video_timestamps(filename, pts_unit="pts"): pts = [] try: - container = av.open(filename, metadata_errors="ignore") + with av.open(filename, metadata_errors="ignore") as container: + if container.streams.video: + video_stream = container.streams.video[0] + video_time_base = video_stream.time_base + try: + pts = _decode_video_timestamps(container) + except av.AVError: + warnings.warn(f"Failed decoding frames for file {filename}") + video_fps = float(video_stream.average_rate) except av.AVError: # TODO add a warning pass - else: - if container.streams.video: - video_stream = container.streams.video[0] - video_time_base = video_stream.time_base - try: - if _can_read_timestamps_from_packets(container): - # fast path - pts = [x.pts for x in container.demux(video=0) if x.pts is not None] - else: - pts = [ - x.pts for x in container.decode(video=0) if x.pts is not None - ] - except av.AVError: - warnings.warn(f"Failed decoding frames for file {filename}") - video_fps = float(video_stream.average_rate) - container.close() pts.sort() From b6f55ed899fdd8a6b73c7a666d30e9afc8f5f314 Mon Sep 17 00:00:00 2001 From: Zhiqiang Wang Date: Tue, 23 Jun 2020 17:13:16 +0800 Subject: [PATCH 03/51] Update torchvision ops in doc (#2341) * Add Deformable Convolution in Doc * Add PSRoIPool and PSRoIAlign in Doc --- docs/source/ops.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/source/ops.rst b/docs/source/ops.rst index ec87d02556e..675aac5bc59 100644 --- a/docs/source/ops.rst +++ b/docs/source/ops.rst @@ -11,7 +11,15 @@ torchvision.ops .. autofunction:: nms .. autofunction:: roi_align +.. autofunction:: ps_roi_align .. autofunction:: roi_pool +.. autofunction:: ps_roi_pool +.. autofunction:: deform_conv2d .. autoclass:: RoIAlign +.. autoclass:: PSRoIAlign .. autoclass:: RoIPool +.. autoclass:: PSRoIPool +.. autoclass:: DeformConv2d +.. autoclass:: MultiScaleRoIAlign +.. autoclass:: FeaturePyramidNetwork From f554f2d6eb39db0b2f7f9b37980e1889a3029422 Mon Sep 17 00:00:00 2001 From: joerg-de <61417801+joerg-de@users.noreply.github.com> Date: Tue, 23 Jun 2020 11:52:18 +0200 Subject: [PATCH 04/51] added fill to __repr__ of RandomRotation (#2340) --- torchvision/transforms/transforms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index d54aa5099f2..ba92bbccf6c 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -1081,6 +1081,8 @@ def __repr__(self): format_string += ', expand={0}'.format(self.expand) if self.center is not None: format_string += ', center={0}'.format(self.center) + if self.fill is not None: + format_string += ', fill={0}'.format(self.fill) format_string += ')' return format_string From e92b5155d7d78363826a822ccacf7c046d19245a Mon Sep 17 00:00:00 2001 From: Zhiqiang Wang Date: Tue, 23 Jun 2020 18:29:22 +0800 Subject: [PATCH 05/51] Update the statement of supporting torchscript ops (#2343) * Update docs * Update docs/source/ops.rst Co-authored-by: Francisco Massa Co-authored-by: Francisco Massa --- docs/source/ops.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/ops.rst b/docs/source/ops.rst index 675aac5bc59..8c619334582 100644 --- a/docs/source/ops.rst +++ b/docs/source/ops.rst @@ -6,7 +6,7 @@ torchvision.ops :mod:`torchvision.ops` implements operators that are specific for Computer Vision. .. note:: - Those operators currently do not support TorchScript. + All operators have native support for TorchScript. .. autofunction:: nms From 2dad9c75137fd4459a8517d631b25f18f96f0625 Mon Sep 17 00:00:00 2001 From: Eli Uriegas <1700823+seemethere@users.noreply.github.com> Date: Fri, 26 Jun 2020 01:56:31 -0700 Subject: [PATCH 06/51] bump nightlies to 0.8.0 (#2351) Signed-off-by: Eli Uriegas --- packaging/build_conda.sh | 2 +- packaging/build_wheel.sh | 2 +- packaging/windows/internal/nightly_defaults.bat | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packaging/build_conda.sh b/packaging/build_conda.sh index ba2f25d2ff1..c4f5cc860a2 100755 --- a/packaging/build_conda.sh +++ b/packaging/build_conda.sh @@ -5,7 +5,7 @@ script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" . "$script_dir/pkg_helpers.bash" export BUILD_TYPE=conda -setup_env 0.7.0 +setup_env 0.8.0 export SOURCE_ROOT_DIR="$PWD" setup_conda_pytorch_constraint setup_conda_cudatoolkit_constraint diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index 3b0e0f46cec..b8b91932066 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -5,7 +5,7 @@ script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" . "$script_dir/pkg_helpers.bash" export BUILD_TYPE=wheel -setup_env 0.7.0 +setup_env 0.8.0 setup_wheel_python pip_install numpy pyyaml future "ninja==1.9.0.post1" setup_pip_pytorch_version diff --git a/packaging/windows/internal/nightly_defaults.bat b/packaging/windows/internal/nightly_defaults.bat index 49a79e2b60e..8bdac633adf 100644 --- a/packaging/windows/internal/nightly_defaults.bat +++ b/packaging/windows/internal/nightly_defaults.bat @@ -144,7 +144,7 @@ if "%CUDA_VERSION%" == "cpu" ( :: pytorch-nightly==1.0.0.dev20180908 :: or in manylinux like :: torch_nightly-1.0.0.dev20180908-cp27-cp27m-linux_x86_64.whl -if "%TORCHVISION_BUILD_VERSION%" == "" set TORCHVISION_BUILD_VERSION=0.7.0.dev%NIGHTLIES_DATE_COMPACT% +if "%TORCHVISION_BUILD_VERSION%" == "" set TORCHVISION_BUILD_VERSION=0.8.0.dev%NIGHTLIES_DATE_COMPACT% if "%~1" == "Wheels" ( if not "%CUDA_VERSION%" == "102" ( diff --git a/setup.py b/setup.py index 85a692120b3..0620193b3a7 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ def get_dist(pkgname): return None -version = '0.7.0a0' +version = '0.8.0a0' sha = 'Unknown' package_name = 'torchvision' From 8dc14cfe324b1eae17ba53abc2f503eb2b6d5945 Mon Sep 17 00:00:00 2001 From: vfdev Date: Fri, 26 Jun 2020 11:03:27 +0200 Subject: [PATCH 07/51] Unified Pad and F.pad opertion for PIL and Tensor inputs (#2345) * [WIP] Add Tensor implementation for pad * Unified Pad and F.pad opertion for PIL and Tensor inputs * Added another test and improved docstring * Updates according to the review * Cosmetics and replaced f-string by "".format * Updated docstring - added compatibility support for padding as [value, ] for functional_pil.pad Co-authored-by: Francisco Massa --- test/test_functional_tensor.py | 30 +++++- test/test_transforms_tensor.py | 54 ++++++++-- torchvision/transforms/functional.py | 102 +++++------------- torchvision/transforms/functional_pil.py | 108 +++++++++++++++++++- torchvision/transforms/functional_tensor.py | 63 +++++++++++- torchvision/transforms/transforms.py | 40 +++++--- 6 files changed, 293 insertions(+), 104 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index 1a8c77c827f..07a699345bd 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -1,17 +1,27 @@ import torch -from torch import Tensor import torchvision.transforms as transforms import torchvision.transforms.functional_tensor as F_t +import torchvision.transforms.functional_pil as F_pil import torchvision.transforms.functional as F import numpy as np import unittest import random import colorsys -from torch.jit.annotations import Optional, List, BroadcastingList2, Tuple + +from PIL import Image class Tester(unittest.TestCase): + def _create_data(self, height=3, width=3, channels=3): + tensor = torch.randint(0, 255, (channels, height, width), dtype=torch.uint8) + pil_img = Image.fromarray(tensor.permute(1, 2, 0).contiguous().numpy()) + return tensor, pil_img + + def compareTensorToPIL(self, tensor, pil_image, msg=None): + pil_tensor = torch.as_tensor(np.array(pil_image).transpose((2, 0, 1))) + self.assertTrue(tensor.equal(pil_tensor), msg) + def test_vflip(self): script_vflip = torch.jit.script(F_t.vflip) img_tensor = torch.randn(3, 16, 16) @@ -234,6 +244,22 @@ def test_ten_crop(self): for cropped_script_img, cropped_tensor_img in zip(cropped_script, cropped_tensor): self.assertTrue(torch.equal(cropped_script_img, cropped_tensor_img)) + def test_pad(self): + script_fn = torch.jit.script(F_t.pad) + tensor, pil_img = self._create_data(7, 8) + for pad in [1, [1, ], [0, 1], (2, 2), [1, 0, 1, 2]]: + padding_mode = "constant" + for fill in [0, 10, 20]: + pad_tensor = F_t.pad(tensor, pad, fill=fill, padding_mode=padding_mode) + pad_pil_img = F_pil.pad(pil_img, pad, fill=fill, padding_mode=padding_mode) + self.compareTensorToPIL(pad_tensor, pad_pil_img, msg="{}, {}".format(pad, fill)) + if isinstance(pad, int): + script_pad = [pad, ] + else: + script_pad = pad + pad_tensor_script = script_fn(tensor, script_pad, fill=fill, padding_mode=padding_mode) + self.assertTrue(pad_tensor.equal(pad_tensor_script), msg="{}, {}".format(pad, fill)) + if __name__ == '__main__': unittest.main() diff --git a/test/test_transforms_tensor.py b/test/test_transforms_tensor.py index 7791dd8b4f9..1479602b534 100644 --- a/test/test_transforms_tensor.py +++ b/test/test_transforms_tensor.py @@ -18,26 +18,38 @@ def compareTensorToPIL(self, tensor, pil_image): pil_tensor = torch.as_tensor(np.array(pil_image).transpose((2, 0, 1))) self.assertTrue(tensor.equal(pil_tensor)) - def _test_flip(self, func, method): - tensor, pil_img = self._create_data() - flip_tensor = getattr(F, func)(tensor) - flip_pil_img = getattr(F, func)(pil_img) - self.compareTensorToPIL(flip_tensor, flip_pil_img) + def _test_functional_geom_op(self, func, fn_kwargs): + if fn_kwargs is None: + fn_kwargs = {} + tensor, pil_img = self._create_data(height=10, width=10) + transformed_tensor = getattr(F, func)(tensor, **fn_kwargs) + transformed_pil_img = getattr(F, func)(pil_img, **fn_kwargs) + self.compareTensorToPIL(transformed_tensor, transformed_pil_img) + + def _test_geom_op(self, func, method, fn_kwargs=None, meth_kwargs=None): + if fn_kwargs is None: + fn_kwargs = {} + if meth_kwargs is None: + meth_kwargs = {} + tensor, pil_img = self._create_data(height=10, width=10) + transformed_tensor = getattr(F, func)(tensor, **fn_kwargs) + transformed_pil_img = getattr(F, func)(pil_img, **fn_kwargs) + self.compareTensorToPIL(transformed_tensor, transformed_pil_img) scripted_fn = torch.jit.script(getattr(F, func)) - flip_tensor_script = scripted_fn(tensor) - self.assertTrue(flip_tensor.equal(flip_tensor_script)) + transformed_tensor_script = scripted_fn(tensor, **fn_kwargs) + self.assertTrue(transformed_tensor.equal(transformed_tensor_script)) # test for class interface - f = getattr(T, method)() + f = getattr(T, method)(**meth_kwargs) scripted_fn = torch.jit.script(f) scripted_fn(tensor) def test_random_horizontal_flip(self): - self._test_flip('hflip', 'RandomHorizontalFlip') + self._test_geom_op('hflip', 'RandomHorizontalFlip') def test_random_vertical_flip(self): - self._test_flip('vflip', 'RandomVerticalFlip') + self._test_geom_op('vflip', 'RandomVerticalFlip') def test_adjustments(self): fns = ['adjust_brightness', 'adjust_contrast', 'adjust_saturation'] @@ -65,6 +77,28 @@ def test_adjustments(self): self.assertLess(max_diff, 5 / 255 + 1e-5) self.assertLess(max_diff_scripted, 5 / 255 + 1e-5) + def test_pad(self): + + # Test functional.pad (PIL and Tensor) with padding as single int + self._test_functional_geom_op( + "pad", fn_kwargs={"padding": 2, "fill": 0, "padding_mode": "constant"} + ) + # Test functional.pad and transforms.Pad with padding as [int, ] + fn_kwargs = meth_kwargs = {"padding": [2, ], "fill": 0, "padding_mode": "constant"} + self._test_geom_op( + "pad", "Pad", fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + # Test functional.pad and transforms.Pad with padding as list + fn_kwargs = meth_kwargs = {"padding": [4, 4], "fill": 0, "padding_mode": "constant"} + self._test_geom_op( + "pad", "Pad", fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + # Test functional.pad and transforms.Pad with padding as tuple + fn_kwargs = meth_kwargs = {"padding": (2, 2, 2, 2), "fill": 127, "padding_mode": "constant"} + self._test_geom_op( + "pad", "Pad", fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + if __name__ == '__main__': unittest.main() diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 5d8549ea883..06a54c6aa5f 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -1,16 +1,20 @@ -import torch -from torch import Tensor import math +import numbers +import warnings +from collections.abc import Iterable + +import numpy as np +from numpy import sin, cos, tan from PIL import Image, ImageOps, ImageEnhance, __version__ as PILLOW_VERSION + +import torch +from torch import Tensor +from torch.jit.annotations import List + try: import accimage except ImportError: accimage = None -import numpy as np -from numpy import sin, cos, tan -import numbers -from collections.abc import Sequence, Iterable -import warnings from . import functional_pil as F_pil from . import functional_tensor as F_t @@ -342,20 +346,24 @@ def scale(*args, **kwargs): return resize(*args, **kwargs) -def pad(img, padding, fill=0, padding_mode='constant'): - r"""Pad the given PIL Image on all sides with specified padding mode and fill value. +def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "constant") -> Tensor: + r"""Pad the given image on all sides with the given "pad" value. + The image can be a PIL Image or a torch Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions Args: - img (PIL Image): Image to be padded. - padding (int or tuple): Padding on each border. If a single int is provided this + img (PIL Image or Tensor): Image to be padded. + padding (int or tuple or list): Padding on each border. If a single int is provided this is used to pad all borders. If tuple of length 2 is provided this is the padding on left/right and top/bottom respectively. If a tuple of length 4 is provided - this is the padding for the left, top, right and bottom borders - respectively. - fill: Pixel fill value for constant fill. Default is 0. If a tuple of + this is the padding for the left, top, right and bottom borders respectively. + In torchscript mode padding as single int is not supported, use a tuple or + list of length 1: ``[padding, ]``. + fill (int or str or tuple): Pixel fill value for constant fill. Default is 0. If a tuple of length 3, it is used to fill R, G, B channels respectively. - This value is only used when the padding_mode is constant + This value is only used when the padding_mode is constant. Only int value is supported for Tensors. padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant. + Only "constant" is supported for Tensors as of now. - constant: pads with a constant value, this value is specified with fill @@ -372,68 +380,12 @@ def pad(img, padding, fill=0, padding_mode='constant'): will result in [2, 1, 1, 2, 3, 4, 4, 3] Returns: - PIL Image: Padded image. + PIL Image or Tensor: Padded image. """ - if not _is_pil_image(img): - raise TypeError('img should be PIL Image. Got {}'.format(type(img))) - - if not isinstance(padding, (numbers.Number, tuple)): - raise TypeError('Got inappropriate padding arg') - if not isinstance(fill, (numbers.Number, str, tuple)): - raise TypeError('Got inappropriate fill arg') - if not isinstance(padding_mode, str): - raise TypeError('Got inappropriate padding_mode arg') - - if isinstance(padding, Sequence) and len(padding) not in [2, 4]: - raise ValueError("Padding must be an int or a 2, or 4 element tuple, not a " + - "{} element tuple".format(len(padding))) - - assert padding_mode in ['constant', 'edge', 'reflect', 'symmetric'], \ - 'Padding mode should be either constant, edge, reflect or symmetric' - - if padding_mode == 'constant': - if isinstance(fill, numbers.Number): - fill = (fill,) * len(img.getbands()) - if len(fill) != len(img.getbands()): - raise ValueError('fill should have the same number of elements ' - 'as the number of channels in the image ' - '({}), got {} instead'.format(len(img.getbands()), len(fill))) - if img.mode == 'P': - palette = img.getpalette() - image = ImageOps.expand(img, border=padding, fill=fill) - image.putpalette(palette) - return image - - return ImageOps.expand(img, border=padding, fill=fill) - else: - if isinstance(padding, int): - pad_left = pad_right = pad_top = pad_bottom = padding - if isinstance(padding, Sequence) and len(padding) == 2: - pad_left = pad_right = padding[0] - pad_top = pad_bottom = padding[1] - if isinstance(padding, Sequence) and len(padding) == 4: - pad_left = padding[0] - pad_top = padding[1] - pad_right = padding[2] - pad_bottom = padding[3] - - if img.mode == 'P': - palette = img.getpalette() - img = np.asarray(img) - img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right)), padding_mode) - img = Image.fromarray(img) - img.putpalette(palette) - return img - - img = np.asarray(img) - # RGB image - if len(img.shape) == 3: - img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)), padding_mode) - # Grayscale image - if len(img.shape) == 2: - img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right)), padding_mode) + if not isinstance(img, torch.Tensor): + return F_pil.pad(img, padding=padding, fill=fill, padding_mode=padding_mode) - return Image.fromarray(img) + return F_t.pad(img, padding=padding, fill=fill, padding_mode=padding_mode) def crop(img, top, left, height, width): diff --git a/torchvision/transforms/functional_pil.py b/torchvision/transforms/functional_pil.py index 84e27e79040..3786d0e31a7 100644 --- a/torchvision/transforms/functional_pil.py +++ b/torchvision/transforms/functional_pil.py @@ -1,9 +1,11 @@ +import numbers + import torch try: import accimage except ImportError: accimage = None -from PIL import Image, ImageOps, ImageEnhance, __version__ as PILLOW_VERSION +from PIL import Image, ImageOps, ImageEnhance import numpy as np @@ -152,3 +154,107 @@ def adjust_hue(img, hue_factor): img = Image.merge('HSV', (h, s, v)).convert(input_mode) return img + + +@torch.jit.unused +def pad(img, padding, fill=0, padding_mode="constant"): + r"""Pad the given PIL.Image on all sides with the given "pad" value. + + Args: + img (PIL Image): Image to be padded. + padding (int or tuple or list): Padding on each border. If a single int is provided this + is used to pad all borders. If a tuple or list of length 2 is provided this is the padding + on left/right and top/bottom respectively. If a tuple or list of length 4 is provided + this is the padding for the left, top, right and bottom borders respectively. For compatibility reasons + with ``functional_tensor.pad``, if a tuple or list of length 1 is provided, it is interpreted as + a single int. + fill (int or str or tuple): Pixel fill value for constant fill. Default is 0. If a tuple of + length 3, it is used to fill R, G, B channels respectively. + This value is only used when the padding_mode is constant. + padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant. + + - constant: pads with a constant value, this value is specified with fill + + - edge: pads with the last value on the edge of the image + + - reflect: pads with reflection of image (without repeating the last value on the edge) + + padding [1, 2, 3, 4] with 2 elements on both sides in reflect mode + will result in [3, 2, 1, 2, 3, 4, 3, 2] + + - symmetric: pads with reflection of image (repeating the last value on the edge) + + padding [1, 2, 3, 4] with 2 elements on both sides in symmetric mode + will result in [2, 1, 1, 2, 3, 4, 4, 3] + + Returns: + PIL Image: Padded image. + """ + + if not _is_pil_image(img): + raise TypeError("img should be PIL Image. Got {}".format(type(img))) + + if not isinstance(padding, (numbers.Number, tuple, list)): + raise TypeError("Got inappropriate padding arg") + if not isinstance(fill, (numbers.Number, str, tuple)): + raise TypeError("Got inappropriate fill arg") + if not isinstance(padding_mode, str): + raise TypeError("Got inappropriate padding_mode arg") + + if isinstance(padding, list): + padding = tuple(padding) + + if isinstance(padding, tuple) and len(padding) not in [1, 2, 4]: + raise ValueError("Padding must be an int or a 1, 2, or 4 element tuple, not a " + + "{} element tuple".format(len(padding))) + + if isinstance(padding, tuple) and len(padding) == 1: + # Compatibility with `functional_tensor.pad` + padding = padding[0] + + if padding_mode not in ["constant", "edge", "reflect", "symmetric"]: + raise ValueError("Padding mode should be either constant, edge, reflect or symmetric") + + if padding_mode == "constant": + if isinstance(fill, numbers.Number): + fill = (fill,) * len(img.getbands()) + if len(fill) != len(img.getbands()): + raise ValueError("fill should have the same number of elements " + "as the number of channels in the image " + "({}), got {} instead".format(len(img.getbands()), len(fill))) + if img.mode == "P": + palette = img.getpalette() + image = ImageOps.expand(img, border=padding, fill=fill) + image.putpalette(palette) + return image + + return ImageOps.expand(img, border=padding, fill=fill) + else: + if isinstance(padding, int): + pad_left = pad_right = pad_top = pad_bottom = padding + if isinstance(padding, tuple) and len(padding) == 2: + pad_left = pad_right = padding[0] + pad_top = pad_bottom = padding[1] + if isinstance(padding, tuple) and len(padding) == 4: + pad_left = padding[0] + pad_top = padding[1] + pad_right = padding[2] + pad_bottom = padding[3] + + if img.mode == 'P': + palette = img.getpalette() + img = np.asarray(img) + img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right)), padding_mode) + img = Image.fromarray(img) + img.putpalette(palette) + return img + + img = np.asarray(img) + # RGB image + if len(img.shape) == 3: + img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right), (0, 0)), padding_mode) + # Grayscale image + if len(img.shape) == 2: + img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right)), padding_mode) + + return Image.fromarray(img) diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index 89440701d17..56703d0a1fd 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -1,6 +1,6 @@ import torch from torch import Tensor -from torch.jit.annotations import Optional, List, BroadcastingList2, Tuple +from torch.jit.annotations import List, BroadcastingList2 def _is_tensor_a_torch_image(input): @@ -327,3 +327,64 @@ def _hsv2rgb(img): a4 = torch.stack((a1, a2, a3)) return torch.einsum("ijk, xijk -> xjk", mask.to(dtype=img.dtype), a4) + + +def pad(img: Tensor, padding: List[int], fill: int, padding_mode: str = "constant") -> Tensor: + r"""Pad the given Tensor Image on all sides with specified padding mode and fill value. + + Args: + img (Tensor): Image to be padded. + padding (int or tuple or list): Padding on each border. If a single int is provided this + is used to pad all borders. If a tuple or list of length 2 is provided this is the padding + on left/right and top/bottom respectively. If a tuple or list of length 4 is provided + this is the padding for the left, top, right and bottom borders + respectively. In torchscript mode padding as single int is not supported, use a tuple or + list of length 1: ``[padding, ]``. + fill (int): Pixel fill value for constant fill. Default is 0. + This value is only used when the padding_mode is constant + padding_mode (str): Type of padding. Only "constant" is supported for Tensors as of now. + + - constant: pads with a constant value, this value is specified with fill + + Returns: + Tensor: Padded image. + """ + if not _is_tensor_a_torch_image(img): + raise TypeError("tensor is not a torch image.") + + if not isinstance(padding, (int, tuple, list)): + raise TypeError("Got inappropriate padding arg") + if not isinstance(fill, (int, float)): + raise TypeError("Got inappropriate fill arg") + if not isinstance(padding_mode, str): + raise TypeError("Got inappropriate padding_mode arg") + + if isinstance(padding, tuple): + padding = list(padding) + + if isinstance(padding, list) and len(padding) not in [1, 2, 4]: + raise ValueError("Padding must be an int or a 1, 2, or 4 element tuple, not a " + + "{} element tuple".format(len(padding))) + + if padding_mode not in ["constant", ]: + raise ValueError("Only constant padding_mode supported for torch tensors") + + if isinstance(padding, int): + if torch.jit.is_scripting(): + raise ValueError("padding can't be an int while torchscripting, set it as a list [value, ]") + pad_left = pad_right = pad_top = pad_bottom = padding + elif len(padding) == 1: + pad_left = pad_right = pad_top = pad_bottom = padding[0] + elif len(padding) == 2: + pad_left = pad_right = padding[0] + pad_top = pad_bottom = padding[1] + else: + pad_left = padding[0] + pad_top = padding[1] + pad_right = padding[2] + pad_bottom = padding[3] + + p = [pad_left, pad_right, pad_top, pad_bottom] + + img = torch.nn.functional.pad(img, p, mode=padding_mode, value=float(fill)) + return img diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index ba92bbccf6c..edf68f63127 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -287,20 +287,23 @@ def __repr__(self): return self.__class__.__name__ + '(size={0})'.format(self.size) -class Pad(object): - """Pad the given PIL Image on all sides with the given "pad" value. +class Pad(torch.nn.Module): + """Pad the given image on all sides with the given "pad" value. + The image can be a PIL Image or a torch Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions Args: - padding (int or tuple): Padding on each border. If a single int is provided this + padding (int or tuple or list): Padding on each border. If a single int is provided this is used to pad all borders. If tuple of length 2 is provided this is the padding on left/right and top/bottom respectively. If a tuple of length 4 is provided - this is the padding for the left, top, right and bottom borders - respectively. + this is the padding for the left, top, right and bottom borders respectively. + In torchscript mode padding as single int is not supported, use a tuple or + list of length 1: ``[padding, ]``. fill (int or tuple): Pixel fill value for constant fill. Default is 0. If a tuple of length 3, it is used to fill R, G, B channels respectively. This value is only used when the padding_mode is constant padding_mode (str): Type of padding. Should be: constant, edge, reflect or symmetric. - Default is constant. + Default is constant. Only "constant" is supported for Tensors as of now. - constant: pads with a constant value, this value is specified with fill @@ -317,25 +320,32 @@ class Pad(object): will result in [2, 1, 1, 2, 3, 4, 4, 3] """ - def __init__(self, padding, fill=0, padding_mode='constant'): - assert isinstance(padding, (numbers.Number, tuple)) - assert isinstance(fill, (numbers.Number, str, tuple)) - assert padding_mode in ['constant', 'edge', 'reflect', 'symmetric'] - if isinstance(padding, Sequence) and len(padding) not in [2, 4]: - raise ValueError("Padding must be an int or a 2, or 4 element tuple, not a " + + def __init__(self, padding, fill=0, padding_mode="constant"): + super().__init__() + if not isinstance(padding, (numbers.Number, tuple, list)): + raise TypeError("Got inappropriate padding arg") + + if not isinstance(fill, (numbers.Number, str, tuple)): + raise TypeError("Got inappropriate fill arg") + + if padding_mode not in ["constant", "edge", "reflect", "symmetric"]: + raise ValueError("Padding mode should be either constant, edge, reflect or symmetric") + + if isinstance(padding, Sequence) and len(padding) not in [1, 2, 4]: + raise ValueError("Padding must be an int or a 1, 2, or 4 element tuple, not a " + "{} element tuple".format(len(padding))) self.padding = padding self.fill = fill self.padding_mode = padding_mode - def __call__(self, img): + def forward(self, img): """ Args: - img (PIL Image): Image to be padded. + img (PIL Image or Tensor): Image to be padded. Returns: - PIL Image: Padded image. + PIL Image or Tensor: Padded image. """ return F.pad(img, self.padding, self.fill, self.padding_mode) From 26dd3f8c8f9fd7f19f9929264634ad9338978e40 Mon Sep 17 00:00:00 2001 From: vfdev Date: Fri, 26 Jun 2020 13:53:54 +0200 Subject: [PATCH 08/51] Updated travis-ci (#2346) - xenial -> bionic - fixed warnings: added os and replaced matrix by jobs --- .travis.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 53f66794c48..ec25bdb8677 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,11 @@ language: python -dist: xenial -matrix: +os: + - linux + +dist: bionic + +jobs: include: - python: "3.6" env: IMAGE_BACKEND=Pillow-SIMD From 1a04d3c265679e1a508e7cd627006aaa9ef1ccfb Mon Sep 17 00:00:00 2001 From: eellison Date: Fri, 26 Jun 2020 06:27:47 -0700 Subject: [PATCH 09/51] Try remove eager scripting calls (#2248) * Try remove eager scripting calls * remove script call Co-authored-by: eellison Co-authored-by: Francisco Massa --- torchvision/models/detection/_utils.py | 2 +- torchvision/models/detection/roi_heads.py | 4 ++-- torchvision/ops/boxes.py | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/torchvision/models/detection/_utils.py b/torchvision/models/detection/_utils.py index 4b65ffa4a4e..3595114f24d 100644 --- a/torchvision/models/detection/_utils.py +++ b/torchvision/models/detection/_utils.py @@ -75,7 +75,7 @@ def __call__(self, matched_idxs): return pos_idx, neg_idx -@torch.jit.script +@torch.jit._script_if_tracing def encode_boxes(reference_boxes, proposals, weights): # type: (torch.Tensor, torch.Tensor, torch.Tensor) -> torch.Tensor """ diff --git a/torchvision/models/detection/roi_heads.py b/torchvision/models/detection/roi_heads.py index 19cc15a8cc0..82ba6e8b5c0 100644 --- a/torchvision/models/detection/roi_heads.py +++ b/torchvision/models/detection/roi_heads.py @@ -205,7 +205,7 @@ def _onnx_heatmaps_to_keypoints(maps, maps_i, roi_map_width, roi_map_height, return xy_preds_i, end_scores_i -@torch.jit.script +@torch.jit._script_if_tracing def _onnx_heatmaps_to_keypoints_loop(maps, rois, widths_ceil, heights_ceil, widths, heights, offset_x, offset_y, num_keypoints): xy_preds = torch.zeros((0, 3, int(num_keypoints)), dtype=torch.float32, device=maps.device) @@ -451,7 +451,7 @@ def _onnx_paste_mask_in_image(mask, box, im_h, im_w): return im_mask -@torch.jit.script +@torch.jit._script_if_tracing def _onnx_paste_masks_in_image_loop(masks, boxes, im_h, im_w): res_append = torch.zeros(0, im_h, im_w) for i in range(masks.size(0)): diff --git a/torchvision/ops/boxes.py b/torchvision/ops/boxes.py index e7442f57352..c7d74db4500 100644 --- a/torchvision/ops/boxes.py +++ b/torchvision/ops/boxes.py @@ -4,7 +4,6 @@ import torchvision -@torch.jit.script def nms(boxes, scores, iou_threshold): # type: (Tensor, Tensor, float) -> Tensor """ @@ -41,7 +40,7 @@ def nms(boxes, scores, iou_threshold): return torch.ops.torchvision.nms(boxes, scores, iou_threshold) -@torch.jit.script +@torch.jit._script_if_tracing def batched_nms(boxes, scores, idxs, iou_threshold): # type: (Tensor, Tensor, Tensor, float) -> Tensor """ From efe90f84351e5bfae3ef92faa12f9ba8ade1ce84 Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Mon, 29 Jun 2020 09:08:15 -0400 Subject: [PATCH 10/51] Fix https://github.com/pytorch/vision/issues/2354 (#2355) --- torchvision/transforms/functional_tensor.py | 31 +++++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index 56703d0a1fd..ccba4d08369 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -288,22 +288,35 @@ def _blend(img1, img2, ratio): def _rgb2hsv(img): r, g, b = img.unbind(0) - maxc, _ = torch.max(img, dim=0) - minc, _ = torch.min(img, dim=0) + maxc = torch.max(img, dim=0).values + minc = torch.min(img, dim=0).values + + # The algorithm erases S and H channel where `maxc = minc`. This avoids NaN + # from happening in the results, because + # + S channel has division by `maxc`, which is zero only if `maxc = minc` + # + H channel has division by `(maxc - minc)`. + # + # Instead of overwriting NaN afterwards, we just prevent it from occuring so + # we don't need to deal with it in case we save the NaN in a buffer in + # backprop, if it is ever supported, but it doesn't hurt to do so. + eqc = maxc == minc cr = maxc - minc - s = cr / maxc - rc = (maxc - r) / cr - gc = (maxc - g) / cr - bc = (maxc - b) / cr + # Since `eqc => cr = 0`, replacing denominator with 1 when `eqc` is fine. + s = cr / torch.where(eqc, maxc.new_ones(()), maxc) + # Note that `eqc => maxc = minc = r = g = b`. So the following calculation + # of `h` would reduce to `bc - gc + 2 + rc - bc + 4 + rc - bc = 6` so it + # would not matter what values `rc`, `gc`, and `bc` have here, and thus + # replacing denominator with 1 when `eqc` is fine. + cr_divisor = torch.where(eqc, maxc.new_ones(()), cr) + rc = (maxc - r) / cr_divisor + gc = (maxc - g) / cr_divisor + bc = (maxc - b) / cr_divisor - t = (maxc != minc) - s = t * s hr = (maxc == r) * (bc - gc) hg = ((maxc == g) & (maxc != r)) * (2.0 + rc - bc) hb = ((maxc != g) & (maxc != r)) * (4.0 + gc - rc) h = (hr + hg + hb) - h = t * h h = torch.fmod((h / 6.0 + 1.0), 1.0) return torch.stack((h, s, maxc)) From dafd6d3527777461c9ffbac13e43360aaa32c31a Mon Sep 17 00:00:00 2001 From: Eli Uriegas <1700823+seemethere@users.noreply.github.com> Date: Mon, 29 Jun 2020 06:09:52 -0700 Subject: [PATCH 11/51] Cherry picking changes for release improvements (#2352) * .circleci: Add workflows to run on tag Signed-off-by: Eli Uriegas * packaging: Add backslash to escape Signed-off-by: Eli Uriegas --- .circleci/config.yml | 148 +++++++++++++++++++++++++++++++++++++ .circleci/regenerate.py | 11 ++- packaging/pkg_helpers.bash | 2 +- 3 files changed, 159 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 081337bdcf9..801905ee5e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -470,6 +470,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cpu python_version: '3.6' - binary_win_wheel_release: @@ -477,6 +479,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cu92 python_version: '3.6' - binary_win_wheel_release: @@ -484,6 +488,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cu101 python_version: '3.6' - binary_win_wheel_release: @@ -491,6 +497,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cu102 python_version: '3.6' - binary_win_wheel_release: @@ -498,6 +506,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cpu python_version: '3.7' - binary_win_wheel_release: @@ -505,6 +515,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cu92 python_version: '3.7' - binary_win_wheel_release: @@ -512,6 +524,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cu101 python_version: '3.7' - binary_win_wheel_release: @@ -519,6 +533,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cu102 python_version: '3.7' - binary_win_wheel_release: @@ -530,6 +546,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.8_cu92 python_version: '3.8' - binary_win_wheel_release: @@ -537,6 +555,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.8_cu101 python_version: '3.8' - binary_win_wheel_release: @@ -623,6 +643,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cpu python_version: '3.6' - binary_win_conda_release: @@ -630,6 +652,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cu92 python_version: '3.6' - binary_win_conda_release: @@ -637,6 +661,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cu101 python_version: '3.6' - binary_win_conda_release: @@ -644,6 +670,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cu102 python_version: '3.6' - binary_win_conda_release: @@ -651,6 +679,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cpu python_version: '3.7' - binary_win_conda_release: @@ -658,6 +688,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cu92 python_version: '3.7' - binary_win_conda_release: @@ -665,6 +697,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cu101 python_version: '3.7' - binary_win_conda_release: @@ -672,6 +706,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cu102 python_version: '3.7' - binary_win_conda_release: @@ -683,6 +719,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.8_cu92 python_version: '3.8' - binary_win_conda_release: @@ -690,6 +728,8 @@ workflows: filters: branches: only: master + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.8_cu101 python_version: '3.8' - binary_win_conda_release: @@ -723,6 +763,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.6_cpu python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda102 @@ -742,6 +784,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.6_cu92 python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda92 @@ -761,6 +805,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.6_cu101 python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda101 @@ -780,6 +826,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.6_cu102 python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda102 @@ -799,6 +847,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.7_cpu python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda102 @@ -818,6 +868,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.7_cu92 python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda92 @@ -837,6 +889,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.7_cu101 python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda101 @@ -856,6 +910,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.7_cu102 python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda102 @@ -875,6 +931,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.8_cpu python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 @@ -894,6 +952,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.8_cu92 python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda92 @@ -913,6 +973,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.8_cu101 python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda101 @@ -932,6 +994,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_wheel_py3.8_cu102 python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 @@ -951,6 +1015,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_macos_wheel_py3.6_cpu python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda102 @@ -970,6 +1036,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_macos_wheel_py3.7_cpu python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda102 @@ -989,6 +1057,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_macos_wheel_py3.8_cpu python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1008,6 +1078,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.6_cpu python_version: '3.6' - binary_wheel_upload: @@ -1026,6 +1098,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.6_cu92 python_version: '3.6' - binary_wheel_upload: @@ -1044,6 +1118,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.6_cu101 python_version: '3.6' - binary_wheel_upload: @@ -1062,6 +1138,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.6_cu102 python_version: '3.6' - binary_wheel_upload: @@ -1080,6 +1158,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.7_cpu python_version: '3.7' - binary_wheel_upload: @@ -1098,6 +1178,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.7_cu92 python_version: '3.7' - binary_wheel_upload: @@ -1116,6 +1198,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.7_cu101 python_version: '3.7' - binary_wheel_upload: @@ -1134,6 +1218,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.7_cu102 python_version: '3.7' - binary_wheel_upload: @@ -1152,6 +1238,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.8_cpu python_version: '3.8' - binary_wheel_upload: @@ -1170,6 +1258,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.8_cu92 python_version: '3.8' - binary_wheel_upload: @@ -1188,6 +1278,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.8_cu101 python_version: '3.8' - binary_wheel_upload: @@ -1206,6 +1298,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_wheel_py3.8_cu102 python_version: '3.8' - binary_wheel_upload: @@ -1224,6 +1318,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.6_cpu python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1242,6 +1338,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.6_cu92 python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda92 @@ -1260,6 +1358,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.6_cu101 python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda101 @@ -1278,6 +1378,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.6_cu102 python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1296,6 +1398,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.7_cpu python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1314,6 +1418,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.7_cu92 python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda92 @@ -1332,6 +1438,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.7_cu101 python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda101 @@ -1350,6 +1458,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.7_cu102 python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1368,6 +1478,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.8_cpu python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1386,6 +1498,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.8_cu92 python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda92 @@ -1404,6 +1518,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.8_cu101 python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda101 @@ -1422,6 +1538,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_linux_conda_py3.8_cu102 python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1440,6 +1558,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_macos_conda_py3.6_cpu python_version: '3.6' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1458,6 +1578,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_macos_conda_py3.7_cpu python_version: '3.7' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1476,6 +1598,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_macos_conda_py3.8_cpu python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 @@ -1494,6 +1618,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.6_cpu python_version: '3.6' - binary_conda_upload: @@ -1511,6 +1637,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.6_cu92 python_version: '3.6' - binary_conda_upload: @@ -1528,6 +1656,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.6_cu101 python_version: '3.6' - binary_conda_upload: @@ -1545,6 +1675,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.6_cu102 python_version: '3.6' - binary_conda_upload: @@ -1562,6 +1694,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.7_cpu python_version: '3.7' - binary_conda_upload: @@ -1579,6 +1713,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.7_cu92 python_version: '3.7' - binary_conda_upload: @@ -1596,6 +1732,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.7_cu101 python_version: '3.7' - binary_conda_upload: @@ -1613,6 +1751,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.7_cu102 python_version: '3.7' - binary_conda_upload: @@ -1630,6 +1770,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.8_cpu python_version: '3.8' - binary_conda_upload: @@ -1647,6 +1789,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.8_cu92 python_version: '3.8' - binary_conda_upload: @@ -1664,6 +1808,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.8_cu101 python_version: '3.8' - binary_conda_upload: @@ -1681,6 +1827,8 @@ workflows: filters: branches: only: nightly + tags: + only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: nightly_binary_win_conda_py3.8_cu102 python_version: '3.8' - binary_conda_upload: diff --git a/.circleci/regenerate.py b/.circleci/regenerate.py index 1e929242974..a0bfd84048d 100755 --- a/.circleci/regenerate.py +++ b/.circleci/regenerate.py @@ -86,7 +86,16 @@ def generate_base_workflow(base_workflow_name, python_version, cu_version, d["wheel_docker_image"] = get_manylinux_image(cu_version) if filter_branch is not None: - d["filters"] = {"branches": {"only": filter_branch}} + d["filters"] = { + "branches": { + "only": filter_branch + }, + "tags": { + # Using a raw string here to avoid having to escape + # anything + "only": r"/v[0-9]+(\.[0-9]+)*-rc[0-9]+/" + } + } w = f"binary_{os_type}_{btype}_release" if os_type == "win" else f"binary_{os_type}_{btype}" return {w: d} diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index d8bdfc61e5d..b262a0f5157 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -214,7 +214,7 @@ setup_pip_pytorch_version() { else pip_install "torch==$PYTORCH_VERSION$PYTORCH_VERSION_SUFFIX" \ -f https://download.pytorch.org/whl/torch_stable.html \ - -f https://download.pytorch.org/whl/test/torch_test.html + -f https://download.pytorch.org/whl/test/torch_test.html \ -f https://download.pytorch.org/whl/nightly/torch_nightly.html fi } From bb14c2bde9579a9a86f1b57e8422f42ec8526ae3 Mon Sep 17 00:00:00 2001 From: peterjc123 Date: Mon, 29 Jun 2020 21:29:56 +0800 Subject: [PATCH 12/51] Unpin ninja version (#2358) --- packaging/build_wheel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index b8b91932066..a075b3b3a00 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -7,7 +7,7 @@ script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" export BUILD_TYPE=wheel setup_env 0.8.0 setup_wheel_python -pip_install numpy pyyaml future "ninja==1.9.0.post1" +pip_install numpy pyyaml future ninja setup_pip_pytorch_version python setup.py clean if [[ "$OSTYPE" == "msys" ]]; then From 446eac615991933ed32919e821d323d5b0c73879 Mon Sep 17 00:00:00 2001 From: Francisco Massa Date: Mon, 29 Jun 2020 16:09:52 +0200 Subject: [PATCH 13/51] Fix torchhub due to numerical changes in torch.sum (#2361) --- test/test_hub.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/test_hub.py b/test/test_hub.py index 4ae9e51021b..29ae90014d1 100644 --- a/test/test_hub.py +++ b/test/test_hub.py @@ -13,7 +13,7 @@ def sum_of_model_parameters(model): return s -SUM_OF_PRETRAINED_RESNET18_PARAMS = -12703.99609375 +SUM_OF_PRETRAINED_RESNET18_PARAMS = -12703.9931640625 @unittest.skipIf('torchvision' in sys.modules, @@ -31,8 +31,9 @@ def test_load_from_github(self): 'resnet18', pretrained=True, progress=False) - self.assertEqual(sum_of_model_parameters(hub_model).item(), - SUM_OF_PRETRAINED_RESNET18_PARAMS) + self.assertAlmostEqual(sum_of_model_parameters(hub_model).item(), + SUM_OF_PRETRAINED_RESNET18_PARAMS, + places=2) def test_set_dir(self): temp_dir = tempfile.gettempdir() @@ -42,8 +43,9 @@ def test_set_dir(self): 'resnet18', pretrained=True, progress=False) - self.assertEqual(sum_of_model_parameters(hub_model).item(), - SUM_OF_PRETRAINED_RESNET18_PARAMS) + self.assertAlmostEqual(sum_of_model_parameters(hub_model).item(), + SUM_OF_PRETRAINED_RESNET18_PARAMS, + places=2) self.assertTrue(os.path.exists(temp_dir + '/pytorch_vision_master')) shutil.rmtree(temp_dir + '/pytorch_vision_master') From a99b6bd7440dfa4d0d362fffaccd5098deda27fa Mon Sep 17 00:00:00 2001 From: vfdev Date: Tue, 30 Jun 2020 15:23:50 +0200 Subject: [PATCH 14/51] Unified Tensor/PIL crop (#2342) * [WIP] Unified Tensor/PIL crop * Fixed misplaced type annotation * Fixed tests - crop with padding - other tests using mising private functions: _is_pil_image, _get_image_size * Unified CenterCrop and F.center_crop - sorted includes in transforms.py - used py3 annotations * Unified FiveCrop and F.five_crop * Improved tests and docs * Unified TenCrop and F.ten_crop * Removed useless typing in functional_pil * Updated code according to the review - removed useless torch.jit.export - added missing typing return type - fixed F.F_pil._is_pil_image -> F._is_pil_image * Removed useless torch.jit.export * Improved code according to the review --- test/test_transforms_tensor.py | 114 ++++++++++++ torchvision/transforms/functional.py | 169 +++++++++++------- torchvision/transforms/functional_pil.py | 30 +++- torchvision/transforms/functional_tensor.py | 72 ++++---- torchvision/transforms/transforms.py | 182 ++++++++++++-------- 5 files changed, 398 insertions(+), 169 deletions(-) diff --git a/test/test_transforms_tensor.py b/test/test_transforms_tensor.py index 1479602b534..1d2d92bb3ae 100644 --- a/test/test_transforms_tensor.py +++ b/test/test_transforms_tensor.py @@ -99,6 +99,120 @@ def test_pad(self): "pad", "Pad", fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs ) + def test_crop(self): + fn_kwargs = {"top": 2, "left": 3, "height": 4, "width": 5} + # Test transforms.RandomCrop with size and padding as tuple + meth_kwargs = {"size": (4, 5), "padding": (4, 4), "pad_if_needed": True, } + self._test_geom_op( + 'crop', 'RandomCrop', fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + + tensor = torch.randint(0, 255, (3, 10, 10), dtype=torch.uint8) + # Test torchscript of transforms.RandomCrop with size as int + f = T.RandomCrop(size=5) + scripted_fn = torch.jit.script(f) + scripted_fn(tensor) + + # Test torchscript of transforms.RandomCrop with size as [int, ] + f = T.RandomCrop(size=[5, ], padding=[2, ]) + scripted_fn = torch.jit.script(f) + scripted_fn(tensor) + + # Test torchscript of transforms.RandomCrop with size as list + f = T.RandomCrop(size=[6, 6]) + scripted_fn = torch.jit.script(f) + scripted_fn(tensor) + + def test_center_crop(self): + fn_kwargs = {"output_size": (4, 5)} + meth_kwargs = {"size": (4, 5), } + self._test_geom_op( + "center_crop", "CenterCrop", fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + fn_kwargs = {"output_size": (5,)} + meth_kwargs = {"size": (5, )} + self._test_geom_op( + "center_crop", "CenterCrop", fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + tensor = torch.randint(0, 255, (3, 10, 10), dtype=torch.uint8) + # Test torchscript of transforms.CenterCrop with size as int + f = T.CenterCrop(size=5) + scripted_fn = torch.jit.script(f) + scripted_fn(tensor) + + # Test torchscript of transforms.CenterCrop with size as [int, ] + f = T.CenterCrop(size=[5, ]) + scripted_fn = torch.jit.script(f) + scripted_fn(tensor) + + # Test torchscript of transforms.CenterCrop with size as tuple + f = T.CenterCrop(size=(6, 6)) + scripted_fn = torch.jit.script(f) + scripted_fn(tensor) + + def _test_geom_op_list_output(self, func, method, out_length, fn_kwargs=None, meth_kwargs=None): + if fn_kwargs is None: + fn_kwargs = {} + if meth_kwargs is None: + meth_kwargs = {} + tensor, pil_img = self._create_data(height=20, width=20) + transformed_t_list = getattr(F, func)(tensor, **fn_kwargs) + transformed_p_list = getattr(F, func)(pil_img, **fn_kwargs) + self.assertEqual(len(transformed_t_list), len(transformed_p_list)) + self.assertEqual(len(transformed_t_list), out_length) + for transformed_tensor, transformed_pil_img in zip(transformed_t_list, transformed_p_list): + self.compareTensorToPIL(transformed_tensor, transformed_pil_img) + + scripted_fn = torch.jit.script(getattr(F, func)) + transformed_t_list_script = scripted_fn(tensor.detach().clone(), **fn_kwargs) + self.assertEqual(len(transformed_t_list), len(transformed_t_list_script)) + self.assertEqual(len(transformed_t_list_script), out_length) + for transformed_tensor, transformed_tensor_script in zip(transformed_t_list, transformed_t_list_script): + self.assertTrue(transformed_tensor.equal(transformed_tensor_script), + msg="{} vs {}".format(transformed_tensor, transformed_tensor_script)) + + # test for class interface + f = getattr(T, method)(**meth_kwargs) + scripted_fn = torch.jit.script(f) + output = scripted_fn(tensor) + self.assertEqual(len(output), len(transformed_t_list_script)) + + def test_five_crop(self): + fn_kwargs = meth_kwargs = {"size": (5,)} + self._test_geom_op_list_output( + "five_crop", "FiveCrop", out_length=5, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + fn_kwargs = meth_kwargs = {"size": [5, ]} + self._test_geom_op_list_output( + "five_crop", "FiveCrop", out_length=5, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + fn_kwargs = meth_kwargs = {"size": (4, 5)} + self._test_geom_op_list_output( + "five_crop", "FiveCrop", out_length=5, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + fn_kwargs = meth_kwargs = {"size": [4, 5]} + self._test_geom_op_list_output( + "five_crop", "FiveCrop", out_length=5, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + + def test_ten_crop(self): + fn_kwargs = meth_kwargs = {"size": (5,)} + self._test_geom_op_list_output( + "ten_crop", "TenCrop", out_length=10, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + fn_kwargs = meth_kwargs = {"size": [5, ]} + self._test_geom_op_list_output( + "ten_crop", "TenCrop", out_length=10, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + fn_kwargs = meth_kwargs = {"size": (4, 5)} + self._test_geom_op_list_output( + "ten_crop", "TenCrop", out_length=10, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + fn_kwargs = meth_kwargs = {"size": [4, 5]} + self._test_geom_op_list_output( + "ten_crop", "TenCrop", out_length=10, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs + ) + if __name__ == '__main__': unittest.main() diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 06a54c6aa5f..cda26348552 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -2,6 +2,7 @@ import numbers import warnings from collections.abc import Iterable +from typing import Any import numpy as np from numpy import sin, cos, tan @@ -9,7 +10,7 @@ import torch from torch import Tensor -from torch.jit.annotations import List +from torch.jit.annotations import List, Tuple try: import accimage @@ -20,18 +21,25 @@ from . import functional_tensor as F_t -def _is_pil_image(img): - if accimage is not None: - return isinstance(img, (Image.Image, accimage.Image)) - else: - return isinstance(img, Image.Image) +_is_pil_image = F_pil._is_pil_image + + +def _get_image_size(img: Tensor) -> List[int]: + """Returns image sizea as (w, h) + """ + if isinstance(img, torch.Tensor): + return F_t._get_image_size(img) + return F_pil._get_image_size(img) -def _is_numpy(img): + +@torch.jit.unused +def _is_numpy(img: Any) -> bool: return isinstance(img, np.ndarray) -def _is_numpy_image(img): +@torch.jit.unused +def _is_numpy_image(img: Any) -> bool: return img.ndim in {2, 3} @@ -46,7 +54,7 @@ def to_tensor(pic): Returns: Tensor: Converted image. """ - if not(_is_pil_image(pic) or _is_numpy(pic)): + if not(F_pil._is_pil_image(pic) or _is_numpy(pic)): raise TypeError('pic should be PIL Image or ndarray. Got {}'.format(type(pic))) if _is_numpy(pic) and not _is_numpy_image(pic): @@ -101,7 +109,7 @@ def pil_to_tensor(pic): Returns: Tensor: Converted image. """ - if not(_is_pil_image(pic)): + if not(F_pil._is_pil_image(pic)): raise TypeError('pic should be PIL Image. Got {}'.format(type(pic))) if accimage is not None and isinstance(pic, accimage.Image): @@ -319,7 +327,7 @@ def resize(img, size, interpolation=Image.BILINEAR): Returns: PIL Image: Resized image. """ - if not _is_pil_image(img): + if not F_pil._is_pil_image(img): raise TypeError('img should be PIL Image. Got {}'.format(type(img))) if not (isinstance(size, int) or (isinstance(size, Iterable) and len(size) == 2)): raise TypeError('Got inappropriate size arg: {}'.format(size)) @@ -388,41 +396,58 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con return F_t.pad(img, padding=padding, fill=fill, padding_mode=padding_mode) -def crop(img, top, left, height, width): - """Crop the given PIL Image. +def crop(img: Tensor, top: int, left: int, height: int, width: int) -> Tensor: + """Crop the given image at specified location and output size. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading + dimensions Args: - img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image. + img (PIL Image or Tensor): Image to be cropped. (0,0) denotes the top left corner of the image. top (int): Vertical component of the top left corner of the crop box. left (int): Horizontal component of the top left corner of the crop box. height (int): Height of the crop box. width (int): Width of the crop box. Returns: - PIL Image: Cropped image. + PIL Image or Tensor: Cropped image. """ - if not _is_pil_image(img): - raise TypeError('img should be PIL Image. Got {}'.format(type(img))) - return img.crop((left, top, left + width, top + height)) + if not isinstance(img, torch.Tensor): + return F_pil.crop(img, top, left, height, width) + return F_t.crop(img, top, left, height, width) -def center_crop(img, output_size): - """Crop the given PIL Image and resize it to desired size. + +def center_crop(img: Tensor, output_size: List[int]) -> Tensor: + """Crops the given image at the center. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions Args: - img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image. - output_size (sequence or int): (height, width) of the crop box. If int, - it is used for both directions + img (PIL Image or Tensor): Image to be cropped. + output_size (sequence or int): (height, width) of the crop box. If int or sequence with single int + it is used for both directions. + Returns: - PIL Image: Cropped image. + PIL Image or Tensor: Cropped image. """ if isinstance(output_size, numbers.Number): output_size = (int(output_size), int(output_size)) - image_width, image_height = img.size + elif isinstance(output_size, (tuple, list)) and len(output_size) == 1: + output_size = (output_size[0], output_size[0]) + + image_width, image_height = _get_image_size(img) crop_height, crop_width = output_size - crop_top = int(round((image_height - crop_height) / 2.)) - crop_left = int(round((image_width - crop_width) / 2.)) + + # crop_top = int(round((image_height - crop_height) / 2.)) + # Result can be different between python func and scripted func + # Temporary workaround: + crop_top = int((image_height - crop_height + 1) * 0.5) + # crop_left = int(round((image_width - crop_width) / 2.)) + # Result can be different between python func and scripted func + # Temporary workaround: + crop_left = int((image_width - crop_width + 1) * 0.5) return crop(img, crop_top, crop_left, crop_height, crop_width) @@ -443,23 +468,23 @@ def resized_crop(img, top, left, height, width, size, interpolation=Image.BILINE Returns: PIL Image: Cropped image. """ - assert _is_pil_image(img), 'img should be PIL Image' + assert F_pil._is_pil_image(img), 'img should be PIL Image' img = crop(img, top, left, height, width) img = resize(img, size, interpolation) return img def hflip(img: Tensor) -> Tensor: - """Horizontally flip the given PIL Image or torch Tensor. + """Horizontally flip the given PIL Image or Tensor. Args: - img (PIL Image or Torch Tensor): Image to be flipped. If img + img (PIL Image or Tensor): Image to be flipped. If img is a Tensor, it is expected to be in [..., H, W] format, where ... means it can have an arbitrary number of trailing dimensions. Returns: - PIL Image: Horizontally flipped image. + PIL Image or Tensor: Horizontally flipped image. """ if not isinstance(img, torch.Tensor): return F_pil.hflip(img) @@ -512,8 +537,7 @@ def _get_perspective_coeffs(startpoints, endpoints): Args: List containing [top-left, top-right, bottom-right, bottom-left] of the original image, - List containing [top-left, top-right, bottom-right, bottom-left] of the transformed - image + List containing [top-left, top-right, bottom-right, bottom-left] of the transformed image Returns: octuple (a, b, c, d, e, f, g, h) for transforming each pixel. """ @@ -545,7 +569,7 @@ def perspective(img, startpoints, endpoints, interpolation=Image.BICUBIC, fill=N PIL Image: Perspectively transformed Image. """ - if not _is_pil_image(img): + if not F_pil._is_pil_image(img): raise TypeError('img should be PIL Image. Got {}'.format(type(img))) opts = _parse_fill(fill, img, '5.0.0') @@ -558,7 +582,7 @@ def vflip(img: Tensor) -> Tensor: """Vertically flip the given PIL Image or torch Tensor. Args: - img (PIL Image or Torch Tensor): Image to be flipped. If img + img (PIL Image or Tensor): Image to be flipped. If img is a Tensor, it is expected to be in [..., H, W] format, where ... means it can have an arbitrary number of trailing dimensions. @@ -572,17 +596,20 @@ def vflip(img: Tensor) -> Tensor: return F_t.vflip(img) -def five_crop(img, size): - """Crop the given PIL Image into four corners and the central crop. +def five_crop(img: Tensor, size: List[int]) -> Tuple[Tensor, Tensor, Tensor, Tensor, Tensor]: + """Crop the given image into four corners and the central crop. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions .. Note:: This transform returns a tuple of images and there may be a mismatch in the number of inputs and targets your ``Dataset`` returns. Args: - size (sequence or int): Desired output size of the crop. If size is an - int instead of sequence like (h, w), a square crop (size, size) is - made. + img (PIL Image or Tensor): Image to be cropped. + size (sequence or int): Desired output size of the crop. If size is an + int instead of sequence like (h, w), a square crop (size, size) is + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). Returns: tuple: tuple (tl, tr, bl, br, center) @@ -590,37 +617,44 @@ def five_crop(img, size): """ if isinstance(size, numbers.Number): size = (int(size), int(size)) - else: - assert len(size) == 2, "Please provide only two dimensions (h, w) for size." + elif isinstance(size, (tuple, list)) and len(size) == 1: + size = (size[0], size[0]) - image_width, image_height = img.size + if len(size) != 2: + raise ValueError("Please provide only two dimensions (h, w) for size.") + + image_width, image_height = _get_image_size(img) crop_height, crop_width = size if crop_width > image_width or crop_height > image_height: msg = "Requested crop size {} is bigger than input size {}" raise ValueError(msg.format(size, (image_height, image_width))) - tl = img.crop((0, 0, crop_width, crop_height)) - tr = img.crop((image_width - crop_width, 0, image_width, crop_height)) - bl = img.crop((0, image_height - crop_height, crop_width, image_height)) - br = img.crop((image_width - crop_width, image_height - crop_height, - image_width, image_height)) - center = center_crop(img, (crop_height, crop_width)) - return (tl, tr, bl, br, center) + tl = crop(img, 0, 0, crop_height, crop_width) + tr = crop(img, 0, image_width - crop_width, crop_height, crop_width) + bl = crop(img, image_height - crop_height, 0, crop_height, crop_width) + br = crop(img, image_height - crop_height, image_width - crop_width, crop_height, crop_width) + + center = center_crop(img, [crop_height, crop_width]) + + return tl, tr, bl, br, center -def ten_crop(img, size, vertical_flip=False): - """Generate ten cropped images from the given PIL Image. - Crop the given PIL Image into four corners and the central crop plus the +def ten_crop(img: Tensor, size: List[int], vertical_flip: bool = False) -> List[Tensor]: + """Generate ten cropped images from the given image. + Crop the given image into four corners and the central crop plus the flipped version of these (horizontal flipping is used by default). + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions .. Note:: This transform returns a tuple of images and there may be a mismatch in the number of inputs and targets your ``Dataset`` returns. Args: + img (PIL Image or Tensor): Image to be cropped. size (sequence or int): Desired output size of the crop. If size is an int instead of sequence like (h, w), a square crop (size, size) is - made. + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). vertical_flip (bool): Use vertical flipping instead of horizontal Returns: @@ -630,8 +664,11 @@ def ten_crop(img, size, vertical_flip=False): """ if isinstance(size, numbers.Number): size = (int(size), int(size)) - else: - assert len(size) == 2, "Please provide only two dimensions (h, w) for size." + elif isinstance(size, (tuple, list)) and len(size) == 1: + size = (size[0], size[0]) + + if len(size) != 2: + raise ValueError("Please provide only two dimensions (h, w) for size.") first_five = five_crop(img, size) @@ -648,13 +685,13 @@ def adjust_brightness(img: Tensor, brightness_factor: float) -> Tensor: """Adjust brightness of an Image. Args: - img (PIL Image or Torch Tensor): Image to be adjusted. + img (PIL Image or Tensor): Image to be adjusted. brightness_factor (float): How much to adjust the brightness. Can be any non negative number. 0 gives a black image, 1 gives the original image while 2 increases the brightness by a factor of 2. Returns: - PIL Image or Torch Tensor: Brightness adjusted image. + PIL Image or Tensor: Brightness adjusted image. """ if not isinstance(img, torch.Tensor): return F_pil.adjust_brightness(img, brightness_factor) @@ -666,13 +703,13 @@ def adjust_contrast(img: Tensor, contrast_factor: float) -> Tensor: """Adjust contrast of an Image. Args: - img (PIL Image or Torch Tensor): Image to be adjusted. + img (PIL Image or Tensor): Image to be adjusted. contrast_factor (float): How much to adjust the contrast. Can be any non negative number. 0 gives a solid gray image, 1 gives the original image while 2 increases the contrast by a factor of 2. Returns: - PIL Image or Torch Tensor: Contrast adjusted image. + PIL Image or Tensor: Contrast adjusted image. """ if not isinstance(img, torch.Tensor): return F_pil.adjust_contrast(img, contrast_factor) @@ -684,13 +721,13 @@ def adjust_saturation(img: Tensor, saturation_factor: float) -> Tensor: """Adjust color saturation of an image. Args: - img (PIL Image or Torch Tensor): Image to be adjusted. + img (PIL Image or Tensor): Image to be adjusted. saturation_factor (float): How much to adjust the saturation. 0 will give a black and white image, 1 will give the original image while 2 will enhance the saturation by a factor of 2. Returns: - PIL Image or Torch Tensor: Saturation adjusted image. + PIL Image or Tensor: Saturation adjusted image. """ if not isinstance(img, torch.Tensor): return F_pil.adjust_saturation(img, saturation_factor) @@ -749,7 +786,7 @@ def adjust_gamma(img, gamma, gain=1): while gamma smaller than 1 make dark regions lighter. gain (float): The constant multiplier. """ - if not _is_pil_image(img): + if not F_pil._is_pil_image(img): raise TypeError('img should be PIL Image. Got {}'.format(type(img))) if gamma < 0: @@ -789,7 +826,7 @@ def rotate(img, angle, resample=False, expand=False, center=None, fill=None): .. _filters: https://pillow.readthedocs.io/en/latest/handbook/concepts.html#filters """ - if not _is_pil_image(img): + if not F_pil._is_pil_image(img): raise TypeError('img should be PIL Image. Got {}'.format(type(img))) opts = _parse_fill(fill, img, '5.2.0') @@ -870,7 +907,7 @@ def affine(img, angle, translate, scale, shear, resample=0, fillcolor=None): If omitted, or if the image has mode "1" or "P", it is set to ``PIL.Image.NEAREST``. fillcolor (int): Optional fill color for the area outside the transform in the output image. (Pillow>=5.0.0) """ - if not _is_pil_image(img): + if not F_pil._is_pil_image(img): raise TypeError('img should be PIL Image. Got {}'.format(type(img))) assert isinstance(translate, (tuple, list)) and len(translate) == 2, \ @@ -897,7 +934,7 @@ def to_grayscale(img, num_output_channels=1): if num_output_channels = 3 : returned image is 3 channel with r = g = b """ - if not _is_pil_image(img): + if not F_pil._is_pil_image(img): raise TypeError('img should be PIL Image. Got {}'.format(type(img))) if num_output_channels == 1: diff --git a/torchvision/transforms/functional_pil.py b/torchvision/transforms/functional_pil.py index 3786d0e31a7..f1bcda113aa 100644 --- a/torchvision/transforms/functional_pil.py +++ b/torchvision/transforms/functional_pil.py @@ -1,4 +1,5 @@ import numbers +from typing import Any, List import torch try: @@ -10,13 +11,20 @@ @torch.jit.unused -def _is_pil_image(img): +def _is_pil_image(img: Any) -> bool: if accimage is not None: return isinstance(img, (Image.Image, accimage.Image)) else: return isinstance(img, Image.Image) +@torch.jit.unused +def _get_image_size(img: Any) -> List[int]: + if _is_pil_image(img): + return img.size + raise TypeError("Unexpected type {}".format(type(img))) + + @torch.jit.unused def hflip(img): """Horizontally flip the given PIL Image. @@ -258,3 +266,23 @@ def pad(img, padding, fill=0, padding_mode="constant"): img = np.pad(img, ((pad_top, pad_bottom), (pad_left, pad_right)), padding_mode) return Image.fromarray(img) + + +@torch.jit.unused +def crop(img: Image.Image, top: int, left: int, height: int, width: int) -> Image.Image: + """Crop the given PIL Image. + + Args: + img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image. + top (int): Vertical component of the top left corner of the crop box. + left (int): Horizontal component of the top left corner of the crop box. + height (int): Height of the crop box. + width (int): Width of the crop box. + + Returns: + PIL Image: Cropped image. + """ + if not _is_pil_image(img): + raise TypeError('img should be PIL Image. Got {}'.format(type(img))) + + return img.crop((left, top, left + width, top + height)) diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index ccba4d08369..54a0975582c 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -3,12 +3,17 @@ from torch.jit.annotations import List, BroadcastingList2 -def _is_tensor_a_torch_image(input): - return input.ndim >= 2 +def _is_tensor_a_torch_image(x: Tensor) -> bool: + return x.ndim >= 2 -def vflip(img): - # type: (Tensor) -> Tensor +def _get_image_size(img: Tensor) -> List[int]: + if _is_tensor_a_torch_image(img): + return [img.shape[-1], img.shape[-2]] + raise TypeError("Unexpected type {}".format(type(img))) + + +def vflip(img: Tensor) -> Tensor: """Vertically flip the given the Image Tensor. Args: @@ -23,8 +28,7 @@ def vflip(img): return img.flip(-2) -def hflip(img): - # type: (Tensor) -> Tensor +def hflip(img: Tensor) -> Tensor: """Horizontally flip the given the Image Tensor. Args: @@ -39,12 +43,11 @@ def hflip(img): return img.flip(-1) -def crop(img, top, left, height, width): - # type: (Tensor, int, int, int, int) -> Tensor +def crop(img: Tensor, top: int, left: int, height: int, width: int) -> Tensor: """Crop the given Image Tensor. Args: - img (Tensor): Image to be cropped in the form [C, H, W]. (0,0) denotes the top left corner of the image. + img (Tensor): Image to be cropped in the form [..., H, W]. (0,0) denotes the top left corner of the image. top (int): Vertical component of the top left corner of the crop box. left (int): Horizontal component of the top left corner of the crop box. height (int): Height of the crop box. @@ -54,13 +57,12 @@ def crop(img, top, left, height, width): Tensor: Cropped image. """ if not _is_tensor_a_torch_image(img): - raise TypeError('tensor is not a torch image.') + raise TypeError("tensor is not a torch image.") return img[..., top:top + height, left:left + width] -def rgb_to_grayscale(img): - # type: (Tensor) -> Tensor +def rgb_to_grayscale(img: Tensor) -> Tensor: """Convert the given RGB Image Tensor to Grayscale. For RGB to Grayscale conversion, ITU-R 601-2 luma transform is performed which is L = R * 0.2989 + G * 0.5870 + B * 0.1140 @@ -78,8 +80,7 @@ def rgb_to_grayscale(img): return (0.2989 * img[0] + 0.5870 * img[1] + 0.1140 * img[2]).to(img.dtype) -def adjust_brightness(img, brightness_factor): - # type: (Tensor, float) -> Tensor +def adjust_brightness(img: Tensor, brightness_factor: float) -> Tensor: """Adjust brightness of an RGB image. Args: @@ -97,8 +98,7 @@ def adjust_brightness(img, brightness_factor): return _blend(img, torch.zeros_like(img), brightness_factor) -def adjust_contrast(img, contrast_factor): - # type: (Tensor, float) -> Tensor +def adjust_contrast(img: Tensor, contrast_factor: float) -> Tensor: """Adjust contrast of an RGB image. Args: @@ -166,8 +166,7 @@ def adjust_hue(img, hue_factor): return img_hue_adj -def adjust_saturation(img, saturation_factor): - # type: (Tensor, float) -> Tensor +def adjust_saturation(img: Tensor, saturation_factor: float) -> Tensor: """Adjust color saturation of an RGB image. Args: @@ -185,12 +184,11 @@ def adjust_saturation(img, saturation_factor): return _blend(img, rgb_to_grayscale(img), saturation_factor) -def center_crop(img, output_size): - # type: (Tensor, BroadcastingList2[int]) -> Tensor +def center_crop(img: Tensor, output_size: BroadcastingList2[int]) -> Tensor: """Crop the Image Tensor and resize it to desired size. Args: - img (Tensor): Image to be cropped. (0,0) denotes the top left corner of the image. + img (Tensor): Image to be cropped. output_size (sequence or int): (height, width) of the crop box. If int, it is used for both directions @@ -202,23 +200,29 @@ def center_crop(img, output_size): _, image_width, image_height = img.size() crop_height, crop_width = output_size - crop_top = int(round((image_height - crop_height) / 2.)) - crop_left = int(round((image_width - crop_width) / 2.)) + # crop_top = int(round((image_height - crop_height) / 2.)) + # Result can be different between python func and scripted func + # Temporary workaround: + crop_top = int((image_height - crop_height + 1) * 0.5) + # crop_left = int(round((image_width - crop_width) / 2.)) + # Result can be different between python func and scripted func + # Temporary workaround: + crop_left = int((image_width - crop_width + 1) * 0.5) return crop(img, crop_top, crop_left, crop_height, crop_width) -def five_crop(img, size): - # type: (Tensor, BroadcastingList2[int]) -> List[Tensor] +def five_crop(img: Tensor, size: BroadcastingList2[int]) -> List[Tensor]: """Crop the given Image Tensor into four corners and the central crop. .. Note:: This transform returns a List of Tensors and there may be a mismatch in the number of inputs and targets your ``Dataset`` returns. Args: - size (sequence or int): Desired output size of the crop. If size is an - int instead of sequence like (h, w), a square crop (size, size) is - made. + img (Tensor): Image to be cropped. + size (sequence or int): Desired output size of the crop. If size is an + int instead of sequence like (h, w), a square crop (size, size) is + made. Returns: List: List (tl, tr, bl, br, center) @@ -244,19 +248,20 @@ def five_crop(img, size): return [tl, tr, bl, br, center] -def ten_crop(img, size, vertical_flip=False): - # type: (Tensor, BroadcastingList2[int], bool) -> List[Tensor] +def ten_crop(img: Tensor, size: BroadcastingList2[int], vertical_flip: bool = False) -> List[Tensor]: """Crop the given Image Tensor into four corners and the central crop plus the flipped version of these (horizontal flipping is used by default). + .. Note:: This transform returns a List of images and there may be a mismatch in the number of inputs and targets your ``Dataset`` returns. Args: - size (sequence or int): Desired output size of the crop. If size is an + img (Tensor): Image to be cropped. + size (sequence or int): Desired output size of the crop. If size is an int instead of sequence like (h, w), a square crop (size, size) is made. - vertical_flip (bool): Use vertical flipping instead of horizontal + vertical_flip (bool): Use vertical flipping instead of horizontal Returns: List: List (tl, tr, bl, br, center, tl_flip, tr_flip, bl_flip, br_flip, center_flip) @@ -279,8 +284,7 @@ def ten_crop(img, size, vertical_flip=False): return first_five + second_five -def _blend(img1, img2, ratio): - # type: (Tensor, Tensor, float) -> Tensor +def _blend(img1: Tensor, img2: Tensor, ratio: float) -> Tensor: bound = 1 if img1.dtype in [torch.half, torch.float32, torch.float64] else 255 return (ratio * img1 + (1 - ratio) * img2).clamp(0, bound).to(img1.dtype) diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index edf68f63127..6ee266d5f79 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -1,16 +1,19 @@ -import torch import math +import numbers import random +import warnings +from collections.abc import Sequence, Iterable +from typing import Tuple + +import numpy as np +import torch from PIL import Image +from torch import Tensor + try: import accimage except ImportError: accimage = None -import numpy as np -import numbers -import types -from collections.abc import Sequence, Iterable -import warnings from . import functional as F @@ -31,15 +34,6 @@ } -def _get_image_size(img): - if F._is_pil_image(img): - return img.size - elif isinstance(img, torch.Tensor) and img.dim() > 2: - return img.shape[-2:][::-1] - else: - raise TypeError("Unexpected type {}".format(type(img))) - - class Compose(object): """Composes several transforms together. @@ -98,7 +92,7 @@ def __repr__(self): class PILToTensor(object): """Convert a ``PIL Image`` to a tensor of the same type. - Converts a PIL Image (H x W x C) to a torch.Tensor of shape (C x H x W). + Converts a PIL Image (H x W x C) to a Tensor of shape (C x H x W). """ def __call__(self, pic): @@ -258,28 +252,36 @@ def __init__(self, *args, **kwargs): super(Scale, self).__init__(*args, **kwargs) -class CenterCrop(object): - """Crops the given PIL Image at the center. +class CenterCrop(torch.nn.Module): + """Crops the given image at the center. + The image can be a PIL Image or a torch Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions Args: size (sequence or int): Desired output size of the crop. If size is an int instead of sequence like (h, w), a square crop (size, size) is - made. + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). """ def __init__(self, size): + super().__init__() if isinstance(size, numbers.Number): self.size = (int(size), int(size)) + elif isinstance(size, Sequence) and len(size) == 1: + self.size = (size[0], size[0]) else: + if len(size) != 2: + raise ValueError("Please provide only two dimensions (h, w) for size.") + self.size = size - def __call__(self, img): + def forward(self, img): """ Args: - img (PIL Image): Image to be cropped. + img (PIL Image or Tensor): Image to be cropped. Returns: - PIL Image: Cropped image. + PIL Image or Tensor: Cropped image. """ return F.center_crop(img, self.size) @@ -443,25 +445,30 @@ def __call__(self, img): return t(img) -class RandomCrop(object): - """Crop the given PIL Image at a random location. +class RandomCrop(torch.nn.Module): + """Crop the given image at a random location. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading + dimensions Args: size (sequence or int): Desired output size of the crop. If size is an int instead of sequence like (h, w), a square crop (size, size) is - made. + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). padding (int or sequence, optional): Optional padding on each border - of the image. Default is None, i.e no padding. If a sequence of length - 4 is provided, it is used to pad left, top, right, bottom borders - respectively. If a sequence of length 2 is provided, it is used to - pad left/right, top/bottom borders, respectively. + of the image. Default is None. If a single int is provided this + is used to pad all borders. If tuple of length 2 is provided this is the padding + on left/right and top/bottom respectively. If a tuple of length 4 is provided + this is the padding for the left, top, right and bottom borders respectively. + In torchscript mode padding as single int is not supported, use a tuple or + list of length 1: ``[padding, ]``. pad_if_needed (boolean): It will pad the image if smaller than the desired size to avoid raising an exception. Since cropping is done after padding, the padding seems to be done at a random offset. - fill: Pixel fill value for constant fill. Default is 0. If a tuple of + fill (int or tuple): Pixel fill value for constant fill. Default is 0. If a tuple of length 3, it is used to fill R, G, B channels respectively. This value is only used when the padding_mode is constant - padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant. + padding_mode (str): Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant. - constant: pads with a constant value, this value is specified with fill @@ -479,60 +486,70 @@ class RandomCrop(object): """ - def __init__(self, size, padding=None, pad_if_needed=False, fill=0, padding_mode='constant'): - if isinstance(size, numbers.Number): - self.size = (int(size), int(size)) - else: - self.size = size - self.padding = padding - self.pad_if_needed = pad_if_needed - self.fill = fill - self.padding_mode = padding_mode - @staticmethod - def get_params(img, output_size): + def get_params(img: Tensor, output_size: Tuple[int, int]) -> Tuple[int, int, int, int]: """Get parameters for ``crop`` for a random crop. Args: - img (PIL Image): Image to be cropped. + img (PIL Image or Tensor): Image to be cropped. output_size (tuple): Expected output size of the crop. Returns: tuple: params (i, j, h, w) to be passed to ``crop`` for random crop. """ - w, h = _get_image_size(img) + w, h = F._get_image_size(img) th, tw = output_size if w == tw and h == th: return 0, 0, h, w - i = random.randint(0, h - th) - j = random.randint(0, w - tw) + i = torch.randint(0, h - th, size=(1, )).item() + j = torch.randint(0, w - tw, size=(1, )).item() return i, j, th, tw - def __call__(self, img): + def __init__(self, size, padding=None, pad_if_needed=False, fill=0, padding_mode="constant"): + super().__init__() + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + elif isinstance(size, Sequence) and len(size) == 1: + self.size = (size[0], size[0]) + else: + if len(size) != 2: + raise ValueError("Please provide only two dimensions (h, w) for size.") + + # cast to tuple for torchscript + self.size = tuple(size) + self.padding = padding + self.pad_if_needed = pad_if_needed + self.fill = fill + self.padding_mode = padding_mode + + def forward(self, img): """ Args: - img (PIL Image): Image to be cropped. + img (PIL Image or Tensor): Image to be cropped. Returns: - PIL Image: Cropped image. + PIL Image or Tensor: Cropped image. """ if self.padding is not None: img = F.pad(img, self.padding, self.fill, self.padding_mode) + width, height = F._get_image_size(img) # pad the width if needed - if self.pad_if_needed and img.size[0] < self.size[1]: - img = F.pad(img, (self.size[1] - img.size[0], 0), self.fill, self.padding_mode) + if self.pad_if_needed and width < self.size[1]: + padding = [self.size[1] - width, 0] + img = F.pad(img, padding, self.fill, self.padding_mode) # pad the height if needed - if self.pad_if_needed and img.size[1] < self.size[0]: - img = F.pad(img, (0, self.size[0] - img.size[1]), self.fill, self.padding_mode) + if self.pad_if_needed and height < self.size[0]: + padding = [0, self.size[0] - height] + img = F.pad(img, padding, self.fill, self.padding_mode) i, j, h, w = self.get_params(img, self.size) return F.crop(img, i, j, h, w) def __repr__(self): - return self.__class__.__name__ + '(size={0}, padding={1})'.format(self.size, self.padding) + return self.__class__.__name__ + "(size={0}, padding={1})".format(self.size, self.padding) class RandomHorizontalFlip(torch.nn.Module): @@ -566,7 +583,7 @@ def __repr__(self): class RandomVerticalFlip(torch.nn.Module): - """Vertically flip the given PIL Image randomly with a given probability. + """Vertically flip the given image randomly with a given probability. The image can be a PIL Image or a torch Tensor, in which case it is expected to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions @@ -702,7 +719,7 @@ def get_params(img, scale, ratio): tuple: params (i, j, h, w) to be passed to ``crop`` for a random sized crop. """ - width, height = _get_image_size(img) + width, height = F._get_image_size(img) area = height * width for _ in range(10): @@ -763,8 +780,11 @@ def __init__(self, *args, **kwargs): super(RandomSizedCrop, self).__init__(*args, **kwargs) -class FiveCrop(object): - """Crop the given PIL Image into four corners and the central crop +class FiveCrop(torch.nn.Module): + """Crop the given image into four corners and the central crop. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading + dimensions .. Note:: This transform returns a tuple of images and there may be a mismatch in the number of @@ -774,6 +794,7 @@ class FiveCrop(object): Args: size (sequence or int): Desired output size of the crop. If size is an ``int`` instead of sequence like (h, w), a square crop of size (size, size) is made. + If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). Example: >>> transform = Compose([ @@ -788,23 +809,37 @@ class FiveCrop(object): """ def __init__(self, size): - self.size = size + super().__init__() if isinstance(size, numbers.Number): self.size = (int(size), int(size)) + elif isinstance(size, Sequence) and len(size) == 1: + self.size = (size[0], size[0]) else: - assert len(size) == 2, "Please provide only two dimensions (h, w) for size." + if len(size) != 2: + raise ValueError("Please provide only two dimensions (h, w) for size.") + self.size = size - def __call__(self, img): + def forward(self, img): + """ + Args: + img (PIL Image or Tensor): Image to be cropped. + + Returns: + tuple of 5 images. Image can be PIL Image or Tensor + """ return F.five_crop(img, self.size) def __repr__(self): return self.__class__.__name__ + '(size={0})'.format(self.size) -class TenCrop(object): - """Crop the given PIL Image into four corners and the central crop plus the flipped version of - these (horizontal flipping is used by default) +class TenCrop(torch.nn.Module): + """Crop the given image into four corners and the central crop plus the flipped version of + these (horizontal flipping is used by default). + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading + dimensions .. Note:: This transform returns a tuple of images and there may be a mismatch in the number of @@ -814,7 +849,7 @@ class TenCrop(object): Args: size (sequence or int): Desired output size of the crop. If size is an int instead of sequence like (h, w), a square crop (size, size) is - made. + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). vertical_flip (bool): Use vertical flipping instead of horizontal Example: @@ -830,15 +865,26 @@ class TenCrop(object): """ def __init__(self, size, vertical_flip=False): - self.size = size + super().__init__() if isinstance(size, numbers.Number): self.size = (int(size), int(size)) + elif isinstance(size, Sequence) and len(size) == 1: + self.size = (size[0], size[0]) else: - assert len(size) == 2, "Please provide only two dimensions (h, w) for size." + if len(size) != 2: + raise ValueError("Please provide only two dimensions (h, w) for size.") + self.size = size self.vertical_flip = vertical_flip - def __call__(self, img): + def forward(self, img): + """ + Args: + img (PIL Image or Tensor): Image to be cropped. + + Returns: + tuple of 10 images. Image can be PIL Image or Tensor + """ return F.ten_crop(img, self.size, self.vertical_flip) def __repr__(self): From 6fe11d5c49d62ba3c692a5ef6371a2371f1878eb Mon Sep 17 00:00:00 2001 From: vfdev Date: Tue, 30 Jun 2020 15:25:51 +0200 Subject: [PATCH 15/51] Issue 2350 - support of all padding modes with tensors (#2368) * [WIP] functional_tensor supports more padding modes * [WIP] Support all padding modes * Removed wip symmetric mode * Improvements according to the review --- test/test_functional_tensor.py | 42 +++++++++++++++------ torchvision/transforms/functional_tensor.py | 38 +++++++++++++++++-- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index 07a699345bd..0c2194e0f7b 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -247,18 +247,36 @@ def test_ten_crop(self): def test_pad(self): script_fn = torch.jit.script(F_t.pad) tensor, pil_img = self._create_data(7, 8) - for pad in [1, [1, ], [0, 1], (2, 2), [1, 0, 1, 2]]: - padding_mode = "constant" - for fill in [0, 10, 20]: - pad_tensor = F_t.pad(tensor, pad, fill=fill, padding_mode=padding_mode) - pad_pil_img = F_pil.pad(pil_img, pad, fill=fill, padding_mode=padding_mode) - self.compareTensorToPIL(pad_tensor, pad_pil_img, msg="{}, {}".format(pad, fill)) - if isinstance(pad, int): - script_pad = [pad, ] - else: - script_pad = pad - pad_tensor_script = script_fn(tensor, script_pad, fill=fill, padding_mode=padding_mode) - self.assertTrue(pad_tensor.equal(pad_tensor_script), msg="{}, {}".format(pad, fill)) + + for dt in [None, torch.float32, torch.float64]: + if dt is not None: + # This is a trivial cast to float of uint8 data to test all cases + tensor = tensor.to(dt) + for pad in [2, [3, ], [0, 3], (3, 3), [4, 2, 4, 3]]: + configs = [ + {"padding_mode": "constant", "fill": 0}, + {"padding_mode": "constant", "fill": 10}, + {"padding_mode": "constant", "fill": 20}, + {"padding_mode": "edge"}, + {"padding_mode": "reflect"}, + ] + for kwargs in configs: + pad_tensor = F_t.pad(tensor, pad, **kwargs) + pad_pil_img = F_pil.pad(pil_img, pad, **kwargs) + + pad_tensor_8b = pad_tensor + # we need to cast to uint8 to compare with PIL image + if pad_tensor_8b.dtype != torch.uint8: + pad_tensor_8b = pad_tensor_8b.to(torch.uint8) + + self.compareTensorToPIL(pad_tensor_8b, pad_pil_img, msg="{}, {}".format(pad, kwargs)) + + if isinstance(pad, int): + script_pad = [pad, ] + else: + script_pad = pad + pad_tensor_script = script_fn(tensor, script_pad, **kwargs) + self.assertTrue(pad_tensor.equal(pad_tensor_script), msg="{}, {}".format(pad, kwargs)) if __name__ == '__main__': diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index 54a0975582c..6ea3ccb2956 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -346,7 +346,7 @@ def _hsv2rgb(img): return torch.einsum("ijk, xijk -> xjk", mask.to(dtype=img.dtype), a4) -def pad(img: Tensor, padding: List[int], fill: int, padding_mode: str = "constant") -> Tensor: +def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "constant") -> Tensor: r"""Pad the given Tensor Image on all sides with specified padding mode and fill value. Args: @@ -363,6 +363,13 @@ def pad(img: Tensor, padding: List[int], fill: int, padding_mode: str = "constan - constant: pads with a constant value, this value is specified with fill + - edge: pads with the last value on the edge of the image + + - reflect: pads with reflection of image (without repeating the last value on the edge) + + padding [1, 2, 3, 4] with 2 elements on both sides in reflect mode + will result in [3, 2, 1, 2, 3, 4, 3, 2] + Returns: Tensor: Padded image. """ @@ -383,8 +390,8 @@ def pad(img: Tensor, padding: List[int], fill: int, padding_mode: str = "constan raise ValueError("Padding must be an int or a 1, 2, or 4 element tuple, not a " + "{} element tuple".format(len(padding))) - if padding_mode not in ["constant", ]: - raise ValueError("Only constant padding_mode supported for torch tensors") + if padding_mode not in ["constant", "edge", "reflect"]: + raise ValueError("Padding mode should be either constant, edge or reflect") if isinstance(padding, int): if torch.jit.is_scripting(): @@ -403,5 +410,30 @@ def pad(img: Tensor, padding: List[int], fill: int, padding_mode: str = "constan p = [pad_left, pad_right, pad_top, pad_bottom] + if padding_mode == "edge": + # remap padding_mode str + padding_mode = "replicate" + + need_squeeze = False + if img.ndim < 4: + img = img.unsqueeze(dim=0) + need_squeeze = True + + out_dtype = img.dtype + need_cast = False + if (padding_mode != "constant") and img.dtype not in (torch.float32, torch.float64): + # Here we temporary cast input tensor to float + # until pytorch issue is resolved : + # https://github.com/pytorch/pytorch/issues/40763 + need_cast = True + img = img.to(torch.float32) + img = torch.nn.functional.pad(img, p, mode=padding_mode, value=float(fill)) + + if need_squeeze: + img = img.squeeze(dim=0) + + if need_cast: + img = img.to(out_dtype) + return img From 7fd24918b2d0a41295600b819f329593d5a33a91 Mon Sep 17 00:00:00 2001 From: Tongzhou Wang Date: Tue, 30 Jun 2020 09:28:02 -0400 Subject: [PATCH 16/51] F_t add factor nonnegativity checks for adjust_* (#2356) * F_t add factor nonnegativity checks for adjust_* * Update functional_tensor.py * Update functional_tensor.py * Update functional_tensor.py * Update functional_tensor.py --- torchvision/transforms/functional_tensor.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index 6ea3ccb2956..c77e393569c 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -92,6 +92,9 @@ def adjust_brightness(img: Tensor, brightness_factor: float) -> Tensor: Returns: Tensor: Brightness adjusted image. """ + if brightness_factor < 0: + raise ValueError('brightness_factor ({}) is not non-negative.'.format(brightness_factor)) + if not _is_tensor_a_torch_image(img): raise TypeError('tensor is not a torch image.') @@ -110,6 +113,9 @@ def adjust_contrast(img: Tensor, contrast_factor: float) -> Tensor: Returns: Tensor: Contrast adjusted image. """ + if contrast_factor < 0: + raise ValueError('contrast_factor ({}) is not non-negative.'.format(contrast_factor)) + if not _is_tensor_a_torch_image(img): raise TypeError('tensor is not a torch image.') @@ -143,7 +149,7 @@ def adjust_hue(img, hue_factor): Returns: Tensor: Hue adjusted image. """ - if not(-0.5 <= hue_factor <= 0.5): + if not (-0.5 <= hue_factor <= 0.5): raise ValueError('hue_factor ({}) is not in [-0.5, 0.5].'.format(hue_factor)) if not _is_tensor_a_torch_image(img): @@ -171,13 +177,16 @@ def adjust_saturation(img: Tensor, saturation_factor: float) -> Tensor: Args: img (Tensor): Image to be adjusted. - saturation_factor (float): How much to adjust the saturation. 0 will - give a black and white image, 1 will give the original image while - 2 will enhance the saturation by a factor of 2. + saturation_factor (float): How much to adjust the saturation. Can be any + non negative number. 0 gives a black and white image, 1 gives the + original image while 2 enhances the saturation by a factor of 2. Returns: Tensor: Saturation adjusted image. """ + if saturation_factor < 0: + raise ValueError('saturation_factor ({}) is not non-negative.'.format(saturation_factor)) + if not _is_tensor_a_torch_image(img): raise TypeError('tensor is not a torch image.') From 4480603831a6ad0bb97e3ffb5234f9d128e548a3 Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Tue, 30 Jun 2020 11:51:23 -0400 Subject: [PATCH 17/51] Port roi_align to actually use dispatcher (#2366) * Switch torchvision registrations to new operator registration API. This is still registering everything as catchalls, so we're really just moving deck chairs around, but payoff is coming soon. Signed-off-by: Edward Z. Yang * Port roi_align to actually use dispatcher Signed-off-by: Edward Z. Yang --- torchvision/csrc/ROIAlign.h | 139 ++++++++++++++++--------- torchvision/csrc/cpu/ROIAlign_cpu.cpp | 24 ++--- torchvision/csrc/cpu/vision_cpu.h | 24 ++--- torchvision/csrc/cuda/ROIAlign_cuda.cu | 24 ++--- torchvision/csrc/cuda/vision_cuda.h | 24 ++--- torchvision/csrc/vision.cpp | 42 ++++++-- 6 files changed, 170 insertions(+), 107 deletions(-) diff --git a/torchvision/csrc/ROIAlign.h b/torchvision/csrc/ROIAlign.h index 78dcb101dce..7a856f34d63 100644 --- a/torchvision/csrc/ROIAlign.h +++ b/torchvision/csrc/ROIAlign.h @@ -9,8 +9,9 @@ #include "hip/vision_cuda.h" #endif -// Interface for Python -at::Tensor ROIAlign_forward( +// TODO: put this stuff in torchvision namespace + +at::Tensor roi_align( const at::Tensor& input, // Input feature map. const at::Tensor& rois, // List of ROIs to pool over. const double spatial_scale, // The scale of the image features. ROIs will be @@ -21,21 +22,10 @@ at::Tensor ROIAlign_forward( const bool aligned) // The flag for pixel shift // along each axis. { - if (input.is_cuda()) { -#if defined(WITH_CUDA) || defined(WITH_HIP) - return ROIAlign_forward_cuda( - input, - rois, - spatial_scale, - pooled_height, - pooled_width, - sampling_ratio, - aligned); -#else - AT_ERROR("Not compiled with GPU support"); -#endif - } - return ROIAlign_forward_cpu( + static auto op = c10::Dispatcher::singleton() + .findSchemaOrThrow("torchvision::roi_align", "") + .typed(); + return op.call( input, rois, spatial_scale, @@ -45,37 +35,23 @@ at::Tensor ROIAlign_forward( aligned); } -at::Tensor ROIAlign_backward( +at::Tensor _roi_align_backward( const at::Tensor& grad, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int batch_size, - const int channels, - const int height, - const int width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t batch_size, + const int64_t channels, + const int64_t height, + const int64_t width, + const int64_t sampling_ratio, const bool aligned) { - if (grad.is_cuda()) { -#if defined(WITH_CUDA) || defined(WITH_HIP) - return ROIAlign_backward_cuda( - grad, - rois, - spatial_scale, - pooled_height, - pooled_width, - batch_size, - channels, - height, - width, - sampling_ratio, - aligned); -#else - AT_ERROR("Not compiled with GPU support"); -#endif - } - return ROIAlign_backward_cpu( + static auto op = + c10::Dispatcher::singleton() + .findSchemaOrThrow("torchvision::_roi_align_backward", "") + .typed(); + return op.call( grad, rois, spatial_scale, @@ -107,7 +83,8 @@ class ROIAlignFunction : public torch::autograd::Function { ctx->saved_data["aligned"] = aligned; ctx->saved_data["input_shape"] = input.sizes(); ctx->save_for_backward({rois}); - auto result = ROIAlign_forward( + at::AutoNonVariableTypeMode g; + auto result = roi_align( input, rois, spatial_scale, @@ -125,7 +102,7 @@ class ROIAlignFunction : public torch::autograd::Function { auto saved = ctx->get_saved_variables(); auto rois = saved[0]; auto input_shape = ctx->saved_data["input_shape"].toIntList(); - auto grad_in = ROIAlign_backward( + auto grad_in = _roi_align_backward( grad_output[0], rois, ctx->saved_data["spatial_scale"].toDouble(), @@ -147,7 +124,47 @@ class ROIAlignFunction : public torch::autograd::Function { } }; -at::Tensor roi_align( +// TODO: There should be an easier way to do this +class ROIAlignBackwardFunction + : public torch::autograd::Function { + public: + static torch::autograd::variable_list forward( + torch::autograd::AutogradContext* ctx, + torch::autograd::Variable grad, + torch::autograd::Variable rois, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t batch_size, + const int64_t channels, + const int64_t height, + const int64_t width, + const int64_t sampling_ratio, + const bool aligned) { + at::AutoNonVariableTypeMode g; + auto result = _roi_align_backward( + grad, + rois, + spatial_scale, + pooled_height, + pooled_width, + batch_size, + channels, + height, + width, + sampling_ratio, + aligned); + return {result}; + } + + static torch::autograd::variable_list backward( + torch::autograd::AutogradContext* ctx, + torch::autograd::variable_list grad_output) { + TORCH_CHECK(0, "double backwards on roi_align not supported"); + } +}; + +at::Tensor ROIAlign_autograd( const at::Tensor& input, const at::Tensor& rois, const double spatial_scale, @@ -164,3 +181,29 @@ at::Tensor roi_align( sampling_ratio, aligned)[0]; } + +at::Tensor ROIAlign_backward_autograd( + const at::Tensor& grad, + const at::Tensor& rois, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t batch_size, + const int64_t channels, + const int64_t height, + const int64_t width, + const int64_t sampling_ratio, + const bool aligned) { + return ROIAlignBackwardFunction::apply( + grad, + rois, + spatial_scale, + pooled_height, + pooled_width, + batch_size, + channels, + height, + width, + sampling_ratio, + aligned)[0]; +} diff --git a/torchvision/csrc/cpu/ROIAlign_cpu.cpp b/torchvision/csrc/cpu/ROIAlign_cpu.cpp index 325221df65b..75d3e7a90b4 100644 --- a/torchvision/csrc/cpu/ROIAlign_cpu.cpp +++ b/torchvision/csrc/cpu/ROIAlign_cpu.cpp @@ -381,10 +381,10 @@ void ROIAlignBackward( at::Tensor ROIAlign_forward_cpu( const at::Tensor& input, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t sampling_ratio, const bool aligned) { AT_ASSERTM(input.device().is_cpu(), "input must be a CPU tensor"); AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor"); @@ -430,14 +430,14 @@ at::Tensor ROIAlign_forward_cpu( at::Tensor ROIAlign_backward_cpu( const at::Tensor& grad, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int batch_size, - const int channels, - const int height, - const int width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t batch_size, + const int64_t channels, + const int64_t height, + const int64_t width, + const int64_t sampling_ratio, const bool aligned) { AT_ASSERTM(grad.device().is_cpu(), "grad must be a CPU tensor"); AT_ASSERTM(rois.device().is_cpu(), "rois must be a CPU tensor"); diff --git a/torchvision/csrc/cpu/vision_cpu.h b/torchvision/csrc/cpu/vision_cpu.h index d81a51a59c4..64aa1ae2119 100644 --- a/torchvision/csrc/cpu/vision_cpu.h +++ b/torchvision/csrc/cpu/vision_cpu.h @@ -23,23 +23,23 @@ at::Tensor ROIPool_backward_cpu( at::Tensor ROIAlign_forward_cpu( const at::Tensor& input, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t sampling_ratio, const bool aligned); at::Tensor ROIAlign_backward_cpu( const at::Tensor& grad, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int batch_size, - const int channels, - const int height, - const int width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t batch_size, + const int64_t channels, + const int64_t height, + const int64_t width, + const int64_t sampling_ratio, const bool aligned); std::tuple PSROIPool_forward_cpu( diff --git a/torchvision/csrc/cuda/ROIAlign_cuda.cu b/torchvision/csrc/cuda/ROIAlign_cuda.cu index 298af06c708..8f8bcd10d48 100644 --- a/torchvision/csrc/cuda/ROIAlign_cuda.cu +++ b/torchvision/csrc/cuda/ROIAlign_cuda.cu @@ -307,10 +307,10 @@ __global__ void RoIAlignBackward( at::Tensor ROIAlign_forward_cuda( const at::Tensor& input, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t sampling_ratio, const bool aligned) { AT_ASSERTM(input.is_cuda(), "input must be a CUDA tensor"); AT_ASSERTM(rois.is_cuda(), "rois must be a CUDA tensor"); @@ -368,14 +368,14 @@ at::Tensor ROIAlign_forward_cuda( at::Tensor ROIAlign_backward_cuda( const at::Tensor& grad, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int batch_size, - const int channels, - const int height, - const int width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t batch_size, + const int64_t channels, + const int64_t height, + const int64_t width, + const int64_t sampling_ratio, const bool aligned) { AT_ASSERTM(grad.is_cuda(), "grad must be a CUDA tensor"); AT_ASSERTM(rois.is_cuda(), "rois must be a CUDA tensor"); diff --git a/torchvision/csrc/cuda/vision_cuda.h b/torchvision/csrc/cuda/vision_cuda.h index 5f0ff05246b..834ba51a4cf 100644 --- a/torchvision/csrc/cuda/vision_cuda.h +++ b/torchvision/csrc/cuda/vision_cuda.h @@ -9,23 +9,23 @@ at::Tensor ROIAlign_forward_cuda( const at::Tensor& input, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t sampling_ratio, const bool aligned); at::Tensor ROIAlign_backward_cuda( const at::Tensor& grad, const at::Tensor& rois, - const float spatial_scale, - const int pooled_height, - const int pooled_width, - const int batch_size, - const int channels, - const int height, - const int width, - const int sampling_ratio, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t batch_size, + const int64_t channels, + const int64_t height, + const int64_t width, + const int64_t sampling_ratio, const bool aligned); std::tuple ROIPool_forward_cuda( diff --git a/torchvision/csrc/vision.cpp b/torchvision/csrc/vision.cpp index 9debc3da9b6..7f56bdb51a1 100644 --- a/torchvision/csrc/vision.cpp +++ b/torchvision/csrc/vision.cpp @@ -42,14 +42,34 @@ int64_t _cuda_version() { #endif } -static auto registry = - torch::RegisterOperators() - .op("torchvision::nms", &nms) - .op("torchvision::roi_align(Tensor input, Tensor rois, float spatial_scale, int pooled_height, int pooled_width, int sampling_ratio, bool aligned) -> Tensor", - &roi_align) - .op("torchvision::roi_pool", &roi_pool) - .op("torchvision::_new_empty_tensor_op", &new_empty_tensor) - .op("torchvision::ps_roi_align", &ps_roi_align) - .op("torchvision::ps_roi_pool", &ps_roi_pool) - .op("torchvision::deform_conv2d", &deform_conv2d) - .op("torchvision::_cuda_version", &_cuda_version); +TORCH_LIBRARY(torchvision, m) { + m.def("nms", &nms); + m.def( + "roi_align(Tensor input, Tensor rois, float spatial_scale, int pooled_height, int pooled_width, int sampling_ratio, bool aligned) -> Tensor"); + m.def( + "_roi_align_backward(Tensor grad, Tensor rois, float spatial_scale, int pooled_height, int pooled_width, int batch_size, int channels, int height, int width, int sampling_ratio, bool aligned) -> Tensor"); + m.def("roi_pool", &roi_pool); + m.def("_new_empty_tensor_op", &new_empty_tensor); + m.def("ps_roi_align", &ps_roi_align); + m.def("ps_roi_pool", &ps_roi_pool); + m.def("deform_conv2d", &deform_conv2d); + m.def("_cuda_version", &_cuda_version); +} + +TORCH_LIBRARY_IMPL(torchvision, CPU, m) { + m.impl("roi_align", ROIAlign_forward_cpu); + m.impl("_roi_align_backward", ROIAlign_backward_cpu); +} + +// TODO: Place this in a hypothetical separate torchvision_cuda library +#if defined(WITH_CUDA) || defined(WITH_HIP) +TORCH_LIBRARY_IMPL(torchvision, CUDA, m) { + m.impl("roi_align", ROIAlign_forward_cuda); + m.impl("_roi_align_backward", ROIAlign_backward_cuda); +} +#endif + +TORCH_LIBRARY_IMPL(torchvision, Autograd, m) { + m.impl("roi_align", ROIAlign_autograd); + m.impl("_roi_align_backward", ROIAlign_backward_autograd); +} From e50c2e36c200be40b54be3f53336aa3d0137a364 Mon Sep 17 00:00:00 2001 From: vfdev Date: Tue, 30 Jun 2020 18:02:16 +0200 Subject: [PATCH 18/51] Improved docs and tests for (#2371) - RandomCrop: crop with padding using all commonly supported modes --- test/test_transforms_tensor.py | 56 +++++++++++---------- torchvision/transforms/functional.py | 2 +- torchvision/transforms/functional_tensor.py | 3 +- torchvision/transforms/transforms.py | 3 +- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/test/test_transforms_tensor.py b/test/test_transforms_tensor.py index 1d2d92bb3ae..6a8d9930754 100644 --- a/test/test_transforms_tensor.py +++ b/test/test_transforms_tensor.py @@ -26,24 +26,29 @@ def _test_functional_geom_op(self, func, fn_kwargs): transformed_pil_img = getattr(F, func)(pil_img, **fn_kwargs) self.compareTensorToPIL(transformed_tensor, transformed_pil_img) - def _test_geom_op(self, func, method, fn_kwargs=None, meth_kwargs=None): - if fn_kwargs is None: - fn_kwargs = {} + def _test_class_geom_op(self, method, meth_kwargs=None): if meth_kwargs is None: meth_kwargs = {} + tensor, pil_img = self._create_data(height=10, width=10) - transformed_tensor = getattr(F, func)(tensor, **fn_kwargs) - transformed_pil_img = getattr(F, func)(pil_img, **fn_kwargs) + # test for class interface + f = getattr(T, method)(**meth_kwargs) + scripted_fn = torch.jit.script(f) + + # set seed to reproduce the same transformation for tensor and PIL image + torch.manual_seed(12) + transformed_tensor = f(tensor) + torch.manual_seed(12) + transformed_pil_img = f(pil_img) self.compareTensorToPIL(transformed_tensor, transformed_pil_img) - scripted_fn = torch.jit.script(getattr(F, func)) - transformed_tensor_script = scripted_fn(tensor, **fn_kwargs) + torch.manual_seed(12) + transformed_tensor_script = scripted_fn(tensor) self.assertTrue(transformed_tensor.equal(transformed_tensor_script)) - # test for class interface - f = getattr(T, method)(**meth_kwargs) - scripted_fn = torch.jit.script(f) - scripted_fn(tensor) + def _test_geom_op(self, func, method, fn_kwargs=None, meth_kwargs=None): + self._test_functional_geom_op(func, fn_kwargs) + self._test_class_geom_op(method, meth_kwargs) def test_random_horizontal_flip(self): self._test_geom_op('hflip', 'RandomHorizontalFlip') @@ -107,21 +112,20 @@ def test_crop(self): 'crop', 'RandomCrop', fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs ) - tensor = torch.randint(0, 255, (3, 10, 10), dtype=torch.uint8) - # Test torchscript of transforms.RandomCrop with size as int - f = T.RandomCrop(size=5) - scripted_fn = torch.jit.script(f) - scripted_fn(tensor) - - # Test torchscript of transforms.RandomCrop with size as [int, ] - f = T.RandomCrop(size=[5, ], padding=[2, ]) - scripted_fn = torch.jit.script(f) - scripted_fn(tensor) - - # Test torchscript of transforms.RandomCrop with size as list - f = T.RandomCrop(size=[6, 6]) - scripted_fn = torch.jit.script(f) - scripted_fn(tensor) + sizes = [5, [5, ], [6, 6]] + padding_configs = [ + {"padding_mode": "constant", "fill": 0}, + {"padding_mode": "constant", "fill": 10}, + {"padding_mode": "constant", "fill": 20}, + {"padding_mode": "edge"}, + {"padding_mode": "reflect"}, + ] + + for size in sizes: + for padding_config in padding_configs: + config = dict(padding_config) + config["size"] = size + self._test_class_geom_op("RandomCrop", config) def test_center_crop(self): fn_kwargs = {"output_size": (4, 5)} diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index cda26348552..81a601a8e20 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -371,7 +371,7 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con length 3, it is used to fill R, G, B channels respectively. This value is only used when the padding_mode is constant. Only int value is supported for Tensors. padding_mode: Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant. - Only "constant" is supported for Tensors as of now. + Mode symmetric is not yet supported for Tensor inputs. - constant: pads with a constant value, this value is specified with fill diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index c77e393569c..d4a8f340997 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -368,7 +368,8 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con list of length 1: ``[padding, ]``. fill (int): Pixel fill value for constant fill. Default is 0. This value is only used when the padding_mode is constant - padding_mode (str): Type of padding. Only "constant" is supported for Tensors as of now. + padding_mode (str): Type of padding. Should be: constant, edge or reflect. Default is constant. + Mode symmetric is not yet supported for Tensor inputs. - constant: pads with a constant value, this value is specified with fill diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index 6ee266d5f79..a5214ed3174 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -305,7 +305,7 @@ class Pad(torch.nn.Module): length 3, it is used to fill R, G, B channels respectively. This value is only used when the padding_mode is constant padding_mode (str): Type of padding. Should be: constant, edge, reflect or symmetric. - Default is constant. Only "constant" is supported for Tensors as of now. + Default is constant. Mode symmetric is not yet supported for Tensor inputs. - constant: pads with a constant value, this value is specified with fill @@ -469,6 +469,7 @@ class RandomCrop(torch.nn.Module): length 3, it is used to fill R, G, B channels respectively. This value is only used when the padding_mode is constant padding_mode (str): Type of padding. Should be: constant, edge, reflect or symmetric. Default is constant. + Mode symmetric is not yet supported for Tensor inputs. - constant: pads with a constant value, this value is specified with fill From 9d9e716ff23a08051df3ca713b30f8ddc89e4e1c Mon Sep 17 00:00:00 2001 From: guyang3532 <62738430+guyang3532@users.noreply.github.com> Date: Wed, 1 Jul 2020 00:15:14 +0800 Subject: [PATCH 19/51] Add unittest job to CircleCI (#2328) --- .circleci/config.yml | 432 +++++++++++------- .circleci/config.yml.in | 273 ++++++----- .circleci/regenerate.py | 40 +- .../unittest/linux/scripts/environment.yml | 14 + .circleci/unittest/linux/scripts/install.sh | 23 + .../unittest/linux/scripts/post_process.sh | 8 + .circleci/unittest/linux/scripts/run_test.sh | 9 + .circleci/unittest/linux/scripts/setup_env.sh | 34 ++ .../unittest/windows/scripts/environment.yml | 14 + .circleci/unittest/windows/scripts/install.sh | 25 + .../windows/scripts/install_conda.bat | 1 + .../unittest/windows/scripts/post_process.sh | 8 + .../unittest/windows/scripts/run_test.sh | 9 + .../unittest/windows/scripts/setup_env.sh | 39 ++ .../windows/scripts/vc_env_helper.bat | 39 ++ packaging/torchvision/meta.yaml | 2 - 16 files changed, 690 insertions(+), 280 deletions(-) create mode 100644 .circleci/unittest/linux/scripts/environment.yml create mode 100755 .circleci/unittest/linux/scripts/install.sh create mode 100755 .circleci/unittest/linux/scripts/post_process.sh create mode 100755 .circleci/unittest/linux/scripts/run_test.sh create mode 100755 .circleci/unittest/linux/scripts/setup_env.sh create mode 100644 .circleci/unittest/windows/scripts/environment.yml create mode 100644 .circleci/unittest/windows/scripts/install.sh create mode 100644 .circleci/unittest/windows/scripts/install_conda.bat create mode 100644 .circleci/unittest/windows/scripts/post_process.sh create mode 100644 .circleci/unittest/windows/scripts/run_test.sh create mode 100644 .circleci/unittest/windows/scripts/setup_env.sh create mode 100644 .circleci/unittest/windows/scripts/vc_env_helper.bat diff --git a/.circleci/config.yml b/.circleci/config.yml index 801905ee5e9..29b5fc77aab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -155,106 +155,7 @@ jobs: - store_test_results: path: build_results/ - binary_linux_conda_cuda: - <<: *binary_common - machine: - image: ubuntu-1604:201903-01 - resource_class: gpu.medium - steps: - - checkout_merge - - run: - name: Setup environment - command: | - set -ex - - curl -L https://packagecloud.io/circleci/trusty/gpgkey | sudo apt-key add - - curl -L https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - - sudo apt-get update - - sudo apt-get install \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg-agent \ - software-properties-common - - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - - sudo add-apt-repository \ - "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) \ - stable" - - sudo apt-get update - export DOCKER_VERSION="5:19.03.2~3-0~ubuntu-xenial" - sudo apt-get install docker-ce=${DOCKER_VERSION} docker-ce-cli=${DOCKER_VERSION} containerd.io=1.2.6-3 - - # Add the package repositories - distribution=$(. /etc/os-release;echo $ID$VERSION_ID) - curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list - - export NVIDIA_CONTAINER_VERSION="1.0.3-1" - sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit=${NVIDIA_CONTAINER_VERSION} - sudo systemctl restart docker - - DRIVER_FN="NVIDIA-Linux-x86_64-440.59.run" - wget "https://s3.amazonaws.com/ossci-linux/nvidia_driver/$DRIVER_FN" - sudo /bin/bash "$DRIVER_FN" -s --no-drm || (sudo cat /var/log/nvidia-installer.log && false) - nvidia-smi - - - run: - name: Pull docker image - command: | - set -ex - export DOCKER_IMAGE=pytorch/conda-cuda - echo Pulling docker image $DOCKER_IMAGE - docker pull $DOCKER_IMAGE >/dev/null - - - run: - name: Build and run tests - command: | - set -ex - - cd ${HOME}/project/ - - export DOCKER_IMAGE=pytorch/conda-cuda - export VARS_TO_PASS="-e PYTHON_VERSION -e BUILD_VERSION -e PYTORCH_VERSION -e UNICODE_ABI -e CU_VERSION" - - docker run --gpus all --ipc=host -v $(pwd):/remote -w /remote ${VARS_TO_PASS} ${DOCKER_IMAGE} ./packaging/build_conda.sh - binary_win_conda: - <<: *binary_common - executor: windows-cpu - steps: - - checkout_merge - - run: - command: | - set -ex - source packaging/windows/internal/vc_install_helper.sh - eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" - conda activate base - conda install -yq conda-build "conda-package-handling!=1.5.0" - packaging/build_conda.sh - - store_test_results: - path: build_results/ - - binary_win_conda_cuda: - <<: *binary_common - executor: windows-gpu - steps: - - checkout_merge - - run: - command: | - set -ex - source packaging/windows/internal/vc_install_helper.sh - eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" - conda activate base - conda install -yq conda-build "conda-package-handling!=1.5.0" - packaging/build_conda.sh - - binary_win_conda_release: <<: *binary_common executor: windows-cpu steps: @@ -279,7 +180,7 @@ jobs: - store_test_results: path: build_results/ - binary_win_wheel_release: + binary_win_wheel: <<: *binary_common executor: windows-cpu steps: @@ -385,6 +286,159 @@ jobs: aws s3 cp "$pkg" "s3://pytorch/whl/${UPLOAD_CHANNEL}/<< parameters.subfolder >>" --acl public-read done + unittest_linux_cpu: + <<: *binary_common + docker: + - image: "pytorch/manylinux-cuda102" + resource_class: 2xlarge+ + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + + keys: + - env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - save_cache: + + key: env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + paths: + - conda + - env + - run: + name: Install torchvision + command: .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/linux/scripts/run_test.sh + - run: + name: Post process + command: .circleci/unittest/linux/scripts/post_process.sh + - store_test_results: + path: test-results + + unittest_linux_gpu: + <<: *binary_common + machine: + image: ubuntu-1604-cuda-10.1:201909-23 + resource_class: gpu.small + environment: + image_name: "pytorch/manylinux-cuda101" + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + + keys: + - env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + - run: + name: Setup + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/setup_env.sh + - save_cache: + + key: env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + paths: + - conda + - env + - run: + name: Install torchvision + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/run_test.sh + - run: + name: Post Process + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/post_process.sh + - store_test_results: + path: test-results + + unittest_windows_cpu: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + + keys: + - env-v2-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - save_cache: + + key: env-v2-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + paths: + - conda + - env + - run: + name: Install torchvision + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - run: + name: Post process + command: .circleci/unittest/windows/scripts/post_process.sh + - store_test_results: + path: test-results + + unittest_windows_gpu: + <<: *binary_common + executor: + name: windows-gpu + environment: + CUDA_VERSION: "10.1" + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + + keys: + - env-v1-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - save_cache: + + key: env-v1-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + + paths: + - conda + - env + - run: + name: Install torchvision + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - run: + name: Post process + command: .circleci/unittest/windows/scripts/post_process.sh + - store_test_results: + path: test-results workflows: build: @@ -465,7 +519,7 @@ workflows: name: binary_macos_wheel_py3.8_cpu python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cpu filters: branches: @@ -474,7 +528,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cpu python_version: '3.6' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu92 filters: branches: @@ -483,7 +537,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cu92 python_version: '3.6' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu101 filters: branches: @@ -492,7 +546,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cu101 python_version: '3.6' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu102 filters: branches: @@ -501,7 +555,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.6_cu102 python_version: '3.6' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cpu filters: branches: @@ -510,7 +564,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cpu python_version: '3.7' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu92 filters: branches: @@ -519,7 +573,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cu92 python_version: '3.7' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu101 filters: branches: @@ -528,7 +582,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cu101 python_version: '3.7' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu102 filters: branches: @@ -537,11 +591,11 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.7_cu102 python_version: '3.7' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cpu name: binary_win_wheel_py3.8_cpu python_version: '3.8' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu92 filters: branches: @@ -550,7 +604,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.8_cu92 python_version: '3.8' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu101 filters: branches: @@ -559,7 +613,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_wheel_py3.8_cu101 python_version: '3.8' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu102 name: binary_win_wheel_py3.8_cu102 python_version: '3.8' @@ -638,7 +692,7 @@ workflows: name: binary_macos_conda_py3.8_cpu python_version: '3.8' wheel_docker_image: pytorch/manylinux-cuda102 - - binary_win_conda_release: + - binary_win_conda: cu_version: cpu filters: branches: @@ -647,7 +701,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cpu python_version: '3.6' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu92 filters: branches: @@ -656,7 +710,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cu92 python_version: '3.6' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu101 filters: branches: @@ -665,7 +719,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cu101 python_version: '3.6' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu102 filters: branches: @@ -674,7 +728,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.6_cu102 python_version: '3.6' - - binary_win_conda_release: + - binary_win_conda: cu_version: cpu filters: branches: @@ -683,7 +737,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cpu python_version: '3.7' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu92 filters: branches: @@ -692,7 +746,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cu92 python_version: '3.7' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu101 filters: branches: @@ -701,7 +755,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cu101 python_version: '3.7' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu102 filters: branches: @@ -710,11 +764,11 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.7_cu102 python_version: '3.7' - - binary_win_conda_release: + - binary_win_conda: cu_version: cpu name: binary_win_conda_py3.8_cpu python_version: '3.8' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu92 filters: branches: @@ -723,7 +777,7 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.8_cu92 python_version: '3.8' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu101 filters: branches: @@ -732,26 +786,84 @@ workflows: only: /v[0-9]+(\.[0-9]+)*-rc[0-9]+/ name: binary_win_conda_py3.8_cu101 python_version: '3.8' - - binary_win_conda_release: + - binary_win_conda: cu_version: cu102 name: binary_win_conda_py3.8_cu102 python_version: '3.8' - - binary_linux_conda_cuda: - name: torchvision_linux_py3.8_cu102_cuda - python_version: "3.8" - cu_version: "cu102" - - binary_win_conda: - name: torchvision_win_py3.6_cpu - python_version: "3.6" - cu_version: "cpu" - - binary_win_conda_cuda: - name: torchvision_win_py3.6_cu101 - python_version: "3.6" - cu_version: "cu101" - python_lint - python_type_check - clang_format + unittest: + jobs: + - unittest_linux_cpu: + cu_version: cpu + name: unittest_linux_cpu_py3.6 + python_version: '3.6' + - unittest_linux_cpu: + cu_version: cpu + name: unittest_linux_cpu_py3.7 + python_version: '3.7' + - unittest_linux_cpu: + cu_version: cpu + name: unittest_linux_cpu_py3.8 + python_version: '3.8' + - unittest_linux_gpu: + cu_version: cu101 + filters: + branches: + only: + - master + - nightly + name: unittest_linux_gpu_py3.6 + python_version: '3.6' + - unittest_linux_gpu: + cu_version: cu101 + filters: + branches: + only: + - master + - nightly + name: unittest_linux_gpu_py3.7 + python_version: '3.7' + - unittest_linux_gpu: + cu_version: cu101 + name: unittest_linux_gpu_py3.8 + python_version: '3.8' + - unittest_windows_cpu: + cu_version: cpu + name: unittest_windows_cpu_py3.6 + python_version: '3.6' + - unittest_windows_cpu: + cu_version: cpu + name: unittest_windows_cpu_py3.7 + python_version: '3.7' + - unittest_windows_cpu: + cu_version: cpu + name: unittest_windows_cpu_py3.8 + python_version: '3.8' + - unittest_windows_gpu: + cu_version: cu101 + filters: + branches: + only: + - master + - nightly + name: unittest_windows_gpu_py3.6 + python_version: '3.6' + - unittest_windows_gpu: + cu_version: cu101 + filters: + branches: + only: + - master + - nightly + name: unittest_windows_gpu_py3.7 + python_version: '3.7' + - unittest_windows_gpu: + cu_version: cu101 + name: unittest_windows_gpu_py3.8 + python_version: '3.8' nightly: jobs: - circleci_consistency @@ -1073,7 +1185,7 @@ workflows: requires: - nightly_binary_macos_wheel_py3.8_cpu subfolder: '' - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cpu filters: branches: @@ -1093,7 +1205,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.6_cpu subfolder: cpu/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu92 filters: branches: @@ -1113,7 +1225,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.6_cu92 subfolder: cu92/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu101 filters: branches: @@ -1133,7 +1245,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.6_cu101 subfolder: cu101/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu102 filters: branches: @@ -1153,7 +1265,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.6_cu102 subfolder: cu102/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cpu filters: branches: @@ -1173,7 +1285,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.7_cpu subfolder: cpu/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu92 filters: branches: @@ -1193,7 +1305,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.7_cu92 subfolder: cu92/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu101 filters: branches: @@ -1213,7 +1325,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.7_cu101 subfolder: cu101/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu102 filters: branches: @@ -1233,7 +1345,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.7_cu102 subfolder: cu102/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cpu filters: branches: @@ -1253,7 +1365,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.8_cpu subfolder: cpu/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu92 filters: branches: @@ -1273,7 +1385,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.8_cu92 subfolder: cu92/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu101 filters: branches: @@ -1293,7 +1405,7 @@ workflows: requires: - nightly_binary_win_wheel_py3.8_cu101 subfolder: cu101/ - - binary_win_wheel_release: + - binary_win_wheel: cu_version: cu102 filters: branches: @@ -1613,7 +1725,7 @@ workflows: name: nightly_binary_macos_conda_py3.8_cpu_upload requires: - nightly_binary_macos_conda_py3.8_cpu - - binary_win_conda_release: + - binary_win_conda: cu_version: cpu filters: branches: @@ -1632,7 +1744,7 @@ workflows: name: nightly_binary_win_conda_py3.6_cpu_upload requires: - nightly_binary_win_conda_py3.6_cpu - - binary_win_conda_release: + - binary_win_conda: cu_version: cu92 filters: branches: @@ -1651,7 +1763,7 @@ workflows: name: nightly_binary_win_conda_py3.6_cu92_upload requires: - nightly_binary_win_conda_py3.6_cu92 - - binary_win_conda_release: + - binary_win_conda: cu_version: cu101 filters: branches: @@ -1670,7 +1782,7 @@ workflows: name: nightly_binary_win_conda_py3.6_cu101_upload requires: - nightly_binary_win_conda_py3.6_cu101 - - binary_win_conda_release: + - binary_win_conda: cu_version: cu102 filters: branches: @@ -1689,7 +1801,7 @@ workflows: name: nightly_binary_win_conda_py3.6_cu102_upload requires: - nightly_binary_win_conda_py3.6_cu102 - - binary_win_conda_release: + - binary_win_conda: cu_version: cpu filters: branches: @@ -1708,7 +1820,7 @@ workflows: name: nightly_binary_win_conda_py3.7_cpu_upload requires: - nightly_binary_win_conda_py3.7_cpu - - binary_win_conda_release: + - binary_win_conda: cu_version: cu92 filters: branches: @@ -1727,7 +1839,7 @@ workflows: name: nightly_binary_win_conda_py3.7_cu92_upload requires: - nightly_binary_win_conda_py3.7_cu92 - - binary_win_conda_release: + - binary_win_conda: cu_version: cu101 filters: branches: @@ -1746,7 +1858,7 @@ workflows: name: nightly_binary_win_conda_py3.7_cu101_upload requires: - nightly_binary_win_conda_py3.7_cu101 - - binary_win_conda_release: + - binary_win_conda: cu_version: cu102 filters: branches: @@ -1765,7 +1877,7 @@ workflows: name: nightly_binary_win_conda_py3.7_cu102_upload requires: - nightly_binary_win_conda_py3.7_cu102 - - binary_win_conda_release: + - binary_win_conda: cu_version: cpu filters: branches: @@ -1784,7 +1896,7 @@ workflows: name: nightly_binary_win_conda_py3.8_cpu_upload requires: - nightly_binary_win_conda_py3.8_cpu - - binary_win_conda_release: + - binary_win_conda: cu_version: cu92 filters: branches: @@ -1803,7 +1915,7 @@ workflows: name: nightly_binary_win_conda_py3.8_cu92_upload requires: - nightly_binary_win_conda_py3.8_cu92 - - binary_win_conda_release: + - binary_win_conda: cu_version: cu101 filters: branches: @@ -1822,7 +1934,7 @@ workflows: name: nightly_binary_win_conda_py3.8_cu101_upload requires: - nightly_binary_win_conda_py3.8_cu101 - - binary_win_conda_release: + - binary_win_conda: cu_version: cu102 filters: branches: diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index 620e00807d4..d9bd257eae6 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -155,106 +155,7 @@ jobs: - store_test_results: path: build_results/ - binary_linux_conda_cuda: - <<: *binary_common - machine: - image: ubuntu-1604:201903-01 - resource_class: gpu.medium - steps: - - checkout_merge - - run: - name: Setup environment - command: | - set -ex - - curl -L https://packagecloud.io/circleci/trusty/gpgkey | sudo apt-key add - - curl -L https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - - sudo apt-get update - - sudo apt-get install \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg-agent \ - software-properties-common - - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - - sudo add-apt-repository \ - "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) \ - stable" - - sudo apt-get update - export DOCKER_VERSION="5:19.03.2~3-0~ubuntu-xenial" - sudo apt-get install docker-ce=${DOCKER_VERSION} docker-ce-cli=${DOCKER_VERSION} containerd.io=1.2.6-3 - - # Add the package repositories - distribution=$(. /etc/os-release;echo $ID$VERSION_ID) - curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list - - export NVIDIA_CONTAINER_VERSION="1.0.3-1" - sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit=${NVIDIA_CONTAINER_VERSION} - sudo systemctl restart docker - - DRIVER_FN="NVIDIA-Linux-x86_64-440.59.run" - wget "https://s3.amazonaws.com/ossci-linux/nvidia_driver/$DRIVER_FN" - sudo /bin/bash "$DRIVER_FN" -s --no-drm || (sudo cat /var/log/nvidia-installer.log && false) - nvidia-smi - - - run: - name: Pull docker image - command: | - set -ex - export DOCKER_IMAGE=pytorch/conda-cuda - echo Pulling docker image $DOCKER_IMAGE - docker pull $DOCKER_IMAGE >/dev/null - - - run: - name: Build and run tests - command: | - set -ex - - cd ${HOME}/project/ - - export DOCKER_IMAGE=pytorch/conda-cuda - export VARS_TO_PASS="-e PYTHON_VERSION -e BUILD_VERSION -e PYTORCH_VERSION -e UNICODE_ABI -e CU_VERSION" - - docker run --gpus all --ipc=host -v $(pwd):/remote -w /remote ${VARS_TO_PASS} ${DOCKER_IMAGE} ./packaging/build_conda.sh - binary_win_conda: - <<: *binary_common - executor: windows-cpu - steps: - - checkout_merge - - run: - command: | - set -ex - source packaging/windows/internal/vc_install_helper.sh - eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" - conda activate base - conda install -yq conda-build "conda-package-handling!=1.5.0" - packaging/build_conda.sh - - store_test_results: - path: build_results/ - - binary_win_conda_cuda: - <<: *binary_common - executor: windows-gpu - steps: - - checkout_merge - - run: - command: | - set -ex - source packaging/windows/internal/vc_install_helper.sh - eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" - conda activate base - conda install -yq conda-build "conda-package-handling!=1.5.0" - packaging/build_conda.sh - - binary_win_conda_release: <<: *binary_common executor: windows-cpu steps: @@ -279,7 +180,7 @@ jobs: - store_test_results: path: build_results/ - binary_win_wheel_release: + binary_win_wheel: <<: *binary_common executor: windows-cpu steps: @@ -385,29 +286,173 @@ jobs: aws s3 cp "$pkg" "s3://pytorch/whl/${UPLOAD_CHANNEL}/<< parameters.subfolder >>" --acl public-read done + unittest_linux_cpu: + <<: *binary_common + docker: + - image: "pytorch/manylinux-cuda102" + resource_class: 2xlarge+ + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + {% raw %} + keys: + - env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - save_cache: + {% raw %} + key: env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + paths: + - conda + - env + - run: + name: Install torchvision + command: .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/linux/scripts/run_test.sh + - run: + name: Post process + command: .circleci/unittest/linux/scripts/post_process.sh + - store_test_results: + path: test-results + + unittest_linux_gpu: + <<: *binary_common + machine: + image: ubuntu-1604-cuda-10.1:201909-23 + resource_class: gpu.small + environment: + image_name: "pytorch/manylinux-cuda101" + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + {% raw %} + keys: + - env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + - run: + name: Setup + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/setup_env.sh + - save_cache: + {% raw %} + key: env-v2-linux-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/linux/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + paths: + - conda + - env + - run: + name: Install torchvision + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/run_test.sh + - run: + name: Post Process + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/post_process.sh + - store_test_results: + path: test-results + + unittest_windows_cpu: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + {% raw %} + keys: + - env-v2-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - save_cache: + {% raw %} + key: env-v2-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + paths: + - conda + - env + - run: + name: Install torchvision + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - run: + name: Post process + command: .circleci/unittest/windows/scripts/post_process.sh + - store_test_results: + path: test-results + + unittest_windows_gpu: + <<: *binary_common + executor: + name: windows-gpu + environment: + CUDA_VERSION: "10.1" + steps: + - checkout + - run: + name: Generate cache key + # This will refresh cache on Sundays, nightly build should generate new cache. + command: echo "$(date +"%Y-%U")" > .circleci-weekly + - restore_cache: + {% raw %} + keys: + - env-v1-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - save_cache: + {% raw %} + key: env-v1-windows-{{ arch }}-py<< parameters.python_version >>-{{ checksum ".circleci/unittest/windows/scripts/environment.yml" }}-{{ checksum ".circleci-weekly" }} + {% endraw %} + paths: + - conda + - env + - run: + name: Install torchvision + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - run: + name: Post process + command: .circleci/unittest/windows/scripts/post_process.sh + - store_test_results: + path: test-results workflows: build: {%- if True %} jobs: - circleci_consistency - {{ workflows(windows_latest_only=True) }} - - binary_linux_conda_cuda: - name: torchvision_linux_py3.8_cu102_cuda - python_version: "3.8" - cu_version: "cu102" - - binary_win_conda: - name: torchvision_win_py3.6_cpu - python_version: "3.6" - cu_version: "cpu" - - binary_win_conda_cuda: - name: torchvision_win_py3.6_cu101 - python_version: "3.6" - cu_version: "cu101" + {{ build_workflows(windows_latest_only=True) }} - python_lint - python_type_check - clang_format + unittest: + jobs: + {{ unittest_workflows() }} nightly: {%- endif %} jobs: @@ -415,4 +460,4 @@ workflows: - python_lint - python_type_check - clang_format - {{ workflows(prefix="nightly_", filter_branch="nightly", upload=True) }} + {{ build_workflows(prefix="nightly_", filter_branch="nightly", upload=True) }} diff --git a/.circleci/regenerate.py b/.circleci/regenerate.py index a0bfd84048d..cf940f42f04 100755 --- a/.circleci/regenerate.py +++ b/.circleci/regenerate.py @@ -19,11 +19,14 @@ import os.path -def workflows(prefix='', filter_branch=None, upload=False, indentation=6, windows_latest_only=False): +PYTHON_VERSIONS = ["3.6", "3.7", "3.8"] + + +def build_workflows(prefix='', filter_branch=None, upload=False, indentation=6, windows_latest_only=False): w = [] for btype in ["wheel", "conda"]: for os_type in ["linux", "macos", "win"]: - python_versions = ["3.6", "3.7", "3.8"] + python_versions = PYTHON_VERSIONS cu_versions = (["cpu", "cu92", "cu101", "cu102"] if os_type == "linux" or os_type == "win" else ["cpu"]) for python_version in python_versions: for cu_version in cu_versions: @@ -97,10 +100,14 @@ def generate_base_workflow(base_workflow_name, python_version, cu_version, } } - w = f"binary_{os_type}_{btype}_release" if os_type == "win" else f"binary_{os_type}_{btype}" + w = f"binary_{os_type}_{btype}" return {w: d} +def gen_filter_branch_tree(*branches): + return {"branches": {"only": [b for b in branches]}} + + def generate_upload_workflow(base_workflow_name, os_type, btype, cu_version, *, filter_branch=None): d = { "name": f"{base_workflow_name}_upload", @@ -131,6 +138,28 @@ def indent(indentation, data_list): yaml.dump(data_list, default_flow_style=False).splitlines()) +def unittest_workflows(indentation=6): + jobs = [] + for os_type in ["linux", "windows"]: + for device_type in ["cpu", "gpu"]: + for i, python_version in enumerate(PYTHON_VERSIONS): + job = { + "name": f"unittest_{os_type}_{device_type}_py{python_version}", + "python_version": python_version, + } + + if device_type == 'gpu': + if python_version != "3.8": + job['filters'] = gen_filter_branch_tree('master', 'nightly') + job['cu_version'] = 'cu101' + else: + job['cu_version'] = 'cpu' + + jobs.append({f"unittest_{os_type}_{device_type}": job}) + + return indent(indentation, jobs) + + if __name__ == "__main__": d = os.path.dirname(__file__) env = jinja2.Environment( @@ -140,4 +169,7 @@ def indent(indentation, data_list): ) with open(os.path.join(d, 'config.yml'), 'w') as f: - f.write(env.get_template('config.yml.in').render(workflows=workflows)) + f.write(env.get_template('config.yml.in').render( + build_workflows=build_workflows, + unittest_workflows=unittest_workflows, + )) diff --git a/.circleci/unittest/linux/scripts/environment.yml b/.circleci/unittest/linux/scripts/environment.yml new file mode 100644 index 00000000000..d664c7c0f7f --- /dev/null +++ b/.circleci/unittest/linux/scripts/environment.yml @@ -0,0 +1,14 @@ +channels: + - defaults +dependencies: + - numpy + - pytest + - pytest-cov + - codecov + - pip + - ca-certificates + - pip: + - future + - pillow>=4.1.1 + - scipy + - av \ No newline at end of file diff --git a/.circleci/unittest/linux/scripts/install.sh b/.circleci/unittest/linux/scripts/install.sh new file mode 100755 index 00000000000..966a6b45eb7 --- /dev/null +++ b/.circleci/unittest/linux/scripts/install.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +unset PYTORCH_VERSION +# For unittest, nightly PyTorch is used as the following section, +# so no need to set PYTORCH_VERSION. +# In fact, keeping PYTORCH_VERSION forces us to hardcode PyTorch version in config. + +set -e + +eval "$(./conda/bin/conda shell.bash hook)" +conda activate ./env + +if [ -z "${CUDA_VERSION:-}" ] ; then + cudatoolkit="cpuonly" +else + version="$(python -c "print('.'.join(\"${CUDA_VERSION}\".split('.')[:2]))")" + cudatoolkit="cudatoolkit=${version}" +fi +printf "Installing PyTorch with %s\n" "${cudatoolkit}" +conda install -y -c pytorch-nightly pytorch "${cudatoolkit}" + +printf "* Installing torchvision\n" +python setup.py develop \ No newline at end of file diff --git a/.circleci/unittest/linux/scripts/post_process.sh b/.circleci/unittest/linux/scripts/post_process.sh new file mode 100755 index 00000000000..b05be6da37e --- /dev/null +++ b/.circleci/unittest/linux/scripts/post_process.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +eval "$(./conda/bin/conda shell.bash hook)" +conda activate ./env + +codecov \ No newline at end of file diff --git a/.circleci/unittest/linux/scripts/run_test.sh b/.circleci/unittest/linux/scripts/run_test.sh new file mode 100755 index 00000000000..c572cfea2c5 --- /dev/null +++ b/.circleci/unittest/linux/scripts/run_test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +eval "$(./conda/bin/conda shell.bash hook)" +conda activate ./env + +python -m torch.utils.collect_env +pytest --cov=torchvision --junitxml=test-results/junit.xml -v --durations 20 test \ No newline at end of file diff --git a/.circleci/unittest/linux/scripts/setup_env.sh b/.circleci/unittest/linux/scripts/setup_env.sh new file mode 100755 index 00000000000..356c806b240 --- /dev/null +++ b/.circleci/unittest/linux/scripts/setup_env.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +# This script is for setting up environment in which unit test is ran. +# To speed up the CI time, the resulting environment is cached. +# +# Do not install PyTorch and torchvision here, otherwise they also get cached. + +set -e + +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +root_dir="$(git rev-parse --show-toplevel)" +conda_dir="${root_dir}/conda" +env_dir="${root_dir}/env" + +cd "${root_dir}" + +# 1. Install conda at ./conda +if [ ! -d "${conda_dir}" ]; then + printf "* Installing conda\n" + wget -O miniconda.sh http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh + bash ./miniconda.sh -b -f -p "${conda_dir}" +fi +eval "$(${conda_dir}/bin/conda shell.bash hook)" + +# 2. Create test environment at ./env +if [ ! -d "${env_dir}" ]; then + printf "* Creating a test environment\n" + conda create --prefix "${env_dir}" -y python="$PYTHON_VERSION" +fi +conda activate "${env_dir}" + +# 3. Install Conda dependencies +printf "* Installing dependencies (except PyTorch)\n" +conda env update --file "${this_dir}/environment.yml" --prune diff --git a/.circleci/unittest/windows/scripts/environment.yml b/.circleci/unittest/windows/scripts/environment.yml new file mode 100644 index 00000000000..fbe92df523d --- /dev/null +++ b/.circleci/unittest/windows/scripts/environment.yml @@ -0,0 +1,14 @@ +channels: + - defaults +dependencies: + - numpy + - pytest + - pytest-cov + - codecov + - pip + - ca-certificates + - pip: + - future + - pillow>=4.1.1 + - scipy==1.4.1 + - av \ No newline at end of file diff --git a/.circleci/unittest/windows/scripts/install.sh b/.circleci/unittest/windows/scripts/install.sh new file mode 100644 index 00000000000..584a868562d --- /dev/null +++ b/.circleci/unittest/windows/scripts/install.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +unset PYTORCH_VERSION +# For unittest, nightly PyTorch is used as the following section, +# so no need to set PYTORCH_VERSION. +# In fact, keeping PYTORCH_VERSION forces us to hardcode PyTorch version in config. + +set -e + +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +eval "$(./conda/Scripts/conda.exe 'shell.bash' 'hook')" +conda activate ./env + +if [ -z "${CUDA_VERSION:-}" ] ; then + cudatoolkit="cpuonly" +else + version="$(python -c "print('.'.join(\"${CUDA_VERSION}\".split('.')[:2]))")" + cudatoolkit="cudatoolkit=${version}" +fi +printf "Installing PyTorch with %s\n" "${cudatoolkit}" +conda install -y -c pytorch-nightly pytorch "${cudatoolkit}" + +printf "* Installing torchvision\n" +"$this_dir/vc_env_helper.bat" python setup.py develop \ No newline at end of file diff --git a/.circleci/unittest/windows/scripts/install_conda.bat b/.circleci/unittest/windows/scripts/install_conda.bat new file mode 100644 index 00000000000..6612fba56f6 --- /dev/null +++ b/.circleci/unittest/windows/scripts/install_conda.bat @@ -0,0 +1 @@ +start /wait "" "%miniconda_exe%" /S /InstallationType=JustMe /RegisterPython=0 /AddToPath=0 /D=%tmp_conda% \ No newline at end of file diff --git a/.circleci/unittest/windows/scripts/post_process.sh b/.circleci/unittest/windows/scripts/post_process.sh new file mode 100644 index 00000000000..b132113194b --- /dev/null +++ b/.circleci/unittest/windows/scripts/post_process.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +eval "$(./conda/Scripts/conda.exe 'shell.bash' 'hook')" +conda activate ./env + +codecov diff --git a/.circleci/unittest/windows/scripts/run_test.sh b/.circleci/unittest/windows/scripts/run_test.sh new file mode 100644 index 00000000000..34de9339429 --- /dev/null +++ b/.circleci/unittest/windows/scripts/run_test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -e + +eval "$(./conda/Scripts/conda.exe 'shell.bash' 'hook')" +conda activate ./env + +python -m torch.utils.collect_env +pytest --cov=torchvision --junitxml=test-results/junit.xml -v --durations 20 test \ No newline at end of file diff --git a/.circleci/unittest/windows/scripts/setup_env.sh b/.circleci/unittest/windows/scripts/setup_env.sh new file mode 100644 index 00000000000..9b8d26a3e94 --- /dev/null +++ b/.circleci/unittest/windows/scripts/setup_env.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# This script is for setting up environment in which unit test is ran. +# To speed up the CI time, the resulting environment is cached. +# +# Do not install PyTorch and torchvision here, otherwise they also get cached. + +set -e + +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +root_dir="$(git rev-parse --show-toplevel)" +conda_dir="${root_dir}/conda" +env_dir="${root_dir}/env" + +cd "${root_dir}" + +# 1. Install conda at ./conda +if [ ! -d "${conda_dir}" ]; then + printf "* Installing conda\n" + export tmp_conda="$(echo $conda_dir | tr '/' '\\')" + export miniconda_exe="$(echo $root_dir | tr '/' '\\')\\miniconda.exe" + curl --output miniconda.exe https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe -O + "$this_dir/install_conda.bat" + unset tmp_conda + unset miniconda_exe +fi + +eval "$(${conda_dir}/Scripts/conda.exe 'shell.bash' 'hook')" + +# 2. Create test environment at ./env +if [ ! -d "${env_dir}" ]; then + printf "* Creating a test environment\n" + conda create --prefix "${env_dir}" -y python="$PYTHON_VERSION" +fi +conda activate "${env_dir}" + +# 3. Install Conda dependencies +printf "* Installing dependencies (except PyTorch)\n" +conda env update --file "${this_dir}/environment.yml" --prune \ No newline at end of file diff --git a/.circleci/unittest/windows/scripts/vc_env_helper.bat b/.circleci/unittest/windows/scripts/vc_env_helper.bat new file mode 100644 index 00000000000..9410135677a --- /dev/null +++ b/.circleci/unittest/windows/scripts/vc_env_helper.bat @@ -0,0 +1,39 @@ +@echo on + +set VC_VERSION_LOWER=16 +set VC_VERSION_UPPER=17 + +for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -legacy -products * -version [%VC_VERSION_LOWER%^,%VC_VERSION_UPPER%^) -property installationPath`) do ( + if exist "%%i" if exist "%%i\VC\Auxiliary\Build\vcvarsall.bat" ( + set "VS15INSTALLDIR=%%i" + set "VS15VCVARSALL=%%i\VC\Auxiliary\Build\vcvarsall.bat" + goto vswhere + ) +) + +:vswhere +if "%VSDEVCMD_ARGS%" == "" ( + call "%VS15VCVARSALL%" x64 || exit /b 1 +) else ( + call "%VS15VCVARSALL%" x64 %VSDEVCMD_ARGS% || exit /b 1 +) + +@echo on + +set DISTUTILS_USE_SDK=1 + +set args=%1 +shift +:start +if [%1] == [] goto done +set args=%args% %1 +shift +goto start + +:done +if "%args%" == "" ( + echo Usage: vc_env_helper.bat [command] [args] + echo e.g. vc_env_helper.bat cl /c test.cpp +) + +%args% || exit /b 1 diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index 8be8eabdd09..7d6f28cdf3c 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -47,8 +47,6 @@ test: - av - ca-certificates {{ environ.get('CONDA_TYPING_CONSTRAINT') }} - commands: - pytest . --verbose --junitxml={{ environ.get("CONDA_PYTORCH_BUILD_RESULTS_DIRECTORY", "build/test_results.xml" )}} about: From 766721b1dfd7a92130146a549c4fcca15cc069b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Tue, 30 Jun 2020 12:23:31 -0500 Subject: [PATCH 20/51] PR: Add libpng and libjpeg-turbo requirement into conda recipe (#2301) * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Add PNG reading capabilities * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Add PNG reading capabilities * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Install libpng on conda-based wheel distributions * Add -y flag * Add -y flag to yum * Locate LibPNG on windows conda * Remove empty else * Copy libpng16.so * Copy dylib on Mac * Improve check on Windows * Try to install ninja using conda on windows * Use libpng on Windows * Package lib on windows wheel * Point library to the correct place * Include binaries as part of wheel * Copy libpng.so on linux * Look for png.h on Windows when using conda-build * Do not skip png tests on Mac/Win * Restore libjpeg-turbo * Install jpeg-turbo on wheel distributions * Install libjpeg-turbo from conda-forge on wheel distributions * Do not pull av on conda-build * Add pillow disclaimer * Vendors libjpeg-turbo 2.0.4 * Merge JPEG work * Remove submodules * Regenerate circle config * Fix style issues * Fix C++ style issues * More style corrections * Add JPEG-turbo to linking libraries * More style corrections * More style corrections * More style corrections * Install libjpeg-turbo-devel * Install libturbo-jpeg on typing pipeline * Update Circle template * Windows and Unix turbojpeg have the same linking name * Install turbojpeg-devel instead of libjpeg-turbo * Copy TurboJPEG binaries to wheel * Move test image * Move back test image * Update JPEG test path * Remove dot from extension * Move image functions to extension * Use stdout arg in subprocess * Disable image extension if libpng or turbojpeg are not found * Append libpng stdout * Prevent list appending on lists * Minor path correction * Minor error correction * Add linking flags * Style issues correction * Address minor review corrections * Refactor library search * Restore access index * Fix JPEG tests * Update libpng version in Travis * Add -y flag * Remove dot * Update libpng using apt * Check libpng version * Change libturbojpeg binary * Update import * Change call * Restore av in conda recipe * Minor error correction * Remove unused comment in travis.yml * Update README * Fix missing links * Remove fixes for 16.04 Co-authored-by: Ryad ZENINE --- .circleci/config.yml | 2 + .circleci/config.yml.in | 2 + .travis.yml | 1 + CMakeLists.txt | 16 +- README.rst | 13 +- packaging/build_wheel.sh | 24 +++ packaging/pkg_helpers.bash | 6 + packaging/torchvision/conda_build_config.yaml | 3 + packaging/torchvision/meta.yaml | 6 + setup.py | 137 +++++++++++++++++- test/test_image.py | 72 +++++++++ torchvision/csrc/cpu/image/image.cpp | 24 +++ torchvision/csrc/cpu/image/image.h | 7 + torchvision/csrc/cpu/image/readjpeg_cpu.cpp | 56 +++++++ torchvision/csrc/cpu/image/readjpeg_cpu.h | 5 + torchvision/csrc/cpu/image/readpng_cpu.cpp | 83 +++++++++++ torchvision/csrc/cpu/image/readpng_cpu.h | 6 + torchvision/io/__init__.py | 2 +- torchvision/io/image.py | 109 ++++++++++++++ 19 files changed, 563 insertions(+), 11 deletions(-) create mode 100644 test/test_image.py create mode 100644 torchvision/csrc/cpu/image/image.cpp create mode 100644 torchvision/csrc/cpu/image/image.h create mode 100644 torchvision/csrc/cpu/image/readjpeg_cpu.cpp create mode 100644 torchvision/csrc/cpu/image/readjpeg_cpu.h create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.cpp create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.h create mode 100644 torchvision/io/image.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 29b5fc77aab..f3fc23b7c92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,6 +107,8 @@ jobs: - checkout - run: command: | + sudo apt-get update -y + sudo apt install -y libturbojpeg-dev pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index d9bd257eae6..f63c3f408ba 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -107,6 +107,8 @@ jobs: - checkout - run: command: | + sudo apt-get update -y + sudo apt install -y libturbojpeg-dev pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.travis.yml b/.travis.yml index ec25bdb8677..0757ad3c159 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ jobs: before_install: - sudo apt-get update + - sudo apt-get install -y libpng-dev libturbojpeg-dev - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" diff --git a/CMakeLists.txt b/CMakeLists.txt index fa50f155ce4..0d7e7aaa9d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,22 +11,28 @@ if(WITH_CUDA) endif() find_package(Python3 COMPONENTS Development) + find_package(Torch REQUIRED) +find_package(PNG REQUIRED) + file(GLOB HEADERS torchvision/csrc/*.h) -file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp torchvision/csrc/*.cpp) +file(GLOB IMAGE_HEADERS torchvision/csrc/cpu/image/*.h) +file(GLOB IMAGE_SOURCES torchvision/csrc/cpu/image/*.cpp) +file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp ${IMAGE_HEADERS} ${IMAGE_SOURCES} ${HEADERS} torchvision/csrc/*.cpp) if(WITH_CUDA) file(GLOB OPERATOR_SOURCES ${OPERATOR_SOURCES} torchvision/csrc/cuda/*.h torchvision/csrc/cuda/*.cu) endif() file(GLOB MODELS_HEADERS torchvision/csrc/models/*.h) file(GLOB MODELS_SOURCES torchvision/csrc/models/*.h torchvision/csrc/models/*.cpp) -add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python) +add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES} ${IMAGE_SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} ${PNG_LIBRARY} Python3::Python) +# target_link_libraries(${PROJECT_NAME} PRIVATE ${PNG_LIBRARY} Python3::Python) set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME TorchVision) target_include_directories(${PROJECT_NAME} INTERFACE - $ + $ $) include(GNUInstallDirs) @@ -61,7 +67,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cpu) if(WITH_CUDA) install(FILES - torchvision/csrc/cuda/vision_cuda.h + torchvision/csrc/cuda/vision_cuda.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cuda) endif() install(FILES ${MODELS_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/models) diff --git a/README.rst b/README.rst index 3150a3023ad..d130ad03a78 100644 --- a/README.rst +++ b/README.rst @@ -78,13 +78,22 @@ Torchvision currently supports the following image backends: * `accimage`_ - if installed can be activated by calling :code:`torchvision.set_image_backend('accimage')` +* `libpng`_ - can be installed via conda :code:`conda install libpng` or any of the package managers for debian-based and RHEL-based Linux distributions. + +* `libturbojpeg`_ - blazing speed, fast JPEG image loading. Can be installed from conda-forge :code:`conda install libjpeg-turbo -c conda-forge`. + +**Notes:** ``libpng`` and ``libturbojpeg`` must be available at compilation time in order to be available. Also, most linux distributions distinguish between +``libturbojpeg`` and ``libjpeg-turbo``, where the former should be installed instead of the latter one. + +.. _libpng : http://www.libpng.org/pub/png/libpng.html +.. _libturbojpeg: https://github.com/libjpeg-turbo/libjpeg-turbo .. _Pillow : https://python-pillow.org/ .. _Pillow-SIMD : https://github.com/uploadcare/pillow-simd .. _accimage: https://github.com/pytorch/accimage C++ API ======= -TorchVision also offers a C++ API that contains C++ equivalent of python models. +TorchVision also offers a C++ API that contains C++ equivalent of python models. Installation From source: @@ -94,7 +103,7 @@ Installation From source: cd build # Add -DWITH_CUDA=on support for the CUDA if needed cmake .. - make + make make install Once installed, the library can be accessed in cmake (after properly configuring ``CMAKE_PREFIX_PATH``) via the :code:`TorchVision::TorchVision` target: diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index a075b3b3a00..30ace73d231 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -10,6 +10,30 @@ setup_wheel_python pip_install numpy pyyaml future ninja setup_pip_pytorch_version python setup.py clean + +# Copy binaries to be included in the wheel distribution +if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then + python_exec="$(which python)" + bin_path=$(dirname $python_exec) + env_path=$(dirname $bin_path) + if [[ "$(uname)" == Darwin ]]; then + # Include LibPNG + cp "$env_path/lib/libpng16.dylib" torchvision + # Include TurboJPEG + cp "$env_path/lib/libturbojpeg.dylib" torchvision + else + # Include libPNG + cp "$bin_path/Library/lib/libpng.lib" torchvision + # Include TurboJPEG + cp "$bin_path/Library/lib/turbojpeg.lib" torchvision + fi +else + # Include LibPNG + cp "/usr/lib64/libpng.so" torchvision + # Include TurboJPEG + cp "/usr/lib64/libturbojpeg.so" torchvision +fi + if [[ "$OSTYPE" == "msys" ]]; then IS_WHEEL=1 "$script_dir/windows/internal/vc_env_helper.bat" python setup.py bdist_wheel else diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index b262a0f5157..88fe52c0b08 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -170,7 +170,13 @@ setup_wheel_python() { conda env remove -n "env$PYTHON_VERSION" || true conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" conda activate "env$PYTHON_VERSION" + # Install libPNG from Anaconda (defaults) + conda install libpng -y + # Install libJPEG-turbo from conda-forge + conda install -y libjpeg-turbo -c conda-forge else + # Install native CentOS libPNG, libJPEG-turbo + yum install -y libpng-devel turbojpeg-devel case "$PYTHON_VERSION" in 2.7) if [[ -n "$UNICODE_ABI" ]]; then diff --git a/packaging/torchvision/conda_build_config.yaml b/packaging/torchvision/conda_build_config.yaml index 5188bb0ebec..adbbc732a0c 100644 --- a/packaging/torchvision/conda_build_config.yaml +++ b/packaging/torchvision/conda_build_config.yaml @@ -1,3 +1,6 @@ +channel_sources: + - defaults,conda-forge + blas_impl: - mkl # [x86_64] c_compiler: diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index 7d6f28cdf3c..62cfc401e3c 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -8,6 +8,8 @@ source: requirements: build: - {{ compiler('c') }} # [win] + - libpng + - libjpeg-turbo host: - python @@ -18,6 +20,10 @@ requirements: run: - python + - libpng + - libjpeg-turbo + # Pillow introduces unwanted conflicts with libjpeg-turbo, as it depends on jpeg + # The fix depends on https://github.com/conda-forge/conda-forge.github.io/issues/673 - pillow >=4.1.1 - numpy >=1.11 {{ environ.get('CONDA_PYTORCH_CONSTRAINT') }} diff --git a/setup.py b/setup.py index 0620193b3a7..e454c59e047 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import re import sys from setuptools import setup, find_packages -from pkg_resources import get_distribution, DistributionNotFound +from pkg_resources import parse_version, get_distribution, DistributionNotFound import subprocess import distutils.command.clean import distutils.spawn @@ -76,7 +76,76 @@ def write_version_file(): requirements.append(pillow_req + pillow_ver) +def find_library(name, vision_include): + this_dir = os.path.dirname(os.path.abspath(__file__)) + build_prefix = os.environ.get('BUILD_PREFIX', None) + is_conda_build = build_prefix is not None + + library_found = False + conda_installed = False + lib_folder = None + include_folder = None + library_header = '{0}.h'.format(name) + + print('Running build on conda-build: {0}'.format(is_conda_build)) + if is_conda_build: + # Add conda headers/libraries + if os.name == 'nt': + build_prefix = os.path.join(build_prefix, 'Library') + include_folder = os.path.join(build_prefix, 'include') + lib_folder = os.path.join(build_prefix, 'lib') + library_header_path = os.path.join( + include_folder, library_header) + library_found = os.path.isfile(library_header_path) + conda_installed = library_found + else: + # Check if using Anaconda to produce wheels + conda = distutils.spawn.find_executable('conda') + is_conda = conda is not None + print('Running build on conda: {0}'.format(is_conda)) + if is_conda: + python_executable = sys.executable + py_folder = os.path.dirname(python_executable) + if os.name == 'nt': + env_path = os.path.join(py_folder, 'Library') + else: + env_path = os.path.dirname(py_folder) + lib_folder = os.path.join(env_path, 'lib') + include_folder = os.path.join(env_path, 'include') + library_header_path = os.path.join( + include_folder, library_header) + library_found = os.path.isfile(library_header_path) + conda_installed = library_found + + # Try to locate turbojpeg in Linux standard paths + if not library_found: + if sys.platform == 'linux': + library_found = os.path.exists('/usr/include/{0}'.format( + library_header)) + library_found = library_found or os.path.exists( + '/usr/local/include/{0}'.format(library_header)) + else: + # Lookup in TORCHVISION_INCLUDE or in the package file + package_path = [os.path.join(this_dir, 'torchvision')] + for folder in vision_include + package_path: + candidate_path = os.path.join(folder, library_header) + library_found = os.path.exists(candidate_path) + if library_found: + break + + return library_found, conda_installed, include_folder, lib_folder + + def get_extensions(): + vision_include = os.environ.get('TORCHVISION_INCLUDE', None) + vision_library = os.environ.get('TORCHVISION_LIBRARY', None) + vision_include = (vision_include.split(os.pathsep) + if vision_include is not None else []) + vision_library = (vision_library.split(os.pathsep) + if vision_library is not None else []) + include_dirs = vision_include + library_dirs = vision_library + this_dir = os.path.dirname(os.path.abspath(__file__)) extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') @@ -149,13 +218,14 @@ def get_extensions(): sources = [os.path.join(extensions_dir, s) for s in sources] - include_dirs = [extensions_dir] + include_dirs += [extensions_dir] ext_modules = [ extension( 'torchvision._C', sources, include_dirs=include_dirs, + library_dirs=library_dirs, define_macros=define_macros, extra_compile_args=extra_compile_args, ) @@ -171,6 +241,65 @@ def get_extensions(): ) ) + # Image reading extension + image_macros = [] + image_include = [extensions_dir] + image_library = [] + image_link_flags = [] + + # Locating libPNG + libpng = distutils.spawn.find_executable('libpng-config') + png_found = libpng is not None + image_macros += [('PNG_FOUND', str(int(png_found)))] + print('PNG found: {0}'.format(png_found)) + if png_found: + png_version = subprocess.run([libpng, '--version'], + stdout=subprocess.PIPE) + png_version = png_version.stdout.strip().decode('utf-8') + print('libpng version: {0}'.format(png_version)) + png_version = parse_version(png_version) + if png_version >= parse_version("1.6.0"): + print('Building torchvision with PNG image support') + png_lib = subprocess.run([libpng, '--libdir'], + stdout=subprocess.PIPE) + png_include = subprocess.run([libpng, '--I_opts'], + stdout=subprocess.PIPE) + image_library += [png_lib.stdout.strip().decode('utf-8')] + image_include += [png_include.stdout.strip().decode('utf-8')] + image_link_flags.append('png' if os.name != 'nt' else 'libpng') + else: + print('libpng installed version is less than 1.6.0, ' + 'disabling PNG support') + png_found = False + + # Locating libjpegturbo + turbojpeg_info = find_library('turbojpeg', vision_include) + (turbojpeg_found, conda_installed, + turbo_include_folder, turbo_lib_folder) = turbojpeg_info + + image_macros += [('JPEG_FOUND', str(int(turbojpeg_found)))] + print('turboJPEG found: {0}'.format(turbojpeg_found)) + if turbojpeg_found: + print('Building torchvision with JPEG image support') + image_link_flags.append('turbojpeg') + if conda_installed: + image_library += [turbo_lib_folder] + image_include += [turbo_include_folder] + + image_path = os.path.join(extensions_dir, 'cpu', 'image') + image_src = glob.glob(os.path.join(image_path, '*.cpp')) + + if png_found or turbojpeg_found: + ext_modules.append(extension( + 'torchvision.image', + image_src, + include_dirs=include_dirs + [image_path] + image_include, + library_dirs=library_dirs + image_library, + define_macros=image_macros, + libraries=image_link_flags, + extra_compile_args=extra_compile_args + )) + ffmpeg_exe = distutils.spawn.find_executable('ffmpeg') has_ffmpeg = ffmpeg_exe is not None @@ -243,7 +372,9 @@ def run(self): # Package info packages=find_packages(exclude=('test',)), - + package_data={ + package_name: ['*.lib', '*.dylib', '*.so'] + }, zip_safe=False, install_requires=requirements, extras_require={ diff --git a/test/test_image.py b/test/test_image.py new file mode 100644 index 00000000000..4ac331f399f --- /dev/null +++ b/test/test_image.py @@ -0,0 +1,72 @@ +import os +import unittest +import sys + +import torch +import torchvision +from PIL import Image +from torchvision.io.image import read_png, decode_png, read_jpeg, decode_jpeg +import numpy as np + +IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") +IMAGE_DIR = os.path.join(IMAGE_ROOT, "fakedata", "imagefolder") + + +def get_images(directory, img_ext): + assert os.path.isdir(directory) + for root, _, files in os.walk(directory): + for fl in files: + _, ext = os.path.splitext(fl) + if ext == img_ext: + yield os.path.join(root, fl) + + +class ImageTester(unittest.TestCase): + def test_read_jpeg(self): + for img_path in get_images(IMAGE_ROOT, "jpg"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + img_ljpeg = read_jpeg(img_path) + + norm = img_ljpeg.shape[0] * img_ljpeg.shape[1] * img_ljpeg.shape[2] * 255 + err = torch.abs(img_ljpeg.flatten().float() - img_pil.flatten().float()).sum().float() / (norm) + self.assertLessEqual(err, 1e-2) + + def test_decode_jpeg(self): + for img_path in get_images(IMAGE_ROOT, "jpg"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + size = os.path.getsize(img_path) + img_ljpeg = decode_jpeg(torch.from_file(img_path, dtype=torch.uint8, size=size)) + + norm = img_ljpeg.shape[0] * img_ljpeg.shape[1] * img_ljpeg.shape[2] * 255 + err = torch.abs(img_ljpeg.flatten().float() - img_pil.flatten().float()).sum().float() / (norm) + + self.assertLessEqual(err, 1e-2) + + with self.assertRaisesRegex(ValueError, "Expected a non empty 1-dimensional tensor."): + decode_jpeg(torch.empty((100, 1), dtype=torch.uint8)) + + with self.assertRaisesRegex(ValueError, "Expected a torch.uint8 tensor."): + decode_jpeg(torch.empty((100, ), dtype=torch.float16)) + + with self.assertRaisesRegex(RuntimeError, "Error while reading jpeg headers"): + decode_jpeg(torch.empty((100), dtype=torch.uint8)) + + def test_read_png(self): + for img_path in get_images(IMAGE_DIR, "png"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + img_lpng = read_png(img_path) + self.assertEqual(img_lpng, img_pil) + + def test_decode_png(self): + for img_path in get_images(IMAGE_DIR, "png"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + size = os.path.getsize(img_path) + img_lpng = decode_png(torch.from_file(img_path, dtype=torch.uint8, size=size)) + self.assertEqual(img_lpng, img_pil) + + self.assertEqual(decode_png(torch.empty()), torch.empty()) + self.assertEqual(decode_png(torch.randint(3, 5, (300,))), torch.empty()) + + +if __name__ == '__main__': + unittest.main() diff --git a/torchvision/csrc/cpu/image/image.cpp b/torchvision/csrc/cpu/image/image.cpp new file mode 100644 index 00000000000..33f492ae683 --- /dev/null +++ b/torchvision/csrc/cpu/image/image.cpp @@ -0,0 +1,24 @@ + +#include "image.h" +#include +#include + +// If we are in a Windows environment, we need to define +// initialization functions for the _custom_ops extension +#ifdef _WIN32 +#if PY_MAJOR_VERSION < 3 +PyMODINIT_FUNC init_image(void) { + // No need to do anything. + return NULL; +} +#else +PyMODINIT_FUNC PyInit_image(void) { + // No need to do anything. + return NULL; +} +#endif +#endif + +static auto registry = torch::RegisterOperators() + .op("image::decode_png", &decodePNG) + .op("image::decode_jpeg", &decodeJPEG); diff --git a/torchvision/csrc/cpu/image/image.h b/torchvision/csrc/cpu/image/image.h new file mode 100644 index 00000000000..6db97d4729a --- /dev/null +++ b/torchvision/csrc/cpu/image/image.h @@ -0,0 +1,7 @@ + +#pragma once + +#include +#include +#include "readjpeg_cpu.h" +#include "readpng_cpu.h" diff --git a/torchvision/csrc/cpu/image/readjpeg_cpu.cpp b/torchvision/csrc/cpu/image/readjpeg_cpu.cpp new file mode 100644 index 00000000000..1bf700a2317 --- /dev/null +++ b/torchvision/csrc/cpu/image/readjpeg_cpu.cpp @@ -0,0 +1,56 @@ +#include "readjpeg_cpu.h" + +#include +#include +#include + +#if !JPEG_FOUND + +torch::Tensor decodeJPEG(const torch::Tensor& data) { + AT_ERROR("decodeJPEG: torchvision not compiled with turboJPEG support"); +} + +#else +#include + +torch::Tensor decodeJPEG(const torch::Tensor& data) { + tjhandle tjInstance = tjInitDecompress(); + if (tjInstance == NULL) { + TORCH_CHECK(false, "libjpeg-turbo decompression initialization failed."); + } + + auto datap = data.accessor().data(); + + int width, height; + + if (tjDecompressHeader(tjInstance, datap, data.numel(), &width, &height) < + 0) { + tjDestroy(tjInstance); + TORCH_CHECK(false, "Error while reading jpeg headers"); + } + auto tensor = + torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); + + auto ptr = tensor.accessor().data(); + + int pixelFormat = TJPF_RGB; + + auto ret = tjDecompress2( + tjInstance, + datap, + data.numel(), + ptr, + width, + 0, + height, + pixelFormat, + NULL); + if (ret != 0) { + tjDestroy(tjInstance); + TORCH_CHECK(false, "decompressing JPEG image"); + } + + return tensor; +} + +#endif // JPEG_FOUND diff --git a/torchvision/csrc/cpu/image/readjpeg_cpu.h b/torchvision/csrc/cpu/image/readjpeg_cpu.h new file mode 100644 index 00000000000..40404df29b5 --- /dev/null +++ b/torchvision/csrc/cpu/image/readjpeg_cpu.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +torch::Tensor decodeJPEG(const torch::Tensor& data); diff --git a/torchvision/csrc/cpu/image/readpng_cpu.cpp b/torchvision/csrc/cpu/image/readpng_cpu.cpp new file mode 100644 index 00000000000..b7fb28e2575 --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.cpp @@ -0,0 +1,83 @@ +#include "readpng_cpu.h" + +#include +#include +#include + +#if !PNG_FOUND +torch::Tensor decodePNG(const torch::Tensor& data) { + AT_ERROR("decodePNG: torchvision not compiled with libPNG support"); +} +#else +#include + +torch::Tensor decodePNG(const torch::Tensor& data) { + auto png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + TORCH_CHECK(png_ptr, "libpng read structure allocation failed!") + auto info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + // Seems redundant with the if statement. done here to avoid leaking memory. + TORCH_CHECK(info_ptr, "libpng info structure allocation failed!") + } + + auto datap = data.accessor().data(); + + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(false, "Internal error."); + } + auto is_png = !png_sig_cmp(datap, 0, 8); + TORCH_CHECK(is_png, "Content is not png!") + + struct Reader { + png_const_bytep ptr; + } reader; + reader.ptr = png_const_bytep(datap) + 8; + + auto read_callback = + [](png_structp png_ptr, png_bytep output, png_size_t bytes) { + auto reader = static_cast(png_get_io_ptr(png_ptr)); + std::copy(reader->ptr, reader->ptr + bytes, output); + reader->ptr += bytes; + }; + png_set_sig_bytes(png_ptr, 8); + png_set_read_fn(png_ptr, &reader, read_callback); + png_read_info(png_ptr, info_ptr); + + png_uint_32 width, height; + int bit_depth, color_type; + auto retval = png_get_IHDR( + png_ptr, + info_ptr, + &width, + &height, + &bit_depth, + &color_type, + nullptr, + nullptr, + nullptr); + + if (retval != 1) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(retval == 1, "Could read image metadata from content.") + } + if (color_type != PNG_COLOR_TYPE_RGB) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK( + color_type == PNG_COLOR_TYPE_RGB, "Non RGB images are not supported.") + } + + auto tensor = + torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); + auto ptr = tensor.accessor().data(); + auto bytes = png_get_rowbytes(png_ptr, info_ptr); + for (decltype(height) i = 0; i < height; ++i) { + png_read_row(png_ptr, ptr, nullptr); + ptr += bytes; + } + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return tensor; +} +#endif // PNG_FOUND diff --git a/torchvision/csrc/cpu/image/readpng_cpu.h b/torchvision/csrc/cpu/image/readpng_cpu.h new file mode 100644 index 00000000000..d2151a43aa9 --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +torch::Tensor decodePNG(const torch::Tensor& data); diff --git a/torchvision/io/__init__.py b/torchvision/io/__init__.py index cbbf560412e..4c47d8a51d5 100644 --- a/torchvision/io/__init__.py +++ b/torchvision/io/__init__.py @@ -30,5 +30,5 @@ "_read_video_clip_from_memory", "_read_video_meta_data", "VideoMetaData", - "Timebase", + "Timebase" ] diff --git a/torchvision/io/image.py b/torchvision/io/image.py new file mode 100644 index 00000000000..60d44380356 --- /dev/null +++ b/torchvision/io/image.py @@ -0,0 +1,109 @@ +import torch +from torch import nn, Tensor + +import os +import os.path as osp +import importlib + +_HAS_IMAGE_OPT = False + +try: + lib_dir = osp.join(osp.dirname(__file__), "..") + + loader_details = ( + importlib.machinery.ExtensionFileLoader, + importlib.machinery.EXTENSION_SUFFIXES + ) + + extfinder = importlib.machinery.FileFinder(lib_dir, loader_details) + ext_specs = extfinder.find_spec("image") + if ext_specs is not None: + torch.ops.load_library(ext_specs.origin) + _HAS_IMAGE_OPT = True +except (ImportError, OSError): + pass + + +def decode_png(input): + # type: (Tensor) -> Tensor + """ + Decodes a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + input (Tensor[1]): a one dimensional int8 tensor containing + the raw bytes of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not isinstance(input, torch.Tensor) or len(input) == 0: + raise ValueError("Expected a non empty 1-dimensional tensor.") + + if not input.dtype == torch.uint8: + raise ValueError("Expected a torch.uint8 tensor.") + output = torch.ops.image.decode_png(input) + return output + + +def read_png(path): + # type: (str) -> Tensor + """ + Reads a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + path (str): path of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not os.path.isfile(path): + raise ValueError("Expected a valid file path.") + + size = os.path.getsize(path) + if size == 0: + raise ValueError("Expected a non empty file.") + data = torch.from_file(path, dtype=torch.uint8, size=size) + return decode_png(data) + + +def decode_jpeg(input): + # type: (Tensor) -> Tensor + """ + Decodes a JPEG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + Arguments: + input (Tensor[1]): a one dimensional int8 tensor containing + the raw bytes of the JPEG image. + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not isinstance(input, torch.Tensor) or len(input) == 0 or input.ndim != 1: + raise ValueError("Expected a non empty 1-dimensional tensor.") + + if not input.dtype == torch.uint8: + raise ValueError("Expected a torch.uint8 tensor.") + + output = torch.ops.image.decode_jpeg(input) + return output + + +def read_jpeg(path): + # type: (str) -> Tensor + """ + Reads a JPEG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + Arguments: + path (str): path of the JPEG image. + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not os.path.isfile(path): + raise ValueError("Expected a valid file path.") + + size = os.path.getsize(path) + if size == 0: + raise ValueError("Expected a non empty file.") + data = torch.from_file(path, dtype=torch.uint8, size=size) + return decode_jpeg(data) From d24008c39d22af3dad91399ce30f9ddaa6bda93c Mon Sep 17 00:00:00 2001 From: peterjc123 Date: Wed, 1 Jul 2020 11:25:54 +0800 Subject: [PATCH 21/51] Update MSVC toolchain to 14.13 (#2374) --- packaging/windows/internal/build_conda.bat | 2 +- packaging/windows/internal/build_wheels.bat | 2 +- packaging/windows/internal/vc_install_helper.sh | 2 +- packaging/windows/internal/vs2017_install.ps1 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packaging/windows/internal/build_conda.bat b/packaging/windows/internal/build_conda.bat index e66d5596298..18f0bf13467 100644 --- a/packaging/windows/internal/build_conda.bat +++ b/packaging/windows/internal/build_conda.bat @@ -1,4 +1,4 @@ -if "%VC_YEAR%" == "2017" set VSDEVCMD_ARGS=-vcvars_ver=14.11 +if "%VC_YEAR%" == "2017" set VSDEVCMD_ARGS=-vcvars_ver=14.13 if "%VC_YEAR%" == "2017" powershell packaging/windows/internal/vs2017_install.ps1 if errorlevel 1 exit /b 1 diff --git a/packaging/windows/internal/build_wheels.bat b/packaging/windows/internal/build_wheels.bat index 869e594b395..a321c3ce6e7 100644 --- a/packaging/windows/internal/build_wheels.bat +++ b/packaging/windows/internal/build_wheels.bat @@ -1,4 +1,4 @@ -if "%VC_YEAR%" == "2017" set VSDEVCMD_ARGS=-vcvars_ver=14.11 +if "%VC_YEAR%" == "2017" set VSDEVCMD_ARGS=-vcvars_ver=14.13 if "%VC_YEAR%" == "2017" powershell packaging/windows/internal/vs2017_install.ps1 if errorlevel 1 exit /b 1 diff --git a/packaging/windows/internal/vc_install_helper.sh b/packaging/windows/internal/vc_install_helper.sh index 9910677acac..cdae18065b9 100644 --- a/packaging/windows/internal/vc_install_helper.sh +++ b/packaging/windows/internal/vc_install_helper.sh @@ -4,7 +4,7 @@ set -ex if [[ "$CU_VERSION" == "cu92" ]]; then export VC_YEAR=2017 - export VSDEVCMD_ARGS="-vcvars_ver=14.11" + export VSDEVCMD_ARGS="-vcvars_ver=14.13" powershell packaging/windows/internal/vs2017_install.ps1 elif [[ "$CU_VERSION" == "cu100" ]]; then export VC_YEAR=2017 diff --git a/packaging/windows/internal/vs2017_install.ps1 b/packaging/windows/internal/vs2017_install.ps1 index 6bbb1deb310..3e953de1ab7 100644 --- a/packaging/windows/internal/vs2017_install.ps1 +++ b/packaging/windows/internal/vs2017_install.ps1 @@ -1,6 +1,6 @@ $VS_DOWNLOAD_LINK = "https://aka.ms/vs/15/release/vs_buildtools.exe" $VS_INSTALL_ARGS = @("--nocache","--quiet","--wait", "--add Microsoft.VisualStudio.Workload.VCTools", - "--add Microsoft.VisualStudio.Component.VC.Tools.14.11", + "--add Microsoft.VisualStudio.Component.VC.Tools.14.13", "--add Microsoft.Component.MSBuild", "--add Microsoft.VisualStudio.Component.Roslyn.Compiler", "--add Microsoft.VisualStudio.Component.TextTemplating", From fa6af6d1e6f050d0a930e6f59894b7dd40aa869c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 1 Jul 2020 04:17:14 -0500 Subject: [PATCH 22/51] Revert "PR: Add libpng and libjpeg-turbo requirement into conda recipe (#2301)" (#2375) This reverts commit 766721b1dfd7a92130146a549c4fcca15cc069b2. --- .circleci/config.yml | 2 - .circleci/config.yml.in | 2 - .travis.yml | 1 - CMakeLists.txt | 16 +- README.rst | 13 +- packaging/build_wheel.sh | 24 --- packaging/pkg_helpers.bash | 6 - packaging/torchvision/conda_build_config.yaml | 3 - packaging/torchvision/meta.yaml | 6 - setup.py | 137 +----------------- test/test_image.py | 72 --------- torchvision/csrc/cpu/image/image.cpp | 24 --- torchvision/csrc/cpu/image/image.h | 7 - torchvision/csrc/cpu/image/readjpeg_cpu.cpp | 56 ------- torchvision/csrc/cpu/image/readjpeg_cpu.h | 5 - torchvision/csrc/cpu/image/readpng_cpu.cpp | 83 ----------- torchvision/csrc/cpu/image/readpng_cpu.h | 6 - torchvision/io/__init__.py | 2 +- torchvision/io/image.py | 109 -------------- 19 files changed, 11 insertions(+), 563 deletions(-) delete mode 100644 test/test_image.py delete mode 100644 torchvision/csrc/cpu/image/image.cpp delete mode 100644 torchvision/csrc/cpu/image/image.h delete mode 100644 torchvision/csrc/cpu/image/readjpeg_cpu.cpp delete mode 100644 torchvision/csrc/cpu/image/readjpeg_cpu.h delete mode 100644 torchvision/csrc/cpu/image/readpng_cpu.cpp delete mode 100644 torchvision/csrc/cpu/image/readpng_cpu.h delete mode 100644 torchvision/io/image.py diff --git a/.circleci/config.yml b/.circleci/config.yml index f3fc23b7c92..29b5fc77aab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,8 +107,6 @@ jobs: - checkout - run: command: | - sudo apt-get update -y - sudo apt install -y libturbojpeg-dev pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index f63c3f408ba..d9bd257eae6 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -107,8 +107,6 @@ jobs: - checkout - run: command: | - sudo apt-get update -y - sudo apt install -y libturbojpeg-dev pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.travis.yml b/.travis.yml index 0757ad3c159..ec25bdb8677 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ jobs: before_install: - sudo apt-get update - - sudo apt-get install -y libpng-dev libturbojpeg-dev - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d7e7aaa9d8..fa50f155ce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,28 +11,22 @@ if(WITH_CUDA) endif() find_package(Python3 COMPONENTS Development) - find_package(Torch REQUIRED) -find_package(PNG REQUIRED) - file(GLOB HEADERS torchvision/csrc/*.h) -file(GLOB IMAGE_HEADERS torchvision/csrc/cpu/image/*.h) -file(GLOB IMAGE_SOURCES torchvision/csrc/cpu/image/*.cpp) -file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp ${IMAGE_HEADERS} ${IMAGE_SOURCES} ${HEADERS} torchvision/csrc/*.cpp) +file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp torchvision/csrc/*.cpp) if(WITH_CUDA) file(GLOB OPERATOR_SOURCES ${OPERATOR_SOURCES} torchvision/csrc/cuda/*.h torchvision/csrc/cuda/*.cu) endif() file(GLOB MODELS_HEADERS torchvision/csrc/models/*.h) file(GLOB MODELS_SOURCES torchvision/csrc/models/*.h torchvision/csrc/models/*.cpp) -add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES} ${IMAGE_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} ${PNG_LIBRARY} Python3::Python) -# target_link_libraries(${PROJECT_NAME} PRIVATE ${PNG_LIBRARY} Python3::Python) +add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python) set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME TorchVision) target_include_directories(${PROJECT_NAME} INTERFACE - $ + $ $) include(GNUInstallDirs) @@ -67,7 +61,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cpu) if(WITH_CUDA) install(FILES - torchvision/csrc/cuda/vision_cuda.h + torchvision/csrc/cuda/vision_cuda.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cuda) endif() install(FILES ${MODELS_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/models) diff --git a/README.rst b/README.rst index d130ad03a78..3150a3023ad 100644 --- a/README.rst +++ b/README.rst @@ -78,22 +78,13 @@ Torchvision currently supports the following image backends: * `accimage`_ - if installed can be activated by calling :code:`torchvision.set_image_backend('accimage')` -* `libpng`_ - can be installed via conda :code:`conda install libpng` or any of the package managers for debian-based and RHEL-based Linux distributions. - -* `libturbojpeg`_ - blazing speed, fast JPEG image loading. Can be installed from conda-forge :code:`conda install libjpeg-turbo -c conda-forge`. - -**Notes:** ``libpng`` and ``libturbojpeg`` must be available at compilation time in order to be available. Also, most linux distributions distinguish between -``libturbojpeg`` and ``libjpeg-turbo``, where the former should be installed instead of the latter one. - -.. _libpng : http://www.libpng.org/pub/png/libpng.html -.. _libturbojpeg: https://github.com/libjpeg-turbo/libjpeg-turbo .. _Pillow : https://python-pillow.org/ .. _Pillow-SIMD : https://github.com/uploadcare/pillow-simd .. _accimage: https://github.com/pytorch/accimage C++ API ======= -TorchVision also offers a C++ API that contains C++ equivalent of python models. +TorchVision also offers a C++ API that contains C++ equivalent of python models. Installation From source: @@ -103,7 +94,7 @@ Installation From source: cd build # Add -DWITH_CUDA=on support for the CUDA if needed cmake .. - make + make make install Once installed, the library can be accessed in cmake (after properly configuring ``CMAKE_PREFIX_PATH``) via the :code:`TorchVision::TorchVision` target: diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index 30ace73d231..a075b3b3a00 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -10,30 +10,6 @@ setup_wheel_python pip_install numpy pyyaml future ninja setup_pip_pytorch_version python setup.py clean - -# Copy binaries to be included in the wheel distribution -if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then - python_exec="$(which python)" - bin_path=$(dirname $python_exec) - env_path=$(dirname $bin_path) - if [[ "$(uname)" == Darwin ]]; then - # Include LibPNG - cp "$env_path/lib/libpng16.dylib" torchvision - # Include TurboJPEG - cp "$env_path/lib/libturbojpeg.dylib" torchvision - else - # Include libPNG - cp "$bin_path/Library/lib/libpng.lib" torchvision - # Include TurboJPEG - cp "$bin_path/Library/lib/turbojpeg.lib" torchvision - fi -else - # Include LibPNG - cp "/usr/lib64/libpng.so" torchvision - # Include TurboJPEG - cp "/usr/lib64/libturbojpeg.so" torchvision -fi - if [[ "$OSTYPE" == "msys" ]]; then IS_WHEEL=1 "$script_dir/windows/internal/vc_env_helper.bat" python setup.py bdist_wheel else diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index 88fe52c0b08..b262a0f5157 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -170,13 +170,7 @@ setup_wheel_python() { conda env remove -n "env$PYTHON_VERSION" || true conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" conda activate "env$PYTHON_VERSION" - # Install libPNG from Anaconda (defaults) - conda install libpng -y - # Install libJPEG-turbo from conda-forge - conda install -y libjpeg-turbo -c conda-forge else - # Install native CentOS libPNG, libJPEG-turbo - yum install -y libpng-devel turbojpeg-devel case "$PYTHON_VERSION" in 2.7) if [[ -n "$UNICODE_ABI" ]]; then diff --git a/packaging/torchvision/conda_build_config.yaml b/packaging/torchvision/conda_build_config.yaml index adbbc732a0c..5188bb0ebec 100644 --- a/packaging/torchvision/conda_build_config.yaml +++ b/packaging/torchvision/conda_build_config.yaml @@ -1,6 +1,3 @@ -channel_sources: - - defaults,conda-forge - blas_impl: - mkl # [x86_64] c_compiler: diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index 62cfc401e3c..7d6f28cdf3c 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -8,8 +8,6 @@ source: requirements: build: - {{ compiler('c') }} # [win] - - libpng - - libjpeg-turbo host: - python @@ -20,10 +18,6 @@ requirements: run: - python - - libpng - - libjpeg-turbo - # Pillow introduces unwanted conflicts with libjpeg-turbo, as it depends on jpeg - # The fix depends on https://github.com/conda-forge/conda-forge.github.io/issues/673 - pillow >=4.1.1 - numpy >=1.11 {{ environ.get('CONDA_PYTORCH_CONSTRAINT') }} diff --git a/setup.py b/setup.py index e454c59e047..0620193b3a7 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import re import sys from setuptools import setup, find_packages -from pkg_resources import parse_version, get_distribution, DistributionNotFound +from pkg_resources import get_distribution, DistributionNotFound import subprocess import distutils.command.clean import distutils.spawn @@ -76,76 +76,7 @@ def write_version_file(): requirements.append(pillow_req + pillow_ver) -def find_library(name, vision_include): - this_dir = os.path.dirname(os.path.abspath(__file__)) - build_prefix = os.environ.get('BUILD_PREFIX', None) - is_conda_build = build_prefix is not None - - library_found = False - conda_installed = False - lib_folder = None - include_folder = None - library_header = '{0}.h'.format(name) - - print('Running build on conda-build: {0}'.format(is_conda_build)) - if is_conda_build: - # Add conda headers/libraries - if os.name == 'nt': - build_prefix = os.path.join(build_prefix, 'Library') - include_folder = os.path.join(build_prefix, 'include') - lib_folder = os.path.join(build_prefix, 'lib') - library_header_path = os.path.join( - include_folder, library_header) - library_found = os.path.isfile(library_header_path) - conda_installed = library_found - else: - # Check if using Anaconda to produce wheels - conda = distutils.spawn.find_executable('conda') - is_conda = conda is not None - print('Running build on conda: {0}'.format(is_conda)) - if is_conda: - python_executable = sys.executable - py_folder = os.path.dirname(python_executable) - if os.name == 'nt': - env_path = os.path.join(py_folder, 'Library') - else: - env_path = os.path.dirname(py_folder) - lib_folder = os.path.join(env_path, 'lib') - include_folder = os.path.join(env_path, 'include') - library_header_path = os.path.join( - include_folder, library_header) - library_found = os.path.isfile(library_header_path) - conda_installed = library_found - - # Try to locate turbojpeg in Linux standard paths - if not library_found: - if sys.platform == 'linux': - library_found = os.path.exists('/usr/include/{0}'.format( - library_header)) - library_found = library_found or os.path.exists( - '/usr/local/include/{0}'.format(library_header)) - else: - # Lookup in TORCHVISION_INCLUDE or in the package file - package_path = [os.path.join(this_dir, 'torchvision')] - for folder in vision_include + package_path: - candidate_path = os.path.join(folder, library_header) - library_found = os.path.exists(candidate_path) - if library_found: - break - - return library_found, conda_installed, include_folder, lib_folder - - def get_extensions(): - vision_include = os.environ.get('TORCHVISION_INCLUDE', None) - vision_library = os.environ.get('TORCHVISION_LIBRARY', None) - vision_include = (vision_include.split(os.pathsep) - if vision_include is not None else []) - vision_library = (vision_library.split(os.pathsep) - if vision_library is not None else []) - include_dirs = vision_include - library_dirs = vision_library - this_dir = os.path.dirname(os.path.abspath(__file__)) extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') @@ -218,14 +149,13 @@ def get_extensions(): sources = [os.path.join(extensions_dir, s) for s in sources] - include_dirs += [extensions_dir] + include_dirs = [extensions_dir] ext_modules = [ extension( 'torchvision._C', sources, include_dirs=include_dirs, - library_dirs=library_dirs, define_macros=define_macros, extra_compile_args=extra_compile_args, ) @@ -241,65 +171,6 @@ def get_extensions(): ) ) - # Image reading extension - image_macros = [] - image_include = [extensions_dir] - image_library = [] - image_link_flags = [] - - # Locating libPNG - libpng = distutils.spawn.find_executable('libpng-config') - png_found = libpng is not None - image_macros += [('PNG_FOUND', str(int(png_found)))] - print('PNG found: {0}'.format(png_found)) - if png_found: - png_version = subprocess.run([libpng, '--version'], - stdout=subprocess.PIPE) - png_version = png_version.stdout.strip().decode('utf-8') - print('libpng version: {0}'.format(png_version)) - png_version = parse_version(png_version) - if png_version >= parse_version("1.6.0"): - print('Building torchvision with PNG image support') - png_lib = subprocess.run([libpng, '--libdir'], - stdout=subprocess.PIPE) - png_include = subprocess.run([libpng, '--I_opts'], - stdout=subprocess.PIPE) - image_library += [png_lib.stdout.strip().decode('utf-8')] - image_include += [png_include.stdout.strip().decode('utf-8')] - image_link_flags.append('png' if os.name != 'nt' else 'libpng') - else: - print('libpng installed version is less than 1.6.0, ' - 'disabling PNG support') - png_found = False - - # Locating libjpegturbo - turbojpeg_info = find_library('turbojpeg', vision_include) - (turbojpeg_found, conda_installed, - turbo_include_folder, turbo_lib_folder) = turbojpeg_info - - image_macros += [('JPEG_FOUND', str(int(turbojpeg_found)))] - print('turboJPEG found: {0}'.format(turbojpeg_found)) - if turbojpeg_found: - print('Building torchvision with JPEG image support') - image_link_flags.append('turbojpeg') - if conda_installed: - image_library += [turbo_lib_folder] - image_include += [turbo_include_folder] - - image_path = os.path.join(extensions_dir, 'cpu', 'image') - image_src = glob.glob(os.path.join(image_path, '*.cpp')) - - if png_found or turbojpeg_found: - ext_modules.append(extension( - 'torchvision.image', - image_src, - include_dirs=include_dirs + [image_path] + image_include, - library_dirs=library_dirs + image_library, - define_macros=image_macros, - libraries=image_link_flags, - extra_compile_args=extra_compile_args - )) - ffmpeg_exe = distutils.spawn.find_executable('ffmpeg') has_ffmpeg = ffmpeg_exe is not None @@ -372,9 +243,7 @@ def run(self): # Package info packages=find_packages(exclude=('test',)), - package_data={ - package_name: ['*.lib', '*.dylib', '*.so'] - }, + zip_safe=False, install_requires=requirements, extras_require={ diff --git a/test/test_image.py b/test/test_image.py deleted file mode 100644 index 4ac331f399f..00000000000 --- a/test/test_image.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import unittest -import sys - -import torch -import torchvision -from PIL import Image -from torchvision.io.image import read_png, decode_png, read_jpeg, decode_jpeg -import numpy as np - -IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") -IMAGE_DIR = os.path.join(IMAGE_ROOT, "fakedata", "imagefolder") - - -def get_images(directory, img_ext): - assert os.path.isdir(directory) - for root, _, files in os.walk(directory): - for fl in files: - _, ext = os.path.splitext(fl) - if ext == img_ext: - yield os.path.join(root, fl) - - -class ImageTester(unittest.TestCase): - def test_read_jpeg(self): - for img_path in get_images(IMAGE_ROOT, "jpg"): - img_pil = torch.from_numpy(np.array(Image.open(img_path))) - img_ljpeg = read_jpeg(img_path) - - norm = img_ljpeg.shape[0] * img_ljpeg.shape[1] * img_ljpeg.shape[2] * 255 - err = torch.abs(img_ljpeg.flatten().float() - img_pil.flatten().float()).sum().float() / (norm) - self.assertLessEqual(err, 1e-2) - - def test_decode_jpeg(self): - for img_path in get_images(IMAGE_ROOT, "jpg"): - img_pil = torch.from_numpy(np.array(Image.open(img_path))) - size = os.path.getsize(img_path) - img_ljpeg = decode_jpeg(torch.from_file(img_path, dtype=torch.uint8, size=size)) - - norm = img_ljpeg.shape[0] * img_ljpeg.shape[1] * img_ljpeg.shape[2] * 255 - err = torch.abs(img_ljpeg.flatten().float() - img_pil.flatten().float()).sum().float() / (norm) - - self.assertLessEqual(err, 1e-2) - - with self.assertRaisesRegex(ValueError, "Expected a non empty 1-dimensional tensor."): - decode_jpeg(torch.empty((100, 1), dtype=torch.uint8)) - - with self.assertRaisesRegex(ValueError, "Expected a torch.uint8 tensor."): - decode_jpeg(torch.empty((100, ), dtype=torch.float16)) - - with self.assertRaisesRegex(RuntimeError, "Error while reading jpeg headers"): - decode_jpeg(torch.empty((100), dtype=torch.uint8)) - - def test_read_png(self): - for img_path in get_images(IMAGE_DIR, "png"): - img_pil = torch.from_numpy(np.array(Image.open(img_path))) - img_lpng = read_png(img_path) - self.assertEqual(img_lpng, img_pil) - - def test_decode_png(self): - for img_path in get_images(IMAGE_DIR, "png"): - img_pil = torch.from_numpy(np.array(Image.open(img_path))) - size = os.path.getsize(img_path) - img_lpng = decode_png(torch.from_file(img_path, dtype=torch.uint8, size=size)) - self.assertEqual(img_lpng, img_pil) - - self.assertEqual(decode_png(torch.empty()), torch.empty()) - self.assertEqual(decode_png(torch.randint(3, 5, (300,))), torch.empty()) - - -if __name__ == '__main__': - unittest.main() diff --git a/torchvision/csrc/cpu/image/image.cpp b/torchvision/csrc/cpu/image/image.cpp deleted file mode 100644 index 33f492ae683..00000000000 --- a/torchvision/csrc/cpu/image/image.cpp +++ /dev/null @@ -1,24 +0,0 @@ - -#include "image.h" -#include -#include - -// If we are in a Windows environment, we need to define -// initialization functions for the _custom_ops extension -#ifdef _WIN32 -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC init_image(void) { - // No need to do anything. - return NULL; -} -#else -PyMODINIT_FUNC PyInit_image(void) { - // No need to do anything. - return NULL; -} -#endif -#endif - -static auto registry = torch::RegisterOperators() - .op("image::decode_png", &decodePNG) - .op("image::decode_jpeg", &decodeJPEG); diff --git a/torchvision/csrc/cpu/image/image.h b/torchvision/csrc/cpu/image/image.h deleted file mode 100644 index 6db97d4729a..00000000000 --- a/torchvision/csrc/cpu/image/image.h +++ /dev/null @@ -1,7 +0,0 @@ - -#pragma once - -#include -#include -#include "readjpeg_cpu.h" -#include "readpng_cpu.h" diff --git a/torchvision/csrc/cpu/image/readjpeg_cpu.cpp b/torchvision/csrc/cpu/image/readjpeg_cpu.cpp deleted file mode 100644 index 1bf700a2317..00000000000 --- a/torchvision/csrc/cpu/image/readjpeg_cpu.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "readjpeg_cpu.h" - -#include -#include -#include - -#if !JPEG_FOUND - -torch::Tensor decodeJPEG(const torch::Tensor& data) { - AT_ERROR("decodeJPEG: torchvision not compiled with turboJPEG support"); -} - -#else -#include - -torch::Tensor decodeJPEG(const torch::Tensor& data) { - tjhandle tjInstance = tjInitDecompress(); - if (tjInstance == NULL) { - TORCH_CHECK(false, "libjpeg-turbo decompression initialization failed."); - } - - auto datap = data.accessor().data(); - - int width, height; - - if (tjDecompressHeader(tjInstance, datap, data.numel(), &width, &height) < - 0) { - tjDestroy(tjInstance); - TORCH_CHECK(false, "Error while reading jpeg headers"); - } - auto tensor = - torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); - - auto ptr = tensor.accessor().data(); - - int pixelFormat = TJPF_RGB; - - auto ret = tjDecompress2( - tjInstance, - datap, - data.numel(), - ptr, - width, - 0, - height, - pixelFormat, - NULL); - if (ret != 0) { - tjDestroy(tjInstance); - TORCH_CHECK(false, "decompressing JPEG image"); - } - - return tensor; -} - -#endif // JPEG_FOUND diff --git a/torchvision/csrc/cpu/image/readjpeg_cpu.h b/torchvision/csrc/cpu/image/readjpeg_cpu.h deleted file mode 100644 index 40404df29b5..00000000000 --- a/torchvision/csrc/cpu/image/readjpeg_cpu.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -torch::Tensor decodeJPEG(const torch::Tensor& data); diff --git a/torchvision/csrc/cpu/image/readpng_cpu.cpp b/torchvision/csrc/cpu/image/readpng_cpu.cpp deleted file mode 100644 index b7fb28e2575..00000000000 --- a/torchvision/csrc/cpu/image/readpng_cpu.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "readpng_cpu.h" - -#include -#include -#include - -#if !PNG_FOUND -torch::Tensor decodePNG(const torch::Tensor& data) { - AT_ERROR("decodePNG: torchvision not compiled with libPNG support"); -} -#else -#include - -torch::Tensor decodePNG(const torch::Tensor& data) { - auto png_ptr = - png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - TORCH_CHECK(png_ptr, "libpng read structure allocation failed!") - auto info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_read_struct(&png_ptr, nullptr, nullptr); - // Seems redundant with the if statement. done here to avoid leaking memory. - TORCH_CHECK(info_ptr, "libpng info structure allocation failed!") - } - - auto datap = data.accessor().data(); - - if (setjmp(png_jmpbuf(png_ptr)) != 0) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - TORCH_CHECK(false, "Internal error."); - } - auto is_png = !png_sig_cmp(datap, 0, 8); - TORCH_CHECK(is_png, "Content is not png!") - - struct Reader { - png_const_bytep ptr; - } reader; - reader.ptr = png_const_bytep(datap) + 8; - - auto read_callback = - [](png_structp png_ptr, png_bytep output, png_size_t bytes) { - auto reader = static_cast(png_get_io_ptr(png_ptr)); - std::copy(reader->ptr, reader->ptr + bytes, output); - reader->ptr += bytes; - }; - png_set_sig_bytes(png_ptr, 8); - png_set_read_fn(png_ptr, &reader, read_callback); - png_read_info(png_ptr, info_ptr); - - png_uint_32 width, height; - int bit_depth, color_type; - auto retval = png_get_IHDR( - png_ptr, - info_ptr, - &width, - &height, - &bit_depth, - &color_type, - nullptr, - nullptr, - nullptr); - - if (retval != 1) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - TORCH_CHECK(retval == 1, "Could read image metadata from content.") - } - if (color_type != PNG_COLOR_TYPE_RGB) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - TORCH_CHECK( - color_type == PNG_COLOR_TYPE_RGB, "Non RGB images are not supported.") - } - - auto tensor = - torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); - auto ptr = tensor.accessor().data(); - auto bytes = png_get_rowbytes(png_ptr, info_ptr); - for (decltype(height) i = 0; i < height; ++i) { - png_read_row(png_ptr, ptr, nullptr); - ptr += bytes; - } - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - return tensor; -} -#endif // PNG_FOUND diff --git a/torchvision/csrc/cpu/image/readpng_cpu.h b/torchvision/csrc/cpu/image/readpng_cpu.h deleted file mode 100644 index d2151a43aa9..00000000000 --- a/torchvision/csrc/cpu/image/readpng_cpu.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include - -torch::Tensor decodePNG(const torch::Tensor& data); diff --git a/torchvision/io/__init__.py b/torchvision/io/__init__.py index 4c47d8a51d5..cbbf560412e 100644 --- a/torchvision/io/__init__.py +++ b/torchvision/io/__init__.py @@ -30,5 +30,5 @@ "_read_video_clip_from_memory", "_read_video_meta_data", "VideoMetaData", - "Timebase" + "Timebase", ] diff --git a/torchvision/io/image.py b/torchvision/io/image.py deleted file mode 100644 index 60d44380356..00000000000 --- a/torchvision/io/image.py +++ /dev/null @@ -1,109 +0,0 @@ -import torch -from torch import nn, Tensor - -import os -import os.path as osp -import importlib - -_HAS_IMAGE_OPT = False - -try: - lib_dir = osp.join(osp.dirname(__file__), "..") - - loader_details = ( - importlib.machinery.ExtensionFileLoader, - importlib.machinery.EXTENSION_SUFFIXES - ) - - extfinder = importlib.machinery.FileFinder(lib_dir, loader_details) - ext_specs = extfinder.find_spec("image") - if ext_specs is not None: - torch.ops.load_library(ext_specs.origin) - _HAS_IMAGE_OPT = True -except (ImportError, OSError): - pass - - -def decode_png(input): - # type: (Tensor) -> Tensor - """ - Decodes a PNG image into a 3 dimensional RGB Tensor. - The values of the output tensor are uint8 between 0 and 255. - - Arguments: - input (Tensor[1]): a one dimensional int8 tensor containing - the raw bytes of the PNG image. - - Returns: - output (Tensor[image_width, image_height, 3]) - """ - if not isinstance(input, torch.Tensor) or len(input) == 0: - raise ValueError("Expected a non empty 1-dimensional tensor.") - - if not input.dtype == torch.uint8: - raise ValueError("Expected a torch.uint8 tensor.") - output = torch.ops.image.decode_png(input) - return output - - -def read_png(path): - # type: (str) -> Tensor - """ - Reads a PNG image into a 3 dimensional RGB Tensor. - The values of the output tensor are uint8 between 0 and 255. - - Arguments: - path (str): path of the PNG image. - - Returns: - output (Tensor[image_width, image_height, 3]) - """ - if not os.path.isfile(path): - raise ValueError("Expected a valid file path.") - - size = os.path.getsize(path) - if size == 0: - raise ValueError("Expected a non empty file.") - data = torch.from_file(path, dtype=torch.uint8, size=size) - return decode_png(data) - - -def decode_jpeg(input): - # type: (Tensor) -> Tensor - """ - Decodes a JPEG image into a 3 dimensional RGB Tensor. - The values of the output tensor are uint8 between 0 and 255. - Arguments: - input (Tensor[1]): a one dimensional int8 tensor containing - the raw bytes of the JPEG image. - Returns: - output (Tensor[image_width, image_height, 3]) - """ - if not isinstance(input, torch.Tensor) or len(input) == 0 or input.ndim != 1: - raise ValueError("Expected a non empty 1-dimensional tensor.") - - if not input.dtype == torch.uint8: - raise ValueError("Expected a torch.uint8 tensor.") - - output = torch.ops.image.decode_jpeg(input) - return output - - -def read_jpeg(path): - # type: (str) -> Tensor - """ - Reads a JPEG image into a 3 dimensional RGB Tensor. - The values of the output tensor are uint8 between 0 and 255. - Arguments: - path (str): path of the JPEG image. - Returns: - output (Tensor[image_width, image_height, 3]) - """ - if not os.path.isfile(path): - raise ValueError("Expected a valid file path.") - - size = os.path.getsize(path) - if size == 0: - raise ValueError("Expected a non empty file.") - data = torch.from_file(path, dtype=torch.uint8, size=size) - return decode_jpeg(data) From c1a99b7b53fed35dacfcc165be7dd09675d402b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Thu, 2 Jul 2020 04:31:50 -0500 Subject: [PATCH 23/51] PR: Enable libPNG support (#2379) * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Add PNG reading capabilities * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Add PNG reading capabilities * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Install libpng on conda-based wheel distributions * Add -y flag * Add -y flag to yum * Locate LibPNG on windows conda * Remove empty else * Copy libpng16.so * Copy dylib on Mac * Improve check on Windows * Try to install ninja using conda on windows * Use libpng on Windows * Package lib on windows wheel * Point library to the correct place * Include binaries as part of wheel * Copy libpng.so on linux * Look for png.h on Windows when using conda-build * Do not skip png tests on Mac/Win * Restore libjpeg-turbo * Install jpeg-turbo on wheel distributions * Install libjpeg-turbo from conda-forge on wheel distributions * Do not pull av on conda-build * Add pillow disclaimer * Vendors libjpeg-turbo 2.0.4 * Merge JPEG work * Remove submodules * Regenerate circle config * Fix style issues * Fix C++ style issues * More style corrections * Add JPEG-turbo to linking libraries * More style corrections * More style corrections * More style corrections * Install libjpeg-turbo-devel * Install libturbo-jpeg on typing pipeline * Update Circle template * Windows and Unix turbojpeg have the same linking name * Install turbojpeg-devel instead of libjpeg-turbo * Copy TurboJPEG binaries to wheel * Move test image * Move back test image * Update JPEG test path * Remove dot from extension * Move image functions to extension * Use stdout arg in subprocess * Disable image extension if libpng or turbojpeg are not found * Append libpng stdout * Prevent list appending on lists * Minor path correction * Minor error correction * Add linking flags * Style issues correction * Address minor review corrections * Refactor library search * Restore access index * Fix JPEG tests * Update libpng version in Travis * Add -y flag * Remove dot * Update libpng using apt * Check libpng version * Change libturbojpeg binary * Update import * Change call * Restore av in conda recipe * Minor error correction * Remove unused comment in travis.yml * Update README * Fix missing links * Remove fixes for 16.04 * Remove JPEG-related code * Remove installation references to turbojpeg * Remove further references to turbojpeg * Fix c++ style issues * Fix c++ style issues * Fix libpng-config include flag parsing * Remove conda-forge * Remove include dirs from main extension * Do not pass extra include and library paths to main torchvision extension * Add libpng to environment.yml * Remove inexistent imports * Add instructions regarding environment variables to README Co-authored-by: Ryad ZENINE --- .circleci/config.yml | 1 + .circleci/config.yml.in | 1 + .../unittest/linux/scripts/environment.yml | 1 + .gitignore | 2 + .travis.yml | 1 + CMakeLists.txt | 16 ++- README.rst | 10 +- packaging/build_wheel.sh | 18 +++ packaging/pkg_helpers.bash | 4 + packaging/torchvision/conda_build_config.yaml | 3 + packaging/torchvision/meta.yaml | 2 + setup.py | 122 +++++++++++++++++- test/test_image.py | 43 ++++++ torchvision/csrc/cpu/image/image.cpp | 23 ++++ torchvision/csrc/cpu/image/image.h | 6 + torchvision/csrc/cpu/image/readpng_cpu.cpp | 83 ++++++++++++ torchvision/csrc/cpu/image/readpng_cpu.h | 6 + torchvision/io/__init__.py | 2 +- torchvision/io/image.py | 68 ++++++++++ 19 files changed, 402 insertions(+), 10 deletions(-) create mode 100644 test/test_image.py create mode 100644 torchvision/csrc/cpu/image/image.cpp create mode 100644 torchvision/csrc/cpu/image/image.h create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.cpp create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.h create mode 100644 torchvision/io/image.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 29b5fc77aab..e1fc2d392c8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,6 +107,7 @@ jobs: - checkout - run: command: | + sudo apt-get update -y pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index d9bd257eae6..62ef94a1169 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -107,6 +107,7 @@ jobs: - checkout - run: command: | + sudo apt-get update -y pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/unittest/linux/scripts/environment.yml b/.circleci/unittest/linux/scripts/environment.yml index d664c7c0f7f..2b3604ee1c8 100644 --- a/.circleci/unittest/linux/scripts/environment.yml +++ b/.circleci/unittest/linux/scripts/environment.yml @@ -6,6 +6,7 @@ dependencies: - pytest-cov - codecov - pip + - libpng - ca-certificates - pip: - future diff --git a/.gitignore b/.gitignore index 6bea8609b93..6d649a7c019 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ htmlcov *.swo gen.yml .mypy_cache +.vscode/ +*.orig diff --git a/.travis.yml b/.travis.yml index ec25bdb8677..d8c45c2defe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ jobs: before_install: - sudo apt-get update + - sudo apt-get install -y libpng-dev - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" diff --git a/CMakeLists.txt b/CMakeLists.txt index fa50f155ce4..0d7e7aaa9d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,22 +11,28 @@ if(WITH_CUDA) endif() find_package(Python3 COMPONENTS Development) + find_package(Torch REQUIRED) +find_package(PNG REQUIRED) + file(GLOB HEADERS torchvision/csrc/*.h) -file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp torchvision/csrc/*.cpp) +file(GLOB IMAGE_HEADERS torchvision/csrc/cpu/image/*.h) +file(GLOB IMAGE_SOURCES torchvision/csrc/cpu/image/*.cpp) +file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp ${IMAGE_HEADERS} ${IMAGE_SOURCES} ${HEADERS} torchvision/csrc/*.cpp) if(WITH_CUDA) file(GLOB OPERATOR_SOURCES ${OPERATOR_SOURCES} torchvision/csrc/cuda/*.h torchvision/csrc/cuda/*.cu) endif() file(GLOB MODELS_HEADERS torchvision/csrc/models/*.h) file(GLOB MODELS_SOURCES torchvision/csrc/models/*.h torchvision/csrc/models/*.cpp) -add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python) +add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES} ${IMAGE_SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} ${PNG_LIBRARY} Python3::Python) +# target_link_libraries(${PROJECT_NAME} PRIVATE ${PNG_LIBRARY} Python3::Python) set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME TorchVision) target_include_directories(${PROJECT_NAME} INTERFACE - $ + $ $) include(GNUInstallDirs) @@ -61,7 +67,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cpu) if(WITH_CUDA) install(FILES - torchvision/csrc/cuda/vision_cuda.h + torchvision/csrc/cuda/vision_cuda.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cuda) endif() install(FILES ${MODELS_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/models) diff --git a/README.rst b/README.rst index 3150a3023ad..0de404c74b3 100644 --- a/README.rst +++ b/README.rst @@ -78,13 +78,19 @@ Torchvision currently supports the following image backends: * `accimage`_ - if installed can be activated by calling :code:`torchvision.set_image_backend('accimage')` +* `libpng`_ - can be installed via conda :code:`conda install libpng` or any of the package managers for debian-based and RHEL-based Linux distributions. + +**Notes:** ``libpng`` must be available at compilation time in order to be available. Make sure that it is available on the standard library locations, +otherwise, add the include and library paths in the environment variables ``TORCHVISION_INCLUDE`` and ``TORCHVISION_LIBRARY``, respectively. + +.. _libpng : http://www.libpng.org/pub/png/libpng.html .. _Pillow : https://python-pillow.org/ .. _Pillow-SIMD : https://github.com/uploadcare/pillow-simd .. _accimage: https://github.com/pytorch/accimage C++ API ======= -TorchVision also offers a C++ API that contains C++ equivalent of python models. +TorchVision also offers a C++ API that contains C++ equivalent of python models. Installation From source: @@ -94,7 +100,7 @@ Installation From source: cd build # Add -DWITH_CUDA=on support for the CUDA if needed cmake .. - make + make make install Once installed, the library can be accessed in cmake (after properly configuring ``CMAKE_PREFIX_PATH``) via the :code:`TorchVision::TorchVision` target: diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index a075b3b3a00..fae1da2649b 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -10,6 +10,24 @@ setup_wheel_python pip_install numpy pyyaml future ninja setup_pip_pytorch_version python setup.py clean + +# Copy binaries to be included in the wheel distribution +if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then + python_exec="$(which python)" + bin_path=$(dirname $python_exec) + env_path=$(dirname $bin_path) + if [[ "$(uname)" == Darwin ]]; then + # Include LibPNG + cp "$env_path/lib/libpng16.dylib" torchvision + else + # Include libPNG + cp "$bin_path/Library/lib/libpng.lib" torchvision + fi +else + # Include LibPNG + cp "/usr/lib64/libpng.so" torchvision +fi + if [[ "$OSTYPE" == "msys" ]]; then IS_WHEEL=1 "$script_dir/windows/internal/vc_env_helper.bat" python setup.py bdist_wheel else diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index b262a0f5157..16f1a26c300 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -170,7 +170,11 @@ setup_wheel_python() { conda env remove -n "env$PYTHON_VERSION" || true conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" conda activate "env$PYTHON_VERSION" + # Install libPNG from Anaconda (defaults) + conda install libpng -y else + # Install native CentOS libPNG + yum install -y libpng-devel case "$PYTHON_VERSION" in 2.7) if [[ -n "$UNICODE_ABI" ]]; then diff --git a/packaging/torchvision/conda_build_config.yaml b/packaging/torchvision/conda_build_config.yaml index 5188bb0ebec..f745e4f0e5a 100644 --- a/packaging/torchvision/conda_build_config.yaml +++ b/packaging/torchvision/conda_build_config.yaml @@ -1,3 +1,6 @@ +channel_sources: + - defaults + blas_impl: - mkl # [x86_64] c_compiler: diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index 7d6f28cdf3c..b3fc2a2e9df 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -8,6 +8,7 @@ source: requirements: build: - {{ compiler('c') }} # [win] + - libpng host: - python @@ -18,6 +19,7 @@ requirements: run: - python + - libpng - pillow >=4.1.1 - numpy >=1.11 {{ environ.get('CONDA_PYTORCH_CONSTRAINT') }} diff --git a/setup.py b/setup.py index 0620193b3a7..5ba9c4e90ae 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import re import sys from setuptools import setup, find_packages -from pkg_resources import get_distribution, DistributionNotFound +from pkg_resources import parse_version, get_distribution, DistributionNotFound import subprocess import distutils.command.clean import distutils.spawn @@ -76,6 +76,65 @@ def write_version_file(): requirements.append(pillow_req + pillow_ver) +def find_library(name, vision_include): + this_dir = os.path.dirname(os.path.abspath(__file__)) + build_prefix = os.environ.get('BUILD_PREFIX', None) + is_conda_build = build_prefix is not None + + library_found = False + conda_installed = False + lib_folder = None + include_folder = None + library_header = '{0}.h'.format(name) + + print('Running build on conda-build: {0}'.format(is_conda_build)) + if is_conda_build: + # Add conda headers/libraries + if os.name == 'nt': + build_prefix = os.path.join(build_prefix, 'Library') + include_folder = os.path.join(build_prefix, 'include') + lib_folder = os.path.join(build_prefix, 'lib') + library_header_path = os.path.join( + include_folder, library_header) + library_found = os.path.isfile(library_header_path) + conda_installed = library_found + else: + # Check if using Anaconda to produce wheels + conda = distutils.spawn.find_executable('conda') + is_conda = conda is not None + print('Running build on conda: {0}'.format(is_conda)) + if is_conda: + python_executable = sys.executable + py_folder = os.path.dirname(python_executable) + if os.name == 'nt': + env_path = os.path.join(py_folder, 'Library') + else: + env_path = os.path.dirname(py_folder) + lib_folder = os.path.join(env_path, 'lib') + include_folder = os.path.join(env_path, 'include') + library_header_path = os.path.join( + include_folder, library_header) + library_found = os.path.isfile(library_header_path) + conda_installed = library_found + + if not library_found: + if sys.platform == 'linux': + library_found = os.path.exists('/usr/include/{0}'.format( + library_header)) + library_found = library_found or os.path.exists( + '/usr/local/include/{0}'.format(library_header)) + else: + # Lookup in TORCHVISION_INCLUDE or in the package file + package_path = [os.path.join(this_dir, 'torchvision')] + for folder in vision_include + package_path: + candidate_path = os.path.join(folder, library_header) + library_found = os.path.exists(candidate_path) + if library_found: + break + + return library_found, conda_installed, include_folder, lib_folder + + def get_extensions(): this_dir = os.path.dirname(os.path.abspath(__file__)) extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') @@ -171,6 +230,63 @@ def get_extensions(): ) ) + # ------------------- Torchvision extra extensions ------------------------ + vision_include = os.environ.get('TORCHVISION_INCLUDE', None) + vision_library = os.environ.get('TORCHVISION_LIBRARY', None) + vision_include = (vision_include.split(os.pathsep) + if vision_include is not None else []) + vision_library = (vision_library.split(os.pathsep) + if vision_library is not None else []) + include_dirs += vision_include + library_dirs = vision_library + + # Image reading extension + image_macros = [] + image_include = [extensions_dir] + image_library = [] + image_link_flags = [] + + # Locating libPNG + libpng = distutils.spawn.find_executable('libpng-config') + png_found = libpng is not None + image_macros += [('PNG_FOUND', str(int(png_found)))] + print('PNG found: {0}'.format(png_found)) + if png_found: + png_version = subprocess.run([libpng, '--version'], + stdout=subprocess.PIPE) + png_version = png_version.stdout.strip().decode('utf-8') + print('libpng version: {0}'.format(png_version)) + png_version = parse_version(png_version) + if png_version >= parse_version("1.6.0"): + print('Building torchvision with PNG image support') + png_lib = subprocess.run([libpng, '--libdir'], + stdout=subprocess.PIPE) + png_include = subprocess.run([libpng, '--I_opts'], + stdout=subprocess.PIPE) + png_include = png_include.stdout.strip().decode('utf-8') + _, png_include = png_include.split('-I') + image_library += [png_lib.stdout.strip().decode('utf-8')] + image_include += [png_include] + image_link_flags.append('png' if os.name != 'nt' else 'libpng') + else: + print('libpng installed version is less than 1.6.0, ' + 'disabling PNG support') + png_found = False + + image_path = os.path.join(extensions_dir, 'cpu', 'image') + image_src = glob.glob(os.path.join(image_path, '*.cpp')) + + if png_found: + ext_modules.append(extension( + 'torchvision.image', + image_src, + include_dirs=image_include + include_dirs + [image_path], + library_dirs=image_library + library_dirs, + define_macros=image_macros, + libraries=image_link_flags, + extra_compile_args=extra_compile_args + )) + ffmpeg_exe = distutils.spawn.find_executable('ffmpeg') has_ffmpeg = ffmpeg_exe is not None @@ -243,7 +359,9 @@ def run(self): # Package info packages=find_packages(exclude=('test',)), - + package_data={ + package_name: ['*.lib', '*.dylib', '*.so'] + }, zip_safe=False, install_requires=requirements, extras_require={ diff --git a/test/test_image.py b/test/test_image.py new file mode 100644 index 00000000000..f40dacd4be4 --- /dev/null +++ b/test/test_image.py @@ -0,0 +1,43 @@ +import os +import unittest +import sys + +import torch +import torchvision +from PIL import Image +from torchvision.io.image import read_png, decode_png +import numpy as np + +IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") +IMAGE_DIR = os.path.join(IMAGE_ROOT, "fakedata", "imagefolder") + + +def get_images(directory, img_ext): + assert os.path.isdir(directory) + for root, _, files in os.walk(directory): + for fl in files: + _, ext = os.path.splitext(fl) + if ext == img_ext: + yield os.path.join(root, fl) + + +class ImageTester(unittest.TestCase): + def test_read_png(self): + for img_path in get_images(IMAGE_DIR, "png"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + img_lpng = read_png(img_path) + self.assertEqual(img_lpng, img_pil) + + def test_decode_png(self): + for img_path in get_images(IMAGE_DIR, "png"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + size = os.path.getsize(img_path) + img_lpng = decode_png(torch.from_file(img_path, dtype=torch.uint8, size=size)) + self.assertEqual(img_lpng, img_pil) + + self.assertEqual(decode_png(torch.empty()), torch.empty()) + self.assertEqual(decode_png(torch.randint(3, 5, (300,))), torch.empty()) + + +if __name__ == '__main__': + unittest.main() diff --git a/torchvision/csrc/cpu/image/image.cpp b/torchvision/csrc/cpu/image/image.cpp new file mode 100644 index 00000000000..ca9082db3c0 --- /dev/null +++ b/torchvision/csrc/cpu/image/image.cpp @@ -0,0 +1,23 @@ + +#include "image.h" +#include +#include + +// If we are in a Windows environment, we need to define +// initialization functions for the _custom_ops extension +#ifdef _WIN32 +#if PY_MAJOR_VERSION < 3 +PyMODINIT_FUNC init_image(void) { + // No need to do anything. + return NULL; +} +#else +PyMODINIT_FUNC PyInit_image(void) { + // No need to do anything. + return NULL; +} +#endif +#endif + +static auto registry = + torch::RegisterOperators().op("image::decode_png", &decodePNG); diff --git a/torchvision/csrc/cpu/image/image.h b/torchvision/csrc/cpu/image/image.h new file mode 100644 index 00000000000..3f1c9561b40 --- /dev/null +++ b/torchvision/csrc/cpu/image/image.h @@ -0,0 +1,6 @@ + +#pragma once + +#include +#include +#include "readpng_cpu.h" diff --git a/torchvision/csrc/cpu/image/readpng_cpu.cpp b/torchvision/csrc/cpu/image/readpng_cpu.cpp new file mode 100644 index 00000000000..b7fb28e2575 --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.cpp @@ -0,0 +1,83 @@ +#include "readpng_cpu.h" + +#include +#include +#include + +#if !PNG_FOUND +torch::Tensor decodePNG(const torch::Tensor& data) { + AT_ERROR("decodePNG: torchvision not compiled with libPNG support"); +} +#else +#include + +torch::Tensor decodePNG(const torch::Tensor& data) { + auto png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + TORCH_CHECK(png_ptr, "libpng read structure allocation failed!") + auto info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + // Seems redundant with the if statement. done here to avoid leaking memory. + TORCH_CHECK(info_ptr, "libpng info structure allocation failed!") + } + + auto datap = data.accessor().data(); + + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(false, "Internal error."); + } + auto is_png = !png_sig_cmp(datap, 0, 8); + TORCH_CHECK(is_png, "Content is not png!") + + struct Reader { + png_const_bytep ptr; + } reader; + reader.ptr = png_const_bytep(datap) + 8; + + auto read_callback = + [](png_structp png_ptr, png_bytep output, png_size_t bytes) { + auto reader = static_cast(png_get_io_ptr(png_ptr)); + std::copy(reader->ptr, reader->ptr + bytes, output); + reader->ptr += bytes; + }; + png_set_sig_bytes(png_ptr, 8); + png_set_read_fn(png_ptr, &reader, read_callback); + png_read_info(png_ptr, info_ptr); + + png_uint_32 width, height; + int bit_depth, color_type; + auto retval = png_get_IHDR( + png_ptr, + info_ptr, + &width, + &height, + &bit_depth, + &color_type, + nullptr, + nullptr, + nullptr); + + if (retval != 1) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(retval == 1, "Could read image metadata from content.") + } + if (color_type != PNG_COLOR_TYPE_RGB) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK( + color_type == PNG_COLOR_TYPE_RGB, "Non RGB images are not supported.") + } + + auto tensor = + torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); + auto ptr = tensor.accessor().data(); + auto bytes = png_get_rowbytes(png_ptr, info_ptr); + for (decltype(height) i = 0; i < height; ++i) { + png_read_row(png_ptr, ptr, nullptr); + ptr += bytes; + } + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return tensor; +} +#endif // PNG_FOUND diff --git a/torchvision/csrc/cpu/image/readpng_cpu.h b/torchvision/csrc/cpu/image/readpng_cpu.h new file mode 100644 index 00000000000..d2151a43aa9 --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +torch::Tensor decodePNG(const torch::Tensor& data); diff --git a/torchvision/io/__init__.py b/torchvision/io/__init__.py index cbbf560412e..4c47d8a51d5 100644 --- a/torchvision/io/__init__.py +++ b/torchvision/io/__init__.py @@ -30,5 +30,5 @@ "_read_video_clip_from_memory", "_read_video_meta_data", "VideoMetaData", - "Timebase", + "Timebase" ] diff --git a/torchvision/io/image.py b/torchvision/io/image.py new file mode 100644 index 00000000000..ea12f13e3bd --- /dev/null +++ b/torchvision/io/image.py @@ -0,0 +1,68 @@ +import torch +from torch import nn, Tensor + +import os +import os.path as osp +import importlib + +_HAS_IMAGE_OPT = False + +try: + lib_dir = osp.join(osp.dirname(__file__), "..") + + loader_details = ( + importlib.machinery.ExtensionFileLoader, + importlib.machinery.EXTENSION_SUFFIXES + ) + + extfinder = importlib.machinery.FileFinder(lib_dir, loader_details) + ext_specs = extfinder.find_spec("image") + if ext_specs is not None: + torch.ops.load_library(ext_specs.origin) + _HAS_IMAGE_OPT = True +except (ImportError, OSError): + pass + + +def decode_png(input): + # type: (Tensor) -> Tensor + """ + Decodes a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + input (Tensor[1]): a one dimensional int8 tensor containing + the raw bytes of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not isinstance(input, torch.Tensor) or len(input) == 0: + raise ValueError("Expected a non empty 1-dimensional tensor.") + + if not input.dtype == torch.uint8: + raise ValueError("Expected a torch.uint8 tensor.") + output = torch.ops.image.decode_png(input) + return output + + +def read_png(path): + # type: (str) -> Tensor + """ + Reads a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + path (str): path of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not os.path.isfile(path): + raise ValueError("Expected a valid file path.") + + size = os.path.getsize(path) + if size == 0: + raise ValueError("Expected a non empty file.") + data = torch.from_file(path, dtype=torch.uint8, size=size) + return decode_png(data) From e4b9823f4ad2d752c38d6df4f4b901f97cfafd1a Mon Sep 17 00:00:00 2001 From: Francisco Massa Date: Thu, 2 Jul 2020 13:11:07 +0200 Subject: [PATCH 24/51] Revert "PR: Enable libPNG support (#2379)" (#2383) This reverts commit c1a99b7b53fed35dacfcc165be7dd09675d402b2. --- .circleci/config.yml | 1 - .circleci/config.yml.in | 1 - .../unittest/linux/scripts/environment.yml | 1 - .gitignore | 2 - .travis.yml | 1 - CMakeLists.txt | 16 +-- README.rst | 10 +- packaging/build_wheel.sh | 18 --- packaging/pkg_helpers.bash | 4 - packaging/torchvision/conda_build_config.yaml | 3 - packaging/torchvision/meta.yaml | 2 - setup.py | 122 +----------------- test/test_image.py | 43 ------ torchvision/csrc/cpu/image/image.cpp | 23 ---- torchvision/csrc/cpu/image/image.h | 6 - torchvision/csrc/cpu/image/readpng_cpu.cpp | 83 ------------ torchvision/csrc/cpu/image/readpng_cpu.h | 6 - torchvision/io/__init__.py | 2 +- torchvision/io/image.py | 68 ---------- 19 files changed, 10 insertions(+), 402 deletions(-) delete mode 100644 test/test_image.py delete mode 100644 torchvision/csrc/cpu/image/image.cpp delete mode 100644 torchvision/csrc/cpu/image/image.h delete mode 100644 torchvision/csrc/cpu/image/readpng_cpu.cpp delete mode 100644 torchvision/csrc/cpu/image/readpng_cpu.h delete mode 100644 torchvision/io/image.py diff --git a/.circleci/config.yml b/.circleci/config.yml index e1fc2d392c8..29b5fc77aab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,7 +107,6 @@ jobs: - checkout - run: command: | - sudo apt-get update -y pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index 62ef94a1169..d9bd257eae6 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -107,7 +107,6 @@ jobs: - checkout - run: command: | - sudo apt-get update -y pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/unittest/linux/scripts/environment.yml b/.circleci/unittest/linux/scripts/environment.yml index 2b3604ee1c8..d664c7c0f7f 100644 --- a/.circleci/unittest/linux/scripts/environment.yml +++ b/.circleci/unittest/linux/scripts/environment.yml @@ -6,7 +6,6 @@ dependencies: - pytest-cov - codecov - pip - - libpng - ca-certificates - pip: - future diff --git a/.gitignore b/.gitignore index 6d649a7c019..6bea8609b93 100644 --- a/.gitignore +++ b/.gitignore @@ -21,5 +21,3 @@ htmlcov *.swo gen.yml .mypy_cache -.vscode/ -*.orig diff --git a/.travis.yml b/.travis.yml index d8c45c2defe..ec25bdb8677 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ jobs: before_install: - sudo apt-get update - - sudo apt-get install -y libpng-dev - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d7e7aaa9d8..fa50f155ce4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,28 +11,22 @@ if(WITH_CUDA) endif() find_package(Python3 COMPONENTS Development) - find_package(Torch REQUIRED) -find_package(PNG REQUIRED) - file(GLOB HEADERS torchvision/csrc/*.h) -file(GLOB IMAGE_HEADERS torchvision/csrc/cpu/image/*.h) -file(GLOB IMAGE_SOURCES torchvision/csrc/cpu/image/*.cpp) -file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp ${IMAGE_HEADERS} ${IMAGE_SOURCES} ${HEADERS} torchvision/csrc/*.cpp) +file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp torchvision/csrc/*.cpp) if(WITH_CUDA) file(GLOB OPERATOR_SOURCES ${OPERATOR_SOURCES} torchvision/csrc/cuda/*.h torchvision/csrc/cuda/*.cu) endif() file(GLOB MODELS_HEADERS torchvision/csrc/models/*.h) file(GLOB MODELS_SOURCES torchvision/csrc/models/*.h torchvision/csrc/models/*.cpp) -add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES} ${IMAGE_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} ${PNG_LIBRARY} Python3::Python) -# target_link_libraries(${PROJECT_NAME} PRIVATE ${PNG_LIBRARY} Python3::Python) +add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python) set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME TorchVision) target_include_directories(${PROJECT_NAME} INTERFACE - $ + $ $) include(GNUInstallDirs) @@ -67,7 +61,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cpu) if(WITH_CUDA) install(FILES - torchvision/csrc/cuda/vision_cuda.h + torchvision/csrc/cuda/vision_cuda.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cuda) endif() install(FILES ${MODELS_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/models) diff --git a/README.rst b/README.rst index 0de404c74b3..3150a3023ad 100644 --- a/README.rst +++ b/README.rst @@ -78,19 +78,13 @@ Torchvision currently supports the following image backends: * `accimage`_ - if installed can be activated by calling :code:`torchvision.set_image_backend('accimage')` -* `libpng`_ - can be installed via conda :code:`conda install libpng` or any of the package managers for debian-based and RHEL-based Linux distributions. - -**Notes:** ``libpng`` must be available at compilation time in order to be available. Make sure that it is available on the standard library locations, -otherwise, add the include and library paths in the environment variables ``TORCHVISION_INCLUDE`` and ``TORCHVISION_LIBRARY``, respectively. - -.. _libpng : http://www.libpng.org/pub/png/libpng.html .. _Pillow : https://python-pillow.org/ .. _Pillow-SIMD : https://github.com/uploadcare/pillow-simd .. _accimage: https://github.com/pytorch/accimage C++ API ======= -TorchVision also offers a C++ API that contains C++ equivalent of python models. +TorchVision also offers a C++ API that contains C++ equivalent of python models. Installation From source: @@ -100,7 +94,7 @@ Installation From source: cd build # Add -DWITH_CUDA=on support for the CUDA if needed cmake .. - make + make make install Once installed, the library can be accessed in cmake (after properly configuring ``CMAKE_PREFIX_PATH``) via the :code:`TorchVision::TorchVision` target: diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index fae1da2649b..a075b3b3a00 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -10,24 +10,6 @@ setup_wheel_python pip_install numpy pyyaml future ninja setup_pip_pytorch_version python setup.py clean - -# Copy binaries to be included in the wheel distribution -if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then - python_exec="$(which python)" - bin_path=$(dirname $python_exec) - env_path=$(dirname $bin_path) - if [[ "$(uname)" == Darwin ]]; then - # Include LibPNG - cp "$env_path/lib/libpng16.dylib" torchvision - else - # Include libPNG - cp "$bin_path/Library/lib/libpng.lib" torchvision - fi -else - # Include LibPNG - cp "/usr/lib64/libpng.so" torchvision -fi - if [[ "$OSTYPE" == "msys" ]]; then IS_WHEEL=1 "$script_dir/windows/internal/vc_env_helper.bat" python setup.py bdist_wheel else diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index 16f1a26c300..b262a0f5157 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -170,11 +170,7 @@ setup_wheel_python() { conda env remove -n "env$PYTHON_VERSION" || true conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" conda activate "env$PYTHON_VERSION" - # Install libPNG from Anaconda (defaults) - conda install libpng -y else - # Install native CentOS libPNG - yum install -y libpng-devel case "$PYTHON_VERSION" in 2.7) if [[ -n "$UNICODE_ABI" ]]; then diff --git a/packaging/torchvision/conda_build_config.yaml b/packaging/torchvision/conda_build_config.yaml index f745e4f0e5a..5188bb0ebec 100644 --- a/packaging/torchvision/conda_build_config.yaml +++ b/packaging/torchvision/conda_build_config.yaml @@ -1,6 +1,3 @@ -channel_sources: - - defaults - blas_impl: - mkl # [x86_64] c_compiler: diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index b3fc2a2e9df..7d6f28cdf3c 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -8,7 +8,6 @@ source: requirements: build: - {{ compiler('c') }} # [win] - - libpng host: - python @@ -19,7 +18,6 @@ requirements: run: - python - - libpng - pillow >=4.1.1 - numpy >=1.11 {{ environ.get('CONDA_PYTORCH_CONSTRAINT') }} diff --git a/setup.py b/setup.py index 5ba9c4e90ae..0620193b3a7 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import re import sys from setuptools import setup, find_packages -from pkg_resources import parse_version, get_distribution, DistributionNotFound +from pkg_resources import get_distribution, DistributionNotFound import subprocess import distutils.command.clean import distutils.spawn @@ -76,65 +76,6 @@ def write_version_file(): requirements.append(pillow_req + pillow_ver) -def find_library(name, vision_include): - this_dir = os.path.dirname(os.path.abspath(__file__)) - build_prefix = os.environ.get('BUILD_PREFIX', None) - is_conda_build = build_prefix is not None - - library_found = False - conda_installed = False - lib_folder = None - include_folder = None - library_header = '{0}.h'.format(name) - - print('Running build on conda-build: {0}'.format(is_conda_build)) - if is_conda_build: - # Add conda headers/libraries - if os.name == 'nt': - build_prefix = os.path.join(build_prefix, 'Library') - include_folder = os.path.join(build_prefix, 'include') - lib_folder = os.path.join(build_prefix, 'lib') - library_header_path = os.path.join( - include_folder, library_header) - library_found = os.path.isfile(library_header_path) - conda_installed = library_found - else: - # Check if using Anaconda to produce wheels - conda = distutils.spawn.find_executable('conda') - is_conda = conda is not None - print('Running build on conda: {0}'.format(is_conda)) - if is_conda: - python_executable = sys.executable - py_folder = os.path.dirname(python_executable) - if os.name == 'nt': - env_path = os.path.join(py_folder, 'Library') - else: - env_path = os.path.dirname(py_folder) - lib_folder = os.path.join(env_path, 'lib') - include_folder = os.path.join(env_path, 'include') - library_header_path = os.path.join( - include_folder, library_header) - library_found = os.path.isfile(library_header_path) - conda_installed = library_found - - if not library_found: - if sys.platform == 'linux': - library_found = os.path.exists('/usr/include/{0}'.format( - library_header)) - library_found = library_found or os.path.exists( - '/usr/local/include/{0}'.format(library_header)) - else: - # Lookup in TORCHVISION_INCLUDE or in the package file - package_path = [os.path.join(this_dir, 'torchvision')] - for folder in vision_include + package_path: - candidate_path = os.path.join(folder, library_header) - library_found = os.path.exists(candidate_path) - if library_found: - break - - return library_found, conda_installed, include_folder, lib_folder - - def get_extensions(): this_dir = os.path.dirname(os.path.abspath(__file__)) extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') @@ -230,63 +171,6 @@ def get_extensions(): ) ) - # ------------------- Torchvision extra extensions ------------------------ - vision_include = os.environ.get('TORCHVISION_INCLUDE', None) - vision_library = os.environ.get('TORCHVISION_LIBRARY', None) - vision_include = (vision_include.split(os.pathsep) - if vision_include is not None else []) - vision_library = (vision_library.split(os.pathsep) - if vision_library is not None else []) - include_dirs += vision_include - library_dirs = vision_library - - # Image reading extension - image_macros = [] - image_include = [extensions_dir] - image_library = [] - image_link_flags = [] - - # Locating libPNG - libpng = distutils.spawn.find_executable('libpng-config') - png_found = libpng is not None - image_macros += [('PNG_FOUND', str(int(png_found)))] - print('PNG found: {0}'.format(png_found)) - if png_found: - png_version = subprocess.run([libpng, '--version'], - stdout=subprocess.PIPE) - png_version = png_version.stdout.strip().decode('utf-8') - print('libpng version: {0}'.format(png_version)) - png_version = parse_version(png_version) - if png_version >= parse_version("1.6.0"): - print('Building torchvision with PNG image support') - png_lib = subprocess.run([libpng, '--libdir'], - stdout=subprocess.PIPE) - png_include = subprocess.run([libpng, '--I_opts'], - stdout=subprocess.PIPE) - png_include = png_include.stdout.strip().decode('utf-8') - _, png_include = png_include.split('-I') - image_library += [png_lib.stdout.strip().decode('utf-8')] - image_include += [png_include] - image_link_flags.append('png' if os.name != 'nt' else 'libpng') - else: - print('libpng installed version is less than 1.6.0, ' - 'disabling PNG support') - png_found = False - - image_path = os.path.join(extensions_dir, 'cpu', 'image') - image_src = glob.glob(os.path.join(image_path, '*.cpp')) - - if png_found: - ext_modules.append(extension( - 'torchvision.image', - image_src, - include_dirs=image_include + include_dirs + [image_path], - library_dirs=image_library + library_dirs, - define_macros=image_macros, - libraries=image_link_flags, - extra_compile_args=extra_compile_args - )) - ffmpeg_exe = distutils.spawn.find_executable('ffmpeg') has_ffmpeg = ffmpeg_exe is not None @@ -359,9 +243,7 @@ def run(self): # Package info packages=find_packages(exclude=('test',)), - package_data={ - package_name: ['*.lib', '*.dylib', '*.so'] - }, + zip_safe=False, install_requires=requirements, extras_require={ diff --git a/test/test_image.py b/test/test_image.py deleted file mode 100644 index f40dacd4be4..00000000000 --- a/test/test_image.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -import unittest -import sys - -import torch -import torchvision -from PIL import Image -from torchvision.io.image import read_png, decode_png -import numpy as np - -IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") -IMAGE_DIR = os.path.join(IMAGE_ROOT, "fakedata", "imagefolder") - - -def get_images(directory, img_ext): - assert os.path.isdir(directory) - for root, _, files in os.walk(directory): - for fl in files: - _, ext = os.path.splitext(fl) - if ext == img_ext: - yield os.path.join(root, fl) - - -class ImageTester(unittest.TestCase): - def test_read_png(self): - for img_path in get_images(IMAGE_DIR, "png"): - img_pil = torch.from_numpy(np.array(Image.open(img_path))) - img_lpng = read_png(img_path) - self.assertEqual(img_lpng, img_pil) - - def test_decode_png(self): - for img_path in get_images(IMAGE_DIR, "png"): - img_pil = torch.from_numpy(np.array(Image.open(img_path))) - size = os.path.getsize(img_path) - img_lpng = decode_png(torch.from_file(img_path, dtype=torch.uint8, size=size)) - self.assertEqual(img_lpng, img_pil) - - self.assertEqual(decode_png(torch.empty()), torch.empty()) - self.assertEqual(decode_png(torch.randint(3, 5, (300,))), torch.empty()) - - -if __name__ == '__main__': - unittest.main() diff --git a/torchvision/csrc/cpu/image/image.cpp b/torchvision/csrc/cpu/image/image.cpp deleted file mode 100644 index ca9082db3c0..00000000000 --- a/torchvision/csrc/cpu/image/image.cpp +++ /dev/null @@ -1,23 +0,0 @@ - -#include "image.h" -#include -#include - -// If we are in a Windows environment, we need to define -// initialization functions for the _custom_ops extension -#ifdef _WIN32 -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC init_image(void) { - // No need to do anything. - return NULL; -} -#else -PyMODINIT_FUNC PyInit_image(void) { - // No need to do anything. - return NULL; -} -#endif -#endif - -static auto registry = - torch::RegisterOperators().op("image::decode_png", &decodePNG); diff --git a/torchvision/csrc/cpu/image/image.h b/torchvision/csrc/cpu/image/image.h deleted file mode 100644 index 3f1c9561b40..00000000000 --- a/torchvision/csrc/cpu/image/image.h +++ /dev/null @@ -1,6 +0,0 @@ - -#pragma once - -#include -#include -#include "readpng_cpu.h" diff --git a/torchvision/csrc/cpu/image/readpng_cpu.cpp b/torchvision/csrc/cpu/image/readpng_cpu.cpp deleted file mode 100644 index b7fb28e2575..00000000000 --- a/torchvision/csrc/cpu/image/readpng_cpu.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "readpng_cpu.h" - -#include -#include -#include - -#if !PNG_FOUND -torch::Tensor decodePNG(const torch::Tensor& data) { - AT_ERROR("decodePNG: torchvision not compiled with libPNG support"); -} -#else -#include - -torch::Tensor decodePNG(const torch::Tensor& data) { - auto png_ptr = - png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - TORCH_CHECK(png_ptr, "libpng read structure allocation failed!") - auto info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_read_struct(&png_ptr, nullptr, nullptr); - // Seems redundant with the if statement. done here to avoid leaking memory. - TORCH_CHECK(info_ptr, "libpng info structure allocation failed!") - } - - auto datap = data.accessor().data(); - - if (setjmp(png_jmpbuf(png_ptr)) != 0) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - TORCH_CHECK(false, "Internal error."); - } - auto is_png = !png_sig_cmp(datap, 0, 8); - TORCH_CHECK(is_png, "Content is not png!") - - struct Reader { - png_const_bytep ptr; - } reader; - reader.ptr = png_const_bytep(datap) + 8; - - auto read_callback = - [](png_structp png_ptr, png_bytep output, png_size_t bytes) { - auto reader = static_cast(png_get_io_ptr(png_ptr)); - std::copy(reader->ptr, reader->ptr + bytes, output); - reader->ptr += bytes; - }; - png_set_sig_bytes(png_ptr, 8); - png_set_read_fn(png_ptr, &reader, read_callback); - png_read_info(png_ptr, info_ptr); - - png_uint_32 width, height; - int bit_depth, color_type; - auto retval = png_get_IHDR( - png_ptr, - info_ptr, - &width, - &height, - &bit_depth, - &color_type, - nullptr, - nullptr, - nullptr); - - if (retval != 1) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - TORCH_CHECK(retval == 1, "Could read image metadata from content.") - } - if (color_type != PNG_COLOR_TYPE_RGB) { - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - TORCH_CHECK( - color_type == PNG_COLOR_TYPE_RGB, "Non RGB images are not supported.") - } - - auto tensor = - torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); - auto ptr = tensor.accessor().data(); - auto bytes = png_get_rowbytes(png_ptr, info_ptr); - for (decltype(height) i = 0; i < height; ++i) { - png_read_row(png_ptr, ptr, nullptr); - ptr += bytes; - } - png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); - return tensor; -} -#endif // PNG_FOUND diff --git a/torchvision/csrc/cpu/image/readpng_cpu.h b/torchvision/csrc/cpu/image/readpng_cpu.h deleted file mode 100644 index d2151a43aa9..00000000000 --- a/torchvision/csrc/cpu/image/readpng_cpu.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include -#include - -torch::Tensor decodePNG(const torch::Tensor& data); diff --git a/torchvision/io/__init__.py b/torchvision/io/__init__.py index 4c47d8a51d5..cbbf560412e 100644 --- a/torchvision/io/__init__.py +++ b/torchvision/io/__init__.py @@ -30,5 +30,5 @@ "_read_video_clip_from_memory", "_read_video_meta_data", "VideoMetaData", - "Timebase" + "Timebase", ] diff --git a/torchvision/io/image.py b/torchvision/io/image.py deleted file mode 100644 index ea12f13e3bd..00000000000 --- a/torchvision/io/image.py +++ /dev/null @@ -1,68 +0,0 @@ -import torch -from torch import nn, Tensor - -import os -import os.path as osp -import importlib - -_HAS_IMAGE_OPT = False - -try: - lib_dir = osp.join(osp.dirname(__file__), "..") - - loader_details = ( - importlib.machinery.ExtensionFileLoader, - importlib.machinery.EXTENSION_SUFFIXES - ) - - extfinder = importlib.machinery.FileFinder(lib_dir, loader_details) - ext_specs = extfinder.find_spec("image") - if ext_specs is not None: - torch.ops.load_library(ext_specs.origin) - _HAS_IMAGE_OPT = True -except (ImportError, OSError): - pass - - -def decode_png(input): - # type: (Tensor) -> Tensor - """ - Decodes a PNG image into a 3 dimensional RGB Tensor. - The values of the output tensor are uint8 between 0 and 255. - - Arguments: - input (Tensor[1]): a one dimensional int8 tensor containing - the raw bytes of the PNG image. - - Returns: - output (Tensor[image_width, image_height, 3]) - """ - if not isinstance(input, torch.Tensor) or len(input) == 0: - raise ValueError("Expected a non empty 1-dimensional tensor.") - - if not input.dtype == torch.uint8: - raise ValueError("Expected a torch.uint8 tensor.") - output = torch.ops.image.decode_png(input) - return output - - -def read_png(path): - # type: (str) -> Tensor - """ - Reads a PNG image into a 3 dimensional RGB Tensor. - The values of the output tensor are uint8 between 0 and 255. - - Arguments: - path (str): path of the PNG image. - - Returns: - output (Tensor[image_width, image_height, 3]) - """ - if not os.path.isfile(path): - raise ValueError("Expected a valid file path.") - - size = os.path.getsize(path) - if size == 0: - raise ValueError("Expected a non empty file.") - data = torch.from_file(path, dtype=torch.uint8, size=size) - return decode_png(data) From 39e4057c77ced6e027f32b862ffe21ad9f950e7a Mon Sep 17 00:00:00 2001 From: vfdev Date: Thu, 2 Jul 2020 13:48:41 +0200 Subject: [PATCH 25/51] Added symmetric padding mode for Tensors (#2373) * [WIP] Added symmetric padding mode * Added check and raise error if padding is negative for symmetric padding mode * Added test check for raising error if negative pad --- test/test_functional_tensor.py | 4 +++ torchvision/transforms/functional_tensor.py | 37 +++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index 0c2194e0f7b..89af6dce5d7 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -259,6 +259,7 @@ def test_pad(self): {"padding_mode": "constant", "fill": 20}, {"padding_mode": "edge"}, {"padding_mode": "reflect"}, + {"padding_mode": "symmetric"}, ] for kwargs in configs: pad_tensor = F_t.pad(tensor, pad, **kwargs) @@ -278,6 +279,9 @@ def test_pad(self): pad_tensor_script = script_fn(tensor, script_pad, **kwargs) self.assertTrue(pad_tensor.equal(pad_tensor_script), msg="{}, {}".format(pad, kwargs)) + with self.assertRaises(ValueError, msg="Padding can not be negative for symmetric padding_mode"): + F_t.pad(tensor, (-2, -3), padding_mode="symmetric") + if __name__ == '__main__': unittest.main() diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index d4a8f340997..8b64abe9f9c 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -355,6 +355,29 @@ def _hsv2rgb(img): return torch.einsum("ijk, xijk -> xjk", mask.to(dtype=img.dtype), a4) +def _pad_symmetric(img: Tensor, padding: List[int]) -> Tensor: + # padding is left, right, top, bottom + in_sizes = img.size() + + x_indices = [i for i in range(in_sizes[-1])] # [0, 1, 2, 3, ...] + left_indices = [i for i in range(padding[0] - 1, -1, -1)] # e.g. [3, 2, 1, 0] + right_indices = [-(i + 1) for i in range(padding[1])] # e.g. [-1, -2, -3] + x_indices = torch.tensor(left_indices + x_indices + right_indices) + + y_indices = [i for i in range(in_sizes[-2])] + top_indices = [i for i in range(padding[2] - 1, -1, -1)] + bottom_indices = [-(i + 1) for i in range(padding[3])] + y_indices = torch.tensor(top_indices + y_indices + bottom_indices) + + ndim = img.ndim + if ndim == 3: + return img[:, y_indices[:, None], x_indices[None, :]] + elif ndim == 4: + return img[:, :, y_indices[:, None], x_indices[None, :]] + else: + raise RuntimeError("Symmetric padding of N-D tensors are not supported yet") + + def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "constant") -> Tensor: r"""Pad the given Tensor Image on all sides with specified padding mode and fill value. @@ -380,6 +403,11 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con padding [1, 2, 3, 4] with 2 elements on both sides in reflect mode will result in [3, 2, 1, 2, 3, 4, 3, 2] + - symmetric: pads with reflection of image (repeating the last value on the edge) + + padding [1, 2, 3, 4] with 2 elements on both sides in symmetric mode + will result in [2, 1, 1, 2, 3, 4, 4, 3] + Returns: Tensor: Padded image. """ @@ -400,8 +428,8 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con raise ValueError("Padding must be an int or a 1, 2, or 4 element tuple, not a " + "{} element tuple".format(len(padding))) - if padding_mode not in ["constant", "edge", "reflect"]: - raise ValueError("Padding mode should be either constant, edge or reflect") + if padding_mode not in ["constant", "edge", "reflect", "symmetric"]: + raise ValueError("Padding mode should be either constant, edge, reflect or symmetric") if isinstance(padding, int): if torch.jit.is_scripting(): @@ -423,6 +451,11 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con if padding_mode == "edge": # remap padding_mode str padding_mode = "replicate" + elif padding_mode == "symmetric": + # route to another implementation + if p[0] < 0 or p[1] < 0 or p[2] < 0 or p[3] < 0: # no any support for torch script + raise ValueError("Padding can not be negative for symmetric padding_mode") + return _pad_symmetric(img, p) need_squeeze = False if img.ndim < 4: From 5247f7b6a019290bd2a838dc0baa8acec5fe484a Mon Sep 17 00:00:00 2001 From: Francisco Massa Date: Fri, 3 Jul 2020 10:33:50 +0200 Subject: [PATCH 26/51] Fix image extension tests (#2382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Add PNG reading capabilities * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Add PNG reading capabilities * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Install libpng on conda-based wheel distributions * Add -y flag * Add -y flag to yum * Locate LibPNG on windows conda * Remove empty else * Copy libpng16.so * Copy dylib on Mac * Improve check on Windows * Try to install ninja using conda on windows * Use libpng on Windows * Package lib on windows wheel * Point library to the correct place * Include binaries as part of wheel * Copy libpng.so on linux * Look for png.h on Windows when using conda-build * Do not skip png tests on Mac/Win * Restore libjpeg-turbo * Install jpeg-turbo on wheel distributions * Install libjpeg-turbo from conda-forge on wheel distributions * Do not pull av on conda-build * Add pillow disclaimer * Vendors libjpeg-turbo 2.0.4 * Merge JPEG work * Remove submodules * Regenerate circle config * Fix style issues * Fix C++ style issues * More style corrections * Add JPEG-turbo to linking libraries * More style corrections * More style corrections * More style corrections * Install libjpeg-turbo-devel * Install libturbo-jpeg on typing pipeline * Update Circle template * Windows and Unix turbojpeg have the same linking name * Install turbojpeg-devel instead of libjpeg-turbo * Copy TurboJPEG binaries to wheel * Move test image * Move back test image * Update JPEG test path * Remove dot from extension * Move image functions to extension * Use stdout arg in subprocess * Disable image extension if libpng or turbojpeg are not found * Append libpng stdout * Prevent list appending on lists * Minor path correction * Minor error correction * Add linking flags * Style issues correction * Address minor review corrections * Refactor library search * Restore access index * Fix JPEG tests * Update libpng version in Travis * Add -y flag * Remove dot * Update libpng using apt * Check libpng version * Change libturbojpeg binary * Update import * Change call * Restore av in conda recipe * Minor error correction * Remove unused comment in travis.yml * Update README * Fix missing links * Remove fixes for 16.04 * Remove JPEG-related code * Remove installation references to turbojpeg * Remove further references to turbojpeg * Fix c++ style issues * Fix c++ style issues * Fix libpng-config include flag parsing * Remove conda-forge * Remove include dirs from main extension * Do not pass extra include and library paths to main torchvision extension * Add libpng to environment.yml * Remove inexistent imports * Add instructions regarding environment variables to README * Fix image extension tests * Add libpng to environment + test fixes * Minor improvements * Remove unused Py2 code * Add stub comments to prevent deletion while merging * Reintroduce files in order to prevent deletion during merge * Remove unwanted merge sections * Restore libpng conda installation on wheel distributions * Restore comment * Fix libpng discovery on Windows * Fix PEP8 style issues * Add linking flag on Windows * Remove parenthesis * Restore libpng during runtime Co-authored-by: Edgar Andrés Margffoy Tuay Co-authored-by: Ryad ZENINE --- .../unittest/linux/scripts/environment.yml | 1 + .../unittest/windows/scripts/environment.yml | 3 +- .gitignore | 2 + .travis.yml | 1 + CMakeLists.txt | 17 ++- README.rst | 10 +- packaging/build_wheel.sh | 17 +++ packaging/pkg_helpers.bash | 4 + packaging/torchvision/meta.yaml | 2 + setup.py | 135 +++++++++++++++++- test/test_image.py | 46 ++++++ torchvision/csrc/cpu/image/image.cpp | 16 +++ torchvision/csrc/cpu/image/image.h | 7 + torchvision/csrc/cpu/image/readpng_cpu.cpp | 84 +++++++++++ torchvision/csrc/cpu/image/readpng_cpu.h | 7 + torchvision/io/image.py | 68 +++++++++ 16 files changed, 410 insertions(+), 10 deletions(-) create mode 100644 test/test_image.py create mode 100644 torchvision/csrc/cpu/image/image.cpp create mode 100644 torchvision/csrc/cpu/image/image.h create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.cpp create mode 100644 torchvision/csrc/cpu/image/readpng_cpu.h create mode 100644 torchvision/io/image.py diff --git a/.circleci/unittest/linux/scripts/environment.yml b/.circleci/unittest/linux/scripts/environment.yml index d664c7c0f7f..2b3604ee1c8 100644 --- a/.circleci/unittest/linux/scripts/environment.yml +++ b/.circleci/unittest/linux/scripts/environment.yml @@ -6,6 +6,7 @@ dependencies: - pytest-cov - codecov - pip + - libpng - ca-certificates - pip: - future diff --git a/.circleci/unittest/windows/scripts/environment.yml b/.circleci/unittest/windows/scripts/environment.yml index fbe92df523d..ddbf7445a92 100644 --- a/.circleci/unittest/windows/scripts/environment.yml +++ b/.circleci/unittest/windows/scripts/environment.yml @@ -6,9 +6,10 @@ dependencies: - pytest-cov - codecov - pip + - libpng - ca-certificates - pip: - future - pillow>=4.1.1 - scipy==1.4.1 - - av \ No newline at end of file + - av diff --git a/.gitignore b/.gitignore index 6bea8609b93..6d649a7c019 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ htmlcov *.swo gen.yml .mypy_cache +.vscode/ +*.orig diff --git a/.travis.yml b/.travis.yml index ec25bdb8677..d8c45c2defe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ jobs: before_install: - sudo apt-get update + - sudo apt-get install -y libpng-dev - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" diff --git a/CMakeLists.txt b/CMakeLists.txt index fa50f155ce4..2d28bc8a4c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,22 +11,29 @@ if(WITH_CUDA) endif() find_package(Python3 COMPONENTS Development) + find_package(Torch REQUIRED) +find_package(PNG REQUIRED) + file(GLOB HEADERS torchvision/csrc/*.h) -file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp torchvision/csrc/*.cpp) +# Image extension +file(GLOB IMAGE_HEADERS torchvision/csrc/cpu/image/*.h) +file(GLOB IMAGE_SOURCES torchvision/csrc/cpu/image/*.cpp) +file(GLOB OPERATOR_SOURCES torchvision/csrc/cpu/*.h torchvision/csrc/cpu/*.cpp ${IMAGE_HEADERS} ${IMAGE_SOURCES} ${HEADERS} torchvision/csrc/*.cpp) if(WITH_CUDA) file(GLOB OPERATOR_SOURCES ${OPERATOR_SOURCES} torchvision/csrc/cuda/*.h torchvision/csrc/cuda/*.cu) endif() file(GLOB MODELS_HEADERS torchvision/csrc/models/*.h) file(GLOB MODELS_SOURCES torchvision/csrc/models/*.h torchvision/csrc/models/*.cpp) -add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} Python3::Python) +add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES} ${IMAGE_SOURCES}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} ${PNG_LIBRARY} Python3::Python) +# target_link_libraries(${PROJECT_NAME} PRIVATE ${PNG_LIBRARY} Python3::Python) set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME TorchVision) target_include_directories(${PROJECT_NAME} INTERFACE - $ + $ $) include(GNUInstallDirs) @@ -61,7 +68,7 @@ install(FILES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cpu) if(WITH_CUDA) install(FILES - torchvision/csrc/cuda/vision_cuda.h + torchvision/csrc/cuda/vision_cuda.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/cuda) endif() install(FILES ${MODELS_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/models) diff --git a/README.rst b/README.rst index 3150a3023ad..0de404c74b3 100644 --- a/README.rst +++ b/README.rst @@ -78,13 +78,19 @@ Torchvision currently supports the following image backends: * `accimage`_ - if installed can be activated by calling :code:`torchvision.set_image_backend('accimage')` +* `libpng`_ - can be installed via conda :code:`conda install libpng` or any of the package managers for debian-based and RHEL-based Linux distributions. + +**Notes:** ``libpng`` must be available at compilation time in order to be available. Make sure that it is available on the standard library locations, +otherwise, add the include and library paths in the environment variables ``TORCHVISION_INCLUDE`` and ``TORCHVISION_LIBRARY``, respectively. + +.. _libpng : http://www.libpng.org/pub/png/libpng.html .. _Pillow : https://python-pillow.org/ .. _Pillow-SIMD : https://github.com/uploadcare/pillow-simd .. _accimage: https://github.com/pytorch/accimage C++ API ======= -TorchVision also offers a C++ API that contains C++ equivalent of python models. +TorchVision also offers a C++ API that contains C++ equivalent of python models. Installation From source: @@ -94,7 +100,7 @@ Installation From source: cd build # Add -DWITH_CUDA=on support for the CUDA if needed cmake .. - make + make make install Once installed, the library can be accessed in cmake (after properly configuring ``CMAKE_PREFIX_PATH``) via the :code:`TorchVision::TorchVision` target: diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index a075b3b3a00..1a6e1b1761a 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -10,6 +10,23 @@ setup_wheel_python pip_install numpy pyyaml future ninja setup_pip_pytorch_version python setup.py clean + +# Copy binaries to be included in the wheel distribution +if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then + python_exec="$(which python)" + bin_path=$(dirname $python_exec) + env_path=$(dirname $bin_path) + if [[ "$(uname)" == Darwin ]]; then + # Include LibPNG + cp "$env_path/lib/libpng16.dylib" torchvision + else + cp "$bin_path/Library/bin/libpng16.dll" torchvision + fi +else + # Include LibPNG + cp "/usr/lib64/libpng.so" torchvision +fi + if [[ "$OSTYPE" == "msys" ]]; then IS_WHEEL=1 "$script_dir/windows/internal/vc_env_helper.bat" python setup.py bdist_wheel else diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index b262a0f5157..128d0b51913 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -170,7 +170,11 @@ setup_wheel_python() { conda env remove -n "env$PYTHON_VERSION" || true conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" conda activate "env$PYTHON_VERSION" + # Install libpng from Anaconda (defaults) + conda install libpng -y else + # Install native CentOS libPNG + yum install -y libpng-devel case "$PYTHON_VERSION" in 2.7) if [[ -n "$UNICODE_ABI" ]]; then diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index 7d6f28cdf3c..b3fc2a2e9df 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -8,6 +8,7 @@ source: requirements: build: - {{ compiler('c') }} # [win] + - libpng host: - python @@ -18,6 +19,7 @@ requirements: run: - python + - libpng - pillow >=4.1.1 - numpy >=1.11 {{ environ.get('CONDA_PYTORCH_CONSTRAINT') }} diff --git a/setup.py b/setup.py index 0620193b3a7..01276f1893d 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import re import sys from setuptools import setup, find_packages -from pkg_resources import get_distribution, DistributionNotFound +from pkg_resources import parse_version, get_distribution, DistributionNotFound import subprocess import distutils.command.clean import distutils.spawn @@ -76,6 +76,65 @@ def write_version_file(): requirements.append(pillow_req + pillow_ver) +def find_library(name, vision_include): + this_dir = os.path.dirname(os.path.abspath(__file__)) + build_prefix = os.environ.get('BUILD_PREFIX', None) + is_conda_build = build_prefix is not None + + library_found = False + conda_installed = False + lib_folder = None + include_folder = None + library_header = '{0}.h'.format(name) + + print('Running build on conda-build: {0}'.format(is_conda_build)) + if is_conda_build: + # Add conda headers/libraries + if os.name == 'nt': + build_prefix = os.path.join(build_prefix, 'Library') + include_folder = os.path.join(build_prefix, 'include') + lib_folder = os.path.join(build_prefix, 'lib') + library_header_path = os.path.join( + include_folder, library_header) + library_found = os.path.isfile(library_header_path) + conda_installed = library_found + else: + # Check if using Anaconda to produce wheels + conda = distutils.spawn.find_executable('conda') + is_conda = conda is not None + print('Running build on conda: {0}'.format(is_conda)) + if is_conda: + python_executable = sys.executable + py_folder = os.path.dirname(python_executable) + if os.name == 'nt': + env_path = os.path.join(py_folder, 'Library') + else: + env_path = os.path.dirname(py_folder) + lib_folder = os.path.join(env_path, 'lib') + include_folder = os.path.join(env_path, 'include') + library_header_path = os.path.join( + include_folder, library_header) + library_found = os.path.isfile(library_header_path) + conda_installed = library_found + + if not library_found: + if sys.platform == 'linux': + library_found = os.path.exists('/usr/include/{0}'.format( + library_header)) + library_found = library_found or os.path.exists( + '/usr/local/include/{0}'.format(library_header)) + else: + # Lookup in TORCHVISION_INCLUDE or in the package file + package_path = [os.path.join(this_dir, 'torchvision')] + for folder in vision_include + package_path: + candidate_path = os.path.join(folder, library_header) + library_found = os.path.exists(candidate_path) + if library_found: + break + + return library_found, conda_installed, include_folder, lib_folder + + def get_extensions(): this_dir = os.path.dirname(os.path.abspath(__file__)) extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') @@ -171,6 +230,76 @@ def get_extensions(): ) ) + # ------------------- Torchvision extra extensions ------------------------ + vision_include = os.environ.get('TORCHVISION_INCLUDE', None) + vision_library = os.environ.get('TORCHVISION_LIBRARY', None) + vision_include = (vision_include.split(os.pathsep) + if vision_include is not None else []) + vision_library = (vision_library.split(os.pathsep) + if vision_library is not None else []) + include_dirs += vision_include + library_dirs = vision_library + + # Image reading extension + image_macros = [] + image_include = [extensions_dir] + image_library = [] + image_link_flags = [] + + # Locating libPNG + libpng = distutils.spawn.find_executable('libpng-config') + pngfix = distutils.spawn.find_executable('pngfix') + png_found = libpng is not None or pngfix is not None + image_macros += [('PNG_FOUND', str(int(png_found)))] + print('PNG found: {0}'.format(png_found)) + if png_found: + if libpng is not None: + # Linux / Mac + png_version = subprocess.run([libpng, '--version'], + stdout=subprocess.PIPE) + png_version = png_version.stdout.strip().decode('utf-8') + print('libpng version: {0}'.format(png_version)) + png_version = parse_version(png_version) + if png_version >= parse_version("1.6.0"): + print('Building torchvision with PNG image support') + png_lib = subprocess.run([libpng, '--libdir'], + stdout=subprocess.PIPE) + png_include = subprocess.run([libpng, '--I_opts'], + stdout=subprocess.PIPE) + png_include = png_include.stdout.strip().decode('utf-8') + _, png_include = png_include.split('-I') + print('libpng include path: {0}'.format(png_include)) + image_library += [png_lib.stdout.strip().decode('utf-8')] + image_include += [png_include] + image_link_flags.append('png') + else: + print('libpng installed version is less than 1.6.0, ' + 'disabling PNG support') + png_found = False + else: + # Windows + png_lib = os.path.join( + os.path.dirname(os.path.dirname(pngfix)), 'lib') + png_include = os.path.join(os.path.dirname( + os.path.dirname(pngfix)), 'include', 'libpng16') + image_library += [png_lib] + image_include += [png_include] + image_link_flags.append('libpng') + + image_path = os.path.join(extensions_dir, 'cpu', 'image') + image_src = glob.glob(os.path.join(image_path, '*.cpp')) + + if png_found: + ext_modules.append(extension( + 'torchvision.image', + image_src, + include_dirs=image_include + include_dirs + [image_path], + library_dirs=image_library + library_dirs, + define_macros=image_macros, + libraries=image_link_flags, + extra_compile_args=extra_compile_args + )) + ffmpeg_exe = distutils.spawn.find_executable('ffmpeg') has_ffmpeg = ffmpeg_exe is not None @@ -243,7 +372,9 @@ def run(self): # Package info packages=find_packages(exclude=('test',)), - + package_data={ + package_name: ['*.dll', '*.dylib', '*.so'] + }, zip_safe=False, install_requires=requirements, extras_require={ diff --git a/test/test_image.py b/test/test_image.py new file mode 100644 index 00000000000..a7f660127b8 --- /dev/null +++ b/test/test_image.py @@ -0,0 +1,46 @@ +import os +import unittest +import sys + +import torch +import torchvision +from PIL import Image +from torchvision.io.image import read_png, decode_png +import numpy as np + +IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") +IMAGE_DIR = os.path.join(IMAGE_ROOT, "fakedata", "imagefolder") + + +def get_images(directory, img_ext): + assert os.path.isdir(directory) + for root, _, files in os.walk(directory): + for fl in files: + _, ext = os.path.splitext(fl) + if ext == img_ext: + yield os.path.join(root, fl) + + +class ImageTester(unittest.TestCase): + def test_read_png(self): + # Check across .png + for img_path in get_images(IMAGE_DIR, ".png"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + img_lpng = read_png(img_path) + self.assertTrue(img_lpng.equal(img_pil)) + + def test_decode_png(self): + for img_path in get_images(IMAGE_DIR, ".png"): + img_pil = torch.from_numpy(np.array(Image.open(img_path))) + size = os.path.getsize(img_path) + img_lpng = decode_png(torch.from_file(img_path, dtype=torch.uint8, size=size)) + self.assertTrue(img_lpng.equal(img_pil)) + + with self.assertRaises(ValueError): + decode_png(torch.empty((), dtype=torch.uint8)) + with self.assertRaises(RuntimeError): + decode_png(torch.randint(3, 5, (300,), dtype=torch.uint8)) + + +if __name__ == '__main__': + unittest.main() diff --git a/torchvision/csrc/cpu/image/image.cpp b/torchvision/csrc/cpu/image/image.cpp new file mode 100644 index 00000000000..0dc82a69827 --- /dev/null +++ b/torchvision/csrc/cpu/image/image.cpp @@ -0,0 +1,16 @@ + +#include "image.h" +#include +#include + +// If we are in a Windows environment, we need to define +// initialization functions for the _custom_ops extension +#ifdef _WIN32 +PyMODINIT_FUNC PyInit_image(void) { + // No need to do anything. + return NULL; +} +#endif + +static auto registry = + torch::RegisterOperators().op("image::decode_png", &decodePNG); diff --git a/torchvision/csrc/cpu/image/image.h b/torchvision/csrc/cpu/image/image.h new file mode 100644 index 00000000000..f5b86cf683b --- /dev/null +++ b/torchvision/csrc/cpu/image/image.h @@ -0,0 +1,7 @@ + +#pragma once + +// Comment +#include +#include +#include "readpng_cpu.h" diff --git a/torchvision/csrc/cpu/image/readpng_cpu.cpp b/torchvision/csrc/cpu/image/readpng_cpu.cpp new file mode 100644 index 00000000000..b284067b1ff --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.cpp @@ -0,0 +1,84 @@ +#include "readpng_cpu.h" + +// Comment +#include +#include +#include + +#if !PNG_FOUND +torch::Tensor decodePNG(const torch::Tensor& data) { + AT_ERROR("decodePNG: torchvision not compiled with libPNG support"); +} +#else +#include + +torch::Tensor decodePNG(const torch::Tensor& data) { + auto png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + TORCH_CHECK(png_ptr, "libpng read structure allocation failed!") + auto info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + // Seems redundant with the if statement. done here to avoid leaking memory. + TORCH_CHECK(info_ptr, "libpng info structure allocation failed!") + } + + auto datap = data.accessor().data(); + + if (setjmp(png_jmpbuf(png_ptr)) != 0) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(false, "Internal error."); + } + auto is_png = !png_sig_cmp(datap, 0, 8); + TORCH_CHECK(is_png, "Content is not png!") + + struct Reader { + png_const_bytep ptr; + } reader; + reader.ptr = png_const_bytep(datap) + 8; + + auto read_callback = + [](png_structp png_ptr, png_bytep output, png_size_t bytes) { + auto reader = static_cast(png_get_io_ptr(png_ptr)); + std::copy(reader->ptr, reader->ptr + bytes, output); + reader->ptr += bytes; + }; + png_set_sig_bytes(png_ptr, 8); + png_set_read_fn(png_ptr, &reader, read_callback); + png_read_info(png_ptr, info_ptr); + + png_uint_32 width, height; + int bit_depth, color_type; + auto retval = png_get_IHDR( + png_ptr, + info_ptr, + &width, + &height, + &bit_depth, + &color_type, + nullptr, + nullptr, + nullptr); + + if (retval != 1) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK(retval == 1, "Could read image metadata from content.") + } + if (color_type != PNG_COLOR_TYPE_RGB) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + TORCH_CHECK( + color_type == PNG_COLOR_TYPE_RGB, "Non RGB images are not supported.") + } + + auto tensor = + torch::empty({int64_t(height), int64_t(width), int64_t(3)}, torch::kU8); + auto ptr = tensor.accessor().data(); + auto bytes = png_get_rowbytes(png_ptr, info_ptr); + for (decltype(height) i = 0; i < height; ++i) { + png_read_row(png_ptr, ptr, nullptr); + ptr += bytes; + } + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + return tensor; +} +#endif // PNG_FOUND diff --git a/torchvision/csrc/cpu/image/readpng_cpu.h b/torchvision/csrc/cpu/image/readpng_cpu.h new file mode 100644 index 00000000000..38fab84dc7c --- /dev/null +++ b/torchvision/csrc/cpu/image/readpng_cpu.h @@ -0,0 +1,7 @@ +#pragma once + +// Comment +#include +#include + +torch::Tensor decodePNG(const torch::Tensor& data); diff --git a/torchvision/io/image.py b/torchvision/io/image.py new file mode 100644 index 00000000000..1ad13ed27ad --- /dev/null +++ b/torchvision/io/image.py @@ -0,0 +1,68 @@ +import torch +from torch import nn, Tensor + +import os +import os.path as osp +import importlib + +_HAS_IMAGE_OPT = False + +try: + lib_dir = osp.join(osp.dirname(__file__), "..") + + loader_details = ( + importlib.machinery.ExtensionFileLoader, + importlib.machinery.EXTENSION_SUFFIXES + ) + + extfinder = importlib.machinery.FileFinder(lib_dir, loader_details) + ext_specs = extfinder.find_spec("image") + if ext_specs is not None: + torch.ops.load_library(ext_specs.origin) + _HAS_IMAGE_OPT = True +except (ImportError, OSError): + pass + + +def decode_png(input): + # type: (Tensor) -> Tensor + """ + Decodes a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + input (Tensor[1]): a one dimensional int8 tensor containing + the raw bytes of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not isinstance(input, torch.Tensor) or input.numel() == 0 or input.ndim != 1: + raise ValueError("Expected a non empty 1-dimensional tensor.") + + if not input.dtype == torch.uint8: + raise ValueError("Expected a torch.uint8 tensor.") + output = torch.ops.image.decode_png(input) + return output + + +def read_png(path): + # type: (str) -> Tensor + """ + Reads a PNG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + + Arguments: + path (str): path of the PNG image. + + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not os.path.isfile(path): + raise ValueError("Expected a valid file path.") + + size = os.path.getsize(path) + if size == 0: + raise ValueError("Expected a non empty file.") + data = torch.from_file(path, dtype=torch.uint8, size=size) + return decode_png(data) From e757d521edfcc2f975a83b9cd6917435dbcf7a58 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Fri, 3 Jul 2020 14:52:56 +0200 Subject: [PATCH 27/51] add error message if Google Drive quota is exceeded (#2321) Co-authored-by: Francisco Massa --- torchvision/datasets/utils.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/torchvision/datasets/utils.py b/torchvision/datasets/utils.py index 995162871fc..442add7e81d 100644 --- a/torchvision/datasets/utils.py +++ b/torchvision/datasets/utils.py @@ -118,6 +118,10 @@ def list_files(root, suffix, prefix=False): return files +def _quota_exceeded(response: "requests.models.Response") -> bool: + return "Google Drive - Quota exceeded" in response.text + + def download_file_from_google_drive(file_id, root, filename=None, md5=None): """Download a Google Drive file from and place it in root. @@ -150,6 +154,14 @@ def download_file_from_google_drive(file_id, root, filename=None, md5=None): params = {'id': file_id, 'confirm': token} response = session.get(url, params=params, stream=True) + if _quota_exceeded(response): + msg = ( + f"The daily quota of the file {filename} is exceeded and it " + f"can't be downloaded. This is a limitation of Google Drive " + f"and can only be overcome by trying again later." + ) + raise RuntimeError(msg) + _save_response_content(response, fpath) From 75f5b57e680549d012b3fc01b356b2fb92658ea7 Mon Sep 17 00:00:00 2001 From: vfdev Date: Fri, 3 Jul 2020 15:22:27 +0200 Subject: [PATCH 28/51] [BC-breaking] RandomErasing is now scriptable (#2386) * Related to #2292 - RandomErasing is not scriptable * Fixed code according to review comments - added additional checking of value vs img num_channels --- test/test_transforms.py | 90 ++++++++++++++++++---------- torchvision/transforms/functional.py | 2 +- torchvision/transforms/transforms.py | 78 ++++++++++++++++-------- 3 files changed, 113 insertions(+), 57 deletions(-) diff --git a/test/test_transforms.py b/test/test_transforms.py index 8423bf99ee3..b0eb844fcf8 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -1618,38 +1618,64 @@ def test_random_grayscale(self): def test_random_erasing(self): """Unit tests for random erasing transform""" - - img = torch.rand([3, 60, 60]) - - # Test Set 1: Erasing with int value - img_re = transforms.RandomErasing(value=0.2) - i, j, h, w, v = img_re.get_params(img, scale=img_re.scale, ratio=img_re.ratio, value=img_re.value) - img_output = F.erase(img, i, j, h, w, v) - self.assertEqual(img_output.size(0), 3) - - # Test Set 2: Check if the unerased region is preserved - orig_unerased = img.clone() - orig_unerased[:, i:i + h, j:j + w] = 0 - output_unerased = img_output.clone() - output_unerased[:, i:i + h, j:j + w] = 0 - self.assertTrue(torch.equal(orig_unerased, output_unerased)) - - # Test Set 3: Erasing with random value - img_re = transforms.RandomErasing(value='random')(img) - self.assertEqual(img_re.size(0), 3) - - # Test Set 4: Erasing with tuple value - img_re = transforms.RandomErasing(value=(0.2, 0.2, 0.2))(img) - self.assertEqual(img_re.size(0), 3) - - # Test Set 5: Testing the inplace behaviour - img_re = transforms.RandomErasing(value=(0.2), inplace=True)(img) - self.assertTrue(torch.equal(img_re, img)) - - # Test Set 6: Checking when no erased region is selected - img = torch.rand([3, 300, 1]) - img_re = transforms.RandomErasing(ratio=(0.1, 0.2), value='random')(img) - self.assertTrue(torch.equal(img_re, img)) + for is_scripted in [False, True]: + torch.manual_seed(12) + img = torch.rand(3, 60, 60) + + # Test Set 0: invalid value + random_erasing = transforms.RandomErasing(value=(0.1, 0.2, 0.3, 0.4), p=1.0) + with self.assertRaises(ValueError, msg="If value is a sequence, it should have either a single value or 3"): + img_re = random_erasing(img) + + # Test Set 1: Erasing with int value + random_erasing = transforms.RandomErasing(value=0.2) + if is_scripted: + random_erasing = torch.jit.script(random_erasing) + + i, j, h, w, v = transforms.RandomErasing.get_params( + img, scale=random_erasing.scale, ratio=random_erasing.ratio, value=[random_erasing.value, ] + ) + img_output = F.erase(img, i, j, h, w, v) + self.assertEqual(img_output.size(0), 3) + + # Test Set 2: Check if the unerased region is preserved + true_output = img.clone() + true_output[:, i:i + h, j:j + w] = random_erasing.value + self.assertTrue(torch.equal(true_output, img_output)) + + # Test Set 3: Erasing with random value + random_erasing = transforms.RandomErasing(value="random") + if is_scripted: + random_erasing = torch.jit.script(random_erasing) + img_re = random_erasing(img) + + self.assertEqual(img_re.size(0), 3) + + # Test Set 4: Erasing with tuple value + random_erasing = transforms.RandomErasing(value=(0.2, 0.2, 0.2)) + if is_scripted: + random_erasing = torch.jit.script(random_erasing) + img_re = random_erasing(img) + self.assertEqual(img_re.size(0), 3) + true_output = img.clone() + true_output[:, i:i + h, j:j + w] = torch.tensor(random_erasing.value)[:, None, None] + self.assertTrue(torch.equal(true_output, img_output)) + + # Test Set 5: Testing the inplace behaviour + random_erasing = transforms.RandomErasing(value=(0.2,), inplace=True) + if is_scripted: + random_erasing = torch.jit.script(random_erasing) + + img_re = random_erasing(img) + self.assertTrue(torch.equal(img_re, img)) + + # Test Set 6: Checking when no erased region is selected + img = torch.rand([3, 300, 1]) + random_erasing = transforms.RandomErasing(ratio=(0.1, 0.2), value="random") + if is_scripted: + random_erasing = torch.jit.script(random_erasing) + img_re = random_erasing(img) + self.assertTrue(torch.equal(img_re, img)) if __name__ == '__main__': diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 81a601a8e20..9c7efe0ef53 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -950,7 +950,7 @@ def to_grayscale(img, num_output_channels=1): return img -def erase(img, i, j, h, w, v, inplace=False): +def erase(img: Tensor, i: int, j: int, h: int, w: int, v: Tensor, inplace: bool = False) -> Tensor: """ Erase the input Tensor Image with given value. Args: diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index a5214ed3174..6bc9e7cbc4d 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -3,7 +3,7 @@ import random import warnings from collections.abc import Sequence, Iterable -from typing import Tuple +from typing import Tuple, List, Optional import numpy as np import torch @@ -1343,7 +1343,7 @@ def __repr__(self): return self.__class__.__name__ + '(p={0})'.format(self.p) -class RandomErasing(object): +class RandomErasing(torch.nn.Module): """ Randomly selects a rectangle region in an image and erases its pixels. 'Random Erasing Data Augmentation' by Zhong et al. See https://arxiv.org/pdf/1708.04896.pdf @@ -1370,13 +1370,21 @@ class RandomErasing(object): """ def __init__(self, p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace=False): - assert isinstance(value, (numbers.Number, str, tuple, list)) + super().__init__() + if not isinstance(value, (numbers.Number, str, tuple, list)): + raise TypeError("Argument value should be either a number or str or a sequence") + if isinstance(value, str) and value != "random": + raise ValueError("If value is str, it should be 'random'") + if not isinstance(scale, (tuple, list)): + raise TypeError("Scale should be a sequence") + if not isinstance(ratio, (tuple, list)): + raise TypeError("Ratio should be a sequence") if (scale[0] > scale[1]) or (ratio[0] > ratio[1]): - warnings.warn("range should be of kind (min, max)") + warnings.warn("Scale and ratio should be of kind (min, max)") if scale[0] < 0 or scale[1] > 1: - raise ValueError("range of scale should be between 0 and 1") + raise ValueError("Scale should be between 0 and 1") if p < 0 or p > 1: - raise ValueError("range of random erasing probability should be between 0 and 1") + raise ValueError("Random erasing probability should be between 0 and 1") self.p = p self.scale = scale @@ -1385,13 +1393,18 @@ def __init__(self, p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0, inplace self.inplace = inplace @staticmethod - def get_params(img, scale, ratio, value=0): + def get_params( + img: Tensor, scale: Tuple[float, float], ratio: Tuple[float, float], value: Optional[List[float]] = None + ) -> Tuple[int, int, int, int, Tensor]: """Get parameters for ``erase`` for a random erasing. Args: img (Tensor): Tensor image of size (C, H, W) to be erased. - scale: range of proportion of erased area against input image. - ratio: range of aspect ratio of erased area. + scale (tuple or list): range of proportion of erased area against input image. + ratio (tuple or list): range of aspect ratio of erased area. + value (list, optional): erasing value. If None, it is interpreted as "random" + (erasing each pixel with random values). If ``len(value)`` is 1, it is interpreted as a number, + i.e. ``value[0]``. Returns: tuple: params (i, j, h, w, v) to be passed to ``erase`` for random erasing. @@ -1400,27 +1413,27 @@ def get_params(img, scale, ratio, value=0): area = img_h * img_w for _ in range(10): - erase_area = random.uniform(scale[0], scale[1]) * area - aspect_ratio = random.uniform(ratio[0], ratio[1]) + erase_area = area * torch.empty(1).uniform_(scale[0], scale[1]).item() + aspect_ratio = torch.empty(1).uniform_(ratio[0], ratio[1]).item() h = int(round(math.sqrt(erase_area * aspect_ratio))) w = int(round(math.sqrt(erase_area / aspect_ratio))) + if not (h < img_h and w < img_w): + continue + + if value is None: + v = torch.empty([img_c, h, w], dtype=torch.float32).normal_() + else: + v = torch.tensor(value)[:, None, None] - if h < img_h and w < img_w: - i = random.randint(0, img_h - h) - j = random.randint(0, img_w - w) - if isinstance(value, numbers.Number): - v = value - elif isinstance(value, torch._six.string_classes): - v = torch.empty([img_c, h, w], dtype=torch.float32).normal_() - elif isinstance(value, (list, tuple)): - v = torch.tensor(value, dtype=torch.float32).view(-1, 1, 1).expand(-1, h, w) - return i, j, h, w, v + i = torch.randint(0, img_h - h, size=(1, )).item() + j = torch.randint(0, img_w - w, size=(1, )).item() + return i, j, h, w, v # Return original image return 0, 0, img_h, img_w, img - def __call__(self, img): + def forward(self, img): """ Args: img (Tensor): Tensor image of size (C, H, W) to be erased. @@ -1428,7 +1441,24 @@ def __call__(self, img): Returns: img (Tensor): Erased Tensor image. """ - if random.uniform(0, 1) < self.p: - x, y, h, w, v = self.get_params(img, scale=self.scale, ratio=self.ratio, value=self.value) + if torch.rand(1) < self.p: + + # cast self.value to script acceptable type + if isinstance(self.value, (int, float)): + value = [self.value, ] + elif isinstance(self.value, str): + value = None + elif isinstance(self.value, tuple): + value = list(self.value) + else: + value = self.value + + if value is not None and not (len(value) in (1, img.shape[-3])): + raise ValueError( + "If value is a sequence, it should have either a single value or " + "{} (number of input channels)".format(img.shape[-3]) + ) + + x, y, h, w, v = self.get_params(img, scale=self.scale, ratio=self.ratio, value=value) return F.erase(img, x, y, h, w, v, self.inplace) return img From 67f5fcf7099aa0857230995277c264d66d2fc0ab Mon Sep 17 00:00:00 2001 From: Jeff Kriske Date: Mon, 6 Jul 2020 08:08:14 -0400 Subject: [PATCH 29/51] For std::min and std::max cast to like data types (#2389) Signed-off-by: Jeff Kriske --- torchvision/csrc/cpu/decoder/seekable_buffer.cpp | 4 ++-- torchvision/csrc/cpu/decoder/util.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/torchvision/csrc/cpu/decoder/seekable_buffer.cpp b/torchvision/csrc/cpu/decoder/seekable_buffer.cpp index 0d7ec7236a2..41e3e689c7b 100644 --- a/torchvision/csrc/cpu/decoder/seekable_buffer.cpp +++ b/torchvision/csrc/cpu/decoder/seekable_buffer.cpp @@ -55,7 +55,7 @@ bool SeekableBuffer::readBytes( size_t maxBytes, uint64_t timeoutMs) { // Resize to th minimum 4K page or less - buffer_.resize(std::min(maxBytes, 4 * 1024UL)); + buffer_.resize(std::min(maxBytes, size_t(4 * 1024UL))); end_ = 0; eof_ = false; @@ -72,7 +72,7 @@ bool SeekableBuffer::readBytes( if (res > 0) { end_ += res; if (end_ == buffer_.size()) { - buffer_.resize(std::min(end_ * 4UL, maxBytes)); + buffer_.resize(std::min(size_t(end_ * 4UL), maxBytes)); } } else if (res == 0) { eof_ = true; diff --git a/torchvision/csrc/cpu/decoder/util.cpp b/torchvision/csrc/cpu/decoder/util.cpp index 0dbcf885cf5..774612d3927 100644 --- a/torchvision/csrc/cpu/decoder/util.cpp +++ b/torchvision/csrc/cpu/decoder/util.cpp @@ -395,8 +395,8 @@ void setFormatDimensions( } } // prevent zeros - destW = std::max(destW, 1UL); - destH = std::max(destH, 1UL); + destW = std::max(destW, size_t(1UL)); + destH = std::max(destH, size_t(1UL)); } } // namespace Util } // namespace ffmpeg From 971c3e45b96bc5aa5868c45cd40e4f3c3d90d126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Manojlovi=C4=87?= Date: Mon, 6 Jul 2020 14:44:37 +0200 Subject: [PATCH 30/51] Type annotations for torchvision.ops (#2331) * Add type annotations for torchvision.ops * Fix type annotations for torchvision.ops * Fix typo in import * Fix undefined name in FeaturePyramidNetwork --- torchvision/ops/_utils.py | 10 ++- torchvision/ops/boxes.py | 21 +++--- torchvision/ops/deform_conv.py | 30 +++++++-- torchvision/ops/feature_pyramid_network.py | 78 +++++++++++++--------- torchvision/ops/misc.py | 25 +++++-- torchvision/ops/new_empty_tensor.py | 3 +- torchvision/ops/poolers.py | 53 ++++++++++----- torchvision/ops/ps_roi_align.py | 22 ++++-- torchvision/ops/ps_roi_pool.py | 16 +++-- torchvision/ops/roi_align.py | 22 ++++-- torchvision/ops/roi_pool.py | 14 ++-- 11 files changed, 194 insertions(+), 100 deletions(-) diff --git a/torchvision/ops/_utils.py b/torchvision/ops/_utils.py index f514664042b..6c9a040ecd0 100644 --- a/torchvision/ops/_utils.py +++ b/torchvision/ops/_utils.py @@ -1,10 +1,9 @@ import torch from torch import Tensor -from torch.jit.annotations import List +from torch.jit.annotations import List, Tuple -def _cat(tensors, dim=0): - # type: (List[Tensor], int) -> Tensor +def _cat(tensors: List[Tensor], dim: int = 0) -> Tensor: """ Efficient version of torch.cat that avoids a copy if there is only a single element in a list """ @@ -15,8 +14,7 @@ def _cat(tensors, dim=0): return torch.cat(tensors, dim) -def convert_boxes_to_roi_format(boxes): - # type: (List[Tensor]) -> Tensor +def convert_boxes_to_roi_format(boxes: List[Tensor]) -> Tensor: concat_boxes = _cat([b for b in boxes], dim=0) temp = [] for i, b in enumerate(boxes): @@ -26,7 +24,7 @@ def convert_boxes_to_roi_format(boxes): return rois -def check_roi_boxes_shape(boxes): +def check_roi_boxes_shape(boxes: Tensor): if isinstance(boxes, (list, tuple)): for _tensor in boxes: assert _tensor.size(1) == 4, \ diff --git a/torchvision/ops/boxes.py b/torchvision/ops/boxes.py index c7d74db4500..6183d259212 100644 --- a/torchvision/ops/boxes.py +++ b/torchvision/ops/boxes.py @@ -4,8 +4,7 @@ import torchvision -def nms(boxes, scores, iou_threshold): - # type: (Tensor, Tensor, float) -> Tensor +def nms(boxes: Tensor, scores: Tensor, iou_threshold: float) -> Tensor: """ Performs non-maximum suppression (NMS) on the boxes according to their intersection-over-union (IoU). @@ -41,8 +40,12 @@ def nms(boxes, scores, iou_threshold): @torch.jit._script_if_tracing -def batched_nms(boxes, scores, idxs, iou_threshold): - # type: (Tensor, Tensor, Tensor, float) -> Tensor +def batched_nms( + boxes: Tensor, + scores: Tensor, + idxs: Tensor, + iou_threshold: float, +) -> Tensor: """ Performs non-maximum suppression in a batched fashion. @@ -83,8 +86,7 @@ def batched_nms(boxes, scores, idxs, iou_threshold): return keep -def remove_small_boxes(boxes, min_size): - # type: (Tensor, float) -> Tensor +def remove_small_boxes(boxes: Tensor, min_size: float) -> Tensor: """ Remove boxes which contains at least one side smaller than min_size. @@ -102,8 +104,7 @@ def remove_small_boxes(boxes, min_size): return keep -def clip_boxes_to_image(boxes, size): - # type: (Tensor, Tuple[int, int]) -> Tensor +def clip_boxes_to_image(boxes: Tensor, size: Tuple[int, int]) -> Tensor: """ Clip boxes so that they lie inside an image of size `size`. @@ -132,7 +133,7 @@ def clip_boxes_to_image(boxes, size): return clipped_boxes.reshape(boxes.shape) -def box_area(boxes): +def box_area(boxes: Tensor) -> Tensor: """ Computes the area of a set of bounding boxes, which are specified by its (x1, y1, x2, y2) coordinates. @@ -149,7 +150,7 @@ def box_area(boxes): # implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py # with slight modifications -def box_iou(boxes1, boxes2): +def box_iou(boxes1: Tensor, boxes2: Tensor) -> Tensor: """ Return intersection-over-union (Jaccard index) of boxes. diff --git a/torchvision/ops/deform_conv.py b/torchvision/ops/deform_conv.py index c948b164196..aa5e42c4a6e 100644 --- a/torchvision/ops/deform_conv.py +++ b/torchvision/ops/deform_conv.py @@ -8,8 +8,15 @@ from torch.jit.annotations import Optional, Tuple -def deform_conv2d(input, offset, weight, bias=None, stride=(1, 1), padding=(0, 0), dilation=(1, 1)): - # type: (Tensor, Tensor, Tensor, Optional[Tensor], Tuple[int, int], Tuple[int, int], Tuple[int, int]) -> Tensor +def deform_conv2d( + input: Tensor, + offset: Tensor, + weight: Tensor, + bias: Optional[Tensor] = None, + stride: Tuple[int, int] = (1, 1), + padding: Tuple[int, int] = (0, 0), + dilation: Tuple[int, int] = (1, 1), +) -> Tensor: """ Performs Deformable Convolution, described in Deformable Convolutional Networks @@ -80,8 +87,17 @@ class DeformConv2d(nn.Module): """ See deform_conv2d """ - def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, - dilation=1, groups=1, bias=True): + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: int, + stride: int = 1, + padding: int = 0, + dilation: int = 1, + groups: int = 1, + bias: bool = True, + ): super(DeformConv2d, self).__init__() if in_channels % groups != 0: @@ -107,14 +123,14 @@ def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, self.reset_parameters() - def reset_parameters(self): + def reset_parameters(self) -> None: init.kaiming_uniform_(self.weight, a=math.sqrt(5)) if self.bias is not None: fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight) bound = 1 / math.sqrt(fan_in) init.uniform_(self.bias, -bound, bound) - def forward(self, input, offset): + def forward(self, input: Tensor, offset: Tensor) -> Tensor: """ Arguments: input (Tensor[batch_size, in_channels, in_height, in_width]): input tensor @@ -125,7 +141,7 @@ def forward(self, input, offset): return deform_conv2d(input, offset, self.weight, self.bias, stride=self.stride, padding=self.padding, dilation=self.dilation) - def __repr__(self): + def __repr__(self) -> str: s = self.__class__.__name__ + '(' s += '{in_channels}' s += ', {out_channels}' diff --git a/torchvision/ops/feature_pyramid_network.py b/torchvision/ops/feature_pyramid_network.py index a2d8c409490..979bbfb1c10 100644 --- a/torchvision/ops/feature_pyramid_network.py +++ b/torchvision/ops/feature_pyramid_network.py @@ -4,7 +4,31 @@ import torch.nn.functional as F from torch import nn, Tensor -from torch.jit.annotations import Tuple, List, Dict +from torch.jit.annotations import Tuple, List, Dict, Optional + + +class ExtraFPNBlock(nn.Module): + """ + Base class for the extra block in the FPN. + + Arguments: + results (List[Tensor]): the result of the FPN + x (List[Tensor]): the original feature maps + names (List[str]): the names for each one of the + original feature maps + + Returns: + results (List[Tensor]): the extended set of results + of the FPN + names (List[str]): the extended set of names for the results + """ + def forward( + self, + results: List[Tensor], + x: List[Tensor], + names: List[str], + ) -> Tuple[List[Tensor], List[str]]: + pass class FeaturePyramidNetwork(nn.Module): @@ -44,7 +68,12 @@ class FeaturePyramidNetwork(nn.Module): >>> ('feat3', torch.Size([1, 5, 8, 8]))] """ - def __init__(self, in_channels_list, out_channels, extra_blocks=None): + def __init__( + self, + in_channels_list: List[int], + out_channels: int, + extra_blocks: Optional[ExtraFPNBlock] = None, + ): super(FeaturePyramidNetwork, self).__init__() self.inner_blocks = nn.ModuleList() self.layer_blocks = nn.ModuleList() @@ -66,8 +95,7 @@ def __init__(self, in_channels_list, out_channels, extra_blocks=None): assert isinstance(extra_blocks, ExtraFPNBlock) self.extra_blocks = extra_blocks - def get_result_from_inner_blocks(self, x, idx): - # type: (Tensor, int) -> Tensor + def get_result_from_inner_blocks(self, x: Tensor, idx: int) -> Tensor: """ This is equivalent to self.inner_blocks[idx](x), but torchscript doesn't support this yet @@ -85,8 +113,7 @@ def get_result_from_inner_blocks(self, x, idx): i += 1 return out - def get_result_from_layer_blocks(self, x, idx): - # type: (Tensor, int) -> Tensor + def get_result_from_layer_blocks(self, x: Tensor, idx: int) -> Tensor: """ This is equivalent to self.layer_blocks[idx](x), but torchscript doesn't support this yet @@ -104,8 +131,7 @@ def get_result_from_layer_blocks(self, x, idx): i += 1 return out - def forward(self, x): - # type: (Dict[str, Tensor]) -> Dict[str, Tensor] + def forward(self, x: Dict[str, Tensor]) -> Dict[str, Tensor]: """ Computes the FPN for a set of feature maps. @@ -140,31 +166,16 @@ def forward(self, x): return out -class ExtraFPNBlock(nn.Module): - """ - Base class for the extra block in the FPN. - - Arguments: - results (List[Tensor]): the result of the FPN - x (List[Tensor]): the original feature maps - names (List[str]): the names for each one of the - original feature maps - - Returns: - results (List[Tensor]): the extended set of results - of the FPN - names (List[str]): the extended set of names for the results - """ - def forward(self, results, x, names): - pass - - class LastLevelMaxPool(ExtraFPNBlock): """ Applies a max_pool2d on top of the last feature map """ - def forward(self, x, y, names): - # type: (List[Tensor], List[Tensor], List[str]) -> Tuple[List[Tensor], List[str]] + def forward( + self, + x: List[Tensor], + y: List[Tensor], + names: List[str], + ) -> Tuple[List[Tensor], List[str]]: names.append("pool") x.append(F.max_pool2d(x[-1], 1, 2, 0)) return x, names @@ -174,7 +185,7 @@ class LastLevelP6P7(ExtraFPNBlock): """ This module is used in RetinaNet to generate extra layers, P6 and P7. """ - def __init__(self, in_channels, out_channels): + def __init__(self, in_channels: int, out_channels: int): super(LastLevelP6P7, self).__init__() self.p6 = nn.Conv2d(in_channels, out_channels, 3, 2, 1) self.p7 = nn.Conv2d(out_channels, out_channels, 3, 2, 1) @@ -183,7 +194,12 @@ def __init__(self, in_channels, out_channels): nn.init.constant_(module.bias, 0) self.use_P5 = in_channels == out_channels - def forward(self, p, c, names): + def forward( + self, + p: List[Tensor], + c: List[Tensor], + names: List[str], + ) -> Tuple[List[Tensor], List[str]]: p5, c5 = p[-1], c[-1] x = p5 if self.use_P5 else c5 p6 = self.p6(x) diff --git a/torchvision/ops/misc.py b/torchvision/ops/misc.py index 61fab3edd7a..a0f15653145 100644 --- a/torchvision/ops/misc.py +++ b/torchvision/ops/misc.py @@ -10,6 +10,8 @@ import warnings import torch +from torch import Tensor, Size +from torch.jit.annotations import List, Optional, Tuple class Conv2d(torch.nn.Conv2d): @@ -46,7 +48,12 @@ class FrozenBatchNorm2d(torch.nn.Module): are fixed """ - def __init__(self, num_features, eps=0., n=None): + def __init__( + self, + num_features: Tuple[int, ...], + eps: float = 0., + n: Optional[Tuple[int, ...]] = None, + ): # n=None for backward-compatibility if n is not None: warnings.warn("`n` argument is deprecated and has been renamed `num_features`", @@ -59,8 +66,16 @@ def __init__(self, num_features, eps=0., n=None): self.register_buffer("running_mean", torch.zeros(num_features)) self.register_buffer("running_var", torch.ones(num_features)) - def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, - missing_keys, unexpected_keys, error_msgs): + def _load_from_state_dict( + self, + state_dict: dict, + prefix: str, + local_metadata: dict, + strict: bool, + missing_keys: List[str], + unexpected_keys: List[str], + error_msgs: List[str], + ): num_batches_tracked_key = prefix + 'num_batches_tracked' if num_batches_tracked_key in state_dict: del state_dict[num_batches_tracked_key] @@ -69,7 +84,7 @@ def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs) - def forward(self, x): + def forward(self, x: Tensor) -> Tensor: # move reshapes to the beginning # to make it fuser-friendly w = self.weight.reshape(1, -1, 1, 1) @@ -80,5 +95,5 @@ def forward(self, x): bias = b - rm * scale return x * scale + bias - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.weight.shape[0]})" diff --git a/torchvision/ops/new_empty_tensor.py b/torchvision/ops/new_empty_tensor.py index 74455a98c4f..e964e7a7e15 100644 --- a/torchvision/ops/new_empty_tensor.py +++ b/torchvision/ops/new_empty_tensor.py @@ -3,8 +3,7 @@ from torch import Tensor -def _new_empty_tensor(x, shape): - # type: (Tensor, List[int]) -> Tensor +def _new_empty_tensor(x: Tensor, shape: List[int]) -> Tensor: """ Arguments: input (Tensor): input tensor diff --git a/torchvision/ops/poolers.py b/torchvision/ops/poolers.py index 06bbc86a93c..adaedb6e4c3 100644 --- a/torchvision/ops/poolers.py +++ b/torchvision/ops/poolers.py @@ -15,8 +15,7 @@ # _onnx_merge_levels() is an implementation supported by ONNX # that merges the levels to the right indices @torch.jit.unused -def _onnx_merge_levels(levels, unmerged_results): - # type: (Tensor, List[Tensor]) -> Tensor +def _onnx_merge_levels(levels: Tensor, unmerged_results: List[Tensor]) -> Tensor: first_result = unmerged_results[0] dtype, device = first_result.dtype, first_result.device res = torch.zeros((levels.size(0), first_result.size(1), @@ -33,8 +32,13 @@ def _onnx_merge_levels(levels, unmerged_results): # TODO: (eellison) T54974082 https://github.com/pytorch/pytorch/issues/26744/pytorch/issues/26744 -def initLevelMapper(k_min, k_max, canonical_scale=224, canonical_level=4, eps=1e-6): - # type: (int, int, int, int, float) -> LevelMapper +def initLevelMapper( + k_min: int, + k_max: int, + canonical_scale: int = 224, + canonical_level: int = 4, + eps: float = 1e-6, +): return LevelMapper(k_min, k_max, canonical_scale, canonical_level, eps) @@ -50,16 +54,21 @@ class LevelMapper(object): eps (float) """ - def __init__(self, k_min, k_max, canonical_scale=224, canonical_level=4, eps=1e-6): - # type: (int, int, int, int, float) -> None + def __init__( + self, + k_min: int, + k_max: int, + canonical_scale: int = 224, + canonical_level: int = 4, + eps: float = 1e-6, + ): self.k_min = k_min self.k_max = k_max self.s0 = canonical_scale self.lvl0 = canonical_level self.eps = eps - def __call__(self, boxlists): - # type: (List[Tensor]) -> Tensor + def __call__(self, boxlists: List[Tensor]) -> Tensor: """ Arguments: boxlists (list[BoxList]) @@ -107,7 +116,12 @@ class MultiScaleRoIAlign(nn.Module): 'map_levels': Optional[LevelMapper] } - def __init__(self, featmap_names, output_size, sampling_ratio): + def __init__( + self, + featmap_names: List[str], + output_size: List[int], + sampling_ratio: int, + ): super(MultiScaleRoIAlign, self).__init__() if isinstance(output_size, int): output_size = (output_size, output_size) @@ -117,8 +131,7 @@ def __init__(self, featmap_names, output_size, sampling_ratio): self.scales = None self.map_levels = None - def convert_to_roi_format(self, boxes): - # type: (List[Tensor]) -> Tensor + def convert_to_roi_format(self, boxes: List[Tensor]) -> Tensor: concat_boxes = torch.cat(boxes, dim=0) device, dtype = concat_boxes.device, concat_boxes.dtype ids = torch.cat( @@ -131,8 +144,7 @@ def convert_to_roi_format(self, boxes): rois = torch.cat([ids, concat_boxes], dim=1) return rois - def infer_scale(self, feature, original_size): - # type: (Tensor, List[int]) -> float + def infer_scale(self, feature: Tensor, original_size: List[int]) -> float: # assumption: the scale is of the form 2 ** (-k), with k integer size = feature.shape[-2:] possible_scales = torch.jit.annotate(List[float], []) @@ -143,8 +155,11 @@ def infer_scale(self, feature, original_size): assert possible_scales[0] == possible_scales[1] return possible_scales[0] - def setup_scales(self, features, image_shapes): - # type: (List[Tensor], List[Tuple[int, int]]) -> None + def setup_scales( + self, + features: List[Tensor], + image_shapes: List[Tuple[int, int]], + ) -> None: assert len(image_shapes) != 0 max_x = 0 max_y = 0 @@ -161,8 +176,12 @@ def setup_scales(self, features, image_shapes): self.scales = scales self.map_levels = initLevelMapper(int(lvl_min), int(lvl_max)) - def forward(self, x, boxes, image_shapes): - # type: (Dict[str, Tensor], List[Tensor], List[Tuple[int, int]]) -> Tensor + def forward( + self, + x: Dict[str, Tensor], + boxes: List[Tensor], + image_shapes: List[Tuple[int, int]], + ) -> Tensor: """ Arguments: x (OrderedDict[Tensor]): feature maps for each level. They are assumed to have diff --git a/torchvision/ops/ps_roi_align.py b/torchvision/ops/ps_roi_align.py index c0c761b72cc..49ee0c21fac 100644 --- a/torchvision/ops/ps_roi_align.py +++ b/torchvision/ops/ps_roi_align.py @@ -2,13 +2,18 @@ from torch import nn, Tensor from torch.nn.modules.utils import _pair -from torch.jit.annotations import List +from torch.jit.annotations import List, Tuple from ._utils import convert_boxes_to_roi_format, check_roi_boxes_shape -def ps_roi_align(input, boxes, output_size, spatial_scale=1.0, sampling_ratio=-1): - # type: (Tensor, Tensor, int, float, int) -> Tensor +def ps_roi_align( + input: Tensor, + boxes: Tensor, + output_size: int, + spatial_scale: float = 1.0, + sampling_ratio: int = -1, +) -> Tensor: """ Performs Position-Sensitive Region of Interest (RoI) Align operator mentioned in Light-Head R-CNN. @@ -49,17 +54,22 @@ class PSRoIAlign(nn.Module): """ See ps_roi_align """ - def __init__(self, output_size, spatial_scale, sampling_ratio): + def __init__( + self, + output_size: int, + spatial_scale: float, + sampling_ratio: int, + ): super(PSRoIAlign, self).__init__() self.output_size = output_size self.spatial_scale = spatial_scale self.sampling_ratio = sampling_ratio - def forward(self, input, rois): + def forward(self, input: Tensor, rois: Tensor) -> Tensor: return ps_roi_align(input, rois, self.output_size, self.spatial_scale, self.sampling_ratio) - def __repr__(self): + def __repr__(self) -> str: tmpstr = self.__class__.__name__ + '(' tmpstr += 'output_size=' + str(self.output_size) tmpstr += ', spatial_scale=' + str(self.spatial_scale) diff --git a/torchvision/ops/ps_roi_pool.py b/torchvision/ops/ps_roi_pool.py index 710f2cb0195..58c8aa2742a 100644 --- a/torchvision/ops/ps_roi_pool.py +++ b/torchvision/ops/ps_roi_pool.py @@ -2,13 +2,17 @@ from torch import nn, Tensor from torch.nn.modules.utils import _pair -from torch.jit.annotations import List +from torch.jit.annotations import List, Tuple from ._utils import convert_boxes_to_roi_format, check_roi_boxes_shape -def ps_roi_pool(input, boxes, output_size, spatial_scale=1.0): - # type: (Tensor, Tensor, int, float) -> Tensor +def ps_roi_pool( + input: Tensor, + boxes: Tensor, + output_size: int, + spatial_scale: float = 1.0, +) -> Tensor: """ Performs Position-Sensitive Region of Interest (RoI) Pool operator described in R-FCN @@ -43,15 +47,15 @@ class PSRoIPool(nn.Module): """ See ps_roi_pool """ - def __init__(self, output_size, spatial_scale): + def __init__(self, output_size: int, spatial_scale: float): super(PSRoIPool, self).__init__() self.output_size = output_size self.spatial_scale = spatial_scale - def forward(self, input, rois): + def forward(self, input: Tensor, rois: Tensor) -> Tensor: return ps_roi_pool(input, rois, self.output_size, self.spatial_scale) - def __repr__(self): + def __repr__(self) -> str: tmpstr = self.__class__.__name__ + '(' tmpstr += 'output_size=' + str(self.output_size) tmpstr += ', spatial_scale=' + str(self.spatial_scale) diff --git a/torchvision/ops/roi_align.py b/torchvision/ops/roi_align.py index 14224d8a83e..444f0d7addb 100644 --- a/torchvision/ops/roi_align.py +++ b/torchvision/ops/roi_align.py @@ -7,8 +7,14 @@ from ._utils import convert_boxes_to_roi_format, check_roi_boxes_shape -def roi_align(input, boxes, output_size, spatial_scale=1.0, sampling_ratio=-1, aligned=False): - # type: (Tensor, Tensor, BroadcastingList2[int], float, int, bool) -> Tensor +def roi_align( + input: Tensor, + boxes: Tensor, + output_size: BroadcastingList2[int], + spatial_scale: float = 1.0, + sampling_ratio: int = -1, + aligned: bool = False, +) -> Tensor: """ Performs Region of Interest (RoI) Align operator described in Mask R-CNN @@ -49,17 +55,23 @@ class RoIAlign(nn.Module): """ See roi_align """ - def __init__(self, output_size, spatial_scale, sampling_ratio, aligned=False): + def __init__( + self, + output_size: BroadcastingList2[int], + spatial_scale: float, + sampling_ratio: int, + aligned: bool = False, + ): super(RoIAlign, self).__init__() self.output_size = output_size self.spatial_scale = spatial_scale self.sampling_ratio = sampling_ratio self.aligned = aligned - def forward(self, input, rois): + def forward(self, input: Tensor, rois: Tensor) -> Tensor: return roi_align(input, rois, self.output_size, self.spatial_scale, self.sampling_ratio, self.aligned) - def __repr__(self): + def __repr__(self) -> str: tmpstr = self.__class__.__name__ + '(' tmpstr += 'output_size=' + str(self.output_size) tmpstr += ', spatial_scale=' + str(self.spatial_scale) diff --git a/torchvision/ops/roi_pool.py b/torchvision/ops/roi_pool.py index 10232f16b4a..5a71e90d7e7 100644 --- a/torchvision/ops/roi_pool.py +++ b/torchvision/ops/roi_pool.py @@ -7,8 +7,12 @@ from ._utils import convert_boxes_to_roi_format, check_roi_boxes_shape -def roi_pool(input, boxes, output_size, spatial_scale=1.0): - # type: (Tensor, Tensor, BroadcastingList2[int], float) -> Tensor +def roi_pool( + input: Tensor, + boxes: Tensor, + output_size: BroadcastingList2[int], + spatial_scale: float = 1.0, +) -> Tensor: """ Performs Region of Interest (RoI) Pool operator described in Fast R-CNN @@ -41,15 +45,15 @@ class RoIPool(nn.Module): """ See roi_pool """ - def __init__(self, output_size, spatial_scale): + def __init__(self, output_size: BroadcastingList2[int], spatial_scale: float): super(RoIPool, self).__init__() self.output_size = output_size self.spatial_scale = spatial_scale - def forward(self, input, rois): + def forward(self, input: Tensor, rois: Tensor) -> Tensor: return roi_pool(input, rois, self.output_size, self.spatial_scale) - def __repr__(self): + def __repr__(self) -> str: tmpstr = self.__class__.__name__ + '(' tmpstr += 'output_size=' + str(self.output_size) tmpstr += ', spatial_scale=' + str(self.spatial_scale) From e212cc86b80baf1a46681442db1312ebce5a21bb Mon Sep 17 00:00:00 2001 From: vfdev Date: Mon, 6 Jul 2020 14:51:21 +0200 Subject: [PATCH 31/51] Unified input for resize op (#2394) * [WIP] F.resize with tensor * Adapted T.Resize and F.resize with a test * According to the review, fixed copy-pasted messages and unused imports --- test/test_functional_tensor.py | 61 ++++++++++++-- test/test_transforms_tensor.py | 28 +++++++ torchvision/transforms/functional.py | 38 +++------ torchvision/transforms/functional_pil.py | 43 +++++++++- torchvision/transforms/functional_tensor.py | 91 +++++++++++++++++++++ torchvision/transforms/transforms.py | 27 +++--- 6 files changed, 246 insertions(+), 42 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index 89af6dce5d7..cd3ae5a0a82 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -1,14 +1,17 @@ -import torch -import torchvision.transforms as transforms -import torchvision.transforms.functional_tensor as F_t -import torchvision.transforms.functional_pil as F_pil -import torchvision.transforms.functional as F -import numpy as np import unittest import random import colorsys from PIL import Image +from PIL.Image import NEAREST, BILINEAR, BICUBIC + +import numpy as np + +import torch +import torchvision.transforms as transforms +import torchvision.transforms.functional_tensor as F_t +import torchvision.transforms.functional_pil as F_pil +import torchvision.transforms.functional as F class Tester(unittest.TestCase): @@ -22,6 +25,14 @@ def compareTensorToPIL(self, tensor, pil_image, msg=None): pil_tensor = torch.as_tensor(np.array(pil_image).transpose((2, 0, 1))) self.assertTrue(tensor.equal(pil_tensor), msg) + def approxEqualTensorToPIL(self, tensor, pil_image, tol=1e-5, msg=None): + pil_tensor = torch.as_tensor(np.array(pil_image).transpose((2, 0, 1))).to(tensor) + mae = torch.abs(tensor - pil_tensor).mean().item() + self.assertTrue( + mae < tol, + msg="{}: mae={}, tol={}: \n{}\nvs\n{}".format(msg, mae, tol, tensor[0, :10, :10], pil_tensor[0, :10, :10]) + ) + def test_vflip(self): script_vflip = torch.jit.script(F_t.vflip) img_tensor = torch.randn(3, 16, 16) @@ -282,6 +293,44 @@ def test_pad(self): with self.assertRaises(ValueError, msg="Padding can not be negative for symmetric padding_mode"): F_t.pad(tensor, (-2, -3), padding_mode="symmetric") + def test_resize(self): + script_fn = torch.jit.script(F_t.resize) + tensor, pil_img = self._create_data(26, 36) + + for dt in [None, torch.float32, torch.float64]: + if dt is not None: + # This is a trivial cast to float of uint8 data to test all cases + tensor = tensor.to(dt) + for size in [32, [32, ], [32, 32], (32, 32), ]: + for interpolation in [BILINEAR, BICUBIC, NEAREST]: + resized_tensor = F_t.resize(tensor, size=size, interpolation=interpolation) + resized_pil_img = F_pil.resize(pil_img, size=size, interpolation=interpolation) + + self.assertEqual( + resized_tensor.size()[1:], resized_pil_img.size[::-1], msg="{}, {}".format(size, interpolation) + ) + + if interpolation != NEAREST: + # We can not check values if mode = NEAREST, as results are different + # E.g. resized_tensor = [[a, a, b, c, d, d, e, ...]] + # E.g. resized_pil_img = [[a, b, c, c, d, e, f, ...]] + resized_tensor_f = resized_tensor + # we need to cast to uint8 to compare with PIL image + if resized_tensor_f.dtype == torch.uint8: + resized_tensor_f = resized_tensor_f.to(torch.float) + + # Pay attention to high tolerance for MAE + self.approxEqualTensorToPIL( + resized_tensor_f, resized_pil_img, tol=8.0, msg="{}, {}".format(size, interpolation) + ) + + if isinstance(size, int): + script_size = [size, ] + else: + script_size = size + pad_tensor_script = script_fn(tensor, size=script_size, interpolation=interpolation) + self.assertTrue(resized_tensor.equal(pad_tensor_script), msg="{}, {}".format(size, interpolation)) + if __name__ == '__main__': unittest.main() diff --git a/test/test_transforms_tensor.py b/test/test_transforms_tensor.py index 6a8d9930754..9d70744dfc1 100644 --- a/test/test_transforms_tensor.py +++ b/test/test_transforms_tensor.py @@ -2,6 +2,7 @@ from torchvision import transforms as T from torchvision.transforms import functional as F from PIL import Image +from PIL.Image import NEAREST, BILINEAR, BICUBIC import numpy as np @@ -217,6 +218,33 @@ def test_ten_crop(self): "ten_crop", "TenCrop", out_length=10, fn_kwargs=fn_kwargs, meth_kwargs=meth_kwargs ) + def test_resize(self): + tensor, _ = self._create_data(height=34, width=36) + script_fn = torch.jit.script(F.resize) + + for dt in [None, torch.float32, torch.float64]: + if dt is not None: + # This is a trivial cast to float of uint8 data to test all cases + tensor = tensor.to(dt) + for size in [32, [32, ], [32, 32], (32, 32), ]: + for interpolation in [BILINEAR, BICUBIC, NEAREST]: + + resized_tensor = F.resize(tensor, size=size, interpolation=interpolation) + + if isinstance(size, int): + script_size = [size, ] + else: + script_size = size + + s_resized_tensor = script_fn(tensor, size=script_size, interpolation=interpolation) + self.assertTrue(s_resized_tensor.equal(resized_tensor)) + + transform = T.Resize(size=script_size, interpolation=interpolation) + resized_tensor = transform(tensor) + script_transform = torch.jit.script(transform) + s_resized_tensor = script_transform(tensor) + self.assertTrue(s_resized_tensor.equal(resized_tensor)) + if __name__ == '__main__': unittest.main() diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 9c7efe0ef53..72ca54d7260 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -311,41 +311,29 @@ def normalize(tensor, mean, std, inplace=False): return tensor -def resize(img, size, interpolation=Image.BILINEAR): - r"""Resize the input PIL Image to the given size. +def resize(img: Tensor, size: List[int], interpolation: int = 2) -> Tensor: + r"""Resize the input image to the given size. + The image can be a PIL Image or a torch Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions Args: - img (PIL Image): Image to be resized. + img (PIL Image or Tensor): Image to be resized. size (sequence or int): Desired output size. If size is a sequence like (h, w), the output size will be matched to this. If size is an int, the smaller edge of the image will be matched to this number maintaining the aspect ratio. i.e, if height > width, then image will be rescaled to - :math:`\left(\text{size} \times \frac{\text{height}}{\text{width}}, \text{size}\right)` - interpolation (int, optional): Desired interpolation. Default is - ``PIL.Image.BILINEAR`` + :math:`\left(\text{size} \times \frac{\text{height}}{\text{width}}, \text{size}\right)`. + In torchscript mode padding as single int is not supported, use a tuple or + list of length 1: ``[size, ]``. + interpolation (int, optional): Desired interpolation. Default is bilinear. Returns: - PIL Image: Resized image. + PIL Image or Tensor: Resized image. """ - if not F_pil._is_pil_image(img): - raise TypeError('img should be PIL Image. Got {}'.format(type(img))) - if not (isinstance(size, int) or (isinstance(size, Iterable) and len(size) == 2)): - raise TypeError('Got inappropriate size arg: {}'.format(size)) + if not isinstance(img, torch.Tensor): + return F_pil.resize(img, size=size, interpolation=interpolation) - if isinstance(size, int): - w, h = img.size - if (w <= h and w == size) or (h <= w and h == size): - return img - if w < h: - ow = size - oh = int(size * h / w) - return img.resize((ow, oh), interpolation) - else: - oh = size - ow = int(size * w / h) - return img.resize((ow, oh), interpolation) - else: - return img.resize(size[::-1], interpolation) + return F_t.resize(img, size=size, interpolation=interpolation) def scale(*args, **kwargs): diff --git a/torchvision/transforms/functional_pil.py b/torchvision/transforms/functional_pil.py index f1bcda113aa..994988ce1f6 100644 --- a/torchvision/transforms/functional_pil.py +++ b/torchvision/transforms/functional_pil.py @@ -1,5 +1,5 @@ import numbers -from typing import Any, List +from typing import Any, List, Sequence import torch try: @@ -286,3 +286,44 @@ def crop(img: Image.Image, top: int, left: int, height: int, width: int) -> Imag raise TypeError('img should be PIL Image. Got {}'.format(type(img))) return img.crop((left, top, left + width, top + height)) + + +@torch.jit.unused +def resize(img, size, interpolation=Image.BILINEAR): + r"""Resize the input PIL Image to the given size. + + Args: + img (PIL Image): Image to be resized. + size (sequence or int): Desired output size. If size is a sequence like + (h, w), the output size will be matched to this. If size is an int, + the smaller edge of the image will be matched to this number maintaining + the aspect ratio. i.e, if height > width, then image will be rescaled to + :math:`\left(\text{size} \times \frac{\text{height}}{\text{width}}, \text{size}\right)`. + For compatibility reasons with ``functional_tensor.resize``, if a tuple or list of length 1 is provided, + it is interpreted as a single int. + interpolation (int, optional): Desired interpolation. Default is ``PIL.Image.BILINEAR``. + + Returns: + PIL Image: Resized image. + """ + if not _is_pil_image(img): + raise TypeError('img should be PIL Image. Got {}'.format(type(img))) + if not (isinstance(size, int) or (isinstance(size, Sequence) and len(size) in (1, 2))): + raise TypeError('Got inappropriate size arg: {}'.format(size)) + + if isinstance(size, int) or len(size) == 1: + if isinstance(size, Sequence): + size = size[0] + w, h = img.size + if (w <= h and w == size) or (h <= w and h == size): + return img + if w < h: + ow = size + oh = int(size * h / w) + return img.resize((ow, oh), interpolation) + else: + oh = size + ow = int(size * w / h) + return img.resize((ow, oh), interpolation) + else: + return img.resize(size[::-1], interpolation) diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index 8b64abe9f9c..be0b7b3a622 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -8,6 +8,7 @@ def _is_tensor_a_torch_image(x: Tensor) -> bool: def _get_image_size(img: Tensor) -> List[int]: + """Returns (w, h) of tensor image""" if _is_tensor_a_torch_image(img): return [img.shape[-1], img.shape[-2]] raise TypeError("Unexpected type {}".format(type(img))) @@ -433,6 +434,7 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con if isinstance(padding, int): if torch.jit.is_scripting(): + # This maybe unreachable raise ValueError("padding can't be an int while torchscripting, set it as a list [value, ]") pad_left = pad_right = pad_top = pad_bottom = padding elif len(padding) == 1: @@ -480,3 +482,92 @@ def pad(img: Tensor, padding: List[int], fill: int = 0, padding_mode: str = "con img = img.to(out_dtype) return img + + +def resize(img: Tensor, size: List[int], interpolation: int = 2) -> Tensor: + r"""Resize the input Tensor to the given size. + + Args: + img (Tensor): Image to be resized. + size (int or tuple or list): Desired output size. If size is a sequence like + (h, w), the output size will be matched to this. If size is an int, + the smaller edge of the image will be matched to this number maintaining + the aspect ratio. i.e, if height > width, then image will be rescaled to + :math:`\left(\text{size} \times \frac{\text{height}}{\text{width}}, \text{size}\right)`. + In torchscript mode padding as a single int is not supported, use a tuple or + list of length 1: ``[size, ]``. + interpolation (int, optional): Desired interpolation. Default is bilinear. + + Returns: + Tensor: Resized image. + """ + if not _is_tensor_a_torch_image(img): + raise TypeError("tensor is not a torch image.") + + if not isinstance(size, (int, tuple, list)): + raise TypeError("Got inappropriate size arg") + if not isinstance(interpolation, int): + raise TypeError("Got inappropriate interpolation arg") + + _interpolation_modes = { + 0: "nearest", + 2: "bilinear", + 3: "bicubic", + } + + if interpolation not in _interpolation_modes: + raise ValueError("This interpolation mode is unsupported with Tensor input") + + if isinstance(size, tuple): + size = list(size) + + if isinstance(size, list) and len(size) not in [1, 2]: + raise ValueError("Size must be an int or a 1 or 2 element tuple/list, not a " + "{} element tuple/list".format(len(size))) + + w, h = _get_image_size(img) + + if isinstance(size, int): + size_w, size_h = size, size + elif len(size) < 2: + size_w, size_h = size[0], size[0] + else: + size_w, size_h = size[0], size[1] + + if isinstance(size, int) or len(size) < 2: + if w < h: + size_h = int(size_w * h / w) + else: + size_w = int(size_h * w / h) + + if (w <= h and w == size_w) or (h <= w and h == size_h): + return img + + # make image NCHW + need_squeeze = False + if img.ndim < 4: + img = img.unsqueeze(dim=0) + need_squeeze = True + + mode = _interpolation_modes[interpolation] + + out_dtype = img.dtype + need_cast = False + if img.dtype not in (torch.float32, torch.float64): + need_cast = True + img = img.to(torch.float32) + + # Define align_corners to avoid warnings + align_corners = False if mode in ["bilinear", "bicubic"] else None + + img = torch.nn.functional.interpolate(img, size=(size_h, size_w), mode=mode, align_corners=align_corners) + + if need_squeeze: + img = img.squeeze(dim=0) + + if need_cast: + if mode == "bicubic": + img = img.clamp(min=0, max=255) + img = img.to(out_dtype) + + return img diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index 6bc9e7cbc4d..9f4ad8175c6 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -2,7 +2,7 @@ import numbers import random import warnings -from collections.abc import Sequence, Iterable +from collections.abc import Sequence from typing import Tuple, List, Optional import numpy as np @@ -209,31 +209,38 @@ def __repr__(self): return self.__class__.__name__ + '(mean={0}, std={1})'.format(self.mean, self.std) -class Resize(object): - """Resize the input PIL Image to the given size. +class Resize(torch.nn.Module): + """Resize the input image to the given size. + The image can be a PIL Image or a torch Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions Args: size (sequence or int): Desired output size. If size is a sequence like (h, w), output size will be matched to this. If size is an int, smaller edge of the image will be matched to this number. i.e, if height > width, then image will be rescaled to - (size * height / width, size) - interpolation (int, optional): Desired interpolation. Default is - ``PIL.Image.BILINEAR`` + (size * height / width, size). + In torchscript mode padding as single int is not supported, use a tuple or + list of length 1: ``[size, ]``. + interpolation (int, optional): Desired interpolation. Default is ``PIL.Image.BILINEAR`` """ def __init__(self, size, interpolation=Image.BILINEAR): - assert isinstance(size, int) or (isinstance(size, Iterable) and len(size) == 2) + super().__init__() + if not isinstance(size, (int, Sequence)): + raise TypeError("Size should be int or sequence. Got {}".format(type(size))) + if isinstance(size, Sequence) and len(size) not in (1, 2): + raise ValueError("If size is a sequence, it should have 1 or 2 values") self.size = size self.interpolation = interpolation - def __call__(self, img): + def forward(self, img): """ Args: - img (PIL Image): Image to be scaled. + img (PIL Image or Tensor): Image to be scaled. Returns: - PIL Image: Rescaled image. + PIL Image or Tensor: Rescaled image. """ return F.resize(img, self.size, self.interpolation) From d96832aceb54a3aced9d975d796eb5513f321a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Mon, 6 Jul 2020 15:54:41 -0500 Subject: [PATCH 32/51] PR: Improve calls to libpng-config on Ubuntu/Debian (#2398) * Improve detection of libpng-config on Ubuntu/Debian * Do not disable libdir on Mac --- setup.py | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 01276f1893d..198c14bfbf1 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ import io import re import sys +import csv from setuptools import setup, find_packages from pkg_resources import parse_version, get_distribution, DistributionNotFound import subprocess @@ -135,6 +136,26 @@ def find_library(name, vision_include): return library_found, conda_installed, include_folder, lib_folder +def get_linux_distribution(): + release_data = {} + with open("/etc/os-release") as f: + reader = csv.reader(f, delimiter="=") + for row in reader: + if row: + release_data[row[0]] = row[1] + if release_data["ID"] in ["debian", "raspbian"]: + with open("/etc/debian_version") as f: + debian_version = f.readline().strip() + major_version = debian_version.split(".")[0] + version_split = release_data["VERSION"].split(" ", maxsplit=1) + if version_split[0] == major_version: + # Just major version shown, replace it with the full version + release_data["VERSION"] = " ".join( + [debian_version] + version_split[1:]) + print("{} {}".format(release_data["NAME"], release_data["VERSION"])) + return release_data + + def get_extensions(): this_dir = os.path.dirname(os.path.abspath(__file__)) extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') @@ -246,6 +267,14 @@ def get_extensions(): image_library = [] image_link_flags = [] + # Detect if build is running under conda/conda-build + conda = distutils.spawn.find_executable('conda') + is_conda = conda is not None + + build_prefix = os.environ.get('BUILD_PREFIX', None) + is_conda_build = build_prefix is not None + running_under_conda = is_conda or is_conda_build + # Locating libPNG libpng = distutils.spawn.find_executable('libpng-config') pngfix = distutils.spawn.find_executable('pngfix') @@ -262,14 +291,26 @@ def get_extensions(): png_version = parse_version(png_version) if png_version >= parse_version("1.6.0"): print('Building torchvision with PNG image support') - png_lib = subprocess.run([libpng, '--libdir'], - stdout=subprocess.PIPE) + linux = sys.platform == 'linux' + not_debian = False + libpng_on_conda = False + if linux: + bin_folder = os.path.dirname(sys.executable) + png_bin_folder = os.path.dirname(libpng) + libpng_on_conda = ( + running_under_conda and bin_folder == png_bin_folder) + release_info = get_linux_distribution() + not_debian = release_info["NAME"] not in {'Ubuntu', 'Debian'} + if not linux or libpng_on_conda or not_debian: + png_lib = subprocess.run([libpng, '--libdir'], + stdout=subprocess.PIPE) + png_lib = png_lib.stdout.strip().decode('utf-8') + image_library += [png_lib] png_include = subprocess.run([libpng, '--I_opts'], stdout=subprocess.PIPE) png_include = png_include.stdout.strip().decode('utf-8') _, png_include = png_include.split('-I') print('libpng include path: {0}'.format(png_include)) - image_library += [png_lib.stdout.strip().decode('utf-8')] image_include += [png_include] image_link_flags.append('png') else: From 86b6c3e22e9d7d8b0fa25d08704e6a31a364973b Mon Sep 17 00:00:00 2001 From: Max Frei <36265931+maxfrei750@users.noreply.github.com> Date: Mon, 6 Jul 2020 22:55:54 +0200 Subject: [PATCH 33/51] Removed obsolete import. (#2399) --- references/segmentation/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/references/segmentation/utils.py b/references/segmentation/utils.py index d9251b72b9f..b67c18052fb 100644 --- a/references/segmentation/utils.py +++ b/references/segmentation/utils.py @@ -1,6 +1,5 @@ from collections import defaultdict, deque import datetime -import math import time import torch import torch.distributed as dist From b572d5e61f785b877d2a04489768fca9ba135e3c Mon Sep 17 00:00:00 2001 From: vfdev Date: Tue, 7 Jul 2020 16:27:09 +0200 Subject: [PATCH 34/51] Fixed torch randint incoherent sampling (compatible to random.randint) (#2401) --- torchvision/transforms/transforms.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index 9f4ad8175c6..442c8dbcb1c 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -510,8 +510,8 @@ def get_params(img: Tensor, output_size: Tuple[int, int]) -> Tuple[int, int, int if w == tw and h == th: return 0, 0, h, w - i = torch.randint(0, h - th, size=(1, )).item() - j = torch.randint(0, w - tw, size=(1, )).item() + i = torch.randint(0, h - th + 1, size=(1, )).item() + j = torch.randint(0, w - tw + 1, size=(1, )).item() return i, j, th, tw def __init__(self, size, padding=None, pad_if_needed=False, fill=0, padding_mode="constant"): @@ -1433,8 +1433,8 @@ def get_params( else: v = torch.tensor(value)[:, None, None] - i = torch.randint(0, img_h - h, size=(1, )).item() - j = torch.randint(0, img_w - w, size=(1, )).item() + i = torch.randint(0, img_h - h + 1, size=(1, )).item() + j = torch.randint(0, img_w - w + 1, size=(1, )).item() return i, j, h, w, v # Return original image From 9b804659e1accdafc5cf3872ccbcdd1330844dbe Mon Sep 17 00:00:00 2001 From: vfdev Date: Tue, 7 Jul 2020 16:30:11 +0200 Subject: [PATCH 35/51] Unified input for resized crop op (#2396) * [WIP] Unify random resized crop * Unify input for RandomResizedCrop * Fixed bugs and updated test * Added resized crop functional test - fixed bug with size convention * Fixed incoherent sampling * Fixed torch randint review remark --- test/test_functional_tensor.py | 17 ++++++ test/test_transforms_tensor.py | 19 ++++++ torchvision/transforms/functional.py | 16 +++--- torchvision/transforms/functional_tensor.py | 2 +- torchvision/transforms/transforms.py | 64 +++++++++++++-------- 5 files changed, 87 insertions(+), 31 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index cd3ae5a0a82..95f7383a4f7 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -331,6 +331,23 @@ def test_resize(self): pad_tensor_script = script_fn(tensor, size=script_size, interpolation=interpolation) self.assertTrue(resized_tensor.equal(pad_tensor_script), msg="{}, {}".format(size, interpolation)) + def test_resized_crop(self): + # test values of F.resized_crop in several cases: + # 1) resize to the same size, crop to the same size => should be identity + tensor, _ = self._create_data(26, 36) + for i in [0, 2, 3]: + out_tensor = F.resized_crop(tensor, top=0, left=0, height=26, width=36, size=[26, 36], interpolation=i) + self.assertTrue(tensor.equal(out_tensor), msg="{} vs {}".format(out_tensor[0, :5, :5], tensor[0, :5, :5])) + + # 2) resize by half and crop a TL corner + tensor, _ = self._create_data(26, 36) + out_tensor = F.resized_crop(tensor, top=0, left=0, height=20, width=30, size=[10, 15], interpolation=0) + expected_out_tensor = tensor[:, :20:2, :30:2] + self.assertTrue( + expected_out_tensor.equal(out_tensor), + msg="{} vs {}".format(expected_out_tensor[0, :10, :10], out_tensor[0, :10, :10]) + ) + if __name__ == '__main__': unittest.main() diff --git a/test/test_transforms_tensor.py b/test/test_transforms_tensor.py index 9d70744dfc1..fbd3331a490 100644 --- a/test/test_transforms_tensor.py +++ b/test/test_transforms_tensor.py @@ -245,6 +245,25 @@ def test_resize(self): s_resized_tensor = script_transform(tensor) self.assertTrue(s_resized_tensor.equal(resized_tensor)) + def test_resized_crop(self): + tensor = torch.randint(0, 255, size=(3, 44, 56), dtype=torch.uint8) + + scale = (0.7, 1.2) + ratio = (0.75, 1.333) + + for size in [(32, ), [32, ], [32, 32], (32, 32)]: + for interpolation in [NEAREST, BILINEAR, BICUBIC]: + transform = T.RandomResizedCrop( + size=size, scale=scale, ratio=ratio, interpolation=interpolation + ) + s_transform = torch.jit.script(transform) + + torch.manual_seed(12) + out1 = transform(tensor) + torch.manual_seed(12) + out2 = s_transform(tensor) + self.assertTrue(out1.equal(out2)) + if __name__ == '__main__': unittest.main() diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 72ca54d7260..4b38c7bb92e 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -439,24 +439,26 @@ def center_crop(img: Tensor, output_size: List[int]) -> Tensor: return crop(img, crop_top, crop_left, crop_height, crop_width) -def resized_crop(img, top, left, height, width, size, interpolation=Image.BILINEAR): - """Crop the given PIL Image and resize it to desired size. +def resized_crop( + img: Tensor, top: int, left: int, height: int, width: int, size: List[int], interpolation: int = Image.BILINEAR +) -> Tensor: + """Crop the given image and resize it to desired size. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions Notably used in :class:`~torchvision.transforms.RandomResizedCrop`. Args: - img (PIL Image): Image to be cropped. (0,0) denotes the top left corner of the image. + img (PIL Image or Tensor): Image to be cropped. (0,0) denotes the top left corner of the image. top (int): Vertical component of the top left corner of the crop box. left (int): Horizontal component of the top left corner of the crop box. height (int): Height of the crop box. width (int): Width of the crop box. size (sequence or int): Desired output size. Same semantics as ``resize``. - interpolation (int, optional): Desired interpolation. Default is - ``PIL.Image.BILINEAR``. + interpolation (int, optional): Desired interpolation. Default is ``PIL.Image.BILINEAR``. Returns: - PIL Image: Cropped image. + PIL Image or Tensor: Cropped image. """ - assert F_pil._is_pil_image(img), 'img should be PIL Image' img = crop(img, top, left, height, width) img = resize(img, size, interpolation) return img diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index be0b7b3a622..59cf6bc2764 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -532,7 +532,7 @@ def resize(img: Tensor, size: List[int], interpolation: int = 2) -> Tensor: elif len(size) < 2: size_w, size_h = size[0], size[0] else: - size_w, size_h = size[0], size[1] + size_w, size_h = size[1], size[0] # Convention (h, w) if isinstance(size, int) or len(size) < 2: if w < h: diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index 442c8dbcb1c..a403c54261f 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -687,8 +687,10 @@ def __repr__(self): return self.__class__.__name__ + '(p={})'.format(self.p) -class RandomResizedCrop(object): - """Crop the given PIL Image to random size and aspect ratio. +class RandomResizedCrop(torch.nn.Module): + """Crop the given image to random size and aspect ratio. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions A crop of random size (default: of 0.08 to 1.0) of the original size and a random aspect ratio (default: of 3/4 to 4/3) of the original aspect ratio is made. This crop @@ -696,31 +698,45 @@ class RandomResizedCrop(object): This is popularly used to train the Inception networks. Args: - size: expected output size of each edge - scale: range of size of the origin size cropped - ratio: range of aspect ratio of the origin aspect ratio cropped - interpolation: Default: PIL.Image.BILINEAR + size (int or sequence): expected output size of each edge. If size is an + int instead of sequence like (h, w), a square output size ``(size, size)`` is + made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). + scale (tuple of float): range of size of the origin size cropped + ratio (tuple of float): range of aspect ratio of the origin aspect ratio cropped. + interpolation (int): Desired interpolation. Default: ``PIL.Image.BILINEAR`` """ def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Image.BILINEAR): - if isinstance(size, (tuple, list)): - self.size = size + super().__init__() + if isinstance(size, numbers.Number): + self.size = (int(size), int(size)) + elif isinstance(size, Sequence) and len(size) == 1: + self.size = (size[0], size[0]) else: - self.size = (size, size) + if len(size) != 2: + raise ValueError("Please provide only two dimensions (h, w) for size.") + self.size = size + + if not isinstance(scale, (tuple, list)): + raise TypeError("Scale should be a sequence") + if not isinstance(ratio, (tuple, list)): + raise TypeError("Ratio should be a sequence") if (scale[0] > scale[1]) or (ratio[0] > ratio[1]): - warnings.warn("range should be of kind (min, max)") + warnings.warn("Scale and ratio should be of kind (min, max)") self.interpolation = interpolation self.scale = scale self.ratio = ratio @staticmethod - def get_params(img, scale, ratio): + def get_params( + img: Tensor, scale: Tuple[float, float], ratio: Tuple[float, float] + ) -> Tuple[int, int, int, int]: """Get parameters for ``crop`` for a random sized crop. Args: - img (PIL Image): Image to be cropped. - scale (tuple): range of size of the origin size cropped + img (PIL Image or Tensor): Input image. + scale (tuple): range of scale of the origin size cropped ratio (tuple): range of aspect ratio of the origin aspect ratio cropped Returns: @@ -731,24 +747,26 @@ def get_params(img, scale, ratio): area = height * width for _ in range(10): - target_area = random.uniform(*scale) * area - log_ratio = (math.log(ratio[0]), math.log(ratio[1])) - aspect_ratio = math.exp(random.uniform(*log_ratio)) + target_area = area * torch.empty(1).uniform_(*scale).item() + log_ratio = torch.log(torch.tensor(ratio)) + aspect_ratio = torch.exp( + torch.empty(1).uniform_(log_ratio[0], log_ratio[1]) + ).item() w = int(round(math.sqrt(target_area * aspect_ratio))) h = int(round(math.sqrt(target_area / aspect_ratio))) if 0 < w <= width and 0 < h <= height: - i = random.randint(0, height - h) - j = random.randint(0, width - w) + i = torch.randint(0, height - h + 1, size=(1,)).item() + j = torch.randint(0, width - w + 1, size=(1,)).item() return i, j, h, w # Fallback to central crop in_ratio = float(width) / float(height) - if (in_ratio < min(ratio)): + if in_ratio < min(ratio): w = width h = int(round(w / min(ratio))) - elif (in_ratio > max(ratio)): + elif in_ratio > max(ratio): h = height w = int(round(h * max(ratio))) else: # whole image @@ -758,13 +776,13 @@ def get_params(img, scale, ratio): j = (width - w) // 2 return i, j, h, w - def __call__(self, img): + def forward(self, img): """ Args: - img (PIL Image): Image to be cropped and resized. + img (PIL Image or Tensor): Image to be cropped and resized. Returns: - PIL Image: Randomly cropped and resized image. + PIL Image or Tensor: Randomly cropped and resized image. """ i, j, h, w = self.get_params(img, self.scale, self.ratio) return F.resized_crop(img, i, j, h, w, self.size, self.interpolation) From 4433a5b2e7570ca86b08e0c229e3ac5bed8046bb Mon Sep 17 00:00:00 2001 From: vfdev Date: Wed, 8 Jul 2020 11:21:01 +0200 Subject: [PATCH 36/51] Minor docs improvement (#2403) * Minor docs improvement * Replaced link by already defined `filters`_ --- torchvision/transforms/functional.py | 10 +++++++--- torchvision/transforms/transforms.py | 8 ++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 4b38c7bb92e..801df42a187 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -311,7 +311,7 @@ def normalize(tensor, mean, std, inplace=False): return tensor -def resize(img: Tensor, size: List[int], interpolation: int = 2) -> Tensor: +def resize(img: Tensor, size: List[int], interpolation: int = Image.BILINEAR) -> Tensor: r"""Resize the input image to the given size. The image can be a PIL Image or a torch Tensor, in which case it is expected to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions @@ -325,7 +325,9 @@ def resize(img: Tensor, size: List[int], interpolation: int = 2) -> Tensor: :math:`\left(\text{size} \times \frac{\text{height}}{\text{width}}, \text{size}\right)`. In torchscript mode padding as single int is not supported, use a tuple or list of length 1: ``[size, ]``. - interpolation (int, optional): Desired interpolation. Default is bilinear. + interpolation (int, optional): Desired interpolation enum defined by `filters`_. + Default is ``PIL.Image.BILINEAR``. If input is Tensor, only ``PIL.Image.NEAREST``, ``PIL.Image.BILINEAR`` + and ``PIL.Image.BICUBIC`` are supported. Returns: PIL Image or Tensor: Resized image. @@ -455,7 +457,9 @@ def resized_crop( height (int): Height of the crop box. width (int): Width of the crop box. size (sequence or int): Desired output size. Same semantics as ``resize``. - interpolation (int, optional): Desired interpolation. Default is ``PIL.Image.BILINEAR``. + interpolation (int, optional): Desired interpolation enum defined by `filters`_. + Default is ``PIL.Image.BILINEAR``. If input is Tensor, only ``PIL.Image.NEAREST``, ``PIL.Image.BILINEAR`` + and ``PIL.Image.BICUBIC`` are supported. Returns: PIL Image or Tensor: Cropped image. """ diff --git a/torchvision/transforms/transforms.py b/torchvision/transforms/transforms.py index a403c54261f..f7d421d2b83 100644 --- a/torchvision/transforms/transforms.py +++ b/torchvision/transforms/transforms.py @@ -222,7 +222,9 @@ class Resize(torch.nn.Module): (size * height / width, size). In torchscript mode padding as single int is not supported, use a tuple or list of length 1: ``[size, ]``. - interpolation (int, optional): Desired interpolation. Default is ``PIL.Image.BILINEAR`` + interpolation (int, optional): Desired interpolation enum defined by `filters`_. + Default is ``PIL.Image.BILINEAR``. If input is Tensor, only ``PIL.Image.NEAREST``, ``PIL.Image.BILINEAR`` + and ``PIL.Image.BICUBIC`` are supported. """ def __init__(self, size, interpolation=Image.BILINEAR): @@ -703,7 +705,9 @@ class RandomResizedCrop(torch.nn.Module): made. If provided a tuple or list of length 1, it will be interpreted as (size[0], size[0]). scale (tuple of float): range of size of the origin size cropped ratio (tuple of float): range of aspect ratio of the origin aspect ratio cropped. - interpolation (int): Desired interpolation. Default: ``PIL.Image.BILINEAR`` + interpolation (int): Desired interpolation enum defined by `filters`_. + Default is ``PIL.Image.BILINEAR``. If input is Tensor, only ``PIL.Image.NEAREST``, ``PIL.Image.BILINEAR`` + and ``PIL.Image.BICUBIC`` are supported. """ def __init__(self, size, scale=(0.08, 1.0), ratio=(3. / 4., 4. / 3.), interpolation=Image.BILINEAR): From 4246abc897aa4547b76bad51357178e5911b803d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 8 Jul 2020 12:58:56 -0500 Subject: [PATCH 37/51] PR: Read JPEG images directly (#2388) * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Add PNG reading capabilities * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Add PNG reading capabilities * Add libpng requirement into conda recipe * Try to install libjpeg-turbo * Remove newline * Add image extension to compilation instructions * Include png functions as part of the main library * Update CMakeLists * Detect if building on conda-build * Debug * More debug messages * Print globbed libreries * Print globbed libreries * Point to correct PNG path * Remove libJPEG preventively * Debug extension loading * Link libpng explicitly * Link with PNG * Install libpng on conda-based wheel distributions * Add -y flag * Add -y flag to yum * Locate LibPNG on windows conda * Remove empty else * Copy libpng16.so * Copy dylib on Mac * Improve check on Windows * Try to install ninja using conda on windows * Use libpng on Windows * Package lib on windows wheel * Point library to the correct place * Include binaries as part of wheel * Copy libpng.so on linux * Look for png.h on Windows when using conda-build * Do not skip png tests on Mac/Win * Restore libjpeg-turbo * Install jpeg-turbo on wheel distributions * Install libjpeg-turbo from conda-forge on wheel distributions * Do not pull av on conda-build * Add pillow disclaimer * Vendors libjpeg-turbo 2.0.4 * Merge JPEG work * Remove submodules * Regenerate circle config * Fix style issues * Fix C++ style issues * More style corrections * Add JPEG-turbo to linking libraries * More style corrections * More style corrections * More style corrections * Install libjpeg-turbo-devel * Install libturbo-jpeg on typing pipeline * Update Circle template * Windows and Unix turbojpeg have the same linking name * Install turbojpeg-devel instead of libjpeg-turbo * Copy TurboJPEG binaries to wheel * Move test image * Move back test image * Update JPEG test path * Remove dot from extension * Move image functions to extension * Use stdout arg in subprocess * Disable image extension if libpng or turbojpeg are not found * Append libpng stdout * Prevent list appending on lists * Minor path correction * Minor error correction * Add linking flags * Style issues correction * Address minor review corrections * Refactor library search * Restore access index * Fix JPEG tests * Update libpng version in Travis * Add -y flag * Remove dot * Update libpng using apt * Check libpng version * Change libturbojpeg binary * Update import * Change call * Restore av in conda recipe * Minor error correction * Remove unused comment in travis.yml * Update README * Fix missing links * Remove fixes for 16.04 * Enable JPEG support using libjpeg directly * Install libjpeg-turbo8 on Travis * Fix styling issues * Do not append to paths if library found in standard library locations * Add macro flag * Use custom error handler * Use 3? * Use short-lived buffer * Return TRUE instead of true * Assert RuntimeError * Use .jpg extension * Remove conda-forge * Use data_ptr instead of accessor * Use assertTrue for jpeg verification * Remove unnecessary memcpy * Debug test on Windows * Remove PIL from jpeg verification Co-authored-by: Ryad ZENINE --- .circleci/config.yml | 2 + .circleci/config.yml.in | 2 + .../unittest/linux/scripts/environment.yml | 1 + .../unittest/windows/scripts/environment.yml | 1 + .travis.yml | 2 +- CMakeLists.txt | 5 +- README.rst | 6 +- packaging/build_wheel.sh | 5 + packaging/pkg_helpers.bash | 4 +- packaging/torchvision/meta.yaml | 2 + setup.py | 15 +- test/assets/grace_hopper_517x606.pth | Bin 0 -> 940664 bytes test/test_image.py | 24 ++- torchvision/csrc/cpu/image/image.cpp | 5 +- torchvision/csrc/cpu/image/image.h | 1 + torchvision/csrc/cpu/image/readjpeg_cpu.cpp | 140 ++++++++++++++++++ torchvision/csrc/cpu/image/readjpeg_cpu.h | 5 + torchvision/io/__init__.py | 2 +- torchvision/io/image.py | 41 +++++ 19 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 test/assets/grace_hopper_517x606.pth create mode 100644 torchvision/csrc/cpu/image/readjpeg_cpu.cpp create mode 100644 torchvision/csrc/cpu/image/readjpeg_cpu.h diff --git a/.circleci/config.yml b/.circleci/config.yml index 29b5fc77aab..f3fc23b7c92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,6 +107,8 @@ jobs: - checkout - run: command: | + sudo apt-get update -y + sudo apt install -y libturbojpeg-dev pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/config.yml.in b/.circleci/config.yml.in index d9bd257eae6..f63c3f408ba 100644 --- a/.circleci/config.yml.in +++ b/.circleci/config.yml.in @@ -107,6 +107,8 @@ jobs: - checkout - run: command: | + sudo apt-get update -y + sudo apt install -y libturbojpeg-dev pip install --user --progress-bar off numpy mypy pip install --user --progress-bar off --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html pip install --user --progress-bar off --editable . diff --git a/.circleci/unittest/linux/scripts/environment.yml b/.circleci/unittest/linux/scripts/environment.yml index 2b3604ee1c8..96b66319ed6 100644 --- a/.circleci/unittest/linux/scripts/environment.yml +++ b/.circleci/unittest/linux/scripts/environment.yml @@ -7,6 +7,7 @@ dependencies: - codecov - pip - libpng + - jpeg - ca-certificates - pip: - future diff --git a/.circleci/unittest/windows/scripts/environment.yml b/.circleci/unittest/windows/scripts/environment.yml index ddbf7445a92..49795f73bc3 100644 --- a/.circleci/unittest/windows/scripts/environment.yml +++ b/.circleci/unittest/windows/scripts/environment.yml @@ -7,6 +7,7 @@ dependencies: - codecov - pip - libpng + - jpeg - ca-certificates - pip: - future diff --git a/.travis.yml b/.travis.yml index d8c45c2defe..f5656f926f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ jobs: before_install: - sudo apt-get update - - sudo apt-get install -y libpng-dev + - sudo apt-get install -y libpng-dev libjpeg-turbo8-dev - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - bash miniconda.sh -b -p $HOME/miniconda - export PATH="$HOME/miniconda/bin:$PATH" diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d28bc8a4c5..5d2e86291f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,7 @@ find_package(Python3 COMPONENTS Development) find_package(Torch REQUIRED) find_package(PNG REQUIRED) +find_package(JPEG REQUIRED) file(GLOB HEADERS torchvision/csrc/*.h) @@ -28,12 +29,12 @@ file(GLOB MODELS_HEADERS torchvision/csrc/models/*.h) file(GLOB MODELS_SOURCES torchvision/csrc/models/*.h torchvision/csrc/models/*.cpp) add_library(${PROJECT_NAME} SHARED ${MODELS_SOURCES} ${OPERATOR_SOURCES} ${IMAGE_SOURCES}) -target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} ${PNG_LIBRARY} Python3::Python) +target_link_libraries(${PROJECT_NAME} PRIVATE ${TORCH_LIBRARIES} ${PNG_LIBRARY} ${JPEG_LIBRARIES} Python3::Python) # target_link_libraries(${PROJECT_NAME} PRIVATE ${PNG_LIBRARY} Python3::Python) set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME TorchVision) target_include_directories(${PROJECT_NAME} INTERFACE - $ + $ $) include(GNUInstallDirs) diff --git a/README.rst b/README.rst index 0de404c74b3..1d55e9387c3 100644 --- a/README.rst +++ b/README.rst @@ -80,13 +80,17 @@ Torchvision currently supports the following image backends: * `libpng`_ - can be installed via conda :code:`conda install libpng` or any of the package managers for debian-based and RHEL-based Linux distributions. -**Notes:** ``libpng`` must be available at compilation time in order to be available. Make sure that it is available on the standard library locations, +* `libjpeg`_ - can be installed via conda :code:`conda install jpeg` or any of the package managers for debian-based and RHEL-based Linux distributions. `libjpeg-turbo`_ can be used as well. + +**Notes:** ``libpng`` and ``libjpeg`` must be available at compilation time in order to be available. Make sure that it is available on the standard library locations, otherwise, add the include and library paths in the environment variables ``TORCHVISION_INCLUDE`` and ``TORCHVISION_LIBRARY``, respectively. .. _libpng : http://www.libpng.org/pub/png/libpng.html .. _Pillow : https://python-pillow.org/ .. _Pillow-SIMD : https://github.com/uploadcare/pillow-simd .. _accimage: https://github.com/pytorch/accimage +.. _libjpeg: http://ijg.org/ +.. _libjpeg-turbo: https://libjpeg-turbo.org/ C++ API ======= diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh index 1a6e1b1761a..043d2ed7ea9 100755 --- a/packaging/build_wheel.sh +++ b/packaging/build_wheel.sh @@ -19,12 +19,17 @@ if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then if [[ "$(uname)" == Darwin ]]; then # Include LibPNG cp "$env_path/lib/libpng16.dylib" torchvision + # Include LibJPEG + cp "$env_path/lib/libjpeg.dylib" torchvision else cp "$bin_path/Library/bin/libpng16.dll" torchvision + cp "$bin_path/Library/bin/libjpeg.dll" torchvision fi else # Include LibPNG cp "/usr/lib64/libpng.so" torchvision + # Include LibJPEG + cp "/usr/lib64/libjpeg.so" torchvision fi if [[ "$OSTYPE" == "msys" ]]; then diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash index 128d0b51913..ff7abdab7fb 100644 --- a/packaging/pkg_helpers.bash +++ b/packaging/pkg_helpers.bash @@ -171,10 +171,10 @@ setup_wheel_python() { conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" conda activate "env$PYTHON_VERSION" # Install libpng from Anaconda (defaults) - conda install libpng -y + conda install libpng jpeg -y else # Install native CentOS libPNG - yum install -y libpng-devel + yum install -y libpng-devel libjpeg-turbo-devel case "$PYTHON_VERSION" in 2.7) if [[ -n "$UNICODE_ABI" ]]; then diff --git a/packaging/torchvision/meta.yaml b/packaging/torchvision/meta.yaml index b3fc2a2e9df..1b61464f01e 100644 --- a/packaging/torchvision/meta.yaml +++ b/packaging/torchvision/meta.yaml @@ -9,6 +9,7 @@ requirements: build: - {{ compiler('c') }} # [win] - libpng + - jpeg host: - python @@ -20,6 +21,7 @@ requirements: run: - python - libpng + - jpeg - pillow >=4.1.1 - numpy >=1.11 {{ environ.get('CONDA_PYTORCH_CONSTRAINT') }} diff --git a/setup.py b/setup.py index 198c14bfbf1..e1587996362 100644 --- a/setup.py +++ b/setup.py @@ -327,10 +327,23 @@ def get_extensions(): image_include += [png_include] image_link_flags.append('libpng') + # Locating libjpeg + (jpeg_found, jpeg_conda, + jpeg_include, jpeg_lib) = find_library('jpeglib', vision_include) + + print('JPEG found: {0}'.format(jpeg_found)) + image_macros += [('JPEG_FOUND', str(int(jpeg_found)))] + if jpeg_found: + print('Building torchvision with JPEG image support') + image_link_flags.append('jpeg') + if jpeg_conda: + image_library += [jpeg_lib] + image_include += [jpeg_include] + image_path = os.path.join(extensions_dir, 'cpu', 'image') image_src = glob.glob(os.path.join(image_path, '*.cpp')) - if png_found: + if png_found or jpeg_found: ext_modules.append(extension( 'torchvision.image', image_src, diff --git a/test/assets/grace_hopper_517x606.pth b/test/assets/grace_hopper_517x606.pth new file mode 100644 index 0000000000000000000000000000000000000000..54b39dc0cd7d7a5689d42260e9bcce9d31854819 GIT binary patch literal 940664 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
  • +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

    5` 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�PQ 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 literal 0 HcmV?d00001 diff --git a/test/test_image.py b/test/test_image.py index a7f660127b8..09a591d19ef 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -5,7 +5,7 @@ import torch import torchvision from PIL import Image -from torchvision.io.image import read_png, decode_png +from torchvision.io.image import read_png, decode_png, read_jpeg, decode_jpeg import numpy as np IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") @@ -22,6 +22,28 @@ def get_images(directory, img_ext): class ImageTester(unittest.TestCase): + def test_read_jpeg(self): + for img_path in get_images(IMAGE_ROOT, ".jpg"): + img_pil = torch.load(img_path.replace('jpg', 'pth')) + img_ljpeg = read_jpeg(img_path) + self.assertTrue(img_ljpeg.equal(img_pil)) + + def test_decode_jpeg(self): + for img_path in get_images(IMAGE_ROOT, ".jpg"): + img_pil = torch.load(img_path.replace('jpg', 'pth')) + size = os.path.getsize(img_path) + img_ljpeg = decode_jpeg(torch.from_file(img_path, dtype=torch.uint8, size=size)) + self.assertTrue(img_ljpeg.equal(img_pil)) + + with self.assertRaisesRegex(ValueError, "Expected a non empty 1-dimensional tensor."): + decode_jpeg(torch.empty((100, 1), dtype=torch.uint8)) + + with self.assertRaisesRegex(ValueError, "Expected a torch.uint8 tensor."): + decode_jpeg(torch.empty((100, ), dtype=torch.float16)) + + with self.assertRaises(RuntimeError): + decode_jpeg(torch.empty((100), dtype=torch.uint8)) + def test_read_png(self): # Check across .png for img_path in get_images(IMAGE_DIR, ".png"): diff --git a/torchvision/csrc/cpu/image/image.cpp b/torchvision/csrc/cpu/image/image.cpp index 0dc82a69827..5efe53b02b7 100644 --- a/torchvision/csrc/cpu/image/image.cpp +++ b/torchvision/csrc/cpu/image/image.cpp @@ -12,5 +12,6 @@ PyMODINIT_FUNC PyInit_image(void) { } #endif -static auto registry = - torch::RegisterOperators().op("image::decode_png", &decodePNG); +static auto registry = torch::RegisterOperators() + .op("image::decode_png", &decodePNG) + .op("image::decode_jpeg", &decodeJPEG); diff --git a/torchvision/csrc/cpu/image/image.h b/torchvision/csrc/cpu/image/image.h index f5b86cf683b..324ecea8a28 100644 --- a/torchvision/csrc/cpu/image/image.h +++ b/torchvision/csrc/cpu/image/image.h @@ -4,4 +4,5 @@ // Comment #include #include +#include "readjpeg_cpu.h" #include "readpng_cpu.h" diff --git a/torchvision/csrc/cpu/image/readjpeg_cpu.cpp b/torchvision/csrc/cpu/image/readjpeg_cpu.cpp new file mode 100644 index 00000000000..4954b2c2474 --- /dev/null +++ b/torchvision/csrc/cpu/image/readjpeg_cpu.cpp @@ -0,0 +1,140 @@ +#include "readjpeg_cpu.h" + +#include +#include +#include + +#if !JPEG_FOUND + +torch::Tensor decodeJPEG(const torch::Tensor& data) { + AT_ERROR("decodeJPEG: torchvision not compiled with libjpeg support"); +} + +#else +#include + +const static JOCTET EOI_BUFFER[1] = {JPEG_EOI}; +char jpegLastErrorMsg[JMSG_LENGTH_MAX]; + +struct torch_jpeg_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; + +typedef struct torch_jpeg_error_mgr* torch_jpeg_error_ptr; + +void torch_jpeg_error_exit(j_common_ptr cinfo) { + /* cinfo->err really points to a torch_jpeg_error_mgr struct, so coerce + * pointer */ + torch_jpeg_error_ptr myerr = (torch_jpeg_error_ptr)cinfo->err; + + /* Always display the message. */ + /* We could postpone this until after returning, if we chose. */ + // (*cinfo->err->output_message)(cinfo); + /* Create the message */ + (*(cinfo->err->format_message))(cinfo, jpegLastErrorMsg); + + /* Return control to the setjmp point */ + longjmp(myerr->setjmp_buffer, 1); +} + +struct torch_jpeg_mgr { + struct jpeg_source_mgr pub; + const JOCTET* data; + size_t len; +}; + +static void torch_jpeg_init_source(j_decompress_ptr cinfo) {} + +static boolean torch_jpeg_fill_input_buffer(j_decompress_ptr cinfo) { + torch_jpeg_mgr* src = (torch_jpeg_mgr*)cinfo->src; + // No more data. Probably an incomplete image; just output EOI. + src->pub.next_input_byte = EOI_BUFFER; + src->pub.bytes_in_buffer = 1; + return TRUE; +} + +static void torch_jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + torch_jpeg_mgr* src = (torch_jpeg_mgr*)cinfo->src; + if (src->pub.bytes_in_buffer < num_bytes) { + // Skipping over all of remaining data; output EOI. + src->pub.next_input_byte = EOI_BUFFER; + src->pub.bytes_in_buffer = 1; + } else { + // Skipping over only some of the remaining data. + src->pub.next_input_byte += num_bytes; + src->pub.bytes_in_buffer -= num_bytes; + } +} + +static void torch_jpeg_term_source(j_decompress_ptr cinfo) {} + +static void torch_jpeg_set_source_mgr( + j_decompress_ptr cinfo, + const unsigned char* data, + size_t len) { + torch_jpeg_mgr* src; + if (cinfo->src == 0) { // if this is first time; allocate memory + cinfo->src = (struct jpeg_source_mgr*)(*cinfo->mem->alloc_small)( + (j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(torch_jpeg_mgr)); + } + src = (torch_jpeg_mgr*)cinfo->src; + src->pub.init_source = torch_jpeg_init_source; + src->pub.fill_input_buffer = torch_jpeg_fill_input_buffer; + src->pub.skip_input_data = torch_jpeg_skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; // default + src->pub.term_source = torch_jpeg_term_source; + // fill the buffers + src->data = (const JOCTET*)data; + src->len = len; + src->pub.bytes_in_buffer = len; + src->pub.next_input_byte = src->data; +} + +torch::Tensor decodeJPEG(const torch::Tensor& data) { + struct jpeg_decompress_struct cinfo; + struct torch_jpeg_error_mgr jerr; + + auto datap = data.data_ptr(); + // Setup decompression structure + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = torch_jpeg_error_exit; + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp(jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object. + */ + jpeg_destroy_decompress(&cinfo); + AT_ERROR(jpegLastErrorMsg); + } + + jpeg_create_decompress(&cinfo); + torch_jpeg_set_source_mgr(&cinfo, datap, data.numel()); + + // read info from header. + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + int height = cinfo.output_height; + int width = cinfo.output_width; + int components = cinfo.output_components; + + auto stride = width * components; + auto tensor = torch::empty( + {int64_t(height), int64_t(width), int64_t(components)}, torch::kU8); + auto ptr = tensor.data_ptr(); + while (cinfo.output_scanline < cinfo.output_height) { + /* jpeg_read_scanlines expects an array of pointers to scanlines. + * Here the array is only one element long, but you could ask for + * more than one scanline at a time if that's more convenient. + */ + jpeg_read_scanlines(&cinfo, &ptr, 1); + ptr += stride; + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + return tensor; +} + +#endif // JPEG_FOUND diff --git a/torchvision/csrc/cpu/image/readjpeg_cpu.h b/torchvision/csrc/cpu/image/readjpeg_cpu.h new file mode 100644 index 00000000000..40404df29b5 --- /dev/null +++ b/torchvision/csrc/cpu/image/readjpeg_cpu.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +torch::Tensor decodeJPEG(const torch::Tensor& data); diff --git a/torchvision/io/__init__.py b/torchvision/io/__init__.py index cbbf560412e..4c47d8a51d5 100644 --- a/torchvision/io/__init__.py +++ b/torchvision/io/__init__.py @@ -30,5 +30,5 @@ "_read_video_clip_from_memory", "_read_video_meta_data", "VideoMetaData", - "Timebase", + "Timebase" ] diff --git a/torchvision/io/image.py b/torchvision/io/image.py index 1ad13ed27ad..8d5da4899ca 100644 --- a/torchvision/io/image.py +++ b/torchvision/io/image.py @@ -66,3 +66,44 @@ def read_png(path): raise ValueError("Expected a non empty file.") data = torch.from_file(path, dtype=torch.uint8, size=size) return decode_png(data) + + +def decode_jpeg(input): + # type: (Tensor) -> Tensor + """ + Decodes a JPEG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + Arguments: + input (Tensor[1]): a one dimensional int8 tensor containing + the raw bytes of the JPEG image. + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not isinstance(input, torch.Tensor) or len(input) == 0 or input.ndim != 1: + raise ValueError("Expected a non empty 1-dimensional tensor.") + + if not input.dtype == torch.uint8: + raise ValueError("Expected a torch.uint8 tensor.") + + output = torch.ops.image.decode_jpeg(input) + return output + + +def read_jpeg(path): + # type: (str) -> Tensor + """ + Reads a JPEG image into a 3 dimensional RGB Tensor. + The values of the output tensor are uint8 between 0 and 255. + Arguments: + path (str): path of the JPEG image. + Returns: + output (Tensor[image_width, image_height, 3]) + """ + if not os.path.isfile(path): + raise ValueError("Expected a valid file path.") + + size = os.path.getsize(path) + if size == 0: + raise ValueError("Expected a non empty file.") + data = torch.from_file(path, dtype=torch.uint8, size=size) + return decode_jpeg(data) From 0a8586c958f164a879f72161c48a9d88c9377a11 Mon Sep 17 00:00:00 2001 From: mcarilli Date: Thu, 9 Jul 2020 04:05:48 -0600 Subject: [PATCH 38/51] [WIP] Allow autocast for 1.6 (#2384) * Fixes Xiao's repro * Ports nms to use full dispatcher * Move HIPGuard to nms_cuda * clang-format * run models in test_models.py on GPU if available * Francisco's comment, also disable cuda model tests to see if CPU alone still passes * cuda tests now pass locally, although still not comparing to saved numerics * add note for thing to ask francisco * Allow cuda and cpu tests to share a data file * ignore suffix if unneeded * Skip autocast numerics checks for a few models * Add roi_align test Co-authored-by: Michael Carilli --- test/common_utils.py | 16 ++- test/test_models.py | 206 +++++++++++++++++++--------- test/test_ops.py | 20 ++- torchvision/csrc/ROIAlign.h | 24 ++++ torchvision/csrc/autocast.h | 28 ++++ torchvision/csrc/cpu/nms_cpu.cpp | 23 +++- torchvision/csrc/cpu/vision_cpu.h | 2 +- torchvision/csrc/cuda/nms_cuda.cu | 37 ++++- torchvision/csrc/cuda/vision_cuda.h | 7 +- torchvision/csrc/nms.h | 55 +++----- torchvision/csrc/vision.cpp | 12 +- torchvision/ops/poolers.py | 9 +- 12 files changed, 314 insertions(+), 125 deletions(-) create mode 100644 torchvision/csrc/autocast.h diff --git a/test/common_utils.py b/test/common_utils.py index 9dbd04f4217..d3b6e97a6dc 100644 --- a/test/common_utils.py +++ b/test/common_utils.py @@ -85,7 +85,7 @@ def is_iterable(obj): class TestCase(unittest.TestCase): precision = 1e-5 - def assertExpected(self, output, subname=None, prec=None): + def assertExpected(self, output, subname=None, prec=None, strip_suffix=None): r""" Test that a python value matches the recorded contents of a file derived from the name of this test and subname. The value must be @@ -96,16 +96,24 @@ def assertExpected(self, output, subname=None, prec=None): If you call this multiple times in a single function, you must give a unique subname each time. + + strip_suffix allows different tests that expect similar numerics, e.g. + "test_xyz_cuda" and "test_xyz_cpu", to use the same pickled data. + test_xyz_cuda would pass strip_suffix="_cuda", test_xyz_cpu would pass + strip_suffix="_cpu", and they would both use a data file name based on + "test_xyz". """ - def remove_prefix(text, prefix): + def remove_prefix_suffix(text, prefix, suffix): if text.startswith(prefix): - return text[len(prefix):] + text = text[len(prefix):] + if suffix is not None and text.endswith(suffix): + text = text[:len(text) - len(suffix)] return text # NB: we take __file__ from the module that defined the test # class, so we place the expect directory where the test script # lives, NOT where test/common_utils.py lives. module_id = self.__class__.__module__ - munged_id = remove_prefix(self.id(), module_id + ".") + munged_id = remove_prefix_suffix(self.id(), module_id + ".", strip_suffix) test_file = os.path.realpath(sys.modules[module_id].__file__) expected_file = os.path.join(os.path.dirname(test_file), "expect", diff --git a/test/test_models.py b/test/test_models.py index 1cee7a90003..faa14f8250e 100644 --- a/test/test_models.py +++ b/test/test_models.py @@ -74,6 +74,26 @@ def get_available_video_models(): } +# The following models exhibit flaky numerics under autocast in _test_*_model harnesses. +# This may be caused by the harness environment (e.g. num classes, input initialization +# via torch.rand), and does not prove autocast is unsuitable when training with real data +# (autocast has been used successfully with real data for some of these models). +# TODO: investigate why autocast numerics are flaky in the harnesses. +# +# For the following models, _test_*_model harnesses skip numerical checks on outputs when +# trying autocast. However, they still try an autocasted forward pass, so they still ensure +# autocast coverage suffices to prevent dtype errors in each model. +autocast_flaky_numerics = ( + "fasterrcnn_resnet50_fpn", + "inception_v3", + "keypointrcnn_resnet50_fpn", + "maskrcnn_resnet50_fpn", + "resnet101", + "resnet152", + "wide_resnet101_2", +) + + class ModelTester(TestCase): def checkModule(self, model, name, args): if name not in script_test_models: @@ -81,65 +101,87 @@ def checkModule(self, model, name, args): unwrapper = script_test_models[name].get('unwrapper', None) return super(ModelTester, self).checkModule(model, args, unwrapper=unwrapper, skip=False) - def _test_classification_model(self, name, input_shape): + def _test_classification_model(self, name, input_shape, dev): set_rng_seed(0) # passing num_class equal to a number other than 1000 helps in making the test # more enforcing in nature model = models.__dict__[name](num_classes=50) - model.eval() - x = torch.rand(input_shape) + model.eval().to(device=dev) + # RNG always on CPU, to ensure x in cuda tests is bitwise identical to x in cpu tests + x = torch.rand(input_shape).to(device=dev) out = model(x) - self.assertExpected(out, prec=0.1) + self.assertExpected(out.cpu(), prec=0.1, strip_suffix="_" + dev) self.assertEqual(out.shape[-1], 50) self.checkModule(model, name, (x,)) - def _test_segmentation_model(self, name): + if dev == "cuda": + with torch.cuda.amp.autocast(): + out = model(x) + # See autocast_flaky_numerics comment at top of file. + if name not in autocast_flaky_numerics: + self.assertExpected(out.cpu(), prec=0.1, strip_suffix="_" + dev) + self.assertEqual(out.shape[-1], 50) + + def _test_segmentation_model(self, name, dev): # passing num_class equal to a number other than 1000 helps in making the test # more enforcing in nature model = models.segmentation.__dict__[name](num_classes=50, pretrained_backbone=False) - model.eval() + model.eval().to(device=dev) input_shape = (1, 3, 300, 300) - x = torch.rand(input_shape) + # RNG always on CPU, to ensure x in cuda tests is bitwise identical to x in cpu tests + x = torch.rand(input_shape).to(device=dev) out = model(x) self.assertEqual(tuple(out["out"].shape), (1, 50, 300, 300)) self.checkModule(model, name, (x,)) - def _test_detection_model(self, name): + if dev == "cuda": + with torch.cuda.amp.autocast(): + out = model(x) + self.assertEqual(tuple(out["out"].shape), (1, 50, 300, 300)) + + def _test_detection_model(self, name, dev): set_rng_seed(0) model = models.detection.__dict__[name](num_classes=50, pretrained_backbone=False) - model.eval() + model.eval().to(device=dev) input_shape = (3, 300, 300) - x = torch.rand(input_shape) + # RNG always on CPU, to ensure x in cuda tests is bitwise identical to x in cpu tests + x = torch.rand(input_shape).to(device=dev) model_input = [x] out = model(model_input) self.assertIs(model_input[0], x) - self.assertEqual(len(out), 1) - def subsample_tensor(tensor): - num_elems = tensor.numel() - num_samples = 20 - if num_elems <= num_samples: - return tensor - - flat_tensor = tensor.flatten() - ith_index = num_elems // num_samples - return flat_tensor[ith_index - 1::ith_index] - - def compute_mean_std(tensor): - # can't compute mean of integral tensor - tensor = tensor.to(torch.double) - mean = torch.mean(tensor) - std = torch.std(tensor) - return {"mean": mean, "std": std} - - # maskrcnn_resnet_50_fpn numerically unstable across platforms, so for now - # compare results with mean and std - if name == "maskrcnn_resnet50_fpn": - test_value = map_nested_tensor_object(out, tensor_map_fn=compute_mean_std) - # mean values are small, use large prec - self.assertExpected(test_value, prec=.01) - else: - self.assertExpected(map_nested_tensor_object(out, tensor_map_fn=subsample_tensor), prec=0.01) + def check_out(out): + self.assertEqual(len(out), 1) + + def subsample_tensor(tensor): + num_elems = tensor.numel() + num_samples = 20 + if num_elems <= num_samples: + return tensor + + flat_tensor = tensor.flatten() + ith_index = num_elems // num_samples + return flat_tensor[ith_index - 1::ith_index] + + def compute_mean_std(tensor): + # can't compute mean of integral tensor + tensor = tensor.to(torch.double) + mean = torch.mean(tensor) + std = torch.std(tensor) + return {"mean": mean, "std": std} + + # maskrcnn_resnet_50_fpn numerically unstable across platforms, so for now + # compare results with mean and std + if name == "maskrcnn_resnet50_fpn": + test_value = map_nested_tensor_object(out, tensor_map_fn=compute_mean_std) + # mean values are small, use large prec + self.assertExpected(test_value, prec=.01, strip_suffix="_" + dev) + else: + self.assertExpected(map_nested_tensor_object(out, tensor_map_fn=subsample_tensor), + prec=0.01, + strip_suffix="_" + dev) + + check_out(out) scripted_model = torch.jit.script(model) scripted_model.eval() @@ -156,6 +198,13 @@ def compute_mean_std(tensor): # self.check_script(model, name) self.checkModule(model, name, ([x],)) + if dev == "cuda": + with torch.cuda.amp.autocast(): + out = model(model_input) + # See autocast_flaky_numerics comment at top of file. + if name not in autocast_flaky_numerics: + check_out(out) + def _test_detection_model_validation(self, name): set_rng_seed(0) model = models.detection.__dict__[name](num_classes=50, pretrained_backbone=False) @@ -179,18 +228,24 @@ def _test_detection_model_validation(self, name): targets = [{'boxes': boxes}] self.assertRaises(ValueError, model, x, targets=targets) - def _test_video_model(self, name): + def _test_video_model(self, name, dev): # the default input shape is # bs * num_channels * clip_len * h *w input_shape = (1, 3, 4, 112, 112) # test both basicblock and Bottleneck model = models.video.__dict__[name](num_classes=50) - model.eval() - x = torch.rand(input_shape) + model.eval().to(device=dev) + # RNG always on CPU, to ensure x in cuda tests is bitwise identical to x in cpu tests + x = torch.rand(input_shape).to(device=dev) out = model(x) self.checkModule(model, name, (x,)) self.assertEqual(out.shape[-1], 50) + if dev == "cuda": + with torch.cuda.amp.autocast(): + out = model(x) + self.assertEqual(out.shape[-1], 50) + def _make_sliced_model(self, model, stop_layer): layers = OrderedDict() for name, layer in model.named_children(): @@ -272,6 +327,12 @@ def test_googlenet_eval(self): @unittest.skipIf(not torch.cuda.is_available(), 'needs GPU') def test_fasterrcnn_switch_devices(self): + def checkOut(out): + self.assertEqual(len(out), 1) + self.assertTrue("boxes" in out[0]) + self.assertTrue("scores" in out[0]) + self.assertTrue("labels" in out[0]) + model = models.detection.fasterrcnn_resnet50_fpn(num_classes=50, pretrained_backbone=False) model.cuda() model.eval() @@ -280,17 +341,20 @@ def test_fasterrcnn_switch_devices(self): model_input = [x] out = model(model_input) self.assertIs(model_input[0], x) - self.assertEqual(len(out), 1) - self.assertTrue("boxes" in out[0]) - self.assertTrue("scores" in out[0]) - self.assertTrue("labels" in out[0]) + + checkOut(out) + + with torch.cuda.amp.autocast(): + out = model(model_input) + + checkOut(out) + # now switch to cpu and make sure it works model.cpu() x = x.cpu() out_cpu = model([x]) - self.assertTrue("boxes" in out_cpu[0]) - self.assertTrue("scores" in out_cpu[0]) - self.assertTrue("labels" in out_cpu[0]) + + checkOut(out_cpu) def test_generalizedrcnn_transform_repr(self): @@ -312,34 +376,40 @@ def test_generalizedrcnn_transform_repr(self): self.assertEqual(t.__repr__(), expected_string) +_devs = ["cpu", "cuda"] if torch.cuda.is_available() else ["cpu"] + + for model_name in get_available_classification_models(): - # for-loop bodies don't define scopes, so we have to save the variables - # we want to close over in some way - def do_test(self, model_name=model_name): - input_shape = (1, 3, 224, 224) - if model_name in ['inception_v3']: - input_shape = (1, 3, 299, 299) - self._test_classification_model(model_name, input_shape) + for dev in _devs: + # for-loop bodies don't define scopes, so we have to save the variables + # we want to close over in some way + def do_test(self, model_name=model_name, dev=dev): + input_shape = (1, 3, 224, 224) + if model_name in ['inception_v3']: + input_shape = (1, 3, 299, 299) + self._test_classification_model(model_name, input_shape, dev) - setattr(ModelTester, "test_" + model_name, do_test) + setattr(ModelTester, "test_" + model_name + "_" + dev, do_test) for model_name in get_available_segmentation_models(): - # for-loop bodies don't define scopes, so we have to save the variables - # we want to close over in some way - def do_test(self, model_name=model_name): - self._test_segmentation_model(model_name) + for dev in _devs: + # for-loop bodies don't define scopes, so we have to save the variables + # we want to close over in some way + def do_test(self, model_name=model_name, dev=dev): + self._test_segmentation_model(model_name, dev) - setattr(ModelTester, "test_" + model_name, do_test) + setattr(ModelTester, "test_" + model_name + "_" + dev, do_test) for model_name in get_available_detection_models(): - # for-loop bodies don't define scopes, so we have to save the variables - # we want to close over in some way - def do_test(self, model_name=model_name): - self._test_detection_model(model_name) + for dev in _devs: + # for-loop bodies don't define scopes, so we have to save the variables + # we want to close over in some way + def do_test(self, model_name=model_name, dev=dev): + self._test_detection_model(model_name, dev) - setattr(ModelTester, "test_" + model_name, do_test) + setattr(ModelTester, "test_" + model_name + "_" + dev, do_test) def do_validation_test(self, model_name=model_name): self._test_detection_model_validation(model_name) @@ -348,11 +418,11 @@ def do_validation_test(self, model_name=model_name): for model_name in get_available_video_models(): + for dev in _devs: + def do_test(self, model_name=model_name, dev=dev): + self._test_video_model(model_name, dev) - def do_test(self, model_name=model_name): - self._test_video_model(model_name) - - setattr(ModelTester, "test_" + model_name, do_test) + setattr(ModelTester, "test_" + model_name + "_" + dev, do_test) if __name__ == '__main__': unittest.main() diff --git a/test/test_ops.py b/test/test_ops.py index 2e3107f8d7e..564d5d54559 100644 --- a/test/test_ops.py +++ b/test/test_ops.py @@ -52,25 +52,30 @@ def _test_backward(self, device, contiguous): class RoIOpTester(OpTester): - def _test_forward(self, device, contiguous): + def _test_forward(self, device, contiguous, x_dtype=None, rois_dtype=None): + x_dtype = self.dtype if x_dtype is None else x_dtype + rois_dtype = self.dtype if rois_dtype is None else rois_dtype pool_size = 5 # n_channels % (pool_size ** 2) == 0 required for PS opeartions. n_channels = 2 * (pool_size ** 2) - x = torch.rand(2, n_channels, 10, 10, dtype=self.dtype, device=device) + x = torch.rand(2, n_channels, 10, 10, dtype=x_dtype, device=device) if not contiguous: x = x.permute(0, 1, 3, 2) rois = torch.tensor([[0, 0, 0, 9, 9], # format is (xyxy) [0, 0, 5, 4, 9], [0, 5, 5, 9, 9], [1, 0, 0, 9, 9]], - dtype=self.dtype, device=device) + dtype=rois_dtype, device=device) pool_h, pool_w = pool_size, pool_size y = self.fn(x, rois, pool_h, pool_w, spatial_scale=1, sampling_ratio=-1) + # the following should be true whether we're running an autocast test or not. + self.assertTrue(y.dtype == x.dtype) gt_y = self.expected_fn(x, rois, pool_h, pool_w, spatial_scale=1, sampling_ratio=-1, device=device, dtype=self.dtype) - self.assertTrue(torch.allclose(gt_y, y)) + tol = 1e-3 if (x_dtype is torch.half or rois_dtype is torch.half) else 1e-5 + self.assertTrue(torch.allclose(gt_y.to(y.dtype), y, rtol=tol, atol=tol)) def _test_backward(self, device, contiguous): pool_size = 2 @@ -290,6 +295,13 @@ def expected_fn(self, in_data, rois, pool_h, pool_w, spatial_scale=1, sampling_r def _test_boxes_shape(self): self._helper_boxes_shape(ops.roi_align) + @unittest.skipIf(not torch.cuda.is_available(), "CUDA unavailable") + def test_roi_align_autocast(self): + for x_dtype in (torch.float, torch.half): + for rois_dtype in (torch.float, torch.half): + with torch.cuda.amp.autocast(): + self._test_forward(torch.device("cuda"), contiguous=False, x_dtype=x_dtype, rois_dtype=rois_dtype) + class PSRoIAlignTester(RoIOpTester, unittest.TestCase): def fn(self, x, rois, pool_h, pool_w, spatial_scale=1, sampling_ratio=-1, **kwargs): diff --git a/torchvision/csrc/ROIAlign.h b/torchvision/csrc/ROIAlign.h index 7a856f34d63..a7cbe954a4d 100644 --- a/torchvision/csrc/ROIAlign.h +++ b/torchvision/csrc/ROIAlign.h @@ -3,6 +3,7 @@ #include "cpu/vision_cpu.h" #ifdef WITH_CUDA +#include "autocast.h" #include "cuda/vision_cuda.h" #endif #ifdef WITH_HIP @@ -11,6 +12,7 @@ // TODO: put this stuff in torchvision namespace +// roi_align dispatch nexus at::Tensor roi_align( const at::Tensor& input, // Input feature map. const at::Tensor& rois, // List of ROIs to pool over. @@ -35,6 +37,28 @@ at::Tensor roi_align( aligned); } +#ifdef WITH_CUDA +at::Tensor ROIAlign_autocast( + const at::Tensor& input, + const at::Tensor& rois, + const double spatial_scale, + const int64_t pooled_height, + const int64_t pooled_width, + const int64_t sampling_ratio, + const bool aligned) { + c10::impl::ExcludeDispatchKeyGuard no_autocast(c10::DispatchKey::Autocast); + return roi_align( + autocast::_cast(at::kFloat, input), + autocast::_cast(at::kFloat, rois), + spatial_scale, + pooled_height, + pooled_width, + sampling_ratio, + aligned) + .to(input.scalar_type()); +} +#endif + at::Tensor _roi_align_backward( const at::Tensor& grad, const at::Tensor& rois, diff --git a/torchvision/csrc/autocast.h b/torchvision/csrc/autocast.h new file mode 100644 index 00000000000..93c079fb1c5 --- /dev/null +++ b/torchvision/csrc/autocast.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef WITH_CUDA +namespace autocast { + +inline bool is_eligible(const at::Tensor& arg) { + return ( + arg.is_cuda() && arg.is_floating_point() && + (arg.scalar_type() != at::kDouble)); +} + +// Overload to catch Tensor args +inline at::Tensor _cast(at::ScalarType to_type, const at::Tensor& arg) { + if (is_eligible(arg) && (arg.scalar_type() != to_type)) { + return arg.to(to_type); + } else { + return arg; + } +} + +// Template to catch non-Tensor args +template +inline T _cast(at::ScalarType to_type, T arg) { + return arg; +} + +} // namespace autocast +#endif diff --git a/torchvision/csrc/cpu/nms_cpu.cpp b/torchvision/csrc/cpu/nms_cpu.cpp index 14c3b8b4f16..753a9c9e362 100644 --- a/torchvision/csrc/cpu/nms_cpu.cpp +++ b/torchvision/csrc/cpu/nms_cpu.cpp @@ -4,7 +4,7 @@ template at::Tensor nms_cpu_kernel( const at::Tensor& dets, const at::Tensor& scores, - const float iou_threshold) { + const double iou_threshold) { AT_ASSERTM(!dets.is_cuda(), "dets must be a CPU tensor"); AT_ASSERTM(!scores.is_cuda(), "scores must be a CPU tensor"); AT_ASSERTM( @@ -72,7 +72,26 @@ at::Tensor nms_cpu_kernel( at::Tensor nms_cpu( const at::Tensor& dets, const at::Tensor& scores, - const float iou_threshold) { + const double iou_threshold) { + TORCH_CHECK( + dets.dim() == 2, "boxes should be a 2d tensor, got ", dets.dim(), "D"); + TORCH_CHECK( + dets.size(1) == 4, + "boxes should have 4 elements in dimension 1, got ", + dets.size(1)); + TORCH_CHECK( + scores.dim() == 1, + "scores should be a 1d tensor, got ", + scores.dim(), + "D"); + TORCH_CHECK( + dets.size(0) == scores.size(0), + "boxes and scores should have same number of elements in ", + "dimension 0, got ", + dets.size(0), + " and ", + scores.size(0)); + auto result = at::empty({0}, dets.options()); AT_DISPATCH_FLOATING_TYPES(dets.scalar_type(), "nms", [&] { diff --git a/torchvision/csrc/cpu/vision_cpu.h b/torchvision/csrc/cpu/vision_cpu.h index 64aa1ae2119..6b68b356225 100644 --- a/torchvision/csrc/cpu/vision_cpu.h +++ b/torchvision/csrc/cpu/vision_cpu.h @@ -85,7 +85,7 @@ at::Tensor PSROIAlign_backward_cpu( at::Tensor nms_cpu( const at::Tensor& dets, const at::Tensor& scores, - const float iou_threshold); + const double iou_threshold); at::Tensor DeformConv2d_forward_cpu( const at::Tensor& input, diff --git a/torchvision/csrc/cuda/nms_cuda.cu b/torchvision/csrc/cuda/nms_cuda.cu index 2c519c4499d..f9c39541174 100644 --- a/torchvision/csrc/cuda/nms_cuda.cu +++ b/torchvision/csrc/cuda/nms_cuda.cu @@ -1,6 +1,11 @@ #include #include + +#if defined(WITH_CUDA) #include +#elif defined(WITH_HIP) +#include +#endif #include "cuda_helpers.h" @@ -70,10 +75,40 @@ __global__ void nms_kernel( at::Tensor nms_cuda(const at::Tensor& dets, const at::Tensor& scores, - float iou_threshold) { + const double iou_threshold) { AT_ASSERTM(dets.is_cuda(), "dets must be a CUDA tensor"); AT_ASSERTM(scores.is_cuda(), "scores must be a CUDA tensor"); + + TORCH_CHECK( + dets.dim() == 2, "boxes should be a 2d tensor, got ", dets.dim(), "D"); + TORCH_CHECK( + dets.size(1) == 4, + "boxes should have 4 elements in dimension 1, got ", + dets.size(1)); + TORCH_CHECK( + scores.dim() == 1, + "scores should be a 1d tensor, got ", + scores.dim(), + "D"); + TORCH_CHECK( + dets.size(0) == scores.size(0), + "boxes and scores should have same number of elements in ", + "dimension 0, got ", + dets.size(0), + " and ", + scores.size(0)) + +#if defined(WITH_CUDA) at::cuda::CUDAGuard device_guard(dets.device()); +#elif defined(WITH_HIP) + at::cuda::HIPGuard device_guard(dets.device()); +#else + AT_ERROR("Not compiled with GPU support"); +#endif + + if (dets.numel() == 0) { + return at::empty({0}, dets.options().dtype(at::kLong)); + } auto order_t = std::get<1>(scores.sort(0, /* descending=*/true)); auto dets_sorted = dets.index_select(0, order_t).contiguous(); diff --git a/torchvision/csrc/cuda/vision_cuda.h b/torchvision/csrc/cuda/vision_cuda.h index 834ba51a4cf..ef53d0c08b4 100644 --- a/torchvision/csrc/cuda/vision_cuda.h +++ b/torchvision/csrc/cuda/vision_cuda.h @@ -1,9 +1,4 @@ #pragma once -#if defined(WITH_CUDA) -#include -#elif defined(WITH_HIP) -#include -#endif #include at::Tensor ROIAlign_forward_cuda( @@ -90,7 +85,7 @@ at::Tensor PSROIAlign_backward_cuda( at::Tensor nms_cuda( const at::Tensor& dets, const at::Tensor& scores, - const float iou_threshold); + const double iou_threshold); at::Tensor DeformConv2d_forward_cuda( const at::Tensor& input, diff --git a/torchvision/csrc/nms.h b/torchvision/csrc/nms.h index 3c2faba8353..6bbd3e0bc65 100644 --- a/torchvision/csrc/nms.h +++ b/torchvision/csrc/nms.h @@ -2,52 +2,33 @@ #include "cpu/vision_cpu.h" #ifdef WITH_CUDA +#include "autocast.h" #include "cuda/vision_cuda.h" #endif #ifdef WITH_HIP #include "hip/vision_cuda.h" #endif +// nms dispatch nexus at::Tensor nms( const at::Tensor& dets, const at::Tensor& scores, const double iou_threshold) { - TORCH_CHECK( - dets.dim() == 2, "boxes should be a 2d tensor, got ", dets.dim(), "D"); - TORCH_CHECK( - dets.size(1) == 4, - "boxes should have 4 elements in dimension 1, got ", - dets.size(1)); - TORCH_CHECK( - scores.dim() == 1, - "scores should be a 1d tensor, got ", - scores.dim(), - "D"); - TORCH_CHECK( - dets.size(0) == scores.size(0), - "boxes and scores should have same number of elements in ", - "dimension 0, got ", - dets.size(0), - " and ", - scores.size(0)); - if (dets.is_cuda()) { -#if defined(WITH_CUDA) - if (dets.numel() == 0) { - at::cuda::CUDAGuard device_guard(dets.device()); - return at::empty({0}, dets.options().dtype(at::kLong)); - } - return nms_cuda(dets, scores, iou_threshold); -#elif defined(WITH_HIP) - if (dets.numel() == 0) { - at::cuda::HIPGuard device_guard(dets.device()); - return at::empty({0}, dets.options().dtype(at::kLong)); - } - return nms_cuda(dets, scores, iou_threshold); -#else - AT_ERROR("Not compiled with GPU support"); -#endif - } + static auto op = c10::Dispatcher::singleton() + .findSchemaOrThrow("torchvision::nms", "") + .typed(); + return op.call(dets, scores, iou_threshold); +} - at::Tensor result = nms_cpu(dets, scores, iou_threshold); - return result; +#ifdef WITH_CUDA +at::Tensor nms_autocast( + const at::Tensor& dets, + const at::Tensor& scores, + const double iou_threshold) { + c10::impl::ExcludeDispatchKeyGuard no_autocast(c10::DispatchKey::Autocast); + return nms( + autocast::_cast(at::kFloat, dets), + autocast::_cast(at::kFloat, scores), + iou_threshold); } +#endif diff --git a/torchvision/csrc/vision.cpp b/torchvision/csrc/vision.cpp index 7f56bdb51a1..aa2ec26bfef 100644 --- a/torchvision/csrc/vision.cpp +++ b/torchvision/csrc/vision.cpp @@ -43,7 +43,7 @@ int64_t _cuda_version() { } TORCH_LIBRARY(torchvision, m) { - m.def("nms", &nms); + m.def("nms(Tensor dets, Tensor scores, float iou_threshold) -> Tensor"); m.def( "roi_align(Tensor input, Tensor rois, float spatial_scale, int pooled_height, int pooled_width, int sampling_ratio, bool aligned) -> Tensor"); m.def( @@ -59,6 +59,7 @@ TORCH_LIBRARY(torchvision, m) { TORCH_LIBRARY_IMPL(torchvision, CPU, m) { m.impl("roi_align", ROIAlign_forward_cpu); m.impl("_roi_align_backward", ROIAlign_backward_cpu); + m.impl("nms", nms_cpu); } // TODO: Place this in a hypothetical separate torchvision_cuda library @@ -66,6 +67,15 @@ TORCH_LIBRARY_IMPL(torchvision, CPU, m) { TORCH_LIBRARY_IMPL(torchvision, CUDA, m) { m.impl("roi_align", ROIAlign_forward_cuda); m.impl("_roi_align_backward", ROIAlign_backward_cuda); + m.impl("nms", nms_cuda); +} +#endif + +// Autocast only needs to wrap forward pass ops. +#if defined(WITH_CUDA) +TORCH_LIBRARY_IMPL(torchvision, Autocast, m) { + m.impl("roi_align", ROIAlign_autocast); + m.impl("nms", nms_autocast); } #endif diff --git a/torchvision/ops/poolers.py b/torchvision/ops/poolers.py index adaedb6e4c3..32734cff86a 100644 --- a/torchvision/ops/poolers.py +++ b/torchvision/ops/poolers.py @@ -243,7 +243,14 @@ def forward( if torchvision._is_tracing(): tracing_results.append(result_idx_in_level.to(dtype)) else: - result[idx_in_level] = result_idx_in_level + # result and result_idx_in_level's dtypes are based on dtypes of different + # elements in x_filtered. x_filtered contains tensors output by different + # layers. When autocast is active, it may choose different dtypes for + # different layers' outputs. Therefore, we defensively match result's dtype + # before copying elements from result_idx_in_level in the following op. + # We need to cast manually (can't rely on autocast to cast for us) because + # the op acts on result in-place, and autocast only affects out-of-place ops. + result[idx_in_level] = result_idx_in_level.to(result.dtype) if torchvision._is_tracing(): result = _onnx_merge_levels(levels, tracing_results) From 131ba1320b8208f10eb58d5feb7416c90ed839bb Mon Sep 17 00:00:00 2001 From: Francisco Massa Date: Thu, 9 Jul 2020 12:25:48 +0200 Subject: [PATCH 39/51] Fix wrong clamping in RoIAlign with aligned=True (#2438) * Fix wrong clamping in RoIAlign with aligned=True * Fix silly mistake * Bugfix pointed out during code-review --- torchvision/csrc/cpu/ROIAlign_cpu.cpp | 20 ++++++++++++++------ torchvision/csrc/cuda/ROIAlign_cuda.cu | 20 ++++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/torchvision/csrc/cpu/ROIAlign_cpu.cpp b/torchvision/csrc/cpu/ROIAlign_cpu.cpp index 75d3e7a90b4..03545883a69 100644 --- a/torchvision/csrc/cpu/ROIAlign_cpu.cpp +++ b/torchvision/csrc/cpu/ROIAlign_cpu.cpp @@ -141,9 +141,13 @@ void ROIAlignForward( T roi_end_w = offset_rois[3] * spatial_scale - offset; T roi_end_h = offset_rois[4] * spatial_scale - offset; - // Force malformed ROIs to be 1x1 - T roi_width = std::max(roi_end_w - roi_start_w, (T)1.); - T roi_height = std::max(roi_end_h - roi_start_h, (T)1.); + T roi_width = roi_end_w - roi_start_w; + T roi_height = roi_end_h - roi_start_h; + if (!aligned) { + // Force malformed ROIs to be 1x1 + roi_width = std::max(roi_width, (T)1.); + roi_height = std::max(roi_height, (T)1.); + } T bin_size_h = static_cast(roi_height) / static_cast(pooled_height); T bin_size_w = static_cast(roi_width) / static_cast(pooled_width); @@ -309,9 +313,13 @@ void ROIAlignBackward( T roi_end_w = offset_rois[3] * spatial_scale - offset; T roi_end_h = offset_rois[4] * spatial_scale - offset; - // Force malformed ROIs to be 1x1 - T roi_width = std::max(roi_end_w - roi_start_w, (T)1.); - T roi_height = std::max(roi_end_h - roi_start_h, (T)1.); + T roi_width = roi_end_w - roi_start_w; + T roi_height = roi_end_h - roi_start_h; + if (!aligned) { + // Force malformed ROIs to be 1x1 + roi_width = std::max(roi_width, (T)1.); + roi_height = std::max(roi_height, (T)1.); + } T bin_size_h = static_cast(roi_height) / static_cast(pooled_height); T bin_size_w = static_cast(roi_width) / static_cast(pooled_width); diff --git a/torchvision/csrc/cuda/ROIAlign_cuda.cu b/torchvision/csrc/cuda/ROIAlign_cuda.cu index 8f8bcd10d48..84a8ba4e3bd 100644 --- a/torchvision/csrc/cuda/ROIAlign_cuda.cu +++ b/torchvision/csrc/cuda/ROIAlign_cuda.cu @@ -91,9 +91,13 @@ __global__ void RoIAlignForward( T roi_end_w = offset_rois[3] * spatial_scale - offset; T roi_end_h = offset_rois[4] * spatial_scale - offset; - // Force malformed ROIs to be 1x1 - T roi_width = max(roi_end_w - roi_start_w, (T)1.); - T roi_height = max(roi_end_h - roi_start_h, (T)1.); + T roi_width = roi_end_w - roi_start_w; + T roi_height = roi_end_h - roi_start_h; + if (!aligned) { + // Force malformed ROIs to be 1x1 + roi_width = max(roi_width, (T)1.); + roi_height = max(roi_height, (T)1.); + } T bin_size_h = static_cast(roi_height) / static_cast(pooled_height); T bin_size_w = static_cast(roi_width) / static_cast(pooled_width); @@ -229,9 +233,13 @@ __global__ void RoIAlignBackward( T roi_end_w = offset_rois[3] * spatial_scale - offset; T roi_end_h = offset_rois[4] * spatial_scale - offset; - // Force malformed ROIs to be 1x1 - T roi_width = max(roi_end_w - roi_start_w, (T)1.); - T roi_height = max(roi_end_h - roi_start_h, (T)1.); + T roi_width = roi_end_w - roi_start_w; + T roi_height = roi_end_h - roi_start_h; + if (!aligned) { + // Force malformed ROIs to be 1x1 + roi_width = max(roi_width, (T)1.); + roi_height = max(roi_height, (T)1.); + } T bin_size_h = static_cast(roi_height) / static_cast(pooled_height); T bin_size_w = static_cast(roi_width) / static_cast(pooled_width); From 892d0efbd6041f160400f7bb420f6d842d68731e Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Fri, 10 Jul 2020 04:49:46 -0400 Subject: [PATCH 40/51] add probot (#2448) --- .github/pytorch-probot.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/pytorch-probot.yml diff --git a/.github/pytorch-probot.yml b/.github/pytorch-probot.yml new file mode 100644 index 00000000000..27d0f2a1f0b --- /dev/null +++ b/.github/pytorch-probot.yml @@ -0,0 +1 @@ +tracking_issue: 2447 From cf534fdaee048a7edce90a5577508985c58c3602 Mon Sep 17 00:00:00 2001 From: Brian Vaughan Date: Fri, 10 Jul 2020 11:43:32 -0400 Subject: [PATCH 41/51] variable name should match that of tested method (#2456) --- test/test_functional_tensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index 95f7383a4f7..d23930e7313 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -328,8 +328,8 @@ def test_resize(self): script_size = [size, ] else: script_size = size - pad_tensor_script = script_fn(tensor, size=script_size, interpolation=interpolation) - self.assertTrue(resized_tensor.equal(pad_tensor_script), msg="{}, {}".format(size, interpolation)) + resize_result = script_fn(tensor, size=script_size, interpolation=interpolation) + self.assertTrue(resized_tensor.equal(resize_result), msg="{}, {}".format(size, interpolation)) def test_resized_crop(self): # test values of F.resized_crop in several cases: From 750e38f5056e6699ac76a8ded661d1ff1646fbfc Mon Sep 17 00:00:00 2001 From: peterjc123 Date: Sun, 12 Jul 2020 23:30:24 +0800 Subject: [PATCH 42/51] Fixes function signatures for torch::nn::Functional (#2463) --- torchvision/csrc/models/modelsimpl.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/torchvision/csrc/models/modelsimpl.h b/torchvision/csrc/models/modelsimpl.h index ddde2071db5..1dc8d06b15e 100644 --- a/torchvision/csrc/models/modelsimpl.h +++ b/torchvision/csrc/models/modelsimpl.h @@ -14,22 +14,22 @@ namespace modelsimpl { // TODO here torch::relu_ and torch::adaptive_avg_pool2d wrapped in // torch::nn::Fuctional don't work. so keeping these for now -inline torch::Tensor& relu_(torch::Tensor x) { - return torch::relu_(x); +inline torch::Tensor& relu_(const torch::Tensor& x) { + return x.relu_(); } -inline torch::Tensor relu6_(torch::Tensor x) { +inline torch::Tensor& relu6_(const torch::Tensor& x) { return x.clamp_(0, 6); } inline torch::Tensor adaptive_avg_pool2d( - torch::Tensor x, + const torch::Tensor& x, torch::ExpandingArray<2> output_size) { return torch::adaptive_avg_pool2d(x, output_size); } inline torch::Tensor max_pool2d( - torch::Tensor x, + const torch::Tensor& x, torch::ExpandingArray<2> kernel_size, torch::ExpandingArray<2> stride) { return torch::max_pool2d(x, kernel_size, stride); From ad0553c0ad368ef9cde12d5a0bb6f2ecd5285544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vuka=C5=A1in=20Manojlovi=C4=87?= Date: Sun, 12 Jul 2020 17:31:04 +0200 Subject: [PATCH 43/51] Fix type annotation for num_features in FrozenBatchNorm2d (#2462) --- torchvision/ops/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/torchvision/ops/misc.py b/torchvision/ops/misc.py index a0f15653145..17e69c506d8 100644 --- a/torchvision/ops/misc.py +++ b/torchvision/ops/misc.py @@ -50,9 +50,9 @@ class FrozenBatchNorm2d(torch.nn.Module): def __init__( self, - num_features: Tuple[int, ...], + num_features: int, eps: float = 0., - n: Optional[Tuple[int, ...]] = None, + n: Optional[int] = None, ): # n=None for backward-compatibility if n is not None: From 812395ca6aee985423601aa9cfc51001287f5cb6 Mon Sep 17 00:00:00 2001 From: guyang3532 <62738430+guyang3532@users.noreply.github.com> Date: Mon, 13 Jul 2020 16:18:53 +0800 Subject: [PATCH 44/51] Fix CPU unit tests (#2465) --- .circleci/unittest/linux/scripts/install.sh | 8 +++++++- .circleci/unittest/windows/scripts/install.sh | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.circleci/unittest/linux/scripts/install.sh b/.circleci/unittest/linux/scripts/install.sh index 966a6b45eb7..65273c75152 100755 --- a/.circleci/unittest/linux/scripts/install.sh +++ b/.circleci/unittest/linux/scripts/install.sh @@ -10,9 +10,15 @@ set -e eval "$(./conda/bin/conda shell.bash hook)" conda activate ./env -if [ -z "${CUDA_VERSION:-}" ] ; then +if [ "${CU_VERSION:-}" == cpu ] ; then cudatoolkit="cpuonly" else + if [[ ${#CU_VERSION} -eq 4 ]]; then + CUDA_VERSION="${CU_VERSION:2:1}.${CU_VERSION:3:1}" + elif [[ ${#CU_VERSION} -eq 5 ]]; then + CUDA_VERSION="${CU_VERSION:2:2}.${CU_VERSION:4:1}" + fi + echo "Using CUDA $CUDA_VERSION as determined by CU_VERSION" version="$(python -c "print('.'.join(\"${CUDA_VERSION}\".split('.')[:2]))")" cudatoolkit="cudatoolkit=${version}" fi diff --git a/.circleci/unittest/windows/scripts/install.sh b/.circleci/unittest/windows/scripts/install.sh index 584a868562d..b0f585a5483 100644 --- a/.circleci/unittest/windows/scripts/install.sh +++ b/.circleci/unittest/windows/scripts/install.sh @@ -12,9 +12,15 @@ this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" eval "$(./conda/Scripts/conda.exe 'shell.bash' 'hook')" conda activate ./env -if [ -z "${CUDA_VERSION:-}" ] ; then +if [ "${CU_VERSION:-}" == cpu ] ; then cudatoolkit="cpuonly" else + if [[ ${#CU_VERSION} -eq 4 ]]; then + CUDA_VERSION="${CU_VERSION:2:1}.${CU_VERSION:3:1}" + elif [[ ${#CU_VERSION} -eq 5 ]]; then + CUDA_VERSION="${CU_VERSION:2:2}.${CU_VERSION:4:1}" + fi + echo "Using CUDA $CUDA_VERSION as determined by CU_VERSION" version="$(python -c "print('.'.join(\"${CUDA_VERSION}\".split('.')[:2]))")" cudatoolkit="cudatoolkit=${version}" fi From 0344603ece20ba5e4086aa8c49b2cce0829aefe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Mon, 13 Jul 2020 04:24:20 -0500 Subject: [PATCH 45/51] PR: Remove linux distribution detection, search first on local paths for libraries (#2457) * Remove linux distribution detection, search first on local paths for libraries * Use not in * Remove unused variables --- setup.py | 123 +++++++++++++++++++------------------------------------ 1 file changed, 43 insertions(+), 80 deletions(-) diff --git a/setup.py b/setup.py index e1587996362..13c7a98ec74 100644 --- a/setup.py +++ b/setup.py @@ -88,74 +88,55 @@ def find_library(name, vision_include): include_folder = None library_header = '{0}.h'.format(name) - print('Running build on conda-build: {0}'.format(is_conda_build)) - if is_conda_build: - # Add conda headers/libraries - if os.name == 'nt': - build_prefix = os.path.join(build_prefix, 'Library') - include_folder = os.path.join(build_prefix, 'include') - lib_folder = os.path.join(build_prefix, 'lib') - library_header_path = os.path.join( - include_folder, library_header) - library_found = os.path.isfile(library_header_path) - conda_installed = library_found - else: - # Check if using Anaconda to produce wheels - conda = distutils.spawn.find_executable('conda') - is_conda = conda is not None - print('Running build on conda: {0}'.format(is_conda)) - if is_conda: - python_executable = sys.executable - py_folder = os.path.dirname(python_executable) + # Lookup in TORCHVISION_INCLUDE or in the package file + package_path = [os.path.join(this_dir, 'torchvision')] + for folder in vision_include + package_path: + candidate_path = os.path.join(folder, library_header) + library_found = os.path.exists(candidate_path) + if library_found: + break + + if not library_found: + print('Running build on conda-build: {0}'.format(is_conda_build)) + if is_conda_build: + # Add conda headers/libraries if os.name == 'nt': - env_path = os.path.join(py_folder, 'Library') - else: - env_path = os.path.dirname(py_folder) - lib_folder = os.path.join(env_path, 'lib') - include_folder = os.path.join(env_path, 'include') + build_prefix = os.path.join(build_prefix, 'Library') + include_folder = os.path.join(build_prefix, 'include') + lib_folder = os.path.join(build_prefix, 'lib') library_header_path = os.path.join( include_folder, library_header) library_found = os.path.isfile(library_header_path) conda_installed = library_found - - if not library_found: - if sys.platform == 'linux': - library_found = os.path.exists('/usr/include/{0}'.format( - library_header)) - library_found = library_found or os.path.exists( - '/usr/local/include/{0}'.format(library_header)) else: - # Lookup in TORCHVISION_INCLUDE or in the package file - package_path = [os.path.join(this_dir, 'torchvision')] - for folder in vision_include + package_path: - candidate_path = os.path.join(folder, library_header) - library_found = os.path.exists(candidate_path) - if library_found: - break + # Check if using Anaconda to produce wheels + conda = distutils.spawn.find_executable('conda') + is_conda = conda is not None + print('Running build on conda: {0}'.format(is_conda)) + if is_conda: + python_executable = sys.executable + py_folder = os.path.dirname(python_executable) + if os.name == 'nt': + env_path = os.path.join(py_folder, 'Library') + else: + env_path = os.path.dirname(py_folder) + lib_folder = os.path.join(env_path, 'lib') + include_folder = os.path.join(env_path, 'include') + library_header_path = os.path.join( + include_folder, library_header) + library_found = os.path.isfile(library_header_path) + conda_installed = library_found + + if not library_found: + if sys.platform == 'linux': + library_found = os.path.exists('/usr/include/{0}'.format( + library_header)) + library_found = library_found or os.path.exists( + '/usr/local/include/{0}'.format(library_header)) return library_found, conda_installed, include_folder, lib_folder -def get_linux_distribution(): - release_data = {} - with open("/etc/os-release") as f: - reader = csv.reader(f, delimiter="=") - for row in reader: - if row: - release_data[row[0]] = row[1] - if release_data["ID"] in ["debian", "raspbian"]: - with open("/etc/debian_version") as f: - debian_version = f.readline().strip() - major_version = debian_version.split(".")[0] - version_split = release_data["VERSION"].split(" ", maxsplit=1) - if version_split[0] == major_version: - # Just major version shown, replace it with the full version - release_data["VERSION"] = " ".join( - [debian_version] + version_split[1:]) - print("{} {}".format(release_data["NAME"], release_data["VERSION"])) - return release_data - - def get_extensions(): this_dir = os.path.dirname(os.path.abspath(__file__)) extensions_dir = os.path.join(this_dir, 'torchvision', 'csrc') @@ -267,14 +248,6 @@ def get_extensions(): image_library = [] image_link_flags = [] - # Detect if build is running under conda/conda-build - conda = distutils.spawn.find_executable('conda') - is_conda = conda is not None - - build_prefix = os.environ.get('BUILD_PREFIX', None) - is_conda_build = build_prefix is not None - running_under_conda = is_conda or is_conda_build - # Locating libPNG libpng = distutils.spawn.find_executable('libpng-config') pngfix = distutils.spawn.find_executable('pngfix') @@ -291,20 +264,10 @@ def get_extensions(): png_version = parse_version(png_version) if png_version >= parse_version("1.6.0"): print('Building torchvision with PNG image support') - linux = sys.platform == 'linux' - not_debian = False - libpng_on_conda = False - if linux: - bin_folder = os.path.dirname(sys.executable) - png_bin_folder = os.path.dirname(libpng) - libpng_on_conda = ( - running_under_conda and bin_folder == png_bin_folder) - release_info = get_linux_distribution() - not_debian = release_info["NAME"] not in {'Ubuntu', 'Debian'} - if not linux or libpng_on_conda or not_debian: - png_lib = subprocess.run([libpng, '--libdir'], - stdout=subprocess.PIPE) - png_lib = png_lib.stdout.strip().decode('utf-8') + png_lib = subprocess.run([libpng, '--libdir'], + stdout=subprocess.PIPE) + png_lib = png_lib.stdout.strip().decode('utf-8') + if 'disabled' not in png_lib: image_library += [png_lib] png_include = subprocess.run([libpng, '--I_opts'], stdout=subprocess.PIPE) From a568c7f17aab3f257bd547a21e92cd7d6b482a59 Mon Sep 17 00:00:00 2001 From: vfdev Date: Wed, 15 Jul 2020 17:05:43 +0200 Subject: [PATCH 46/51] Fixes incoherence in affine transformation when center is defined as half image size + 0.5 (#2468) Incoherence is when affine transformation is 90 degrees rotation and output contains a zero line --- test/test_transforms.py | 14 +++++++------- torchvision/transforms/functional.py | 8 +++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/test/test_transforms.py b/test/test_transforms.py index b0eb844fcf8..d583881b472 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -1311,14 +1311,11 @@ def test_rotate_fill(self): def test_affine(self): input_img = np.zeros((40, 40, 3), dtype=np.uint8) - pts = [] cnt = [20, 20] for pt in [(16, 16), (20, 16), (20, 20)]: for i in range(-5, 5): for j in range(-5, 5): input_img[pt[0] + i, pt[1] + j, :] = [255, 155, 55] - pts.append((pt[0] + i, pt[1] + j)) - pts = list(set(pts)) with self.assertRaises(TypeError): F.affine(input_img, 10) @@ -1373,9 +1370,12 @@ def _test_transformation(a, t, s, sh): inv_true_matrix = np.linalg.inv(true_matrix) for y in range(true_result.shape[0]): for x in range(true_result.shape[1]): - res = np.dot(inv_true_matrix, [x, y, 1]) - _x = int(res[0] + 0.5) - _y = int(res[1] + 0.5) + # Same as for PIL: + # https://github.com/python-pillow/Pillow/blob/71f8ec6a0cfc1008076a023c0756542539d057ab/ + # src/libImaging/Geometry.c#L1060 + input_pt = np.array([x + 0.5, y + 0.5, 1.0]) + res = np.floor(np.dot(inv_true_matrix, input_pt)).astype(np.int) + _x, _y = res[:2] if 0 <= _x < input_img.shape[1] and 0 <= _y < input_img.shape[0]: true_result[y, x, :] = input_img[_y, _x, :] @@ -1408,7 +1408,7 @@ def _test_transformation(a, t, s, sh): # Test rotation, scale, translation, shear for a in range(-90, 90, 25): for t1 in range(-10, 10, 5): - for s in [0.75, 0.98, 1.0, 1.1, 1.2]: + for s in [0.75, 0.98, 1.0, 1.2, 1.4]: for sh in range(-15, 15, 5): _test_transformation(a=a, t=(t1, t1), s=s, sh=(sh, sh)) diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 801df42a187..35cd222acd9 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -1,12 +1,11 @@ import math import numbers import warnings -from collections.abc import Iterable from typing import Any import numpy as np from numpy import sin, cos, tan -from PIL import Image, ImageOps, ImageEnhance, __version__ as PILLOW_VERSION +from PIL import Image, __version__ as PILLOW_VERSION import torch from torch import Tensor @@ -910,7 +909,10 @@ def affine(img, angle, translate, scale, shear, resample=0, fillcolor=None): assert scale > 0.0, "Argument scale should be positive" output_size = img.size - center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5) + # center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5) + # it is visually better to estimate the center without 0.5 offset + # otherwise image rotated by 90 degrees is shifted 1 pixel + center = (img.size[0] * 0.5, img.size[1] * 0.5) matrix = _get_inverse_affine_matrix(center, angle, translate, scale, shear) kwargs = {"fillcolor": fillcolor} if int(PILLOW_VERSION.split('.')[0]) >= 5 else {} return img.transform(output_size, Image.AFFINE, matrix, resample, **kwargs) From 03b1d38ba3c67703e648fb067570eeb1a1e61265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Andr=C3=A9s=20Margffoy=20Tuay?= Date: Wed, 15 Jul 2020 10:20:16 -0500 Subject: [PATCH 47/51] PR: Improve handling of truncated/incomplete and corrupt JPEG images (#2471) * Add corruption cases * Read jpeg headers until exhaustion * Minor error correction * Add test script * Raise exception when image is truncated * Add test * Skip damaged_jpeg folder * Compare against basename * Remove unused test file --- test/assets/damaged_jpeg/TensorFlow-LICENSE | 13 +++++++++++++ test/assets/damaged_jpeg/bad_huffman.jpg | Bin 0 -> 15416 bytes test/assets/damaged_jpeg/corrupt.jpg | Bin 0 -> 1552 bytes test/assets/damaged_jpeg/corrupt34_2.jpg | Bin 0 -> 755 bytes test/assets/damaged_jpeg/corrupt34_3.jpg | Bin 0 -> 5505 bytes test/assets/damaged_jpeg/corrupt34_4.jpg | Bin 0 -> 5092 bytes test/test_image.py | 20 ++++++++++++++++++++ torchvision/csrc/cpu/image/readjpeg_cpu.cpp | 5 ++++- 8 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 test/assets/damaged_jpeg/TensorFlow-LICENSE create mode 100644 test/assets/damaged_jpeg/bad_huffman.jpg create mode 100644 test/assets/damaged_jpeg/corrupt.jpg create mode 100644 test/assets/damaged_jpeg/corrupt34_2.jpg create mode 100644 test/assets/damaged_jpeg/corrupt34_3.jpg create mode 100644 test/assets/damaged_jpeg/corrupt34_4.jpg diff --git a/test/assets/damaged_jpeg/TensorFlow-LICENSE b/test/assets/damaged_jpeg/TensorFlow-LICENSE new file mode 100644 index 00000000000..c7563fe4e5b --- /dev/null +++ b/test/assets/damaged_jpeg/TensorFlow-LICENSE @@ -0,0 +1,13 @@ + Copyright 2019 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. diff --git a/test/assets/damaged_jpeg/bad_huffman.jpg b/test/assets/damaged_jpeg/bad_huffman.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ef5b6f12c5588e63f9573f921286141ec0af1335 GIT binary patch literal 15416 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 literal 0 HcmV?d00001 diff --git a/test/assets/damaged_jpeg/corrupt34_3.jpg b/test/assets/damaged_jpeg/corrupt34_3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c1c2a9d1e1e9d942a982c4f53815d4af0ef8e92a GIT binary patch literal 5505 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 N%VYWIp?j>Re**2dQX2pO literal 0 HcmV?d00001 diff --git a/test/test_image.py b/test/test_image.py index 09a591d19ef..0bf3daf5528 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -1,4 +1,5 @@ import os +import glob import unittest import sys @@ -10,11 +11,15 @@ IMAGE_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") IMAGE_DIR = os.path.join(IMAGE_ROOT, "fakedata", "imagefolder") +DAMAGED_JPEG = os.path.join(IMAGE_ROOT, 'damaged_jpeg') def get_images(directory, img_ext): assert os.path.isdir(directory) for root, _, files in os.walk(directory): + if os.path.basename(root) == 'damaged_jpeg': + continue + for fl in files: _, ext = os.path.splitext(fl) if ext == img_ext: @@ -44,6 +49,21 @@ def test_decode_jpeg(self): with self.assertRaises(RuntimeError): decode_jpeg(torch.empty((100), dtype=torch.uint8)) + def test_damaged_images(self): + # Test image with bad Huffman encoding (should not raise) + bad_huff = os.path.join(DAMAGED_JPEG, 'bad_huffman.jpg') + try: + _ = read_jpeg(bad_huff) + except RuntimeError: + self.assertTrue(False) + + # Truncated images should raise an exception + truncated_images = glob.glob( + os.path.join(DAMAGED_JPEG, 'corrupt*.jpg')) + for image_path in truncated_images: + with self.assertRaises(RuntimeError): + read_jpeg(image_path) + def test_read_png(self): # Check across .png for img_path in get_images(IMAGE_DIR, ".png"): diff --git a/torchvision/csrc/cpu/image/readjpeg_cpu.cpp b/torchvision/csrc/cpu/image/readjpeg_cpu.cpp index 4954b2c2474..b3e3d2ffa5a 100644 --- a/torchvision/csrc/cpu/image/readjpeg_cpu.cpp +++ b/torchvision/csrc/cpu/image/readjpeg_cpu.cpp @@ -48,7 +48,10 @@ static void torch_jpeg_init_source(j_decompress_ptr cinfo) {} static boolean torch_jpeg_fill_input_buffer(j_decompress_ptr cinfo) { torch_jpeg_mgr* src = (torch_jpeg_mgr*)cinfo->src; - // No more data. Probably an incomplete image; just output EOI. + // No more data. Probably an incomplete image; Raise exception. + torch_jpeg_error_ptr myerr = (torch_jpeg_error_ptr)cinfo->err; + strcpy(jpegLastErrorMsg, "Image is incomplete or truncated"); + longjmp(myerr->setjmp_buffer, 1); src->pub.next_input_byte = EOI_BUFFER; src->pub.bytes_in_buffer = 1; return TRUE; From 5f4b5794bb361ce6c6d1b31e39b97c95cff766be Mon Sep 17 00:00:00 2001 From: vfdev Date: Thu, 16 Jul 2020 11:27:31 +0200 Subject: [PATCH 48/51] Unified input for F.affine (#2444) * [WIP] F.affine * [WIP] F.affine + tests * Unified input for F.affine * Removed commented code * Removed unused imports --- test/test_functional_tensor.py | 89 +++++++++++ test/test_transforms.py | 4 +- torchvision/transforms/functional.py | 161 ++++++++++---------- torchvision/transforms/functional_pil.py | 67 +++++++- torchvision/transforms/functional_tensor.py | 67 +++++++- 5 files changed, 301 insertions(+), 87 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index d23930e7313..aaecffe6c5b 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -348,6 +348,95 @@ def test_resized_crop(self): msg="{} vs {}".format(expected_out_tensor[0, :10, :10], out_tensor[0, :10, :10]) ) + def test_affine(self): + # Tests on square image + tensor, pil_img = self._create_data(26, 26) + + scripted_affine = torch.jit.script(F.affine) + # 1) identity map + out_tensor = F.affine(tensor, angle=0, translate=[0, 0], scale=1.0, shear=[0.0, 0.0], resample=0) + self.assertTrue( + tensor.equal(out_tensor), msg="{} vs {}".format(out_tensor[0, :5, :5], tensor[0, :5, :5]) + ) + out_tensor = scripted_affine(tensor, angle=0, translate=[0, 0], scale=1.0, shear=[0.0, 0.0], resample=0) + self.assertTrue( + tensor.equal(out_tensor), msg="{} vs {}".format(out_tensor[0, :5, :5], tensor[0, :5, :5]) + ) + + # 2) Test rotation + test_configs = [ + (90, torch.rot90(tensor, k=1, dims=(-1, -2))), + (45, None), + (30, None), + (-30, None), + (-45, None), + (-90, torch.rot90(tensor, k=-1, dims=(-1, -2))), + (180, torch.rot90(tensor, k=2, dims=(-1, -2))), + ] + for a, true_tensor in test_configs: + for fn in [F.affine, scripted_affine]: + out_tensor = fn(tensor, angle=a, translate=[0, 0], scale=1.0, shear=[0.0, 0.0], resample=0) + if true_tensor is not None: + self.assertTrue( + true_tensor.equal(out_tensor), + msg="{}\n{} vs \n{}".format(a, out_tensor[0, :5, :5], true_tensor[0, :5, :5]) + ) + else: + true_tensor = out_tensor + + out_pil_img = F.affine(pil_img, angle=a, translate=[0, 0], scale=1.0, shear=[0.0, 0.0], resample=0) + out_pil_tensor = torch.from_numpy(np.array(out_pil_img).transpose((2, 0, 1))) + + num_diff_pixels = (true_tensor != out_pil_tensor).sum().item() / 3.0 + ratio_diff_pixels = num_diff_pixels / true_tensor.shape[-1] / true_tensor.shape[-2] + # Tolerance : less than 6% of different pixels + self.assertLess( + ratio_diff_pixels, + 0.06, + msg="{}\n{} vs \n{}".format( + ratio_diff_pixels, true_tensor[0, :7, :7], out_pil_tensor[0, :7, :7] + ) + ) + # 3) Test translation + test_configs = [ + [10, 12], (12, 13) + ] + for t in test_configs: + for fn in [F.affine, scripted_affine]: + out_tensor = fn(tensor, angle=0, translate=t, scale=1.0, shear=[0.0, 0.0], resample=0) + out_pil_img = F.affine(pil_img, angle=0, translate=t, scale=1.0, shear=[0.0, 0.0], resample=0) + self.compareTensorToPIL(out_tensor, out_pil_img) + + # 3) Test rotation + translation + scale + share + test_configs = [ + (45, [5, 6], 1.0, [0.0, 0.0]), + (33, (5, -4), 1.0, [0.0, 0.0]), + (45, [5, 4], 1.2, [0.0, 0.0]), + (33, (4, 8), 2.0, [0.0, 0.0]), + (85, (10, -10), 0.7, [0.0, 0.0]), + (0, [0, 0], 1.0, [35.0, ]), + (25, [0, 0], 1.2, [0.0, 15.0]), + (45, [10, 0], 0.7, [2.0, 5.0]), + (45, [10, -10], 1.2, [4.0, 5.0]), + ] + for r in [0, ]: + for a, t, s, sh in test_configs: + for fn in [F.affine, scripted_affine]: + out_tensor = fn(tensor, angle=a, translate=t, scale=s, shear=sh, resample=r) + out_pil_img = F.affine(pil_img, angle=a, translate=t, scale=s, shear=sh, resample=r) + out_pil_tensor = torch.from_numpy(np.array(out_pil_img).transpose((2, 0, 1))) + + num_diff_pixels = (out_tensor != out_pil_tensor).sum().item() / 3.0 + ratio_diff_pixels = num_diff_pixels / out_tensor.shape[-1] / out_tensor.shape[-2] + # Tolerance : less than 5% of different pixels + self.assertLess( + ratio_diff_pixels, + 0.05, + msg="{}: {}\n{} vs \n{}".format( + (r, a, t, s, sh), ratio_diff_pixels, out_tensor[0, :7, :7], out_pil_tensor[0, :7, :7] + ) + ) + if __name__ == '__main__': unittest.main() diff --git a/test/test_transforms.py b/test/test_transforms.py index d583881b472..125502a3ad5 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -1317,8 +1317,8 @@ def test_affine(self): for j in range(-5, 5): input_img[pt[0] + i, pt[1] + j, :] = [255, 155, 55] - with self.assertRaises(TypeError): - F.affine(input_img, 10) + with self.assertRaises(TypeError, msg="Argument translate should be a sequence"): + F.affine(input_img, 10, translate=0, scale=1, shear=1) pil_img = F.to_pil_image(input_img) diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 35cd222acd9..340592a01f0 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -1,11 +1,10 @@ import math import numbers import warnings -from typing import Any +from typing import Any, Optional import numpy as np -from numpy import sin, cos, tan -from PIL import Image, __version__ as PILLOW_VERSION +from PIL import Image import torch from torch import Tensor @@ -21,6 +20,7 @@ _is_pil_image = F_pil._is_pil_image +_parse_fill = F_pil._parse_fill def _get_image_size(img: Tensor) -> List[int]: @@ -485,43 +485,6 @@ def hflip(img: Tensor) -> Tensor: return F_t.hflip(img) -def _parse_fill(fill, img, min_pil_version): - """Helper function to get the fill color for rotate and perspective transforms. - - Args: - fill (n-tuple or int or float): Pixel fill value for area outside the transformed - image. If int or float, the value is used for all bands respectively. - Defaults to 0 for all bands. - img (PIL Image): Image to be filled. - min_pil_version (str): The minimum PILLOW version for when the ``fillcolor`` option - was first introduced in the calling function. (e.g. rotate->5.2.0, perspective->5.0.0) - - Returns: - dict: kwarg for ``fillcolor`` - """ - major_found, minor_found = (int(v) for v in PILLOW_VERSION.split('.')[:2]) - major_required, minor_required = (int(v) for v in min_pil_version.split('.')[:2]) - if major_found < major_required or (major_found == major_required and minor_found < minor_required): - if fill is None: - return {} - else: - msg = ("The option to fill background area of the transformed image, " - "requires pillow>={}") - raise RuntimeError(msg.format(min_pil_version)) - - num_bands = len(img.getbands()) - if fill is None: - fill = 0 - if isinstance(fill, (int, float)) and num_bands > 1: - fill = tuple([fill] * num_bands) - if not isinstance(fill, (int, float)) and len(fill) != num_bands: - msg = ("The number of elements in 'fill' does not match the number of " - "bands of the image ({} != {})") - raise ValueError(msg.format(len(fill), num_bands)) - - return {"fillcolor": fill} - - def _get_perspective_coeffs(startpoints, endpoints): """Helper function to get the coefficients (a, b, c, d, e, f, g, h) for the perspective transforms. @@ -827,7 +790,9 @@ def rotate(img, angle, resample=False, expand=False, center=None, fill=None): return img.rotate(angle, resample, expand, center, **opts) -def _get_inverse_affine_matrix(center, angle, translate, scale, shear): +def _get_inverse_affine_matrix( + center: List[int], angle: float, translate: List[float], scale: float, shear: List[float] +) -> List[float]: # Helper method to compute inverse matrix for affine transformation # As it is explained in PIL.Image.rotate @@ -847,14 +812,6 @@ def _get_inverse_affine_matrix(center, angle, translate, scale, shear): # # Thus, the inverse is M^-1 = C * RSS^-1 * C^-1 * T^-1 - if isinstance(shear, numbers.Number): - shear = [shear, 0] - - if not isinstance(shear, (tuple, list)) and len(shear) == 2: - raise ValueError( - "Shear should be a single value or a tuple/list containing " + - "two values. Got {}".format(shear)) - rot = math.radians(angle) sx, sy = [math.radians(s) for s in shear] @@ -862,60 +819,100 @@ def _get_inverse_affine_matrix(center, angle, translate, scale, shear): tx, ty = translate # RSS without scaling - a = cos(rot - sy) / cos(sy) - b = -cos(rot - sy) * tan(sx) / cos(sy) - sin(rot) - c = sin(rot - sy) / cos(sy) - d = -sin(rot - sy) * tan(sx) / cos(sy) + cos(rot) + a = math.cos(rot - sy) / math.cos(sy) + b = -math.cos(rot - sy) * math.tan(sx) / math.cos(sy) - math.sin(rot) + c = math.sin(rot - sy) / math.cos(sy) + d = -math.sin(rot - sy) * math.tan(sx) / math.cos(sy) + math.cos(rot) # Inverted rotation matrix with scale and shear # det([[a, b], [c, d]]) == 1, since det(rotation) = 1 and det(shear) = 1 - M = [d, -b, 0, - -c, a, 0] - M = [x / scale for x in M] + matrix = [d, -b, 0.0, -c, a, 0.0] + matrix = [x / scale for x in matrix] # Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1 - M[2] += M[0] * (-cx - tx) + M[1] * (-cy - ty) - M[5] += M[3] * (-cx - tx) + M[4] * (-cy - ty) + matrix[2] += matrix[0] * (-cx - tx) + matrix[1] * (-cy - ty) + matrix[5] += matrix[3] * (-cx - tx) + matrix[4] * (-cy - ty) # Apply center translation: C * RSS^-1 * C^-1 * T^-1 - M[2] += cx - M[5] += cy - return M + matrix[2] += cx + matrix[5] += cy + return matrix -def affine(img, angle, translate, scale, shear, resample=0, fillcolor=None): - """Apply affine transformation on the image keeping image center invariant + +def affine( + img: Tensor, angle: float, translate: List[int], scale: float, shear: List[float], + resample: int = 0, fillcolor: Optional[int] = None +) -> Tensor: + """Apply affine transformation on the image keeping image center invariant. + The image can be a PIL Image or a Tensor, in which case it is expected + to have [..., H, W] shape, where ... means an arbitrary number of leading dimensions. Args: - img (PIL Image): PIL Image to be rotated. + img (PIL Image or Tensor): image to be rotated. angle (float or int): rotation angle in degrees between -180 and 180, clockwise direction. translate (list or tuple of integers): horizontal and vertical translations (post-rotation translation) scale (float): overall scale shear (float or tuple or list): shear angle value in degrees between -180 to 180, clockwise direction. - If a tuple of list is specified, the first value corresponds to a shear parallel to the x axis, while - the second value corresponds to a shear parallel to the y axis. + If a tuple of list is specified, the first value corresponds to a shear parallel to the x axis, while + the second value corresponds to a shear parallel to the y axis. resample (``PIL.Image.NEAREST`` or ``PIL.Image.BILINEAR`` or ``PIL.Image.BICUBIC``, optional): - An optional resampling filter. - See `filters`_ for more information. - If omitted, or if the image has mode "1" or "P", it is set to ``PIL.Image.NEAREST``. + An optional resampling filter. See `filters`_ for more information. + If omitted, or if the image is PIL Image and has mode "1" or "P", it is set to ``PIL.Image.NEAREST``. + If input is Tensor, only ``PIL.Image.NEAREST`` and ``PIL.Image.BILINEAR`` are supported. fillcolor (int): Optional fill color for the area outside the transform in the output image. (Pillow>=5.0.0) + + Returns: + PIL Image or Tensor: Transformed image. """ - if not F_pil._is_pil_image(img): - raise TypeError('img should be PIL Image. Got {}'.format(type(img))) + if not isinstance(angle, (int, float)): + raise TypeError("Argument angle should be int or float") + + if not isinstance(translate, (list, tuple)): + raise TypeError("Argument translate should be a sequence") + + if len(translate) != 2: + raise ValueError("Argument translate should be a sequence of length 2") + + if scale <= 0.0: + raise ValueError("Argument scale should be positive") + + if not isinstance(shear, (numbers.Number, (list, tuple))): + raise TypeError("Shear should be either a single value or a sequence of two values") + + if isinstance(angle, int): + angle = float(angle) + + if isinstance(translate, tuple): + translate = list(translate) + + if isinstance(shear, numbers.Number): + shear = [shear, 0.0] + + if isinstance(shear, tuple): + shear = list(shear) + + if len(shear) == 1: + shear = [shear[0], shear[0]] + + if len(shear) != 2: + raise ValueError("Shear should be a sequence containing two values. Got {}".format(shear)) + + img_size = _get_image_size(img) + if not isinstance(img, torch.Tensor): + # center = (img_size[0] * 0.5 + 0.5, img_size[1] * 0.5 + 0.5) + # it is visually better to estimate the center without 0.5 offset + # otherwise image rotated by 90 degrees is shifted vs output image of torch.rot90 or F_t.affine + center = [img_size[0] * 0.5, img_size[1] * 0.5] + matrix = _get_inverse_affine_matrix(center, angle, translate, scale, shear) - assert isinstance(translate, (tuple, list)) and len(translate) == 2, \ - "Argument translate should be a list or tuple of length 2" + return F_pil.affine(img, matrix=matrix, resample=resample, fillcolor=fillcolor) - assert scale > 0.0, "Argument scale should be positive" + # we need to rescale translate by image size / 2 as its values can be between -1 and 1 + translate = [2.0 * t / s for s, t in zip(img_size, translate)] - output_size = img.size - # center = (img.size[0] * 0.5 + 0.5, img.size[1] * 0.5 + 0.5) - # it is visually better to estimate the center without 0.5 offset - # otherwise image rotated by 90 degrees is shifted 1 pixel - center = (img.size[0] * 0.5, img.size[1] * 0.5) - matrix = _get_inverse_affine_matrix(center, angle, translate, scale, shear) - kwargs = {"fillcolor": fillcolor} if int(PILLOW_VERSION.split('.')[0]) >= 5 else {} - return img.transform(output_size, Image.AFFINE, matrix, resample, **kwargs) + matrix = _get_inverse_affine_matrix([0, 0], angle, translate, scale, shear) + return F_t.affine(img, matrix=matrix, resample=resample, fillcolor=fillcolor) def to_grayscale(img, num_output_channels=1): diff --git a/torchvision/transforms/functional_pil.py b/torchvision/transforms/functional_pil.py index 994988ce1f6..f165b65f8d8 100644 --- a/torchvision/transforms/functional_pil.py +++ b/torchvision/transforms/functional_pil.py @@ -1,13 +1,14 @@ import numbers from typing import Any, List, Sequence +import numpy as np import torch +from PIL import Image, ImageOps, ImageEnhance, __version__ as PILLOW_VERSION + try: import accimage except ImportError: accimage = None -from PIL import Image, ImageOps, ImageEnhance -import numpy as np @torch.jit.unused @@ -327,3 +328,65 @@ def resize(img, size, interpolation=Image.BILINEAR): return img.resize((ow, oh), interpolation) else: return img.resize(size[::-1], interpolation) + + +@torch.jit.unused +def _parse_fill(fill, img, min_pil_version): + """Helper function to get the fill color for rotate and perspective transforms. + + Args: + fill (n-tuple or int or float): Pixel fill value for area outside the transformed + image. If int or float, the value is used for all bands respectively. + Defaults to 0 for all bands. + img (PIL Image): Image to be filled. + min_pil_version (str): The minimum PILLOW version for when the ``fillcolor`` option + was first introduced in the calling function. (e.g. rotate->5.2.0, perspective->5.0.0) + + Returns: + dict: kwarg for ``fillcolor`` + """ + major_found, minor_found = (int(v) for v in PILLOW_VERSION.split('.')[:2]) + major_required, minor_required = (int(v) for v in min_pil_version.split('.')[:2]) + if major_found < major_required or (major_found == major_required and minor_found < minor_required): + if fill is None: + return {} + else: + msg = ("The option to fill background area of the transformed image, " + "requires pillow>={}") + raise RuntimeError(msg.format(min_pil_version)) + + num_bands = len(img.getbands()) + if fill is None: + fill = 0 + if isinstance(fill, (int, float)) and num_bands > 1: + fill = tuple([fill] * num_bands) + if not isinstance(fill, (int, float)) and len(fill) != num_bands: + msg = ("The number of elements in 'fill' does not match the number of " + "bands of the image ({} != {})") + raise ValueError(msg.format(len(fill), num_bands)) + + return {"fillcolor": fill} + + +@torch.jit.unused +def affine(img, matrix, resample=0, fillcolor=None): + """Apply affine transformation on the PIL Image keeping image center invariant. + + Args: + img (PIL Image): image to be rotated. + matrix (list of floats): list of 6 float values representing inverse matrix for affine transformation. + resample (``PIL.Image.NEAREST`` or ``PIL.Image.BILINEAR`` or ``PIL.Image.BICUBIC``, optional): + An optional resampling filter. + See `filters`_ for more information. + If omitted, or if the image has mode "1" or "P", it is set to ``PIL.Image.NEAREST``. + fillcolor (int): Optional fill color for the area outside the transform in the output image. (Pillow>=5.0.0) + + Returns: + PIL Image: Transformed image. + """ + if not _is_pil_image(img): + raise TypeError('img should be PIL Image. Got {}'.format(type(img))) + + output_size = img.size + opts = _parse_fill(fillcolor, img, '5.0.0') + return img.transform(output_size, Image.AFFINE, matrix, resample, **opts) diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index 59cf6bc2764..2bd4549059e 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -1,5 +1,9 @@ +import warnings +from typing import Optional + import torch from torch import Tensor +from torch.nn.functional import affine_grid, grid_sample from torch.jit.annotations import List, BroadcastingList2 @@ -496,7 +500,8 @@ def resize(img: Tensor, size: List[int], interpolation: int = 2) -> Tensor: :math:`\left(\text{size} \times \frac{\text{height}}{\text{width}}, \text{size}\right)`. In torchscript mode padding as a single int is not supported, use a tuple or list of length 1: ``[size, ]``. - interpolation (int, optional): Desired interpolation. Default is bilinear. + interpolation (int, optional): Desired interpolation. Default is bilinear (=2). Other supported values: + nearest(=0) and bicubic(=3). Returns: Tensor: Resized image. @@ -571,3 +576,63 @@ def resize(img: Tensor, size: List[int], interpolation: int = 2) -> Tensor: img = img.to(out_dtype) return img + + +def affine( + img: Tensor, matrix: List[float], resample: int = 0, fillcolor: Optional[int] = None +) -> Tensor: + """Apply affine transformation on the Tensor image keeping image center invariant. + + Args: + img (Tensor): image to be rotated. + matrix (list of floats): list of 6 float values representing inverse matrix for affine transformation. + resample (int, optional): An optional resampling filter. Default is nearest (=2). Other supported values: + bilinear(=2). + fillcolor (int, optional): this option is not supported for Tensor input. Fill value for the area outside the + transform in the output image is always 0. + + Returns: + Tensor: Transformed image. + """ + if not (isinstance(img, torch.Tensor) and _is_tensor_a_torch_image(img)): + raise TypeError('img should be Tensor Image. Got {}'.format(type(img))) + + if fillcolor is not None: + warnings.warn("Argument fillcolor is not supported for Tensor input. Fill value is zero") + + _interpolation_modes = { + 0: "nearest", + 2: "bilinear", + } + + if resample not in _interpolation_modes: + raise ValueError("This resampling mode is unsupported with Tensor input") + + theta = torch.tensor(matrix, dtype=torch.float).reshape(1, 2, 3) + shape = img.shape + grid = affine_grid(theta, size=(1, shape[-3], shape[-2], shape[-1]), align_corners=False) + + # make image NCHW + need_squeeze = False + if img.ndim < 4: + img = img.unsqueeze(dim=0) + need_squeeze = True + + mode = _interpolation_modes[resample] + + out_dtype = img.dtype + need_cast = False + if img.dtype not in (torch.float32, torch.float64): + need_cast = True + img = img.to(torch.float32) + + img = grid_sample(img, grid, mode=mode, padding_mode="zeros", align_corners=False) + + if need_squeeze: + img = img.squeeze(dim=0) + + if need_cast: + # it is better to round before cast + img = torch.round(img).to(out_dtype) + + return img From ab73b44887eb9d7566b2a80fd45cb5f68b11d643 Mon Sep 17 00:00:00 2001 From: Kushajveer Singh Date: Thu, 16 Jul 2020 15:26:57 +0530 Subject: [PATCH 49/51] perform cyclic check for hue in test_rgb2hsv (#2477) * perform cyclic check for hue in test_rgb2hsv Test fails for cases when hue=0 and hue=360. As hue is cyclic in nature, add cyclic logic for checking the max difference by taking the sin of the tensor and then comparing the max values. * address linter issues --- test/test_functional_tensor.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index aaecffe6c5b..7b4b9b490da 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -1,6 +1,7 @@ import unittest import random import colorsys +import math from PIL import Image from PIL.Image import NEAREST, BILINEAR, BICUBIC @@ -114,7 +115,13 @@ def test_rgb2hsv(self): colorsys_img = torch.tensor(hsv, dtype=torch.float32) - max_diff = (colorsys_img - ft_hsv_img).abs().max() + ft_hsv_img_h, ft_hsv_img_sv = torch.split(ft_hsv_img, [1, 2], dim=1) + colorsys_img_h, colorsys_img_sv = torch.split(colorsys_img, [1, 2], dim=1) + + max_diff_h = ((colorsys_img_h * 2 * math.pi).sin() - (ft_hsv_img_h * 2 * math.pi).sin()).abs().max() + max_diff_sv = (colorsys_img_sv - ft_hsv_img_sv).abs().max() + max_diff = max(max_diff_h, max_diff_sv) + self.assertLess(max_diff, 1e-5) def test_adjustments(self): From d481f2d88f16c6a6c1f143629876bf536072921b Mon Sep 17 00:00:00 2001 From: Brian Vaughan Date: Fri, 17 Jul 2020 10:56:05 -0400 Subject: [PATCH 50/51] Add torchscriptable adjust_gamma transform (#2459) * add torchscriptable adjust_gamma transform https://github.com/pytorch/vision/issues/1375 * changes based on code-review * Apply suggested change to add type hint Required by mypy, even thought technically incorrect due to possible Image parameter. torchscript doesn't support a union based type hint. Co-authored-by: vfdev Co-authored-by: vfdev --- test/test_functional_tensor.py | 29 +++++++++++++++ test/test_transforms.py | 4 +- torchvision/transforms/functional.py | 30 +++++++-------- torchvision/transforms/functional_pil.py | 36 ++++++++++++++++++ torchvision/transforms/functional_tensor.py | 41 +++++++++++++++++++++ 5 files changed, 122 insertions(+), 18 deletions(-) diff --git a/test/test_functional_tensor.py b/test/test_functional_tensor.py index 7b4b9b490da..2e3477ad12b 100644 --- a/test/test_functional_tensor.py +++ b/test/test_functional_tensor.py @@ -24,6 +24,8 @@ def _create_data(self, height=3, width=3, channels=3): def compareTensorToPIL(self, tensor, pil_image, msg=None): pil_tensor = torch.as_tensor(np.array(pil_image).transpose((2, 0, 1))) + if msg is None: + msg = "tensor:\n{} \ndid not equal PIL tensor:\n{}".format(tensor, pil_tensor) self.assertTrue(tensor.equal(pil_tensor), msg) def approxEqualTensorToPIL(self, tensor, pil_image, tol=1e-5, msg=None): @@ -300,6 +302,33 @@ def test_pad(self): with self.assertRaises(ValueError, msg="Padding can not be negative for symmetric padding_mode"): F_t.pad(tensor, (-2, -3), padding_mode="symmetric") + def test_adjust_gamma(self): + script_fn = torch.jit.script(F_t.adjust_gamma) + tensor, pil_img = self._create_data(26, 36) + + for dt in [torch.float64, torch.float32, None]: + + if dt is not None: + tensor = F.convert_image_dtype(tensor, dt) + + gammas = [0.8, 1.0, 1.2] + gains = [0.7, 1.0, 1.3] + for gamma, gain in zip(gammas, gains): + + adjusted_tensor = F_t.adjust_gamma(tensor, gamma, gain) + adjusted_pil = F_pil.adjust_gamma(pil_img, gamma, gain) + scripted_result = script_fn(tensor, gamma, gain) + self.assertEqual(adjusted_tensor.dtype, scripted_result.dtype) + self.assertEqual(adjusted_tensor.size()[1:], adjusted_pil.size[::-1]) + + rbg_tensor = adjusted_tensor + if adjusted_tensor.dtype != torch.uint8: + rbg_tensor = F.convert_image_dtype(adjusted_tensor, torch.uint8) + + self.compareTensorToPIL(rbg_tensor, adjusted_pil) + + self.assertTrue(adjusted_tensor.equal(scripted_result)) + def test_resize(self): script_fn = torch.jit.script(F_t.resize) tensor, pil_img = self._create_data(26, 36) diff --git a/test/test_transforms.py b/test/test_transforms.py index 125502a3ad5..61ec525961d 100644 --- a/test/test_transforms.py +++ b/test/test_transforms.py @@ -1179,14 +1179,14 @@ def test_adjust_gamma(self): # test 1 y_pil = F.adjust_gamma(x_pil, 0.5) y_np = np.array(y_pil) - y_ans = [0, 35, 57, 117, 185, 240, 97, 45, 244, 151, 255, 15] + y_ans = [0, 35, 57, 117, 186, 241, 97, 45, 245, 152, 255, 16] y_ans = np.array(y_ans, dtype=np.uint8).reshape(x_shape) self.assertTrue(np.allclose(y_np, y_ans)) # test 2 y_pil = F.adjust_gamma(x_pil, 2) y_np = np.array(y_pil) - y_ans = [0, 0, 0, 11, 71, 200, 5, 0, 214, 31, 255, 0] + y_ans = [0, 0, 0, 11, 71, 201, 5, 0, 215, 31, 255, 0] y_ans = np.array(y_ans, dtype=np.uint8).reshape(x_shape) self.assertTrue(np.allclose(y_np, y_ans)) diff --git a/torchvision/transforms/functional.py b/torchvision/transforms/functional.py index 340592a01f0..f3d1f96089f 100644 --- a/torchvision/transforms/functional.py +++ b/torchvision/transforms/functional.py @@ -160,8 +160,14 @@ def convert_image_dtype(image: torch.Tensor, dtype: torch.dtype = torch.float) - msg = f"The cast from {image.dtype} to {dtype} cannot be performed safely." raise RuntimeError(msg) + # https://github.com/pytorch/vision/pull/2078#issuecomment-612045321 + # For data in the range 0-1, (float * 255).to(uint) is only 255 + # when float is exactly 1.0. + # `max + 1 - epsilon` provides more evenly distributed mapping of + # ranges of floats to ints. eps = 1e-3 - return image.mul(torch.iinfo(dtype).max + 1 - eps).to(dtype) + result = image.mul(torch.iinfo(dtype).max + 1 - eps) + return result.to(dtype) else: # int to float if dtype.is_floating_point: @@ -722,7 +728,7 @@ def adjust_hue(img: Tensor, hue_factor: float) -> Tensor: raise TypeError('img should be PIL Image. Got {}'.format(type(img))) -def adjust_gamma(img, gamma, gain=1): +def adjust_gamma(img: Tensor, gamma: float, gain: float = 1) -> Tensor: r"""Perform gamma correction on an image. Also known as Power Law Transform. Intensities in RGB mode are adjusted @@ -736,26 +742,18 @@ def adjust_gamma(img, gamma, gain=1): .. _Gamma Correction: https://en.wikipedia.org/wiki/Gamma_correction Args: - img (PIL Image): PIL Image to be adjusted. + img (PIL Image or Tensor): PIL Image to be adjusted. gamma (float): Non negative real number, same as :math:`\gamma` in the equation. gamma larger than 1 make the shadows darker, while gamma smaller than 1 make dark regions lighter. gain (float): The constant multiplier. + Returns: + PIL Image or Tensor: Gamma correction adjusted image. """ - if not F_pil._is_pil_image(img): - raise TypeError('img should be PIL Image. Got {}'.format(type(img))) - - if gamma < 0: - raise ValueError('Gamma should be a non-negative real number') - - input_mode = img.mode - img = img.convert('RGB') - - gamma_map = [255 * gain * pow(ele / 255., gamma) for ele in range(256)] * 3 - img = img.point(gamma_map) # use PIL's point-function to accelerate this part + if not isinstance(img, torch.Tensor): + return F_pil.adjust_gamma(img, gamma, gain) - img = img.convert(input_mode) - return img + return F_t.adjust_gamma(img, gamma, gain) def rotate(img, angle, resample=False, expand=False, center=None, fill=None): diff --git a/torchvision/transforms/functional_pil.py b/torchvision/transforms/functional_pil.py index f165b65f8d8..f66c1a35bd7 100644 --- a/torchvision/transforms/functional_pil.py +++ b/torchvision/transforms/functional_pil.py @@ -165,6 +165,42 @@ def adjust_hue(img, hue_factor): return img +@torch.jit.unused +def adjust_gamma(img, gamma, gain=1): + r"""Perform gamma correction on an image. + + Also known as Power Law Transform. Intensities in RGB mode are adjusted + based on the following equation: + + .. math:: + I_{\text{out}} = 255 \times \text{gain} \times \left(\frac{I_{\text{in}}}{255}\right)^{\gamma} + + See `Gamma Correction`_ for more details. + + .. _Gamma Correction: https://en.wikipedia.org/wiki/Gamma_correction + + Args: + img (PIL Image): PIL Image to be adjusted. + gamma (float): Non negative real number, same as :math:`\gamma` in the equation. + gamma larger than 1 make the shadows darker, + while gamma smaller than 1 make dark regions lighter. + gain (float): The constant multiplier. + """ + if not _is_pil_image(img): + raise TypeError('img should be PIL Image. Got {}'.format(type(img))) + + if gamma < 0: + raise ValueError('Gamma should be a non-negative real number') + + input_mode = img.mode + img = img.convert('RGB') + gamma_map = [(255 + 1 - 1e-3) * gain * pow(ele / 255., gamma) for ele in range(256)] * 3 + img = img.point(gamma_map) # use PIL's point-function to accelerate this part + + img = img.convert(input_mode) + return img + + @torch.jit.unused def pad(img, padding, fill=0, padding_mode="constant"): r"""Pad the given PIL.Image on all sides with the given "pad" value. diff --git a/torchvision/transforms/functional_tensor.py b/torchvision/transforms/functional_tensor.py index 2bd4549059e..f2e47b056d3 100644 --- a/torchvision/transforms/functional_tensor.py +++ b/torchvision/transforms/functional_tensor.py @@ -198,6 +198,47 @@ def adjust_saturation(img: Tensor, saturation_factor: float) -> Tensor: return _blend(img, rgb_to_grayscale(img), saturation_factor) +def adjust_gamma(img: Tensor, gamma: float, gain: float = 1) -> Tensor: + r"""Adjust gamma of an RGB image. + + Also known as Power Law Transform. Intensities in RGB mode are adjusted + based on the following equation: + + .. math:: + `I_{\text{out}} = 255 \times \text{gain} \times \left(\frac{I_{\text{in}}}{255}\right)^{\gamma}` + + See `Gamma Correction`_ for more details. + + .. _Gamma Correction: https://en.wikipedia.org/wiki/Gamma_correction + + Args: + img (Tensor): Tensor of RBG values to be adjusted. + gamma (float): Non negative real number, same as :math:`\gamma` in the equation. + gamma larger than 1 make the shadows darker, + while gamma smaller than 1 make dark regions lighter. + gain (float): The constant multiplier. + """ + + if not isinstance(img, torch.Tensor): + raise TypeError('img should be a Tensor. Got {}'.format(type(img))) + + if gamma < 0: + raise ValueError('Gamma should be a non-negative real number') + + result = img + dtype = img.dtype + if not torch.is_floating_point(img): + result = result / 255.0 + + result = (gain * result ** gamma).clamp(0, 1) + + if result.dtype != dtype: + eps = 1e-3 + result = (255 + 1.0 - eps) * result + result = result.to(dtype) + return result + + def center_crop(img: Tensor, output_size: BroadcastingList2[int]) -> Tensor: """Crop the Image Tensor and resize it to desired size. From 1aef87d01eec2c0989458387fa04baebcc86ea7b Mon Sep 17 00:00:00 2001 From: Negin Raoof Date: Mon, 20 Jul 2020 12:42:04 -0700 Subject: [PATCH 51/51] [ONNX] fix dynamic resize (#2488) * Update transform.py * Update transform.py --- torchvision/models/detection/transform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/torchvision/models/detection/transform.py b/torchvision/models/detection/transform.py index 5564866c571..9059c184949 100644 --- a/torchvision/models/detection/transform.py +++ b/torchvision/models/detection/transform.py @@ -20,7 +20,7 @@ def _resize_image_and_masks_onnx(image, self_min_size, self_max_size, target): scale_factor = torch.min(self_min_size / min_size, self_max_size / max_size) image = torch.nn.functional.interpolate( - image[None], scale_factor=scale_factor, mode='bilinear', + image[None], scale_factor=scale_factor, mode='bilinear', recompute_scale_factor=True, align_corners=False)[0] if target is None: @@ -42,7 +42,7 @@ def _resize_image_and_masks(image, self_min_size, self_max_size, target): if max_size * scale_factor > self_max_size: scale_factor = self_max_size / max_size image = torch.nn.functional.interpolate( - image[None], scale_factor=scale_factor, mode='bilinear', + image[None], scale_factor=scale_factor, mode='bilinear', recompute_scale_factor=True, align_corners=False)[0] if target is None:

    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