Spaces:
Running on Zero
Running on Zero
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |
| # All rights reserved. | |
| # | |
| # This source code is licensed under the BSD-style license found in the | |
| # LICENSE file in the root directory of this source tree. | |
| import os | |
| import unittest | |
| from numbers import Real | |
| from pathlib import Path | |
| from typing import Callable, Optional, Union | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| def interactive_testing_requested() -> bool: | |
| """ | |
| Certain tests are only useful when run interactively, and so are not regularly run. | |
| These are activated by this funciton returning True, which the user requests by | |
| setting the environment variable `PYTORCH3D_INTERACTIVE_TESTING` to 1. | |
| """ | |
| return os.environ.get("PYTORCH3D_INTERACTIVE_TESTING", "") == "1" | |
| def skip_opengl_requested() -> bool: | |
| return os.environ.get("PYTORCH3D_NO_TEST_OPENGL", "") == "1" | |
| usesOpengl = unittest.skipIf(skip_opengl_requested(), "uses opengl") | |
| def get_tests_dir() -> Path: | |
| """ | |
| Returns Path for the directory containing this file. | |
| """ | |
| return Path(__file__).resolve().parent | |
| def get_pytorch3d_dir() -> Path: | |
| """ | |
| Returns Path for the root PyTorch3D directory. | |
| Meta internal systems need a special case here. | |
| """ | |
| if os.environ.get("INSIDE_RE_WORKER") is not None: | |
| return Path(__file__).resolve().parent.parent | |
| elif os.environ.get("CONDA_BUILD_STATE", "") == "TEST": | |
| return Path(os.environ["SRC_DIR"]) | |
| else: | |
| return Path(__file__).resolve().parent.parent | |
| def load_rgb_image(filename: str, data_dir: Union[str, Path]): | |
| filepath = os.path.join(data_dir, filename) | |
| with Image.open(filepath) as raw_image: | |
| image = torch.from_numpy(np.array(raw_image) / 255.0) | |
| image = image.to(dtype=torch.float32) | |
| return image[..., :3] | |
| TensorOrArray = Union[torch.Tensor, np.ndarray] | |
| def get_random_cuda_device() -> str: | |
| """ | |
| Function to get a random GPU device from the | |
| available devices. This is useful for testing | |
| that custom cuda kernels can support inputs on | |
| any device without having to set the device explicitly. | |
| """ | |
| num_devices = torch.cuda.device_count() | |
| device_id = ( | |
| torch.randint(high=num_devices, size=(1,)).item() if num_devices > 1 else 0 | |
| ) | |
| return "cuda:%d" % device_id | |
| class TestCaseMixin(unittest.TestCase): | |
| def assertSeparate(self, tensor1, tensor2) -> None: | |
| """ | |
| Verify that tensor1 and tensor2 have their data in distinct locations. | |
| """ | |
| self.assertNotEqual(tensor1.storage().data_ptr(), tensor2.storage().data_ptr()) | |
| def assertNotSeparate(self, tensor1, tensor2) -> None: | |
| """ | |
| Verify that tensor1 and tensor2 have their data in the same locations. | |
| """ | |
| self.assertEqual(tensor1.storage().data_ptr(), tensor2.storage().data_ptr()) | |
| def assertAllSeparate(self, tensor_list) -> None: | |
| """ | |
| Verify that all tensors in tensor_list have their data in | |
| distinct locations. | |
| """ | |
| ptrs = [i.storage().data_ptr() for i in tensor_list] | |
| self.assertCountEqual(ptrs, set(ptrs)) | |
| def assertNormsClose( | |
| self, | |
| input: TensorOrArray, | |
| other: TensorOrArray, | |
| norm_fn: Callable[[TensorOrArray], TensorOrArray], | |
| *, | |
| rtol: float = 1e-05, | |
| atol: float = 1e-08, | |
| equal_nan: bool = False, | |
| msg: Optional[str] = None, | |
| ) -> None: | |
| """ | |
| Verifies that two tensors or arrays have the same shape and are close | |
| given absolute and relative tolerance; raises AssertionError otherwise. | |
| A custom norm function is computed before comparison. If no such pre- | |
| processing needed, pass `torch.abs` or, equivalently, call `assertClose`. | |
| Args: | |
| input, other: two tensors or two arrays. | |
| norm_fn: The function evaluates | |
| `all(norm_fn(input - other) <= atol + rtol * norm_fn(other))`. | |
| norm_fn is a tensor -> tensor function; the output has: | |
| * all entries non-negative, | |
| * shape defined by the input shape only. | |
| rtol, atol, equal_nan: as for torch.allclose. | |
| msg: message in case the assertion is violated. | |
| Note: | |
| Optional arguments here are all keyword-only, to avoid confusion | |
| with msg arguments on other assert functions. | |
| """ | |
| self.assertEqual(np.shape(input), np.shape(other)) | |
| diff = norm_fn(input - other) | |
| other_ = norm_fn(other) | |
| # We want to generalize allclose(input, output), which is essentially | |
| # all(diff <= atol + rtol * other) | |
| # but with a sophisticated handling non-finite values. | |
| # We work that around by calling allclose() with the following arguments: | |
| # allclose(diff + other_, other_). This computes what we want because | |
| # all(|diff + other_ - other_| <= atol + rtol * |other_|) == | |
| # all(|norm_fn(input - other)| <= atol + rtol * |norm_fn(other)|) == | |
| # all(norm_fn(input - other) <= atol + rtol * norm_fn(other)). | |
| self.assertClose( | |
| diff + other_, other_, rtol=rtol, atol=atol, equal_nan=equal_nan, msg=msg | |
| ) | |
| def assertClose( | |
| self, | |
| input: TensorOrArray, | |
| other: TensorOrArray, | |
| *, | |
| rtol: float = 1e-05, | |
| atol: float = 1e-08, | |
| equal_nan: bool = False, | |
| msg: Optional[str] = None, | |
| ) -> None: | |
| """ | |
| Verifies that two tensors or arrays have the same shape and are close | |
| given absolute and relative tolerance, i.e. checks | |
| `all(|input - other| <= atol + rtol * |other|)`; | |
| raises AssertionError otherwise. | |
| Args: | |
| input, other: two tensors or two arrays. | |
| rtol, atol, equal_nan: as for torch.allclose. | |
| msg: message in case the assertion is violated. | |
| Note: | |
| Optional arguments here are all keyword-only, to avoid confusion | |
| with msg arguments on other assert functions. | |
| """ | |
| self.assertEqual(np.shape(input), np.shape(other)) | |
| backend = torch if torch.is_tensor(input) else np | |
| close = backend.allclose( | |
| input, other, rtol=rtol, atol=atol, equal_nan=equal_nan | |
| ) | |
| if close: | |
| return | |
| # handle bool case | |
| if backend == torch and input.dtype == torch.bool: | |
| diff = (input != other).float() | |
| ratio = diff | |
| if backend == np and input.dtype == bool: | |
| diff = (input != other).astype(float) | |
| ratio = diff | |
| else: | |
| diff = backend.abs(input + 0.0 - other) | |
| ratio = diff / backend.abs(other) | |
| try_relative = (diff <= atol) | (backend.isfinite(ratio) & (ratio > 0)) | |
| if try_relative.all(): | |
| if backend == np: | |
| # Avoid a weirdness with zero dimensional arrays. | |
| ratio = np.array(ratio) | |
| ratio[diff <= atol] = 0 | |
| extra = f" Max relative diff {ratio.max()}" | |
| else: | |
| extra = "" | |
| shape = tuple(input.shape) | |
| loc = np.unravel_index(int(diff.argmax()), shape) | |
| max_diff = diff.max() | |
| err = f"Not close. Max diff {max_diff}.{extra} Shape {shape}. At {loc}." | |
| if msg is not None: | |
| self.fail(f"{msg} {err}") | |
| self.fail(err) | |
| def assertConstant( | |
| self, input: TensorOrArray, value: Real, *, atol: float = 0 | |
| ) -> None: | |
| """ | |
| Asserts input is entirely filled with value. | |
| Args: | |
| input: tensor or array | |
| value: expected value | |
| atol: tolerance | |
| """ | |
| mn, mx = input.min(), input.max() | |
| msg = f"values in range [{mn}, {mx}], not {value}, shape {input.shape}" | |
| if atol == 0: | |
| self.assertEqual(input.min(), value, msg=msg) | |
| self.assertEqual(input.max(), value, msg=msg) | |
| else: | |
| self.assertGreater(input.min(), value - atol, msg=msg) | |
| self.assertLess(input.max(), value + atol, msg=msg) | |