Camera Model

See Precomputing Camera Optics for how the kernels its optics need are built, and Pixel Coordinate Conventions for the pixel coordinates it takes and returns.

template<IsSpectral TSpectral>
class CameraModel : public huira::SceneObject<CameraModel<TSpectral>>

CameraModel represents a pinhole or thin-lens camera with configurable sensor, aperture, and distortion models.

This class provides a flexible camera abstraction for rendering and simulation, supporting various sensor types, aperture shapes, and lens distortion models. It allows configuration of focal length, f-stop, sensor resolution, pixel pitch, and more. The camera can project 3D points to the image plane, compute projected aperture area, and supports both analytic and PSF-based point spread functions. All units are SI unless otherwise noted.

The kernels the camera’s optics need for rendering (the PSF’s tables or grid, and the convolution of resolved bodies with it), and the tables of its geometry (each pixel’s ray direction and solid angle, and the view frustum), are derived from its settings and built when first needed, not by the setters, which are therefore cheap. precompute() builds them ahead of time; see there for what a render does when they are out of date. Getters give the same results either way.

Settings can be made in any order, with one exception: the aperture. Until it is set, the camera is f/2.8 at whatever focal length it has. set_fstop() and set_aperture_diameter() fix the aperture’s diameter, which a later focal length change keeps, changing the f-number (and logging a warning if the f-stop was set directly). See set_fstop().

Every setter checks its values and throws for ones that are not physically meaningful, leaving the camera as it was.

Template Parameters:

TSpectral – The spectral type (e.g., RGB, Visible8)

Public Functions

CameraModel()

Construct a new CameraModel with default sensor and aperture.

Initializes the camera with a 50 mm focal length, a SimpleSensor, and a CircularAperture at f/2.8, which it stays at until the aperture is set (see set_fstop()).

CameraModel(const CameraModel&) = delete
CameraModel &operator=(const CameraModel&) = delete
void set_focal_length(units::Millimeter focal_length)

Set the focal length of the camera (in millimeters).

Updates the camera intrinsics. What happens to the aperture depends on how it was set:

Parameters:

focal_length – Focal length in millimeters

Throws:

std::runtime_error – if the focal length is not positive and finite, or if the focus is a sensor offset that would put the sensor at the lens at this focal length.

inline units::Millimeter focal_length() const

Get the focal length of the camera in millimeters.

void set_fstop(float fstop)

Set the f-stop (focal ratio) of the camera.

Sets the aperture’s diameter to the current focal length divided by the f-stop. The diameter is what is kept: if the focal length changes later, the f-number changes with it, and a warning is logged the first time, in case that was not intended. Set the f-stop after the focal length to get the f-number asked for, or use set_aperture_diameter() to set the diameter itself.

Until the aperture is set, by this or set_aperture_diameter() or set_aperture(), the camera is f/2.8 at any focal length.

Parameters:

fstop – The f-number, e.g. 8 for f/8.

Throws:

std::runtime_error – if it is not positive and finite.

float fstop() const

Get the f-stop (focal ratio) of the camera: the focal length divided by the aperture’s diameter (for a non-circular aperture, the diameter of a circle of the same area).

void set_aperture_diameter(units::Millimeter diameter)

Set the aperture’s diameter, which is kept if the focal length changes.

The aperture keeps its shape. For a non-circular aperture this is the diameter of a circle of the same area. Unlike set_fstop(), the f-number then follows the focal length without a warning, since the diameter is what was asked for.

Parameters:

diameter – The aperture’s diameter.

Throws:

std::runtime_error – if it is not positive and finite.

units::Millimeter aperture_diameter() const

Get the aperture’s diameter (for a non-circular aperture, that of a circle of the same area).

template<IsDistortion<TSpectral> TDistortion, typename ...Args>
void set_distortion(Args&&... args)

Set the distortion model for the camera.

The distortion has to have an inverse over the whole image: every position on the sensor has to be where some ray lands. A model can fail that at the edges of a wide field, where it folds over or stops short of the image’s corners, which depends on the focal length and sensor as well as the coefficients. Since those can be set in any order, a failure is not reported here but by precompute(), a render, or cast_ray() and pixel_radiance_to_power() at the position concerned, until a setting changes that fixes it.

Template Parameters:
  • TDistortion – Distortion model type

  • Args – Constructor arguments for the distortion model

Parameters:

args – Arguments to construct the distortion model

void set_brown_conrady_distortion(BrownCoefficients coeffs)

Set Brown-Conrady distortion coefficients.

Parameters:

coeffs – Brown distortion coefficients

void set_opencv_distortion(OpenCVCoefficients coeffs)

Set OpenCV distortion coefficients.

Parameters:

coeffs – OpenCV distortion coefficients

void set_owen_distortion(OwenCoefficients coeffs)

Set Owen distortion coefficients.

Parameters:

coeffs – Owen distortion coefficients

void delete_distortion()

Delete the current distortion model.

template<IsSensor<TSpectral> TSensor, typename ...Args>
void set_sensor(Args&&... args)

Set the sensor model for the camera.

The new sensor brings its own resolution and pixel pitch. If they are the old sensor’s (a new noise model for the same sensor, say), the intrinsics are kept: the principal point, skew and any intrinsic matrix. If not, those were set for a different sensor and no longer apply, so they are reset, with a warning: the principal point to the center of the new sensor, the skew to 0, and fx and fy to the focal length over the new pitch. The focal length, aperture, distortion and focus belong to the lens and are kept either way.

Template Parameters:
  • TSensor – Sensor model type

  • Args – Constructor arguments for the sensor

Parameters:

args – Arguments to construct the sensor

void configure_sensor_from_pitch(const Resolution &resolution, units::Micrometer pitch_x, std::optional<units::Micrometer> pitch_y = std::nullopt, std::optional<float> cx = std::nullopt, std::optional<float> cy = std::nullopt)

Configure the sensor using pixel pitch and resolution.

This method sets the sensor resolution, pixel pitch, and principal point. It also computes the intrinsics based on the new configuration.

Parameters:
  • resolution – Sensor resolution

  • pitch_x – Pixel pitch in x direction (micrometers)

  • pitch_y – Pixel pitch in y direction (micrometers)

  • cx – Principal point x coordinate, in the camera’s pixel convention (see set_pixel_convention()). Defaults to the center of the sensor.

  • cy – Principal point y coordinate, likewise.

void configure_sensor_from_size(const Resolution &resolution, units::Millimeter width, std::optional<units::Millimeter> height = std::nullopt, std::optional<float> cx = std::nullopt, std::optional<float> cy = std::nullopt)

Configure the sensor using physical size and resolution.

This method sets the sensor resolution, physical size, and principal point. It also computes the intrinsics based on the new configuration.

Parameters:
  • resolution – Sensor resolution

  • width – Sensor width in millimeters

  • height – Sensor height in millimeters

  • cx – Principal point x coordinate, in the camera’s pixel convention (see set_pixel_convention()). Defaults to the center of the sensor.

  • cy – Principal point y coordinate, likewise.

void set_intrinsic_matrix(const Mat3<float> &intrinsic_matrix, const Resolution &resolution, units::Millimeter anchor_focal_length)

Set the intrinsics from a camera matrix.

The matrix maps normalized image coordinates to pixel coordinates, and is upper triangular:

| fx  s  cx |
|  0  fy cy |
|  0  0  1  |

Like any homogeneous matrix it is defined up to scale, so a matrix whose bottom-right entry is not 1 is divided through by it.

Mat3 is GLM’s column-major matrix, indexed K[column][row], so cx is K[2][0], cy is K[2][1] and s is K[1][0]. GLM’s constructor also takes the values a column at a time: Mat3<float>{fx, 0, 0, s, fy, 0, cx, cy, 1}. (From Python, pass the matrix as written above, as a row-major 3x3 array.) See set_intrinsics() for what the values mean.

