Source code for pipeline.domain.source

import itertools
import logging
import pprint

import numpy

from pipeline.infrastructure import casa_tools

LOG = logging.getLogger(__name__)


_pprinter = pprint.PrettyPrinter()


[docs] class Source: """A logical representation of an astronomical source. Attributes: id: The numerical identifier of the source. name: The name of the source. fields: List of Field objects for field(s) associated with the source. is_eph_obj: Boolean declaring whether this is a moving source (with entry in the ephemeris table). """ def __init__(self, source_id: int | numpy.integer, name: str, direction: dict, proper_motion: dict[str, dict], is_eph_obj: bool, table_name: str, avg_spacing: float | str) -> None: """Initialize a Source object. Args: source_id: The numerical identifier of the source. name: The name of the source. direction: The direction to the source as a CASA 'direction' measure dictionary. proper_motion: The proper motion of the source; provided as a 2-element dictionary with keys "longitude" and "latitude", containing the respective components of the proper motion as CASA quantities (dictionaries). is_eph_obj: Boolean declaring whether this is a moving source (with entry in the ephemeris table). table_name: Base file name of ephemeris table (without extension). avg_spacing: Average time spacing (in minutes) between entries in the ephemeris table. """ self.id = source_id self.name = name self.is_eph_obj = is_eph_obj # Fields associated with this source. Initialized as an empty list, and # expected to be populated in a separate step during the import of a # measurement set (see MeasurementSetReader.link_fields_to_sources). self.fields = [] self._avg_spacing = avg_spacing self._direction = direction self._ephemeris_table = table_name self._proper_motion = proper_motion def __repr__(self) -> str: # use pretty printer so we have consistent ordering of dicts return '{0}({1}, {2!r}, {3}, {4})'.format( self.__class__.__name__, self.id, self.name, _pprinter.pformat(self._direction), _pprinter.pformat(self._proper_motion) ) @property def dec(self) -> str: """Return declination of the source.""" return casa_tools.quanta.formxxx(self.latitude, format='dms', prec=2) @property def direction(self) -> dict: """Return source direction as a CASA 'direction' measure dictionary.""" return self._direction @property def frame(self) -> str: """Return reference frame code for the direction to the source.""" return self._direction['refer'] @property def intents(self) -> set[str]: """Return unique scan intents associated with the source.""" return set(itertools.chain(*[f.intents for f in self.fields])) @property def latitude(self) -> dict: """Return latitude of the source.""" return self._direction['m1'] @property def longitude(self) -> dict: """Return longitude of the source.""" return self._direction['m0'] @property def pm_x(self) -> str: """Return string representation of longitudinal component of proper motion of the source.""" return self.__format_pm(axis='longitude') @property def pm_y(self) -> str: """Return string representation of latitudinal component of proper motion of the source.""" return self.__format_pm(axis='latitude') @property def proper_motion(self) -> str: """Return string representation of proper motion of the source.""" qa = casa_tools.quanta return '%.3e %.3e %s' % (qa.getvalue(self.pm_x), qa.getvalue(self.pm_y), qa.getunit(self.pm_x)) @property def ra(self) -> str: """Return right ascension of the source.""" return casa_tools.quanta.formxxx(self.longitude, format='hms', prec=3) # Galactic Longitude: it is usually expressed in DMS format @property def gl(self) -> str: """Return longitude for the source, in DMS format.""" return casa_tools.quanta.formxxx(self.longitude, format='dms', prec=2) # Galactic Latitude @property def gb(self) -> str: """Return declination of the source.""" return self.dec @property def ephemeris_table(self) -> str: """ Returns the name of the ephemeris table associated with this Source or "" if this is not an ephemeris source. """ return self._ephemeris_table @property def avg_spacing(self) -> float | str: """ Returns the average time spacing (in minutes) between table entries in the ephemeris table or "" if this is not an ephemeris source. """ return self._avg_spacing def __format_pm(self, axis: str) -> str: """Return proper motion along given axis. Args: axis: Axis to return proper motion for; can be either 'longitude' or 'latitude'. Returns: String representation of proper motion along given axis. """ qa = casa_tools.quanta val = qa.getvalue(self._proper_motion[axis]) # Handle array-to-scalar conversion for NumPy 1.25+ compatibility # Ensure val is at least a 1-D array, then extract first element val_arr = numpy.array(val, ndmin=1) if val_arr.size > 1: LOG.warning('Proper motion array for axis %s has %d elements; using first element only', axis, val_arr.size) val = val_arr[0] units = qa.getunit(self._proper_motion[axis]) return '' if val == 0 else '%.3e %s' % (val, units) def __str__(self) -> str: return ('Source({0}:{1}, pos={2} {3} ({4}), pm={5})' ''.format(self.id, self.name, self.ra, self.dec, self.frame, self.proper_motion))