kendamaworld op• Data kinds: table → any
• Call: import fullseye as fs; fs.ledger.camera_perceiver(world: 'dict', rig: 'list', *, fps: 'float' = 100.0, pixel_noise: 'float' = 0.0, rng=None, slack_margin: 'float' = 0.005, min_frames: 'int' = 3, window: 'int' = 128, color_tol: 'float' = 0.12, g: 'float' = 9.81, keep_frames: 'bool' = False, slack_frames: 'int' = 2, min_fill: 'float' = 0.6, reject: 'float' = 0.004, render_fn=None, holes: 'bool' = True, flight_from: 'str' = 'tie', flight_margin: 'float' = 0.012) (to call the implementation directly, import kendamaworld; kendamaworld.camera_perceiver(world: 'dict', rig: 'list', *, fps: 'float' = 100.0, pixel_noise: 'float' = 0.0, rng=None, slack_margin: 'float' = 0.005, min_frames: 'int' = 3, window: 'int' = 128, color_tol: 'float' = 0.12, g: 'float' = 9.81, keep_frames: 'bool' = False, slack_frames: 'int' = 2, min_fill: 'float' = 0.6, reject: 'float' = 0.004, render_fn=None, holes: 'bool' = True, flight_from: 'str' = 'tie', flight_margin: 'float' = 0.012); from the registry, opsdrive.get("camera_perceiver"))
Perception that derives the ball's (p̂, v̂) from images alone: perceive(t, p, v, scene) → (p̂, v̂) | None (for kendama.kendama_simulate; with attribute observe_all = True it is called every step). The ground truth (p, v) is used only to render the world, and v is never read.
For each frame time k / fps: (1) place the world (ken = scene["hand"], ball = p, string) with kendama_pose and render with each camera (only a window of window pixels around the ball is rendered: the window is set from the previous frame's detection (or the parabola's prediction if one has been fitted); if there is no detection or it touches the window edge, the full frame is re-rendered —— the window pixels are identical to a crop of the full frame), (2) detect the ball with balltrack.ball_detect (chroma, orange) and add Gaussian noise of σ = pixel_noise to the pixels, (3) if seen by 2 or more cameras, recover 3-D with balltrack.triangulate_dlt,
(4) when "slack" = frames in which the distance between the triangulated ball and the string hole of the cup body (scene["tie"]: known from the hand's proprioception) is shorter than the string's effective length by at least slack_margin
continue for slack_frames frames (with 1 frame, 2 px of pixel noise gave false detections while taut —— measured and rejected), fit a parabola with known gravity g (same formula as kendama.parabola_fit_g, 6 unknowns) by least squares to the points from that first frame onwards (at least min_frames points). Return value = (p̂, v̂) of the fitted parabola extrapolated to t. None if there is no parabola yet (the plan does not move).
Pose of the ball in the world (rendering convention, ground truth): while scene["taut"] the string hole points at the cup body's string hole; once slack it keeps its last pose (recorded in R_ball). Records (attributes): frames = [{"t", "p_hat" (3,) or NaN, "uv" [(col,row)…], "full" [bool…], "tie", "hole_uv", "hole_dir"}], R_ball = true pose of the ball in each frame (true hole direction = R_ball @ HOLE_AXIS_LOCAL: ground truth for the gate), slack_frame (index of the frame where slack was detected or None), fits = [(t_frame, n_used, p_ref, v_ref, t_ref)], render_s (total seconds of rendering + detection), n_render (number of windows rendered), images (full frames from each camera when keep_frames=True …… heavy, so only for figures). render_fn(world, cam) → (H, W, 3) float: hook to replace the renderer (default = mesh rendering of driveworld.world_camera; 3DGS etc.). When rendering only a window, it is passed a dict with cam's K (principal point shifted), width and height adjusted to the window. holes=True: searches for the hole with kendama.hole_detect inside the ball window (on pixels before noise is added); for frames seen by 2 cameras it triangulates the hole centroid and records the direction from the ball centre hole_dir (unit vector in the world) (in cup tricks this is only reported, not used for planning). flight_from="cup" (iteration 19, consecutive tricks): "start of flight" is decided not by string slack but by the distance between the triangulated ball and the position of the ball resting on the cup (scene["rest"] = centre of the rim of the receiving cup + h_c·axis: known from proprioception at the hand) exceeding flight_margin for slack_frames consecutive frames (frames while it rests on the cup are not included in the fit). If scene["R_ken"] is present, the ken is rendered in that pose (regrip). perceive.reset() discards the current parabola and waits for the next start of flight (record flights = sequence of indices of flight-start frames). The default "tie" behaves as before.
fail-closed: fps ≤ 0, pixel_noise < 0, min_frames < 2, window < 32, fewer than 2 cameras, or a world without a kendama raises ValueError.
• 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.
• The canonical algorithm (author, year) and its uses are named in the family usage guide above.
• poc_kendama — py -3.11 examples/poc_kendama.py
any as input)course_crank · course_s_curve · course_turnaround · course_slope · course_intersection · course_parallel_parking · course_crossing · course_road
kendamaworld)ken_mesh · add_ken · ken_set_pose · string_mesh · add_string · string_set · kendama_world · kendama_rig
*Provenance: kendamaworld.py — DRIVE 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.