Parameters:
  • intrinsic_matrix – The camera matrix.

  • resolution – Sensor resolution

  • anchor_focal_length – Anchor focal length in millimeters

Throws:

std::runtime_error – if the matrix is not upper triangular (most likely, it was filled one row at a time and is transposed), its bottom-right entry is 0, or as set_intrinsics().

void set_intrinsics(float fx, float fy, float cx, float cy, const Resolution &resolution, units::Millimeter anchor_focal_length, float skew = 0.f)

Set the intrinsic parameters for the camera.

A point at normalized image coordinates (x, y) projects to pixel (fx * x + skew * y + cx, fy * y + cy).

The principal point is read in the camera’s pixel convention (see set_pixel_convention()), so a calibration can be passed in the convention of the tool that produced it. fx and fy are scales, and mean the same in every convention. The skew does too, except that a convention counting y up from the bottom row flips its sign.

The skew is 0 for pixels with perpendicular axes, which is what most calibration tools assume (OpenCV, for one, never estimates it). It is kept if the focal length changes later, as a fraction of fx; configure_sensor_from_pitch() and configure_sensor_from_size() describe rectangular pixels and set it back to 0.

The anchor focal length sets the physical scale: the pixel pitch is the anchor focal length divided by fx and fy. Like set_focal_length(), it changes the focal length, with the same effect on the aperture.

Parameters:
  • fx – Focal length in x direction, in pixels

  • fy – Focal length in y direction, in pixels

  • cx – Principal point x coordinate, in the camera’s pixel convention

  • cy – Principal point y coordinate, in the camera’s pixel convention

  • resolution – Sensor resolution

  • anchor_focal_length – Anchor focal length in millimeters

  • skew – Skew, in pixels; 0 for perpendicular pixel axes.

Throws:

std::runtime_error – if fx or fy is not positive and finite, the principal point or skew is not finite, the resolution is not at least 1x1, or the anchor focal length is not valid (see set_focal_length()).

void set_sensor_roll(units::Radian angle)

Set the sensor’s roll: its rotation about the optical axis, relative to the camera.

The sensor’s x axis is turned by the angle toward its y axis, so the scene turns the other way in the image: with OpenCV’s axes (the default; x right, y down), a positive roll turns the scene counterclockwise in the image. The camera’s pose, and so the direction it looks, is unchanged. Projections and rays (project_point(), cast_ray()) are in the sensor’s axes.

Parameters:

angle – The roll angle (any angle unit).

Throws:

std::runtime_error – if the angle is not finite.

units::Radian sensor_roll() const

The sensor’s roll about the optical axis. See set_sensor_roll().

Rotation<double> sensor_orientation() const

The sensor’s orientation relative to the camera: the rotation from the sensor’s axes, in which projections and rays are given, to the camera’s.

It is the roll about the optical axis (see set_sensor_roll()); a tilted sensor will add to it.

std::pair<units::Micrometer, units::Micrometer> pixel_pitch() const

Get the pixel pitch.

Returns:

The pitch along x and along y.

Mat3<float> intrinsic_matrix() const

Get the camera matrix K = [[fx, s, cx], [0, fy, cy], [0, 0, 1]], as set_intrinsics() and set_intrinsic_matrix() take it: in pixels, with the principal point (cx, cy) in the camera’s pixel convention (see set_pixel_convention()).

Mat3 is column-major (GLM), so it is indexed K[column][row]: cx is K[2][0] and cy K[2][1].

template<IsAperture TAperture, typename ...Args>
void set_aperture(Args&&... args)

Set the aperture model for the camera.

The aperture’s size is kept if the focal length later changes, so the f-number follows the focal length. See set_fstop().

Template Parameters:
  • TAperture – Aperture model type

  • Args – Constructor arguments for the aperture

Parameters:

args – Arguments to construct the aperture

Throws:

std::runtime_error – if the aperture’s area or bounding radius is not positive and finite; the camera then keeps the aperture it had.

void enable_diffraction(bool diffraction = true)

Turn the aperture’s diffraction pattern on (the default) or off.

On, unresolved sources are drawn with the diffraction pattern of the aperture, averaged evenly over each spectral bin’s wavelengths, out to where their light fades (see Renderer::set_unresolved_taper()), and resolved bodies are convolved with it across the frame (see enable_psf_convolution()). Off, an unresolved source in focus puts all the light that diffraction would spread into the pixel it falls in. Either way scattered light (see set_scatter()) and the defocus at each source’s depth (see set_focus_distance()) are added.

A PSF that was set (see set_psf()) takes the diffraction pattern’s place, whether it is on or off; clear_psf() goes back to it. See psf_image() for the PSF as the renderer draws it, and diffraction_image() for the pattern alone.

Parameters:

diffraction – True to turn the diffraction pattern on, false to turn it off.

inline bool diffraction_enabled() const

Whether the aperture’s diffraction pattern is on (the default). A PSF that was set takes its place either way. See enable_diffraction().

template<IsPSF TPSF, typename ...Args>
void set_psf(Args&&... args)

Set a point spread function (PSF) in place of the aperture’s diffraction pattern.

The PSF is taken as complete within its radius (see PSF::radius()); one that does not set its radius gets DEFAULT_PSF_RADIUS. Unresolved sources are drawn from it on a grid (see GridPsf), tapered as with diffraction (see Renderer::set_unresolved_taper()): a still one at its exact position, and a moving one along its path, as line integrals (see GridPsf::draw_line()). Out of focus, it is blurred by the defocus at the source’s depth, as diffraction is (see psf_image()), unless it already holds it (PSF::includes_defocus(), as a MeasuredPSF can with PSFFocus::IncludesDefocus). Scattered light, if set, is added to it.

It stays in place until clear_psf(), whether diffraction is on or off (see enable_diffraction()). It is put on its grid here rather than at the next render, which checks every sample, and takes about a tenth of a second at the default radius.

Template Parameters:
  • TPSF – PSF model type

  • Args – Constructor arguments for the PSF

Parameters:

args – Arguments to construct the PSF

Throws:

std::runtime_error – if the PSF’s constructor throws, or any of its samples within its radius is negative or not finite, or they hold no light in a channel (see GridPsf). The camera then keeps the PSF it had.

void set_measured_psf(const Image<TSpectral> &data, float samples_per_pixel, PSFSampling sampling, PSFFocus focus = PSFFocus::InFocus)

Set a measured (user-supplied) PSF in place of the aperture’s diffraction pattern.

Convenience wrapper around set_psf<MeasuredPSF>() that is also exposed through the Python bindings. See MeasuredPSF for the data conventions (centered measurement, sampling density). The measurement is taken whole, as holding all of a source’s light out to its farthest sample; crop it to use less.

Parameters:
  • data – Measured PSF samples, centered on the image.

  • samples_per_pixel – Measurement samples per sensor pixel per axis.

  • sampling – What the samples are: the PSF’s intensity at points, or the light a pixel centered there receives, such as a star image taken with the sensor itself (see PSFSampling).

  • focus – Whether the samples are a point’s in focus, which the camera’s focus blurs as it blurs diffraction, or already hold the defocus the camera gives (see PSFFocus).

Throws:

std::runtime_error – if the data is not a valid measurement (see MeasuredPSF); the camera then keeps the PSF it had.

void clear_psf()

Remove a PSF set with set_psf() or set_measured_psf(): unresolved sources are drawn with the aperture’s diffraction pattern again, or without one if it is off (see enable_diffraction()).

inline bool has_set_psf() const

Whether a PSF was set with set_psf() or set_measured_psf(), in place of the aperture’s diffraction pattern. See clear_psf().

