Source code for physical_apertures.spider

"""Spider Aperture Module

This module contains the SpiderAperture class, which represents the vanes of a
spider: straight arms of equal width that run outward from a common centre and
block the rays that fall on them.
"""

from __future__ import annotations

import math

import optiland.backend as be
from optiland.physical_apertures.base import BaseAperture


[docs] class SpiderAperture(BaseAperture): """Represents a spider that blocks the rays falling on its arms. The arms are half-lines of equal width from the centre, equally spaced in angle, the first along +x. A ray is blocked when it lies on an arm's side of the centre and within half the width of the arm's centreline; every other ray passes, with no outer limit. Attributes: width (float): The full width of each arm. num_arms (int): The number of arms. offset_x (float): The x-coordinate of the spider's centre. offset_y (float): The y-coordinate of the spider's centre. """ def __init__(self, width, num_arms, offset_x=0, offset_y=0): super().__init__() if int(num_arms) < 1: raise ValueError("A spider needs at least one arm.") self.width = width self.num_arms = int(num_arms) self.offset_x = offset_x self.offset_y = offset_y @property def extent(self): """Returns the extent of the aperture. Returns: tuple: The extent of the aperture in the x and y directions. The arms have no outer limit. """ return -be.inf, be.inf, -be.inf, be.inf
[docs] def contains(self, x, y): """Checks if the given point is inside the aperture. Args: x (np.ndarray): The x-coordinate of the point. y (np.ndarray): The y-coordinate of the point. Returns: np.ndarray: Boolean array indicating if the point is inside the aperture, that is, not on an arm. """ u = x - self.offset_x v = y - self.offset_y half_width = self.width / 2 blocked = be.zeros_like(u) != 0 for k in range(self.num_arms): angle = 2 * math.pi * k / self.num_arms cos, sin = math.cos(angle), math.sin(angle) along = u * cos + v * sin across = -u * sin + v * cos blocked = blocked | ((along >= 0) & (be.abs(across) <= half_width)) return ~blocked
[docs] def scale(self, scale_factor): """Scales the aperture by the given factor. Args: scale_factor (float): The factor by which to scale the aperture. """ self.width = self.width * scale_factor self.offset_x = self.offset_x * scale_factor self.offset_y = self.offset_y * scale_factor
[docs] def to_dict(self): """Convert the aperture to a dictionary. Returns: dict: The dictionary representation of the aperture. """ aperture_dict = super().to_dict() aperture_dict["width"] = self.width aperture_dict["num_arms"] = self.num_arms aperture_dict["offset_x"] = self.offset_x aperture_dict["offset_y"] = self.offset_y return aperture_dict
[docs] @classmethod def from_dict(cls, data): """Create an aperture from a dictionary representation. Args: data (dict): The dictionary representation of the aperture. Returns: SpiderAperture: The aperture object. """ return cls( data["width"], data["num_arms"], data.get("offset_x", 0), data.get("offset_y", 0), )