Skip to content

Commit c6fa38a

Browse files
authored
Merge branch 'main' into please_dont_modify_this_branch_unless_you_are_just_merging_with_main__
2 parents 5c80991 + 7a936da commit c6fa38a

File tree

11 files changed

+305
-201
lines changed

11 files changed

+305
-201
lines changed

.github/scripts/setup-env.sh

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,8 @@ if [[ $GPU_ARCH_TYPE == 'cuda' ]]; then
8080
fi
8181
echo '::endgroup::'
8282

83-
echo '::group::Install third party dependencies prior to TorchVision install'
84-
# Installing with `easy_install`, e.g. `python setup.py install` or `python setup.py develop`, has some quirks when
85-
# when pulling in third-party dependencies. For example:
86-
# - On Windows, we often hit an SSL error although `pip` can install just fine.
87-
# - It happily pulls in pre-releases, which can lead to more problems down the line.
88-
# `pip` does not unless explicitly told to do so.
89-
# Thus, we use `easy_install` to extract the third-party dependencies here and install them upfront with `pip`.
90-
python setup.py egg_info
91-
# The requires.txt cannot be used with `pip install -r` directly. The requirements are listed at the top and the
92-
# optional dependencies come in non-standard syntax after a blank line. Thus, we just extract the header.
93-
sed -e '/^$/,$d' *.egg-info/requires.txt | tee requirements.txt
94-
pip install --progress-bar=off -r requirements.txt
95-
echo '::endgroup::'
96-
9783
echo '::group::Install TorchVision'
98-
python setup.py develop
84+
pip install -e . -v --no-build-isolation
9985
echo '::endgroup::'
10086

10187
echo '::group::Install torchvision-extra-decoders'

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ details.
5858
```bash
5959
git clone https://github.com/pytorch/vision.git
6060
cd vision
61-
python setup.py develop # use install instead of develop if you don't care about development.
61+
pip install -e . -v --no-build-isolation # leave out the -e switch if you don't care about development.
6262
# or, for OSX
63-
# MACOSX_DEPLOYMENT_TARGET=10.9 CC=clang CXX=clang++ python setup.py develop
63+
# MACOSX_DEPLOYMENT_TARGET=10.9 CC=clang CXX=clang++ pip install -e . -v --no-build-isolation
6464
# for C++ debugging, use DEBUG=1
65-
# DEBUG=1 python setup.py develop
65+
# DEBUG=1 pip install -e . -v --no-build-isolation
6666
```
6767

6868
By default, GPU support is built if CUDA is found and `torch.cuda.is_available()` is true. It's possible to force

README.md

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,21 @@ versions.
2121
| `torch` | `torchvision` | Python |
2222
| ------------------ | ------------------ | ------------------- |
2323
| `main` / `nightly` | `main` / `nightly` | `>=3.9`, `<=3.12` |
24-
| `2.5` | `0.20` | `>=3.9`, `<=3.12` |
25-
| `2.4` | `0.19` | `>=3.8`, `<=3.12` |
26-
| `2.3` | `0.18` | `>=3.8`, `<=3.12` |
27-
| `2.2` | `0.17` | `>=3.8`, `<=3.11` |
28-
| `2.1` | `0.16` | `>=3.8`, `<=3.11` |
29-
| `2.0` | `0.15` | `>=3.8`, `<=3.11` |
24+
| `2.8` | `0.23` | `>=3.9`, `<=3.13` |
25+
| `2.7` | `0.22` | `>=3.9`, `<=3.13` |
26+
| `2.6` | `0.21` | `>=3.9`, `<=3.12` |
3027

3128
<details>
3229
<summary>older versions</summary>
3330

3431
| `torch` | `torchvision` | Python |
3532
|---------|-------------------|---------------------------|
33+
| `2.5` | `0.20` | `>=3.9`, `<=3.12` |
34+
| `2.4` | `0.19` | `>=3.8`, `<=3.12` |
35+
| `2.3` | `0.18` | `>=3.8`, `<=3.12` |
36+
| `2.2` | `0.17` | `>=3.8`, `<=3.11` |
37+
| `2.1` | `0.16` | `>=3.8`, `<=3.11` |
38+
| `2.0` | `0.15` | `>=3.8`, `<=3.11` |
3639
| `1.13` | `0.14` | `>=3.7.2`, `<=3.10` |
3740
| `1.12` | `0.13` | `>=3.7`, `<=3.10` |
3841
| `1.11` | `0.12` | `>=3.7`, `<=3.10` |
@@ -61,19 +64,6 @@ Torchvision currently supports the following image backends:
6164