void set_scatter(float fraction, float slope, units::Radian shoulder_angle, std::optional<units::Radian> outer_angle = std::nullopt)

Set scattered light: Harvey-Shack wings around every source, in angles.

A fraction of each source’s light leaves the diffraction pattern and spreads as

S(theta) = K (1 + (theta / theta_0)^2)^(-slope / 2) (1 + (theta / theta_1)^2)^(-1)

with theta the angle from the source, theta_0 the shoulder angle and theta_1 the outer angle: flat within the shoulder, falling as theta^-slope beyond it, and as theta^-(slope + 2) beyond the outer angle, if one is given. K makes the scattered light total the fraction. Measured stray light around stars often falls with a slope of 2 to 3 (King 1971, Racine 1996).

Without an outer angle the light totals a finite amount only for a slope over 2, so a slope of 2 or less needs one. The outer angle bends the wings down smoothly rather than cutting them off, so that they leave no edge in an image. An angle theta lands f theta from the source on the sensor, within 0.1% below 3 degrees.

The scattered light follows the focal length and pixel pitch. It is included in psf_image(), and given alone by scatter_image(). Unresolved sources, still or moving, are drawn from tables that hold it: the diffraction pattern’s, or with a PSF set in its place or none, its own, blurred by each source’s defocus; resolved bodies are convolved with it across the frame (see enable_psf_convolution()). disable_scatter() turns it off.

Parameters:
  • fraction – Fraction of the light scattered, in [0, 1); 0 for none.

  • slope – How fast the wings fall beyond the shoulder; positive.

  • shoulder_angle – The angle within which the wings are flat; positive.

  • outer_angle – The angle beyond which they fall faster, beyond the shoulder; none by default.

Throws:

std::runtime_error – if an argument is out of range, an angle is not below 90 degrees, or the slope is 2 or less without an outer angle.

void disable_scatter()

Turn scattered light off (the default), as set_scatter() with a fraction of 0 does.

inline bool scatter_enabled() const

Whether scattered light is on: see set_scatter(). Off by default.

void set_veiling_glare(float alpha)

Set the veiling glare alpha value.

Parameters:

alpha – Veiling glare alpha (0 to 1)

void disable_veiling_glare()

Disable veiling glare effects.

inline void enable_psf_convolution(bool convolve_psf = true)

Choose whether the PSF and scattered light blur resolved bodies (the path-traced image). On by default. Off, bodies are sharp, and rendering is faster without the whole-image convolution; unresolved sources, such as stars, get the PSF and scattered light either way. Bodies get the whole PSF and scattered light, across the frame, in focus: path tracing gives them their defocus.

inline bool psf_convolution_enabled() const

Whether the PSF and scattered light blur resolved bodies (the default). See enable_psf_convolution().

Image<TSpectral> psf_image(int radius, float x_offset = 0.f, float y_offset = 0.f, std::optional<units::Meter> range = std::nullopt)

The light an unresolved source puts in each pixel around it, per channel, as the renderer draws it.

Each pixel holds the fraction of the source’s light, in each channel, that falls on it: the PSF integrated over the pixel, for the source at its exact position. The PSF is the aperture’s diffraction pattern (see enable_diffraction()), averaged evenly over each spectral bin’s wavelengths, for a circular aperture, with scattered light set with set_scatter(), blurred by defocus as the focus setting blurs a point at the given range: a uniform disc, the aperture’s image out of focus (see set_focus_distance()). Without a range the point is a star, at infinity. There is no blur with depth of field off (see enable_depth_of_field()). As the renderer draws unresolved objects, a source whose blur is a star’s within PsfTables::BLUR_TOLERANCE is given a star’s tables. See PsfTables for how it is computed and how accurate it is.

A PSF set with set_psf() or set_measured_psf() is given as the renderer draws a still source with it (see GridPsf), blurred by the same defocus unless the PSF already holds it (see PSFFocus); with diffraction off and no PSF set, the defocus blur alone, a uniform disc (see GridPsf::disc()), or in focus the pixel the source falls in, holds that share of its light. Either way the scattered light’s share, if any, is from its own tables.

Resolved bodies are convolved with this PSF in focus (see enable_psf_convolution()). The components are given alone by diffraction_image(), scatter_image() and defocus_image().

The tables for stars are built the first time they are needed after the optics change: the focal length, aperture, pixel pitch, resolution, scattered light or focus. Tables for another blur are built the first time it is needed, and the last few are kept, shared with the renderer. Either can take a second or two.

Parameters:
  • radius – The image is 2 radius + 1 pixels square, centered on the pixel the source is offset from.

  • x_offset – The source’s position right of the center pixel’s center, in pixels.

  • y_offset – Its position below the center pixel’s center, in pixels.

  • range – The source’s distance along the camera’s axis, which sets its defocus; none for a star.

Throws:

std::runtime_error – if the radius is negative or over MAX_PSF_IMAGE_RADIUS, an offset is not finite, or the range is not positive.

Returns:

The image. Each channel sums to the part of the source’s light that falls within it.

Image<TSpectral> diffraction_image(int radius, float x_offset = 0.f, float y_offset = 0.f) const

The aperture’s diffraction pattern alone, in focus, as psf_image() gives the light in each pixel: averaged evenly over each spectral bin’s wavelengths, for a circular aperture of the aperture’s bounding radius, at the current focal length and pixel pitch.

It is the aperture’s whether or not diffraction is on and whether or not a PSF was set in its place (see enable_diffraction()), without scattered light or defocus. Its tables are built for each call, which can take a second.

Parameters:
  • radius – The image is 2 radius + 1 pixels square, centered on the pixel the source is offset from.

  • x_offset – The source’s position right of the center pixel’s center, in pixels.

  • y_offset – Its position below the center pixel’s center, in pixels.

Throws:

std::runtime_error – if the radius is negative or over MAX_PSF_IMAGE_RADIUS, or an offset is not finite.

Returns:

The image. Each channel sums to the part of the pattern’s light that falls within it.

Image<TSpectral> scatter_image(int radius, float x_offset = 0.f, float y_offset = 0.f) const

The scattered light alone (see set_scatter()), in focus, as psf_image() gives the light in each pixel: its profile, at its size in pixels at the current focal length and pixel pitch, holding all of the light, not just the scattered fraction.

Parameters:
  • radius – The image is 2 radius + 1 pixels square, centered on the pixel the source is offset from.

  • x_offset – The source’s position right of the center pixel’s center, in pixels.

  • y_offset – Its position below the center pixel’s center, in pixels.

Throws:

std::runtime_error – if there is no scattered light, the radius is negative or over MAX_PSF_IMAGE_RADIUS, or an offset is not finite.

Returns:

The image. Each channel sums to the part of the scattered light that falls within it.

Image<TSpectral> defocus_image(int radius, float x_offset = 0.f, float y_offset = 0.f, std::optional<units::Meter> range = std::nullopt) const

The defocus blur alone that the focus gives a point at a range, as psf_image() gives the light in each pixel: a uniform disc of radius defocus_blur_radius(range), each pixel holding the share of it that falls on the pixel.

In focus, or with depth of field off, the pixel the point falls in holds all of it. A PSF that was set and already holds the defocus (see PSFFocus) has none added to it when drawn; this still gives the camera’s.

Parameters:
  • radius – The image is 2 radius + 1 pixels square, centered on the pixel the point is offset from.

  • x_offset – The point’s position right of the center pixel’s center, in pixels.

  • y_offset – Its position below the center pixel’s center, in pixels.

  • range – The point’s distance along the camera’s axis; none for a star.

Throws:

std::runtime_error – if the radius is negative or over MAX_PSF_IMAGE_RADIUS, an offset is not finite, or the range is not positive.

Returns:

