| Title: | Estimate 3D Orientations from 2D Landmarks |
| Version: | 2.0.0 |
| Description: | Estimates possible 3D orientations of directed objects from the 2D image coordinates of two landmarks and any additional information about the imaging geometry or object. Because a 2D image generally does not uniquely determine a 3D orientation, the package returns sets of compatible pitch, yaw, and view-elevation angles rather than a single unconstrained estimate. |
| License: | GPL (≥ 3) |
| URL: | https://github.com/jocateme/araponga |
| BugReports: | https://github.com/jocateme/araponga/issues |
| Depends: | R (≥ 4.1.0) |
| Imports: | graphics |
| Suggests: | knitr, rmarkdown, testthat (≥ 3.0.0) |
| VignetteBuilder: | knitr |
| Config/roxygen2/version: | 8.0.0 |
| Config/testthat/edition: | 3 |
| Encoding: | UTF-8 |
| NeedsCompilation: | no |
| Packaged: | 2026-10-26 23:52:41 UTC; jocateme |
| Author: | João C T Menezes |
| Maintainer: | João C T Menezes <jocateme@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-10-27 00:10:02 UTC |
Plot angle conventions
Description
Quick visual reminders of the angle conventions used by araponga.
Usage
conventions(type = c("pitch", "yaw", "view_elevation"))
Arguments
type |
Character vector specifying which convention plot(s) to draw.
Choices are |
Value
No return value.
See Also
Examples
conventions()
conventions("yaw")
Convert degrees to radians, and vice versa
Description
deg2rad() takes angles in degrees and returns them converted in radians.
rad2deg() takes angles in radians and returns them converted in degrees.
Usage
deg2rad(deg)
rad2deg(rad)
Arguments
deg |
Numeric vector of angles in degrees. |
rad |
Numeric vector of angles in radians. |
Value
A numeric vector of angles, in radians or degrees. Length matches deg or rad.
Examples
deg2rad(c(0, 90, 180))
rad2deg(c(0, pi/2, pi))
Find 3D orientations compatible with observed 2D constraints
Description
Evaluate combinations of candidate 3D pitch, yaw, and view elevation and return those whose predicted
2D projection is compatible with supplied observations. find.pitch() and find.yaw() are convenience
wrappers for finding compatible 3D pitch and yaw orientations, respectively.
Usage
find.3d(
observed2d = NULL,
find = "all",
label_error = NULL,
full_length = NULL,
pitch2d = NULL,
length2d = NULL,
candidate_pitches = seq(-90, 90, default_step),
candidate_yaws = seq(-180 + default_step, 180, default_step),
candidate_view_elevations = seq(-90, 90, default_step),
default_step = 1,
max_combinations = 1e+08
)
find.yaw(
observed2d = NULL,
label_error = NULL,
full_length = NULL,
pitch2d = NULL,
length2d = NULL,
candidate_pitches = seq(-90, 90, default_step),
candidate_yaws = seq(-180 + default_step, 180, default_step),
candidate_view_elevations = seq(-90, 90, default_step),
paired = FALSE,
default_step = 1,
max_combinations = 1e+08
)
find.pitch(
observed2d = NULL,
label_error = NULL,
full_length = NULL,
pitch2d = NULL,
length2d = NULL,
candidate_pitches = seq(-90, 90, default_step),
candidate_yaws = seq(-180 + default_step, 180, default_step),
candidate_view_elevations = seq(-90, 90, default_step),
paired = FALSE,
default_step = 1,
max_combinations = 1e+08
)
Arguments
observed2d |
Optional one-row |
find |
Character vector specifying which 3D angle(s) to return. One or more of "pitch", "yaw", and "view_elevation", or "all" to return all three. Default is "all". |
label_error |
Non-negative numeric scalar specifying the maximum labeling error in each landmark
coordinate, in the same units as the coordinates in |
full_length |
Optional positive numeric vector giving the possible unprojected (no foreshortening)
length of the directed vector, in the same units as |
pitch2d |
Optional numeric vector of projected 2D pitch angles, in degrees in the interval (-180,
180]. The smallest continuous angular interval containing the supplied values is treated as the allowed
|
length2d |
Optional non-negative numeric vector of projected 2D lengths. Its minimum and maximum
define a continuous allowed range. Requires |
candidate_pitches |
Numeric vector of candidate 3D pitch angles, in degrees in the interval
(-180, 180]. Convention: |
candidate_yaws |
Numeric vector of candidate yaw angles, in degrees in the
interval (-180, 180]. Convention: |
candidate_view_elevations |
Numeric vector of candidate camera elevation angles relative to the
object, in degrees in the interval [-90, 90]. Convention: |
default_step |
Positive numeric scalar specifying the step size, in degrees, used to generate
default candidate-angle vectors. Must evenly divide 180 to ensure full coverage of default candidate
ranges. Default is |
max_combinations |
Positive numeric scalar specifying the maximum total number of candidate
pitch-yaw-view elevation combinations to evaluate, in order to prevent excessive memory use and
computation. Default is |
paired |
Logical scalar used by |
Details
For each unique combination of candidate pitch, yaw, and view elevation, find.3d() and wrappers use
project2d.from.3d() to predict the corresponding 2D projection. Candidate orientations are retained
when that projection is compatible with all supplied observational constraints.
The function supports two main ways of specifying the observed 2D projection.
The preferred route is to supply observed2d together with label_error. If full_length is not known,
the landmarks constrain projected pitch only. If full_length is also supplied, both projected
pitch and length are used jointly, preserving the relationship between them under landmark labeling
uncertainty.
Alternatively, projected measurements may be supplied directly through pitch2d and/or length2d. This
is useful when landmark coordinates are unavailable or when the user already has suitable 2D constraints.
length2d requires full_length. When both pitch2d and length2d are supplied directly, they are
treated as independent constraints.
Candidate angles supplied explicitly are evaluated exactly and are not rounded. For candidate
vectors left at their defaults, default_step determines the resolution of the search.
The default pitch range is [-90, 90], which provides a conventional down-to-up representation. Explicit candidate pitches may extend over (-180, 180]. Such extended pitch values should be used intentionally because they necessarily have equivalent representations involving a different yaw (e.g., a pitch of 135° at a yaw of 10° is equivalent to a pitch of 45° at the opposite yaw of -170°).
Value
A data.frame containing the requested subset of:
- pitch
numeric: 3D pitch angles, in degrees.
- yaw
numeric: yaw angles, in degrees.
- view_elevation
numeric: view elevation angles, in degrees.
Each row represents a candidate 3D orientation compatible with all supplied observational constraints.
If no combinations are compatible, the returned data.frame has zero rows.
For find.pitch(..., paired = FALSE) and find.yaw(..., paired = FALSE), a numeric vector of unique
compatible pitch or yaw angles is returned.
See Also
project2d.from.3d(), project2d.from.xy(), pitch2d.w.error()
Examples
# construct a hypothetical observation from a known 3D orientation
predicted <- project2d.from.3d(
pitch = 30,
yaw = 20,
view_elevation = 10,
full_length = 100
)
observed <- project2d.from.xy(
x_tip = predicted$dx,
y_tip = predicted$dy,
x_base = 0,
y_base = 0
)
# landmark route: constrain projected direction only
find.3d(
observed2d = observed,
label_error = 1,
candidate_pitches = 25:35,
candidate_yaws = 15:25,
candidate_view_elevations = 5:15
)
# landmark route with full-length information: constrain projected direction and length jointly
find.3d(
observed2d = observed,
label_error = 1,
full_length = c(98, 102),
candidate_pitches = 25:35,
candidate_yaws = 15:25,
candidate_view_elevations = 5:15
)
# direct constraints
find.3d(
pitch2d = predicted$pitch2d + c(-1, 1),
length2d = predicted$length2d + c(-2, 2),
full_length = c(98, 102),
candidate_pitches = 25:35,
candidate_yaws = 15:25,
candidate_view_elevations = 5:15
)
# convenience wrappers
find.pitch(
observed2d = observed,
label_error = 1,
candidate_yaws = 15:25,
candidate_view_elevations = 5:15
)
find.yaw(
observed2d = observed,
label_error = 1,
candidate_pitches = 25:35,
candidate_view_elevations = 5:15
)
Estimate full length from camera parameters
Description
Estimates the unforeshortened length of an object in image pixels from its physical length, distance from the camera, and camera parameters.
Usage
full_length.from.camera(
length_physical,
distance,
image_width,
focal_length = NULL,
sensor_width = NULL,
field_of_view = NULL
)
Arguments
length_physical |
Positive numeric vector specifying the physical length of the object. When
|
distance |
Positive numeric vector specifying the distance from the camera to the object, in the
same units as |
image_width |
Positive numeric vector specifying the image width, in pixels, corresponding to the
supplied camera parameters and image scale. When |
focal_length |
Positive numeric vector specifying camera focal length. Must be in the same units
as |
sensor_width |
Positive numeric vector specifying sensor width. Must be
in the same units as |
field_of_view |
Positive numeric vector specifying horizontal field of view, in degrees in
the interval (0, 180). When |
Details
Camera geometry can be specified either by focal_length and sensor_width, or by
field_of_view. These alternatives are mutually exclusive.
Input vectors represent ranges of possible values rather than paired observations. The returned
interval contains the minimum and maximum full_length values compatible with all supplied
ranges, allowing inputs to vary independently within those ranges.
The calculation assumes scaled orthographic projection: image scale is determined at the supplied camera-to-object distance and treated as constant across the object.
Value
Numeric vector of length 2 giving the minimum and maximum compatible full_length
values, in pixels.
See Also
Examples
# focal length and sensor width
full_length.from.camera(length_physical = 10,
distance = 1000,
image_width = 2000,
focal_length = 50,
sensor_width = 20)
# equivalent camera geometry specified by field of view
full_length.from.camera(length_physical = 10,
distance = 1000,
image_width = 2000,
field_of_view = rad2deg(2 * atan(20 / (2 * 50))))
# propagate uncertainty in physical length and distance
full_length.from.camera(length_physical = c(9, 11),
distance = c(900, 1100),
image_width = 2000,
focal_length = 50,
sensor_width = 20)
Estimate full length from a reference segment
Description
Estimates the unforeshortened length of a target object in image pixels from a reference segment of known physical length.
Usage
full_length.from.reference(length_physical, reference_physical, reference_px)
Arguments
length_physical |
Positive numeric vector specifying the physical length of the target object.
When |
reference_physical |
Positive numeric vector specifying the physical length of the reference segment,
in the same units as |
reference_px |
Positive numeric vector specifying the length of the reference segment in image
pixels. When |
Details
Input vectors represent ranges of possible values rather than paired observations. The returned
interval contains the minimum and maximum full_length values compatible with all supplied
ranges, allowing inputs to vary independently within those ranges.
The reference segment should be approximately perpendicular to the camera's viewing axis and at
approximately the same distance from the camera as the target object. Foreshortening of the
reference segment or differences in camera distance can bias the estimated full_length.
Value
Numeric vector of length 2 giving the minimum and maximum compatible full_length
values, in pixels.
See Also
Examples
full_length.from.reference(length_physical = 10,
reference_physical = 20,
reference_px = 100)
# propagate uncertainty in target and reference measurements
full_length.from.reference(length_physical = c(9, 11),
reference_physical = c(18, 22),
reference_px = c(90, 110))
Calculate 2D pitch uncertainty from landmark labeling uncertainty
Description
Calculate the range of projected 2D pitch angles compatible with uncertainty in the locations of two landmarks.
Usage
pitch2d.w.error(observed2d, label_error)
Arguments
observed2d |
One-row |
label_error |
Non-negative numeric scalar specifying the maximum labeling error in each landmark
coordinate, in the same units as the coordinates in |
Details
label_error is applied independently to each landmark coordinate. The function returns the smallest
continuous interval of projected 2D pitch angles compatible with that uncertainty.
If the uncertainty is large enough that all projected directions are possible, the returned interval spans the full circle.
Value
A named list describing the smallest continuous angular interval compatible with the specified labeling error, with components:
-
from: Starting angle of the interval, in degrees. -
to: Ending angle of the interval, in degrees. -
width: Width of the interval, in degrees. -
wrap: Logical indicating whether the interval crosses the180/-180boundary. -
all: An empty list, included for consistency withsummarize.yaws().
If the landmark error region contains the origin in its interior, all 2D pitch angles are possible and
the returned interval has from = -180, to = 180, and width = 360.
See Also
project2d.from.xy(), summarize.yaws()
Examples
# Projected vector from hypothetical pixel coordinates
observed <- project2d.from.xy(
x_tip = 10, y_tip = 8,
x_base = 11, y_base = 12
)
# Pitch interval compatible with ±1 pixel labeling uncertainty
pitch2d.w.error(observed, label_error = 1)
# Pitch interval compatible with ±5 pixel labeling uncertainty (full circle returned)
pitch2d.w.error(observed, label_error = 5)
Visualize angles
Description
Plotting helper for quick visual inspection of angles, in particular pitches, yaws, and view elevations.
Usage
## S3 method for class 'angles'
plot(
angles = NULL,
type = NULL,
facing = c("right", "left"),
col = NULL,
main = NULL,
labels = TRUE,
add = FALSE
)
Arguments
angles |
Set(s) of angles to be plotted, in degrees in the range (-180, 180]. If a numeric vector,
one plot is generated. If a |
type |
When |
facing |
Character choice between |
col |
Optional vector of colors to draw angle segments in each plot. Recycled to match
|
main |
Optional character vector: titles for each plot. Recycled to match |
labels |
Logical scalar (default |
add |
Logical scalar (default |
Value
No return value.
See Also
Examples
# calls with a vector
## pitch between 60 and 70
plot.angles(60:70, "pitch")
## view_elevation between -30 and -40
plot.angles(-30:-40, "view_elevation")
# call with named list
list <- list(pitch = 10:30, yaw = -100:-45, view_elevation = -10:-15)
plot.angles(list)
# call with output from find.3d
## pitches and yaws that project to 10° (± 1° error) if seen from 15° below
df <- find.3d(
pitch2d = 9:11,
find = c("pitch", "yaw"),
candidate_view_elevations = -15
)
plot.angles(df)
Project a 3D directed vector into two dimensions
Description
Calculate the projected 2D pitch and length (relative or absolute) produced by combinations of 3D pitch, yaw, and view elevation.
Usage
project2d.from.3d(pitch, yaw, view_elevation, full_length = NULL, plot = FALSE)
Arguments
pitch |
Numeric vector: vertical orientation of the object relative to the horizontal plane, in
degrees, in the interval \(-180, 180]. Convention: |
yaw |
Numeric vector: horizontal orientation around the vertical axis, in degrees in the
interval (-180, 180]. Convention: |
view_elevation |
Numeric vector: camera elevation relative to the object, in degrees in
the interval [-90, 90]. Convention: |
full_length |
Optional positive numeric vector giving the unprojected (no foreshortening) length of
the directed vector. Values may be expressed in any linear unit. The returned |
plot |
Logical scalar. |
Details
For the rotation matrix
R = R_x(e)R_y(y)R_z(p),
where p, y, and e are respectively pitch, yaw, and view elevation in radians
(as in rotate3d()), the projected components of the directed vector are
f_x = R_{1,1} = \cos(y)\cos(p)
and
f_y = R_{2,1} = \cos(e)\sin(p) + \sin(e)\sin(y)\cos(p).
These are returned as dx_factor and dy_factor, respectively. Projected 2D pitch is then
p_{2} = \operatorname{atan2}(f_y, f_x)
and projection factor is
q = \sqrt{f_x^2 + f_y^2}.
If full_length L is supplied, the projected components are
dx = L f_x
and
dy = L f_y,
giving projected length
L_2 = L q.
full_length therefore need not represent a physical length. For example, a pixel length measured from
the same object under a known orientation with projection_factor == 1 may be supplied as a reference
length, provided image scale is unchanged. In that case, length2d, dx, and dy are returned in pixels.
pitch, yaw, and view_elevation are evaluated elementwise and must have the same length.
Value
A list containing:
-
pitch2d: projected 2D pitch angle(s), in degrees in the interval (-180, 180]. -
projection_factor: projected length relative tofull_length, ranging from 0 to 1. -
dx_factor,dy_factor: projected x and y components per unitfull_length. -
length2d: iffull_lengthis supplied, projected length in the same units. -
dx,dy: iffull_lengthis supplied, projected x and y components in the same units.
When the vector is effectively parallel to the viewing axis, projection_factor is zero and 2D pitch
is undefined (NA).
See Also
rotate3d(), project2d.from.xy(), find.3d()
Examples
# projected 2D pitch of object pointed up 15º and 30º toward camera, seen from 20º below
project2d.from.3d(
pitch = 15,
yaw = -30,
view_elevation = -20,
plot = TRUE
)
# also predict projected length from a 100-pixel reference length
project2d.from.3d(
pitch = 15,
yaw = -30,
view_elevation = -20,
full_length = 100,
plot = TRUE
)
# vectorized usage
project2d.from.3d(
pitch = c(15, 15, 15),
yaw = c(0, -30, 0),
view_elevation = c(0, 0, -30),
full_length = 100
)
Calculate a projected 2D vector from two landmarks
Description
Calculate the projected pitch and length of the 2D directed vector defined by a base landmark and a tip landmark.
Usage
project2d.from.xy(x_tip, y_tip, x_base, y_base, plot = FALSE)
Arguments
x_tip, x_base |
Numeric vectors: image x-coordinate(s) of tip and base of object, with x increasing right. |
y_tip, y_base |
Numeric vectors: image y-coordinate(s) of the tip and base of objects, with y increasing up. |
plot |
Logical scalar. |
Details
The landmarks define the directed vector
(dx,dy)=(x_{tip}-x_{base}, y_{tip}-y_{base})
with projected 2D pitch
p_{2} = \operatorname{atan2}(dy, dx)
and projected 2D length
L_2 = \sqrt{dx^2 + dy^2}.
length2d is expressed in the same units as the landmark coordinates. For image coordinates in pixels,
for example, length2d is returned in pixels.
Coordinate arguments may have length 1 or a common length greater than 1. Length-1 arguments are recycled.
Value
A data.frame of class araponga2d, with one row per base-tip pair and the following columns:
-
x_tip,y_tip,x_base,y_base: supplied landmark coordinates. -
pitch2d: projected 2D pitch angle(s), in degrees in the interval (-180, 180]. -
length2d: Euclidean distance(s) between base and tip, in the same units as the supplied coordinates.
When tip and base coincide, length2d is zero and pitch2d is undefined (NA).
The returned object can be supplied as observed2d to find.3d() and wrappers.
See Also
pitch2d.w.error(), project2d.from.3d()
Examples
# scalar examples (plots)
project2d.from.xy(1, 0, 0, 0, plot = TRUE) # pointed right -> 0°
project2d.from.xy(-1, 0, 0, 0, plot = TRUE) # pointed left -> 180°
project2d.from.xy(0, 1, 0, 0, plot = TRUE) # pointed up -> 90°
project2d.from.xy(0, -1, 0, 0, plot = TRUE) # pointed down -> -90°
# vectorised usage (no plot)
x_tips <- c(1, 0, -1)
y_tips <- c(0, 1, 0)
x_bases <- c(0, 0, 0)
y_bases <- c(0, 0, 0)
project2d.from.xy(x_tips, y_tips, x_bases, y_bases)
Create and apply 3D rotations
Description
Create a 3×3 rotation matrix corresponding to sequential Euler rotations about Z, then Y, then X (i.e., ZYX or pitch–yaw–roll sequence).
Usage
rotate3d(pitch = 0, yaw = 0, roll = 0)
Rz(pitch = 0)
Ry(yaw = 0)
Rx(roll = 0)
Arguments
pitch |
Numeric scalar: angle of rotation about the Z axis, in degrees. Defaults to |
yaw |
Numeric scalar: angle of rotation about the Y axis, in degrees. Defaults to |
roll |
Numeric scalar: angle of rotation about the X axis, in degrees. Defaults to |
Details
Rz(), Ry(), and Rx() construct elementary rotation matrices about
the Z, Y, and X axes, respectively. rotate3d() combines these as
R = R_x(r) R_y(y) R_z(p),
where p, y, and r are pitch, yaw, and roll. When the
resulting matrix is applied to a column vector, the rotations therefore
occur in the order pitch, then yaw, then roll.
Angles are not restricted to a particular interval because rotations are periodic; any finite value in degrees is accepted.
In project2d.from.3d(), the same X-axis rotation represented here by
roll is used to incorporate view elevation. This represents relative
viewing geometry and should not be confused with image-plane camera roll.
Value
A numeric 3×3 matrix (class matrix).
Examples
# rotate 45 about z, then 30 about y, then -80 about x
rotate3d(45, 30, -80)
# rotate 30 degrees about x
Rx(30)
Summarize yaw angles into the smallest continuous interval
Description
Given a set of yaw angles (or any set of angles that wrap around ± 180), summarize.yaws() returns
the endpoints and width of the smallest continuous angular interval that contains all supplied angles.
Usage
summarize.yaws(yaws, plot = FALSE, tie_action = c("all", "error"))
Arguments
yaws |
Numeric vector of yaw angles, in degrees, in the interval (-180, 180]. |
plot |
Logical scalar. |
tie_action |
Choice between |
Value
A named list with components:
- from
numeric scalar: interval start (clockwise-most endpoint), in degrees.
- to
numeric scalar: interval end (counterclockwise-most endpoint), in degrees.
- width
numeric scalar: angular width of the shortest enclosing arc, in degrees.
- wrap
logical:
TRUEif the interval crosses the ±180 boundary.- all
list containing the output (from, to, width, wrap) for all alternative intervals, if any
Examples
# simple non-wrapping interval
summarize.yaws(-60:60, plot = TRUE)
# a wrapping interval (points around ±180)
summarize.yaws(c(-175:-170, 171:179), plot = TRUE)
# singleton (no plotting)
summarize.yaws(30)
# more than one interval possible (returns warning)
summarize.yaws(c(-90, 90))
# output from find.yaw()
yaws <- find.yaw(
pitch2d = 14:16,
candidate_view_elevations = -45,
candidate_pitches = 10:20
)
summarize.yaws(yaws, plot = TRUE)
Trim candidate yaw sets by directional separation
Description
Trim two candidate yaw sets to angles that have at least one compatible partner in the other set, given minimum and maximum directional separation constraints.
Usage
trim.yaws(ccw_yaws, cw_yaws, min_sep, max_sep, plot = FALSE)
Arguments
ccw_yaws |
Numeric vector: candidate yaw angles known/expected to be
counterclockwise of |
cw_yaws |
Numeric vector: candidate yaw angles known/expected to be clockwise of
|
min_sep |
Numeric scalar: minimum directional angular separation in degrees, satisfying
|
max_sep |
Numeric scalar: maximum directional angular separation in degrees, satisfying
|
plot |
Logical scalar. |
Details
Given two sets of candidate yaw angles, trim.yaws() retains angles that
have at least one compatible partner in the other set. Directional
separation is measured counterclockwise from cw_yaws to ccw_yaws
and is represented in the interval [0, 360).
For a pair of angles c from ccw_yaws and w from cw_yaws,
their directional separation is
(c - w) \bmod 360.
A pair is compatible when this separation is greater than or equal to
min_sep and less than or equal to max_sep. Each returned angle
therefore has at least one compatible partner in the other returned set.
Value
A list with elements:
- trimmed_ccw_yaws
numeric: subset of
ccw_yawsthat have at least one matchingcw_yaws.- trimmed_cw_yaws
numeric: subset of
cw_yawsthat have at least one matchingccw_yaws.
Examples
# hypothetical candidate yaw sets:
a <- 10:80
b <- 30:90
# we know `a` to be CCW of `b` by up to 180°
trim.yaws(a, b, 0, 180, plot = TRUE)
# we know `a` to be CCW of `b` by 30 to 45°
trim.yaws(a, b, 30, 45, plot = TRUE)
# if no mutual partners exist, both returned vectors become empty
trim.yaws(ccw_yaws = c(-10), cw_yaws = c(130), min_sep = 0, max_sep = 180)