Skip to content

Commit 04ad207

Browse files
Merge branch 'master' into svhn-hwc
2 parents 5ce2758 + 07fb8ba commit 04ad207

32 files changed

+620
-200
lines changed

.circleci/regenerate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""
1616

1717
import jinja2
18+
from jinja2 import select_autoescape
1819
import yaml
1920
import os.path
2021

@@ -295,7 +296,7 @@ def ios_workflows(indentation=6, nightly=False):
295296
env = jinja2.Environment(
296297
loader=jinja2.FileSystemLoader(d),
297298
lstrip_blocks=True,
298-
autoescape=False,
299+
autoescape=select_autoescape(enabled_extensions=('html', 'xml')),
299300
keep_trailing_newline=True,
300301
)
301302

.circleci/unittest/linux/scripts/install.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,11 @@ fi
2626
printf "Installing PyTorch with %s\n" "${cudatoolkit}"
2727
conda install -y -c "pytorch-${UPLOAD_CHANNEL}" -c conda-forge "pytorch-${UPLOAD_CHANNEL}"::pytorch "${cudatoolkit}"
2828

29+
if [ $PYTHON_VERSION == "3.6" ]; then
30+
printf "Installing minimal PILLOW version\n"
31+
# Install the minimal PILLOW version. Otherwise, let setup.py install the latest
32+
pip install pillow==5.3.0
33+
fi
34+
2935
printf "* Installing torchvision\n"
3036
python setup.py develop

.circleci/unittest/windows/scripts/install.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,11 @@ fi
2828
printf "Installing PyTorch with %s\n" "${cudatoolkit}"
2929
conda install -y -c "pytorch-${UPLOAD_CHANNEL}" -c conda-forge "pytorch-${UPLOAD_CHANNEL}"::pytorch "${cudatoolkit}"
3030

31+
if [ $PYTHON_VERSION == "3.6" ]; then
32+
printf "Installing minimal PILLOW version\n"
33+
# Install the minimal PILLOW version. Otherwise, let setup.py install the latest
34+
pip install pillow==5.3.0
35+
fi
36+
3137
printf "* Installing torchvision\n"
3238
"$this_dir/vc_env_helper.bat" python setup.py develop

.github/workflows/bandit.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# GitHub Actions Bandit Workflow
2+
3+
name: Bandit
4+
5+
on:
6+
pull_request:
7+
branches: [ master ]
8+
9+
workflow_dispatch:
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v2
17+
18+
# Task will fail if any high-severity issues are found
19+
# Ignoring submodules
20+
- name: Run Bandit Security Analysis
21+
run: |
22+
python -m pip install bandit
23+
python -m bandit -r . -x ./third_party -lll

.github/workflows/codeql.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# GitHub Actions CodeQL Workflow
2+
3+
name: CodeQL
4+
5+
on:
6+
pull_request:
7+
branches: [ master ]
8+
9+
workflow_dispatch:
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v2
17+
18+
- name: Initialize CodeQL
19+
uses: github/codeql-action/init@v1
20+
with:
21+
languages: python, cpp
22+
23+
- name: Install Ninja
24+
run: |
25+
sudo apt-get update -y
26+
sudo apt-get install -y ninja-build
27+
28+
- name: Update submodules
29+
run: git submodule update --init --recursive
30+
31+
- name: Install Torch
32+
run: |
33+
python -m pip install cmake
34+
python -m pip install torch==1.8.1+cpu -f https://download.pytorch.org/whl/torch_stable.html
35+
sudo ln -s /usr/bin/ninja /usr/bin/ninja-build
36+
37+
- name: Build TorchVision
38+
run: python setup.py develop --user
39+
40+
# If any code scanning alerts are found, they will be under Security -> CodeQL
41+
# Link: https://github.com/pytorch/vision/security/code-scanning
42+
- name: Perform CodeQL Analysis
43+
uses: github/codeql-action/analyze@v1

docs/source/transforms.rst

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,34 @@ torchvision.transforms
44
.. currentmodule:: torchvision.transforms
55