The image. It sums to the part of the disc that falls within it.

void precompute()

Build everything the camera’s optics and geometry need for the next render, now.

The kernels a render uses are derived from the camera’s settings: the diffraction pattern’s tables for unresolved sources, with the defocus blur and scattered light (see enable_diffraction(), set_focus_distance() and set_scatter()), or a PSF that was set, or the defocus blur without either, on its grid, blurred as a star is by the focus (see set_psf()), with the scattered light’s own tables, and the convolution of resolved bodies with the whole PSF (see enable_psf_convolution()). So are the tables of the camera’s geometry: each pixel’s ray direction with distortion, and its solid angle, and the view frustum, which depend on the focal length, sensor, intrinsics, distortion and axis convention. The setters only record settings, so they are cheap and can be made in any order, and each kernel or table is rebuilt only when a setting it depends on has changed: refocusing while a star’s blur stays under PsfTables::MIN_BLUR, for example, rebuilds nothing.

Building can take a while, for the PSF’s tables or a large set PSF, or a large sensor with distortion. Calling this once the camera is configured keeps that time out of the first render, so that every render takes comparable time, as a timed or hardware-in-the-loop run needs.

Without it, a render builds whatever is out of date itself and logs the time taken: as a warning the first time after precompute() was called (the settings have then changed since), and as information otherwise. enable_auto_precompute(false) makes the render throw instead.

Only what the current settings use is built: for example, no tables of the diffraction pattern while a PSF is set in its place. Tables and grids for unresolved objects whose blur differs from a star’s are built by the render that first needs them (see psf_image()).

Throws:

std::runtime_error – if the lens distortion has no inverse somewhere in the image at this focal length and sensor (see set_distortion()).

bool is_precomputed() const

Whether everything the next render needs from the camera’s optics and geometry is built and up to date.

True after precompute(), until a setting changes that a kernel or table the render uses depends on. Setting a value to what it already is changes nothing. Also true when the settings need nothing built.

void enable_auto_precompute(bool auto_precompute = true)

Choose what a render does when the camera’s geometry or optics are out of date.

Enabled by default: the render builds them, as precompute() would, and logs the time taken. Disabled, the render throws instead, which keeps that time out of every render: a timed or hardware-in-the-loop run can disable it and call precompute() after every change.

The exception is an unresolved object whose defocus blur is not a star’s (see psf_image()): what it is drawn from depends on its depth, which only the render knows, so the render builds it the first time that blur is needed either way, which for the aperture’s diffraction can take a second or two. Disabled, the render warns when it does, once until precompute() is called again. Objects far enough away share the stars’.

Parameters:

auto_precompute – True for the render to build what is out of date, false to throw.

inline bool auto_precompute_enabled() const

Whether a render builds an out-of-date camera itself (the default). See enable_auto_precompute().

void enable_depth_of_field(bool depth_of_field = true)

Turn depth of field on (the default) or off.

On, the camera has the focus that set_focus_distance(), set_focus_diopters() or set_focus_sensor_offset() gave it (at infinity unless set): resolved bodies are traced with rays from across the aperture, which blurs whatever is out of focus, and unresolved sources are blurred by the defocus at their depth: stars as defocus_blur_radius() says, and objects nearer by their own (see psf_image()). Off, everything is in focus, as through a pinhole, whatever the focus setting.

Rays from across the aperture are noisier where bodies are out of focus, and need more samples per pixel to look clean: with focus at infinity, bodies closer than about the aperture’s diameter over the angle a pixel spans (for 50 mm at f/2.8 and 8.5 um pixels, about 100 m).

Parameters:

depth_of_field – True to turn depth of field on, false to turn it off.

inline bool depth_of_field_enabled() const

Whether depth of field is on (the default). See enable_depth_of_field().

void enable_motion_blur(bool motion_blur = true)

Turn motion blur on (the default) or off.

On, a view through the camera (a SceneView) shows what moves during the exposure blurred along its path: it gives each thing as many temporal samples as its motion needs, or the number it is given. Off, it renders a still frame, at the middle of the exposure, as one temporal sample does, whatever number it is given.

Parameters:

motion_blur – True to turn motion blur on, false to turn it off.

inline bool motion_blur_enabled() const

Whether motion blur is on (the default). See enable_motion_blur().

void set_focus_distance(units::Meter focus_distance)

Focus the camera at a distance.

Any value other than zero or NaN is accepted. Positive values focus in front of the camera, and infinity of either sign focuses at infinity. Negative values focus past infinity: the rays then diverge from a virtual point that distance behind the camera.

Equivalent to set_focus_diopters() with the reciprocal. Either way the camera stays focused at this distance if the focal length later changes, and focus_distance() returns exactly this value (with infinity of either sign returned as +inf).

Parameters:

focus_distance – Focus distance (any length unit).

Throws:

std::runtime_error – if the distance is NaN or closer to zero than 1e-12 m.

void set_focus_diopters(units::Diopter diopters)

Focus the camera in diopters: the reciprocal of the focus distance.

0 focuses at infinity, positive values focus in front of the camera, and negative values focus past infinity. This is the sign of a focus scale marked in diopters. (In the sign convention of optics, where light diverging from a point in front of a lens has negative vergence, the light from the focus distance has vergence -diopters.)

Parameters:

diopters – The reciprocal of the focus distance (diopters, i.e. reciprocal meters).

Throws:

std::runtime_error – if it is not finite, or corresponds to a focus distance closer to zero than 1e-12 m.

void set_focus_sensor_offset(units::Micrometer offset)

Focus the camera by moving the sensor away from the infinity-focus position.

The offset is measured along the optical axis, from the plane where the lens focuses an object at infinity. 0 focuses at infinity, positive values (sensor farther from the lens) focus in front of the camera, and negative values focus past infinity. The focus distance follows from the thin lens equation, 1/f = 1/distance + 1/(f + offset).

Unlike the other focus setters this one is image-side, so the offset is what is held fixed if the focal length later changes, and the focus distance moves with it.

A point at infinity blurs to a disk of diameter close to |offset| / N, for f-number N. The camera’s projection does not change with focus, so for offsets that are a sizeable fraction of the focal length this differs from a physical lens by roughly a factor f / (f + offset).

Parameters:

offset – Sensor offset (any length unit).

Throws:

std::runtime_error – if the offset is not finite, or puts the sensor at the lens.

units::Meter focus_distance() const

Get the distance the camera is focused at: negative past infinity, +inf at infinity.

units::Diopter focus_diopters() const

Get the focus in diopters: the reciprocal of focus_distance(), 0 at infinity.

units::Micrometer focus_sensor_offset() const

Get the focus as a sensor offset from the infinity-focus position.

See set_focus_sensor_offset(). For a focus distance of exactly one focal length the offset is infinite, and for shorter distances, which a physical lens cannot focus on, it is below -f (the thin lens image is virtual). Those values still round-trip through set_focus_sensor_offset().

float defocus_blur_radius(std::optional<units::Meter> range = std::nullopt) const

Get the radius, in pixel widths, of the defocus blur of a point at a range, or of a star without one.

Depth of field sends light from a point through aperture point a to the sensor position for direction a / d off the point’s own, for focus distance d. Over an aperture of bounding radius R that is a disc of radius R f |v|, for v the difference in vergence between the point and the focus: exactly what the ray tracer does, since focus does not change the projection, and to first order what a physical lens does (see set_focus_sensor_offset()).

This is the blur unresolved sources are drawn with (see defocus_image() and psf_image()): 0 below PsfTables::MIN_BLUR, where it changes no pixel by more than a part in 10^5 of the peak, and with depth of field off (see enable_depth_of_field()). A PSF that was set and already holds the defocus has none added to it (see PSFFocus).

Parameters:

