Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes related to #13: Adds focal, fov #24

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions visu3d/dc_arrays/camera_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class implementation.
repr=False,
init=False,
)
focal_length: Tuple[float, float]

@property
def h(self) -> int:
Expand All @@ -118,14 +119,60 @@ def w(self) -> int:

@property
def hw(self) -> tuple[int, int]:
"""`(Height, Width)` in pixel (for usage in `(i, j)` coordinates)."""
"""`(Height, Width)` in pixel (for usage in `(u, v)` coordinates)."""
return (self.h, self.w)

@property
def wh(self) -> tuple[int, int]:
"""`(Width, Height)` in pixel (for usage in `(u, v)` coordinates)."""
"""`(Width, Height)` in pixel (for usage in `(i, j)` coordinates)."""
return (self.w, self.h)

@property
def fw(self) -> float:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think users will often need this property and this can easily be extracted with cam.focal_px_wh[0]. Which is more explicit than fw (e.g. as focal is sometimes defined in mm).

So I would remove those fw, fh

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contrary to the resolution which is always constant across all camera stacked together, focal length can vary for individual cameras.

This means we should wrap the property around @vectorize_method similarly to the scale property in

def scale(self):

This would allow for example to vary the focal length across camera, like:

cams = v3d.PinholeCamera(
    resolution=(h, w),
    focal_in_px=[200, 240, 260, 300],
)
cams.shape == (4,)  # 4 cameras stacked together with focal 200, 240,...

This also mean focal_length should likely be a FloatArray['*shape 2']

"""Focal length (along x-axis) in pixels (for usage in `(i, j)` coordinates)."""
return self.focal_length[0]

@property
def fh(self) -> float:
"""Focal length (along y-axis) in pixels (for usage in `(i, j)` coordinates)."""
return self.focal_length[1]

@property
def focal_px_wh(self) -> tuple[float, float]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems redundant with focal_length defined line 110.

To keep the API minimal, I would remove this property and rename focal_length -> focal_px_wh in the dataclass attribute.

"""Focal length in pixel (`(fw, fh)`)."""
return (self.fw, self.fh)

@property
def focal_px(self) -> float:
"""Unique Focal length in pixels (when fw == fh)."""

def _err_msg():
return (
'Cannot get `CameraSpec.focal_px` when fw and fh are '
f'different: {self.focal_px_wh}'
)

if self.fw != self.fh:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focal is focal, so we should use isclose

raise ValueError(_err_msg())

return self.fw

@property
def fov_w(self) -> float:
"""Field of view (horizontal) in radians (for usage in `(i, j)` coordinates)."""
return 2 * self.xnp.arctan(self.w / (2 * self.fw))

@property
def fov_h(self) -> float:
"""Field of view (vertical) in radians (for usage in `(i, j)` coordinates)."""
return 2 * self.xnp.arctan(self.h / (2 * self.fh))

@property
def fov(self) -> float:
"""Field of view in radians (`(fov_w, fov_h)`)."""

return (self.fov_w, self.fov_h)
Copy link
Collaborator

@Conchylicultor Conchylicultor May 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename fov -> fov_hw (as .fov sounds like it should be a single value)


# @abc.abstractmethod
@property
def px_from_cam(self) -> transformation.TransformBase:
Expand Down Expand Up @@ -300,8 +347,7 @@ def from_focal(
[0, 0, 1],
])
return cls(
K=K,
resolution=resolution,
K=K, resolution=resolution, focal_length=(focal_in_px, focal_in_px)
)

@property
Expand Down
14 changes: 13 additions & 1 deletion visu3d/dc_arrays/camera_spec_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@

H, W = 640, 480

FOCAL_PX = 35.

FIELD_OF_VIEW_W_RADIANS = 2 * np.arctan(W / (2 * FOCAL_PX))
FIELD_OF_VIEW_H_RADIANS = 2 * np.arctan(H / (2 * FOCAL_PX))


def make_camera_spec(
*,
Expand All @@ -34,7 +39,7 @@ def make_camera_spec(
) -> v3d.PinholeCamera:
spec = v3d.PinholeCamera.from_focal(
resolution=(H, W),
focal_in_px=35.,
focal_in_px=FOCAL_PX,
xnp=xnp,
)
spec = spec.broadcast_to(shape)
Expand All @@ -58,6 +63,13 @@ def test_camera_spec_init(
assert spec.hw == (H, W)
assert spec.shape == spec_shape
assert spec.K.shape == spec_shape + (3, 3)
assert spec.fw == FOCAL_PX
assert spec.fh == FOCAL_PX
assert spec.focal_px_wh == (FOCAL_PX, FOCAL_PX)
assert spec.focal_px == FOCAL_PX
assert spec.fov_w == FIELD_OF_VIEW_W_RADIANS
assert spec.fov_h == FIELD_OF_VIEW_H_RADIANS
assert spec.fov == (FIELD_OF_VIEW_W_RADIANS, FIELD_OF_VIEW_H_RADIANS)

if xnp is None:
xnp = np
Expand Down
14 changes: 12 additions & 2 deletions visu3d/dc_arrays/camera_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
# Activate the fixture
set_tnp = enp.testing.set_tnp


H, W = 64, 128
FOCAL_PX = 34.

FIELD_OF_VIEW_W_RADIANS = 2 * np.arctan(W / (2 * FOCAL_PX))
FIELD_OF_VIEW_H_RADIANS = 2 * np.arctan(H / (2 * FOCAL_PX))


def _make_cam(
Expand All @@ -34,7 +37,7 @@ def _make_cam(
shape: v3d.typing.Shape,
) -> v3d.Camera:
"""Create a camera at (0, 4, 0) looking at the center."""
spec = v3d.PinholeCamera.from_focal(resolution=(H, W), focal_in_px=34.)
spec = v3d.PinholeCamera.from_focal(resolution=(H, W), focal_in_px=FOCAL_PX)
cam = v3d.Camera.from_look_at(
spec=spec.as_xnp(xnp),
pos=[0, 4, 0], # Camera on the `y` axis
Expand All @@ -59,6 +62,13 @@ def test_camera_properties(xnp: enp.NpModule, shape: v3d.typing.Shape):
assert cam.w == W
assert cam.wh == (W, H)
assert cam.hw == (H, W)
assert cam.spec.fw == FOCAL_PX
assert cam.spec.fh == FOCAL_PX
assert cam.spec.focal_px_wh == (FOCAL_PX, FOCAL_PX)
assert cam.spec.focal_px == FOCAL_PX
assert cam.spec.fov_w == FIELD_OF_VIEW_W_RADIANS
assert cam.spec.fov_h == FIELD_OF_VIEW_H_RADIANS
assert cam.spec.fov == (FIELD_OF_VIEW_W_RADIANS, FIELD_OF_VIEW_H_RADIANS)


@enp.testing.parametrize_xnp()
Expand Down