scene_material — OPTICS scene op

• Data kinds: none → table (an op determined by its arguments alone — it takes no image or data input)

• Call: import fullseye as fs; fs.ledger.scene_material(kind: 'str' = 'lambert', albedo=0.6, metal: 'str' = 'al', finish: 'str' = 'random', glass: 'str' = 'N-BK7', sigma_per_mm: 'float' = 0.0, roughness_um: 'float' = 0.05, metal_source: 'str' = 'materials', reflectance_curve=None, absorption_from_k: 'bool' = False) -> 'dict' (to call the implementation directly, import optscene; optscene.scene_material(kind: 'str' = 'lambert', albedo=0.6, metal: 'str' = 'al', finish: 'str' = 'random', glass: 'str' = 'N-BK7', sigma_per_mm: 'float' = 0.0, roughness_um: 'float' = 0.05, metal_source: 'str' = 'materials', reflectance_curve=None, absorption_from_k: 'bool' = False) -> 'dict'; from the registry, opsoptics.get("scene_material"))

Usage

Make one material. kind is one of three: lambert / conductor / dielectric.

There are only 3 kinds because mixing them goes silently wrong; to add more, split the types (diffuse, metal or transparent fundamentally changes where rays go).

lambert: perfectly diffuse, using only albedo (scalar or 3 RGB elements).

conductor: the colour is set by the complex refractive index of metal (glassmirror.METALS),

and the anisotropic lobe width by finish (metalfinish.FINISHES). No colour is specified.

roughness_um is the surface roughness Rq [µm]. The fraction returned as specular is set by

exp(-(4πσcosθ/λ)²) (Davies 1954 / Bennett-Porteus 1961), so

this 1 value decides specular (0.01 = polished) versus diffuse-leaning (1.0 = satin finish).

The wavelength enters the formula, so the same surface looks different depending on the light source's wavelength.

metal_source is where n,k come from: "materials" (default, the measured tables of the shared material table;

the 10 metals of glassmirror.MEASURED_METALS — Ni / Pt / Ti / W / Rh can be chosen here too) or

"table" (the coarse 5-point table). The observed object reads the same table as the lens glasses. ★ Aligned on 2026-10-15 with

the default of glassmirror becoming the measured table (until then the default was the coarse table, and the 2 defaults disagreed).

dielectric: refracts with the Sellmeier dispersion of glass (raytrace.glass_catalog()),

and absorbs with Beer–Lambert using sigma_per_mm.

Glass names reach the transparent materials of the shared material table (other makers' glasses, crystals, resins, water).

Materials that are not lens media (semiconductors such as Si and Ge) can also be read with the n of materials. With absorption_from_k=True,

α = 4πk/λ from the extinction coefficient k of the material table is added per wavelength (the reason an Si wafer is opaque at 850 nm and transparent at 1100 nm).

On the spectral path (render_optscene(spectral=True) / layout_capture(mode="spectral")), lambert reads reflectance_curve (pairs(λ [nm], R), no extrapolation); if absent, the mean of the RGB albedo is taken as independent of wavelength (kept in the result as an assumption). Metals are solved with Fresnel from the n,k of the material table (Johnson & Christy and others), dielectrics with n(λ). The RGB path (default) is unchanged.

Returns: a dict storing the above, validated (pass it as is to the other scene_* ops).

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_material_optical_library — py -3.11 examples/poc_material_optical_library.py

• poc_nir_silicon_transmission — py -3.11 examples/poc_nir_silicon_transmission.py

• poc_pocket_interreflection — py -3.11 examples/poc_pocket_interreflection.py

• studio_raytrace_scene — py -3.11 examples/studio_raytrace_scene.py

• virtual_machine_vision — py -3.11 examples/virtual_machine_vision.py

• vision_layout_from_catalog — py -3.11 examples/vision_layout_from_catalog.py

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

abcd_matrix · wavefront_stats · sfr_from_edge · paraxial_trace · seidel_coefficients · spot_stats · tolerance_analysis · wavefront_from_opd

Same category (scene)

scene_plane · scene_sphere · scene_box · scene_cylinder · surface_defect · 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.