6265
Read more in in our [docs](https://pytorch.org/vision/stable/transforms.html).
6366

64-
## [UNSTABLE] Video Backend
65-
66-
Torchvision currently supports the following video backends:
67-
68-
- [pyav](https://github.com/PyAV-Org/PyAV) (default) - Pythonic binding for ffmpeg libraries.
69-
- video_reader - This needs ffmpeg to be installed and torchvision to be built from source. There shouldn't be any
70-
conflicting version of ffmpeg installed. Currently, this is only supported on Linux.
71-
72-
```
73-
conda install -c conda-forge 'ffmpeg<4.3'
74-
python setup.py install
75-
```
76-
7767
# Using the models on C++
7868

7969
Refer to [example/cpp](https://github.com/pytorch/vision/tree/main/examples/cpp).

test/test_ops.py

Lines changed: 103 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import math
22
import os
33
from abc import ABC, abstractmethod
4-
from functools import lru_cache
4+
from functools import lru_cache, partial
55
from itertools import product
66
from typing import Callable
77

@@ -242,7 +242,7 @@ def _helper_boxes_shape(self, func):
242242
boxes = torch.tensor([[0, 0, 3, 3]], dtype=a.dtype)
243243
func(a, boxes, output_size=(2, 2))
244244

245-
# test boxes as List[Tensor[N, 4]]
245+
# test boxes as list[Tensor[N, 4]]
246246
with pytest.raises(AssertionError):
247247
a = torch.linspace(1, 8 * 8, 8 * 8).reshape(1, 1, 8, 8)
248248
boxes = torch.tensor([[0, 0, 3]], dtype=a.dtype)
@@ -1073,15 +1073,15 @@ def test_forward(self, device, contiguous, batch_sz, dtype=None):
10731073
expected = self.expected_fn(x, weight, offset, mask, bias, stride=stride, padding=padding, dilation=dilation)
10741074

10751075
torch.testing.assert_close(
1076-
res.to(expected), expected, rtol=tol, atol=tol, msg=f"\nres:\n{res}\nexpected:\n{expected}"
1076+
res.to(expected), expected, rtol=tol, atol=tol, msg=f"\nres: \n{res}\nexpected: \n{expected}"
10771077
)
10781078

10791079
# no modulation test
10801080
res = layer(x, offset)
10811081
expected = self.expected_fn(x, weight, offset, None, bias, stride=stride, padding=padding, dilation=dilation)
10821082

10831083
torch.testing.assert_close(
1084-
res.to(expected), expected, rtol=tol, atol=tol, msg=f"\nres:\n{res}\nexpected:\n{expected}"
1084+
res.to(expected), expected, rtol=tol, atol=tol, msg=f"\nres: \n{res}\nexpected: \n{expected}"
10851085
)
10861086

10871087
def test_wrong_sizes(self):
@@ -1446,34 +1446,60 @@ def test_bbox_convert_jit(self):
14461446

14471447

14481448
class TestBoxArea:
1449-
def area_check(self, box, expected, atol=1e-4):
1450-
out = ops.box_area(box)
1449+
def area_check(self, box, expected, fmt="xyxy", atol=1e-4):
1450+
out = ops.box_area(box, fmt=fmt)
14511451
torch.testing.assert_close(out, expected, rtol=0.0, check_dtype=False, atol=atol)
14521452

14531453
@pytest.mark.parametrize("dtype", [torch.int8, torch.int16, torch.int32, torch.int64])
1454-
def test_int_boxes(self, dtype):
1455-
box_tensor = torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0]], dtype=dtype)
1454+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1455+
def test_int_boxes(self, dtype, fmt):
1456+
box_tensor = ops.box_convert(
1457+
torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0]], dtype=dtype), in_fmt="xyxy", out_fmt=fmt
1458+
)
14561459
expected = torch.tensor([10000, 0], dtype=torch.int32)
1457-
self.area_check(box_tensor, expected)
1460+
self.area_check(box_tensor, expected, fmt)
14581461

