scene op• Data kinds: table × image2d → table
• Call: import fullseye as fs; fs.ledger.surface_defect(primitive: 'dict', field, mask=None, uv_size_mm=(20.0, 20.0), centre_mm=(0.0, 0.0), height_um: 'float' = 0.0, height_field=None, roughness_um: 'float' = 0.6) -> 'dict' (to call the implementation directly, import optscene; optscene.surface_defect(primitive: 'dict', field, mask=None, uv_size_mm=(20.0, 20.0), centre_mm=(0.0, 0.0), height_um: 'float' = 0.0, height_field=None, roughness_um: 'float' = 0.6) -> 'dict'; from the registry, opsoptics.get("surface_defect"))
Paste a 2-D defect map onto a part's surface (it eats the output of defectgen directly).
field is the brightness modulation (H, W) (0 = no change, −0.3 = a scratch 30% darker). If height_um is given, the same map is also interpreted as height and the normals are tilted from its gradient ―― with this, the point of changing the illumination is reproduced: the same scratch vanishes under dome illumination and glows under dark-field illumination (this is the core of training data for visual inspection AI).
Passing a separate height_field creates defects with no colour change, only relief (dents, sink marks, shallow scuffs). Setting field to all zeros gives a purely terrain-type (relief-only) defect, which vanishes under dome illumination and glows under low-angle dark-field illumination ―― this difference is exactly the reason for choosing the illumination.
roughness_um is the amount by which the surface gets rougher only at the defect (default 0.6 µm).
Seen differently, the machining marks of surface_finish hold the same thing at a different granularity ―― machining marks are a representation that compresses the texture of surface relief into noise of a few octaves, while roughness here is a representation that squashes "relief finer than a pixel that cannot be resolved" into 1 number (σ). Resolvable relief is handled as normals, unresolvable relief as roughness: that is the division of labour. A scratch is a trace where material was removed, so it is always rougher than the sound surface. With this, the specular fraction exp(-(4πσcosθ/λ)²) drops and it becomes dark in bright field, while scattering increases and it becomes bright in dark field ―― the textbook contrast reversal comes not from the tilt of the normals but from this roughness (to send low-angle light straight up the surface would need to tilt by 39 degrees, which the slope of a scratch cannot reach; confirmed by measurement on 2026-09-05). Setting it to 0 gives a defect that does not change roughness.
uv_size_mm is the real size [mm] to apply it at, centre_mm the centre in surface coordinates. Passing mask makes its pixels the defect label (the ground truth returned by optscene_defect_mask).
Returns a new primitive with the defect attached (the original is not modified).
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.
• 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
• 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.
• studio_raytrace_scene — py -3.11 examples/studio_raytrace_scene.py
table as input)abcd_matrix · wavefront_stats · sfr_from_edge · paraxial_trace · seidel_coefficients · spot_stats · tolerance_analysis · wavefront_from_opd
scene)scene_material · scene_plane · scene_sphere · scene_box · scene_cylinder · surface_finish · random_defects · scene_difference
*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.