66
Transforms are common image transformations. They can be chained together using :class:`Compose`.
7-
Additionally, there is the :mod:`torchvision.transforms.functional` module.
8-
Functional transforms give fine-grained control over the transformations.
7+
Most transform classes have a function equivalent: :ref:`functional
8+
transforms <functional_transforms>` give fine-grained control over the
9+
transformations.
910
This is useful if you have to build a more complex transformation pipeline
1011
(e.g. in the case of segmentation tasks).
1112

12-
All transformations accept PIL Image, Tensor Image or batch of Tensor Images as input. Tensor Image is a tensor with
13-
``(C, H, W)`` shape, where ``C`` is a number of channels, ``H`` and ``W`` are image height and width. Batch of
14-
Tensor Images is a tensor of ``(B, C, H, W)`` shape, where ``B`` is a number of images in the batch. Deterministic or
15-
random transformations applied on the batch of Tensor Images identically transform all the images of the batch.
13+
Most transformations accept both `PIL <https://pillow.readthedocs.io>`_
14+
images and tensor images, although some transformations are :ref:`PIL-only
15+
<transforms_pil_only>` and some are :ref:`tensor-only
16+
<transforms_tensor_only>`. The :ref:`conversion_transforms` may be used to
17+
convert to and from PIL images.
18+
19+
The transformations that accept tensor images also accept batches of tensor
20+
images. A Tensor Image is a tensor with ``(C, H, W)`` shape, where ``C`` is a
21+
number of channels, ``H`` and ``W`` are image height and width. A batch of
22+
Tensor Images is a tensor of ``(B, C, H, W)`` shape, where ``B`` is a number
23+
of images in the batch.
24+
25+
The expected range of the values of a tensor image is implicitely defined by
26+
the tensor dtype. Tensor images with a float dtype are expected to have
27+
values in ``[0, 1)``. Tensor images with an integer dtype are expected to
28+
have values in ``[0, MAX_DTYPE]`` where ``MAX_DTYPE`` is the largest value
29+
that can be represented in that dtype.
30+
31+
Randomized transformations will apply the same transformation to all the
32+
images of a given batch, but they will produce different transformations
33+
across calls. For reproducible transformations across calls, you may use
34+
:ref:`functional transforms <functional_transforms>`.
1635

1736
.. warning::
1837

@@ -117,13 +136,16 @@ Transforms on PIL Image and torch.\*Tensor
117136
.. autoclass:: GaussianBlur
118137
:members:
119138

139+
.. _transforms_pil_only:
140+
120141
Transforms on PIL Image only
121142
----------------------------
122143

123144
.. autoclass:: RandomChoice
124145

125146
.. autoclass:: RandomOrder
126147

148+
.. _transforms_tensor_only:
127149

128150
Transforms on torch.\*Tensor only
129151
---------------------------------
@@ -139,6 +161,7 @@ Transforms on torch.\*Tensor only
139161

140162
.. autoclass:: ConvertImageDtype
141163

164+
.. _conversion_transforms:
142165

143166
Conversion Transforms
144167
---------------------
@@ -173,13 +196,16 @@ The new transform can be used standalone or mixed-and-matched with existing tran
173196
:members:
174197

175198

199+
.. _functional_transforms:
200+
176201
Functional Transforms
177202
---------------------
178203

179204
Functional transforms give you fine-grained control of the transformation pipeline.
180205
As opposed to the transformations above, functional transforms don't contain a random number
181206
generator for their parameters.
182-
That means you have to specify/generate all parameters, but you can reuse the functional transform.
207+
That means you have to specify/generate all parameters, but the functional transform will give you
208+
reproducible results across calls.
183209

184210
Example:
185211
you can apply a functional transform with the same parameters to multiple images like this:

mypy.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,7 @@ ignore_missing_imports = True
6363
[mypy-av.*]
6464

6565
ignore_missing_imports = True
66+
67+
[mypy-defusedxml.*]
68+
69+
ignore_missing_imports = True

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def write_version_file():
6767
pytorch_dep,
6868
]
6969

70-
pillow_ver = ' >= 4.1.1'
70+
pillow_ver = ' >= 5.3.0'
7171
pillow_req = 'pillow-simd' if get_dist('pillow-simd') is not None else 'pillow'
7272
requirements.append(pillow_req + pillow_ver)
7373

0 Bytes
Loading

