Source code for sofia_redux.scan.source_models.beams.instant_focus

# Licensed under a 3-clause BSD style license - see LICENSE.rst

from abc import ABC
from astropy import units
from copy import deepcopy
import numpy as np

__all__ = ['InstantFocus']


[docs] class InstantFocus(ABC): def __init__(self): """ Initialize an InstantFocus object. The instant focus is designed to store focus measurements for later access. Focus measurements may be derived from the configuration, asymmetry, and elliptical elongation parameters. """ self.x = None self.x_weight = None self.y = None self.y_weight = None self.z = None self.z_weight = None
[docs] def copy(self): """ Return a copy of the focus. Returns ------- InstantFocus """ return deepcopy(self)
[docs] def copy_from(self, focus): """ Copy the focus from another focus object. Parameters ---------- focus : InstantFocus Returns ------- None """ self.x = focus.x self.y = focus.y self.z = focus.z self.x_weight = focus.x_weight self.y_weight = focus.y_weight self.z_weight = focus.z_weight
@property def is_valid(self): """ Return whether the focus is valid. For the focus to be valid, it must contain at least one value in the x, y, or z attribute (not `None`). Returns ------- bool """ if self.x is not None or self.y is not None or self.z is not None: return True return False @property def is_complete(self): """ Return whether all focus parameters are available. Returns ------- bool """ if self.x is None or self.y is None or self.z is None: return False return True def __str__(self): """ Return a string representation of the focus. Returns ------- str """ if not self.is_valid: return "No focus results" info = [] for param in ['x', 'y', 'z']: v, w = getattr(self, param), getattr(self, f'{param}_weight') if v is None: continue unit = None if isinstance(v, units.Quantity): unit = v.unit v = v.value if isinstance(w, units.Quantity): w = w.value if w is None or np.isnan(w) or w == 0: rms = None else: rms = 1 / np.sqrt(w) s = f'{param}={v:.6f}' if rms is not None: s += f'+-{rms:.6f}' if unit is not None: s += f' {unit}' info.append(s) return f'Focus results: {" ".join(info)}' def __repr__(self): """ Return a string representation of the focus instance. Returns ------- str """ return f'{object.__repr__(self)} {self}' def __eq__(self, other): """ Check if this InstantFocus is equal to another. Parameters ---------- other : InstantFocus Returns ------- equal : bool """ if self is other: return True if self.__class__ != other.__class__: return False if self.x != other.x or self.x_weight != other.x_weight: return False if self.y != other.y or self.y_weight != other.y_weight: return False if self.z != other.z or self.z_weight != other.z_weight: return False return True
[docs] def derive_from(self, configuration, asymmetry=None, elongation=None, elongation_weight=None): """ Derive the focus from the asymmetry and elongation measurements. The focus results are derived from the (x, y) asymmetry measurement, and from the elongation in the z-direction. Parameters ---------- configuration : Configuration The SOFSCAN configuration options. asymmetry : Asymmetry2D, optional The source asymmetry. elongation : float, optional The source elongation. elongation_weight : float, optional The source elongation weight. Returns ------- None """ self.x = self.y = self.z = None self.x_weight = self.y_weight = self.z_weight = None mm = units.Unit('mm') s2n = configuration.get_float('focus.significance', default=2.0) if elongation is not None: elongation -= configuration.get_float( 'focus.elong0', default=0.0) * 0.01 for direction in ['x', 'y', 'z']: coeff = configuration.get_float(f'focus.{direction}coeff', default=np.nan) if np.isnan(coeff) or coeff == 0: continue # pragma: no cover if direction == 'z': v = elongation w = elongation_weight elif asymmetry is None: continue else: v = getattr(asymmetry, direction) w = getattr(asymmetry, f'{direction}_weight') if v is None or w is None: continue # pragma: no cover significance = np.abs(v) * np.sqrt(w) if significance <= s2n: continue scale = -mm / coeff v *= scale w /= scale * scale scatter = configuration.get_float(f'focus.{direction}scatter', default=np.nan) * mm if not np.isnan(scatter): variance = (1.0 / w) + (scatter ** 2) w = 1.0 / variance setattr(self, direction, v) setattr(self, f'{direction}_weight', w)