range – The point’s distance along the camera’s axis; none for a star, at infinity.

Throws:

std::runtime_error – if the range is not positive.

void set_pixel_convention(PixelConvention convention)

Set the pixel coordinate convention the camera’s users work in.

Applies to the principal point passed to configure_sensor_from_pitch(), configure_sensor_from_size(), set_intrinsics() and set_intrinsic_matrix(), and to the positions project_point() returns and cast_ray() takes. The default, PixelConvention::opencv(), puts the center of the top-left pixel at (0, 0) with y down. See PixelConvention for the others.

It can be set before or after the principal point: a principal point is kept as given and read in whichever convention is current. Nothing rendered changes unless a principal point was given, since the default principal point is the center of the sensor in every convention.

Parameters:

convention – The convention, e.g. PixelConvention::fits().

inline PixelConvention pixel_convention() const

Get the pixel coordinate convention used for the principal point, project_point() and cast_ray(). See PixelConvention.

Pixel project_point(const Vec3<float> &point_camera_coords) const

Project a 3D point in camera coordinates onto the image plane.

Uses the pinhole camera model and applies distortion if present.

Parameters:

point_camera_coords – 3D point in camera coordinates (meters)

Returns:

Pixel 2D point on the image plane, in the camera’s pixel convention (see set_pixel_convention()), or NaN in both coordinates where the lens images nothing: past the pole of an OpenCV distortion model’s rational radial factor.

Pixel try_project_point(const Vec3<float> &point_camera_coords) const
Ray<TSpectral> cast_ray(const Pixel &pixel, Sampler<float> &sampler) const

Cast a camera ray through a position on the image, sampling the aperture when depth of field is enabled.

The position may be anywhere, including outside the image.

Parameters:
Throws:

std::runtime_error – if the lens distortion has no inverse at the position (see set_distortion()).

Ray<TSpectral> cast_ray(const Pixel &pixel) const

Cast a pinhole camera ray through a position on the image.

The position may be anywhere, including outside the image. The direction is computed exactly (with distortion, by inverting it), so it needs nothing built (see precompute()).

Parameters:

pixel – Position in the camera’s pixel convention (see set_pixel_convention()).

Throws:

std::runtime_error – if the lens distortion has no inverse at the position (see set_distortion()).

Ray<TSpectral> cast_ray(int x, int y) const

Cast a pinhole camera ray through integer coordinates (x, y) in the camera’s pixel convention: with the default, PixelConvention::opencv(), the center of pixel (x, y).

std::optional<Ray<TSpectral>> try_cast_ray(const Pixel &pixel) const

As cast_ray(const Pixel&), but empty, rather than throwing, where the lens distortion has no inverse at the position (see set_distortion()).

Parameters:

pixel – Position in the camera’s pixel convention (see set_pixel_convention()).

inline const Frustum<TSpectral> &view_frustum() const

Get the frustum representing the camera’s field of view. Found from the image’s boundary when first needed after a change (see precompute()).

float pixel_radiance_to_power(int x, int y) const

The power a pixel receives per unit of radiance: its solid angle times the aperture area projected along the direction to the pixel’s center.

Read from a table that precompute() and renders build; until then, computed directly, the same way.

Parameters:
  • x – Pixel column.

  • y – Pixel row, from the top.

Throws:

std::runtime_error – if the lens distortion has no inverse at the pixel (see set_distortion()).

bool in_fov(const Vec3<float> &point) const
inline void readout(FrameBuffer<TSpectral> &fb, units::Second exposure_time) const

Read out the sensor into the given frame buffer with the specified exposure time.

Does nothing when the frame buffer’s sensor response is disabled: there is nowhere to put the result, and sensor models write into it unconditionally.

Throws:

std::runtime_error – if the exposure time is negative or not finite, or the sensor response is enabled but the received power it reads out is not (FrameBuffer::enable_received_power(false) after enable_sensor_response()).

float projected_aperture_area(const Vec3<float> &direction) const

The aperture’s area projected along a direction in camera coordinates, in square meters: its area times the cosine of the direction’s angle from the optical axis.

Parameters:

direction – Direction vector (need not be normalized)

inline Resolution resolution() const

Get the sensor resolution.

inline virtual std::string type() const override

Get the type of the camera model.

inline FrameBuffer<TSpectral> make_frame_buffer() const

Create a frame buffer matching the sensor resolution.

void use_blender_convention(bool value = true)

Enable or disable the Blender convention for the camera model.

In the Blender convention the camera looks along -Z with +Y up, rather than along +Z with +Y down. The image itself is the same either way.

inline bool uses_blender_convention() const

Whether the camera uses Blender’s axes (-z forward, y up) rather than OpenCV’s (z forward, y down, the default). See use_blender_convention().

std::string describe() const

A summary of the camera’s settings, one per line, for reading: its optics, sensor, focus, PSF, motion blur and conventions. Builds nothing. The format is for people, and may change.

template<typename ...Args>
inline Rotation<double> sensor_rotation(Args&&...) const
template<typename ...Args>
inline bool is_blender_convention(Args&&...) const
template<typename ...Args>
inline float get_projected_aperture_area(Args&&...) const
template<typename ...Args>
inline Resolution res(Args&&...) const
template<typename ...Args>
inline void set_measured_psf(const Image<TSpectral>&, float, Args&&...)
template<typename ...Args>
inline void set_diopters(Args&&...)
template<typename ...Args>
inline units::Diopter get_diopters(Args&&...) const
template<typename ...Args>
inline units::Meter get_focus_distance(Args&&...) const
template<typename ...Args>
inline void use_aperture_psf(Args&&...)
template<typename ...Args>
inline void delete_psf(Args&&...)
template<typename ...Args>
inline bool has_psf(Args&&...) const
template<typename ...Args>
inline void set_psf_convolution_radius(Args&&...)
template<typename ...Args>
inline void set_harvey_shack_scatter(Args&&...)
template<typename ...Args>
inline void disable_harvey_shack_scatter(Args&&...)
template<typename ...Args>
inline const Image<TSpectral> &get_psf_kernel(Args&&...)
template<typename ...Args>
inline int get_psf_radius(Args&&...) const
template<typename ...Args>
inline const Image<TSpectral> &get_psf_convolution_kernel(Args&&...)
template<typename ...Args>
inline const Image<TSpectral> &get_psf_wings_kernel(Args&&...)
template<typename ...Args>
inline std::uint64_t psf_kernel_version(Args&&...) const
inline bool is_scene_owned() const

Check if the object is owned by the scene.

Returns:

bool True if owned

inline void set_scene_owned(bool owned)

Set scene ownership flag.

Parameters:

owned – True if owned

inline std::string name() const

Get the object’s name.

Returns:

std::string Name

inline virtual std::uint64_t id() const

Get the object’s unique ID.

Returns:

std::uint64_t ID

inline virtual std::string get_info() const

Get a descriptive info string for the object.

Returns:

std::string Info string

Public Static Attributes

static constexpr int DEFAULT_PSF_RADIUS = 64

How far from the source, in pixels, a PSF that was set holds light, unless it says (see PSF::radius() and set_psf()).

static constexpr int MAX_PSF_IMAGE_RADIUS = 16384

The largest radius psf_image() takes, in pixels.

Protected Types

enum class OpticsInput : std::size_t

The settings the optics kernels are derived from. Each kernel depends on some of them, and is rebuilt only when one of those has changed since it was built.

Values:

enumerator FocalLength
enumerator PixelPitch
enumerator Resolution
enumerator Aperture
enumerator Focus
enumerator CorePSF
enumerator Scatter
enumerator Count
enum class FocusReference

Values:

enumerator Distance
enumerator Diopters
enumerator SensorOffset
enum class ApertureSource