14591462
@pytest.mark.parametrize("dtype", [torch.float32, torch.float64])
1460-
def test_float_boxes(self, dtype):
1461-
box_tensor = torch.tensor(FLOAT_BOXES, dtype=dtype)
1463+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1464+
def test_float_boxes(self, dtype, fmt):
1465+
box_tensor = ops.box_convert(torch.tensor(FLOAT_BOXES, dtype=dtype), in_fmt="xyxy", out_fmt=fmt)
14621466
expected = torch.tensor([604723.0806, 600965.4666, 592761.0085], dtype=dtype)
1463-
self.area_check(box_tensor, expected)
1464-
1465-
def test_float16_box(self):
1466-
box_tensor = torch.tensor(
1467-
[[2.825, 1.8625, 3.90, 4.85], [2.825, 4.875, 19.20, 5.10], [2.925, 1.80, 8.90, 4.90]], dtype=torch.float16
1467+
self.area_check(box_tensor, expected, fmt)
1468+
1469+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1470+
def test_float16_box(self, fmt):
1471+
box_tensor = ops.box_convert(
1472+
torch.tensor(
1473+
[[2.825, 1.8625, 3.90, 4.85], [2.825, 4.875, 19.20, 5.10], [2.925, 1.80, 8.90, 4.90]],
1474+
dtype=torch.float16,
1475+
),
1476+
in_fmt="xyxy",
1477+
out_fmt=fmt,
14681478
)
14691479

14701480
expected = torch.tensor([3.2170, 3.7108, 18.5071], dtype=torch.float16)
1471-
self.area_check(box_tensor, expected, atol=0.01)
1481+
self.area_check(box_tensor, expected, fmt, atol=0.01)
1482+
1483+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1484+
def test_box_area_jit(self, fmt):
1485+
box_tensor = ops.box_convert(
1486+
torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0]], dtype=torch.float), in_fmt="xyxy", out_fmt=fmt
1487+
)
1488+
expected = ops.box_area(box_tensor, fmt)
14721489

1473-
def test_box_area_jit(self):
1474-
box_tensor = torch.tensor([[0, 0, 100, 100], [0, 0, 0, 0]], dtype=torch.float)
1475-
expected = ops.box_area(box_tensor)
1476-
scripted_fn = torch.jit.script(ops.box_area)
1490+
class BoxArea(torch.nn.Module):
1491+
# We are using this intermediate class
1492+
# since torchscript does not support
1493+
# neither partial nor lambda functions for this test.
1494+
def __init__(self, fmt):
1495+
super().__init__()
1496+
self.area = ops.box_area
1497+
self.fmt = fmt
1498+
1499+
def forward(self, boxes):
1500+
return self.area(boxes, self.fmt)
1501+
1502+
scripted_fn = torch.jit.script(BoxArea(fmt))
14771503
scripted_area = scripted_fn(box_tensor)
14781504
torch.testing.assert_close(scripted_area, expected)
14791505

@@ -1487,25 +1513,28 @@ def test_box_area_jit(self):
14871513
]
14881514

14891515

1490-
def gen_box(size, dtype=torch.float):
1516+
def gen_box(size, dtype=torch.float, fmt="xyxy") -> Tensor:
14911517
xy1 = torch.rand((size, 2), dtype=dtype)
14921518
xy2 = xy1 + torch.rand((size, 2), dtype=dtype)
1493-
return torch.cat([xy1, xy2], axis=-1)
1519+
return ops.box_convert(torch.cat([xy1, xy2], axis=-1), in_fmt="xyxy", out_fmt=fmt)
14941520

14951521

14961522
class TestIouBase:
14971523
@staticmethod
1498-
def _run_test(target_fn: Callable, actual_box1, actual_box2, dtypes, atol, expected):
1524+
def _run_test(target_fn: Callable, actual_box1, actual_box2, dtypes, atol, expected, fmt="xyxy"):
14991525
for dtype in dtypes:
1500-
actual_box1 = torch.tensor(actual_box1, dtype=dtype)
1501-
actual_box2 = torch.tensor(actual_box2, dtype=dtype)
1526+
_actual_box1 = ops.box_convert(torch.tensor(actual_box1, dtype=dtype), in_fmt="xyxy", out_fmt=fmt)
1527+
_actual_box2 = ops.box_convert(torch.tensor(actual_box2, dtype=dtype), in_fmt="xyxy", out_fmt=fmt)
15021528
expected_box = torch.tensor(expected)
1503-
out = target_fn(actual_box1, actual_box2)
1529+
out = target_fn(
1530+
_actual_box1,
1531+
_actual_box2,
1532+
)
15041533
torch.testing.assert_close(out, expected_box, rtol=0.0, check_dtype=False, atol=atol)
15051534

