Source code for openlifu.bf.focal_patterns.wheel

from __future__ import annotations

from dataclasses import dataclass
from typing import Annotated

import numpy as np
import pandas as pd

from openlifu.bf.focal_patterns import FocalPattern
from openlifu.geo import Point
from openlifu.util.annotations import OpenLIFUFieldData


[docs] @dataclass class Wheel(FocalPattern): """ Class for representing a wheel pattern """ center: Annotated[bool, OpenLIFUFieldData("Include center point?", "Whether to include the center for the wheel pattern")] = True """Whether to include the center for the wheel pattern""" num_spokes: Annotated[int, OpenLIFUFieldData("Number of spokes", "Number of spokes in the wheel pattern")] = 4 """Number of spokes in the wheel pattern""" spoke_radius: Annotated[float, OpenLIFUFieldData("Spoke radius", "Radius of the spokes in the wheel pattern")] = 1.0 # mm """Radius of the spokes in the wheel pattern""" distance_units: Annotated[str, OpenLIFUFieldData("Units", "Units of the wheel pattern parameters")] = "mm" """Units of the wheel pattern parameters""" def __post_init__(self): if not isinstance(self.center, bool): raise TypeError(f"Center must be a boolean, got {type(self.center).__name__}.") if not isinstance(self.num_spokes, int) or self.num_spokes < 1: raise ValueError(f"Number of spokes must be a positive integer, got {self.num_spokes}.") if not isinstance(self.spoke_radius, (int, float)) or self.spoke_radius <= 0: raise ValueError(f"Spoke radius must be a positive number, got {self.spoke_radius}.") super().__post_init__()
[docs] def get_targets(self, target: Point): """ Get the targets of the focal pattern :param target: Target point of the focal pattern :returns: List of target points """ if self.center: targets = [target.copy()] targets[0].id = f"{target.id}_center" targets[0].id = f"{target.id} (Center)" else: targets = [] m = target.get_matrix(center_on_point=True) for i in range(self.num_spokes): theta = 2*np.pi*i/self.num_spokes local_position = self.spoke_radius * np.array([np.cos(theta), np.sin(theta), 0.0]) position = np.dot(m, np.append(local_position, 1.0))[:3] spoke = Point(id=f"{target.id}_{np.rad2deg(theta):.0f}deg", name=f"{target.name} ({np.rad2deg(theta):.0f}°)", position=position, units=self.distance_units, radius=target.radius) targets.append(spoke) return targets
[docs] def num_foci(self) -> int: """ Get the number of foci in the focal pattern :returns: Number of foci """ return int(self.center) + self.num_spokes
[docs] def to_table(self) -> pd.DataFrame: """ Get a table of the focal pattern parameters :returns: Pandas DataFrame of the focal pattern parameters """ records = [ {"Name": "Type", "Value": "Wheel", "Unit": ""}, {"Name": "Target Pressure", "Value": self.target_pressure, "Unit": self.units}, {"Name": "Center", "Value": self.center, "Unit": ""}, {"Name": "Number of Spokes", "Value": self.num_spokes, "Unit": ""}, {"Name": "Spoke Radius", "Value": self.spoke_radius, "Unit": self.distance_units}, ] return pd.DataFrame.from_records(records)