9 Commits

Author SHA1 Message Date
generatedunixname89002005307016
88e182f989 Suppress type errors for Pyre upgrade
Summary:
This diff was automatically generated by the Pyre per-target upgrade tool.

It adds `# pyrefly: ignore` comments for type errors that an upcoming Pyrefly version will report. These suppressions keep existing type-checking tests passing while the upgrade rolls out. This diff changes comments only; it does not change Python runtime behavior or application logic.

pyre-upgrade-september-29

#pyreupgrade

Differential Revision: D122460195

fbshipit-source-id: 2ca62cd61e77d222c5b42ac89e73fc570a9781ae
2026-09-29 23:00:50 -07:00
Jeremy Reizenstein
978cd99221 replace IASF/FASI pulsar macros with inline bit-cast functions
Summary: This avoids UB as reported in https://github.com/facebookresearch/pytorch3d/pull/2035

Reviewed By: MichaelRamamonjisoa

Differential Revision: D113009614

fbshipit-source-id: 92e3c6fe34731104ce85de909892b292b4464160
2026-09-15 03:55:04 -07:00
Leo Camus
716c594eab Fix symeig3x3 for inputs that are not of order 1 (#2045)
Summary:
## Motivation

`symeig3x3` uses a fixed absolute epsilon (`torch.finfo(torch.float).eps`) in several places where the magnitude of the input actually matters:

- `p1.clamp(self._eps)`
- the `diag_soft_cond` test, which decides whether a matrix is treated as diagonal
- the `+= eps * sign(...)` regularizers in `_get_ev0` and `_get_ev1`

Since those thresholds are absolute, the routine only behaves as intended for matrices whose entries are of order 1. Once the entries drop below roughly `1e-3` the regularizers dominate the data and the output stops being an eigendecomposition at all. At the other end, `1e10` is already wrong and `1e30` returns NaN.

This is not an exotic corner. `estimate_pointcloud_normals` and `estimate_pointcloud_local_coord_frames` call `symeig3x3` by default (`use_symeig_workaround=True`) on per-point covariance matrices, and those scale with the square of the local neighbourhood size. The denser the point cloud, the smaller the covariances, and the worse the normals get, silently.

Points sampled on a sphere, `neighborhood_size=50`, median angle between the estimated normal and the true radial normal:

| points | radius | mean abs cov entry | main | this PR | `torch.linalg.eigh` |
|--------|--------|--------------------|------|---------|---------------------|
| 4 000 | 1.0 | 4.5e-03 | 1.60° | 1.61° | 1.61° |
| 20 000 | 1.0 | 9.1e-04 | 1.55° | 0.68° | 0.68° |
| 100 000 | 1.0 | 1.8e-04 | 31.86° | 0.31° | 0.31° |
| 4 000 | 0.1 | 4.5e-05 | 50.24° | 1.54° | 1.54° |

This looks like the cause of https://github.com/facebookresearch/pytorch3d/issues/1577, where the reporter narrowed bad normals down to `symeig3x3` and found that passing `use_symeig_workaround=False` fixed them.

## Fix

The decomposition is homogeneous: `eig(s * A) == s * eig(A)` and the eigenvectors do not change. So preconditioning each matrix to unit maximum magnitude and undoing the scaling on the eigenvalues afterwards is exact, and it puts the absolute thresholds back in the regime they were written for.

The scale is detached. For any fixed scale the expression is exactly the eigendecomposition of the input, so gradients are unaffected, and this avoids differentiating through `amax`.

Max relative reconstruction error `max|V diag(w) Vᵀ - A| / max|A|` over 2 000 random symmetric matrices:

| input scale | main | this PR |
|-------------|---------|---------|
| 1e30 | nan | 9.9e-07 |
| 1e10 | 1.00 | 1.0e-06 |
| 1e0 | 1.6e-06 | 1.0e-06 |
| 1e-10 | 1.74 | 9.7e-07 |
| 1e-30 | 1.73 | 9.7e-07 |

float64 inputs break on main below about `1e-8` and are accurate here down to `1e-60`. Exactly-zero, identity, diagonal and rank-1 inputs, and batches mixing magnitudes within one call, all behave.

## Tests

Added `test_is_eigen_small_magnitude`, which runs the existing eigen check on the same random matrices scaled by `1e-2`, `1e-4` and `1e-6` with a correspondingly scaled `atol`. On main it fails with a max relative difference of 101.6; with the fix it passes.

The 13 existing tests in `tests/test_symeig3x3.py` pass unchanged both before and after, on CPU and on GPU, `gradcheck` included.

Cost is one `amax` and one division: no measurable change on GPU, around 2% on CPU at 1e6 matrices.

Pull Request resolved: https://github.com/facebookresearch/pytorch3d/pull/2045

Reviewed By: MichaelRamamonjisoa

Differential Revision: D119970731

Pulled By: bottler

fbshipit-source-id: 35d85a342513c11faca4e6d8ee98052be90172a6
2026-09-15 03:54:46 -07:00
Jeremy Reizenstein
87a6e51e34 Speed up save_obj text formatting, tidy _save helpers
Summary:
Format OBJ text through detached numpy views instead of per-element tensor indexing, and simplify the helpers accordingly.

___

Differential Revision: D119966991

fbshipit-source-id: a3c0bc069431eb1feb913069de922914c2e1ab5e
2026-09-15 03:53:54 -07:00
trueoneplusone
fa22ad16c4 Allow saving OBJ UVs without a texture map (#2047)
Summary:
Closes https://github.com/facebookresearch/pytorch3d/issues/2001.

`save_obj` currently omits UV coordinates and face UV indices unless `texture_map` is also supplied. This loses UV topology when exporting mesh optimization snapshots or meshes whose UVs describe non-color data, unless a redundant texture image and material file are written for every OBJ.

Save UVs whenever both `verts_uvs` and `faces_uvs` are supplied. Material headers and `.mtl`/`.png` files still require a texture map. The public signature is unchanged, and the docstring describes the independent UV export.

The regression test round-trips UV coordinates and distinct face UV indices, both with and without normals, and checks that no material references or sidecar files are created.

## Validation

- Confirmed both regression subcases fail on unchanged main because exported faces omit UV indices.
- Six targeted CPU OBJ IO tests pass: the new regression, existing texture export, normal export, combined normal/texture export, invalid texture inputs, and the pluggable IO texture export.
- Black, flake8, and `git diff --check` pass for the changed files.
- Native extension/full-suite validation was unavailable: the installed MSVC 14.20 toolchain fails while compiling the installed PyTorch headers (C2642/C2737). The targeted tests ran the actual Python OBJ IO code with a local import-only placeholder for `_C`; it exposes only two Pulsar default constants and raises on any native API access. No native operations were exercised. CI should verify the standard test runner with a built extension.

Pull Request resolved: https://github.com/facebookresearch/pytorch3d/pull/2047

Reviewed By: MichaelRamamonjisoa

Differential Revision: D119957610

Pulled By: bottler

fbshipit-source-id: 8ddbbf42b13c264a8f93d699fbbfe96eaa2c0650
2026-09-15 03:53:54 -07:00
Victor Komarov
0a7d4c1a17 Check CUDA device calls in Pulsar renderer
Summary: Wrap Pulsar renderer `cudaGetDevice` and `cudaSetDevice` calls in `C10_CUDA_CHECK` so HIPified AMD builds do not fail on ignored `hipError_t` return values.

Reviewed By: yiminglin-ai, bottler

Differential Revision: D118286930

fbshipit-source-id: 796993ada47734bba0427572ddcde51c19fb47ba
2026-09-03 04:53:35 -07:00
tandede
e73a7e7bfc Fix coordinate indexing in frustum face culling (#2044)
Summary:
Fixes the coordinate indexing used by frustum face culling, and keeps the now-live culling from deleting faces that straddle the camera plane.

`face_verts` has shape `[F, 3, 3]`, where the last two dimensions are the vertex and the xyz coordinate. `_get_culled_faces` indexed it as `face_verts[:, axis]`, which picks one whole vertex out of every face rather than one coordinate out of all three vertices, so `verts_clipped.sum(1) == 3` was asking whether a single vertex was outside the plane on all three axes at once. Every other access in the file agrees with the documented layout — `clip_faces` reads z as `face_verts_unclipped[:, :, 2]`. The fix selects the coordinate with `face_verts[:, :, axis]` and reduces with `all(dim=1)`, which states the documented condition directly: a face is culled only when all three of its vertices lie outside the same plane.

Because the old indexing almost never fired, this is the first time frustum culling does real work, and that exposes a second problem. `_get_culled_faces` runs on the unclipped face verts, and `rasterize_meshes` passes `left`/`right`/`top`/`bottom` in NDC while z stays in world space. The perspective divide mirrors vertices behind the camera through the origin, so a triangle straddling the camera plane can have all 3 projected vertices outside the same xy plane while the part of it in front of the camera still crosses the frustum. At fov 90, for instance, the vertices `(-1.1, 0, 1)`, `(100, 0, -1)` and `(100, 0.1, -1)` all project to x < -1, yet the edge from the first to the second passes through x = 0 while still at z > 0. Culled faces are classified as case 2 and dropped outright, before z clipping could have salvaged them, so that triangle would vanish from the render.

xy culling is therefore now applied only to faces lying entirely in front of the clipping plane (`z >= z_clip_value`, or `z > 0` when no clip value is set), and only when `perspective_correct` is set; straddling faces are left to the z clipping step. Orthographic projections keep usable xy coordinates behind the camera and are unaffected, as is culling on the z axis, which uses world coordinates throughout. The `frustum.cull` check also moves out of the per-plane loop into an early return.

For blast radius: `RasterizationSettings.cull_to_frustum` defaults to `False`, but `rasterize_meshes_python` defaults it to `True`.

Fixes https://github.com/facebookresearch/pytorch3d/issues/1936.

Pull Request resolved: https://github.com/facebookresearch/pytorch3d/pull/2044

Test Plan:
`buck2 test fbcode//vision/fair/pytorch3d:tests`, filtered to the culling and rendering tests (`--regex 'test_render_meshes_clipped|test_render_meshes\b|clip'`): 34 passed, 0 failed, 8 skipped. The skips are the OpenGL-only tests.

Two new unit tests over `_get_culled_faces`:

- `test_cull_faces_uses_coordinate_axis` — for each of the 6 planes, a face whose 3 vertices all lie outside it is culled; and a face intersecting the left plane stays visible for 3 different vertex orders. Fails on the old `face_verts[:, axis]` indexing.
- `test_cull_faces_straddling_perspective_camera` — a face with one vertex in front of the camera and two behind it, all 3 projecting outside the left plane, is not culled under a perspective projection (with and without a `z_clip_value`) but is culled under an orthographic one; and z-axis culling still fires for a face lying entirely behind `znear`. Negative control: with the xy gating disabled and nothing else changed, this test fails on 2 assertions, so it pins the new behaviour rather than restating it.

`arc lint` on `pytorch3d/renderer/mesh/clip.py` and `tests/test_render_meshes_clipped.py`: no issues.

Reviewed By: MichaelRamamonjisoa

Differential Revision: D117200371

Pulled By: bottler

fbshipit-source-id: 99422fc69f4f5725bd82f6af205a0a333a4aef40
2026-08-27 06:32:44 -07:00
Jeremy Reizenstein
a081d766b0 Fix flaky ICP test_heterogeneous_inputs
Summary:
`TestICP.test_heterogeneous_inputs` has been failing intermittently for a long
time. The seed was already bumped from 4 to 14 in D80625966 for the same
reason, which relocated the failure rather than removing it.

Root cause: the test aligned two independent random point clouds, and with
`estimate_scale=True` that problem is ill-posed. ICP almost always collapses
`X` onto a single point of `Y`, driving `s` to ~1e-16 and the rmse to zero.
That solution fits perfectly, but the rotation of a cloud that has shrunk to a
point is completely unconstrained, so the batched run and the per-cloud runs
each returned an arbitrary, and different, `R`. Over 600 seeds of the old data
~45% of batch elements collapsed, and every element that exceeded the
tolerance was a collapsed one, with `R` the only quantity that disagreed
(`T`, `s` and `Xt` always matched). This is the same non-uniqueness that
`corresponding_points_alignment` warns about with "Excessively low rank of
cross-correlation".

Which seeds tripped over it came down to float32 rounding: the batched path
sums over the zero-weighted padding and the per-cloud path does not, so the
two differ by ~1e-7, and that difference decides which arbitrary rotation
comes out. It therefore moves with GPU model, BLAS version and TF32, which is
why picking a seed was never a fix - forcing TF32 on makes seed 14 fail
immediately.

Fix: build `Y` as a rigidly moved copy of `X` plus a few extra points. The
clouds still have different sizes within a batch and between `X` and `Y`, so
the padding and masking path is exercised exactly as before, but the alignment
now has a well-determined optimum that ICP cannot collapse.

Also loosen `atol` from 1e-5 to 1e-4. That is needed independently of the
collapse: the two runs sum a different number of terms and so round
differently, and the legitimate deviation on `Xt` reaches 1.17e-5, above the
old tolerance. That was a second latent failure waiting to happen.

The test no longer emits the "Excessively low rank of cross-correlation"
warning, which is the collapse disappearing.

___

Differential Revision: D117539518

fbshipit-source-id: 73e3d70a8f7547de7179b71e6a705fc37f4920c7
2026-08-26 10:47:50 -07:00
generatedunixname89002005307016
fdaf9bd6fe Remove unused type error suppressions
Summary:
This diff was automatically generated by the Pyre per-target upgrade tool.

It removes `# pyre-fixme` or `pyrefly: ignore` comments that are no longer needed because the underlying type errors have been resolved.
Note that it will also aim to ensure type checking runs cleanly, and will add suppressions to existing type errors.

#pyreupgrade

Differential Revision: D116557086

fbshipit-source-id: 1337613d4fb3ab79bd3a733f3a4c9a499a72f971
2026-08-18 22:34:31 -07:00
75 changed files with 384 additions and 289 deletions

View File

@@ -117,16 +117,12 @@ class Experiment(Configurable):
will be saved here.
"""
# pyre-fixme[13]: Attribute `data_source` is never initialized.
data_source: DataSourceBase
data_source_class_type: str = "ImplicitronDataSource"
# pyre-fixme[13]: Attribute `model_factory` is never initialized.
model_factory: ModelFactoryBase
model_factory_class_type: str = "ImplicitronModelFactory"
# pyre-fixme[13]: Attribute `optimizer_factory` is never initialized.
optimizer_factory: OptimizerFactoryBase
optimizer_factory_class_type: str = "ImplicitronOptimizerFactory"
# pyre-fixme[13]: Attribute `training_loop` is never initialized.
training_loop: TrainingLoopBase
training_loop_class_type: str = "ImplicitronTrainingLoop"

View File

@@ -59,7 +59,6 @@ class ImplicitronModelFactory(ModelFactoryBase):
"""
# pyre-fixme[13]: Attribute `model` is never initialized.
model: ImplicitronModelBase
model_class_type: str = "GenericModel"
resume: bool = True

View File

@@ -169,7 +169,6 @@ class ImplicitronOptimizerFactory(OptimizerFactoryBase):
gamma=self.gamma,
)
elif self.lr_policy.casefold() == "Exponential".casefold():
# pyre-fixme[28]: Unexpected keyword argument `verbose`.
scheduler = torch.optim.lr_scheduler.LambdaLR(
optimizer,
lambda epoch: self.gamma ** (epoch / self.exponential_lr_step_size),
@@ -190,9 +189,7 @@ class ImplicitronOptimizerFactory(OptimizerFactoryBase):
gamma = self.gamma ** (epoch_rest / self.exponential_lr_step_size)
return gamma
# pyre-fixme[28]: Unexpected keyword argument `verbose`.
scheduler = torch.optim.lr_scheduler.LambdaLR(
# pyrefly: ignore [unexpected-keyword]
optimizer,
_get_lr,
# pyrefly: ignore [unexpected-keyword]

View File

@@ -36,7 +36,6 @@ class TrainingLoopBase(ReplaceableBase):
evaluator: An EvaluatorBase instance, used to evaluate training results.
"""
# pyre-fixme[13]: Attribute `evaluator` is never initialized.
evaluator: Optional[EvaluatorBase]
evaluator_class_type: Optional[str] = "ImplicitronEvaluator"
@@ -380,7 +379,6 @@ class ImplicitronTrainingLoop(TrainingLoopBase):
# update the stats logger
stats.update(preds, time_start=t_start, stat_set=trainmode)
# pyre-ignore [16]
assert stats.it[trainmode] == it, "inconsistent stat iteration number!"
# print textual status update

View File

@@ -23,7 +23,6 @@ def meshgrid_ij(
Like torch.meshgrid was before PyTorch 1.10.0, i.e. with indexing set to ij
"""
if (
# pyre-fixme[16]: Callable `meshgrid` has no attribute `__kwdefaults__`.
torch.meshgrid.__kwdefaults__ is not None
and "indexing" in torch.meshgrid.__kwdefaults__
):

View File

@@ -77,6 +77,20 @@ class _SymEig3x3(nn.Module):
if inputs.shape[-2:] != (3, 3):
raise ValueError("Only inputs of shape (..., 3, 3) are supported.")
# The thresholds and regularizers used below are absolute, so they are
# only meaningful for inputs of order 1: rescale each matrix to unit
# maximum magnitude and undo it on the eigenvalues. The clamp only
# matters for the all-zero matrix. The scale is detached because the
# decomposition is homogeneous of degree 1, so its Jacobian is
# homogeneous of degree 0 and holding the scale constant is exact.
scale = (
inputs.detach()
.abs()
.amax(dim=(-2, -1), keepdim=True)
.clamp(min=torch.finfo(inputs.dtype).tiny)
)
inputs = inputs / scale
inputs_diag = inputs.diagonal(dim1=-2, dim2=-1)
inputs_trace = inputs_diag.sum(-1)
q = inputs_trace / 3.0
@@ -120,7 +134,7 @@ class _SymEig3x3(nn.Module):
else:
eigenvecs = None
return eigenvals, eigenvecs
return eigenvals * scale.squeeze(-1), eigenvecs
def _construct_eigenvecs_set(
self, inputs: torch.Tensor, eigenvals: torch.Tensor

View File

@@ -159,8 +159,6 @@ INLINE DEVICE float3 WARP_SUM_FLOAT3(
#define FFLOOR(a) floorf(a)
#define FROUND(x) nearbyintf(x)
#define FABS(a) abs(a)
#define IASF(a, loc) (loc) = __int_as_float(a)
#define FASI(a, loc) (loc) = __float_as_int(a)
#define FABSLEQAS(a, b, c) \
((a) <= (b) ? FSUB((b), (a)) <= (c) : FSUB((a), (b)) < (c))
#define I2F(a) __int2float_rn(a)

View File

@@ -114,8 +114,6 @@ INLINE float atomicMin(float* address, float val) {
#define FMA(x, y, z) ((x) * (y) + (z))
#define I2F(a) static_cast<float>(a)
#define FRCP(x) (1.f / (x))
#define IASF(x, loc) memcpy(&(loc), &(x), sizeof(x))
#define FASI(x, loc) memcpy(&(loc), &(x), sizeof(x))
#define DMAX(a, b) std::max((a), (b))
#define DMIN(a, b) std::min((a), (b))
#define DSATURATE(a) DMIN(1., DMAX(0., (a)))

View File

@@ -9,7 +9,11 @@
#ifndef PULSAR_NATIVE_COMMANDS_ROUTING_H_
#define PULSAR_NATIVE_COMMANDS_ROUTING_H_
#include <cstring>
#include "../global.h"
#if __cplusplus >= 202002L
#include <bit>
#endif
// Commands available everywhere.
#define MALLOC_HOST(VAR, TYPE, SIZE) \
@@ -32,4 +36,37 @@
#include "../host/commands.h"
#endif
// Host + device int <-> float bit punning. Replaces IASF / FASI macros.
IHD float pulsar_int_as_float(int v) noexcept {
#ifdef __CUDA_ARCH__
return __int_as_float(v);
#elif defined(__HIP_DEVICE_COMPILE__)
return __int_as_float(v);
#else
#if __cpp_lib_bit_cast >= 201806L
return std::bit_cast<float>(v);
#else
float f;
std::memcpy(&f, &v, sizeof(f));
return f;
#endif
#endif
}
IHD int pulsar_float_as_int(float v) noexcept {
#ifdef __CUDA_ARCH__
return __float_as_int(v);
#elif defined(__HIP_DEVICE_COMPILE__)
return __float_as_int(v);
#else
#if __cpp_lib_bit_cast >= 201806L
return std::bit_cast<int>(v);
#else
int i;
std::memcpy(&i, &v, sizeof(i));
return i;
#endif
#endif
}
#endif

View File

@@ -121,8 +121,8 @@ GLOBAL void calc_gradients(
grad_im_l[2]);
// Start processing.
for (int grad_idx = 0; grad_idx < n_track; ++grad_idx) {
int sphere_idx;
FASI(forw_info_d[fwi_loc + 3 + 2 * grad_idx], sphere_idx);
int sphere_idx =
pulsar_float_as_int(forw_info_d[fwi_loc + 3 + 2 * grad_idx]);
PASSERT(
sphere_idx == -1 ||
sphere_idx >= 0 && static_cast<uint>(sphere_idx) < num_balls);

View File

@@ -41,7 +41,8 @@ GLOBAL void fill_bg(
// sphere IDs and intersection depths.
for (int i = 0; i < renderer.n_track; ++i) {
int sphere_id = -1;
IASF(sphere_id, renderer.forw_info_d[write_loc + 3 + i * 2]);
renderer.forw_info_d[write_loc + 3 + i * 2] =
pulsar_int_as_float(sphere_id);
renderer.forw_info_d[write_loc + 3 + i * 2 + 1] = -1.f;
}
if (mode == 0) {

View File

@@ -406,7 +406,7 @@ GLOBAL void render(
IMIN(n_track, tracker.get_n_hits()));
for (int i = 0; i < n_track; ++i) {
int sphere_id = tracker.get_closest_sphere_id(i);
IASF(sphere_id, forw_info_d[write_loc + 3 + i * 2]);
forw_info_d[write_loc + 3 + i * 2] = pulsar_int_as_float(sphere_id);
forw_info_d[write_loc + 3 + i * 2 + 1] =
tracker.get_closest_sphere_depth(i) == MAX_FLOAT
? -1.f

View File

@@ -11,6 +11,7 @@
#include "./util.h"
#ifdef WITH_CUDA
#include <c10/cuda/CUDAException.h>
#include <c10/cuda/CUDAGuard.h>
#endif
@@ -106,10 +107,10 @@ void Renderer::ensure_on_device(torch::Device device, bool /*non_blocking*/) {
LOG_IF(INFO, PULSAR_LOG_INIT)
<< "Transferring render buffers between devices.";
int prev_active;
cudaGetDevice(&prev_active);
C10_CUDA_CHECK(cudaGetDevice(&prev_active));
if (this->device_type == c10::DeviceType::CUDA) {
LOG_IF(INFO, PULSAR_LOG_INIT) << " Destructing on CUDA.";
cudaSetDevice(this->device_index);
C10_CUDA_CHECK(cudaSetDevice(this->device_index));
for (auto& nrend : this->renderer_vec) {
PRE::destruct<true>(&nrend);
}
@@ -121,7 +122,7 @@ void Renderer::ensure_on_device(torch::Device device, bool /*non_blocking*/) {
}
if (device.type() == c10::DeviceType::CUDA) {
LOG_IF(INFO, PULSAR_LOG_INIT) << " Constructing on CUDA.";
cudaSetDevice(device.index());
C10_CUDA_CHECK(cudaSetDevice(device.index()));
for (auto& nrend : this->renderer_vec) {
PRE::construct<true>(
&nrend,
@@ -149,7 +150,7 @@ void Renderer::ensure_on_device(torch::Device device, bool /*non_blocking*/) {
this->n_track());
}
}
cudaSetDevice(prev_active);
C10_CUDA_CHECK(cudaSetDevice(prev_active));
this->device_type = device.type();
this->device_index = device.index();
#else
@@ -743,8 +744,8 @@ std::tuple<torch::Tensor, torch::Tensor> Renderer::forward(
// moved to a CUDA device).
#ifdef WITH_CUDA
int prev_active;
cudaGetDevice(&prev_active);
cudaSetDevice(this->device_index);
C10_CUDA_CHECK(cudaGetDevice(&prev_active));
C10_CUDA_CHECK(cudaSetDevice(this->device_index));
#ifdef PULSAR_TIMINGS_BATCHED_ENABLED
START_TIME_CU(batch_forward);
#endif
@@ -789,7 +790,7 @@ std::tuple<torch::Tensor, torch::Tensor> Renderer::forward(
std::cout << "Forward render batched time per example: "
<< time_ms / static_cast<float>(batch_size) << "ms" << std::endl;
#endif
cudaSetDevice(prev_active);
C10_CUDA_CHECK(cudaSetDevice(prev_active));
#endif
} else {
#ifdef PULSAR_TIMINGS_BATCHED_ENABLED
@@ -1100,8 +1101,8 @@ Renderer::backward(
// the renderer to a CUDA device if not built with CUDA.
#ifdef WITH_CUDA
int prev_active;
cudaGetDevice(&prev_active);
cudaSetDevice(this->device_index);
C10_CUDA_CHECK(cudaGetDevice(&prev_active));
C10_CUDA_CHECK(cudaSetDevice(this->device_index));
#ifdef PULSAR_TIMINGS_BATCHED_ENABLED
START_TIME_CU(batch_backward);
#endif
@@ -1205,7 +1206,7 @@ Renderer::backward(
at::cuda::getCurrentCUDAStream());
}
}
cudaSetDevice(prev_active);
C10_CUDA_CHECK(cudaSetDevice(prev_active));
#ifdef PULSAR_TIMINGS_BATCHED_ENABLED
STOP_TIME_CU(batch_backward);
float time_ms;

View File

@@ -52,11 +52,8 @@ class ImplicitronDataSource(DataSourceBase):
data_loader_map_provider_class_type: identifies type for data_loader_map_provider.
"""
# pyre-fixme[13]: Attribute `dataset_map_provider` is never initialized.
dataset_map_provider: DatasetMapProviderBase
# pyre-fixme[13]: Attribute `dataset_map_provider_class_type` is never initialized.
dataset_map_provider_class_type: str
# pyre-fixme[13]: Attribute `data_loader_map_provider` is never initialized.
data_loader_map_provider: DataLoaderMapProviderBase
data_loader_map_provider_class_type: str = "SequenceDataLoaderMapProvider"
@@ -78,7 +75,7 @@ class ImplicitronDataSource(DataSourceBase):
)
try:
from .sql_dataset_provider import ( # noqa: F401 # pyre-ignore
from .sql_dataset_provider import ( # noqa: F401
SqlIndexDatasetMapProvider,
)
except ModuleNotFoundError:
@@ -100,7 +97,7 @@ class ImplicitronDataSource(DataSourceBase):
"""
DEPRECATED! The property will be removed in future versions.
"""
if self._all_train_cameras_cache is None: # pyre-ignore[16]
if self._all_train_cameras_cache is None:
all_train_cameras = self.dataset_map_provider.get_all_train_cameras()
self._all_train_cameras_cache = (all_train_cameras,)

View File

@@ -297,7 +297,6 @@ class FrameData(Mapping[str, Any]):
depth_map = self.depth_map
if depth_map is not None:
clamp_bbox_xyxy_depth = rescale_bbox(
# pyrefly: ignore [bad-argument-type]
clamp_bbox_xyxy,
# pyrefly: ignore [bad-argument-type]
tuple(depth_map.shape[-2:]),
@@ -312,7 +311,6 @@ class FrameData(Mapping[str, Any]):
depth_mask = self.depth_mask
if depth_mask is not None:
clamp_bbox_xyxy_depth = rescale_bbox(
# pyrefly: ignore [bad-argument-type]
clamp_bbox_xyxy,
# pyrefly: ignore [bad-argument-type]
tuple(depth_mask.shape[-2:]),

View File

@@ -175,7 +175,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
self._filter_db() # also computes sequence indices
self._extract_and_set_eval_batches()
# pyre-ignore
self._frame_data_builder = FrameDataBuilder(
dataset_root=self.dataset_root,
load_images=self.load_images,
@@ -220,7 +219,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
raise ValueError("This function can only join a list of JsonIndexDataset")
# pyre-ignore[16]
self.frame_annots.extend([fa for d in other_datasets for fa in d.frame_annots])
# pyre-ignore[16]
self.seq_annots.update(
# https://gist.github.com/treyhunner/f35292e676efa0be1728
functools.reduce(
@@ -301,7 +299,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
self.frame_annots[idx]["frame_annotation"].frame_number: idx
for idx in seq_idx
}
# pyre-ignore[16]
for seq, seq_idx in self._seq_to_idx.items()
}
@@ -374,7 +371,7 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
# Deep copy the whole dataset except frame_annots, which are large so we
# deep copy only the requested subset of frame_annots.
memo = {id(self.frame_annots): None} # pyre-ignore[16]
memo = {id(self.frame_annots): None}
dataset_new = copy.deepcopy(self, memo)
dataset_new.frame_annots = copy.deepcopy(
[self.frame_annots[i] for i in valid_dataset_indices]
@@ -402,11 +399,9 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
return dataset_new
def __str__(self) -> str:
# pyre-ignore[16]
return f"JsonIndexDataset #frames={len(self.frame_annots)}"
def __len__(self) -> int:
# pyre-ignore[16]
return len(self.frame_annots)
def _get_frame_type(self, entry: FrameAnnotsEntry) -> Optional[str]:
@@ -418,7 +413,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
"""
logger.info("Loading all train cameras.")
cameras = []
# pyre-ignore[16]
for frame_idx, frame_annot in enumerate(tqdm(self.frame_annots)):
frame_type = self._get_frame_type(frame_annot)
if frame_type is None:
@@ -429,16 +423,13 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
return join_cameras_as_batch(cameras)
def __getitem__(self, index) -> FrameData:
# pyre-ignore[16]
if index >= len(self.frame_annots):
raise IndexError(f"index {index} out of range {len(self.frame_annots)}")
entry = self.frame_annots[index]["frame_annotation"]
# pyre-ignore
frame_data = self._frame_data_builder.build(
entry,
# pyre-ignore
self.seq_annots[entry.sequence_name],
)
# Optional field
@@ -483,7 +474,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
for subset, frames in subset_to_seq_frame.items()
for _, _, path in frames
}
# pyre-ignore[16]
for frame in self.frame_annots:
frame["subset"] = frame_path_to_subset.get(
frame["frame_annotation"].image.path, None
@@ -496,7 +486,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
def _sort_frames(self) -> None:
# Sort frames to have them grouped by sequence, ordered by timestamp
# pyre-ignore[16]
self.frame_annots = sorted(
self.frame_annots,
key=lambda f: (
@@ -508,7 +497,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
def _filter_db(self) -> None:
if self.remove_empty_masks:
logger.info("Removing images with empty masks.")
# pyre-ignore[16]
old_len = len(self.frame_annots)
msg = "remove_empty_masks needs every MaskAnnotation.mass to be set."
@@ -549,7 +537,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
if len(self.limit_category_to) > 0:
logger.info(f"Limiting dataset to categories: {self.limit_category_to}")
# pyre-ignore[16]
self.seq_annots = {
name: entry
for name, entry in self.seq_annots.items()
@@ -587,7 +574,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
if self.n_frames_per_sequence > 0:
logger.info(f"Taking max {self.n_frames_per_sequence} per sequence.")
keep_idx = []
# pyre-ignore[16]
for seq, seq_indices in self._seq_to_idx.items():
# infer the seed from the sequence name, this is reproducible
# and makes the selection differ for different sequences
@@ -617,7 +603,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
self._invalidate_seq_to_idx()
if filter_seq_annots:
# pyre-ignore[16]
self.seq_annots = {
k: v
for k, v in self.seq_annots.items()
@@ -627,7 +612,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
def _invalidate_seq_to_idx(self) -> None:
seq_to_idx = defaultdict(list)
# pyre-ignore[16]
for idx, entry in enumerate(self.frame_annots):
seq_to_idx[entry["frame_annotation"].sequence_name].append(idx)
# pyre-ignore[16]
@@ -658,7 +642,6 @@ class JsonIndexDataset(DatasetBase, ReplaceableBase):
def category_to_sequence_names(self) -> Dict[str, List[str]]:
c2seq = defaultdict(list)
# pyre-ignore
for sequence_name, sa in self.seq_annots.items():
c2seq[sa.category].append(sequence_name)
return dict(c2seq)

View File

@@ -94,7 +94,6 @@ class JsonIndexDatasetMapProvider(DatasetMapProviderBase):
path_manager_factory_class_type: The class type of `path_manager_factory`.
"""
# pyre-fixme[13]: Attribute `category` is never initialized.
category: str
task_str: str = "singlesequence"
dataset_root: str = _CO3D_DATASET_ROOT
@@ -104,10 +103,8 @@ class JsonIndexDatasetMapProvider(DatasetMapProviderBase):
test_restrict_sequence_id: int = -1
assert_single_seq: bool = False
only_test_set: bool = False
# pyre-fixme[13]: Attribute `dataset` is never initialized.
dataset: JsonIndexDataset
dataset_class_type: str = "JsonIndexDataset"
# pyre-fixme[13]: Attribute `path_manager_factory` is never initialized.
path_manager_factory: PathManagerFactory
path_manager_factory_class_type: str = "PathManagerFactory"

View File

@@ -169,9 +169,7 @@ class JsonIndexDatasetMapProviderV2(DatasetMapProviderBase):
path_manager_factory_class_type: The class type of `path_manager_factory`.
"""
# pyre-fixme[13]: Attribute `category` is never initialized.
category: str
# pyre-fixme[13]: Attribute `subset_name` is never initialized.
subset_name: str
dataset_root: str = _CO3DV2_DATASET_ROOT
@@ -183,10 +181,8 @@ class JsonIndexDatasetMapProviderV2(DatasetMapProviderBase):
n_known_frames_for_test: int = 0
dataset_class_type: str = "JsonIndexDataset"
# pyre-fixme[13]: Attribute `dataset` is never initialized.
dataset: JsonIndexDataset
# pyre-fixme[13]: Attribute `path_manager_factory` is never initialized.
path_manager_factory: PathManagerFactory
path_manager_factory_class_type: str = "PathManagerFactory"

View File

@@ -76,12 +76,10 @@ class RenderedMeshDatasetMapProvider(DatasetMapProviderBase):
resolution: int = 128
use_point_light: bool = True
gpu_idx: Optional[int] = 0
# pyre-fixme[13]: Attribute `path_manager_factory` is never initialized.
path_manager_factory: PathManagerFactory
path_manager_factory_class_type: str = "PathManagerFactory"
def get_dataset_map(self) -> DatasetMap:
# pyre-ignore[16]
return DatasetMap(train=self.train_dataset, val=None, test=None)
def get_all_train_cameras(self) -> CamerasBase:
@@ -117,10 +115,8 @@ class RenderedMeshDatasetMapProvider(DatasetMapProviderBase):
device=device,
use_point_light=self.use_point_light,
)
# pyre-ignore[16]
self.poses = poses.cpu()
# pyre-ignore[16]
self.train_dataset = SingleSceneDataset( # pyre-ignore[28]
self.train_dataset = SingleSceneDataset(
# pyrefly: ignore [unexpected-keyword]
object_name="cow",
# pyrefly: ignore [unexpected-keyword]

View File

@@ -99,11 +99,8 @@ class SingleSceneDatasetMapProviderBase(DatasetMapProviderBase):
testing frame.
"""
# pyre-fixme[13]: Attribute `base_dir` is never initialized.
base_dir: str
# pyre-fixme[13]: Attribute `object_name` is never initialized.
object_name: str
# pyre-fixme[13]: Attribute `path_manager_factory` is never initialized.
path_manager_factory: PathManagerFactory
path_manager_factory_class_type: str = "PathManagerFactory"
n_known_frames_for_test: Optional[int] = None
@@ -149,7 +146,6 @@ class SingleSceneDatasetMapProviderBase(DatasetMapProviderBase):
split = np.concatenate([split, train_split])
frame_types.extend([DATASET_TYPE_KNOWN] * len(train_split))
# pyre-ignore[28]
return SingleSceneDataset(
object_name=self.object_name,
# pyre-ignore[16]

View File

@@ -205,7 +205,7 @@ class SqlIndexDataset(DatasetBase, ReplaceableBase):
logger.info(str(self))
if self.scoped_session:
self._session_factory = sessionmaker(bind=self._sql_engine) # pyre-ignore
self._session_factory = sessionmaker(bind=self._sql_engine)
if self.precompute_seq_to_idx:
# This is deprecated and will be removed in the future.
@@ -215,7 +215,7 @@ class SqlIndexDataset(DatasetBase, ReplaceableBase):
)
self._index["rowid"] = np.arange(len(self._index))
groupby = self._index.groupby("sequence_name", sort=False)["rowid"]
self._seq_to_indices = dict(groupby.apply(list)) # pyre-ignore
self._seq_to_indices = dict(groupby.apply(list))
del self._index["rowid"]
def __len__(self) -> int:
@@ -280,7 +280,6 @@ class SqlIndexDataset(DatasetBase, ReplaceableBase):
self.sequence_annotations_type.sequence_name == seq
)
if self.scoped_session:
# pyre-ignore
with scoped_session(self._session_factory)() as session:
entry = session.scalars(stmt).one()
seq_metadata = session.scalars(seq_stmt).one()
@@ -404,7 +403,6 @@ class SqlIndexDataset(DatasetBase, ReplaceableBase):
only dataset indices.
"""
if self.precompute_seq_to_idx and subset_filter is None:
# pyre-ignore
yield from self._seq_to_indices[seq_name]
else:
for _, _, idx in self.sequence_frames_in_order(seq_name, subset_filter):
@@ -836,7 +834,7 @@ class SqlIndexDataset(DatasetBase, ReplaceableBase):
if self.scoped_session:
stmt_text = str(stmt.compile(compile_kwargs={"literal_binds": True}))
with scoped_session(self._session_factory)() as session: # pyre-ignore
with scoped_session(self._session_factory)() as session:
frame_no_ts = pd.read_sql_query(stmt_text, session.connection())
else:
with self._sql_engine.connect() as connection:

View File

@@ -193,9 +193,9 @@ class SqlIndexDatasetMapProvider(DatasetMapProviderBase):
# this is a mould that is never constructed, used to build self._dataset_map values
dataset_class_type: str = "SqlIndexDataset"
dataset: SqlIndexDataset # pyre-ignore [13]
dataset: SqlIndexDataset
path_manager_factory: PathManagerFactory # pyre-ignore [13]
path_manager_factory: PathManagerFactory
path_manager_factory_class_type: str = "PathManagerFactory"
def __post_init__(self):

View File

@@ -306,7 +306,6 @@ def _unwrap_type(tp):
def _get_dataclass_field_default(field: Field) -> Any:
if field.default_factory is not MISSING:
# pyre-fixme[29]: `Union[dataclasses._MISSING_TYPE,
# dataclasses._DefaultFactory[typing.Any]]` is not a function.
return field.default_factory()
elif field.default is not MISSING:

View File

@@ -192,7 +192,6 @@ def rescale_bbox(
assert bbox is not None
assert np.prod(orig_res) > 1e-8
# average ratio of dimensions
# pyre-ignore
rel_size = (new_res[0] / orig_res[0] + new_res[1] / orig_res[1]) / 2.0
return bbox * rel_size
@@ -368,7 +367,6 @@ def adjust_camera_to_bbox_crop_(
)
camera.focal_length = focal_length[None]
# pyre-fixme[16]: `PerspectiveCameras` has no attribute `principal_point`.
camera.principal_point = principal_point_cropped[None]
@@ -397,8 +395,7 @@ def adjust_camera_to_image_scale_(
image_size_wh_output,
)
camera.focal_length = focal_length_scaled[None]
# pyre-fixme[16]: `PerspectiveCameras` has no attribute `principal_point`.
camera.principal_point = principal_point_scaled[None] # pyre-ignore[16]
camera.principal_point = principal_point_scaled[None]
# NOTE this cache is per-worker; they are implemented as processes.

View File

@@ -46,7 +46,6 @@ def get_implicitron_sequence_pointcloud(
sequence_entries = [
ei
for ei in sequence_entries
# pyre-ignore[16]
if dataset.frame_annots[ei]["frame_annotation"].sequence_name
== sequence_name
]

View File

@@ -321,7 +321,6 @@ def eval_batch(
# only record depth metrics for the foreground
_, abs_ = eval_depth(
cloned_render["depth_render"],
# pyre-fixme[6]: For 2nd param expected `Tensor` but got
# `Optional[Tensor]`.
frame_data.depth_map,
get_best_scale=True,

View File

@@ -220,6 +220,5 @@ class ResNetFeatureExtractor(FeatureExtractorBase):
if self.feature_rescale != 1.0:
out_feats = {k: self.feature_rescale * f for k, f in out_feats.items()}
# pyre-fixme[7]: Incompatible return type, expected `Dict[typing.Any, Tensor]`
# but got `Dict[typing.Any, float]`
return out_feats

View File

@@ -222,42 +222,34 @@ class GenericModel(ImplicitronModelBase):
# ---- global encoder settings
global_encoder_class_type: Optional[str] = None
# pyre-fixme[13]: Attribute `global_encoder` is never initialized.
global_encoder: Optional[GlobalEncoderBase]
# ---- raysampler
raysampler_class_type: str = "AdaptiveRaySampler"
# pyre-fixme[13]: Attribute `raysampler` is never initialized.
raysampler: RaySamplerBase
# ---- renderer configs
renderer_class_type: str = "MultiPassEmissionAbsorptionRenderer"
# pyre-fixme[13]: Attribute `renderer` is never initialized.
renderer: BaseRenderer
# ---- image feature extractor settings
# (This is only created if view_pooler is enabled)
# pyre-fixme[13]: Attribute `image_feature_extractor` is never initialized.
image_feature_extractor: Optional[FeatureExtractorBase]
image_feature_extractor_class_type: Optional[str] = None
# ---- view pooler settings
view_pooler_enabled: bool = False
# pyre-fixme[13]: Attribute `view_pooler` is never initialized.
view_pooler: Optional[ViewPooler]
# ---- implicit function settings
implicit_function_class_type: str = "NeuralRadianceFieldImplicitFunction"
# This is just a model, never constructed.
# The actual implicit functions live in self._implicit_functions
# pyre-fixme[13]: Attribute `implicit_function` is never initialized.
implicit_function: ImplicitFunctionBase
# ----- metrics
# pyre-fixme[13]: Attribute `view_metrics` is never initialized.
view_metrics: ViewMetricsBase
view_metrics_class_type: str = "ViewMetrics"
# pyre-fixme[13]: Attribute `regularization_metrics` is never initialized.
regularization_metrics: RegularizationMetricsBase
regularization_metrics_class_type: str = "RegularizationMetrics"
@@ -475,7 +467,6 @@ class GenericModel(ImplicitronModelBase):
# pyrefly: ignore [unsupported-operation]
custom_args["global_code"] = global_code
# pyre-fixme[29]: `Union[(self: Tensor) -> Any, Tensor, Module]` is not a
# function.
for func in self._implicit_functions:
func.bind_args(**custom_args)
@@ -499,7 +490,6 @@ class GenericModel(ImplicitronModelBase):
# Unbind the custom arguments to prevent pytorch from storing
# large buffers of intermediate results due to points in the
# bound arguments.
# pyre-fixme[29]: `Union[(self: Tensor) -> Any, Tensor, Module]` is not a
# function.
for func in self._implicit_functions:
func.unbind_args()

View File

@@ -65,7 +65,6 @@ class SequenceAutodecoder(GlobalEncoderBase, torch.nn.Module):
of the frame's sequence identifier.
"""
# pyre-fixme[13]: Attribute `autodecoder` is never initialized.
autodecoder: Autodecoder
def __post_init__(self):

View File

@@ -229,10 +229,8 @@ class MLPWithInputSkips(Configurable, torch.nn.Module):
# if the skip tensor is None, we use `x` instead.
z = x
skipi = 0
# pyre-fixme[6]: For 1st argument expected `Iterable[_T]` but got
# `Union[Tensor, Module]`.
for li, layer in enumerate(self.mlp):
# pyre-fixme[58]: `in` is not supported for right operand type
# `Union[Tensor, Module]`.
if li in self._input_skips:
if self._skip_affine_trans:
@@ -273,7 +271,6 @@ class MLPDecoder(DecoderFunctionBase):
input_dim: int = 3
param_groups: Dict[str, str] = field(default_factory=lambda: {})
# pyre-fixme[13]: Attribute `network` is never initialized.
network: MLPWithInputSkips
def __post_init__(self):
@@ -351,7 +348,6 @@ class TransformerWithInputSkips(torch.nn.Module):
self.last = torch.nn.Linear(dimout, output_dim)
_xavier_init(self.last)
# pyre-fixme[8]: Attribute has type `Tuple[ModuleList, ModuleList]`; used as
# `ModuleList`.
self.layers_pool, self.layers_ray = (
torch.nn.ModuleList(layers_pool),

View File

@@ -177,7 +177,6 @@ class IdrFeatureField(ImplicitFunctionBase, torch.nn.Module):
# pyre-fixme[29]: `Union[(self: TensorBase, indices: Union[None, slice[An...
x = self.linear_layers[layer_idx](x)
# pyre-fixme[29]: `Union[(self: TensorBase, other: Union[bool, complex,
# float, int, Tensor]) -> Tensor, Module, Tensor]` is not a function.
if layer_idx < self.num_layers - 2:
# pyre-fixme[29]: `Union[Module, Tensor]` is not a function.

View File

@@ -125,7 +125,6 @@ class NeuralRadianceFieldBase(ImplicitFunctionBase, torch.nn.Module):
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
rays_embedding = self.harmonic_embedding_dir(rays_directions_normed)
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
return self.color_layer((self.intermediate_linear(features), rays_embedding))
@staticmethod
@@ -196,7 +195,6 @@ class NeuralRadianceFieldBase(ImplicitFunctionBase, torch.nn.Module):
embeds = create_embeddings_for_implicit_function(
xyz_world=rays_points_world,
# for 2nd param but got `Union[None, torch.Tensor, torch.nn.Module]`.
# pyre-fixme[6]: For 2nd argument expected `Optional[(...) -> Any]` but
# got `Union[None, Tensor, Module]`.
xyz_embedding_function=(
self.harmonic_embedding_xyz if self.input_xyz else None
@@ -224,7 +222,6 @@ class NeuralRadianceFieldBase(ImplicitFunctionBase, torch.nn.Module):
if camera is None:
raise ValueError("Camera must be given if xyz_ray_dir_in_camera_coords")
# pyre-fixme[58]: `@` is not supported for operand types `Tensor` and
# `Union[Tensor, Module]`.
directions = ray_bundle.directions @ camera.R
else:

View File

@@ -171,7 +171,6 @@ class SRNPixelGenerator(Configurable, torch.nn.Module):
# Obtain the harmonic embedding of the normalized ray directions.
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
rays_embedding = self._harmonic_embedding(rays_directions_normed)
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
return self._color_layer((features, rays_embedding))
def forward(
@@ -208,7 +207,6 @@ class SRNPixelGenerator(Configurable, torch.nn.Module):
if camera is None:
raise ValueError("Camera must be given if xyz_ray_dir_in_camera_coords")
# pyre-fixme[58]: `@` is not supported for operand types `Tensor` and
# `Union[Tensor, Module]`.
directions = ray_bundle.directions @ camera.R
else:
@@ -331,9 +329,7 @@ class SRNRaymarchHyperNet(Configurable, torch.nn.Module):
@registry.register
class SRNImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
latent_dim: int = 0
# pyre-fixme[13]: Attribute `raymarch_function` is never initialized.
raymarch_function: SRNRaymarchFunction
# pyre-fixme[13]: Attribute `pixel_generator` is never initialized.
pixel_generator: SRNPixelGenerator
def __post_init__(self):
@@ -389,9 +385,7 @@ class SRNHyperNetImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
latent_dim_hypernet: int = 0
latent_dim: int = 0
# pyre-fixme[13]: Attribute `hypernet` is never initialized.
hypernet: SRNRaymarchHyperNet
# pyre-fixme[13]: Attribute `pixel_generator` is never initialized.
pixel_generator: SRNPixelGenerator
def __post_init__(self):

View File

@@ -844,7 +844,6 @@ class VoxelGridModule(Configurable, torch.nn.Module):
"""
voxel_grid_class_type: str = "FullResolutionVoxelGrid"
# pyre-fixme[13]: Attribute `voxel_grid` is never initialized.
voxel_grid: VoxelGridBase
extents: Tuple[float, float, float] = (2.0, 2.0, 2.0)
@@ -907,7 +906,6 @@ class VoxelGridModule(Configurable, torch.nn.Module):
else:
# Torch Module to hold parameters since they can only be registered
# at object level.
# pyrefly: ignore [bad-assignment]
self.params = _RegistratedBufferDict(vars(params))
@staticmethod
@@ -996,7 +994,6 @@ class VoxelGridModule(Configurable, torch.nn.Module):
"""
'''
new_params = {}
# pyre-fixme[29]: `Union[(self: Tensor) -> Any, Tensor, Module]` is not a
# function.
for name in self.params:
key = prefix + "params." + name
@@ -1035,7 +1032,6 @@ class VoxelGridModule(Configurable, torch.nn.Module):
grid_values, _ = self.voxel_grid.change_resolution(
new_grid_values, grid_values_with_wanted_resolution=old_grid_values
)
# pyre-fixme[16]: `VoxelGridModule` has no attribute `params`.
self.params = torch.nn.ParameterDict(
{
k: torch.nn.Parameter(val)

View File

@@ -142,11 +142,9 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
"""
# ---- voxel grid for density
# pyre-fixme[13]: Attribute `voxel_grid_density` is never initialized.
voxel_grid_density: VoxelGridModule
# ---- voxel grid for color
# pyre-fixme[13]: Attribute `voxel_grid_color` is never initialized.
voxel_grid_color: VoxelGridModule
# ---- harmonic embeddings density
@@ -162,12 +160,10 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
# ---- decoder function for density
decoder_density_class_type: str = "MLPDecoder"
# pyre-fixme[13]: Attribute `decoder_density` is never initialized.
decoder_density: DecoderFunctionBase
# ---- decoder function for color
decoder_color_class_type: str = "MLPDecoder"
# pyre-fixme[13]: Attribute `decoder_color` is never initialized.
decoder_color: DecoderFunctionBase
# ---- cuda streams
@@ -190,25 +186,20 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
def __post_init__(self) -> None:
run_auto_creation(self)
# pyre-fixme[16]: `VoxelGridImplicitFunction` has no attribute
# `voxel_grid_scaffold`.
self.voxel_grid_scaffold = self._create_voxel_grid_scaffold()
# pyre-fixme[16]: `VoxelGridImplicitFunction` has no attribute
# `harmonic_embedder_xyz_density`.
self.harmonic_embedder_xyz_density = HarmonicEmbedding(
**self.harmonic_embedder_xyz_density_args
)
# pyre-fixme[16]: `VoxelGridImplicitFunction` has no attribute
# `harmonic_embedder_xyz_color`.
self.harmonic_embedder_xyz_color = HarmonicEmbedding(
**self.harmonic_embedder_xyz_color_args
)
# pyre-fixme[16]: `VoxelGridImplicitFunction` has no attribute
# `harmonic_embedder_dir_color`.
self.harmonic_embedder_dir_color = HarmonicEmbedding(
**self.harmonic_embedder_dir_color_args
)
# pyre-fixme[16]: `VoxelGridImplicitFunction` has no attribute
# `_scaffold_ready`.
self._scaffold_ready = False
@@ -372,7 +363,6 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
feature dimensionality which `decoder_density` returns
"""
embeds_density = self.voxel_grid_density(points)
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
harmonic_embedding_density = self.harmonic_embedder_xyz_density(embeds_density)
# shape = [..., density_dim]
return self.decoder_density(harmonic_embedding_density)
@@ -407,7 +397,6 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
if self.xyz_ray_dir_in_camera_coords:
if camera is None:
raise ValueError("Camera must be given if xyz_ray_dir_in_camera_coords")
# pyre-fixme[58]: `@` is not supported for operand types `Tensor` and
# `Union[Tensor, Module]`.
directions = directions @ camera.R
@@ -417,13 +406,11 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
# ########## embed with the harmonic function ########## #
# Obtain the harmonic embedding of the voxel grid output.
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
harmonic_embedding_color = self.harmonic_embedder_xyz_color(embeds_color)
# Normalize the ray_directions to unit l2 norm.
rays_directions_normed = torch.nn.functional.normalize(directions, dim=-1)
# Obtain the harmonic embedding of the normalized ray directions.
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
harmonic_embedding_dir = self.harmonic_embedder_dir_color(
rays_directions_normed
)
@@ -493,7 +480,6 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
an object inside, else False.
"""
# find bounding box
# pyre-fixme[16]: Item `Tensor` of `Tensor | Module` has no attribute
# `get_grid_points`.
points = self.voxel_grid_scaffold.get_grid_points(epoch=epoch)
assert self._scaffold_ready, "Scaffold has to be calculated before cropping."
@@ -529,7 +515,6 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
"""
planes = []
# pyre-fixme[16]: Item `Tensor` of `Tensor | Module` has no attribute
# `get_grid_points`.
points = self.voxel_grid_scaffold.get_grid_points(epoch=epoch)
@@ -550,9 +535,7 @@ class VoxelGridImplicitFunction(ImplicitFunctionBase, torch.nn.Module):
stride=1,
)
occupancy_cube = density_cube > self.scaffold_empty_space_threshold
# pyre-fixme[16]: Item `Tensor` of `Tensor | Module` has no attribute `params`.
self.voxel_grid_scaffold.params["voxel_grid"] = occupancy_cube.float()
# pyre-fixme[16]: `VoxelGridImplicitFunction` has no attribute
# `_scaffold_ready`.
self._scaffold_ready = True

View File

@@ -195,34 +195,27 @@ class OverfitModel(ImplicitronModelBase):
# ---- global encoder settings
global_encoder_class_type: Optional[str] = None
# pyre-fixme[13]: Attribute `global_encoder` is never initialized.
global_encoder: Optional[GlobalEncoderBase]
# ---- raysampler
raysampler_class_type: str = "AdaptiveRaySampler"
# pyre-fixme[13]: Attribute `raysampler` is never initialized.
raysampler: RaySamplerBase
# ---- renderer configs
renderer_class_type: str = "MultiPassEmissionAbsorptionRenderer"
# pyre-fixme[13]: Attribute `renderer` is never initialized.
renderer: BaseRenderer
# ---- implicit function settings
share_implicit_function_across_passes: bool = False
implicit_function_class_type: str = "NeuralRadianceFieldImplicitFunction"
# pyre-fixme[13]: Attribute `implicit_function` is never initialized.
implicit_function: ImplicitFunctionBase
coarse_implicit_function_class_type: Optional[str] = None
# pyre-fixme[13]: Attribute `coarse_implicit_function` is never initialized.
coarse_implicit_function: Optional[ImplicitFunctionBase]
# ----- metrics
# pyre-fixme[13]: Attribute `view_metrics` is never initialized.
view_metrics: ViewMetricsBase
view_metrics_class_type: str = "ViewMetrics"
# pyre-fixme[13]: Attribute `regularization_metrics` is never initialized.
regularization_metrics: RegularizationMetricsBase
regularization_metrics_class_type: str = "RegularizationMetrics"
@@ -658,7 +651,6 @@ class OverfitModel(ImplicitronModelBase):
implicit_function_type = registry.get(
ImplicitFunctionBase,
# pyre-ignore: config is None allow to check if this is None.
self.coarse_implicit_function_class_type,
)
expand_args_fields(implicit_function_type)

View File

@@ -108,7 +108,6 @@ class ImplicitronRayBundle:
def lengths(self) -> torch.Tensor:
if self.bins is not None:
# equivalent to: 0.5 * (bins[..., 1:] + bins[..., :-1]) but more efficient
# pyre-ignore
return torch.lerp(self.bins[..., :-1], self.bins[..., 1:], 0.5)
# pyrefly: ignore [bad-return]
return self._lengths

View File

@@ -135,7 +135,6 @@ class LSTMRenderer(BaseRenderer, torch.nn.Module):
break
# run the lstm marcher
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
state_h, state_c = self._lstm(
raymarch_features.view(-1, raymarch_features.shape[-1]),
states[-1],

View File

@@ -84,7 +84,6 @@ class MultiPassEmissionAbsorptionRenderer(BaseRenderer, torch.nn.Module):
"""
raymarcher_class_type: str = "EmissionAbsorptionRaymarcher"
# pyre-fixme[13]: Attribute `raymarcher` is never initialized.
raymarcher: RaymarcherBase
n_pts_per_ray_fine_training: int = 64

View File

@@ -42,9 +42,7 @@ class RayPointRefiner(Configurable, torch.nn.Module):
for Anti-Aliasing Neural Radiance Fields." ICCV 2021.
"""
# pyre-fixme[13]: Attribute `n_pts_per_ray` is never initialized.
n_pts_per_ray: int
# pyre-fixme[13]: Attribute `random_sampling` is never initialized.
random_sampling: bool
add_input_samples: bool = True
blurpool_weights: bool = False

View File

@@ -207,7 +207,6 @@ class AbstractMaskRaySampler(RaySamplerBase, torch.nn.Module):
"""
sample_mask = None
if (
# pyre-fixme[29]: `Union[(self: TensorBase, indices: Union[None, slice[An...
self._sampling_mode[evaluation_mode] == RenderSamplingMode.MASK_SAMPLE
and mask is not None
):
@@ -242,7 +241,6 @@ class AbstractMaskRaySampler(RaySamplerBase, torch.nn.Module):
"Heterogeneous ray bundle is not supported for conical frustum computation yet"
)
elif self.cast_ray_bundle_as_cone:
# pyre-fixme[9]: pixel_hw has type `Tuple[float, float]`; used as
# `Tuple[Union[Tensor, Module], Union[Tensor, Module]]`.
pixel_hw: Tuple[float, float] = (self.pixel_height, self.pixel_width)
pixel_radii_2d = compute_radii(cameras, ray_bundle.xys[..., :2], pixel_hw)

View File

@@ -571,7 +571,6 @@ def _get_sphere_intersection(
# cam_loc = cam_loc.unsqueeze(-1)
# ray_cam_dot = torch.bmm(ray_directions, cam_loc).squeeze()
ray_cam_dot = (ray_directions * cam_loc).sum(-1) # n_images x n_rays
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`.
under_sqrt = ray_cam_dot**2 - (cam_loc.norm(2, dim=-1) ** 2 - r**2)
under_sqrt = under_sqrt.reshape(-1)

View File

@@ -27,7 +27,6 @@ from .rgb_net import RayNormalColoringNetwork
class SignedDistanceFunctionRenderer(BaseRenderer, torch.nn.Module):
render_features_dimensions: int = 3
object_bounding_sphere: float = 1.0
# pyre-fixme[13]: Attribute `ray_tracer` is never initialized.
ray_tracer: RayTracing
ray_normal_coloring_network_args: DictConfig = get_default_args_field(
RayNormalColoringNetwork
@@ -208,7 +207,6 @@ class SignedDistanceFunctionRenderer(BaseRenderer, torch.nn.Module):
]
normals_full.view(-1, 3)[surface_mask] = normals
render_full.view(-1, self.render_features_dimensions)[surface_mask] = (
# pyre-fixme[29]: `Union[Tensor, Module]` is not a function.
self._rgb_network(
features,
differentiable_surface_points[None],

View File

@@ -532,7 +532,6 @@ def _get_ray_dir_dot_prods(camera: CamerasBase, pts: torch.Tensor):
# does not produce nans randomly unlike get_camera_center() below
cam_centers_rep = -torch.bmm(
# pyre-fixme[29]: `Union[(self: TensorBase, indices: Union[None, slice[Any, A...
camera_rep.T[:, None],
camera_rep.R.permute(0, 2, 1),
).reshape(-1, *([1] * (pts.ndim - 2)), 3)
@@ -632,7 +631,6 @@ def _avgmaxstd_reduction_function(
x_aggr = torch.cat(pooled_features, dim=-1)
# zero out features that were all masked out
# pyre-fixme[16]: `bool` has no attribute `type_as`.
any_active = (w.max(dim=dim, keepdim=True).values > 1e-4).type_as(x_aggr)
x_aggr = x_aggr * any_active[..., None]
@@ -660,7 +658,6 @@ def _std_reduction_function(
):
if mu is None:
mu = _avg_reduction_function(x, w, dim=dim)
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`.
std = wmean((x - mu) ** 2, w, dim=dim, eps=1e-2).clamp(1e-4).sqrt()
# FIXME: somehow this is extremely heavy in mem?
return std

View File

@@ -34,10 +34,8 @@ class ViewPooler(Configurable, torch.nn.Module):
from a set of source images. FeatureAggregator executes step (4) above.
"""
# pyre-fixme[13]: Attribute `view_sampler` is never initialized.
view_sampler: ViewSampler
feature_aggregator_class_type: str = "AngleWeightedReductionFeatureAggregator"
# pyre-fixme[13]: Attribute `feature_aggregator` is never initialized.
feature_aggregator: FeatureAggregatorBase
def __post_init__(self):

View File

@@ -312,7 +312,6 @@ class _Registry:
raise ValueError(
f"{name} resolves to {result} which does not subclass {base_class_wanted}"
)
# pyre-ignore[7]
return result
def get_all(

View File

@@ -51,7 +51,6 @@ def cleanup_eval_depth(
# the threshold is a sigma-multiple of the standard deviation of the depth
mu = wmean(depth.view(ba, -1, 1), mask.view(ba, -1)).view(ba, 1)
std = (
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`.
wmean((depth.view(ba, -1) - mu).view(ba, -1, 1) ** 2, mask.view(ba, -1))
.clamp(1e-4)
.sqrt()

View File

@@ -182,10 +182,12 @@ def generate_eval_video_cameras(
)
test_cameras = PerspectiveCameras(
# pyrefly: ignore [bad-argument-type]
focal_length=focal_length,
principal_point=principal_point,
R=R,
T=T,
# pyrefly: ignore [missing-attribute]
device=focal_length.device,
)

View File

@@ -79,7 +79,6 @@ def eval_depth(
df = gt - pred
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`.
mse_depth = (dmask * (df**2)).sum((1, 2, 3)) / dmask_mass
abs_depth = (dmask * df.abs()).sum((1, 2, 3)) / dmask_mass
@@ -115,10 +114,8 @@ def calc_mse(
Calculates the mean square error between tensors `x` and `y`.
"""
if mask is None:
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`.
return torch.mean((x - y) ** 2)
else:
# pyre-fixme[58]: `**` is not supported for operand types `Tensor` and `int`.
return (((x - y) ** 2) * mask).sum() / mask.expand_as(x).sum().clamp(1e-5)
@@ -146,7 +143,6 @@ def calc_bce(
mask_bg = (1 - mask_fg) * mask
weight = mask_fg / mask_fg.sum().clamp(1.0) + mask_bg / mask_bg.sum().clamp(1.0)
# weight sum should be at this point ~2
# pyre-fixme[58]: `/` is not supported for operand types `int` and `Tensor`.
weight = weight * (weight.numel() / weight.sum().clamp(1.0))
else:
weight = torch.ones_like(gt) * mask

View File

@@ -49,7 +49,6 @@ def get_stats_path(fl, eval_results: bool = False) -> str:
break
else:
flstats = "%s_stats.jgz" % fl
# pyre-fixme[61]: `flstats` is undefined, or not always defined.
return flstats
@@ -148,15 +147,12 @@ def find_last_checkpoint(
)
if len(fls) > 0:
break
# pyre-fixme[61]: `fls` is undefined, or not always defined.
if len(fls) == 0:
fl = None
else:
if all_checkpoints:
# pyre-fixme[61]: `fls` is undefined, or not always defined.
fl = [f[0 : -len(ext)] + ".pth" for f in fls]
else:
# pyre-fixme[61]: `ext` is undefined, or not always defined.
fl = fls[-1][0 : -len(ext)] + ".pth"
return fl

View File

@@ -62,7 +62,6 @@ def rasterize_sparse_ray_bundle(
max_size = torch.max(camera_counts).item()
features_depth_ras = packed_to_padded(
# pyrefly: ignore [bad-argument-type]
features_depth_ras[:, 0],
first_idxs,
# pyrefly: ignore [bad-argument-type]

View File

@@ -218,7 +218,6 @@ def load_obj(
"""
data_dir = "./"
if isinstance(f, (str, bytes, Path)):
# pyre-fixme[6]: For 1st argument expected `PathLike[Variable[AnyStr <:
# [str, bytes]]]` but got `Union[Path, bytes, str]`.
data_dir = os.path.dirname(f)
if path_manager is None:
@@ -713,6 +712,7 @@ def save_obj(
faces_normals_idx: LongTensor of shape (F, 3) giving the index into
normals for each vertex in the face.
verts_uvs: FloatTensor of shape (V, 2) giving the uv coordinate per vertex.
When provided with faces_uvs, UVs are saved even without a texture_map.
faces_uvs: LongTensor of shape (F, 3) giving the index into verts_uvs for
each vertex in the face.
texture_map: FloatTensor of shape (H, W, 3) representing the texture map
@@ -758,7 +758,8 @@ def save_obj(
if path_manager is None:
path_manager = PathManager()
save_texture = all(t is not None for t in [faces_uvs, verts_uvs, texture_map])
save_uvs = faces_uvs is not None and verts_uvs is not None
save_texture = save_uvs and texture_map is not None
output_path = Path(f)
# Save the .obj file
@@ -779,7 +780,7 @@ def save_obj(
faces_normals_idx=faces_normals_idx,
verts_uvs=verts_uvs,
faces_uvs=faces_uvs,
save_texture=save_texture,
save_uvs=save_uvs,
save_normals=normals is not None,
)
@@ -817,7 +818,7 @@ def _save(
faces_normals_idx: Optional[torch.Tensor] = None,
verts_uvs: Optional[torch.Tensor] = None,
faces_uvs: Optional[torch.Tensor] = None,
save_texture: bool = False,
save_uvs: bool = False,
save_normals: bool = False,
) -> None:
if len(verts) and (verts.dim() != 2 or verts.size(1) != 3):
@@ -833,26 +834,24 @@ def _save(
return
verts, faces = verts.cpu(), faces.cpu()
vert_rows = verts.detach().cpu().numpy()
lines = ""
lines = []
if decimal_places is None:
float_str = "%f"
else:
float_str = "%" + ".%df" % decimal_places
if len(verts):
V, D = verts.shape
for i in range(V):
vert = [float_str % verts[i, j] for j in range(D)]
lines += "v %s\n" % " ".join(vert)
for vert in vert_rows:
lines.append("v %s\n" % " ".join(float_str % v for v in vert))
if save_normals:
assert normals is not None
assert faces_normals_idx is not None
lines += _write_normals(normals, faces_normals_idx, float_str)
lines.append(_write_normals(normals, float_str))
if save_texture:
if save_uvs:
assert faces_uvs is not None
assert verts_uvs is not None
@@ -865,15 +864,13 @@ def _save(
raise ValueError(message)
verts_uvs, faces_uvs = verts_uvs.cpu(), faces_uvs.cpu()
uv_rows = verts_uvs.detach().cpu().numpy()
# Save verts uvs after verts
if len(verts_uvs):
uV, uD = verts_uvs.shape
for i in range(uV):
uv = [float_str % verts_uvs[i, j] for j in range(uD)]
lines += "vt %s\n" % " ".join(uv)
for uv in uv_rows:
lines.append("vt %s\n" % " ".join(float_str % u for u in uv))
f.write(lines)
f.write("".join(lines))
if torch.any(faces >= verts.shape[0]) or torch.any(faces < 0):
warnings.warn("Faces have invalid indices")
@@ -882,31 +879,21 @@ def _save(
_write_faces(
f,
faces,
faces_uvs if save_texture else None,
faces_uvs if save_uvs else None,
faces_normals_idx if save_normals else None,
)
def _write_normals(
normals: torch.Tensor, faces_normals_idx: torch.Tensor, float_str: str
) -> str:
if faces_normals_idx.dim() != 2 or faces_normals_idx.size(1) != 3:
message = (
"'faces_normals_idx' should either be empty or of shape (num_faces, 3)."
)
raise ValueError(message)
def _write_normals(normals: torch.Tensor, float_str: str) -> str:
if normals.dim() != 2 or normals.size(1) != 3:
message = "'normals' should either be empty or of shape (num_verts, 3)."
raise ValueError(message)
normals, faces_normals_idx = normals.cpu(), faces_normals_idx.cpu()
normal_rows = normals.detach().cpu().numpy()
lines = []
V, D = normals.shape
for i in range(V):
normal = [float_str % normals[i, j] for j in range(D)]
lines.append("vn %s\n" % " ".join(normal))
for normal in normal_rows:
lines.append("vn %s\n" % " ".join(float_str % n for n in normal))
return "".join(lines)
@@ -916,31 +903,37 @@ def _write_faces(
faces_uvs: Optional[torch.Tensor],
faces_normals_idx: Optional[torch.Tensor],
) -> None:
face_rows = faces.detach().cpu().numpy()
uv_rows = faces_uvs.detach().cpu().numpy() if faces_uvs is not None else None
normal_rows = (
faces_normals_idx.detach().cpu().numpy()
if faces_normals_idx is not None
else None
)
F, P = faces.shape
for i in range(F):
if faces_normals_idx is not None:
if faces_uvs is not None:
face_row = face_rows[i]
if normal_rows is not None:
normal_row = normal_rows[i]
if uv_rows is not None:
uv_row = uv_rows[i]
# Format faces as {verts_idx}/{verts_uvs_idx}/{verts_normals_idx}
face = [
"%d/%d/%d"
% (
faces[i, j] + 1,
faces_uvs[i, j] + 1,
faces_normals_idx[i, j] + 1,
)
"%d/%d/%d" % (face_row[j] + 1, uv_row[j] + 1, normal_row[j] + 1)
for j in range(P)
]
else:
# Format faces as {verts_idx}//{verts_normals_idx}
face = [
"%d//%d" % (faces[i, j] + 1, faces_normals_idx[i, j] + 1)
for j in range(P)
"%d//%d" % (face_row[j] + 1, normal_row[j] + 1) for j in range(P)
]
elif faces_uvs is not None:
elif uv_rows is not None:
uv_row = uv_rows[i]
# Format faces as {verts_idx}/{verts_uvs_idx}
face = ["%d/%d" % (faces[i, j] + 1, faces_uvs[i, j] + 1) for j in range(P)]
face = ["%d/%d" % (face_row[j] + 1, uv_row[j] + 1) for j in range(P)]
else:
face = ["%d" % (faces[i, j] + 1) for j in range(P)]
face = ["%d" % (face_row[j] + 1) for j in range(P)]
if i + 1 < F:
f.write("f %s\n" % " ".join(face))

View File

@@ -122,16 +122,13 @@ def corresponding_cameras_alignment(
# create a new cameras object and set the R and T accordingly
cameras_src_aligned = cameras_src.clone()
# pyre-fixme[6]: For 2nd argument expected `Tensor` but got `Union[Tensor, Module]`.
cameras_src_aligned.R = torch.bmm(align_t_R.expand_as(cameras_src.R), cameras_src.R)
cameras_src_aligned.T = (
torch.bmm(
align_t_T[:, None].repeat(cameras_src.R.shape[0], 1, 1),
# pyre-fixme[6]: For 2nd argument expected `Tensor` but got
# `Union[Tensor, Module]`.
cameras_src.R,
)[:, 0]
# pyre-fixme[29]: `Union[(self: TensorBase, other: Union[bool, complex,
# float, int, Tensor]) -> Tensor, Tensor, Module]` is not a function.
+ cameras_src.T * align_t_s
)
@@ -180,8 +177,6 @@ def _align_camera_extrinsics(
R_A = (U V^T)^T
```
"""
# pyre-fixme[6]: For 1st argument expected `Tensor` but got `Union[Tensor, Module]`.
# pyre-fixme[29]: `Union[(self: TensorBase, dim0: int, dim1: int) -> Tensor,
# Tensor, Module]` is not a function.
RRcov = torch.bmm(cameras_src.R, cameras_tgt.R.transpose(2, 1)).mean(0)
U, _, V = torch.svd(RRcov)
@@ -212,11 +207,7 @@ def _align_camera_extrinsics(
T_A = mean(B) - mean(A) * s_A
```
"""
# pyre-fixme[6]: For 1st argument expected `Tensor` but got `Union[Tensor, Module]`.
# pyre-fixme[29]: `Union[(self: TensorBase, indices: Union[None, slice[Any, Any, ...
A = torch.bmm(cameras_src.R, cameras_src.T[:, :, None])[:, :, 0]
# pyre-fixme[6]: For 1st argument expected `Tensor` but got `Union[Tensor, Module]`.
# pyre-fixme[29]: `Union[(self: TensorBase, indices: Union[None, slice[Any, Any, ...
B = torch.bmm(cameras_src.R, cameras_tgt.T[:, :, None])[:, :, 0]
Amu = A.mean(0, keepdim=True)
Bmu = B.mean(0, keepdim=True)

View File

@@ -103,7 +103,6 @@ def cot_laplacian(
s = 0.5 * (A + B + C)
# note that the area can be negative (close to 0) causing nans after sqrt()
# we clip it to a small positive value
# pyre-fixme[16]: `float` has no attribute `clamp`.
area = (s * (s - A) * (s - B) * (s - C)).clamp(min=eps).sqrt()
# Compute cotangents of angles, of shape (sum(F_n), 3)

View File

@@ -340,8 +340,11 @@ def corresponding_points_alignment(
total_weight = torch.clamp(num_points, 1)
# special handling for heterogeneous point clouds and/or input weights
if weights is not None:
# pyrefly: ignore [bad-index]
Xc *= weights[:, :, None]
# pyrefly: ignore [bad-index]
Yc *= weights[:, :, None]
# pyrefly: ignore [missing-attribute]
total_weight = torch.clamp(weights.sum(1), eps)
if (num_points < (dim + 1)).any():

View File

@@ -156,7 +156,6 @@ def estimate_pointcloud_local_coord_frames(
if disambiguate_directions:
# disambiguate normal
n = _disambiguate_vector_directions(
# pyrefly: ignore [unsupported-operation]
points_centered,
knns,
# pyrefly: ignore [unsupported-operation]
@@ -164,7 +163,6 @@ def estimate_pointcloud_local_coord_frames(
)
# disambiguate the main curvature
z = _disambiguate_vector_directions(
# pyrefly: ignore [unsupported-operation]
points_centered,
knns,
# pyrefly: ignore [unsupported-operation]

View File

@@ -168,9 +168,7 @@ def sample_farthest_points_naive(
sample_idx_batch[0] = selected_idx
# If the pointcloud has fewer than K points then only iterate over the min
# pyre-fixme[6]: For 1st param expected `SupportsRichComparisonT` but got
# `Tensor`.
# pyre-fixme[6]: For 2nd param expected `SupportsRichComparisonT` but got
# `Tensor`.
k_n = min(lengths[n], K[n])

View File

@@ -91,7 +91,6 @@ def wmean(
args = {"dim": dim, "keepdim": keepdim}
if weight is None:
# pyre-fixme[6]: For 1st param expected `Optional[dtype]` but got
# `Union[Tuple[int], int]`.
return x.mean(**args)
@@ -101,7 +100,6 @@ def wmean(
):
raise ValueError("wmean: weights are not compatible with the tensor")
# pyre-fixme[6]: For 1st param expected `Optional[dtype]` but got
# `Union[Tuple[int], int]`.
return (x * weight[..., None]).sum(**args) / weight[..., None].sum(**args).clamp(
eps

View File

@@ -228,7 +228,6 @@ def softmax_rgb_blend(
# Also apply exp normalize trick for the background color weight.
# Clamp to ensure delta is never 0.
# pyre-fixme[6]: Expected `Tensor` for 1st param but got `float`.
delta = torch.exp((eps - z_inv_max) / blend_params.gamma).clamp(min=eps)
# Normalize weights.

View File

@@ -65,10 +65,8 @@ def _opencv_from_cameras_projection(
cameras: PerspectiveCameras,
image_size: torch.Tensor,
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
# pyre-fixme[29]: `Union[(self: TensorBase, memory_format:
# Optional[memory_format] = ...) -> Tensor, Tensor, Module]` is not a function.
R_pytorch3d = cameras.R.clone()
# pyre-fixme[29]: `Union[(self: TensorBase, memory_format:
# Optional[memory_format] = ...) -> Tensor, Tensor, Module]` is not a function.
T_pytorch3d = cameras.T.clone()
focal_pytorch3d = cameras.focal_length

View File

@@ -230,9 +230,7 @@ class CamerasBase(TensorProperties):
a Transform3d object which represents a batch of transforms
of shape (N, 3, 3)
"""
# pyre-fixme[16]: `CamerasBase` has no attribute `R`.
self.R: torch.Tensor = kwargs.get("R", self.R)
# pyre-fixme[16]: `CamerasBase` has no attribute `T`.
self.T: torch.Tensor = kwargs.get("T", self.T)
world_to_view_transform = self.get_world_to_view_transform(R=self.R, T=self.T)
view_to_proj_transform = self.get_projection_transform(**kwargs)
@@ -409,9 +407,7 @@ class CamerasBase(TensorProperties):
kwargs = {}
tensor_types = {
# pyre-fixme[16]: Module `cuda` has no attribute `BoolTensor`.
"bool": (torch.BoolTensor, torch.cuda.BoolTensor),
# pyre-fixme[16]: Module `cuda` has no attribute `LongTensor`.
"long": (torch.LongTensor, torch.cuda.LongTensor),
}
if not isinstance(
@@ -429,14 +425,13 @@ class CamerasBase(TensorProperties):
index = [index]
if isinstance(index, tensor_types["bool"]):
# pyre-fixme[16]: Item `List` of `Union[List[int], BoolTensor,
# LongTensor]` has no attribute `ndim`.
# pyre-fixme[16]: Item `List` of `Union[List[int], BoolTensor,
# LongTensor]` has no attribute `shape`.
# pyrefly: ignore [missing-attribute]
if index.ndim != 1 or index.shape[0] != len(self):
raise ValueError(
# pyre-fixme[16]: Item `List` of `Union[List[int], BoolTensor,
# LongTensor]` has no attribute `shape`.
# pyrefly: ignore [missing-attribute]
f"Boolean index of shape {index.shape} does not match cameras"
)
elif max(index) >= len(self):
@@ -1179,7 +1174,6 @@ class PerspectiveCameras(CamerasBase):
unprojection_transform = to_camera_transform.inverse()
xy_inv_depth = torch.cat(
# pyre-fixme[6]: For 1st argument expected `Union[List[Tensor],
# tuple[Tensor, ...]]` but got `Tuple[Tensor, float]`.
(xy_depth[..., :2], torch.reciprocal(xy_depth[..., 2:3])),
dim=-1, # type: ignore
@@ -1750,7 +1744,7 @@ def look_at_view_transform(
elev, # pyrefly: ignore [bad-argument-type]
azim, # pyrefly: ignore [bad-argument-type]
degrees=degrees,
device=device, # pyrefly: ignore [bad-argument-type]
device=device,
)
+ at
)
@@ -1798,11 +1792,13 @@ def get_ndc_to_screen_transform(
image_size = torch.tensor(image_size, device=cameras.device)
# pyrefly: ignore [missing-attribute]
image_size = image_size.view(-1, 2) # of shape (1 or B)x2
# pyrefly: ignore [missing-attribute]
height, width = image_size.unbind(1)
# For non square images, we scale the points such that smallest side
# has range [-1, 1] and the largest side has range [-u, u], with u > 1.
# This convention is consistent with the PyTorch3D renderer
# pyrefly: ignore [missing-attribute]
scale = (image_size.min(dim=1).values - 0.0) / 2.0
K[:, 0, 0] = scale

View File

@@ -183,7 +183,6 @@ class HarmonicEmbedding(torch.nn.Module):
so the input might be xyz.
"""
return self.get_output_dim_static(
# pyrefly: ignore [bad-argument-type]
input_dims,
# pyrefly: ignore [bad-argument-type]
len(self._frequencies),

View File

@@ -236,7 +236,6 @@ class MultinomialRaysampler(torch.nn.Module):
# is not batched and does not support partial permutation
_, width, height, _ = xy_grid.shape
weights = xy_grid.new_ones(batch_size, width * height)
# pyre-fixme[6]: For 2nd param expected `int` but got `Union[bool,
# float, int]`.
rays_idx = _safe_multinomial(weights, n_rays_per_image)[..., None].expand(
-1, -1, 2

View File

@@ -170,7 +170,6 @@ class ImplicitRenderer(torch.nn.Module):
# given sampled rays, call the volumetric function that
# evaluates the densities and features at the locations of the
# ray points
# pyre-fixme[23]: Unable to unpack `object` into 2 values.
rays_densities, rays_features = volumetric_function(
ray_bundle=ray_bundle, cameras=cameras, **kwargs
)

View File

@@ -182,17 +182,38 @@ def _get_culled_faces(face_verts: torch.Tensor, frustum: ClipFrustum) -> torch.T
faces_culled = torch.zeros(
[face_verts.shape[0]], dtype=torch.bool, device=face_verts.device
)
if not frustum.cull:
return faces_culled
# With a perspective camera the xy coordinates are in NDC space, where the
# perspective divide mirrors vertices behind the camera through the origin.
# A face with such a vertex can therefore have all 3 xy coordinates outside
# the same plane while the part of it in front of the camera still crosses
# the frustum, so only faces lying entirely in front of the clipping plane
# may be culled on the x and y axes. The z axis uses world coordinates and
# is unaffected.
xy_cullable = None
if frustum.perspective_correct:
if frustum.z_clip_value is not None:
verts_in_front = face_verts[:, :, 2] >= frustum.z_clip_value
else:
verts_in_front = face_verts[:, :, 2] > 0
xy_cullable = verts_in_front.all(dim=1)
for plane in clipping_planes:
clip_value, axis, op = plane
# If clip_value is None then don't clip along that plane
if frustum.cull and clip_value is not None:
if clip_value is not None:
if op == "<":
verts_clipped = face_verts[:, axis] < clip_value
verts_clipped = face_verts[:, :, axis] < clip_value
else:
verts_clipped = face_verts[:, axis] > clip_value
verts_clipped = face_verts[:, :, axis] > clip_value
# If all verts are clipped then face is outside the frustum
faces_culled |= verts_clipped.sum(1) == 3
plane_culled = verts_clipped.all(dim=1)
if axis != 2 and xy_cullable is not None:
plane_culled &= xy_cullable
faces_culled |= plane_culled
return faces_culled
@@ -496,7 +517,6 @@ def clip_faces(
# Solve for the points p4, p5 that intersect the clipping plane
p, p_barycentric = _find_verts_intersecting_clipping_plane(
# pyrefly: ignore [bad-argument-type]
faces_case3,
p1_face_ind,
# pyrefly: ignore [bad-argument-type]
@@ -540,12 +560,10 @@ def clip_faces(
faces_case4 = face_verts_unclipped[case4_unclipped_idx]
# index (0, 1, or 2) of the vertex behind the clipping plane
# pyre-fixme[61]: `faces_clipped_verts` is undefined, or not always defined.
p1_face_ind = torch.where(faces_clipped_verts[case4_unclipped_idx])[1]
# Solve for the points p4, p5 that intersect the clipping plane
p, p_barycentric = _find_verts_intersecting_clipping_plane(
# pyrefly: ignore [bad-argument-type]
faces_case4,
p1_face_ind,
# pyrefly: ignore [bad-argument-type]

View File

@@ -453,7 +453,6 @@ class TexturesAtlas(TexturesBase):
msg = "Expected atlas to be of shape (N, F, R, R, C); got %r"
raise ValueError(msg % repr(atlas.ndim))
self._atlas_padded = atlas
# pyrefly: ignore [bad-assignment]
self._atlas_list = None
self.device = atlas.device
@@ -537,7 +536,6 @@ class TexturesAtlas(TexturesBase):
self._atlas_padded = [
torch.empty((0, 0, 0, 3), dtype=torch.float32, device=self.device)
] * self._N
# pyrefly: ignore [bad-assignment]
self._atlas_list = _padded_to_list_wrapper(
# pyrefly: ignore [bad-argument-type]
self._atlas_padded,
@@ -803,7 +801,6 @@ class TexturesUV(TexturesBase):
msg = "Expected faces_uvs to be of shape (N, F, 3); got %r"
raise ValueError(msg % repr(faces_uvs.shape))
self._faces_uvs_padded = faces_uvs
# pyrefly: ignore [bad-assignment]
self._faces_uvs_list = None
self.device = faces_uvs.device
@@ -840,7 +837,6 @@ class TexturesUV(TexturesBase):
msg = "Expected verts_uvs to be of shape (N, V, 2); got %r"
raise ValueError(msg % repr(verts_uvs.shape))
self._verts_uvs_padded = verts_uvs
# pyrefly: ignore [bad-assignment]
self._verts_uvs_list = None
if verts_uvs.device != self.device:
@@ -853,7 +849,6 @@ class TexturesUV(TexturesBase):
if isinstance(maps, (list, tuple)):
self._maps_list = maps
else:
# pyrefly: ignore [bad-assignment]
self._maps_list = None
self._maps_padded = self._format_maps_padded(maps)
@@ -1099,7 +1094,6 @@ class TexturesUV(TexturesBase):
torch.empty((0, 3), dtype=torch.float32, device=self.device)
] * self._N
else:
# pyrefly: ignore [bad-assignment]
self._faces_uvs_list = padded_to_list(
# pyrefly: ignore [bad-argument-type]
self._faces_uvs_padded,
@@ -1132,7 +1126,7 @@ class TexturesUV(TexturesBase):
# The number of vertices in the mesh and in verts_uvs can differ
# e.g. if a vertex is shared between 3 faces, it can
# have up to 3 different uv coordinates.
# pyrefly: ignore [bad-assignment, missing-attribute]
# pyrefly: ignore [missing-attribute]
self._verts_uvs_list = list(self._verts_uvs_padded.unbind(0))
# pyrefly: ignore [bad-return]
return self._verts_uvs_list
@@ -1755,7 +1749,6 @@ class TexturesVertex(TexturesBase):
msg = "Expected verts_features to be of shape (N, V, C); got %r"
raise ValueError(msg % repr(verts_features.shape))
self._verts_features_padded = verts_features
# pyrefly: ignore [bad-assignment]
self._verts_features_list = None
self.device = verts_features.device
@@ -1832,7 +1825,6 @@ class TexturesVertex(TexturesBase):
torch.empty((0, 3), dtype=torch.float32, device=self.device)
] * self._N
else:
# pyrefly: ignore [bad-assignment]
self._verts_features_list = padded_to_list(
# pyrefly: ignore [bad-argument-type]
self._verts_features_padded,

View File

@@ -288,7 +288,6 @@ class _OpenGLMachinery:
bary_coords = []
zbufs = []
# pyre-ignore Incompatible parameter type [6]
for mesh_id, mesh in enumerate(meshes_gl_ndc):
pix_to_face, bary_coord, zbuf = self._rasterize_mesh(
mesh,
@@ -385,13 +384,11 @@ class _OpenGLMachinery:
# Free GL resources.
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
# pyre-fixme[16]: Module `GL_3_0` has no attribute `glDeleteFramebuffers`.
gl.glDeleteFramebuffers(1, [self.fbo])
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
del self.fbo
gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, self.mesh_buffer_object)
# pyre-fixme[16]: Module `GL_1_5` has no attribute `glDeleteBuffers`.
gl.glDeleteBuffers(1, [self.mesh_buffer_object])
gl.glBindBufferBase(gl.GL_SHADER_STORAGE_BUFFER, 0, 0)
del self.mesh_buffer_object
@@ -408,7 +405,6 @@ class _OpenGLMachinery:
projection matrix: A 3x3 float tensor.
"""
gl.glUseProgram(self.program)
# pyre-fixme[16]: Module `GL_2_0` has no attribute `glUniformMatrix4fv`.
gl.glUniformMatrix4fv(
self.perspective_projection_uniform,
1,

View File

@@ -471,7 +471,6 @@ class Meshes:
):
raise ValueError("Vertex normals tensor has incorrect dimensions.")
self._verts_normals_packed = struct_utils.padded_to_packed(
# pyrefly: ignore [missing-attribute]
verts_normals,
# pyrefly: ignore [missing-attribute]
split_size=self._num_verts_per_mesh.tolist(),
@@ -511,7 +510,9 @@ class Meshes:
index = index.squeeze(1) if index.numel() > 0 else index
# pyrefly: ignore [missing-attribute]
index = index.tolist()
# pyrefly: ignore [not-iterable]
verts = [self.verts_list()[i] for i in index]
# pyrefly: ignore [not-iterable]
faces = [self.faces_list()[i] for i in index]
else:
raise IndexError(index)

View File

@@ -411,10 +411,13 @@ class Pointclouds:
index = index.squeeze(1) if index.numel() > 0 else index
# pyrefly: ignore [missing-attribute]
index = index.tolist()
# pyrefly: ignore [not-iterable]
points = [self.points_list()[i] for i in index]
if normals_list is not None:
# pyrefly: ignore [not-iterable]
normals = [normals_list[i] for i in index]
if features_list is not None:
# pyrefly: ignore [not-iterable]
features = [features_list[i] for i in index]
else:
raise IndexError(index)

View File

@@ -61,7 +61,6 @@ def _is_heterogeneous_ray_bundle(struct: Union[List[Struct], Struct]) -> bool:
True if something is a HeterogeneousRayBundle or ImplicitronRayBundle
and cant be reduced to RayBundle else False
"""
# pyre-ignore[16]
return hasattr(struct, "camera_counts") and struct.camera_counts is not None
@@ -586,15 +585,11 @@ def _add_struct_from_batch(
if isinstance(batched_struct, CamerasBase):
# we can't index directly into camera batches
R, T = batched_struct.R, batched_struct.T
# pyre-fixme[6]: For 1st argument expected
# `pyre_extensions.PyreReadOnly[Sized]` but got `Union[Tensor, Module]`.
r_idx = min(scene_num, len(R) - 1)
# pyre-fixme[6]: For 1st argument expected
# `pyre_extensions.PyreReadOnly[Sized]` but got `Union[Tensor, Module]`.
t_idx = min(scene_num, len(T) - 1)
# pyre-fixme[29]: `Union[(self: TensorBase, indices: Union[None, slice[Any, A...
R = R[r_idx].unsqueeze(0)
# pyre-fixme[29]: `Union[(self: TensorBase, indices: Union[None, slice[Any, A...
T = T[t_idx].unsqueeze(0)
struct = CamerasBase(device=batched_struct.device, R=R, T=T)
elif _is_ray_bundle(batched_struct) and not _is_heterogeneous_ray_bundle(
@@ -616,7 +611,6 @@ def _add_struct_from_batch(
struct = RayBundle(
**{
attr: getattr(batched_struct, attr)[
# pyre-ignore[16]
first_idxs[struct_idx] : first_idxs[struct_idx + 1]
]
for attr in ["origins", "directions", "lengths", "xys"]

View File

@@ -59,7 +59,6 @@ def texturesuv_image_matplotlib(
for i in indices:
# setting clip_on=False makes it obvious when
# we have UV coordinates outside the correct range
# pyre-fixme[6]: For 1st argument expected `Tuple[float, float]` but got
# `ndarray[Any, Any]`.
ax.add_patch(Circle(centers[i], radius, color=color, clip_on=False))

View File

@@ -955,6 +955,60 @@ class TestMeshObjIO(TestCaseMixin, unittest.TestCase):
with open(obj_file, "r") as actual_file:
self.assertEqual(actual_file.read(), expected_obj_file)
def test_save_obj_with_uvs_without_texture_map(self):
verts = torch.tensor(
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0]],
dtype=torch.float32,
)
faces = torch.tensor([[0, 2, 1]], dtype=torch.int64)
verts_uvs = torch.tensor(
[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]],
dtype=torch.float32,
)
faces_uvs = torch.tensor([[3, 1, 2]], dtype=torch.int64)
normals = torch.tensor([[0.0, 0.0, 1.0], [0.0, 1.0, 0.0]])
faces_normals_idx = torch.tensor([[1, 0, 1]], dtype=torch.int64)
for save_normals in (False, True):
with (
self.subTest(save_normals=save_normals),
TemporaryDirectory() as temp_dir,
):
obj_file = Path(temp_dir) / "mesh.obj"
save_obj(
obj_file,
verts,
faces,
verts_uvs=verts_uvs,
faces_uvs=faces_uvs,
normals=normals if save_normals else None,
faces_normals_idx=faces_normals_idx if save_normals else None,
)
self.assertEqual(os.listdir(temp_dir), ["mesh.obj"])
obj_text = obj_file.read_text()
self.assertNotIn("mtllib", obj_text)
self.assertNotIn("usemtl", obj_text)
expected_face = (
"f 1/4/2 3/2/1 2/3/2" if save_normals else "f 1/4 3/2 2/3"
)
self.assertIn(expected_face, obj_text)
loaded_verts, loaded_faces, aux = load_obj(
obj_file, load_textures=False
)
self.assertClose(loaded_verts, verts)
self.assertClose(loaded_faces.verts_idx, faces)
self.assertClose(aux.verts_uvs, verts_uvs)
self.assertClose(loaded_faces.textures_idx, faces_uvs)
self.assertIsNone(aux.material_colors)
self.assertIsNone(aux.texture_images)
if save_normals:
self.assertClose(aux.normals, normals)
self.assertClose(loaded_faces.normals_idx, faces_normals_idx)
else:
self.assertIsNone(aux.normals)
def test_save_obj_with_texture(self):
verts = torch.tensor(
[[0.01, 0.2, 0.301], [0.2, 0.03, 0.408], [0.3, 0.4, 0.05], [0.6, 0.7, 0.8]],

View File

@@ -159,6 +159,38 @@ class TestICP(TestCaseMixin, unittest.TestCase):
self.assertClose(s_init, s, atol=atol)
self.assertClose(Xt_init, Xt, atol=atol)
@staticmethod
def _init_heterogeneous_icp_problem(batch_size, max_n_points, device):
"""
Generate a batch of randomly-sized point clouds `X` together with
`Y`, a rigidly moved copy of each cloud with a few extra points
added, so that the clouds in `Y` have different sizes from those
in `X`.
`Y` deliberately covers the whole of `X`. Two independent random
clouds instead make the problem ill-posed: with `estimate_scale`,
ICP then usually collapses `X` onto a single point of `Y`, driving
the scale to ~1e-16 and the rmse to zero. That solution fits
perfectly but leaves the rotation completely unconstrained, so two
runs of the same algorithm may return wildly different rotations
and can't be compared with each other.
"""
n_points = torch.randint(
low=6, high=max_n_points, size=(batch_size,), device=device
)
X_list = [torch.randn(int(n), 3, device=device) for n in n_points]
R = rotation_conversions.axis_angle_to_matrix(
0.15 * torch.randn(batch_size, 3, device=device)
)
T = 0.1 * torch.randn(batch_size, 3, device=device)
Y_list = []
for i, X in enumerate(X_list):
n_extra = int(torch.randint(low=1, high=5, size=(), device=device))
covered = torch.cat([X, torch.randn(n_extra, 3, device=device)], dim=0)
Y_list.append(covered @ R[i] + T[i] + 0.01 * torch.randn_like(covered))
return Pointclouds(X_list), Pointclouds(Y_list)
def test_heterogeneous_inputs(self, batch_size=7):
"""
Tests whether we get the same result when running ICP on
@@ -171,17 +203,9 @@ class TestICP(TestCaseMixin, unittest.TestCase):
for estimate_scale in (True, False):
for max_n_points in (10, 30, 100):
# initialize ground truth point clouds
X_pcl, Y_pcl = [
TestCorrespondingPointsAlignment.init_point_cloud(
batch_size=batch_size,
n_points=max_n_points,
dim=3,
device=device,
use_pointclouds=True,
random_pcl_size=True,
)
for _ in range(2)
]
X_pcl, Y_pcl = self._init_heterogeneous_icp_problem(
batch_size, max_n_points, device
)
# get the padded versions and their num of points
X_padded = X_pcl.points_padded()
@@ -227,8 +251,11 @@ class TestICP(TestCaseMixin, unittest.TestCase):
torch.cat([x.RTs[i] for x in icp_results], dim=0) for i in range(3)
]
# check that both sets of transforms are the same
atol = 1e-5
# check that both sets of transforms are the same.
# The two runs sum over a different number of points - the
# batched one includes the (zero-weighted) padding - so they
# round differently, by up to ~1e-5 on the transformed clouds.
atol = 1e-4
self.assertClose(R_pcl, R, atol=atol)
self.assertClose(T_pcl, T, atol=atol)
self.assertClose(s_pcl, s, atol=atol)

View File

@@ -31,6 +31,7 @@ from pytorch3d.renderer.mesh import (
convert_clipped_rasterization_to_original_faces,
TexturesUV,
)
from pytorch3d.renderer.mesh.clip import _get_culled_faces
from pytorch3d.renderer.mesh.rasterize_meshes import _RasterizeFaceVerts
from pytorch3d.renderer.mesh.rasterizer import MeshRasterizer, RasterizationSettings
from pytorch3d.renderer.mesh.renderer import MeshRenderer
@@ -194,6 +195,77 @@ class TestRenderMeshesClipping(TestCaseMixin, unittest.TestCase):
)
return clipped_faces
def test_cull_faces_uses_coordinate_axis(self):
"""
A face is culled when all 3 of its vertices are outside the same
clipping plane, not when one vertex is outside it on all 3 axes.
"""
planes_and_faces = (
({"left": -1.0}, [-2.0, 0.0, 1.0]),
({"right": 1.0}, [2.0, 0.0, 1.0]),
({"top": -1.0}, [0.0, -2.0, 1.0]),
({"bottom": 1.0}, [0.0, 2.0, 1.0]),
({"znear": 0.0}, [0.0, 0.0, -1.0]),
({"zfar": 2.0}, [0.0, 0.0, 3.0]),
)
for frustum_kwargs, vertex in planes_and_faces:
with self.subTest(frustum_kwargs=frustum_kwargs):
face_verts = torch.tensor([[vertex, vertex, vertex]])
faces_culled = _get_culled_faces(
face_verts, ClipFrustum(**frustum_kwargs)
)
self.assertTrue(faces_culled.item())
# A face intersecting the left plane must remain visible regardless of
# which of its vertices is first in the face.
face_verts = torch.tensor(
[[[-2.0, -2.0, -2.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]]]
)
for order in ([0, 1, 2], [1, 2, 0], [2, 0, 1]):
with self.subTest(order=order):
faces_culled = _get_culled_faces(
face_verts[:, order], ClipFrustum(left=-1.0)
)
self.assertFalse(faces_culled.item())
def test_cull_faces_straddling_perspective_camera(self):
"""
The perspective divide mirrors vertices behind the camera through the
origin, so a face straddling the camera plane can have all 3 of its
projected vertices outside the same plane while the part of it in front
of the camera still crosses the frustum. Such faces must not be culled
on the x and y axes.
"""
# xy are in NDC and z is in world coordinates. Only the first vertex is
# in front of the camera, and the edges joining it to the other two
# cross the frustum before reaching z = 0.
straddling = torch.tensor(
[[[-1.1, 0.0, 1.0], [-100.0, 0.0, -1.0], [-100.0, 0.1, -1.0]]]
)
# The same projected face with every vertex in front of the camera is
# genuinely outside the left plane.
in_front = straddling.clone()
in_front[:, :, 2] = 1.0
for frustum_kwargs in ({}, {"z_clip_value": 1e-2}):
with self.subTest(frustum_kwargs=frustum_kwargs):
frustum = ClipFrustum(
left=-1.0, perspective_correct=True, **frustum_kwargs
)
self.assertFalse(_get_culled_faces(straddling, frustum).item())
self.assertTrue(_get_culled_faces(in_front, frustum).item())
# An orthographic projection leaves the xy coordinates of vertices
# behind the camera usable, so there the face is culled.
frustum = ClipFrustum(left=-1.0, perspective_correct=False)
self.assertTrue(_get_culled_faces(straddling, frustum).item())
# Culling on the z axis uses world coordinates and is unaffected.
behind = straddling.clone()
behind[:, :, 2] = -1.0
frustum = ClipFrustum(znear=0.0, perspective_correct=True)
self.assertTrue(_get_culled_faces(behind, frustum).item())
def test_grad(self):
"""
Check that gradient flow is unaffected when the camera is inside the mesh

View File

@@ -66,6 +66,51 @@ class TestSymEig3x3(TestCaseMixin, unittest.TestCase):
rtol=rtol,
)
def test_is_eigen_scaled_gpu(self):
self._test_is_eigen_scaled(self._gpu)
def test_is_eigen_scaled_cpu(self):
self._test_is_eigen_scaled(self._cpu)
def _test_is_eigen_scaled(self, device):
"""
The decomposition is homogeneous, so scaling the input must not affect
the relative accuracy of the result.
"""
test_input = self.create_random_sym3x3(device, n=self.TEST_BATCH_SIZE)
for scale in (1e-6, 1e-4, 1e-2, 1e2, 1e4, 1e6):
scaled_input = test_input * scale
self._test_is_not_nan_or_inf(scaled_input)
self._test_is_eigen(scaled_input, atol=1e-04 * scale, rtol=1e-02)
self._test_eigenvectors_are_orthonormal(scaled_input)
def test_mixed_magnitudes_gpu(self):
self._test_mixed_magnitudes(self._gpu)
def test_mixed_magnitudes_cpu(self):
self._test_mixed_magnitudes(self._cpu)
def _test_mixed_magnitudes(self, device):
"""
Each matrix in a batch must be conditioned on its own magnitude, so
matrices spanning many orders of magnitude can be batched together.
"""
test_input = self.create_random_sym3x3(device, n=self.TEST_BATCH_SIZE)
scales = torch.logspace(-6, 6, self.TEST_BATCH_SIZE, device=device)[
:, None, None
]
scaled_input = test_input * scales
eigenvalues, eigenvectors = symeig3x3(scaled_input, eigenvectors=True)
reconstruction = (
eigenvectors @ eigenvalues.diag_embed() @ eigenvectors.transpose(-2, -1)
)
self.assertClose(test_input, reconstruction / scales, atol=1e-04, rtol=1e-02)
self._test_eigenvectors_are_orthonormal(scaled_input)
def test_eigenvectors_are_orthonormal_gpu(self):
test_input = self.create_random_sym3x3(self._gpu, n=self.TEST_BATCH_SIZE)
@@ -192,21 +237,31 @@ class TestSymEig3x3(TestCaseMixin, unittest.TestCase):
def test_degenerate_eigenvalues_cpu(self):
self._test_degenerate_eigenvalues(self._cpu)
def _test_degenerate_eigenvalues(self, device):
def test_degenerate_eigenvalues_scaled_gpu(self):
for scale in (1e-6, 1e6):
self._test_degenerate_eigenvalues(self._gpu, scale=scale)
def test_degenerate_eigenvalues_scaled_cpu(self):
for scale in (1e-6, 1e6):
self._test_degenerate_eigenvalues(self._cpu, scale=scale)
def _test_degenerate_eigenvalues(self, device, scale: float = 1.0):
"""
Test degenerate eigenvalues like zero-valued and with 2-/3-multiplicity
Test degenerate eigenvalues like zero-valued and with 2-/3-multiplicity.
These are the cases held together by the regularizers inside the
decomposition, which only bite at the right input magnitude.
"""
# Error tolerances for degenerate values are increased as things might become
# numerically unstable
deg_atol = 1e-3
deg_atol = 1e-3 * scale
deg_rtol = 1.0
# Construct random orthonormal sets
test_eigenvecs = random_rotations(n=self.TEST_BATCH_SIZE, device=device)
# Construct random eigenvalues
test_eigenvals = torch.randn(
(self.TEST_BATCH_SIZE, 3), device=test_eigenvecs.device
test_eigenvals = (
torch.randn((self.TEST_BATCH_SIZE, 3), device=test_eigenvecs.device) * scale
)
self._test_eigenvalues_and_eigenvectors(
test_eigenvecs, test_eigenvals, atol=deg_atol, rtol=deg_rtol