defocus_blur — OPTICS scene op

• Data kinds: rgbimage × image2d × table → rgbimage

• Call: import fullseye as fs; fs.ledger.defocus_blur(image, depth_mm, camera, f_number: 'float' = 5.6, focus_mm: 'float' = None, layers: 'int' = 7) -> 'np.ndarray' (to call the implementation directly, import optscene; optscene.defocus_blur(image, depth_mm, camera, f_number: 'float' = 5.6, focus_mm: 'float' = None, layers: 'int' = 7) -> 'np.ndarray'; from the registry, opsoptics.get("defocus_blur"))

• GPU: none (CPU reference implementation only)

Usage

Apply depth-of-field blur from a ground-truth depth map (ideal pinhole image → the image of a real lens).

Kept separate because the name is easily confused with optics.depth_of_field: that one returns the value of the depth of field, this one applies blur to an image (discovered on 2026-09-05 when the two collided under the same name in the ledger. Ops with the same name are silently overwritten by the last one registered, so they are distinguished by name).

The diameter of the circle of confusion is the closed formula of geometrical optics c = (f² / (N (z_f − f))) · |z − z_f| / z (f = focal length, N = F-number, z_f = focus distance). Converted to pixels, a Gaussian blur is applied per layer grouped by depth, and the layers are composited from front to back.

Compositing is front-to-back over compositing (using premultiplied colour and the blurred coverage α, a nearer layer hides the farther layers by as much as it covers). An in-focus opaque near surface is not contaminated by the blur behind it (★until 2026-11-05 all layers were summed and divided by the blurred weights, and the edge of an in-focus surface dropped 1.0 -> 0.67). The final division by the accumulated α fills the "shadow of the near layer", which the pinhole image does not see, with the surrounding colour.

Depth +inf (infinity) is blurred with the limit z -> ∞ c = f² / (N (z_f − f)) (★until 2026-11-05 it was replaced by the focus distance, and infinity was rendered sharp). NaN and -inf are pixels without depth and are treated as in focus, as before. Layers are split by half-open depth intervals [e_i, e_{i+1}) (only the last layer includes both ends), so a depth on a boundary is not duplicated into 2 layers.

Without this, setups that "are in the field of view but never come into focus on the real machine" are missed ―― the purpose of virtualization is not to make pictures but to know in advance whether inspection works with that optical setup, so the depth of field cannot be omitted.

If focus_mm is omitted, focus is set to the working distance of camera. Returns the same shape as the input.

Family-wide input contract (fail-closed)

Every optics op validates its input before computing (nothing slips through silently):

• Units are baked into the argument name — _mm / _um / _deg / _mrad. Confusing mm with µm does not crash; it yields a plausible-looking wrong answer, so the name prevents it. Nothing here guesses the unit from the magnitude.

• Strings raise ValueError — float('50') succeeds, so an unparsed configuration value would slip through as a length (measured: thin_lens('50', '200') returned a plausible 66.667 mm). bool is refused too, as the implicit promotion True == 1.

• complex / masked arrays raise ValueError (real-valued slots only; silently dropping the imaginary part or peeling off the mask is refused). NaN/Inf raises ValueError on every input.

• Division by zero and its relatives are refused by name: focal length 0, radius of curvature 0, refractive index <= 0, a fully opaque aperture (all zeros, so the normalisation is 0/0), a PSF whose sum is <= 0, a Stokes vector with S0 = 0, and an object sitting at the front focal point (the image is at infinity).

• Only two ops return a non-finite value, and both state it as a contract: depth_of_field returns far_mm = inf beyond the hyperfocal distance (that is what the hyperfocal distance means), and gaussian_beam returns wavefront_radius_mm = inf at the waist (the radius of curvature of a plane wavefront). Both also return a finite companion (far_is_infinite / curvature_per_mm). Any other silent NaN/Inf is detected internally and raises ValueError — "float64 overflowed" and "the answer is infinite" are different claims, so the first is never returned wearing the face of the second.

• Size caps: generated grids are capped by optics.MAX_GRID (4096); supplied fields/PSFs/apertures by optics.MAX_FIELD_ELEMENTS (2^24); ABCD element chains by optics.MAX_SYSTEM_ELEMENTS (1024); Zernike by MAX_ZERNIKE_TERMS (512) / MAX_ZERNIKE_ORDER (40) / MAX_ZERNIKE_BASIS (2^25). This closes, fail-closed, the paths where a small argument triggers a huge internal allocation (measured: n_max=40 × 4096² needs 108 GB).

• Physically impossible states are refused too: a Stokes vector with degree of polarisation > 1, negative transmittance, negative intensity, and invalid Zernike indices such as n-|m| odd.

Detailed usage guide

• optics_imaging family guide

Background guides (the physics and conventions behind this op)

• dataset_conventions — Training-dataset conventions — COCO / YOLO / VOC and pitfalls in visual inspection

• mv_cameras — Industrial camera makers (sensor mapping, line scan / TDI)

• mv_illumination_practice — Lighting in practice — wavelength, polarisation, drive modes, ambient light, safety

• mv_image_sensors — Industrial image sensors (mainly current products)

• virtual_machine_vision — Virtual machine vision — enumerating the parameters and an object model

References (sample data, literature)

• 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.

Runnable examples (verified samples that actually call this op)

• poc_focus_stacking — py -3.11 examples/poc_focus_stacking.py

• virtual_machine_vision — py -3.11 examples/virtual_machine_vision.py

Ops the type connects to (they accept rgbimage as input)

clearcoat_shade · wetness · diffraction_blur · sensor_capture · underwater_image · cubemap_from_equirect · viewport_from_equirect · equirect_rotate

Same category (scene)

scene_material · scene_plane · scene_sphere · scene_box · scene_cylinder · surface_defect · surface_finish · random_defects


*Provenance: optscene.py — OPTICS 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.