15061535
@staticmethod
1507-
def _run_jit_test(target_fn: Callable, actual_box: list):
1508-
box_tensor = torch.tensor(actual_box, dtype=torch.float)
1536+
def _run_jit_test(target_fn: Callable, actual_box: list, fmt="xyxy"):
1537+
box_tensor = ops.box_convert(torch.tensor(actual_box, dtype=torch.float), in_fmt="xyxy", out_fmt=fmt)
15091538
expected = target_fn(box_tensor, box_tensor)
15101539
scripted_fn = torch.jit.script(target_fn)
15111540
scripted_out = scripted_fn(box_tensor, box_tensor)
@@ -1522,13 +1551,21 @@ def _cartesian_product(boxes1, boxes2, target_fn: Callable):
15221551
return result
15231552

15241553
@staticmethod
1525-
def _run_cartesian_test(target_fn: Callable):
1526-
boxes1 = gen_box(5)
1527-
boxes2 = gen_box(7)
1554+
def _run_cartesian_test(target_fn: Callable, fmt: str = "xyxy"):
1555+
boxes1 = gen_box(5, fmt=fmt)
1556+
boxes2 = gen_box(7, fmt=fmt)
15281557
a = TestIouBase._cartesian_product(boxes1, boxes2, target_fn)
15291558
b = target_fn(boxes1, boxes2)
15301559
torch.testing.assert_close(a, b)
15311560

1561+
@staticmethod
1562+
def _run_batch_test(target_fn: Callable, fmt: str = "xyxy"):
1563+
boxes1 = torch.stack([gen_box(5, fmt=fmt) for _ in range(3)], dim=0)
1564+
boxes2 = torch.stack([gen_box(5, fmt=fmt) for _ in range(3)], dim=0)
1565+
native: Tensor = target_fn(boxes1, boxes2)
1566+
iterative: Tensor = torch.stack([target_fn(*pairs) for pairs in zip(boxes1, boxes2)], dim=0)
1567+
torch.testing.assert_close(native, iterative)
1568+
15321569

15331570
class TestBoxIou(TestIouBase):
15341571
int_expected = [[1.0, 0.25, 0.0], [0.25, 1.0, 0.0], [0.0, 0.0, 1.0], [0.0625, 0.25, 0.0]]
@@ -1542,14 +1579,33 @@ class TestBoxIou(TestIouBase):
15421579
pytest.param(FLOAT_BOXES, FLOAT_BOXES, [torch.float32, torch.float64], 1e-3, float_expected),
15431580
],
15441581
)
1545-
def test_iou(self, actual_box1, actual_box2, dtypes, atol, expected):
1546-
self._run_test(ops.box_iou, actual_box1, actual_box2, dtypes, atol, expected)
1582+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1583+
def test_iou(self, actual_box1, actual_box2, dtypes, atol, expected, fmt):
1584+
self._run_test(partial(ops.box_iou, fmt=fmt), actual_box1, actual_box2, dtypes, atol, expected, fmt)
15471585

1548-
def test_iou_jit(self):
1549-
self._run_jit_test(ops.box_iou, INT_BOXES)
1586+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1587+
def test_iou_jit(self, fmt):
1588+
class IoUJit(torch.nn.Module):
1589+
# We are using this intermediate class
1590+
# since torchscript does not support
1591+
# neither partial nor lambda functions for this test.
1592+
def __init__(self, fmt):
1593+
super().__init__()
1594+
self.iou = ops.box_iou
1595+
self.fmt = fmt
15501596

1551-
def test_iou_cartesian(self):
1552-
self._run_cartesian_test(ops.box_iou)
1597+
def forward(self, boxes1, boxes2):
1598+
return self.iou(boxes1, boxes2, fmt=self.fmt)
1599+
1600+
self._run_jit_test(IoUJit(fmt=fmt), INT_BOXES, fmt)
1601+
1602+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1603+
def test_iou_cartesian(self, fmt):
1604+
self._run_cartesian_test(partial(ops.box_iou, fmt=fmt))
1605+
1606+
@pytest.mark.parametrize("fmt", ["xyxy", "xywh", "cxcywh"])
1607+
def test_iou_batch(self, fmt):
1608+
self._run_batch_test(partial(ops.box_iou, fmt=fmt))
15531609

