Skip to content

Commit 1bb706c

Browse files
authored
Implement Camera Intrinsics & compute ray direction in camera frame (#22)
(Part of MET-48) ## Summary of Changes This PR complete the camera `Intrinsics` and defines two functions for it: - `get_ray_direction`: given a pixel coordinate `(px, py)`, returns the direction of the ray in camera frame - `get_ray_directions`: returns a 2D array of all ray directions in the camera frame. If a range is specified, then this method only compute a subset of the rays. Depends on how we implement the kernel at the end, we may not need both functions, but let's still have them here now just in case... ## Test Plans On Python side, you can find a basic correctness testing in `test_camera.py`, which checks the value of our `Intrinsics::get_ray_direction` against the ground truth fucntion refactored from `run_fmb.py` Due to the complexity of binding `Array2D<Vec3D, device>` to Python, I'm leaving the testing of the batched `get_ray_directions` to C++ side, where `test_camera.cu` only performs a basic sanity check to ensure that the adjacent rays are pointing toward different direction (and of course, that the function runs without error). As always, you can run the tests with ```bash pixi run test ```
1 parent 78159f1 commit 1bb706c

File tree

11 files changed

+173
-12
lines changed

11 files changed

+173
-12
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
1414
################
1515

1616
add_library(genmetaballs_core
17+
genmetaballs/src/cuda/core/camera.cu
18+
genmetaballs/src/cuda/core/camera.cuh
1719
genmetaballs/src/cuda/core/utils.cu
1820
genmetaballs/src/cuda/core/utils.cuh
1921
genmetaballs/src/cuda/core/geometry.cuh

examples/run_fmb.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from tqdm import tqdm
3434

3535
# Import local utilities
36-
from genmetaballs.fmb.utils import DegradeLR, image_grid
36+
from genmetaballs.fmb.utils import DegradeLR, get_camera_rays, image_grid
3737

3838
CURRENT_DIR = Path(__file__).parent
3939
PROJECT_ROOT = CURRENT_DIR.parent
@@ -475,14 +475,12 @@ def main():
475475

476476
# Setup camera rays
477477
height, width = image_size
478-
K = np.array([[focal_length, 0, cx], [0, focal_length, cy], [0, 0, 1]])
479478
pixel_list = (
480479
(np.array(np.meshgrid(np.arange(width), height - np.arange(height) - 1, [0]))[:, :, :, 0])
481480
.reshape((3, -1))
482481
.T
483482
)
484-
camera_rays = (pixel_list - K[:, 2]) / np.diag(K)
485-
camera_rays[:, -1] = -1
483+
camera_rays = get_camera_rays(focal_length, focal_length, cx, cy, pixel_list)
486484
cameras_list = []
487485
for tran, quat in zip(trans, rand_quats, strict=False):
488486
R = transforms3d.quaternions.quat2mat(quat)

genmetaballs/src/cuda/bindings.cu

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <nanobind/stl/vector.h>
66

77
#include "core/blender.cuh"
8+
#include "core/camera.cuh"
89
#include "core/confidence.cuh"
910
#include "core/geometry.cuh"
1011
#include "core/utils.cuh"
@@ -65,7 +66,24 @@ NB_MODULE(_genmetaballs_bindings, m) {
6566
.def_rw("direction", &Ray::direction);
6667

6768
/*
68-
* Confidence submodule bindings
69+
* Camera module bindings
70+
*/
71+
nb::module_ camera = m.def_submodule("camera", "Camera intrinsics and extrinsics");
72+
nb::class_<Intrinsics>(camera, "Intrinsics")
73+
.def(nb::init<uint32_t, uint32_t, float, float, float, float>(), nb::arg("height"),
74+
nb::arg("width"), nb::arg("fx"), nb::arg("fy"), nb::arg("cx"), nb::arg("cy"))
75+
.def_ro("height", &Intrinsics::height)
76+
.def_ro("width", &Intrinsics::width)
77+
.def_ro("fx", &Intrinsics::fx)
78+
.def_ro("fy", &Intrinsics::fy)
79+
.def_ro("cx", &Intrinsics::cx)
80+
.def_ro("cy", &Intrinsics::cy)
81+
.def("get_ray_direction", &Intrinsics::get_ray_direction,
82+
"Get the direction of the ray going through pixel (px, py) in camera frame",
83+
nb::arg("px"), nb::arg("py"));
84+
85+
/*
86+
* Confidence module bindings
6987
*/
7088

7189
nb::module_ confidence = m.def_submodule("confidence");
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <cstdint>
2+
#include <cuda/std/ranges>
3+
#include <cuda_runtime.h>
4+
5+
#include "camera.cuh"
6+
#include "geometry.cuh"
7+
#include "utils.cuh"
8+
9+
CUDA_CALLABLE Vec3D Intrinsics::get_ray_direction(uint32_t px, uint32_t py) const {
10+
auto x = (static_cast<float>(px) - cx) / fx;
11+
auto y = (static_cast<float>(py) - cy) / fy;
12+
return Vec3D{x, y, -1.0f};
13+
}
Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
11
#pragma once
22

33
#include <cstdint>
4+
#include <cuda_runtime.h>
5+
6+
#include "geometry.cuh"
7+
#include "utils.cuh"
48

59
struct Intrinsics {
6-
uint32_t height;
7-
uint32_t width;
10+
uint32_t height; // in x direction
11+
uint32_t width; // in y direction
812
float fx;
913
float fy;
1014
float cx;
1115
float cy;
12-
float near;
13-
float far;
16+
17+
// Returns the direction of the ray going through pixel (px, py) in camera frame.
18+
// For efficiency, this function does not check if the pixel is within bounds.
19+
CUDA_CALLABLE Vec3D get_ray_direction(uint32_t px, uint32_t py) const;
20+
21+
// Returns a 2D array of ray directions in camera frame in the specified pixel range
22+
// and store them in the provided buffer. By default, the full image is used
23+
template <MemoryLocation location>
24+
CUDA_CALLABLE Array2D<Vec3D, location>& get_ray_directions(Array2D<Vec3D, location> buffer,
25+
uint32_t px_start = 0,
26+
uint32_t px_end = UINT32_MAX,
27+
uint32_t py_start = 0,
28+
uint32_t py_end = UINT32_MAX) const {
29+
for (auto i = max(0, px_start); i < min(height, px_end); ++i) {
30+
for (auto j = max(0, py_start); j < min(width, py_end); ++j) {
31+
buffer[i][j] = get_ray_direction(j, i);
32+
}
33+
}
34+
return buffer;
35+
}
1436
};

genmetaballs/src/cuda/core/geometry.cuh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ public:
7272
return {rot, tran};
7373
}
7474

75-
CUDA_CALLABLE Rotation get_rot() const {
75+
CUDA_CALLABLE const Rotation& get_rot() const {
7676
return rot_;
7777
}
7878

79-
CUDA_CALLABLE Vec3D get_tran() const {
79+
CUDA_CALLABLE const Vec3D& get_tran() const {
8080
return tran_;
8181
}
8282

genmetaballs/src/genmetaballs/core/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
FourParameterBlender,
44
ThreeParameterBlender,
55
)
6+
from genmetaballs._genmetaballs_bindings.camera import Intrinsics
67
from genmetaballs._genmetaballs_bindings.confidence import (
78
TwoParameterConfidence,
89
ZeroParameterConfidence,
@@ -30,6 +31,8 @@ def array2d_float(data, device) -> CPUFloatArray2D | GPUFloatArray2D:
3031
"ZeroParameterConfidence",
3132
"TwoParameterConfidence",
3233
"geometry",
34+
"Camera",
35+
"Intrinsics",
3336
"sigmoid",
3437
"FourParameterBlender",
3538
"ThreeParameterBlender",

genmetaballs/src/genmetaballs/fmb/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,12 @@ def compute_normals(camera_rays, depth_py_px, eps=1e-20):
141141
norms = nan_ddiff / (eps + jnp.linalg.norm(nan_ddiff, axis=1, keepdims=True))
142142

143143
return norms
144+
145+
146+
def get_camera_rays(
147+
fx: float, fy: float, cx: float, cy: float, pixel_list: np.ndarray[int]
148+
) -> np.ndarray[np.float32]:
149+
K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]], dtype=np.float32)
150+
camera_rays = (pixel_list - K[:, 2]) / np.diag(K)
151+
camera_rays[:, -1] = -1
152+
return camera_rays

tests/cpp_tests/test_camera.cu

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include <cmath>
2+
#include <cstdint>
3+
#include <cuda_runtime.h>
4+
#include <gtest/gtest.h>
5+
#include <thrust/device_vector.h>
6+
#include <thrust/host_vector.h>
7+
8+
#include "core/camera.cuh"
9+
#include "core/geometry.cuh"
10+
#include "core/utils.cuh"
11+
12+
namespace test_camera_gpu {
13+
14+
// CUDA kernel to call get_ray_directions on device with multiple threads
15+
// Each thread processes one row of the image
16+
__global__ void get_ray_directions_kernel(Intrinsics intrinsics,
17+
Array2D<Vec3D, MemoryLocation::DEVICE> ray_buffer) {
18+
uint32_t row_start = threadIdx.x * 2;
19+
uint32_t row_end = max(row_start + 2, intrinsics.height);
20+
uint32_t col_start = threadIdx.y * 2;
21+
uint32_t col_end = max(col_start + 2, intrinsics.width);
22+
intrinsics.get_ray_directions(ray_buffer, row_start, row_end, col_start, col_end);
23+
}
24+
25+
} // namespace test_camera_gpu
26+
27+
// Test get_ray_directions on GPU (device)
28+
TEST(CameraTest, GetRayDirectionsDevice) {
29+
// Create a small camera intrinsics
30+
Intrinsics intrinsics{4, 6, 100.0f, 100.0f, 3.0f, 2.0f};
31+
32+
// Create Array2D buffer on device
33+
thrust::device_vector<Vec3D> data(intrinsics.height * intrinsics.width);
34+
Array2D<Vec3D, MemoryLocation::DEVICE> ray_buffer(data.data(), intrinsics.height,
35+
intrinsics.width);
36+
37+
// Launch kernel with multiple threads -- divide into 2x2 tiles
38+
test_camera_gpu::
39+
get_ray_directions_kernel<<<1, dim3(intrinsics.height / 2, intrinsics.width / 2)>>>(
40+
intrinsics, ray_buffer);
41+
CUDA_CHECK(cudaGetLastError());
42+
CUDA_CHECK(cudaDeviceSynchronize());
43+
44+
// Copy data back to host for sanity check
45+
thrust::host_vector<Vec3D> ray_data = data;
46+
47+
// Sanity check: adjacent rays should be different
48+
constexpr float eps = 1e-6f;
49+
for (auto i = 0; i < data.size() - 1; ++i) {
50+
auto diff = ray_data[i + 1] - ray_data[i];
51+
float diff_mag = sqrtf(dot(diff, diff));
52+
EXPECT_GT(diff_mag, eps) << "Adjacent rays are too similar at index " << i;
53+
}
54+
}

tests/cpp_tests/test_confidence.cu

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ static std::vector<ConfidenceCase> confidence_cases() {
7575

7676
TEST(GpuConfidenceTest, ConfidenceMultipleValuesGPU_AllTypes) {
7777
using test_float = float;
78-
constexpr float rtol = 1e-6F;
7978

8079
auto sizes = confidence_test_sizes();
8180
std::mt19937 master_gen(MASTER_SEED);

0 commit comments

Comments
 (0)