range_image op• Data kinds: depth → normalmap
• Call: import fullseye as fs; fs.ledger.normals_from_depth(depth, fx=None, fy=None, cx=None, cy=None, orient_to_camera=True, spacing=None) (to call the implementation directly, import range_image; range_image.normals_from_depth(depth, fx=None, fy=None, cx=None, cy=None, orient_to_camera=True, spacing=None); from the registry, ops3d.get("normals_from_depth"))
Organized depth → oriented unit normals (H,W,3). The cross product of the 3-D points of neighbouring pixels (using the grid structure, O(HW)).
Perspective with fx,fy given, orthographic otherwise. orient_to_camera=True unifies the normal signs to face the camera (origin).
★A different function with the same name exists in the 1-line facade. fullseye.normals_from_depth is (depth, K, smooth=0), the perspective version where K is required; this one (via fullseye.ledger = range_image) is the orthographic version that takes no K. If you learn how to call one, calling the other one the same way raises TypeError (measured 2026-09-08).
★spacing=(dy, dx) is the real size of a pixel in orthographic mode. The default None means "1 pixel = 1 unit", and passing an anisotropic grid (e.g. columns 0.05 mm × rows 0.5 mm) as is silently shifts the slopes —— measured: the x component was off by 20 times (= 1/dx times), with neither an exception nor a warning. If you know the real size, always pass it. In perspective mode (fx/fy given) the focal length determines the real size, so it is ignored. ★When passing spacing, set orient_to_camera=False. In orthographic mode the "camera" is a makeshift placed at the origin (top-left, depth 0); entering real sizes changes its position relative to the surface, and the sign may flip (measured: on a plane with slope 0.1, orient_to_camera=False matches the ground truth x=-0.0995, while True gives +0.0995). If you need the orientation, align it in a later stage in a coordinate system where the line of sight is defined.
Normals come from cross products of neighbouring pixels, so both axes need neighbours. With H<2 or W<2 there is no 2nd tangent direction and the normal cannot be defined (treating that axis's gradient as 0 would silently return the degenerate normal cross(dPx,0)=[0,0,0]). It is explicitly rejected with ValueError (fail-closed).
The computation builds 3-D points P with depth_to_organized_points and normalises to unit length the cross product dPx × dPy of the column-direction difference dPx and the row-direction difference dPy from np.gradient (one-sided at the edges). With orient_to_camera=True, pixels with n·(-P) < 0 are flipped so that the normals face the origin (camera). In orthographic mode (fx or fy is None) P=(u,v,d), so the origin lies at depth 0 at the top-left of the image, and note that "facing the camera" means something different from perspective mode. If cx and cy are omitted, the image centre is used. At pixels straddling a depth step (occlusion edge) the cross product picks up the direction of the step and the normal breaks, so remove boundary pixels with occlusion_edges first. Pixels with depth 0 / NaN are not validated, and their normals are undefined. Returns float32 (H,W,3).
• blender_interop — Using Blender alongside — build the shape, measure it with fullseye (pitfalls: axes, units, ground truth)
• depth_sensors — Depth sensors — ranging principles, real-device figures, how dropouts appear
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• range_image — py -3.11 examples_3d/range_image.py
normalmap as input)fuse_to_voxel · render_shaded · phong_shade · matcap_shade · brdf_lommel_seeliger · brdf_hapke · bump_normals_fbm · integrate_normals
range_image)depth_to_organized_points · occlusion_edges · bearing_angle_image
*Provenance: range_image.py — 3D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.