15541610

15551611
class TestGeneralizedBoxIou(TestIouBase):
@@ -1573,6 +1629,9 @@ def test_iou_jit(self):
15731629
def test_iou_cartesian(self):
15741630
self._run_cartesian_test(ops.generalized_box_iou)
15751631

1632+
def test_iou_batch(self):
1633+
self._run_batch_test(ops.generalized_box_iou)
1634+
15761635

15771636
class TestDistanceBoxIoU(TestIouBase):
15781637
int_expected = [
@@ -1600,6 +1659,9 @@ def test_iou_jit(self):
16001659
def test_iou_cartesian(self):
16011660
self._run_cartesian_test(ops.distance_box_iou)
16021661

1662+
def test_iou_batch(self):
1663+
self._run_batch_test(ops.distance_box_iou)
1664+
16031665

16041666
class TestCompleteBoxIou(TestIouBase):
16051667
int_expected = [
@@ -1627,6 +1689,9 @@ def test_iou_jit(self):
16271689
def test_iou_cartesian(self):
16281690
self._run_cartesian_test(ops.complete_box_iou)
16291691

1692+
def test_iou_batch(self):
1693+
self._run_batch_test(ops.complete_box_iou)
1694+
16301695

16311696
def get_boxes(dtype, device):
16321697
box1 = torch.tensor([-1, -1, 1, 1], dtype=dtype, device=device)

test/test_transforms_v2.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2520,14 +2520,29 @@ def test_errors(self):
25202520
with pytest.raises(TypeError, match="Argument transforms should be a sequence of callables"):
25212521
cls(lambda x: x)
25222522

2523-
with pytest.raises(ValueError, match="at least one transform"):
2524-
transforms.Compose([])
2523+
for cls in (
2524+
transforms.Compose,
2525+
transforms.RandomApply,
2526+
transforms.RandomChoice,
2527+
transforms.RandomOrder,
2528+
):
2529+
2530+
with pytest.raises(ValueError, match="at least one transform"):
2531+
cls([])
25252532

25262533
for p in [-1, 2]:
25272534
with pytest.raises(ValueError, match=re.escape("value in the interval [0.0, 1.0]")):
25282535
transforms.RandomApply([lambda x: x], p=p)
25292536

2530-
for transforms_, p in [([lambda x: x], []), ([], [1.0])]:
2537+
for transforms_, p in [
2538+
([lambda x: x], []),
2539+
(
2540+
[lambda x: x, lambda x: x],
2541+
[
2542+
1.0,
2543+
],
2544+
),
2545+
]:
25312546
with pytest.raises(ValueError, match="Length of p doesn't match the number of transforms"):
25322547
transforms.RandomChoice(transforms_, p=p)
25332548

@@ -7001,6 +7016,29 @@ def test_parallelogram_to_bounding_boxes(input_size, device):
70017016
actual = _parallelogram_to_bounding_boxes(parallelogram)
70027017
torch.testing.assert_close(actual, expected)
70037018

7019+
# Test the transformation of a simple parallelogram.
7020+
# 1
7021+
# 1-2 / 2
7022+
# / / -> / /
7023+
# 4-3 4 /
7024+
# 3
7025+
#
7026+
# 1
7027+
# 1-2 \ 2
7028+
# \ \ -> \ \
7029+
# 4-3 4 \
7030+
# 3
7031+
parallelogram = torch.tensor(
7032+
[[0, 4, 3, 1, 5, 1, 2, 4], [0, 1, 2, 1, 5, 4, 3, 4]],
7033+
dtype=torch.float32,
7034+
)
7035+
expected = torch.tensor(
7036+
[[0, 4, 4, 0, 5, 1, 1, 5], [0, 1, 1, 0, 5, 4, 4, 5]],
7037+
dtype=torch.float32,
7038+
)
7039+
actual = _parallelogram_to_bounding_boxes(parallelogram)
7040+
torch.testing.assert_close(actual, expected)
7041+
70047042

70057043
@pytest.mark.parametrize("image_type", (PIL.Image, torch.Tensor, tv_tensors.Image))
70067044
@pytest.mark.parametrize("data_augmentation", ("hflip", "lsj", "multiscale", "ssd", "ssdlite"))

0 commit comments

Comments
 (0)