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)