One camera frame of a part: what each pixel landed on.
Four answers per pixel, all of them exact, because the ray met the surface rather than a depth buffer that then had to be un-projected:
- the point on the part (millimetres, the file's own coordinates);
- the outward normal there — and for a pixel that met nothing, the ray stopped at the far plane and the normal points back at the sensor, which is what "a surface would face me here" means;
- the face that claimed it (
nilfor a pixel that met nothing); - the distance along the ray.
Points and normals are kept as the flat binaries the engine answered; a caller
asks for rows/1 when it wants lists, and pays for the walk then rather than
on every frame.
Summary
Functions
The centre pixel's row: {point, normal, face}.
The share of pixels that met the part, from 0 to 1.
The distance to the nearest and furthest pixel that met the part.
Every pixel's distance along its ray, in millimetres.
Every pixel's face, and nil where the ray met nothing.
The families present in the frame, and how many pixels each covers.
How many pixels met the part.
The frame built from what the engine answered.
Every pixel's outward normal.
The pixel's point, in millimetres.
Every pixel's point, in millimetres.
A shaded image of the frame, [r, g, b, a] per pixel, 0 to 255.
One {[x, y, z], [nx, ny, nz], face} row per pixel, row-major.
The rows a sensor observation carries: [x, y, z, semantic_id] per pixel.
Types
@type t() :: %Quiddity.Frame{ camera: Quiddity.Camera.t(), depth: binary(), faces: binary(), height: pos_integer(), normals: binary(), points: binary(), width: pos_integer() }
Functions
@spec centre(t()) :: {[float()], [float()], non_neg_integer() | nil}
The centre pixel's row: {point, normal, face}.
A patch sensor reads its location from the middle pixel, which is why this is the one pixel with a name.
The share of pixels that met the part, from 0 to 1.
The distance to the nearest and furthest pixel that met the part.
{:none, :none} for a frame that met nothing, which is a different answer from
a frame whose surface is at zero distance.
Every pixel's distance along its ray, in millimetres.
@spec face_ids(t()) :: [non_neg_integer() | nil]
Every pixel's face, and nil where the ray met nothing.
@spec families(t(), Quiddity.Recognition.t()) :: [ {String.t() | :body | :unexplained, pos_integer()} ]
The families present in the frame, and how many pixels each covers.
This is the answer to "what am I looking at": the frame's faces are looked up in the part's recognition, so a patch on a counterbore says so, and a patch on plain stock says nothing at all.
@spec hits(t()) :: non_neg_integer()
How many pixels met the part.
@spec new(map(), Quiddity.Camera.t()) :: t()
The frame built from what the engine answered.
points, normals and faces are the engine's own binaries: 3 * w * h
little-endian f64, 3 * w * h f64, w * h u32 — and depth, w * h f64.
Every pixel's outward normal.
@spec point(t(), non_neg_integer()) :: [float()]
The pixel's point, in millimetres.
Every pixel's point, in millimetres.
@spec rgba(t(), keyword()) :: [[non_neg_integer()]]
A shaded image of the frame, [r, g, b, a] per pixel, 0 to 255.
A B-rep has no colour: quiddity reads geometry, and a STEP file that carries no
appearance is grey. So this is shading — one light from the upper left of the
camera — and it is honest about what it is: a hsv feature read from these
pixels says how the surface turns, not what it is painted.
@spec rows(t()) :: [{[float()], [float()], non_neg_integer() | nil}]
One {[x, y, z], [nx, ny, nz], face} row per pixel, row-major.
The rows a sensor observation carries: [x, y, z, semantic_id] per pixel.
A pixel that met the part carries semantic_id; a pixel that met nothing
carries 0.0, the background id. A sensor model reads that fourth column to
decide which pixels are on the object, and what fraction of the patch it
covers.