Where the aperture’s size came from, which decides what a focal length change does to it: the default aperture stays f/2.8, and one that was set keeps its diameter.

Values:

enumerator Default
enumerator FStop
enumerator Diameter

Protected Functions

inline bool uses_diffraction_() const

Whether unresolved sources are drawn with the aperture’s diffraction pattern: it is on, and no PSF was set in its place. Its tables then hold the scattered light too.

inline bool has_psf_or_scatter_() const

Whether there is anything to blur with: diffraction, a PSF that was set, or scattered light.

inline bool convolves_bodies_() const

Whether the path-traced image of resolved bodies is convolved with the PSF and scattered light: see enable_psf_convolution().

inline void optics_changed_(OpticsInput input)
bool stale_(std::uint64_t built_at, std::initializer_list<OpticsInput> inputs) const

Whether anything built at built_at is out of date: never built, or one of the inputs has changed since.

void note_object_build_()

Warns, with auto precompute off, that what an unresolved object is drawn from for its own blur is being built, as it must be (see enable_auto_precompute()): once until precompute() is called again. optics_mutex_ must be held.

void ensure_render_tables_()

Build the tables the renderer draws unresolved sources from, if out of date: the aperture’s diffraction with the scattered light, or the scattered light alone with another PSF or none, blurred as a star is by the focus. See render_tables_.

void ensure_body_convolver_()

Prepare the convolution for resolved bodies, if out of date: with the whole PSF (see body_parts_()), from the renderer’s tables in focus, or from tables of their own otherwise. A PSF that was set must be on its grid (see ensure_grid_psf_()).

void ensure_grid_psf_()

Put a PSF set with set_psf() or set_measured_psf() on its grid, in focus and blurred as a star is by the focus, if out of date (see GridPsf). Without a PSF, a star’s defocus blur alone, a disc, or in focus a point, is drawn from a GridPsf of its own (see GridPsf::disc()).

std::vector<std::shared_ptr<const PsfTables<TSpectral>>> tables_at_locked_(const std::vector<double> &inverse_depths)

tables_at_(), with optics_mutex_ held.

std::vector<std::shared_ptr<const GridPsf<TSpectral>>> grids_at_locked_(const std::vector<double> &inverse_depths)

grids_at_(), with optics_mutex_ held.

std::shared_ptr<const GridPsf<TSpectral>> blurred_grid_(double blur, double aspect) const

The grid a source with the given blur, in pixel widths, is drawn from: the PSF that was set, blurred by it, or without one the disc alone (see GridPsf::disc()).

double source_blur_(double inverse_depth) const

The blur, in pixel widths, an unresolved source is given at an inverse depth (0 for a star): none below PsfTables::MIN_BLUR, or if the PSF already holds the defocus (see PSF::includes_defocus()).

double tables_reach_() const

How far the tables reach: twice the frame’s diagonal, since a source lights pixels up to a diagonal away and can lie as far outside the frame, and at least 4096 pixels, so that smaller frames share tables. Beyond, the far field continues as a power law, which is exact for diffraction alone.

bool render_tables_stale_() const

Whether the renderer’s tables are out of date. A smaller frame does not rebuild them.

double tables_blur_() const

The blur, in pixel widths, the tables give a star: none below PsfTables::MIN_BLUR, where they are the same as in focus.

bool body_tables_stale_() const

Whether the tables for resolved bodies are out of date: the renderer’s, in focus. None are needed without diffraction or scattered light.

bool body_convolver_stale_() const

Whether the convolution for resolved bodies is out of date: its tables, its core, or the resolution.

bool grid_psf_stale_() const

Whether the grids are out of date: the PSF that was set (or that there is none) or the pixels’ shape changed, or the blur a star gets.

std::shared_ptr<const PsfTables<TSpectral>> build_psf_tables_(double blur, const std::optional<typename PsfTables<TSpectral>::Scatter> &scatter, double reach) const

Build the tables for the current optics, the given blur (in pixel widths) and scattered light, out to the given reach (see tables_reach_()). With all of the light scattered, the diffraction pattern is left out.

std::optional<typename PsfTables<TSpectral>::Scatter> scatter_for_tables_() const

The scattered light in pixel widths for the tables, as set with set_scatter(); none if it is off.

std::optional<typename PsfTables<TSpectral>::Scatter> tables_scatter_() const

The scattered light the renderer’s tables hold: as set with the diffraction pattern, which they hold too; and otherwise all of their light, the scattered light alone, which the renderer gives its fraction (see scatter_for_tables_()).

inline bool uses_tables_() const
PsfConvolver<TSpectral>::Parts body_parts_() const

What resolved bodies are convolved with, in focus, since path tracing gives them their defocus: the aperture’s tables, which hold its diffraction and the scattered light; or a PSF that was set on its grid, or a point without one, with 1 - f of the light, and the scattered light’s tables with f of it, f being the scattered fraction (0 without scattered light).

double blur_pixels_(double inverse_range) const

The radius, in pixel widths, of the defocus blur of a point at 1 / inverse_range (0 for infinity): the aperture’s radius times the focal length times the difference in vergence between the point and the focus. See defocus_blur_radius(), which this is without its threshold. 0 with depth of field off.

void check_image_arguments_(const char *caller, int radius, float x_offset, float y_offset, std::optional<units::Meter> range) const

Checks the arguments the PSF’s images take, throwing with the caller’s name.

double image_reach_(int radius) const

How far the tables for an image of the given radius need to reach: past its corners, at least 64 pixels.

void precompute_locked_()

Build every kernel the current settings use that is out of date.

bool is_precomputed_locked_() const

Whether precompute_locked_() has nothing to do. Mirrors it.

void precompute_for_render_()

Called by the renderer before each render: makes sure the optics kernels are up to date, as enable_auto_precompute() chooses.

std::vector<std::shared_ptr<const PsfTables<TSpectral>>> tables_at_(const std::vector<double> &inverse_depths)

The tables for unresolved sources at depths (their distance along the axis, which sets their defocus, as it does for path-traced bodies), given as their inverses: 0 for a star. One call gives all that a render needs.

A source whose blur is a star’s, within PsfTables::BLUR_TOLERANCE, is drawn from the renderer’s tables: with the Jupiter camera (125 mm at f/3.3, 8.5 um pixels, 278.5 px of blur per diopter), any at more than about 110,000 km focused at 10 m, or 2,800 km focused at infinity, where blurs under MIN_BLUR count as none. Others get tables for their own blur, built the first time a blur is needed (taking as long as the renderer’s) and kept for later sources and renders whose blur is within the tolerance of it: all that this call used, and the most recently used others up to OBJECT_TABLES_KEPT_ in all. They are rebuilt when the optics change.

std::vector<std::shared_ptr<const GridPsf<TSpectral>>> grids_at_(const std::vector<double> &inverse_depths)

The grids of a PSF that was set (see set_psf()) for unresolved sources at depths, given as their inverses (0 for a star), as tables_at_() gives the aperture PSF’s tables: the PSF blurred by each one’s defocus, unless the PSF already holds it (see PSF::includes_defocus()). A source whose blur is a star’s, within PsfTables::BLUR_TOLERANCE, is drawn from the stars’ grid, and one in focus from the PSF’s own; others from grids for their own blur, built the first time a blur is needed and kept as tables_at_() keeps tables. They are rebuilt when the PSF or the pixels’ shape changes.

std::array<double, TSpectral::size()> psf_convolution_error_(const Image<TSpectral> &image, FftPrecision precision) const

About the most convolving an image with the whole PSF in a precision would put in each channel of its pixels where it is dark, from rounding (see PsfConvolver::rounding_error()).

For the renderer, after precompute_for_render_().

void apply_psf_convolution_(Image<TSpectral> &image, FftPrecision precision, FftPlanEffort effort) const

