Source code for visualization.coloring.properties
"""Component Property Module
This module contains value functions that read a glass property of each lens
element, for use with :class:`PropertyColorizer`.
Kramer Harrison, 2026
"""
from __future__ import annotations
import math
from typing import TYPE_CHECKING
import numpy as np
import optiland.backend as be
from optiland.materials.base import BaseMaterial
if TYPE_CHECKING:
from collections.abc import Callable
from optiland.optic import Optic
from optiland.visualization.coloring.components import ComponentRef
def _element_material(optic: Optic, ref: ComponentRef) -> BaseMaterial | None:
"""Return the glass of a lens element, or None for any other component."""
if ref.kind != "lens_element":
return None
return optic.surfaces[ref.surface_indices[0]].material_post
def _finite_float(value) -> float | None:
"""Convert a backend scalar to a detached Python float, or None if not finite."""
result = float(np.asarray(be.to_numpy(value), dtype=float).item())
return result if math.isfinite(result) else None
[docs]
def abbe_number(optic: Optic, ref: ComponentRef) -> float | None:
"""Abbe number V_d of a lens element's glass.
V_d is always computed from the glass's indices at the Fraunhofer d, F and
C lines, so materials defined by a stored Abbe number in another convention
(e.g. V_e for ``AbbeMaterialE``) are colored on the same scale as catalog
glasses.
Example:
>>> from optiland.visualization.coloring import (
... ColorScale, PropertyColorizer, abbe_number)
>>> optic.draw(component_colorizer=PropertyColorizer(
... abbe_number, ColorScale(cmap="coolwarm_r", label="Abbe number")))
Args:
optic (Optic): The system being drawn.
ref (ComponentRef): The component to evaluate.
Returns:
float | None: V_d, or None for components that are not lens elements
and for materials without dispersion (e.g. ``IdealMaterial``), which
keep the default color.
"""
material = _element_material(optic, ref)
if material is None:
return None
# BaseMaterial.abbe is called explicitly because Abbe-defined materials
# shadow the method with their stored Abbe number. A non-dispersive
# material divides by zero here and is reported as None.
with np.errstate(divide="ignore", invalid="ignore"):
return _finite_float(BaseMaterial.abbe(material))
[docs]
def refractive_index(
wavelength: float | None = None,
) -> Callable[[Optic, ComponentRef], float | None]:
"""Build a value function giving each lens element's refractive index.
Example:
>>> from optiland.visualization.coloring import (
... ColorScale, PropertyColorizer, refractive_index)
>>> optic.draw(component_colorizer=PropertyColorizer(
... refractive_index(0.5876), ColorScale(label="n_d")))
Args:
wavelength (float | None): Wavelength in microns. None means the
optic's primary wavelength at the time of drawing.
Returns:
Callable[[Optic, ComponentRef], float | None]: Value function for
:class:`PropertyColorizer` that returns the index, or None for
components that are not lens elements.
"""
def value_fn(optic: Optic, ref: ComponentRef) -> float | None:
material = _element_material(optic, ref)
if material is None:
return None
wl = optic.primary_wavelength if wavelength is None else wavelength
return _finite_float(material.n(wl))
return value_fn