test/test_ops.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,78 @@ def _test_forward(self, device, contiguous, x_dtype=None, rois_dtype=None, **kwa
299299
for aligned in (True, False):
300300
super()._test_forward(device, contiguous, x_dtype, rois_dtype, aligned=aligned)
301301

302+
def test_qroialign(self):
303+
"""Make sure quantized version of RoIAlign is close to float version"""
304+
pool_size = 5
305+
img_size = 10
306+
n_channels = 2
307+
num_imgs = 1
308+
dtype = torch.float
309+
310+
def make_rois(num_rois=1000):
311+
rois = torch.randint(0, img_size // 2, size=(num_rois, 5)).to(dtype)
312+
rois[:, 0] = torch.randint(0, num_imgs, size=(num_rois,)) # set batch index
313+
rois[:, 3:] += rois[:, 1:3] # make sure boxes aren't degenerate
314+
return rois
315+
316+
for aligned in (True, False):
317+
for scale, zero_point in ((1, 0), (2, 10), (0.1, 50)):
318+
for qdtype in (torch.qint8, torch.quint8, torch.qint32):
319+
320+
x = torch.randint(50, 100, size=(num_imgs, n_channels, img_size, img_size)).to(dtype)
321+
qx = torch.quantize_per_tensor(x, scale=scale, zero_point=zero_point, dtype=qdtype)
322+
323+
rois = make_rois()
324+
qrois = torch.quantize_per_tensor(rois, scale=scale, zero_point=zero_point, dtype=qdtype)
325+
326+
x, rois = qx.dequantize(), qrois.dequantize() # we want to pass the same inputs
327+
328+
y = ops.roi_align(
329+
x,
330+
rois,
331+
output_size=pool_size,
332+
spatial_scale=1,
333+
sampling_ratio=-1,
334+
aligned=aligned,
335+
)
336+
qy = ops.roi_align(
337+
qx,
338+
qrois,
339+
output_size=pool_size,
340+
spatial_scale=1,
341+
sampling_ratio=-1,
342+
aligned=aligned,
343+
)
344+
345+
# The output qy is itself a quantized tensor and there might have been a loss of info when it was
346+
# quantized. For a fair comparison we need to quantize y as well
347+
quantized_float_y = torch.quantize_per_tensor(y, scale=scale, zero_point=zero_point, dtype=qdtype)
348+
349+
try:
350+
# Ideally, we would assert this, which passes with (scale, zero) == (1, 0)
351+
self.assertTrue((qy == quantized_float_y).all())
352+
except AssertionError:
353+
# But because the computation aren't exactly the same between the 2 RoIAlign procedures, some
354+
# rounding error may lead to a difference of 2 in the output.
355+
# For example with (scale, zero) = (2, 10), 45.00000... will be quantized to 44
356+
# but 45.00000001 will be rounded to 46. We make sure below that:
357+
# - such discrepancies between qy and quantized_float_y are very rare (less then 5%)
358+
# - any difference between qy and quantized_float_y is == scale
359+
diff_idx = torch.where(qy != quantized_float_y)
360+
num_diff = diff_idx[0].numel()
361+
self.assertTrue(num_diff / qy.numel() < .05)
362+
363+
abs_diff = torch.abs(qy[diff_idx].dequantize() - quantized_float_y[diff_idx].dequantize())
364+
t_scale = torch.full_like(abs_diff, fill_value=scale)
365+
self.assertTrue(torch.allclose(abs_diff, t_scale, atol=1e-5))
366+
367+
x = torch.randint(50, 100, size=(2, 3, 10, 10)).to(dtype)
368+
qx = torch.quantize_per_tensor(x, scale=1, zero_point=0, dtype=torch.qint8)
369+
rois = make_rois(10)
370+
qrois = torch.quantize_per_tensor(rois, scale=1, zero_point=0, dtype=torch.qint8)
371+
with self.assertRaisesRegex(RuntimeError, "Only one image per batch is allowed"):
372+
ops.roi_align(qx, qrois, output_size=pool_size)
373+
302374

303375
class PSRoIAlignTester(RoIOpTester, unittest.TestCase):
304376
def fn(self, x, rois, pool_h, pool_w, spatial_scale=1, sampling_ratio=-1, **kwargs):

0 commit comments

Comments
 (0)