Convolve an image in place with the whole PSF, across the frame (see PsfConvolver and body_parts_()), in a precision, with plans made with an effort.

For the renderer, after precompute_for_render_().

void check_focal_length_(float focal_length, const std::string &caller) const

Checks a focal length before it is stored.

It must be positive and finite, and must leave the focus valid: a focus given as a sensor offset is re-resolved at the new focal length, and must not put the sensor at the lens.

Parameters:
  • focal_length – Focal length in meters.

  • caller – Name for the error message.

void set_focus_(FocusReference reference, double setting)

Store an already validated focus setting and apply it.

void update_focus_()

Resolve the focus setting into the focus distance used by ray generation.

Must run whenever the focus setting or the focal length changes (compute_intrinsics_() calls this), since a sensor offset resolves to a different distance at a different focal length.

Pixel project_to_sensor_(const Vec3<float> &point_camera_coords) const

Project a point in camera coordinates to sensor coordinates (pixel i covers [i, i + 1), y down), whatever the pixel convention.

Ray<TSpectral> sensor_ray_(const Pixel &sensor_position, Sampler<float> &sampler, bool use_field = true) const

cast_ray() in sensor coordinates (pixel i covers [i, i + 1), y down), for internal use: the renderer samples pixels in sensor coordinates.

Ray<TSpectral> sensor_ray_(const Pixel &sensor_position, bool use_field = true) const

Pinhole cast_ray() in sensor coordinates, for internal use.

void set_aperture_area_(units::Meter diameter)

Resize the aperture to the area of a circle of the given diameter, keeping its shape.

void resize_default_aperture_(float focal_length)

Resize the default aperture, which keeps the camera at f/2.8, for a focal length about to be stored. A focal length whose aperture can’t be held (its area overflows or underflows single precision) is then refused before anything changes, since set_aperture_area_() changes nothing when it throws. The setters call this last of all that can throw.

Parameters:

focal_length – The new focal length, in meters.

void focal_length_changed_(float previous_focal_length)

Apply a focal length change to the aperture. See set_focal_length().

Parameters:

previous_focal_length – The focal length before the change, in meters; 0 when the camera is first constructed.

void compute_intrinsics_()

Compute the camera intrinsic parameters (focal lengths, principal point, resolution).

Resolve the intrinsics (fx, fy, cx, cy, skew) from the geometry settings, and mark the geometry tables out of date. Every setter that changes focal length, sensor, principal point, distortion or axis convention calls this, so the result does not depend on the order the settings were made in. It is cheap: the tables are built when needed (see ensure_pixel_geometry_() and ensure_frustum_()).

template<IsFloatingPoint TFloat>
Vec3<TFloat> pixel_to_direction_(const Pixel &pixel) const
inline bool pixel_geometry_current_() const
void ensure_pixel_geometry_() const

Build the per-pixel tables (the distortion field and the pixels’ geometry factors) if they are out of date. Thread-safe: concurrent callers wait for a single build.

void build_pixel_geometry_() const

Build the per-pixel tables in one pass over the pixel corners: each corner’s direction is found once, and serves the distortion field and the four pixels that share it.

double pixel_geometry_factor_(int x, int y) const

Pixel (x, y)’s geometry factor, computed directly: as the table holds it.

double pixel_geometry_factor_(const Vec3<double> &c00, const Vec3<double> &c10, const Vec3<double> &c11, const Vec3<double> &c01) const

A pixel’s power per unit radiance and aperture area, from the unit directions of its corners (c00 at (x, y), c10 at (x + 1, y), c11 at (x + 1, y + 1), c01 at (x, y + 1)): its solid angle, as two spherical triangles, times the cosine of the angle between the optical axis and its center, which foreshortens the aperture. The center’s direction is the mean of the corners’, as interpolating the distortion field gives it.

Vec3<double> corner_direction_(int x, int y) const

The unit direction of the pinhole ray through pixel corner (x, y), in sensor coordinates; NaN where the distortion has no inverse.

Vec3<float> ray_direction_(const Pixel &pixel, bool use_field = true) const

Direction (not normalized) of the pinhole ray through a sensor position.

Direction (not normalized) of the pinhole ray through a sensor position: interpolated from the distortion field when use_field and it is up to date, computed directly otherwise.

Without distortion this is computed exactly. With distortion, for the renderer, it is interpolated from the directions at every pixel corner (the distortion field), which span the whole sensor, [0, width] x [0, height], when use_field and the field is up to date (see precompute()); otherwise, and beyond the sensor, it is computed exactly, by undistorting.

void check_distortion_(const std::string &caller) const

Throw if the lens distortion has no inverse somewhere on the sensor, at the current focal length and sensor. See set_distortion().

Parameters:

caller – Name for the error message.

void throw_no_inverse_(const std::string &caller, const Pixel &sensor_position) const

Throw for a position where the lens distortion has no inverse.

Parameters:
  • caller – Name for the error message.

  • sensor_position – The position, in sensor coordinates; reported in the camera’s pixel convention.

Vec3<double> tangent_(const Vec3<double> &p0, const Vec3<double> &p1) const
double triangle_solid_angle_(const Vec3<double> &c0, const Vec3<double> &c1, const Vec3<double> &c2) const
void ensure_frustum_() const

Build the view frustum if it is out of date. Thread-safe.

void build_frustum_() const

Find the view frustum from the ray directions around the image’s boundary, computed directly (2 (width + height) of them).

Frustum<TSpectral> frustum_from_bounds_(float widen_tan) const
bool geometry_current_() const

Both tables up to date.

void ensure_geometry_() const

Build the geometry tables if they are out of date: the per-pixel tables and the view frustum. Thread-safe.

Frustum<TSpectral> view_frustum_with_margin_(float pixels) const

The view frustum widened by at least the given number of pixels on every side, to cull sources whose light reaches into the image from just outside it.

Protected Attributes

float focal_length_ = .05f
std::unique_ptr<SensorModel<TSpectral>> sensor_
std::unique_ptr<Aperture<TSpectral>> aperture_
std::unique_ptr<Distortion<TSpectral>> distortion_ = nullptr
std::unique_ptr<PSF<TSpectral>> psf_ = nullptr

A PSF set with set_psf() or set_measured_psf(), which takes the place of the aperture’s diffraction pattern; none if not set.

bool diffraction_enabled_ = true
bool convolve_psf_ = true
std::shared_ptr<const PsfTables<TSpectral>> render_tables_

The tables the renderer draws unresolved sources from, and psf_image() reads: with the aperture’s diffraction pattern, it with the scattered light, and otherwise the scattered light alone; blurred as a star is by the focus, with the reach they were built for. Shared, so that an image can be read from them while they are rebuilt for new optics.

double render_tables_reach_ = 0.0
double render_tables_blur_ = 0.0
bool render_tables_scatter_only_ = false

Whether the tables hold the scattered light alone, as they do for a PSF that was set or none: see tables_scatter_(). The same for object_tables_ and body_tables_.

std::shared_ptr<const GridPsf<TSpectral>> focused_grid_

A PSF set with set_psf() or set_measured_psf(), on its grid (see GridPsf), or without one a point, drawn exactly (see GridPsf::disc()): in focus, and blurred as a star is by the focus, with the blur, which the renderer draws unresolved sources from; and for objects whose blur differs from a star’s, by their blur.

std::shared_ptr<const GridPsf<TSpectral>> grid_psf_
double grid_psf_blur_ = 0.0
BlurCache<GridPsf<TSpectral>> object_grids_
BlurCache<PsfTables<TSpectral>> object_tables_

Tables for unresolved objects whose blur differs from a star’s, with the reach they were built for.

double object_tables_reach_ = 0.0
bool object_tables_scatter_only_ = false
PsfConvolver<TSpectral> body_convolver_

