diff --git a/.github/scripts/setup-env.sh b/.github/scripts/setup-env.sh index 33232a78d9f..8bb61789cdf 100755 --- a/.github/scripts/setup-env.sh +++ b/.github/scripts/setup-env.sh @@ -100,7 +100,13 @@ echo '::endgroup::' echo '::group::Install torchvision-extra-decoders' # This can be done after torchvision was built -pip install torchvision-extra-decoders +if [[ "$(uname)" == "Linux" && "$(uname -m)" != "aarch64" ]]; then + extra_decoders_channel="--pre --index-url https://download.pytorch.org/whl/nightly/cpu" +else + extra_decoders_channel="" +fi + +pip install torchvision-extra-decoders $extra_decoders_channel echo '::endgroup::' echo '::group::Collect environment information' diff --git a/.github/scripts/unittest.sh b/.github/scripts/unittest.sh index da8a06928ea..43968762a8b 100755 --- a/.github/scripts/unittest.sh +++ b/.github/scripts/unittest.sh @@ -15,4 +15,4 @@ echo '::endgroup::' python test/smoke_test.py # We explicitly ignore the video tests until we resolve https://github.com/pytorch/vision/issues/8162 -pytest --ignore-glob="*test_video*" --junit-xml="${RUNNER_TEST_RESULTS_DIR}/test-results.xml" -v --durations=25 +pytest --ignore-glob="*test_video*" --ignore-glob="*test_onnx*" --junit-xml="${RUNNER_TEST_RESULTS_DIR}/test-results.xml" -v --durations=25 -k "not TestFxFeatureExtraction" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bad2d274b05..12d643a1c4b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,7 +52,8 @@ jobs: python-version: - "3.9" - "3.10" - - "3.11" + # TODO put back 3.11 (See blame) + # - "3.11" - "3.12" runner: ["macos-m1-stable"] fail-fast: false @@ -81,11 +82,12 @@ jobs: - "3.12" runner: ["windows.4xlarge"] gpu-arch-type: ["cpu"] - include: - - python-version: "3.9" - runner: windows.g5.4xlarge.nvidia.gpu - gpu-arch-type: cuda - gpu-arch-version: "11.8" + # TODO: put GPU testing back + # include: + # - python-version: "3.9" + # runner: windows.g5.4xlarge.nvidia.gpu + # gpu-arch-type: cuda + # gpu-arch-version: "11.8" fail-fast: false uses: pytorch/test-infra/.github/workflows/windows_job.yml@release/2.7 permissions: @@ -109,39 +111,38 @@ jobs: ./.github/scripts/unittest.sh - onnx: - uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@release/2.7 - permissions: - id-token: write - contents: read - with: - repository: pytorch/vision - test-infra-ref: release/2.7 - script: | - set -euo pipefail - - export PYTHON_VERSION=3.10 - export GPU_ARCH_TYPE=cpu - export GPU_ARCH_VERSION='' - - ./.github/scripts/setup-env.sh - - # Prepare conda - CONDA_PATH=$(which conda) - eval "$(${CONDA_PATH} shell.bash hook)" - conda activate ci - - echo '::group::Install ONNX' - pip install --progress-bar=off onnx onnxruntime - echo '::endgroup::' - - echo '::group::Install testing utilities' - pip install --progress-bar=off pytest "numpy<2" - echo '::endgroup::' - - echo '::group::Run ONNX tests' - pytest --junit-xml="${RUNNER_TEST_RESULTS_DIR}/test-results.xml" -v --durations=25 test/test_onnx.py - echo '::endgroup::' + # onnx: + # uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@release/2.7 + # permissions: + # id-token: write + # contents: read + # with: + # repository: pytorch/vision + # test-infra-ref: release/2.7 + # script: | + # set -euo pipefail + # export PYTHON_VERSION=3.10 + # export GPU_ARCH_TYPE=cpu + # export GPU_ARCH_VERSION='' + + # ./.github/scripts/setup-env.sh + + # # Prepare conda + # CONDA_PATH=$(which conda) + # eval "$(${CONDA_PATH} shell.bash hook)" + # conda activate ci + + # echo '::group::Install ONNX' + # pip install --progress-bar=off onnx onnxruntime + # echo '::endgroup::' + + # echo '::group::Install testing utilities' + # pip install --progress-bar=off pytest "numpy<2" + # echo '::endgroup::' + + # echo '::group::Run ONNX tests' + # pytest --junit-xml="${RUNNER_TEST_RESULTS_DIR}/test-results.xml" -v --durations=25 test/test_onnx.py + # echo '::endgroup::' unittests-extended: uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@release/2.7 diff --git a/packaging/post_build_script.sh b/packaging/post_build_script.sh index 253980b98c3..7aefa2649e6 100644 --- a/packaging/post_build_script.sh +++ b/packaging/post_build_script.sh @@ -1,4 +1,16 @@ #!/bin/bash -LD_LIBRARY_PATH="/usr/local/lib:$CUDA_HOME/lib64:$LD_LIBRARY_PATH" python packaging/wheel/relocate.py +set -euxo pipefail -pip install torchvision-extra-decoders +if [ -n "${CUDA_HOME:-}" ]; then + LD_LIBRARY_PATH="/usr/local/lib:${CUDA_HOME}/lib64:${LD_LIBRARY_PATH}" +fi + +python packaging/wheel/relocate.py + +if [[ "$(uname)" == "Linux" && "$(uname -m)" != "aarch64" ]]; then + extra_decoders_channel="--pre --index-url https://download.pytorch.org/whl/nightly/cpu" +else + extra_decoders_channel="" +fi + +pip install torchvision-extra-decoders $extra_decoders_channel diff --git a/packaging/pre_build_script.sh b/packaging/pre_build_script.sh index 8f3fed3b4f2..fcacf4bf8a4 100644 --- a/packaging/pre_build_script.sh +++ b/packaging/pre_build_script.sh @@ -36,7 +36,7 @@ else conda install libwebp -y conda install libjpeg-turbo -c pytorch yum install -y freetype gnutls - pip install auditwheel + pip install "auditwheel<6.3.0" fi pip install numpy pyyaml future ninja diff --git a/packaging/wheel/relocate.py b/packaging/wheel/relocate.py index fb110abd873..4587f3798da 100644 --- a/packaging/wheel/relocate.py +++ b/packaging/wheel/relocate.py @@ -15,7 +15,10 @@ # Third party imports if sys.platform == "linux": - from auditwheel.lddtree import lddtree + try: + from auditwheel.lddtree import lddtree + except ImportError: + from auditwheel import lddtree ALLOWLIST = { diff --git a/release/README.md b/release/README.md new file mode 100644 index 00000000000..830f964e531 --- /dev/null +++ b/release/README.md @@ -0,0 +1,3 @@ +# Vision Release Scripts + +This folder contains script(s) used for releasing new versions of the Vision package diff --git a/release/apply-release-changes.py b/release/apply-release-changes.py new file mode 100644 index 00000000000..22dd37216f8 --- /dev/null +++ b/release/apply-release-changes.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +""" +apply-release-changes.py - Cross-platform script to replace main with a specified release version in YML files + +This script performs two replacements in YML files in .github/workflows/: +1. Replaces @main with @release/VERSION +2. Replaces 'test-infra-ref: main' with 'test-infra-ref: release/VERSION' + +Usage: + python apply-release-changes.py VERSION + +Example: + python apply-release-changes.py 2.7 +""" + +import os +import pathlib +import sys +from typing import Optional + + +def replace_in_file(file_path: pathlib.Path, old_text: str, new_text: str) -> None: + """Replace all occurrences of old_text with new_text in the specified file.""" + try: + # Try reading the file without specifying encoding to use the default + encoding = None + try: + content = file_path.read_text() + except UnicodeDecodeError: + # If that fails, try with UTF-8 + encoding = "utf-8" + content = file_path.read_text(encoding=encoding) + + # Perform the replacement + new_content = content.replace(old_text, new_text) + + # Only write if changes were made + if new_content != content: + # Write with the same encoding we used to read + if encoding: + file_path.write_text(new_content, encoding=encoding) + else: + file_path.write_text(new_content) + print(f"Updated: {file_path}") + + except Exception as e: + print(f"Error processing {file_path}: {e}") + + +def find_repo_root() -> Optional[pathlib.Path]: + """Find the git repository root by searching for .git directory.""" + # Start from the current directory and traverse upwards + current_path = pathlib.Path.cwd().absolute() + + while current_path != current_path.parent: + # Check if .git directory exists + git_dir = current_path / ".git" + if git_dir.exists() and git_dir.is_dir(): + return current_path + + # Move up one directory + current_path = current_path.parent + + # If we get here, we didn't find a repository root + return None + + +def main() -> None: + # Check if version is provided as command line argument + if len(sys.argv) != 2: + print("Error: Exactly one version parameter is required") + print(f"Usage: python {os.path.basename(__file__)} VERSION") + print("Example: python apply-release-changes.py 2.7") + sys.exit(1) + + # Get version from command line argument + version = sys.argv[1] + print(f"Using release version: {version}") + + # Find the repository root by searching for .git directory + repo_root = find_repo_root() + if not repo_root: + print("Error: Not inside a git repository. Please run from within a git repository.") + sys.exit(1) + + print(f"Repository root found at: {repo_root}") + + # Get path to workflow directory + workflow_dir = repo_root / ".github" / "workflows" + + # Process all workflow files and perform both replacements on each file + for yml_file in workflow_dir.glob("*.yml"): + replace_in_file(yml_file, "@main", f"@release/{version}") + replace_in_file(yml_file, "test-infra-ref: main", f"test-infra-ref: release/{version}") + + +if __name__ == "__main__": + print("Starting YML updates...") + main() + print("YML updates completed.") diff --git a/test/test_image.py b/test/test_image.py index 793529e22dc..812c3741f92 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -897,12 +897,16 @@ def test_decode_gif(tmpdir, name, scripted): (decode_gif, re.escape("DGifOpenFileName() failed - 103")), (decode_webp, "WebPGetFeatures failed."), pytest.param( - decode_avif, "BMFF parsing failed", marks=pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) + decode_avif, + "BMFF parsing failed", + # marks=pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) + marks=pytest.mark.skipif(True, reason="Skipping avif/heic tests for now."), ), pytest.param( decode_heic, "Invalid input: No 'ftyp' box", - marks=pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE), + # marks=pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE), + marks=pytest.mark.skipif(True, reason="Skipping avif/heic tests for now."), ), ], ) @@ -961,7 +965,8 @@ def test_decode_webp_against_pil(decode_fun, scripted, mode, pil_mode, filename) img += 123 # make sure image buffer wasn't freed by underlying decoding lib -@pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) +# @pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) +@pytest.mark.skipif(True, reason="Skipping avif/heic tests for now.") @pytest.mark.parametrize("decode_fun", (decode_avif,)) def test_decode_avif(decode_fun): encoded_bytes = read_file(next(get_images(FAKEDATA_DIR, ".avif"))) @@ -973,7 +978,8 @@ def test_decode_avif(decode_fun): # Note: decode_image fails because some of these files have a (valid) signature # we don't recognize. We should probably use libmagic.... -@pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) +# @pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) +@pytest.mark.skipif(True, reason="Skipping avif/heic tests for now.") @pytest.mark.parametrize("decode_fun", (decode_avif, decode_heic)) @pytest.mark.parametrize( "mode, pil_mode", @@ -1050,7 +1056,8 @@ def test_decode_avif_heic_against_pil(decode_fun, mode, pil_mode, filename): torch.testing.assert_close(img, from_pil, rtol=0, atol=3) -@pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) +# @pytest.mark.skipif(not IS_LINUX, reason=HEIC_AVIF_MESSAGE) +@pytest.mark.skipif(True, reason="Skipping avif/heic tests for now.") @pytest.mark.parametrize("decode_fun", (decode_heic,)) def test_decode_heic(decode_fun): encoded_bytes = read_file(next(get_images(FAKEDATA_DIR, ".heic"))) diff --git a/torchvision/models/optical_flow/raft.py b/torchvision/models/optical_flow/raft.py index c294777ee6f..3622887e3a0 100644 --- a/torchvision/models/optical_flow/raft.py +++ b/torchvision/models/optical_flow/raft.py @@ -486,7 +486,7 @@ def forward(self, image1, image2, num_flow_updates: int = 12): batch_size, _, h, w = image1.shape if (h, w) != image2.shape[-2:]: raise ValueError(f"input images should have the same shape, instead got ({h}, {w}) != {image2.shape[-2:]}") - if not (h % 8 == 0) and (w % 8 == 0): + if not ((h % 8 == 0) and (w % 8 == 0)): raise ValueError(f"input image H and W should be divisible by 8, instead got {h} (h) and {w} (w)") fmaps = self.feature_encoder(torch.cat([image1, image2], dim=0)) diff --git a/torchvision/ops/boxes.py b/torchvision/ops/boxes.py index 9674d5bfa1d..48df4d85cc7 100644 --- a/torchvision/ops/boxes.py +++ b/torchvision/ops/boxes.py @@ -78,7 +78,8 @@ def batched_nms( _log_api_usage_once(batched_nms) # Benchmarks that drove the following thresholds are at # https://github.com/pytorch/vision/issues/1311#issuecomment-781329339 - if boxes.numel() > (4000 if boxes.device.type == "cpu" else 20000) and not torchvision._is_tracing(): + # and https://github.com/pytorch/vision/pull/8925 + if boxes.numel() > (4000 if boxes.device.type == "cpu" else 100_000) and not torchvision._is_tracing(): return _batched_nms_vanilla(boxes, scores, idxs, iou_threshold) else: return _batched_nms_coordinate_trick(boxes, scores, idxs, iou_threshold) diff --git a/torchvision/transforms/v2/_color.py b/torchvision/transforms/v2/_color.py index 7a471e7c1f6..2ee83e72a41 100644 --- a/torchvision/transforms/v2/_color.py +++ b/torchvision/transforms/v2/_color.py @@ -134,7 +134,7 @@ def _check_input( raise TypeError(f"{name}={value} should be a single number or a sequence with length 2.") if not bound[0] <= value[0] <= value[1] <= bound[1]: - raise ValueError(f"{name} values should be between {bound}, but got {value}.") + raise ValueError(f"{name} values should be between {bound} and increasing, but got {value}.") return None if value[0] == value[1] == center else (float(value[0]), float(value[1])) diff --git a/torchvision/transforms/v2/_geometry.py b/torchvision/transforms/v2/_geometry.py index c615515b943..c266d23147c 100644 --- a/torchvision/transforms/v2/_geometry.py +++ b/torchvision/transforms/v2/_geometry.py @@ -567,7 +567,7 @@ class RandomRotation(Transform): Args: degrees (sequence or number): Range of degrees to select from. If degrees is a number instead of sequence like (min, max), the range of degrees - will be (-degrees, +degrees). + will be [-degrees, +degrees]. interpolation (InterpolationMode, optional): Desired interpolation enum defined by :class:`torchvision.transforms.InterpolationMode`. Default is ``InterpolationMode.NEAREST``. If input is Tensor, only ``InterpolationMode.NEAREST``, ``InterpolationMode.BILINEAR`` are supported.