What resolved bodies are convolved with: the whole of the PSF, across the frame, from render_tables_ in focus, or body_tables_ otherwise, with a PSF that was set on its grid in focus (see body_parts_()).

std::shared_ptr<const PsfTables<TSpectral>> body_tables_

The tables for resolved bodies when a star is out of focus: in focus, as render_tables_ are otherwise, since path tracing gives bodies their own defocus. Empty in focus, when the renderer’s serve. With the reach they were built for.

double body_tables_reach_ = 0.0
bool body_tables_scatter_only_ = false
std::uint64_t optics_version_ = 1

Counts optics changes. Starts at 1, so that a built_at of 0 means never built.

std::array<std::uint64_t, static_cast<std::size_t>(OpticsInput::Count)> optics_changed_at_ = {}

The version at which each input last changed.

std::uint64_t render_tables_built_at_ = 0
std::uint64_t body_tables_built_at_ = 0
std::uint64_t object_tables_built_at_ = 0
std::uint64_t grid_source_built_at_ = 0
std::uint64_t object_grids_built_at_ = 0
float optics_focal_length_ = 0.f
Vec2<float> optics_pixel_pitch_ = {0.f, 0.f}
int optics_width_ = 0
int optics_height_ = 0
bool auto_precompute_ = true
bool explicitly_precomputed_ = false
bool warned_object_build_ = false

Whether note_object_build_() has warned since precompute() was last called.

mutable std::mutex optics_mutex_

Held while building optics kernels or applying a convolution spectrum, so that renders sharing this camera on different threads do not race.

FocusReference focus_reference_ = FocusReference::Distance
double focus_setting_ = std::numeric_limits<double>::infinity()
float d_ = std::numeric_limits<float>::infinity()

Resolved focus distance used by ray generation.

float veiling_alpha_ = 0.f
bool veiling_glare_enabled_ = false
float scatter_fraction_ = 0.f

The scattered light (see set_scatter()): its fraction, slope, and shoulder and outer angles in radians (0 for no outer angle).

float scatter_slope_ = 2.5f
bool scatter_enabled_ = false
double scatter_shoulder_angle_ = 0.0
double scatter_outer_angle_ = 0.0
float fx_
float fy_
float cx_

Principal point in sensor coordinates; resolved by compute_intrinsics_().

float cy_
float skew_ = 0.f

Skew in sensor coordinates (pixels): x_pixel = fx * x + skew * y + cx. Resolved by compute_intrinsics_() from shear_, the skew as given divided by fx, which is the cotangent of the angle between the pixel axes and so does not change with focal length.

float shear_ = 0.f
std::optional<float> principal_x_

The principal point as it was given, in pixel_convention_; unset means the center of the sensor, whatever its resolution. Kept as given so that the result does not depend on whether the convention is set before or after it.

std::optional<float> principal_y_
PixelConvention pixel_convention_ = {}
float rx_
float ry_
bool is_explicit_matrix_ = false
ApertureSource aperture_source_ = ApertureSource::Default
float fstop_setting_ = DEFAULT_FSTOP_

The f-stop given to set_fstop(), and whether a focal length change since has been warned about.

bool fstop_change_warned_ = false
bool depth_of_field_ = true
bool motion_blur_ = true
std::uint64_t distortion_version_ = 0
GeometryKey geometry_key_ = {}

The version of the geometry settings, set by compute_intrinsics_(): a new one when they change, or, when they change back to what a table was built with, that table’s. So setting a value to what it already is, or back to what it was, rebuilds nothing.

std::uint64_t geometry_version_ = 0
std::uint64_t next_geometry_version_ = 1
mutable std::mutex geometry_mutex_
mutable std::atomic<std::uint64_t> pixel_geometry_built_at_ = {NEVER_BUILT_}

The per-pixel tables: built together, from one pass over the pixel corners.

mutable GeometryKey pixel_geometry_built_key_ = {}
mutable Image<Vec3<float>> distortion_field_

Unit ray directions at every pixel corner, (width + 1) x (height + 1) of them, so that bilinear lookup covers the whole sensor, [0, width] x [0, height]. Empty without distortion, when directions are computed directly.

mutable Image<float> pixel_geometry_factors_

Each pixel’s power per unit radiance and aperture area: its solid angle times the cosine of the angle between the optical axis and the pixel’s center. See pixel_radiance_to_power().

mutable std::optional<Pixel> distortion_failure_

The first pixel corner (sensor coordinates) where the distortion has no inverse, which makes the camera unusable until a setting changes; empty when there is none. Found by build_pixel_geometry_(), and reported by check_distortion_().

mutable std::atomic<std::uint64_t> frustum_built_at_ = {NEVER_BUILT_}

The view frustum, and the bounds it is made from.

mutable GeometryKey frustum_built_key_ = {}
mutable Frustum<TSpectral> view_frustum_
mutable FrustumBounds frustum_bounds_
bool blender_convention_ = false

Protected Static Functions

static double resolve_focus_diopters_(FocusReference reference, double setting, double focal_length)

Convert a focus setting to a vergence (diopters) for the given focal length.

A distance is inverted (infinity giving 0). A sensor offset is converted with the thin lens equation, with the sensor at f + offset: 1/distance = 1/f - 1/(f + offset).

static void check_principal_point_(std::optional<float> cx, std::optional<float> cy, const Resolution &resolution, const std::string &caller)

Check a principal point before it is stored (unset means the sensor’s center).

Throws if it is not finite, and warns if it is far outside the sensor.

Protected Static Attributes

static constexpr std::size_t OBJECT_TABLES_KEPT_ = 8
static constexpr double MIN_FOCUS_DISTANCE_ = 1e-12

Closest focus distance accepted (meters), in either direction.

static constexpr float DEFAULT_FSTOP_ = 2.8f
static constexpr std::uint64_t NEVER_BUILT_ = std::numeric_limits<std::uint64_t>::max()

Friends

friend class CameraModelHandle< TSpectral >
friend class Renderer< TSpectral >
friend class SceneView< TSpectral >
template<class T>
struct BlurCache

What is built for unresolved objects by their blur, where it differs from a star’s: tables with the diffraction pattern, or a PSF that was set on its grid. Most recently used last. Those the last render used are kept, and others up to OBJECT_TABLES_KEPT_ in all.

Public Functions

template<class Build>
inline std::shared_ptr<const T> find(double blur, std::size_t &used, const Build &build)

The kept one nearest blur, if within PsfTables::BLUR_TOLERANCE of it, or one made with build(); used counts those this render has used, which are kept last.

inline void trim(std::size_t used)

Drops those this render did not use, oldest first, beyond the number kept.

Public Members

std::vector<Entry> entries
struct Entry

Public Members

double blur = 0.0
std::shared_ptr<const T> value
struct GeometryKey

What the tables depend on. A change of distortion model, which is an object, counts as a change of distortion_version_.

Public Functions

bool operator==(const GeometryKey&) const = default

Public Members

float fx = 0.f
float fy = 0.f
float cx = 0.f
float cy = 0.f
float skew = 0.f
float rx = 0.f
float ry = 0.f
bool blender = false
std::uint64_t distortion_version = 0
struct FrustumBounds

Where the view frustum’s side planes are, as the tangent of their angle from the forward axis along the image’s x and y axes, and the largest change in that tangent one pixel makes at the image’s boundary. Found by build_frustum_().

Public Members

Vec3<float> xdir = {1, 0, 0}
Vec3<float> ydir = {0, 1, 0}
Vec3<float> zdir = {0, 0, 1}
float min_tan_x = 0.f
float max_tan_x = 0.f
float min_tan_y = 0.f
float max_tan_y = 0.f
float tan_per_pixel = 0.f