From ceae9c7e6931b878b90fd4a5d3e70ef4f71b8bf9 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Wed, 10 Jul 2024 13:34:20 -0600 Subject: [PATCH 01/18] Start off with a few easy ones. --- lsl/catalog.py | 29 ++++----- lsl/correlator/filterbank.py | 18 +++--- lsl/skymap.py | 43 +++++++------ lsl/transform.py | 118 ++++++++++++++++++----------------- 4 files changed, 108 insertions(+), 100 deletions(-) diff --git a/lsl/catalog.py b/lsl/catalog.py index 9c49aefe..c5fc12f4 100644 --- a/lsl/catalog.py +++ b/lsl/catalog.py @@ -19,6 +19,7 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Dict, List, Union __version__ = '0.2' __all__ = ['CatalogEntry', 'Catalog', 'LWA_Catalog', 'PSR_Catalog', 'PKS_Catalog', @@ -43,14 +44,14 @@ class CatalogEntry(object): # limit the class attributes __slots__ = ('name', 'position', 'alias_list') - def __init__(self, name, position): + def __init__(self, name: str, position: transform.CelestialPosition): """ Create a catalog entry. """ self.name = name self.position = position - self.alias_list = [] + self.alias_list: List[str] = [] def __repr__(self): """ @@ -74,15 +75,15 @@ class Catalog(Mapping): __metaclass__ = abc.ABCMeta - def __init__(self, name): + def __init__(self, name: str): """ Create a source catalog. """ # initialize catalog data structures self.name = name - self.source_map = {} - self.alias_map = {} + self.source_map: Dict[str,CatalogEntry] = {} + self.alias_map: Dict[str,CatalogEntry] = {} # parse_file() is an abstract method which must be defined in # a concrete implementation for a particular catalog @@ -95,10 +96,10 @@ def parse_file(self): structures. """ - pass + raise NotImplementedError @staticmethod - def get_directory(): + def get_directory() -> str: """ Returns the path to the catalog data file directory. """ @@ -119,7 +120,7 @@ def __len__(self): return len(self.source_map) - def __getitem__(self, key): + def __getitem__(self, key: str) -> CatalogEntry: """ Access source by subscript name. Raises KeyError if the source is not in the catalog. @@ -137,7 +138,7 @@ def __iter__(self): return iter(self.source_map.keys()) - def lookup(self, name): + def lookup(self, name: str) -> Union[CatalogEntry,None]: """ Lookup a source in the catalog. @@ -592,7 +593,7 @@ class Fermi_LAT_Catalog(Catalog): Base definition for the Fermi LAT point source catalogs. """ - def __init__(self, name, filename): + def __init__(self, name: str, filename: str): """ Create a Fermi LAT catalog instance. """ @@ -694,10 +695,10 @@ class CatalogFactory(object): } # a mapping of catalog names to instances - catalog_instance_map = {} + catalog_instance_map: Dict[str,Catalog] = {} @classmethod - def get_catalog(klass, name): + def get_catalog(klass, name: str) -> Catalog: """ Returns a Catalog object representing the catalog given by name. @@ -714,13 +715,13 @@ def get_catalog(klass, name): catalog = klass.catalog_instance_map[name] except KeyError: catalogClass = klass.catalog_class_map[name] - catalog = catalogClass() + catalog = catalogClass() # type: ignore klass.catalog_instance_map[name] = catalog return catalog @classmethod - def get_names(klass): + def get_names(klass) -> List[str]: """ Return a list of known catalog names. """ diff --git a/lsl/correlator/filterbank.py b/lsl/correlator/filterbank.py index aa05584e..e07aa189 100644 --- a/lsl/correlator/filterbank.py +++ b/lsl/correlator/filterbank.py @@ -11,11 +11,13 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Callable + __version__ = '0.3' __all__ = ['fft', 'fft2', 'fft4', 'fft8', 'fft16', 'fft32'] -def __filterCoeff(N, P): +def __filterCoeff(N: int, P: int) -> np.ndarray: """ Private function to generate the filter bank coefficients for N channels using P taps. @@ -25,7 +27,7 @@ def __filterCoeff(N, P): return np.sinc((t - N*P/2.0 + 0.5)/N) -def fft(signal, N, P=1, window=null_window): +def fft(signal: np.ndarray, N: int, P: int=1, window: Callable[int]=null_window) -> np.ndarray: """ FFT-based poly-phase filter bank for creating N channels with P taps. Optionally, a window function can be specified using the @@ -38,41 +40,41 @@ def fft(signal, N, P=1, window=null_window): for i in range(0, P): fbTemp = np.fft.fft(filteredSignal[i*N:(i+1)*N]) try: - fbOutput += fbTemp + fbOutput += fbTemp # type: ignore except NameError: fbOutput = fbTemp*1.0 return fbOutput -def fft2(signal, N, window=null_window): +def fft2(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses two taps. """ return fft(signal, N, P=2, window=window) -def fft4(signal, N, window=null_window): +def fft4(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses four taps. """ return fft(signal, N, P=4, window=window) -def fft8(signal, N, window=null_window): +def fft8(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses eight taps. """ return fft(signal, N, P=8, window=window) -def fft16(signal, N, window=null_window): +def fft16(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses 16 taps. """ return fft(signal, N, P=16, window=window) -def fft32(signal, N, window=null_window): +def fft32(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses 32 taps. """ diff --git a/lsl/skymap.py b/lsl/skymap.py index 91acb15a..1b92e005 100644 --- a/lsl/skymap.py +++ b/lsl/skymap.py @@ -26,6 +26,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Optional, Tuple + __version__ = '0.5' __all__ = ['SkyMapBase', 'SkyMapBase', 'SkyMapGSM', 'SkyMapLFSM', 'ProjectedSkyMap'] @@ -51,16 +53,15 @@ class SkyMapBase(object): # Class data degToRad = (np.pi/180.) # Usual conversion factor - def __init__(self, filename=None, freq_MHz=73.9): + def __init__(self, filename: Optional[str]=None, freq_MHz: float=73.9): self.filename = filename self.freq_MHz = freq_MHz - self._power = None # Load in the coordinates and data - self._load() + self.ra, self.dec, self._power = self._load() @abc.abstractmethod - def _load(self): + def _load(self) -> Tuple[np.ndarray,np.ndarray,np.ndarray]: """ Load in the specified filename and populate the ra, dec, and _power attributes. This will be over-ridden in the skymap-specific subclasses. @@ -68,7 +69,7 @@ def _load(self): raise NotImplementedError - def normalize_power(self): + def normalize_power(self) -> np.ndarray: """ Compute the skymap power (total power radiated into 4 pi steradians) into a power at antenna, based on pixel count. @@ -76,7 +77,7 @@ def normalize_power(self): return self._power - def compute_total_power(self): + def compute_total_power(self) -> float: """ Compute and return the the total power from the sky. """ @@ -103,7 +104,7 @@ class SkyMapGSM(SkyMapBase): _input = os.path.join('skymap', 'gsm-408locked.npz') - def __init__(self, filename=None, freq_MHz=73.9): + def __init__(self, filename: Optional[str]=None, freq_MHz: float=73.9): """ Initialize the SkyMapGSM object with an optional full file path to the skymap file. @@ -113,15 +114,15 @@ def __init__(self, filename=None, freq_MHz=73.9): filename = self._input SkyMapBase.__init__(self, filename=filename, freq_MHz=freq_MHz) - def _load(self): + def _load(self) -> Tuple[np.ndarray,np.ndarray,np.ndarray]: # Since we are using a pre-computed GSM which is a NPZ file, read it # in with numpy.load. with DataAccess.open(self.filename, 'rb') as fh: dataDict = np.load(fh) # RA and dec. are stored in the dictionary as radians - self.ra = dataDict['ra'].ravel() / self.degToRad - self.dec = dataDict['dec'].ravel() / self.degToRad + ra = dataDict['ra'].ravel() / self.degToRad + dec = dataDict['dec'].ravel() / self.degToRad # Compute the temperature for the current frequency ## Load in the data @@ -143,7 +144,8 @@ def _load(self): output += compFunc(np.log(self.freq_MHz))*maps[:,i] output *= np.exp(scaleFunc(np.log(self.freq_MHz))) ## Save - self._power = output + power = output + return ra, dec, power class SkyMapLFSM(SkyMapGSM): @@ -158,7 +160,7 @@ class SkyMapLFSM(SkyMapGSM): _input = os.path.join('skymap', 'lfsm-5.1deg.npz') - def __init__(self, filename=None, freq_MHz=73.9): + def __init__(self, filename: Optional[str]=None, freq_MHz: float=73.9): """ Initialize the SkyMapLFSM object with an optional full file path to the skymap file. @@ -168,15 +170,15 @@ def __init__(self, filename=None, freq_MHz=73.9): filename = self._input SkyMapBase.__init__(self, filename=filename, freq_MHz=freq_MHz) - def _load(self): + def _load(self) -> Tuple[np.ndarray,np.ndarray,np.ndarray]: # Since we are using a pre-computed GSM which is a NPZ file, read it # in with numpy.load. with DataAccess.open(self.filename, 'rb') as fh: dataDict = np.load(fh) # RA and dec. are stored in the dictionary as radians - self.ra = dataDict['ra'].ravel() / self.degToRad - self.dec = dataDict['dec'].ravel() / self.degToRad + ra = dataDict['ra'].ravel() / self.degToRad + dec = dataDict['dec'].ravel() / self.degToRad # Compute the temperature for the current frequency ## Load in the data @@ -199,7 +201,8 @@ def _load(self): output += compFunc(np.log(self.freq_MHz))*maps[:,i] output *= np.exp(scaleFunc(np.log(self.freq_MHz))) ## Save - self._power = output + power = output + return ra, dec, power class ProjectedSkyMap(object): @@ -214,7 +217,7 @@ class ProjectedSkyMap(object): the sky. """ - def __init__(self, skymap_object, lat, lon, utc_jd): + def __init__(self, skymap_object: SkyMapBase, lat: float, lon: float, utc_jd: float): """ Initialize the skymap at input lat,lon (decimal degrees) and time (in UTC julian day). @@ -245,7 +248,7 @@ def __init__(self, skymap_object, lat, lon, utc_jd): self.visibleAz = np.compress(visibleMask, self.az) self.visiblePower = np.compress(visibleMask, self.skymap_object._power) try: - pixelSolidAngle = (2.0 * np.pi)**2/(self.skymap_object.numPixelsX*self.skymap_object.numPixelsY) + pixelSolidAngle = (2.0 * np.pi)**2/(self.skymap_object.numPixelsX*self.skymap_object.numPixelsY) # type: ignore except AttributeError: pixelSolidAngle = 2.5566346e-4 fractionSolidAngle = (1.0/4.0*np.pi) * pixelSolidAngle @@ -258,7 +261,7 @@ def __init__(self, skymap_object, lat, lon, utc_jd): self.visibleRa = np.compress(visibleMask, self.skymap_object.ra) self.visibleDec = np.compress(visibleMask, self.skymap_object.dec) - def get_direction_cosines(self): + def get_direction_cosines(self) -> Tuple[np.ndarray,np.ndarray,np.ndarray]: """ Compute the direction cosines and return the tuple of arrays (l,m,n). """ @@ -270,7 +273,7 @@ def get_direction_cosines(self): n = np.sin(altRad) return (l, m, n) - def compute_visible_power(self): + def compute_visible_power(self) -> float: """ Compute and return the the total power from visible portion of the sky. """ diff --git a/lsl/transform.py b/lsl/transform.py index 2a1514a0..ebdca7e6 100644 --- a/lsl/transform.py +++ b/lsl/transform.py @@ -4,9 +4,9 @@ from collections.abc import Sequence as SequenceABC import copy -import datetime import math import abc +from datetime import datetime from functools import total_ordering from astropy.time import Time as AstroTime @@ -17,6 +17,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Any, Tuple, Union + __version__ = '0.4' __all__ = ['Time', 'SkyPosition', 'CelestialPosition', 'PlanetaryPosition', @@ -77,7 +79,7 @@ def from_system(klass): return klass(astro.get_julian_from_sys(), klass.FORMAT_JD, klass.TIMESYS_UTC) - def __init__(self, value, format = FORMAT_MJD, timesys = TIMESYS_UTC): + def __init__(self, value: Any, format: str=FORMAT_MJD, timesys: str=TIMESYS_UTC): """ Create a Time instance, using 'value' as the initial time. @@ -193,7 +195,7 @@ def __str__(self): return self.utc_str @property - def utc_jd(self): + def utc_jd(self) -> float: """ Time value formatted as UTC standard julian day (float). """ @@ -201,7 +203,7 @@ def utc_jd(self): return self._time.utc.jd @utc_jd.setter - def utc_jd(self, value): + def utc_jd(self, value: Union[int,float]): if not isinstance(value, (int, float)): raise TypeError("value must be type int or float") @@ -209,7 +211,7 @@ def utc_jd(self, value): self._time = AstroTime(value, format='jd', scale='utc') @property - def utc_mjd(self): + def utc_mjd(self) -> float: """ Time value formatted as UTC modified julian day (float). """ @@ -217,32 +219,32 @@ def utc_mjd(self): return self._time.utc.mjd @utc_mjd.setter - def utc_mjd(self, value): + def utc_mjd(self, value: Union[int,float]): if not isinstance(value, (int, float)): raise TypeError("value must be type int or float") self._time = AstroTime(value, format='mjd', scale='utc') @property - def utc_dp(self): + def utc_dp(self) -> int: return int(self._time.utc.unix * fS) @utc_dp.setter - def utc_dp(self, value): + def utc_dp(self, value: int): if not isinstance(value, int): raise TypeError("value must be type int") self._time = AstroTime(value // fS, value % fS, format='unix', scale='utc') @property - def utc_mcs(self): + def utc_mcs(self) -> Tuple[int,int]: mjd = int(self.utc_mjd) mpm = int(round((self.utc_mjd - mjd) * 86400 * 1000)) return (mjd, mpm) @utc_mcs.setter - def utc_mcs(self, value): - if isinstance(value, (tuple, list)): + def utc_mcs(self, value: SequenceABC): + if isinstance(value, SequenceABC): if len(value) != 2: raise ValueError("value must be a two-element tuple or list") if not isinstance(value[0], (int, float)): @@ -255,7 +257,7 @@ def utc_mcs(self, value): self._time = AstroTime(float(mjd) + float(mpm)/86400/1000, format='mjd', scale='utc') @property - def utc_py_date(self): + def utc_py_date(self) -> datetime: """ Time value formatted as UTC calendar datetime.datetime object. """ @@ -263,19 +265,19 @@ def utc_py_date(self): return self._time.utc.datetime lnDate = astro.get_date(self._time) (usec, sec) = math.modf(lnDate.seconds) - pyDate = datetime.datetime(lnDate.years, lnDate.months, lnDate.days, + pyDate = datetime(lnDate.years, lnDate.months, lnDate.days, lnDate.hours, lnDate.minutes, int(sec), int(usec * 1e6)) return pyDate @utc_py_date.setter - def utc_py_date(self, value): - if not isinstance(value, datetime.datetime): + def utc_py_date(self, value: datetime): + if not isinstance(value, datetime): raise ValueError("value must be type datetime.datetime") self._time = AstroTime(value, format='datetime', scale='utc') @property - def utc_str(self): + def utc_str(self) -> str: """ Time value formatted as UTC ISO 8601 calendar string. """ @@ -283,14 +285,14 @@ def utc_str(self): return self._time.utc.iso @utc_str.setter - def utc_str(self, value): + def utc_str(self, value: str): if not isinstance(value, str): raise TypeError("value must be type str") self._time = AstroTime(value, format='iso', scale='utc') @property - def tai_jd(self): + def tai_jd(self) -> float: """ Time value formatted as TAI standard julian day (float). """ @@ -298,14 +300,14 @@ def tai_jd(self): return self._time.tai.jd @tai_jd.setter - def tai_jd(self, value): + def tai_jd(self, value: Union[int,float]): if not isinstance(value, (int, float)): raise TypeError("value must be type int or float") self._time = AstroTime(value, format='jd', scale='tai') @property - def tai_mjd(self): + def tai_mjd(self) -> float: """ Time value formatted as TAI modified julian day (float). """ @@ -313,14 +315,14 @@ def tai_mjd(self): return self._time.tai.mjd @tai_mjd.setter - def tai_mjd(self, value): + def tai_mjd(self, value: Union[int,float]): if not isinstance(value, (int, float)): raise TypeError("value must be type int or float") self._time = AstroTime(value, format='mjd', scale='tai') @property - def utc_timet(self): + def utc_timet(self) -> float: """ Time value formatted as UTC UNIX timet seconds. """ @@ -328,14 +330,14 @@ def utc_timet(self): return self._time.utc.unix @utc_timet.setter - def utc_timet(self, value): + def utc_timet(self, value: Union[int,float]): if not isinstance(value, (int, float)): raise TypeError("value must be type int or float") self._time = AstroTime(value, format='unix', scale='utc') @property - def tai_timet(self): + def tai_timet(self) -> float: """ Time value formatted as TAI UNIX timet seconds. """ @@ -343,14 +345,14 @@ def tai_timet(self): return self._time.tai.unix_tai @tai_timet.setter - def tai_timet(self, value): + def tai_timet(self, value: Union[int,float]): if not isinstance(value, (int, float)): raise TypeError("value must be type int or float") self._time = AstroTime(value, format='unix_tai', scale='tai') @property - def astropy(self): + def astropy(self) -> AstroTime: """ Time value as a astropy.time.Time instance. """ @@ -358,7 +360,7 @@ def astropy(self): return self._time @astropy.setter - def astropy(self, value): + def astropy(self, value: AstroTime): if not isinstance(value, AstroTime): raise TypeError("value must be type astropy.time.Time") @@ -373,7 +375,7 @@ class SkyPosition(object): __metaclass__ = abc.ABCMeta @abc.abstractmethod - def apparent_equ(self, time_): + def apparent_equ(self, time_: Time) -> astro.equ_posn: """ Return position formatted as apparent equatorial coordinates. The 'time_' parameter should be set to a Time instance providing @@ -381,10 +383,10 @@ def apparent_equ(self, time_): Return value is object of type astro.equ_posn. """ - raise NotImplementedError() + raise NotImplementedError @abc.abstractmethod - def apparent_ecl(self, time_): + def apparent_ecl(self, time_: Time) -> astro.ecl_posn: """ Return position formatted as apparent ecliptic coordinates. The 'time_' parameter should be set to a Time instance providing @@ -392,7 +394,7 @@ def apparent_ecl(self, time_): Return alue is object of type astro.ecl_posn. """ - raise NotImplementedError() + raise NotImplementedError class CelestialPosition(SkyPosition): @@ -429,7 +431,7 @@ class CelestialPosition(SkyPosition): known_epochs = (EPOCH_J2000, EPOCH_B1950) - def __init__(self, value, format = FORMAT_EQU, epoch = EPOCH_J2000, name = ''): + def __init__(self, value: Any, format: str=FORMAT_EQU, epoch: str=EPOCH_J2000, name: str=''): """ Create a CelestialPosition object, using 'value' as the initial coordinates. @@ -488,7 +490,7 @@ def __repr__(self): return "%s.%s(%s, name=%s)" % (type(self).__module__, type(self).__name__, repr(self._posn), repr(self.name)) @property - def j2000_equ(self): + def j2000_equ(self) -> astro.equ_posn: """ Position formatted as J2000 equatorial coordinates. Value is object of type astro.equ_posn. @@ -497,7 +499,7 @@ def j2000_equ(self): return self._posn @j2000_equ.setter - def j2000_equ(self, value): + def j2000_equ(self, value: Union[astro.equ_posn,SequenceABC]): if not isinstance(value, (astro.equ_posn, SequenceABC)): raise TypeError("value must be type astro.equ_posn or sequence of length 2") if isinstance(value, SequenceABC): @@ -507,7 +509,7 @@ def j2000_equ(self, value): self._posn = copy.copy(value) - def apparent_equ(self, time_): + def apparent_equ(self, time_: Time) -> astro.equ_posn: """ Return position formatted as apparent equatorial coordinates. The 'time_' parameter should be set to a Time instance providing @@ -521,7 +523,7 @@ def apparent_equ(self, time_): return astro.get_apparent_posn(self._posn, time_.utc_jd) @property - def b1950_equ(self): + def b1950_equ(self) -> astro.equ_posn: """ Position formatted as B1950 equatorial coordinates. Value is object of type astro.equ_posn. @@ -530,7 +532,7 @@ def b1950_equ(self): return astro.J2000_to_B1950(self._posn) @b1950_equ.setter - def b1950_equ(self, value): + def b1950_equ(self, value: Union[astro.equ_posn,SequenceABC]): if not isinstance(value, (astro.equ_posn, SequenceABC)): raise TypeError("value must be type astro.equ_posn or sequence of length 2") if isinstance(value, SequenceABC): @@ -541,7 +543,7 @@ def b1950_equ(self, value): self._posn = astro.B1950_to_J2000(value) @property - def j2000_ecl(self): + def j2000_ecl(self) -> astro.ecl_posn: """ Position formatted as J2000 ecliptic coordinates. Value is object of type astro.ecl_posn. @@ -550,7 +552,7 @@ def j2000_ecl(self): return astro.get_ecl_from_equ(self._posn, astro.J2000_UTC_JD) @j2000_ecl.setter - def j2000_ecl(self, value): + def j2000_ecl(self, value: Union[astro.ecl_posn,SequenceABC]): if not isinstance(value, (astro.ecl_posn, SequenceABC)): raise TypeError("value must be type astro.ecl_posn or sequence of length 2") if isinstance(value, SequenceABC): @@ -560,7 +562,7 @@ def j2000_ecl(self, value): self._posn = astro.get_equ_from_ecl(value, astro.J2000_UTC_JD) - def apparent_ecl(self, time_): + def apparent_ecl(self, time_: Time) -> astro.ecl_posn: """ Return position formatted as apparent ecliptic coordinates. The 'time_' parameter should be set to a Time instance providing @@ -575,7 +577,7 @@ def apparent_ecl(self, time_): return astro.get_ecl_from_equ(equ, time_.utc_jd) @property - def j2000_gal(self): + def j2000_gal(self) -> astro.gal_posn: """ Position formatted as J2000 galactic coordinates. Value is object of type astro.gal_posn. @@ -584,7 +586,7 @@ def j2000_gal(self): return astro.get_gal_from_equ(self._posn) @j2000_gal.setter - def j2000_gal(self, value): + def j2000_gal(self, value: Union[astro.gal_posn,SequenceABC]): if not isinstance(value, (astro.gal_posn, SequenceABC)): raise TypeError("value must be type astro.gal_posn or sequence of length 2") if isinstance(value, SequenceABC): @@ -615,7 +617,7 @@ class PlanetaryPosition(SkyPosition): known_names = (NAME_SUN, NAME_MOON, NAME_VENUS, NAME_MARS, NAME_JUPITER, NAME_SATURN) - def __init__(self, name): + def __init__(self, name: str): """ Create a PlanetaryPosition instance. """ @@ -643,7 +645,7 @@ def __init__(self, name): def __repr__(self): return "%s.%s(%s)" % (type(self).__module__, type(self).__name__, repr(self.name)) - def apparent_equ(self, time_): + def apparent_equ(self, time_: Time) -> astro.equ_posn: """ Position formatted as apparent equatorial coordinates. The 'time_' parameter should be set to a Time instance providing @@ -656,7 +658,7 @@ def apparent_equ(self, time_): return self._posn_func(time_.utc_jd) - def apparent_ecl(self, time_): + def apparent_ecl(self, time_: Time) -> astro.ecl_posn: """ Position formatted as apparent ecliptic coordinates. The 'time_' parameter should be set to a Time instance providing @@ -691,14 +693,14 @@ class GeographicalPosition(object): known_formats = (FORMAT_GEO, FORMAT_ECEF) - def __init__(self, value, format = FORMAT_GEO, name = ''): + def __init__(self, value: Any, format: str=FORMAT_GEO, name: str=''): """ Create a new GeographicalPosition instance, using 'value' as the initial coordinates. 'format' describes the type of 'value': - * GeographicalPoistion.FORMAT_GEO - longitude,latitude, and elevation - * GeographicalPoistion.FORMAT_ECEF - ECEF rectangular geodetic + * GeographicalPosition.FORMAT_GEO - longitude,latitude, and elevation + * GeographicalPosition.FORMAT_ECEF - ECEF rectangular geodetic """ # check parameters @@ -719,7 +721,7 @@ def __repr__(self): return "%s.%s(%s, name=%s)" % (type(self).__module__, type(self).__name__, repr(self._posn), repr(self.name)) @property - def geo(self): + def geo(self) -> astro.geo_posn: """ Position formatted as geodedic longitude, latitude, elevation. Value is object of type astro.geo_posn. @@ -728,7 +730,7 @@ def geo(self): return self._posn @geo.setter - def geo(self, value): + def geo(self, value: Union[astro.geo_posn,SequenceABC]): if not isinstance(value, (astro.geo_posn, SequenceABC)): raise TypeError("value must be type astro.geo_posn or sequence of length 2/3") if isinstance(value, SequenceABC): @@ -739,7 +741,7 @@ def geo(self, value): self._posn = copy.copy(value) @property - def ecef(self): + def ecef(self) -> astro.rect_posn: """ Position formatted as ECEF rectagular coordinates. Value is object of type astro.rect_posn. @@ -748,7 +750,7 @@ def ecef(self): return astro.get_rect_from_geo(self._posn) @ecef.setter - def ecef(self, value): + def ecef(self, value: Union[astro.rect_posn,SequenceABC]): if not isinstance(value, (astro.rect_posn, SequenceABC)): raise TypeError("value must be type astro.rect_posn or sequence of length 3") if isinstance(value, SequenceABC): @@ -758,7 +760,7 @@ def ecef(self, value): self._posn = astro.get_geo_from_rect(value) - def sidereal(self, time_): + def sidereal(self, time_: Time) -> float: """ Return the apparent sidereal time for this location. The 'time_' parameter should be set to a Time instance providing @@ -788,7 +790,7 @@ class PointingDirection(object): the rise, set, and transit ephemeris times. """ - def __init__(self, source, site): + def __init__(self, source: SkyPosition, site: GeographicalPosition): """ Create a new pointing direction instance. @@ -805,7 +807,7 @@ def __init__(self, source, site): def __repr__(self): return "%s.%s(source=%s, site=%s)" % (type(self).__module__, type(self).__name__, repr(self.source), repr(self.site)) - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: Any): # make surce 'source' and 'site' member are correct type if (name == 'source') and (not isinstance(value, SkyPosition)): @@ -816,7 +818,7 @@ def __setattr__(self, name, value): object.__setattr__(self, name, value) - def hrz(self, time_): + def hrz(self, time_: Time) -> astro.hrz_posn: """ Return the pointing direction in horizontal coordinates as type astro.hrz_posn. @@ -830,7 +832,7 @@ def hrz(self, time_): return astro.get_hrz_from_equ(self.source.apparent_equ(time_), self.site.geo, time_.utc_jd) - def dir_cos(self, time_): + def dir_cos(self, time_: Time) -> Tuple[float,float,float]: """ Return the pointing direction as three direction cosine components. The 'time_' parameter should be set to a Time instance providing @@ -839,7 +841,7 @@ def dir_cos(self, time_): """ return astro.dir_cos(self.hrz(time_)) - def rst(self, time_): + def rst(self, time_: Time) -> astro.rst_time: """ Return the rise, set, and transit ephemeris times. The 'time_' parameter should be set to a Time instance providing From 6fc615022694ae3986d5301c8b24a67efa9be8c3 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Wed, 10 Jul 2024 13:38:11 -0600 Subject: [PATCH 02/18] Add stub files for lsl.correlator. --- lsl/correlator/_core.pyi | 11 +++++++++++ lsl/correlator/_spec.pyi | 7 +++++++ lsl/correlator/_stokes.pyi | 9 +++++++++ 3 files changed, 27 insertions(+) create mode 100644 lsl/correlator/_core.pyi create mode 100644 lsl/correlator/_spec.pyi create mode 100644 lsl/correlator/_stokes.pyi diff --git a/lsl/correlator/_core.pyi b/lsl/correlator/_core.pyi new file mode 100644 index 00000000..8804e5c8 --- /dev/null +++ b/lsl/correlator/_core.pyi @@ -0,0 +1,11 @@ +import numpy as np + +from typing import Callable, Optional, Tuple + +def FEngine(signals: np.ndarray, freqs: np.ndarray, delays: np.ndarray, LFFT: int=64, overlap: int=1, sample_rate: float=196e6, clip_level: int=0, window: Optional[Callable[int]]=None) -> Tuple[np.ndarray,np.ndarray]: ... + +def PFBEngine(signals: np.ndarray, freqs: np.ndarray, delays: np.ndarray, LFFT: int=64, overlap: int=1, sample_rate: float=196e6, clip_level: int=0, window: Optional[Callable[int]]=None) -> Tuple[np.ndarray,np.ndarray]: ... + +def XEngine2(signals1: np.ndarray, signals2: np.ndarray, valid1: np.ndarray, valid2: np.ndarray) -> np.ndarray: ... + +def XEngine3(signalsX: np.ndarray, signalsY: np.ndarray, validX: np.ndarray, validY: np.ndarray) -> np.ndarray: ... diff --git a/lsl/correlator/_spec.pyi b/lsl/correlator/_spec.pyi new file mode 100644 index 00000000..dcea6b7b --- /dev/null +++ b/lsl/correlator/_spec.pyi @@ -0,0 +1,7 @@ +import numpy as np + +from typing import Callable, Optional + +def FPSD(signals: np.ndarray, LFFT: int=64, overlap: int=1, clip_level: int=0, window: Optional[int]=None) -> np.ndarray: ... + +def PFBPSD(signals: np.ndarray, LFFT: int=64, overlap: int=1, clip_level: int=0, window: Optional[int]=None) -> np.ndarray: ... diff --git a/lsl/correlator/_stokes.pyi b/lsl/correlator/_stokes.pyi new file mode 100644 index 00000000..18df0572 --- /dev/null +++ b/lsl/correlator/_stokes.pyi @@ -0,0 +1,9 @@ +import numpy as np + +from typing import Callable, Optional + +def FPSD(signalsX: np.ndarray, signalsY: np.ndarray, LFFT: int=64, overlap: int=1, clip_level: int=0, window: Optional[int]=None) -> np.ndarray: ... + +def PFBPSD(signalsX: np.ndarray, signalsY: np.ndarray, LFFT: int=64, overlap: int=1, clip_level: int=0, window: Optional[int]=None) -> np.ndarray: ... + +def XEngine3(signalsX: np.ndarray, signalsY: np.ndarray, validX: np.ndarray, validY: np.ndarray) -> np.ndarray: ... From 7c0662ec0db7c2a6f13a46ae055cd7f12bfe6948 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Wed, 10 Jul 2024 13:48:16 -0600 Subject: [PATCH 03/18] Fix Callable call. --- lsl/correlator/filterbank.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lsl/correlator/filterbank.py b/lsl/correlator/filterbank.py index e07aa189..f8a02297 100644 --- a/lsl/correlator/filterbank.py +++ b/lsl/correlator/filterbank.py @@ -27,7 +27,7 @@ def __filterCoeff(N: int, P: int) -> np.ndarray: return np.sinc((t - N*P/2.0 + 0.5)/N) -def fft(signal: np.ndarray, N: int, P: int=1, window: Callable[int]=null_window) -> np.ndarray: +def fft(signal: np.ndarray, N: int, P: int=1, window: Callable[[int],np.ndarray]=null_window) -> np.ndarray: """ FFT-based poly-phase filter bank for creating N channels with P taps. Optionally, a window function can be specified using the @@ -46,35 +46,35 @@ def fft(signal: np.ndarray, N: int, P: int=1, window: Callable[int]=null_window) return fbOutput -def fft2(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: +def fft2(signal: np.ndarray, N: int, window: Callable[[int],np.ndarray]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses two taps. """ return fft(signal, N, P=2, window=window) -def fft4(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: +def fft4(signal: np.ndarray, N: int, window: Callable[[int],np.ndarray]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses four taps. """ return fft(signal, N, P=4, window=window) -def fft8(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: +def fft8(signal: np.ndarray, N: int, window: Callable[[int],np.ndarray]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses eight taps. """ return fft(signal, N, P=8, window=window) -def fft16(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: +def fft16(signal: np.ndarray, N: int, window: Callable[[int],np.ndarray]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses 16 taps. """ return fft(signal, N, P=16, window=window) -def fft32(signal: np.ndarray, N: int, window: Callable[int]=null_window) -> np.ndarray: +def fft32(signal: np.ndarray, N: int, window: Callable[[int],np.ndarray]=null_window) -> np.ndarray: """ Sub-type of :mod:`lsl.correlator.filterbank.fft` that uses 32 taps. """ From 65b035ca54ebbdac2c473ebc8072652fd46ea6c2 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 11:10:34 -0600 Subject: [PATCH 04/18] Add a stub file (plus a missing entry in __all__) for lsl.reader._gofast. --- lsl/reader/_gofast.pyi | 29 +++++++++++++++++++++++++++++ lsl/reader/gofast.cpp | 1 + 2 files changed, 30 insertions(+) create mode 100644 lsl/reader/_gofast.pyi diff --git a/lsl/reader/_gofast.pyi b/lsl/reader/_gofast.pyi new file mode 100644 index 00000000..9fcec8a9 --- /dev/null +++ b/lsl/reader/_gofast.pyi @@ -0,0 +1,29 @@ +from typing import BinaryIO + +from lsl.reader.cor import Frame as CORFrame +from lsl.reader.drspec import Frame as DRSpecFrame +from lsl.reader.drx import Frame as DRXFrame +from lsl.reader.drx8 import Frame as DRX8Frame +from lsl.reader.tbf import Frame as TBFFrame +from lsl.reader.tbn import Frame as TBNFrame +from lsl.reader.tbw import Frame as TBWFrame +from lsl.reader.vdif import Frame as VDIFFrame + +def read_tbw(fh: BinaryIO, frame: TBWFrame) -> TBWFrame: ... +def read_tbn(fh: BinaryIO, frame: TBNFrame) -> TBNFrame: ... +def read_tbn_ci8(fh: BinaryIO, frame: TBNFrame) -> TBNFrame: ... +def read_drx(fh: BinaryIO, frame: DRXFrame) -> DRXFrame: ... +def read_drx_ci8(fh: BinaryIO, frame: DRXFrame) -> DRXFrame: ... +def read_drx8(fh: BinaryIO, frame: DRX8Frame) -> DRX8Frame: ... +def read_drx8_ci8(fh: BinaryIO, frame: DRX8Frame) -> DRX8Frame: ... +def read_drspec(fh: BinaryIO, frame: DRSpecFrame) -> DRSpecFrame: ... +def read_vdif(fh: BinaryIO, frame: VDIFFrame) -> VDIFFrame: ... +def read_vdif_i8(fh: BinaryIO, frame: VDIFFrame) -> VDIFFrame: ... +def read_tbf(fh: BinaryIO, frame: TBFFrame) -> TBFFrame: ... +def read_tbf_ci8(fh: BinaryIO, frame: TBFFrame) -> TBFFrame: ... +def read_cor(fh: BinaryIO, frame: CORFrame) -> CORFrame: ... + +class SyncError(IOError): ... +class EOFError(IOError): ... + +NCHAN_COR: int = ... diff --git a/lsl/reader/gofast.cpp b/lsl/reader/gofast.cpp index c70cec46..a840fcba 100644 --- a/lsl/reader/gofast.cpp +++ b/lsl/reader/gofast.cpp @@ -171,6 +171,7 @@ static int gofast_exec(PyObject *module) { PyList_Append(all, PyUnicode_FromString("read_drx8_ci8")); PyList_Append(all, PyUnicode_FromString("read_drspec")); PyList_Append(all, PyUnicode_FromString("read_vdif")); + PyList_Append(all, PyUnicode_FromString("read_vdif_i8")); PyList_Append(all, PyUnicode_FromString("read_tbf")); PyList_Append(all, PyUnicode_FromString("read_tbf_ci8")); PyList_Append(all, PyUnicode_FromString("read_cor")); From 8447d58106d460a6c5440c2438b8798affce9c95 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 11:10:53 -0600 Subject: [PATCH 05/18] More type hints. --- lsl/common/paths.py | 6 +++--- lsl/config.py | 38 ++++++++++++++++++---------------- lsl/misc/telemetry/__init__.py | 29 +++++++++++++------------- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/lsl/common/paths.py b/lsl/common/paths.py index b70e94d5..ebd07b34 100644 --- a/lsl/common/paths.py +++ b/lsl/common/paths.py @@ -25,7 +25,7 @@ modInfo = importlib.util.find_spec('lsl') #: Absolute path to the LSL intall location -MODULE = os.path.abspath(modInfo.origin) +MODULE = os.path.abspath(modInfo.origin) # type: ignore MODULE = os.path.dirname(MODULE) #: Absolute path to the data directory where data files for LSL are stored @@ -46,8 +46,8 @@ # points to data. currentDir = os.path.abspath(os.getcwd()) if os.path.exists(os.path.join(currentDir, 'setup.py')) and os.path.exists(os.path.join(currentDir, 'lsl')): - modInfoBuild = importlib.util.find_spec('lsl', [currentDir]) - MODULE_BUILD = os.path.abspath(modInfoBuild.origin) + modInfoBuild = importlib.util.find_spec('lsl') + MODULE_BUILD = os.path.abspath(modInfoBuild.origin) # type: ignore MODULE_BUILD = os.path.dirname(MODULE_BUILD) DATA_BUILD = os.path.join(MODULE_BUILD, 'data') else: diff --git a/lsl/config.py b/lsl/config.py index 2686ce7f..0e53c57d 100644 --- a/lsl/config.py +++ b/lsl/config.py @@ -12,6 +12,8 @@ from lsl.version import full_version as lsl_version from lsl.misc.file_lock import FileLock +from typing import Any, Dict, Optional + # Create the .lsl directory and set the config filename try: if not os.path.exists(os.path.join(os.path.expanduser('~'), '.lsl')): @@ -34,7 +36,7 @@ # Default values ## lsl.common.sdf/sdfADP/idf -DEFAULTS_OBS = OrderedDict() +DEFAULTS_OBS: Dict[str,Dict] = OrderedDict() DEFAULTS_OBS['observer_name'] = {'value': None, 'help': 'Observer name for auto-filling Observer classes'} DEFAULTS_OBS['observer_id'] = {'value': None, @@ -45,7 +47,7 @@ 'help': 'Project ID for auto-filling Project classes'} ## lsl.reader.ldp -DEFAULTS_LDP = OrderedDict() +DEFAULTS_LDP: Dict[str,Dict] = OrderedDict() DEFAULTS_LDP['tbn_buffer_size'] = {'value': 20, 'help': 'TBN ring buffer size in timestamps'} DEFAULTS_LDP['drx_buffer_size'] = {'value': 20, @@ -58,12 +60,12 @@ 'help': 'COR ring buffer size in timestamps'} ## lsl.astro -DEFAULTS_ASTRO = OrderedDict() +DEFAULTS_ASTRO: Dict[str,Dict[str,Any]] = OrderedDict() DEFAULTS_ASTRO['leapsec_url'] = {'value': 'https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat', 'help':'URL for accessing leap second information'} ## lsl.misc.ionosphere -DEFAULTS_IONO = OrderedDict() +DEFAULTS_IONO: Dict[str,Dict] = OrderedDict() DEFAULTS_IONO['igs_url'] = {'value': 'ftps://gdc.cddis.eosdis.nasa.gov/gps/products/ionex/', 'help': 'primary URL for accessing the IGS data products'} DEFAULTS_IONO['igs_mirror'] = {'value': 'ftp://gssc.esa.int/gnss/products/ionex/', @@ -92,7 +94,7 @@ 'help': 'maximum cache size in MB; <= 0 disables cache size limiting'} ## lsl.misc.telemetry -DEFAULTS_TELEMETRY = OrderedDict() +DEFAULTS_TELEMETRY: Dict[str,Dict] = OrderedDict() DEFAULTS_TELEMETRY['enabled'] = {'value': True, 'help': 'whether or not LSL telemetry reporting is enabled'} DEFAULTS_TELEMETRY['max_entries'] = {'value': 100, @@ -101,7 +103,7 @@ 'help': 'upload timeout in seconds'} ## Download parameters -DEFAULTS_DOWNLOAD = OrderedDict() +DEFAULTS_DOWNLOAD: Dict[str,Dict] = OrderedDict() DEFAULTS_DOWNLOAD['block_size'] = {'value': 8192, 'help': 'download block size in bytes'} DEFAULTS_DOWNLOAD['timeout'] = {'value': 120, @@ -110,7 +112,7 @@ 'help': 'data cache refresh age in days'} ## Everything -DEFAULTS_ALL = OrderedDict() +DEFAULTS_ALL: Dict[str,Dict] = OrderedDict() DEFAULTS_ALL['observing'] = DEFAULTS_OBS DEFAULTS_ALL['ldp'] = DEFAULTS_LDP DEFAULTS_ALL['astro'] = DEFAULTS_ASTRO @@ -135,7 +137,7 @@ class LSLConfigParameter(object): Class that hold a single configuration parameter. """ - def __init__(self, name, value, help=None): + def __init__(self, name: str, value: Any, help: Optional[str]=None): self.name = name self.value = value self.help = help @@ -163,11 +165,11 @@ class LSLConfigContainer(object): Class for working with all LSL configuration parameters. """ - def __init__(self, filename=_CONFIG_FILENAME): + def __init__(self, filename: str=_CONFIG_FILENAME): self.filename = filename self._changed = False - self._parameters = OrderedDict() + self._parameters: Dict[str,Any] = OrderedDict() self._load_config() def __repr__(self): @@ -265,7 +267,7 @@ def _save_config(self): fh.write(str(self)) self._changed = False - def view(self, section): + def view(self, section: str) -> LSLConfigSubContainer: """ Return a configuration sub-container that defaults to looking up values in the specified section. @@ -276,7 +278,7 @@ def view(self, section): return LSLConfigSubContainer(self, section) - def get(self, name): + def get(self, name: str) -> Any: """ Return the value of a parameter. """ @@ -287,7 +289,7 @@ def get(self, name): raise ValueError(f"Unknown parameter '{name}'") return value - def set(self, name, value): + def set(self, name: str, value: Any): """ Set the value of a parameter. """ @@ -306,7 +308,7 @@ def set(self, name, value): raise ValueError(f"Unknown parameter '{name}'") @contextlib.contextmanager - def set_temp(self, name, value): + def set_temp(self, name: str, value: Any): """ Temporarily set the value of a parameter. This value will not persist across Python sessions. @@ -329,7 +331,7 @@ def set_temp(self, name, value): class LSLConfigSubContainer(object): - def __init__(self, container, section): + def __init__(self, container: LSLConfigContainer, section: str): self.container = container self.section = section @@ -338,7 +340,7 @@ def __repr__(self): a = [(attr,getattr(self, attr, None)) for attr in ('container', 'section',)] return tw_fill(_build_repr(n,a), subsequent_indent=' ') - def get(self, name): + def get(self, name: str) -> Any: """ Return the value of a parameter. """ @@ -349,7 +351,7 @@ def get(self, name): value = self.container.get(name) return value - def set(self, name, value): + def set(self, name: str, value: Any): """ Set the value of a parameter. """ @@ -359,7 +361,7 @@ def set(self, name, value): except KeyError: self.container.set(name, value) - def set_temp(self, name, value): + def set_temp(self, name: str, value: Any): """ Temporarily set the value of a parameter. This value will not persist across Python sessions. diff --git a/lsl/misc/telemetry/__init__.py b/lsl/misc/telemetry/__init__.py index ea528a8a..0afee109 100644 --- a/lsl/misc/telemetry/__init__.py +++ b/lsl/misc/telemetry/__init__.py @@ -22,6 +22,8 @@ from lsl.config import LSL_CONFIG TELE_CONFIG = LSL_CONFIG.view('telemetry') +from typing import Callable, Dict, List + __version__ = '0.3' __all__ = ['is_active', 'enable', 'disable', 'ignore', @@ -49,7 +51,7 @@ _IS_READONLY = False except OSError as e: - _INSTALL_KEY = None + _INSTALL_KEY = '' _IS_READONLY = True warnings.warn("Could not create telemetry cache, telemetry will be disabled for this session: %s" % str(e), @@ -62,7 +64,7 @@ class _TelemetryClient(object): """ _lock = RLock() - def __init__(self, key, version=lsl_version): + def __init__(self, key: str, version: str=lsl_version): # Setup self.key = key self.version = version @@ -74,7 +76,7 @@ def __init__(self, key, version=lsl_version): self._session_start = time.time() # Telemetry cache - self._cache = {} + self._cache: Dict[str,List] = {} self._cache_count = 0 if not _IS_READONLY: @@ -88,7 +90,7 @@ def __init__(self, key, version=lsl_version): else: self.active = False - def track(self, name, timing=0.0): + def track(self, name: str, timing: float=0.0) -> bool: """ Add an entry to the telemetry cache with optional timing information. """ @@ -111,7 +113,7 @@ def track(self, name, timing=0.0): return True - def send(self, final=False): + def send(self, final: bool=False) -> bool: """ Send the current cache of telemetry data back to the maintainers for analysis. @@ -132,16 +134,13 @@ def send(self, final=False): 'py_version' : self.py_version, 'session_time': "%.6f" % ((tNow-self._session_start) if final else 0.0,), 'payload' : payload}) - try: - payload = payload.encode() - except AttributeError: - pass - uh = urlopen('https://fornax.phys.unm.edu/telemetry/log.php', payload, + uh = urlopen('https://fornax.phys.unm.edu/telemetry/log.php', payload.encode(), timeout=self.timeout) status = uh.read() if status == '': self.clear() success = True + uh.close() except Exception as e: warnings.warn("Failed to send telemetry data: %s" % str(e)) else: @@ -159,7 +158,7 @@ def clear(self): self._cache_count = 0 @property - def is_active(self): + def is_active(self) -> bool: """ Whether or not the cache is active and sending data back. """ @@ -197,7 +196,7 @@ def ignore(self): # Telemetry control -def is_active(): +def is_active() -> bool: """ Return a boolean of whether or not the LSL telemetry client is active. """ @@ -264,15 +263,15 @@ def track_module(): _telemetry_client.track(caller.f_globals['__name__']) -def track_function(user_function): +def track_function(user_function: Callable) -> Callable: """ Record the use of a function in LSL without execution time information. """ global _telemetry_client - caller = inspect.currentframe().f_back - mod = caller.f_globals['__name__'] + caller = inspect.currentframe().f_back # type: ignore + mod = caller.f_globals['__name__'] # type: ignore fnc = user_function.__name__ name = mod+'.'+fnc+'()' From 888eaa58389bf415d2162063c8cff28ea2509602 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 11:18:51 -0600 Subject: [PATCH 06/18] Defined too late. --- lsl/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lsl/config.py b/lsl/config.py index 0e53c57d..ddea0bc3 100644 --- a/lsl/config.py +++ b/lsl/config.py @@ -267,7 +267,7 @@ def _save_config(self): fh.write(str(self)) self._changed = False - def view(self, section: str) -> LSLConfigSubContainer: + def view(self, section: str) -> "LSLConfigSubContainer": """ Return a configuration sub-container that defaults to looking up values in the specified section. From fbaf608d7a51f9fa98131fa8402684b506dca2d8 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 11:19:07 -0600 Subject: [PATCH 07/18] Stub file for lsl.common._fir. --- lsl/common/_fir.pyi | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 lsl/common/_fir.pyi diff --git a/lsl/common/_fir.pyi b/lsl/common/_fir.pyi new file mode 100644 index 00000000..1604823d --- /dev/null +++ b/lsl/common/_fir.pyi @@ -0,0 +1,7 @@ +import numpy as np + +from typing import Tuple + +def integer16(signals: np.ndarray, filter: np.ndarray) -> np.ndarray: ... +def integer16Delayed(signals: np.ndarray, filter: np.ndarray, delay: int) -> np.ndarray: ... +def integerBeamformer(signals: np.ndarray, filters: np.ndarray, courses: np.ndarray, fines: np.ndarray, gains: np.ndarray) -> Tuple[np.ndarray,np.ndarray]: ... From ffe33815e669886da9c928424f0a50da30be055c Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 12:43:30 -0600 Subject: [PATCH 08/18] More progress. --- lsl/common/adp.py | 18 ++++--- lsl/common/dp.py | 37 +++++++------- lsl/common/mcs.py | 115 ++++++++++++++++++++++--------------------- lsl/common/mcsADP.py | 12 +++-- lsl/common/mcsNDP.py | 12 +++-- lsl/common/ndp.py | 16 +++--- 6 files changed, 112 insertions(+), 98 deletions(-) diff --git a/lsl/common/adp.py b/lsl/common/adp.py index 5bf3b73f..c9e709ed 100644 --- a/lsl/common/adp.py +++ b/lsl/common/adp.py @@ -22,6 +22,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Callable + __version__ = '0.4' __all__ = ['fS', 'fC', 'T', 'T2', 'N_MAX', 'TBN_TUNING_WORD_MIN', 'TBN_TUNING_WORD_MAX', @@ -73,7 +75,7 @@ _N_PTS = 1000 # Number of points to use in calculating the bandpasses -def freq_to_word(freq): +def freq_to_word(freq: float) -> int: """ Given a frequency in Hz, convert it to the closest DP tuning word. """ @@ -81,7 +83,7 @@ def freq_to_word(freq): return int(round(freq*2**32 / fS)) -def word_to_freq(word): +def word_to_freq(word: int) -> float: """ Given a DP tuning word, convert it to a frequncy in Hz. """ @@ -89,7 +91,7 @@ def word_to_freq(word): return word*fS / 2**32 -def delay_to_dpd(delay): +def delay_to_dpd(delay: float) -> int: """ Given a delay in ns, convert it to a course and fine portion and into the final format expected by ADP (big endian 16.12 unsigned integer) @@ -110,7 +112,7 @@ def delay_to_dpd(delay): return combined -def dpd_to_delay(combined): +def dpd_to_delay(combined: int) -> float: """ Given a delay value in the final format expect by ADP, return the delay in ns. """ @@ -128,7 +130,7 @@ def dpd_to_delay(combined): return delay -def gain_to_dpg(gain): +def gain_to_dpg(gain: float) -> int: """ Given a gain (between 0 and 1), convert it to a gain in the final form expected by ADP (big endian 16.1 signed integer). @@ -143,7 +145,7 @@ def gain_to_dpg(gain): return combined -def dpg_to_gain(combined): +def dpg_to_gain(combined: int) -> float: """ Given a gain value in the final format expected by ADP, return the gain as a decimal value (0 to 1). @@ -158,7 +160,7 @@ def dpg_to_gain(combined): return gain -def tbn_filter(sample_rate=1e5, npts=_N_PTS): +def tbn_filter(sample_rate: float=1e5, npts: int=_N_PTS) -> Callable: """ Return a function that will generate the shape of a TBN filter for a given sample rate. @@ -180,7 +182,7 @@ def tbn_filter(sample_rate=1e5, npts=_N_PTS): return interp1d(h, w/w.max(), kind='cubic', bounds_error=False, fill_value=0.0) -def drx_filter(sample_rate=19.6e6, npts=_N_PTS): +def drx_filter(sample_rate: float=19.6e6, npts: int=_N_PTS) -> Callable: """ Return a function that will generate the shape of a DRX filter for a given sample rate. diff --git a/lsl/common/dp.py b/lsl/common/dp.py index 68dd8a71..2fa56baf 100644 --- a/lsl/common/dp.py +++ b/lsl/common/dp.py @@ -21,6 +21,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Callable, List, Optional, Tuple + __version__ = '0.7' __all__ = ['fS', 'T', 'T2', 'N_MAX', 'TBN_TUNING_WORD_MIN', 'TBN_TUNING_WORD_MAX', @@ -298,7 +300,7 @@ _N_PTS = 1000 # Number of points to use in calculating the bandpasses -def freq_to_word(freq): +def freq_to_word(freq: float) -> int: """ Given a frequency in Hz, convert it to the closest DP tuning word. """ @@ -306,7 +308,7 @@ def freq_to_word(freq): return int(round(freq*2**32 / fS)) -def word_to_freq(word): +def word_to_freq(word: int) -> float: """ Given a DP tuning word, convert it to a frequncy in Hz. """ @@ -314,7 +316,7 @@ def word_to_freq(word): return word*fS / 2**32 -def delay_to_dpd(delay): +def delay_to_dpd(delay: float) -> int: """ Given a delay in ns, convert it to a course and fine portion and into the final format expected by DP (big endian 16.12 unsigned integer). @@ -335,7 +337,7 @@ def delay_to_dpd(delay): return combined -def dpd_to_delay(combined): +def dpd_to_delay(combined: int) -> float: """ Given a delay value in the final format expect by DP, return the delay in ns. """ @@ -353,7 +355,7 @@ def dpd_to_delay(combined): return delay -def gain_to_dpg(gain): +def gain_to_dpg(gain: float) -> int: """ Given a gain (between 0 and 1), convert it to a gain in the final form expected by DP (big endian 16.1 signed integer). @@ -368,7 +370,7 @@ def gain_to_dpg(gain): return combined -def dpg_to_gain(combined): +def dpg_to_gain(combined: int) -> float: """ Given a gain value in the final format expected by DP, return the gain as a decimal value (0 to 1). @@ -383,7 +385,7 @@ def dpg_to_gain(combined): return gain -def tbn_filter(sample_rate=1e5, npts=_N_PTS): +def tbn_filter(sample_rate: float=1e5, npts: int=_N_PTS) -> Callable: """ Return a function that will generate the shape of a TBN filter for a given sample rate. @@ -419,7 +421,7 @@ def tbn_filter(sample_rate=1e5, npts=_N_PTS): return interp1d(h, w/w.max(), kind='cubic', bounds_error=False, fill_value=0.0) -def drx_filter(sample_rate=19.6e6, npts=_N_PTS): +def drx_filter(sample_rate: float=19.6e6, npts: int=_N_PTS) -> Callable: """ Return a function that will generate the shape of a DRX filter for a given sample rate. @@ -506,12 +508,12 @@ class SoftwareDP(object): 3: {'totalD': 196, 'CIC': _DRX_CIC_3, 'cicD': 98, 'FIR': _DRX_FIR, 'firD': 2}, },} - delayFIRs = [] + delayFIRs: List[List] = [] for i in range(520): delayFIRs.append([]) delayFIRs[-1].extend(_DELAY_FIRS) - def __init__(self, mode='DRX', filter=7, central_freq=74e6): + def __init__(self, mode: str='DRX', filter: int=7, central_freq: float=74e6): """ Setup DP for processing an input TBW signal. Keywords accepted are: * mode -> mode of operation (DRX or TBN) @@ -542,7 +544,7 @@ def __init__(self, mode='DRX', filter=7, central_freq=74e6): def __str__(self): return f"Sofware DP: {self.mode} with filter {self.filter} at {self.central_freq/1e6:.3f} MHz" - def set_mode(self, mode): + def set_mode(self, mode: str): """ Set the mode of operation for the software DP instance. """ @@ -551,7 +553,7 @@ def set_mode(self, mode): raise ValueError(f"Unknown mode '{mode}'") self.mode = mode - def set_filter(self, filter): + def set_filter(self, filter: int): """ Set the filter code for the current mode. """ @@ -562,7 +564,7 @@ def set_filter(self, filter): raise ValueError(f"Unknown or unsupported filter for {self.mode}, '{filter}'") self.filter = filter - def set_tuning_freq(self, central_freq): + def set_tuning_freq(self, central_freq: float): """ Set the tuning frequency for the current setup. """ @@ -575,7 +577,7 @@ def set_tuning_freq(self, central_freq): raise ValueError(f"Central frequency of {central_freq/1e6:.2f} MHz outside the DP tuning range.") self.central_freq = central_freq - def set_delay_firs(self, channel, coeffs): + def set_delay_firs(self, channel: int, coeffs: List[List]): """ Set the delay FIR coefficients for a particular channel to the list of lists provided (filter set by filter coefficients). If channel is 0, the delay FIR @@ -600,11 +602,11 @@ def set_delay_firs(self, channel, coeffs): for i in range(520): self.delayFIRs.append([]) self.delayFIRs[-1].extend(coeffs) - + else: self.delayFIRs[channel-1] = coeffs - def form_beam(self, antennas, time, data, course_delays=None, fine_delays=None, gains=None): + def form_beam(self, antennas: List, time: np.ndarray, data: np.ndarray, course_delays: Optional[np.ndarray]=None, fine_delays: Optional[np.ndarray]=None, gains: Optional[np.ndarray]=None) -> Tuple[np.ndarray,np.ndarray]: """ Process a given batch of TBW data using the provided delay and gain information to form a beam. Returns a two-element tuple, one for each beam polarization. @@ -615,9 +617,8 @@ def form_beam(self, antennas, time, data, course_delays=None, fine_delays=None, fine = np.array(fine_delays, dtype=np.int16) gain = (np.array(gains)*32767).astype(np.int16) return _fir.integerBeamformer(data, filters, course, fine, gain) - - def apply_filter(self, time, data, disable_pool=False): + def apply_filter(self, time: np.ndarray, data: np.ndarray) -> np.ndarray: """ Process a given batch of TBW data using the current mode of operation. This function requires both an array of times (int64 in fS since the UNIX epoch) diff --git a/lsl/common/mcs.py b/lsl/common/mcs.py index b013c685..598edf68 100644 --- a/lsl/common/mcs.py +++ b/lsl/common/mcs.py @@ -47,7 +47,7 @@ import ctypes import struct from functools import reduce -from datetime import datetime +from datetime import datetime, tzinfo from astropy import units as astrounits from astropy.coordinates import SphericalRepresentation, CartesianRepresentation @@ -57,6 +57,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Dict, List, Optional, Tuple + __version__ = '0.3' __all__ = ['ME_SSMIF_FORMAT_VERSION', 'ME_MAX_NSTD', 'ME_MAX_NFEE', 'ME_MAX_FEEID_LENGTH', 'ME_MAX_RACK', 'ME_MAX_PORT', @@ -330,7 +332,7 @@ _cDecRE = re.compile(r'(?P[a-z][a-z \t]+)[ \t]+(?P[a-zA-Z_0-9]+)(\[(?P[\*\+A-Z_\d]+)\])?(\[(?P[\*\+A-Z_\d]+)\])?(\[(?P[\*\+A-Z_\d]+)\])?(\[(?P[\*\+A-Z_\d]+)\])?;') -def parse_c_struct(cStruct, char_mode='str', endianness='native', overrides=None): +def parse_c_struct(cStruct: str, char_mode: str='str', endianness: str='native', overrides: Optional[Dict]=None) -> ctypes.Structure: """ Function to take a C structure declaration and build a ctypes.Structure out of it with the appropriate alignment, character interpretation*, and endianness @@ -357,17 +359,16 @@ def parse_c_struct(cStruct, char_mode='str', endianness='native', overrides=None if char_mode not in ('str', 'int'): raise RuntimeError(f"Unknown character mode: '{char_mode}'") if char_mode == 'str': - baseCharType = ctypes.c_char + baseCharType = ctypes.c_char # type: ignore else: - baseCharType = ctypes.c_byte + baseCharType = ctypes.c_byte # type: ignore # Hold the basic fields and dimensions fields = [] - dims2 = {} + dims2: Dict[str,List] = {} # Split into lines and go! - cStruct = cStruct.split('\n') - for line in cStruct: + for line in cStruct.split('\n'): ## Skip structure declaration, blank lines, comments, and lines too short to hold a ## declaration line = line.strip().rstrip() @@ -406,31 +407,31 @@ def parse_c_struct(cStruct, char_mode='str', endianness='native', overrides=None ## Basic data types if dec in ('signed int', 'int'): - typ = ctypes.c_int + typ = ctypes.c_int # type: ignore elif dec == 'unsigned int': - typ = ctypes.c_uint + typ = ctypes.c_uint # type: ignore elif dec in ('signed short int', 'signed short', 'short int', 'short'): - typ = ctypes.c_short + typ = ctypes.c_short # type: ignore elif dec in ('unsigned short int', 'unsigned short'): - typ = ctypes.c_ushort + typ = ctypes.c_ushort # type: ignore elif dec in ('signed long int', 'signed long', 'long int', 'long'): - typ = ctypes.c_long + typ = ctypes.c_long # type: ignore elif dec in ('unsigned long int', 'unsigned long'): - typ = ctypes.c_ulong + typ = ctypes.c_ulong # type: ignore elif dec in ('signed long long', 'long long'): - typ = ctypes.c_longlong + typ = ctypes.c_longlong # type: ignore elif dec == 'unsigned long long': - typ = ctypes.c_uint64 + typ = ctypes.c_uint64 # type: ignore elif dec == 'float': - typ = ctypes.c_float + typ = ctypes.c_float # type: ignore elif dec == 'double': - typ = ctypes.c_double + typ = ctypes.c_double # type: ignore elif dec == 'char': - typ = baseCharType + typ = baseCharType # type: ignore elif dec == 'signed char': - typ = ctypes.c_byte + typ = ctypes.c_byte # type: ignore elif dec == 'unsigned char': - typ = ctypes.c_ubyte + typ = ctypes.c_ubyte # type: ignore else: raise RuntimeError(f"Unparseable line: '{line}' -> type: {dec}, name: {name}, dims: {d1}, {d2}, {d3}, {d4}") @@ -460,17 +461,16 @@ def parse_c_struct(cStruct, char_mode='str', endianness='native', overrides=None if endianness not in ('little', 'big', 'network', 'native'): raise RuntimeError(f"Unknown endianness: '{endianness}'") + ct_endianness = ctypes.Structure if endianness == 'little': - endianness = ctypes.LittleEndianStructure + ct_endianness = ctypes.LittleEndianStructure elif endianness == 'big': - endianness = ctypes.BigEndianStructure + ct_endianness = ctypes.BigEndianStructure elif endianness == 'network': - endianness = ctypes.BigEndianStructure - else: - endianness = ctypes.Structure - + ct_endianness = ctypes.BigEndianStructure + # ctypes creation - actual - class MyStruct(endianness): + class MyStruct(ct_endianness): # type: ignore """ ctypes.Structure of the correct endianness for the provided C structure. @@ -501,14 +501,14 @@ def __str__(self): out += f"{f} ({d}): "+str(getattr(self, f))+'\n' return out - def sizeof(self): + def sizeof(self) -> int: """ Return the size, in bytes, of the structure. """ return ctypes.sizeof(self) - def as_dict(self): + def as_dict(self) -> Dict: """ Return the structure as a simple Python dictionary keyed off the structure elements. @@ -527,7 +527,7 @@ def _two_byte_swap(value): return ((value & 0xFF) << 8) | ((value >> 8) & 0xFF) -def delay_to_mcsd(delay): +def delay_to_mcsd(delay: float) -> int: """ Given a delay in ns, convert it to a course and fine portion and into the form expected by MCS in a custom beamforming SDF (little endian @@ -539,7 +539,7 @@ def delay_to_mcsd(delay): return _two_byte_swap( dpCommon.delay_to_dpd(delay) ) -def mcsd_to_delay(delay): +def mcsd_to_delay(delay: int) -> float: """ Given delay value from an OBS_BEAM_DELAY field in a custom beamforming SDF, return the delay in ns. @@ -550,7 +550,7 @@ def mcsd_to_delay(delay): return dpCommon.dpd_to_delay( _two_byte_swap(delay) ) -def gain_to_mcsg(gain): +def gain_to_mcsg(gain: float) -> int: """ Given a gain (between 0 and 1), convert it to a gain in the form expected by MCS in a custom beamforming SDF (little endian 16.1 @@ -562,7 +562,7 @@ def gain_to_mcsg(gain): return _two_byte_swap( dpCommon.gain_to_dpg(gain) ) -def mcsg_to_gain(gain): +def mcsg_to_gain(gain: int) -> float: """ Given a gain value from an OBS_BEAM_GAIN field in a custom beamforming SDF, return the decimal equivalent. @@ -573,7 +573,7 @@ def mcsg_to_gain(gain): return dpCommon.dpg_to_gain( _two_byte_swap(gain) ) -def mjdmpm_to_datetime(mjd, mpm, tz=None): +def mjdmpm_to_datetime(mjd: int, mpm: int, tz: Optional[tzinfo]=None) -> datetime: """ Convert a MJD, MPM pair to a naive datetime instance. If `tz` is not None the value is converted to a time zone-aware instance in the specified time @@ -593,7 +593,7 @@ def mjdmpm_to_datetime(mjd, mpm, tz=None): return dt -def datetime_to_mjdmpm(dt): +def datetime_to_mjdmpm(dt: datetime) -> Tuple[int,int]: """ Convert a UTC datetime instance to a MJD, MPM pair (returned as a two-element tuple). @@ -771,7 +771,7 @@ class ObservingMode(enum.Enum): TRK_LUN = 9 -def flat_to_multi(inputList, *shape): +def flat_to_multi(inputList: List, *shape: int) -> List: """ Convert a 1-D list into a multi-dimension list of shape 'shape'. @@ -813,7 +813,7 @@ def _get_rotation_matrix(theta, phi, psi, degrees=True): return rot -def apply_pointing_correction(az, el, theta, phi, psi, lat=34.070, degrees=True): +def apply_pointing_correction(az: float, el: float, theta: float, phi: float, psi: float, lat: float=34.070, degrees: bool=True) -> Tuple[float,float]: """ Given a azimuth and elevation pair, and an axis to rotate about, perform the rotation. @@ -883,7 +883,7 @@ def __getitem__(self, key): except KeyError: return self.entries[self.invMapper[key]] - def keys(self, name=False): + def keys(self, name: bool=False) -> List[str]: """ Return a list of entry indicies (or names if the 'name' keyword is set to True) for the MIB. @@ -902,7 +902,7 @@ def keys(self, name=False): # Index return self.entries.keys() - def parse_init_file(self, filename): + def parse_init_file(self, filename: str) -> bool: """ Given a MCS MIB initialization file, i.e., ASP_MIB_init.dat, create a dictionary that maps indicies to human-readable names that can be used @@ -930,7 +930,8 @@ def parse_init_file(self, filename): # Done return True - def from_file(self, filename, init_filename=None): + @classmethod + def from_file(klass, filename: str, init_filename: Optional[str]=None): """ Given the name of a GDBM database file, initialize the MIB. Optionally, use the name of the MCS MIB initialization file to @@ -938,8 +939,9 @@ def from_file(self, filename, init_filename=None): """ # Parse the init. file (if we have one) + mib = klass() if init_filename is not None: - self.parse_init_file(init_filename) + mib.parse_init_file(init_filename) # Make sure we have the .pag file if filename[-4:] == '.dir': @@ -949,23 +951,23 @@ def from_file(self, filename, init_filename=None): db = dbm.open(filename, 'ru') # Go! - entry = db.firstkey() + entry = db.firstkey() # type: ignore while entry is not None: value = db[entry] try: record = MIBEntry() record.from_entry(value) - self.entries[record.index] = record + mib.entries[record.index] = record except ValueError: pass - entry = db.nextkey(entry) + entry = db.nextkey(entry) # type: ignore db.close() # Done - return True + return mib class MIBEntry(object): @@ -999,7 +1001,8 @@ def __str__(self): return f"Index: {self.index}; Value: {self.value}; Updated at {self.updateTime}" - def _parse_value(self, value, dataType): + @staticmethod + def _parse_value(value, dataType): """ Convert an encoded value to something Pythonic (if possible). @@ -1087,7 +1090,8 @@ def _parse_value(self, value, dataType): else: raise ValueError(f"Unknown data type '{dataType}'") - def from_entry(self, value): + @classmethod + def from_entry(klass, value: bytes): """ Given an MIB entry straight out of a GDBM database, populate the MIBEntry instance. @@ -1136,14 +1140,15 @@ def from_entry(self, value): raise ValueError(f"Entry index '{record.index}' does not appear to be numeric") # Basic information - self.eType = int(record.eType) - self.index = index - self.value = self._parse_value(record.val, dbmType) - self.dbmType = dbmType - self.icdType = icdType - self._tv = (int(record.tv[0]), int(record.tv[1])) + mibe = klass() + mibe.eType = int(record.eType) + mibe.index = index + mibe.value = klass._parse_value(record.val, dbmType) + mibe.dbmType = dbmType + mibe.icdType = icdType + mibe._tv = (int(record.tv[0]), int(record.tv[1])) # Time - self.updateTime = datetime.utcfromtimestamp(record.tv[0] + record.tv[1]/1e9) + mibe.updateTime = datetime.utcfromtimestamp(record.tv[0] + record.tv[1]/1e9) - return True + return mibe diff --git a/lsl/common/mcsADP.py b/lsl/common/mcsADP.py index 60df1c09..c695969b 100644 --- a/lsl/common/mcsADP.py +++ b/lsl/common/mcsADP.py @@ -55,6 +55,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Dict, Optional + __version__ = '0.5' __all__ = ['ME_SSMIF_FORMAT_VERSION', 'ME_MAX_NSTD', 'ME_MAX_NFEE', 'ME_MAX_FEEID_LENGTH', 'ME_MAX_RACK', 'ME_MAX_PORT', @@ -321,7 +323,7 @@ unsigned int alignment; """ -def parse_c_struct(cStruct, char_mode='str', endianness='native', overrides=None): +def parse_c_struct(cStruct: str, char_mode: str='str', endianness: str='native', overrides: Optional[Dict]=None) -> ctypes.Structure: adp_macros = {a: globals()[a] for a in __all__} if overrides is not None: adp_macros.update(overrides) @@ -340,7 +342,7 @@ def _two_bytes_swap(value): return ((value & 0xFF) << 8) | ((value >> 8) & 0xFF) -def delay_to_mcsd(delay): +def delay_to_mcsd(delay: float) -> int: """ Given a delay in ns, convert it to a course and fine portion and into the form expected by MCS in a custom beamforming SDF (little endian @@ -352,7 +354,7 @@ def delay_to_mcsd(delay): return _two_bytes_swap( adpCommon.delay_to_dpd(delay) ) -def mcsd_to_delay(delay): +def mcsd_to_delay(delay: int) -> float: """ Given delay value from an OBS_BEAM_DELAY field in a custom beamforming SDF, return the delay in ns. @@ -363,7 +365,7 @@ def mcsd_to_delay(delay): return adpCommon.dpd_to_delay( _two_bytes_swap(delay) ) -def gain_to_mcsg(gain): +def gain_to_mcsg(gain: float) -> int: """ Given a gain (between 0 and 1), convert it to a gain in the form expected by MCS in a custom beamforming SDF (little endian 16.1 @@ -375,7 +377,7 @@ def gain_to_mcsg(gain): return _two_bytes_swap( adpCommon.gain_to_dpg(gain) ) -def mcsg_to_gain(gain): +def mcsg_to_gain(gain: int) -> float: """ Given a gain value from an OBS_BEAM_GAIN field in a custom beamforming SDF, return the decimal equivalent. diff --git a/lsl/common/mcsNDP.py b/lsl/common/mcsNDP.py index e54617e1..d8d38d42 100644 --- a/lsl/common/mcsNDP.py +++ b/lsl/common/mcsNDP.py @@ -55,6 +55,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Optional, Dict + __version__ = '0.1' __all__ = ['ME_SSMIF_FORMAT_VERSION', 'ME_MAX_NSTD', 'ME_MAX_NFEE', 'ME_MAX_FEEID_LENGTH', 'ME_MAX_RACK', 'ME_MAX_PORT', @@ -321,7 +323,7 @@ """ -def parse_c_struct(cStruct, char_mode='str', endianness='native', overrides=None): +def parse_c_struct(cStruct: str, char_mode: str='str', endianness: str='native', overrides: Optional[Dict]=None) -> ctypes.Structure: ndp_macros = {a: globals()[a] for a in __all__} if overrides is not None: ndp_macros.update(overrides) @@ -340,7 +342,7 @@ def _two_bytes_swap(value): return ((value & 0xFF) << 8) | ((value >> 8) & 0xFF) -def delay_to_mcsd(delay): +def delay_to_mcsd(delay: float) -> int: """ Given a delay in ns, convert it to a course and fine portion and into the form expected by MCS in a custom beamforming SDF (little endian @@ -352,7 +354,7 @@ def delay_to_mcsd(delay): return _two_bytes_swap( ndpCommon.delay_to_dpd(delay) ) -def mcsd_to_delay(delay): +def mcsd_to_delay(delay: int) -> float: """ Given delay value from an OBS_BEAM_DELAY field in a custom beamforming SDF, return the delay in ns. @@ -363,7 +365,7 @@ def mcsd_to_delay(delay): return ndpCommon.dpd_to_delay( _two_bytes_swap(delay) ) -def gain_to_mcsg(gain): +def gain_to_mcsg(gain: float) -> int: """ Given a gain (between 0 and 1), convert it to a gain in the form expected by MCS in a custom beamforming SDF (little endian 16.1 @@ -375,7 +377,7 @@ def gain_to_mcsg(gain): return _two_bytes_swap( ndpCommon.gain_to_dpg(gain) ) -def mcsg_to_gain(gain): +def mcsg_to_gain(gain: int) -> float: """ Given a gain value from an OBS_BEAM_GAIN field in a custom beamforming SDF, return the decimal equivalent. diff --git a/lsl/common/ndp.py b/lsl/common/ndp.py index 8700cf23..e8a74822 100644 --- a/lsl/common/ndp.py +++ b/lsl/common/ndp.py @@ -20,6 +20,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Callable + __version__ = '0.1' __all__ = ['fS', 'fC', 'T', 'T2', 'N_MAX', @@ -59,7 +61,7 @@ _N_PTS = 1000 # Number of points to use in calculating the bandpasses -def freq_to_word(freq): +def freq_to_word(freq: float) -> int: """ Given a frequency in Hz, convert it to the closest DP tuning word. """ @@ -67,7 +69,7 @@ def freq_to_word(freq): return int(round(freq*2**32 / fS)) -def word_to_freq(word): +def word_to_freq(word: int) -> float: """ Given a DP tuning word, convert it to a frequncy in Hz. """ @@ -75,7 +77,7 @@ def word_to_freq(word): return word*fS / 2**32 -def delay_to_dpd(delay): +def delay_to_dpd(delay: float) -> int: """ Given a delay in ns, convert it to a course and fine portion and into the final format expected by NDP (big endian 16.12 unsigned integer) @@ -96,7 +98,7 @@ def delay_to_dpd(delay): return combined -def dpd_to_delay(combined): +def dpd_to_delay(combined: int) -> float: """ Given a delay value in the final format expect by NDP, return the delay in ns. """ @@ -114,7 +116,7 @@ def dpd_to_delay(combined): return delay -def gain_to_dpg(gain): +def gain_to_dpg(gain: float) -> int: """ Given a gain (between 0 and 1), convert it to a gain in the final form expected by NDP (big endian 16.1 signed integer). @@ -129,7 +131,7 @@ def gain_to_dpg(gain): return combined -def dpg_to_gain(combined): +def dpg_to_gain(combined: int) -> float: """ Given a gain value in the final format expected by NDP, return the gain as a decimal value (0 to 1). @@ -144,7 +146,7 @@ def dpg_to_gain(combined): return gain -def drx_filter(sample_rate=19.6e6, npts=_N_PTS): +def drx_filter(sample_rate: float=19.6e6, npts: int=_N_PTS) -> Callable: """ Return a function that will generate the shape of a DRX filter for a given sample rate. From c5f6e41f1f6d83c7eefae83033fc76d932cca750 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 13:04:33 -0600 Subject: [PATCH 09/18] Updates for new MIB/MIBEntry class structure. --- lsl/common/_metabundle_utils.py | 3 +-- lsl/common/mcs.py | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lsl/common/_metabundle_utils.py b/lsl/common/_metabundle_utils.py index 843fa34c..9c7ba00e 100644 --- a/lsl/common/_metabundle_utils.py +++ b/lsl/common/_metabundle_utils.py @@ -207,8 +207,7 @@ def get_asp_configuration(tarname, which='beginning'): if which == 'end' and mib.name.find('_ASP_end') == -1: continue - aspMIB = MIB() - aspMIB.from_file(os.path.join(tempDir, mib.name)) + aspMIB = MIB.from_file(os.path.join(tempDir, mib.name)) break # Extract the configuration diff --git a/lsl/common/mcs.py b/lsl/common/mcs.py index 598edf68..03c47586 100644 --- a/lsl/common/mcs.py +++ b/lsl/common/mcs.py @@ -948,24 +948,21 @@ def from_file(klass, filename: str, init_filename: Optional[str]=None): filename = filename.replace('.dir', '.pag') # Open the database - db = dbm.open(filename, 'ru') - - # Go! - entry = db.firstkey() # type: ignore - while entry is not None: - value = db[entry] - - try: - record = MIBEntry() - record.from_entry(value) - mib.entries[record.index] = record + with dbm.open(filename, 'ru') as db: + # Go! + entry = db.firstkey() # type: ignore + while entry is not None: + value = db[entry] - except ValueError: - pass + try: + record = MIBEntry.from_entry(value) + mib.entries[record.index] = record + + except ValueError: + pass + + entry = db.nextkey(entry) # type: ignore - entry = db.nextkey(entry) # type: ignore - db.close() - # Done return mib @@ -1143,7 +1140,7 @@ def from_entry(klass, value: bytes): mibe = klass() mibe.eType = int(record.eType) mibe.index = index - mibe.value = klass._parse_value(record.val, dbmType) + mibe.value = mibe._parse_value(record.val, dbmType) mibe.dbmType = dbmType mibe.icdType = icdType mibe._tv = (int(record.tv[0]), int(record.tv[1])) From 5f140216f72030c73a9e41e9f01232630fdd7e9e Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 13:33:00 -0600 Subject: [PATCH 10/18] Move the CIC/FIR filter coefficients out of lsl.common.dp/adp/ndp and into .npz files. Also, add a test suite for lsl.common.ndp. --- lsl/common/adp.py | 33 ++-- lsl/common/dp.py | 283 +++++--------------------------- lsl/common/ndp.py | 24 +-- lsl/data/digital/adp_coeffs.npz | Bin 0 -> 954 bytes lsl/data/digital/dp_coeffs.npz | Bin 0 -> 26174 bytes lsl/data/digital/ndp_coeffs.npz | Bin 0 -> 532 bytes tests/test_ndp.py | 46 ++++++ 7 files changed, 115 insertions(+), 271 deletions(-) create mode 100644 lsl/data/digital/adp_coeffs.npz create mode 100644 lsl/data/digital/dp_coeffs.npz create mode 100644 lsl/data/digital/ndp_coeffs.npz create mode 100644 tests/test_ndp.py diff --git a/lsl/common/adp.py b/lsl/common/adp.py index c9e709ed..9f709403 100644 --- a/lsl/common/adp.py +++ b/lsl/common/adp.py @@ -19,6 +19,8 @@ from scipy.signal import freqz from scipy.interpolate import interp1d +from lsl.common.data_access import DataAccess + from lsl.misc import telemetry telemetry.track_module() @@ -56,22 +58,21 @@ #: Maximum number of beams DRX_BEAMS_MAX = 3 -# FIR Filters -## TBN -_TBN_FIR = [-3.22350e-05, -0.00021433, 0.0017756, -0.0044913, 0.0040327, - 0.00735870, -0.03218100, 0.0553980, -0.0398360, -0.0748920, - 0.58308000, 0.58308000, -0.0748920, -0.0398360, 0.0553980, - -0.03218100, 0.00735870, 0.0040327, -0.0044913, 0.0017756, - -0.00021433, -3.2235e-05] -## DRX -_DRX_FIR = [ 0.0111580, -0.0074330, 0.0085684, -0.0085984, 0.0070656, -0.0035905, - -0.0020837, 0.0099858, -0.0199800, 0.0316360, -0.0443470, 0.0573270, - -0.0696630, 0.0804420, -0.0888320, 0.0941650, 0.9040000, 0.0941650, - -0.0888320, 0.0804420, -0.0696630, 0.0573270, -0.0443470, 0.0316360, - -0.0199800, 0.0099858, -0.0020837, -0.0035905, 0.0070656, -0.0085984, - 0.0085684, -0.0074330, 0.0111580] - - +with DataAccess.open('digital/adp_coeffs.npz', 'rb') as fh: + dataDict = np.load(fh) + + # FIR Filters + ## TBN + _TBN_FIR = dataDict['TBN_FIR'][...] + + ## DRX + _DRX_FIR = dataDict['DRX_FIR'][...] + + try: + dataDict.close() + except AttributeError: + pass + _N_PTS = 1000 # Number of points to use in calculating the bandpasses diff --git a/lsl/common/dp.py b/lsl/common/dp.py index 2fa56baf..db3068d8 100644 --- a/lsl/common/dp.py +++ b/lsl/common/dp.py @@ -17,6 +17,7 @@ from scipy.interpolate import interp1d from lsl.common import _fir +from lsl.common.data_access import DataAccess from lsl.misc import telemetry telemetry.track_module() @@ -52,251 +53,43 @@ #: Maximum number of beams DRX_BEAMS_MAX = 4 -# CIC Filters -## TBN CIC filter #7 with order 2, decimation by 98 -_TBN_CIC_7 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, - 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, - 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, - 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, - 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, - 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] -## TBN CIC filter #6 with order 2, decimation by 196 -_TBN_CIC_6 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, - 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, - 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 195, 194, 193, - 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 174, 173, - 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, 154, 153, - 152, 151, 150, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, - 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, - 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, - 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, - 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, - 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, - 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] -## TBN CIC filter #5 with order 2, decimation by 392 -_TBN_CIC_5 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, - 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, - 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, - 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, - 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, - 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, - 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, - 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, - 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, - 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, - 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, - 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, - 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, - 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, - 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, - 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, - 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 391, 390, 389, 388, 387, 386, 385, 384, - 383, 382, 381, 380, 379, 378, 377, 376, 375, 374, 373, 372, 371, 370, 369, 368, 367, 366, 365, 364, 363, - 362, 361, 360, 359, 358, 357, 356, 355, 354, 353, 352, 351, 350, 349, 348, 347, 346, 345, 344, 343, 342, - 341, 340, 339, 338, 337, 336, 335, 334, 333, 332, 331, 330, 329, 328, 327, 326, 325, 324, 323, 322, 321, - 320, 319, 318, 317, 316, 315, 314, 313, 312, 311, 310, 309, 308, 307, 306, 305, 304, 303, 302, 301, 300, - 299, 298, 297, 296, 295, 294, 293, 292, 291, 290, 289, 288, 287, 286, 285, 284, 283, 282, 281, 280, 279, - 278, 277, 276, 275, 274, 273, 272, 271, 270, 269, 268, 267, 266, 265, 264, 263, 262, 261, 260, 259, 258, - 257, 256, 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240, 239, 238, 237, - 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, - 215, 214, 213, 212, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 195, - 194, 193, 192, 191, 190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 174, - 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, 154, 153, - 152, 151, 150, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, - 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, - 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, - 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, - 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, - 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, - 4, 3, 2, 1] - -## DRX CIC filter #7 with order 5, decimation by 5 -_DRX_CIC_7 = [1, 5, 15, 35, 70, 121, 185, 255, 320, 365, 381, 365, 320, 255, 185, 121, 70, 35, 15, 5, 1] -## DRX CIC filter #6 with order 5, decimation by 10 -_DRX_CIC_6 = [1, 5, 15, 35, 70, 126, 210, 330, 495, 715, 996, 1340, 1745, 2205, 2710, 3246, 3795, 4335, 4840, 5280, - 5631, 5875, 6000, 6000, 5875, 5631, 5280, 4840, 4335, 3795, 3246, 2710, 2205, 1745, 1340, 996, 715, - 495, 330, 210, 126, 70, 35, 15, 5, 1] -## DRX CIC filter #5 with order 5, decimation by 20 -_DRX_CIC_5 = [1, 5, 15, 35, 70, 126, 210, 330, 495, 715, 1001, 1365, 1820, 2380, 3060, 3876, 4845, 5985, 7315, 8855, - 10621, 12625, 14875, 17375, 20125, 23121, 26355, 29815, 33485, 37345, 41371, 45535, 49805, 54145, - 58515, 62871, 67165, 71345, 75355, 79135, 82631, 85795, 88585, 90965, 92905, 94381, 95375, 95875, - 95875, 95375, 94381, 92905, 90965, 88585, 85795, 82631, 79135, 75355, 71345, 67165, 62871, 58515, - 54145, 49805, 45535, 41371, 37345, 33485, 29815, 26355, 23121, 20125, 17375, 14875, 12625, 10621, - 8855, 7315, 5985, 4845, 3876, 3060, 2380, 1820, 1365, 1001, 715, 495, 330, 210, 126, 70, 35, 15, 5, 1] -## DRX CIC filter #4 with order 5, decimation by 49 -_DRX_CIC_4 = [1, 5, 15, 35, 70, 126, 210, 330, 495, 715, 1001, 1365, 1820, 2380, 3060, 3876, 4845, 5985, 7315, 8855, - 10626, 12650, 14950, 17550, 20475, 23751, 27405, 31465, 35960, 40920, 46376, 52360, 58905, 66045, 73815, - 82251, 91390, 101270, 111930, 123410, 135751, 148995, 163185, 178365, 194580, 211876, 230300, 249900, - 270725, 292820, 316226, 340980, 367115, 394660, 423640, 454076, 485985, 519380, 554270, 590660, 628551, - 667940, 708820, 751180, 795005, 840276, 886970, 935060, 984515, 1035300, 1087376, 1140700, 1195225, - 1250900, 1307670, 1365476, 1424255, 1483940, 1544460, 1605740, 1667701, 1730260, 1793330, 1856820, - 1920635, 1984676, 2048840, 2113020, 2177105, 2240980, 2304526, 2367620, 2430135, 2491940, 2552900, - 2612876, 2671725, 2729300, 2785460, 2840070, 2893001, 2944130, 2993340, 3040520, 3085565, 3128376, - 3168860, 3206930, 3242505, 3275510, 3305876, 3333540, 3358445, 3380540, 3399780, 3416126, 3429545, - 3440010, 3447500, 3452000, 3453501, 3452000, 3447500, 3440010, 3429545, 3416126, 3399780, 3380540, - 3358445, 3333540, 3305876, 3275510, 3242505, 3206930, 3168860, 3128376, 3085565, 3040520, 2993340, - 2944130, 2893001, 2840070, 2785460, 2729300, 2671725, 2612876, 2552900, 2491940, 2430135, 2367620, - 2304526, 2240980, 2177105, 2113020, 2048840, 1984676, 1920635, 1856820, 1793330, 1730260, 1667701, - 1605740, 1544460, 1483940, 1424255, 1365476, 1307670, 1250900, 1195225, 1140700, 1087376, 1035300, - 984515, 935060, 886970, 840276, 795005, 751180, 708820, 667940, 628551, 590660, 554270, 519380, 485985, - 454076, 423640, 394660, 367115, 340980, 316226, 292820, 270725, 249900, 230300, 211876, 194580, 178365, - 163185, 148995, 135751, 123410, 111930, 101270, 91390, 82251, 73815, 66045, 58905, 52360, 46376, 40920, - 35960, 31465, 27405, 23751, 20475, 17550, 14950, 12650, 10626, 8855, 7315, 5985, 4845, 3876, 3060, 2380, - 1820, 1365, 1001, 715, 495, 330, 210, 126, 70, 35, 15, 5, 1] -## DRX CIC filter #3 with order 5, decimation by 98 -_DRX_CIC_3 = [1, 5, 15, 35, 70, 126, 210, 330, 495, 715, 1001, 1365, 1820, 2380, 3060, 3876, 4845, 5985, 7315, 8855, - 10626, 12650, 14950, 17550, 20475, 23751, 27405, 31465, 35960, 40920, 46376, 52360, 58905, 66045, 73815, - 82251, 91390, 101270, 111930, 123410, 135751, 148995, 163185, 178365, 194580, 211876, 230300, 249900, - 270725, 292825, 316251, 341055, 367290, 395010, 424270, 455126, 487635, 521855, 557845, 595665, 635376, - 677040, 720720, 766480, 814385, 864501, 916895, 971635, 1028790, 1088430, 1150626, 1215450, 1282975, - 1353275, 1426425, 1502501, 1581580, 1663740, 1749060, 1837620, 1929501, 2024785, 2123555, 2225895, - 2331890, 2441626, 2555190, 2672670, 2794155, 2919735, 3049501, 3183545, 3321960, 3464840, 3612280, - 3764376, 3921225, 4082925, 4249570, 4421250, 4598051, 4780055, 4967340, 5159980, 5358045, 5561601, - 5770710, 5985430, 6205815, 6431915, 6663776, 6901440, 7144945, 7394325, 7649610, 7910826, 8177995, - 8451135, 8730260, 9015380, 9306501, 9603625, 9906750, 10215870, 10530975, 10852051, 11179080, 11512040, - 11850905, 12195645, 12546226, 12902610, 13264755, 13632615, 14006140, 14385276, 14769965, 15160145, - 15555750, 15956710, 16362951, 16774395, 17190960, 17612560, 18039105, 18470501, 18906650, 19347450, - 19792795, 20242575, 20696676, 21154980, 21617365, 22083705, 22553870, 23027726, 23505135, 23985955, - 24470040, 24957240, 25447401, 25940365, 26435970, 26934050, 27434435, 27936951, 28441420, 28947660, - 29455485, 29964705, 30475126, 30986550, 31498775, 32011595, 32524800, 33038176, 33551505, 34064565, - 34577130, 35088970, 35599851, 36109535, 36617780, 37124340, 37628965, 38131401, 38631390, 39128670, - 39622975, 40114035, 40601576, 41085320, 41564985, 42040285, 42510930, 42976626, 43437085, 43892025, - 44341170, 44784250, 45221001, 45651165, 46074490, 46490730, 46899645, 47301001, 47694570, 48080130, - 48457465, 48826365, 49186626, 49538050, 49880445, 50213625, 50537410, 50851626, 51156105, 51450685, - 51735210, 52009530, 52273501, 52526985, 52769850, 53001970, 53223225, 53433501, 53632690, 53820690, - 53997405, 54162745, 54316626, 54458970, 54589705, 54708765, 54816090, 54911626, 54995325, 55067145, - 55127050, 55175010, 55211001, 55235005, 55247010, 55247010, 55235005, 55211001, 55175010, 55127050, - 55067145, 54995325, 54911626, 54816090, 54708765, 54589705, 54458970, 54316626, 54162745, 53997405, - 53820690, 53632690, 53433501, 53223225, 53001970, 52769850, 52526985, 52273501, 52009530, 51735210, - 51450685, 51156105, 50851626, 50537410, 50213625, 49880445, 49538050, 49186626, 48826365, 48457465, - 48080130, 47694570, 47301001, 46899645, 46490730, 46074490, 45651165, 45221001, 44784250, 44341170, - 43892025, 43437085, 42976626, 42510930, 42040285, 41564985, 41085320, 40601576, 40114035, 39622975, - 39128670, 38631390, 38131401, 37628965, 37124340, 36617780, 36109535, 35599851, 35088970, 34577130, - 34064565, 33551505, 33038176, 32524800, 32011595, 31498775, 30986550, 30475126, 29964705, 29455485, - 28947660, 28441420, 27936951, 27434435, 26934050, 26435970, 25940365, 25447401, 24957240, 24470040, - 23985955, 23505135, 23027726, 22553870, 22083705, 21617365, 21154980, 20696676, 20242575, 19792795, - 19347450, 18906650, 18470501, 18039105, 17612560, 17190960, 16774395, 16362951, 15956710, 15555750, - 15160145, 14769965, 14385276, 14006140, 13632615, 13264755, 12902610, 12546226, 12195645, 11850905, - 11512040, 11179080, 10852051, 10530975, 10215870, 9906750, 9603625, 9306501, 9015380, 8730260, 8451135, - 8177995, 7910826, 7649610, 7394325, 7144945, 6901440, 6663776, 6431915, 6205815, 5985430, 5770710, - 5561601, 5358045, 5159980, 4967340, 4780055, 4598051, 4421250, 4249570, 4082925, 3921225, 3764376, - 3612280, 3464840, 3321960, 3183545, 3049501, 2919735, 2794155, 2672670, 2555190, 2441626, 2331890, - 2225895, 2123555, 2024785, 1929501, 1837620, 1749060, 1663740, 1581580, 1502501, 1426425, 1353275, - 1282975, 1215450, 1150626, 1088430, 1028790, 971635, 916895, 864501, 814385, 766480, 720720, 677040, - 635376, 595665, 557845, 521855, 487635, 455126, 424270, 395010, 367290, 341055, 316251, 292825, 270725, - 249900, 230300, 211876, 194580, 178365, 163185, 148995, 135751, 123410, 111930, 101270, 91390, 82251, - 73815, 66045, 58905, 52360, 46376, 40920, 35960, 31465, 27405, 23751, 20475, 17550, 14950, 12650, - 10626, 8855, 7315, 5985, 4845, 3876, 3060, 2380, 1820, 1365, 1001, 715, 495, 330, 210, 126, 70, 35, - 15, 5, 1] - -# FIR Filters -## Default beamformer delay FIR filters -_DELAY_FIRS = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32767, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [-15, 16, -41, 45, -89, 99, -168, 196, -308, 385, -605, 904, -1896, 32515, 2148, -1051, 630, -477, 316, -267, 171, -152, 90, -82, 42, -39, 15, -15], - [-30, 32, -81, 88, -173, 193, -327, 381, -597, 744, -1163, 1719, -3513, 31851, 4515, -2128, 1263, -949, 627, -528, 339, -301, 177, -162, 83, -77, 30, -29], - [-43, 46, -117, 127, -249, 278, -472, 547, -857, 1063, -1656, 2422, -4833, 30791, 7060, -3191, 1875, -1398, 922, -773, 496, -439, 259, -237, 120, -113, 44, -42], - [-55, 58, -148, 161, -315, 351, -595, 689, -1079, 1332, -2069, 2995, -5845, 29362, 9737, -4202, 2441, -1806, 1189, -993, 637, -563, 331, -303, 154, -144, 56, -53], - [-64, 68, -174, 188, -369, 410, -695, 801, -1254, 1543, -2388, 3424, -6549, 27594, 12494, -5118, 2937, -2157, 1416, -1179, 756, -666, 392, -357, 182, -170, 66, -63], - [-71, 75, -192, 208, -407, 452, -766, 881, -1379, 1689, -2606, 3701, -6950, 25528, 15277, -5900, 3342, -2436, 1595, -1323, 848, -745, 439, -399, 203, -189, 74, -70], - [-75, 79, -203, 220, -430, 476, -808, 926, -1448, 1766, -2719, 3826, -7062, 23211, 18030, -6508, 3636, -2628, 1717, -1419, 908, -796, 469, -426, 217, -202, 79, -75], - [-76, 81, -206, 223, -436, 482, -818, 935, -1461, 1775, -2725, 3801, -6906, 20693, 20693, -6906, 3801, -2725, 1775, -1461, 935, -818, 482, -436, 223, -206, 81, -76], - [-75, 79, -202, 217, -426, 469, -796, 908, -1419, 1717, -2628, 3636, -6508, 18030, 23211, -7062, 3826, -2719, 1766, -1448, 926, -808, 476, -430, 220, -203, 79, -75], - [-70, 74, -189, 203, -399, 439, -745, 848, -1323, 1595, -2436, 3342, -5900, 15277, 25528, -6950, 3701, -2606, 1689, -1379, 881, -766, 452, -407, 208, -192, 75, -71], - [-63, 66, -170, 182, -357, 392, -666, 756, -1179, 1416, -2157, 2937, -5118, 12494, 27594, -6549, 3424, -2388, 1543, -1254, 801, -695, 410, -369, 188, -174, 68, -64], - [-53, 56, -144, 154, -303, 331, -563, 637, -993, 1189, -1806, 2441, -4202, 9737, 29362, -5845, 2995, -2069, 1332, -1079, 689, -595, 351, -315, 161, -148, 58, -55], - [-42, 44, -113, 120, -237, 259, -439, 496, -773, 922, -1398, 1875, -3191, 7060, 30791, -4833, 2422, -1656, 1063, -857, 547, -472, 278, -249, 127, -117, 46, -43], - [-29, 30, -77, 83, -162, 177, -301, 339, -528, 627, -949, 1263, -2128, 4515, 31851, -3513, 1719, -1163, 744, -597, 381, -327, 193, -173, 88, -81, 32, -30], - [-15, 15, -39, 42, -82, 90, -152, 171, -267, 316, -477, 630, -1051, 2148, 32515, -1896, 904, -605, 385, -308, 196, -168, 99, -89, 45, -41, 16, -15]] - - -## TBN FIR filter with decimation of 20 -_TBN_FIR = [-2.7370000000000000e+003, 5.3100000000000000e+002, 5.1600000000000000e+002, - 5.2100000000000000e+002, 5.4300000000000000e+002, 5.7900000000000000e+002, - 6.2500000000000000e+002, 6.7900000000000000e+002, 7.3800000000000000e+002, - 7.9800000000000000e+002, 8.5800000000000000e+002, 9.1500000000000000e+002, - 9.6600000000000000e+002, 1.0090000000000000e+003, 1.0420000000000000e+003, - 1.0620000000000000e+003, 1.0680000000000000e+003, 1.0570000000000000e+003, - 1.0280000000000000e+003, 9.8000000000000000e+002, 9.1100000000000000e+002, - 8.2000000000000000e+002, 7.1100000000000000e+002, 5.7400000000000000e+002, - 4.2100000000000000e+002, 2.4700000000000000e+002, 5.1000000000000000e+001, - -1.6300000000000000e+002, -3.9200000000000000e+002, -6.3400000000000000e+002, - -8.8600000000000000e+002, -1.1450000000000000e+003, -1.4080000000000000e+003, - -1.6710000000000000e+003, -1.9300000000000000e+003, -2.1790000000000000e+003, - -2.4140000000000000e+003, -2.6290000000000000e+003, -2.8200000000000000e+003, - -2.9820000000000000e+003, -3.1100000000000000e+003, -3.1990000000000000e+003, - -3.2440000000000000e+003, -3.2410000000000000e+003, -3.1860000000000000e+003, - -3.0730000000000000e+003, -2.9030000000000000e+003, -2.6710000000000000e+003, - -2.3740000000000000e+003, -2.0130000000000000e+003, -1.5850000000000000e+003, - -1.0920000000000000e+003, -5.3200000000000000e+002, 9.0000000000000000e+001, - 7.7500000000000000e+002, 1.5170000000000000e+003, 2.3140000000000000e+003, - 3.1600000000000000e+003, 4.0510000000000000e+003, 4.9800000000000000e+003, - 5.9410000000000000e+003, 6.9270000000000000e+003, 7.9290000000000000e+003, - 8.9400000000000000e+003, 9.9520000000000000e+003, 1.0955000000000000e+004, - 1.1943000000000000e+004, 1.2904000000000000e+004, 1.3831000000000000e+004, - 1.4715000000000000e+004, 1.5549000000000000e+004, 1.6323000000000000e+004, - 1.7032000000000000e+004, 1.7668000000000000e+004, 1.8225000000000000e+004, - 1.8698000000000000e+004, 1.9082000000000000e+004, 1.9373000000000000e+004, - 1.9569000000000000e+004, 1.9667000000000000e+004, 1.9667000000000000e+004, - 1.9569000000000000e+004, 1.9373000000000000e+004, 1.9082000000000000e+004, - 1.8698000000000000e+004, 1.8225000000000000e+004, 1.7668000000000000e+004, - 1.7032000000000000e+004, 1.6323000000000000e+004, 1.5549000000000000e+004, - 1.4715000000000000e+004, 1.3831000000000000e+004, 1.2904000000000000e+004, - 1.1943000000000000e+004, 1.0955000000000000e+004, 9.9520000000000000e+003, - 8.9400000000000000e+003, 7.9290000000000000e+003, 6.9270000000000000e+003, - 5.9410000000000000e+003, 4.9800000000000000e+003, 4.0510000000000000e+003, - 3.1600000000000000e+003, 2.3140000000000000e+003, 1.5170000000000000e+003, - 7.7500000000000000e+002, 9.0000000000000000e+001, -5.3200000000000000e+002, - -1.0920000000000000e+003, -1.5850000000000000e+003, -2.0130000000000000e+003, - -2.3740000000000000e+003, -2.6710000000000000e+003, -2.9030000000000000e+003, - -3.0730000000000000e+003, -3.1860000000000000e+003, -3.2410000000000000e+003, - -3.2440000000000000e+003, -3.1990000000000000e+003, -3.1100000000000000e+003, - -2.9820000000000000e+003, -2.8200000000000000e+003, -2.6290000000000000e+003, - -2.4140000000000000e+003, -2.1790000000000000e+003, -1.9300000000000000e+003, - -1.6710000000000000e+003, -1.4080000000000000e+003, -1.1450000000000000e+003, - -8.8600000000000000e+002, -6.3400000000000000e+002, -3.9200000000000000e+002, - -1.6300000000000000e+002, 5.1000000000000000e+001, 2.4700000000000000e+002, - 4.2100000000000000e+002, 5.7400000000000000e+002, 7.1100000000000000e+002, - 8.2000000000000000e+002, 9.1100000000000000e+002, 9.8000000000000000e+002, - 1.0280000000000000e+003, 1.0570000000000000e+003, 1.0680000000000000e+003, - 1.0620000000000000e+003, 1.0420000000000000e+003, 1.0090000000000000e+003, - 9.6600000000000000e+002, 9.1500000000000000e+002, 8.5800000000000000e+002, - 7.9800000000000000e+002, 7.3800000000000000e+002, 6.7900000000000000e+002, - 6.2500000000000000e+002, 5.7900000000000000e+002, 5.4300000000000000e+002, - 5.2100000000000000e+002, 5.1600000000000000e+002, 5.3100000000000000e+002, - -2.7370000000000000e+003] - -## DRX FIR filter with decimation of 2 -_DRX_FIR = [-6.2000000000000000e+001, 6.6000000000000000e+001, 1.4500000000000000e+002, - 3.4000000000000000e+001, -1.4400000000000000e+002, -5.9000000000000000e+001, - 1.9900000000000000e+002, 1.4500000000000000e+002, -2.2700000000000000e+002, - -2.5700000000000000e+002, 2.3200000000000000e+002, 4.0500000000000000e+002, - -1.9400000000000000e+002, -5.8300000000000000e+002, 9.2000000000000000e+001, - 7.8200000000000000e+002, 9.4000000000000000e+001, -9.9000000000000000e+002, - -3.9700000000000000e+002, 1.1860000000000000e+003, 8.5900000000000000e+002, - -1.3400000000000000e+003, -1.5650000000000000e+003, 1.3960000000000000e+003, - 2.7180000000000000e+003, -1.1870000000000000e+003, -4.9600000000000000e+003, - -1.8900000000000000e+002, 1.1431000000000000e+004, 1.7747000000000000e+004, - 1.1431000000000000e+004, -1.8900000000000000e+002, -4.9600000000000000e+003, - -1.1870000000000000e+003, 2.7180000000000000e+003, 1.3960000000000000e+003, - -1.5650000000000000e+003, -1.3400000000000000e+003, 8.5900000000000000e+002, - 1.1860000000000000e+003, -3.9700000000000000e+002, -9.9000000000000000e+002, - 9.4000000000000000e+001, 7.8200000000000000e+002, 9.2000000000000000e+001, - -5.8300000000000000e+002, -1.9400000000000000e+002, 4.0500000000000000e+002, - 2.3200000000000000e+002, -2.5700000000000000e+002, -2.2700000000000000e+002, - 1.4500000000000000e+002, 1.9900000000000000e+002, -5.9000000000000000e+001, - -1.4400000000000000e+002, 3.4000000000000000e+001, 1.4500000000000000e+002, - 6.6000000000000000e+001, -6.2000000000000000e+001] - - +with DataAccess.open('digital/dp_coeffs.npz', 'rb') as fh: + dataDict = np.load(fh) + + # CIC Filters + ## TBN CIC filter #7 with order 2, decimation by 98 + _TBN_CIC_7 = dataDict['TBN_CIC_7'][...] + ## TBN CIC filter #6 with order 2, decimation by 196 + _TBN_CIC_6 = dataDict['TBN_CIC_6'][...] + ## TBN CIC filter #5 with order 2, decimation by 392 + _TBN_CIC_5 = dataDict['TBN_CIC_5'][...] + + ## DRX CIC filter #7 with order 5, decimation by 5 + _DRX_CIC_7 = dataDict['DRX_CIC_7'][...] + ## DRX CIC filter #6 with order 5, decimation by 10 + _DRX_CIC_6 = dataDict['DRX_CIC_6'][...] + ## DRX CIC filter #5 with order 5, decimation by 20 + _DRX_CIC_5 = dataDict['DRX_CIC_5'][...] + ## DRX CIC filter #4 with order 5, decimation by 49 + _DRX_CIC_4 = dataDict['DRX_CIC_4'][...] + ## DRX CIC filter #3 with order 5, decimation by 98 + _DRX_CIC_3 = dataDict['DRX_CIC_3'][...] + + # FIR Filters + ## Default beamformer delay FIR filters + _DELAY_FIRS = dataDict['DELAY_FIRS'][...] + + ## TBN FIR filter with decimation of 20 + _TBN_FIR = dataDict['TBN_FIR'][...] + + ## DRX FIR filter with decimation of 2 + _DRX_FIR = dataDict['DRX_FIR'][...] + + try: + dataDict.close() + except AttributeError: + pass + _N_PTS = 1000 # Number of points to use in calculating the bandpasses diff --git a/lsl/common/ndp.py b/lsl/common/ndp.py index e8a74822..92474e27 100644 --- a/lsl/common/ndp.py +++ b/lsl/common/ndp.py @@ -17,6 +17,8 @@ from scipy.signal import freqz from scipy.interpolate import interp1d +from lsl.common.data_access import DataAccess + from lsl.misc import telemetry telemetry.track_module() @@ -48,16 +50,18 @@ #: Maximum number of beams DRX_BEAMS_MAX = 4 -# FIR Filters -## DRX -_DRX_FIR = [ 0.0111580, -0.0074330, 0.0085684, -0.0085984, 0.0070656, -0.0035905, - -0.0020837, 0.0099858, -0.0199800, 0.0316360, -0.0443470, 0.0573270, - -0.0696630, 0.0804420, -0.0888320, 0.0941650, 0.9040000, 0.0941650, - -0.0888320, 0.0804420, -0.0696630, 0.0573270, -0.0443470, 0.0316360, - -0.0199800, 0.0099858, -0.0020837, -0.0035905, 0.0070656, -0.0085984, - 0.0085684, -0.0074330, 0.0111580] - - +with DataAccess.open('digital/ndp_coeffs.npz', 'rb') as fh: + dataDict = np.load(fh) + + # FIR Filters + ## DRX + _DRX_FIR = dataDict['DRX_FIR'][...] + + try: + dataDict.close() + except AttributeError: + pass + _N_PTS = 1000 # Number of points to use in calculating the bandpasses diff --git a/lsl/data/digital/adp_coeffs.npz b/lsl/data/digital/adp_coeffs.npz new file mode 100644 index 0000000000000000000000000000000000000000..8431f66708ad9505fa64f06a493712be7d0eac55 GIT binary patch literal 954 zcmWIWW@Zs#fB;1XR@Tlt28;|0Ak57m!Vu!*7w_g7q?cDv$;co876vH=NrS*-zfj+R zNJfS-hHCYc)Z*kKbt?sRn=}h`9R>BY{GyVg#Ju?YqLfsSxLaaQaVk)}I3uwj70A~x zGSbo1QK(fQA8_4PnWWY9jA6gQshD*;#C7(Y-I@41T|Cxa#`CUbMQYK0hQO;Ur?(W? zyK8AJsQYdYoB*$ zuTlQhjr*tfS)RW!eT)6S8P}F=+`Dc6o3`9;Bk3LXiW`bDCcb@R597n+Vd`P}VdlZi zhuH_SALbsI`(W;cxgX{qnEzn@h50|gn~_O`8CRZx#2JWe0LLFv4no(68vn=|bAeis VQbT|@D;r3f2?&dTv=K9i2LL_+F{uCm literal 0 HcmV?d00001 diff --git a/lsl/data/digital/dp_coeffs.npz b/lsl/data/digital/dp_coeffs.npz new file mode 100644 index 0000000000000000000000000000000000000000..2fd0c6468907bedf9d3cd3e0a7fa7e183db67488 GIT binary patch literal 26174 zcmeI*caT+8wt(>i2nY%yA}ES90)mQ2XmYR#5(Jf~2uhM9LCFG&w3r&Rf}l7mAVv&; z0kdFujw9m}6~jF9V44{pj-n$bUf;dGPibmi)zqV=>iuC?F+b0}_nf=W-g~XR_Pq_$ zr&pOhs%7z0CtJT|?XlOF%d*^`N?EmRK<5*Nb?MnTZE6?$sSOF{IUbr__!pgW0 zR>6I-D(;7N>HZv7#~OG59*8yZAUqgr;UQQX^RNycigmFb*2lxJ0XD?L@d!K;8{tvd z7>~v#*c6*#bIivU*b-Y|YixsUu^qO@4%iWo!2;}rov{mc#bdD>cE=vr6OY4Q*c*?> z6YxargMG0d_QwG@5KqFBaS)z@gK-F+il^aF9EQVj1diP0*ynw{=X*V`@B8`ww#W9_ zUfb{ecz@on_iumLKlYdXXMfti_P71-csM?em*eMnI=+s# z{Bu4!Kb^16U+1&)+xhPNHy(@+%FZ8t#WxabK*0`(S0PgnQ#&SP?7Wo>(5sVOiV*%V23Ng?&ma zYmaRD^vy?#-!k`S=Vh&>%UbS<+>h?ovevv!)8Cf0=ufAW)68k-G;~@zP4nWk%xUbj zcA7iw4Fki%FfnWlBg4utYsB>pL&MTAHEa!I!`d)6>`eov1=ECS!!%-AG0m8EOhcw6 z(^LWNGmV+nOmn6^)1Yb5G-=v2jha?Xv%R^#Y1p)Enl^2l#!c&{dDFhjfXjl*gv*A@ z$Y9=Y2%d_k;ZPig!*K+T#M5yUj>a)K7RTWkI36e9M4W_Y;#oKur{GkahSPBd&cw5E z7S6^wcn+S6bMZW!hlO}P&c_SzLc9ns#!IjW7vQCM8D5T8;FWk4UX9n_wYU%$;dQte zm*Dky11`lI@ppI=-i){4GF*-;@K#)jx8d!02i}Qy;oZ0j@4v`Vmd41o{_qRQ^&-U7W@5lS| ze!YMD!~U_q>_7X{{il&+JHMUp&VS><_%L3KALGgRGTw|o}8Q;gNU*9*zyM0Un0+u^!gNL$MC#VQoAF zYvI9o5Z1&4@c^uW)p38UhWlYv+!w3hK3Ewm;oi6xR>TUpCzi)@SQhudGFTc*VaaXJ z$L~!0>AT`v%Kh1S+tb2rPwt7_kM7pCr&a6bzioS>Kb=-iGpC)?&}r#3&5P51+xE~8 zrZv-?Y0orhS~N|XHcg|ZRnx3#*EDQeHcgwhP2;9@)4XZlWx!>@WnvKRb{TP5ahY-1 zaT#)1a+w;=`7UE4IX)dn;bNpT+0!dHfH20e_D#;veuO+=zd~m+?RG72Jfc;%oRi zZpJt8O?(UA#y{bI;XC*)zK8GQ2lyd=gnz~@_%VKhpW72jGEN6A!|Ju@)YJwJ{It;GtL->tTI73>#oWJRFa}Be4-4 zg^lrOY=TX(88*j!Y=JGY6}HAU*cRJid+dN6@fa+?PS_c{U{^dAyJ2_ifj#j!?1jDY zcsv15#6H*;`(b|^fCKR)JQ)XJ@fYFgXD|-IQ}Hw$io_W5YNZ?cmZCB7vaTt2^Q^g z?DM|f^Sz$e_x*f-+hhA|ukH7Kyg%>P`?o*rAN$Mxvp?-$``i9^JRBd#%kgtO9bd=W z@pnErKb$YlALoHrN_lVM}a*`PdwrVN+~^M`L3= z3LD{(cmy7f4Y2_phV`)?*2P1y4(4HPJOpdu!FUkX!~^jFtbx^Wf2@Z4VO880tKdFZ z87tx5xEEH$3b-eh$8uN}_rNk(8cSh@)+5#@)+^R8)-%>O);rce)GD3 z)?3zJ)??OZ)@#;p)^pZ()_c}})`QlE){EAU)|1wkA5wpPfYzhdr`D_1uhz5Hx7NGX zzt+Rn$JWc%&(_n{*VfzC-`3;S=ho}i@7D9y_tyK?|LzCeAGlv||KNVY{ly0Edp$ml zPvMjJ1U`!__QgJUBA$T9V{hz*$6-(Gf!(ni9*bSE z3wFj%Sb)c1N9=&@u^qO>HrN_lVM}a*`PdwrVN+~^M`L3=3LD{(cmy7f4Y2_phV`)? z*2P1y4(4HPJOpdu!FUkX!~^jFtbx^Wf2@Z4VO880tKdFZ87tx5xEEH$3b-eh$8uN} z_rNk(8cSixuT47cSiU}2QRV*RzBcLFZ&1l!g0|e!-TK<3+3xz4!7Z8 zx_Xq`-hr0CZa3Wac`$Z~MW1`M*tk)omFg->v4HlBcmyrGE zvU>Do6*`Dj?fov%k2kRj_hq&JdQ3ci_VnC-#aI|Fs{it$YF0DLnf1(qcB|cN zw>u0Di^JrwIgAdg!|brnCw#{fzGDdAD=Bt=I5GP31&a4d6XN*uIpM6!!?$h;8$J{k zZ43|mB>agFNlxZNqF#Jx_A?(UF1{?nt?ff$)sMpNRb!be(=D7bH+=Guu-bRw%65_0 zes@9G=#}uII#IQ~es;S*nkBgRL5eEo0XAv0ovbuAP5 zvip*-Uej1+UwQVJp-xhA!9Lx2*$78u(xiXgPy|0NpJ+UyV=^0ZGOxJmROwDAo)1vy$9~gQ3 zoD-rtU)dw7^&VZKdjGLQUV44s)^+0iriUggA5k}XVP3uDx@z^4^~xQV?7A(y{JjS0 zc+ztXlLKyxiS}UO5$U+xNk=AMtk)5S7uV49Y_(jL&>A3xM;iwP7j(Pd%@w*0um(C40E)9p@6Lwfz^4RBn zz2|#9ukZW${V*AjrkMU%D8E?j)@o0P+ug0(O zY_WWF+gna|8`<~#GB`Oy4mzBGTDPtC99Tl25^ z*!*n1Hh-JX&F|)W^S|qX>x1is>xb)!>x=7+>yPV^>yzu1>zC`9>znJH>!0hP>!a&s zw-m)$7Fj9FB8z6fY+_>-$+D7r^X{X!eYLvyrMW*lml4e^BXUpVess6Wi2T;OyAAlQ zjQAwR!)fg_ciI~UhJ|5b*ce8Jm0@Ps8HR?XVQSbK#&1(b455rTCO^uD$0#F8moJwZ zdcy4RjlYMlO^)(m-nZfY7epDcsd|(TU#z?{UM;n6`?FCt)c=CA;m{}-ejFL)Lf6$% zF638?GNIO#C=>dA9A!e?v3cn_pMD$V!B>kS0GD=(a-ii;ho$Ea`F$+^t5(Fa-|C83 z=4%zj^8V<;SjNY#j^(=Oy;yE%9nvbDfBob($)20zL(?yMcTC40y;+c)KeJ1+?%~~% zHNNbTyyvA}$wp7a_Ve8hebaHNcLyZjs5B_Kc)*b4*=vR-SJfMt9Dd*E56w#Udhy)kG2050i&|ck9B}o5+5a=S3a= zeXIDg_{K8vW%E@Z#Fx~gUXCxhS8j~$_vSyvm+Z5@>yt8a<@)huf92uv%K}^P9Frce zyCXg=JYeLEbliGV{L;kdy)I10S8R%X!u$~n)A6A{EKNRp(~9J}9(N~u|F9;x@xe!v zbEj`eE^qT3IC8=aWw^j|F$ZhE3`CFVDQ0EPdO%$?aqQocyW$=gH2O{C7ORX{t*X}Epbaq0NUP2q~HS2|W7_jR7`$Z~MW1 zv7hX>w)CU@YCqfWj)UXkI5}>PqvPs0muB3Z2hI!UiSx#JD?~McF!ZI@Y?@I@h|_I@r3{ zI@!9}I@-G0I@`M2I^4S4I^DY6I^Me8I^VkAeSrG{_X+MB+()>taG&A6!+nVR689BlGWxGG~_cdQAtX%w^Uh$vGxv%1n?cM2=VcmN6>+gO) z_e}0bcWb}jv}L2bX03PUpCpUTUo`38`oBBo{aY{pzh3}`(uxG3|!KH~8_&by!E*mY)Ee~vfs{G+_DzcaCl`}vG^ z&Y<1v;^Ty@`&V&+39;?WZjJ4Jw)!9O_}2KABkT2J98cpqUvl04^xJE^?;6@Mly>#! z_yLY%*Pms7=6EwU=5?2G>~t#6vA@FGkoT!eJMN%e4QOZ4_Bdf?d<>G+{vwXM$M!l~ zbWps}g0JK8{_!zNcIo$Vyp(bOf$RFq9zA(qe`R;Sc=--4n_b0md@7M;Z*cq~w&ngN za=e`5=ecg|UbF08`gINW_afuP$3xj)8J7p*<#K$Gs;;r%c0{Y=A z=J9RxQ*G{#Z^5!Z^1i(o&rP&zDeat0yIXVo636SYE06c%_z8}$@;Y3%C+`!xvn;F0 z{qb>EcF(tQf#LD7R5sz0IR3+gxZul8!qR_>$7j7SHFo1%wV1P)0Q}X zgambOyLh8*pT*;q>4$Uamv&rdGuQ3I`_$ll*U^qCwCg3>-Gz1^$MItvKZGapcngl- z;kX~yd57!1#k{$h_Z>t#KA~MBxWBKszwzAPDiYtB-QvQF7}tDW_XOuX#C2wH-OG8O zExhk$+Hn%?dYg8(+~q#JuLpTNKDEoTW*ooIdA+#~-x6mp^FDX*zP)J2R@ya$cD_x! z%Y7Ud+}tNFs6OuNJkRTRUEjy|wH>z0cG_<5!~60+9Y6cQezBkIH~Z0kwV&;G$H8%N zoE$gD(Q$R09e3w}^TK)Jym21=GOrki7XQomIZvIp&SU4b^W1rF92ghIiE(2b8CS-c zac3MFm&U1aYaAQb#<_8C9xyMMC(Ik>5%Y?9#=K)5GB25@%v?p*B#d(*Cp2}*DcpE z*EQEU*FD!k*G1P!*G<<^*Hzb9*In0P*X8BRPuFeNao6=Xn76L`)&bT9)(O@P))Cef z)*03v)*;p<)+yF4)-l#K);ZQa)+nFjk({=+?RElb(?jZb=^>2 zXWeHVXkBQXXx(TXXBj8SXpWhqy0!j`wljayrNEYux9! z@2SG`-dD-5tCxOo)cC^U3Muzzuj2h{?%&fc-o5^^d8K&DJ)Qgc=bP6t|Kh)<-LlDU z{)%(O)QFL{txsNGSdh%a9{3#|Fh8Czx*%+QQCQ`Y@XMm`l}p3xFAtks85Ug?_PCno zaW!6uRd5;S&Eh)C7Q}h2FAl#vKkPFn{NT*6uxB`Y)cP#ve=&~Byc9mUF&z4G_~CyhvlXv|oi-&4Him_rUyawT2n+s-2fvmcU!Pyn0v`8u zg`DSgyl%$(_`Zd-qtJHQPTHM$AKaJsnQ`Bl{b0Y?PxhPrXusOe_PgWYxHwLZo8#!X zI?j%}^T2uGJaOJQkDOP|v);@n=b`h`dFs4%9y_m{=gxcMz_>6@jGJHLh`2J&j637d zxHL|GiCf~>xHis>`|F8!^MZN8ykQb z8&o>Wa({BkJNNg1c1qs4hjTx=Tgkgco88Rad6B%c6W6Ck74|7e=8X)qPB?OXmis9@ zKKx)@LGCc~b;BpFPsddzhuNfpblhilSTHUgZ#_TEPL9VX7lnl!e|cp*{|d>r=-PPx zEf#|>7sd0_Np7!P7tgo9Exi2Ncz*Z;;j*#G!v6n}o?kzJq{4B=^O>*re6Q#AeLvsd z_Sin#Yx}()@6Y@7{_PL@$NsYa>`(jG{4UPb&g;0c=qX4s&ut7)w1~6_5Y)T zhl?pJmHY1!|BoHJo?7zXLErh*H;cF1o|^sw)~=_P{I>*mKK14Oemyn)Wt?44E&2E9 zc0RT2QCZ2qUAOD0CI61t&Zkan{OhUzw816+zR=F6-q_^VQ~!B`OZ;7holhN{|Ldvg zZ!YZG;F7sl~}f>Q)NsHfa{>ItuD(`9&o~iFxt)MJcHuaks>r;#8n`aYkZ6Dv+;X zY^BU8>|uPE zJWM@IKg>Ls`7rxn_QTu*b05sTF!#g!1M?ruzcBv?cr!AIFyo3wNL+!)25`I~MQVUI QD;r3H5eO}T^ii+^0C~f^82|tP literal 0 HcmV?d00001 diff --git a/tests/test_ndp.py b/tests/test_ndp.py new file mode 100644 index 00000000..95efacc2 --- /dev/null +++ b/tests/test_ndp.py @@ -0,0 +1,46 @@ +""" +Unit test for the lsl.common.ndp module. +""" + +import warnings +import unittest +import numpy as np + +from lsl.common import ndp +from lsl.common import stations + + +__version__ = "0.1" +__author__ = "Jayce Dowell" + + +class ndp_bandpass_tests(unittest.TestCase): + """A unittest.TestCase collection of unit tests for the lsl.common.ndp + module function for the bandpass.""" + + def setUp(self): + """Turn off all numpy and python warnings.""" + + np.seterr(all='ignore') + warnings.simplefilter('ignore') + + def test_drx_bandpass(self): + """Test that the DRX bandpass generator actually runs.""" + + fnc = ndp.drx_filter(sample_rate=19.6e6, npts=256) + junk = fnc(1e3) + + +class ndp_test_suite(unittest.TestSuite): + """A unittest.TestSuite class which contains all of the lsl.common.ndp + module unit tests.""" + + def __init__(self): + unittest.TestSuite.__init__(self) + + loader = unittest.TestLoader() + self.addTests(loader.loadTestsFromTestCase(ndp_bandpass_tests)) + + +if __name__ == '__main__': + unittest.main() From 838da196db222b415523c2c8280e274158596926 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 19:18:37 -0600 Subject: [PATCH 11/18] Type hints and fixes a couple of bugs. --- lsl/astro.py | 743 ++++++++++++++++++++++++++------------------------- 1 file changed, 376 insertions(+), 367 deletions(-) diff --git a/lsl/astro.py b/lsl/astro.py index de136617..4e9826cc 100644 --- a/lsl/astro.py +++ b/lsl/astro.py @@ -23,6 +23,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Optional, Tuple, Union + __version__ = '0.6' __all__ = ['dms', 'hms', 'date', 'zonedate', 'rst_time', 'hrz_posn', 'equ_posn', @@ -67,7 +69,7 @@ class dms(object): seconds - Angle seconds (float). """ - def __init__(self, neg = False, degrees = 0, minutes = 0, seconds = 0.0): + def __init__(self, neg: bool=False, degrees: int=0, minutes: int=0, seconds: float=0.0): """ Create a dms object. @@ -159,7 +161,7 @@ def __eq__(self, other): def __lt__(self, other): return True if self.__cmp__(other) < 0 else False - def to_deg(self): + def to_deg(self) -> float: """ Convert angles degrees, minutes, seconds to float degrees. Returns angle in degrees (float). @@ -167,7 +169,7 @@ def to_deg(self): return dms_to_deg(self) - def to_rad(self): + def to_rad(self) -> float: """ Convert angles degrees, minutes, seconds to float radians. Returns angle in radians (float). @@ -175,7 +177,7 @@ def to_rad(self): return dms_to_rad(self) - def to_hms(self): + def to_hms(self) -> "hms": """ Convert angles degrees, minutes seconds to hours, minutes, seconds. Returns: object of type hms representing angle. @@ -195,7 +197,7 @@ class hms(object): seconds - Angle/time seconds (float). """ - def __init__(self, hours = 0, minutes = 0, seconds = 0.0): + def __init__(self, hours: int=0, minutes: int=0, seconds: float=0.0): """ Create a hms object. @@ -272,7 +274,7 @@ def __eq__(self, other): def __lt__(self, other): return True if self.__cmp__(other) < 0 else False - def to_deg(self): + def to_deg(self) -> float: """ Convert angles hours, minutes, seconds to float degrees. Returns angle in degrees (float). @@ -280,7 +282,7 @@ def to_deg(self): return hms_to_deg(self) - def to_rad(self): + def to_rad(self) -> float: """ Convert angles hours, minutes, second to float radians. Returns angle in radians (float). @@ -288,7 +290,7 @@ def to_rad(self): return hms_to_rad(self) - def to_dms(self): + def to_dms(self) -> dms: """ Convert angle hours, minutes, seconds to degrees, minutes, seconds. Returns: object of type dms representing angle. @@ -296,7 +298,7 @@ def to_dms(self): return deg_to_dms(self.to_deg()) - def to_sec(self): + def to_sec(self) -> float: """ Convert angle hours, minutes, seconds to seconds. Returns: time/angle as seconds. @@ -319,7 +321,7 @@ class date(object): seconds - Date seconds (float). """ - def __init__(self, years = 2000, months = 1, days = 1, hours = 0, minutes = 0, seconds = 0.0): + def __init__(self, years: int=2000, months: int=1, days: int=1, hours: int=0, minutes: int=0, seconds: float=0.0): """ Create a date object. @@ -401,7 +403,7 @@ def __eq__(self, other): def __lt__(self, other): return True if self.__cmp__(other) < 0 else False - def to_zone(self, gmtoff = None): + def to_zone(self, gmtoff: Optional[int]= None) -> "zonedate": """ Convert UTC calendar time to local calendar time. @@ -417,7 +419,7 @@ def to_zone(self, gmtoff = None): return date_to_zonedate(self, gmtoff) - def to_jd(self): + def to_jd(self) -> float: """ Convert calendar time to Julian day. Returns UTC time in Julian days (float). @@ -425,7 +427,7 @@ def to_jd(self): return get_julian_day(self) - def load(self, dateStr, timeStr): + def load(self, dateStr: str, timeStr: str): """ Load date object from formatted strings. @@ -483,7 +485,7 @@ def load(self, dateStr, timeStr): except ValueError: raise ValueError(f"seconds incorrectly formated: {secondStr}") if second < 0.0 or second >= 60.0: - raise ValueError(f"seconds paramerer range is [0.0, 60.0), is set to {seconds:0.3f}") + raise ValueError(f"seconds paramerer range is [0.0, 60.0), is set to {second:0.3f}") # set attributes self.years = year @@ -509,7 +511,7 @@ class zonedate(object): gmtoff - Seconds offset from GM (integer). """ - def __init__(self, years = 2000, months = 1, days = 1, hours = 0, minutes = 0, seconds = 0.0, gmtoff = 0): + def __init__(self, years: int=2000, months: int=1, days: int=1, hours: int=0, minutes: int=0, seconds: float=0.0, gmtoff: int=0): """ Create a zonedate object. @@ -596,7 +598,7 @@ def __eq__(self, other): def __lt__(self, other): return True if self.__cmp__(other) < 0 else False - def to_date(self): + def to_date(self) -> date: """ Convert local calendar time to UTC calendar time. Returns object of type date representing UTC time. @@ -604,7 +606,7 @@ def to_date(self): return zonedate_to_date(self) - def to_jd(self): + def to_jd(self) -> float: """ Convert calendar time to Julian day. Returns UTC time in Julian days (float). @@ -623,7 +625,7 @@ class rst_time(object): transit - Transit time in UTC Julian days (float). """ - def __init__(self, rise = None, set = None, transit = None): + def __init__(self, rise: Optional[Union[date,float]]=None, set: Optional[Union[date,float]]=None, transit: Optional[Union[date,float]]=None): """ Create a rst_time object. @@ -671,7 +673,7 @@ def __reduce__(self): return (rst_time, (self.rise, self.set, self.transit)) - def format(self): + def format(self) -> Tuple[date,date,date]: """ Return a tuple (rise, set, transit) where all three are date objects representing the ephemeris times. @@ -698,7 +700,7 @@ class hrz_posn(object): _astropy = None _distance = None - def __init__(self, az = 0.0, alt = 0.0): + def __init__(self, az: float=0.0, alt: float=0.0): """ Create a hrz_posn object. @@ -713,7 +715,7 @@ def __init__(self, az = 0.0, alt = 0.0): self.alt = alt @classmethod - def from_astropy(kls, value): + def from_astropy(kls, value: SkyCoord): if not isinstance(value, SkyCoord): raise TypeError("Expected an object of type SkyCoord") @@ -731,7 +733,7 @@ def from_astropy(kls, value): return _posn @property - def az(self): + def az(self) -> float: """ Azimiuth in degrees. """ @@ -739,7 +741,7 @@ def az(self): return self._az @az.setter - def az(self, value): + def az(self, value: float): if self.astropy is not None: raise RuntimeError("Cannot set az when this.astropy is not None") @@ -748,7 +750,7 @@ def az(self, value): self._az = value @property - def alt(self): + def alt(self) -> float: """ Altitude in degrees. """ @@ -756,7 +758,7 @@ def alt(self): return self._alt @alt.setter - def alt(self, value): + def alt(self, value: float): if self.astropy is not None: raise RuntimeError("Cannot set alt when this.astropy is not None") @@ -765,7 +767,7 @@ def alt(self, value): self._alt = value @property - def distance(self): + def distance(self) -> Optional[float]: """ Distance in pc or None is the distance is unknown. """ @@ -773,10 +775,10 @@ def distance(self): return self._distance @property - def astropy(self): + def astropy(self) -> SkyCoord: return self._astropy - def zen(self, value = None): + def zen(self, value: Optional[float]=None) -> Union[float,None]: """ If value is None, returns position zenith angle (float degrees [0, 180]) Otherwise, sets the altitude according to the zenith angle @@ -787,7 +789,8 @@ def zen(self, value = None): return 90.0 - self.alt if value < 0.0 or value > 180.0: raise ValueError(f"value paramerer range is [0.0, 180.0], is set to {value:0.3f}") - self.alt = 90.0 - value + self.alt = 90.0 - value + return None def __str__(self): """ @@ -870,7 +873,7 @@ def to_equ(self, observer, jD): return get_equ_from_hrz(self, observer, jD) - def dir_cos(self): + def dir_cos(self) -> Tuple[float,float,float]: """ Get direction cosines from horizontal coordinates. @@ -901,7 +904,7 @@ class equ_posn(object): _pm_dec = None _distance = None - def __init__(self, ra = 0.0, dec = 0.0): + def __init__(self, ra: float=0.0, dec: float=0.0): """ Create a equ_posn object. @@ -918,7 +921,7 @@ def __init__(self, ra = 0.0, dec = 0.0): self.dec = dec @classmethod - def from_astropy(kls, value): + def from_astropy(kls, value: Union[SkyCoord,ICRS,FK4,FK5]): if not isinstance(value, (SkyCoord, ICRS, FK4, FK5)): raise TypeError("Expected an object of type SkyCoord, ICRS, FK4, or FK5") @@ -942,7 +945,7 @@ def from_astropy(kls, value): return _posn @property - def ra(self): + def ra(self) -> float: """ RA in degrees. """ @@ -950,7 +953,7 @@ def ra(self): return self._ra @ra.setter - def ra(self, value): + def ra(self, value: Union[hms,float]): if self.astropy is not None: raise RuntimeError("Cannot set ra when this.astropy is not None") @@ -961,7 +964,7 @@ def ra(self, value): self._ra = value @property - def dec(self): + def dec(self) -> float: """ Declination in degrees. """ @@ -969,7 +972,7 @@ def dec(self): return self._dec @dec.setter - def dec(self, value): + def dec(self, value: Union[dms,float]): if self.astropy is not None: raise RuntimeError("Cannot set dec when this.astropy is not None") @@ -980,7 +983,7 @@ def dec(self, value): self._dec = value @property - def pm_ra(self): + def pm_ra(self) -> Union[float,None]: """ Proper motion in RA in mas/yr or None if it is unknown. """ @@ -988,7 +991,7 @@ def pm_ra(self): return self._pm_ra @property - def pm_dec(self): + def pm_dec(self) -> Union[float,None]: """ Proper motion in declination in mas/yr or None if it is unknown. """ @@ -996,7 +999,7 @@ def pm_dec(self): return self._pm_dec @property - def distance(self): + def distance(self) -> Union[float,None]: """ Distance in pc or None if it is unknown. """ @@ -1004,7 +1007,7 @@ def distance(self): return self._distance @property - def astropy(self): + def astropy(self) -> Union[SkyCoord,ICRS,FK4,FK5]: return self._astropy def __str__(self): @@ -1068,12 +1071,12 @@ def __ne__(self, other): return (self.ra != other.ra) or (self.dec != other.dec) - def to_hrz(self, observer, jD): + def to_hrz(self, observer: "geo_posn", jD: float) -> hrz_posn: """ Get local horizontal coordinates from equatorial/celestial coordinates. - Param: observer - Object of type hrz_posn representing the object's - position in the sky. + Param: observer - Object of type geo_posn representing the observer's + location on Earth Param: jD - UTC Julian day (float). Returns object of type hrz_posn representing local position. @@ -1081,7 +1084,7 @@ def to_hrz(self, observer, jD): return get_hrz_from_equ(self, observer, jD) - def to_gal(self, jD): + def to_gal(self, jD: float) -> "gal_posn": """ Get J2000 galactic coordinates from apparent equatorial coordinates. @@ -1094,7 +1097,7 @@ def to_gal(self, jD): equ = get_equ_prec2(self, jD, J2000_UTC_JD) return get_gal_from_equ(equ) - def to_ecl(self, jD): + def to_ecl(self, jD: float) -> "ecl_posn": """ Get ecliptical coordinates from equatorial coordinates. @@ -1105,7 +1108,7 @@ def to_ecl(self, jD): return get_ecl_from_equ(self, jD) - def angular_separation(self, posn): + def angular_separation(self, posn: "equ_posn") -> float: """ Get angular separation from equatorial position. @@ -1116,7 +1119,7 @@ def angular_separation(self, posn): return get_angular_separation(self, posn) - def format(self): + def format(self) -> Tuple[hms,dms]: """ Return a tuple (ra, dec) where ra is an hms object and dec is a dms object representing ra and dec position coordinates. @@ -1124,7 +1127,7 @@ def format(self): return (deg_to_hms(self.ra), deg_to_dms(self.dec)) - def precess(self, jD): + def precess(self, jD: float) -> "equ_posn": """ Get position of celestial object accounting for precession. The equatorial coordinates should be for the J2000 epoch. @@ -1162,7 +1165,7 @@ class gal_posn(object): _pm_b = None _distance = None - def __init__(self, l = 0.0, b = 0.0): + def __init__(self, l: float=0.0, b: float=0.0): """ Create a gal_posn object. @@ -1179,7 +1182,7 @@ def __init__(self, l = 0.0, b = 0.0): self.b = b @classmethod - def from_astropy(kls, value): + def from_astropy(kls, value: SkyCoord): if not isinstance(value, SkyCoord): raise TypeError("Expected an object of type SkyCoord") @@ -1202,7 +1205,7 @@ def from_astropy(kls, value): return _posn @property - def l(self): + def l(self) -> float: """ Galactic longitude in degrees. """ @@ -1210,7 +1213,7 @@ def l(self): return self._l @l.setter - def l(self, value): + def l(self, value: Union[hms,float]): if self.astropy is not None: raise RuntimeError("Cannot set l when this.astropy is not None") @@ -1221,7 +1224,7 @@ def l(self, value): self._l = value @property - def b(self): + def b(self) -> float: """ Galactic latitude in degrees. """ @@ -1229,7 +1232,7 @@ def b(self): return self._b @b.setter - def b(self, value): + def b(self, value: Union[dms,float]): if self.astropy is not None: raise RuntimeError("Cannot set b when this.astropy is not None") @@ -1240,7 +1243,7 @@ def b(self, value): self._b = value @property - def pm_l(self): + def pm_l(self) -> Union[float,None]: """ Proper motion in Galactic longitude in mas/yr or None if it is unknown. """ @@ -1248,7 +1251,7 @@ def pm_l(self): return self._pm_l @property - def pm_b(self): + def pm_b(self) -> Union[float,None]: """ Proper motion in Galactic latitude in mas/yr or None if it is unknown. """ @@ -1256,7 +1259,7 @@ def pm_b(self): return self._pm_b @property - def distance(self): + def distance(self) -> Union[float,None]: """ Distance in pc or None if it is unknown. """ @@ -1264,7 +1267,7 @@ def distance(self): return self._distance @property - def astropy(self): + def astropy(self) -> SkyCoord: return self._astropy def __str__(self): @@ -1335,7 +1338,7 @@ def __ne__(self, other): return (self.l != other.l) or (self.b != other.b) - def to_equ(self, jD): + def to_equ(self, jD: float) -> equ_posn: """ Get apparent equatorial coordinates from J2000 galactic coordinates. @@ -1348,7 +1351,7 @@ def to_equ(self, jD): equ = get_equ_from_gal(self) return get_equ_prec(equ, jD) - def format(self): + def format(self) -> Tuple[dms,dms]: """ Return a tuple (lng, lat) where lng is an dms object and lat is a dms object representing galactic longitude and latitude @@ -1373,7 +1376,7 @@ class rect_posn(object): rect_posn[2] = Z """ - def __init__(self, X = 0.0, Y = 0.0, Z = 0.0): + def __init__(self, X: float=0.0, Y: float=0.0, Z: float=0.0): """ Create a rect_posn object Param: X - Position X coordinate (float). @@ -1477,7 +1480,7 @@ class ecl_posn(object): _pm_lat = None _distance = None - def __init__(self, lng = 0.0, lat = 0.0): + def __init__(self, lng: float=0.0, lat: float=0.0): if lng is not None: self.lng = lng @@ -1485,7 +1488,7 @@ def __init__(self, lng = 0.0, lat = 0.0): self.lat = lat @classmethod - def from_astropy(kls, value): + def from_astropy(kls, value: SkyCoord): if not isinstance(value, SkyCoord): raise TypeError("Expected an object of type SkyCoord") @@ -1508,7 +1511,7 @@ def from_astropy(kls, value): return _posn @property - def lng(self): + def lng(self) -> float: """ Ecliptic longitude in degees. """ @@ -1516,7 +1519,7 @@ def lng(self): return self._lng @lng.setter - def lng(self, value): + def lng(self, value: Union[dms,float]): if self.astropy is not None: raise RuntimeError("Cannot set lng when this.astropy is not None") @@ -1527,7 +1530,7 @@ def lng(self, value): self._lng = value @property - def lat(self): + def lat(self) -> float: """ Ecliptic latitude in degrees. """ @@ -1535,7 +1538,7 @@ def lat(self): return self._lat @lat.setter - def lat(self, value): + def lat(self, value: Union[dms,float]): if self.astropy is not None: raise RuntimeError("Cannot set lat when this.astropy is not None") @@ -1546,7 +1549,7 @@ def lat(self, value): self._lat = value @property - def pm_lng(self): + def pm_lng(self) -> Union[float,None]: """ Proper motion in Ecliptic longitude in mas/yr or None if it is unknown. """ @@ -1554,7 +1557,7 @@ def pm_lng(self): return self._pm_lng @property - def pm_lat(self): + def pm_lat(self) -> Union[float,None]: """ Proper motion in Ecliptic latitude in mas/yr or None if it is unknown. """ @@ -1562,7 +1565,7 @@ def pm_lat(self): return self._pm_lat @property - def distance(self): + def distance(self) -> Union[float,None]: """ Distance in pc or None if it is unknown. """ @@ -1570,7 +1573,7 @@ def distance(self): return self._distance @property - def astropy(self): + def astropy(self) -> SkyCoord: return self._astropy def __str__(self): @@ -1621,7 +1624,7 @@ def __setitem__(self, key, value): else: raise ValueError(f"subscript {key} out of range") - def to_equ(self, jD): + def to_equ(self, jD: float) -> equ_posn: """ Get equatorial coordinates from ecliptical coordinates for a given time. @@ -1632,7 +1635,7 @@ def to_equ(self, jD): return get_equ_from_ecl(self, jD) - def format(self): + def format(self) -> Tuple[dms,dms]: """ Return a tuple (lng, lat) where lng is an dms object and lat is a dms object representing longitude and latitude @@ -1646,7 +1649,7 @@ def format(self): # time helper python fucntions ###################################################################### -def get_gmtoff(): +def get_gmtoff() -> int: """ Get local UTC offset based on Python time module and system info. """ @@ -1661,7 +1664,7 @@ def get_gmtoff(): # General Conversion Functions ###################################################################### -def date_to_zonedate(date, gmtoff): +def date_to_zonedate(date: date, gmtoff: int) -> zonedate: """ Convert UTC calendar time to local calendar time. @@ -1687,7 +1690,7 @@ def date_to_zonedate(date, gmtoff): return _zdate -def zonedate_to_date(zonedate): +def zonedate_to_date(zonedate: zonedate) -> date: """ Convert local calendar time to UTC calendar time. @@ -1697,10 +1700,10 @@ def zonedate_to_date(zonedate): """ t0, junk = str(zonedate).rsplit(None, 1) - t0 = time.strptime(t0, "%Y-%m-%d %H:%M:%S.%f") + tt0 = time.strptime(t0, "%Y-%m-%d %H:%M:%S.%f") fracSec = zonedate.seconds - int(zonedate.seconds) - t1 = timegm(t0) - zonedate.gmtoff - years, months, days, hours, minutes, seconds, wday, yday, dst = time.gmtime(t1) + tt1 = timegm(tt0) - zonedate.gmtoff + years, months, days, hours, minutes, seconds, wday, yday, dst = time.gmtime(tt1) _date = date() _date.years = years @@ -1712,7 +1715,7 @@ def zonedate_to_date(zonedate): return _date -def rad_to_deg(radians): +def rad_to_deg(radians: float) -> float: """ Convert radians to degrees. @@ -1724,7 +1727,7 @@ def rad_to_deg(radians): return radians * 180.0/math.pi -def deg_to_rad(degrees): +def deg_to_rad(degrees: float) -> float: """ Convert degres to radians. @@ -1736,7 +1739,7 @@ def deg_to_rad(degrees): return degrees * math.pi/180.0 -def dms_to_rad(dms): +def dms_to_rad(dms: dms) -> float: """ Convert angles degrees, minutes, seconds to radians. @@ -1749,7 +1752,7 @@ def dms_to_rad(dms): return deg_to_rad(degrees) -def dms_to_deg(dms): +def dms_to_deg(dms: dms) -> float: """ Convert angles degrees, minutes, seconds to float degrees. @@ -1777,7 +1780,7 @@ def _float_to_sexa(value): return sgn, d, m, s -def deg_to_dms(degrees): +def deg_to_dms(degrees: float) -> dms: """ Convert angles float degrees to degrees, minutes, seconds. @@ -1798,7 +1801,7 @@ def deg_to_dms(degrees): return _dms -def rad_to_dms(radians): +def rad_to_dms(radians: float) -> dms: """ Convert angles float radians to degrees, minutes, seconds. @@ -1811,7 +1814,7 @@ def rad_to_dms(radians): return deg_to_dms(degrees) -def hms_to_deg(hms): +def hms_to_deg(hms: hms) -> float: """ Convert angles hours, minutes, seconds to float degrees. @@ -1825,7 +1828,7 @@ def hms_to_deg(hms): return degrees -def hms_to_rad(hms): +def hms_to_rad(hms: hms) -> float: """ Convert angles hours, minutes, seconds to float radians. @@ -1838,7 +1841,7 @@ def hms_to_rad(hms): return deg_to_rad(degrees) -def deg_to_hms(degrees): +def deg_to_hms(degrees: float) -> hms: """ Convert angles float degrees to hours, minutes, seconds. @@ -1851,7 +1854,7 @@ def deg_to_hms(degrees): return hms(h, m, s) -def rad_to_hms(radians): +def rad_to_hms(radians: float) -> hms: """ Convert angles float radians to hours, minutes, seconds. @@ -1864,7 +1867,7 @@ def rad_to_hms(radians): return deg_to_hms(degrees) -def add_secs_hms(hms, seconds): +def add_secs_hms(hms: hms, seconds: float) -> hms: """ Add seconds to time/angle hours, minutes, seconds. @@ -1879,7 +1882,7 @@ def add_secs_hms(hms, seconds): return deg_to_hms(degrees) -def add_hms(source, dest): +def add_hms(source: hms, dest: hms) -> hms: """ Adds time/angle hours, minutes, seconds. @@ -1894,14 +1897,14 @@ def add_hms(source, dest): degrees1 += degrees2 _hms = deg_to_hms(degrees1) - dest.degrees = 1*_hms.hours + dest.hours = 1*_hms.hours dest.minutes = 1*_hms.minutes dest.seconds = 1*_hms.seconds return dest -def hrz_to_nswe(pos): +def hrz_to_nswe(pos: hrz_posn) -> str: """ Get cardinal/ordinal azimuth direction. @@ -1921,7 +1924,7 @@ def hrz_to_nswe(pos): return 'W' -def range_degrees(degrees): +def range_degrees(degrees: float) -> float: """ Put angle into range [0, 360]. @@ -1937,7 +1940,7 @@ def range_degrees(degrees): # General Calendar Functions ###################################################################### -def get_julian_day(date): +def get_julian_day(date: date) -> float: """ Convert calendar time to Julian day. @@ -1953,7 +1956,7 @@ def get_julian_day(date): return d.jd -def get_julian_local_date(zonedate): +def get_julian_local_date(zonedate: zonedate) -> float: """ Convert local calendar time to Julian day. @@ -1966,7 +1969,7 @@ def get_julian_local_date(zonedate): return get_julian_day(_date) -def get_date(jD): +def get_date(jD: float) -> date: """ Convert Julian day to calendar time. @@ -1988,7 +1991,7 @@ def get_date(jD): return _date -def get_day_of_week(date): +def get_day_of_week(date: date) -> int: """ Gets day of week from calendar time. @@ -2001,7 +2004,7 @@ def get_day_of_week(date): return (int(round(jd)) + 1) % 7 -def get_julian_from_sys(): +def get_julian_from_sys() -> float: """ Returns UTC Julian day (float) from system clock. """ @@ -2010,7 +2013,7 @@ def get_julian_from_sys(): return t0.utc.jd -def get_date_from_sys(): +def get_date_from_sys() -> date: """ Gets calendar time from system clock. @@ -2022,7 +2025,7 @@ def get_date_from_sys(): return _date -def get_julian_from_timet(timet): +def get_julian_from_timet(timet: float) -> float: """ Gets Julian day from Unix time. @@ -2035,7 +2038,7 @@ def get_julian_from_timet(timet): return t.jd -def get_timet_from_julian(jD): +def get_timet_from_julian(jD: float) -> float: """ Gets Unix time from Julian day. @@ -2048,11 +2051,192 @@ def get_timet_from_julian(jD): return t.unix +###################################################################### +# Position utility classes +###################################################################### + +class geo_posn(object): + """ + Class to represent geographical position in terms of longitude, lattitude, + and elevation. This is a set of geodetic coordinates based on the WGS84 model. + + Public members: + lng - longitude (float) + lat - latitude (float) + elv - elevation (float) + + Members may also be accessed by subscript: + geo_posn[0] = lng + geo_posn[1] = lat + geo_posn[2] = elv + """ + + _astropy = None + + def __init__(self, lng: float=0.0, lat: float=0.0, elv: float=0.0): + """ + Create a geo_posn object. + + Param: lng - longitude coordinate + Object of type dms or float degrees (-360.0, 360.0). + Param: lat - Position latitude coordinate + Object of type dms or float degrees [-90.0, 90.0]. + Param: elv - elevation (float meters) + """ + + if lng is not None: + self.lng = lng + + if lat is not None: + self.lat = lat + + if elv is not None: + self.elv = elv + + @classmethod + def from_astropy(kls, value: EarthLocation): + if not isinstance(value, EarthLocation): + raise TypeError("Expected an object of type EarthLocation") + + _posn = kls() + _posn._astropy = value + _posn._lng = value.lon.wrap_at(360*astrounits.deg).deg + _posn._lat = value.lat.deg + _posn._elv = value.height.to('m').value + return _posn + + @property + def lng(self) -> float: + return self._lng + + @lng.setter + def lng(self, value: Union[dms,float]): + if self.astropy is not None: + raise RuntimeError(f"Cannot set lng when this.astropy is not None") + + if isinstance(value, dms): + value = value.to_deg() + if value <= -360.0 or value >= 360.0: + raise ValueError(f"lng parameter range is (-360.0, 360.0), is set to {value:0.3f}") + self._lng = value + + @property + def lat(self) -> float: + return self._lat + + @lat.setter + def lat(self, value: Union[dms,float]): + if self.astropy is not None: + raise RuntimeError(f"Cannot set lat when this.astropy is not None") + + if isinstance(value, dms): + value = value.to_deg() + if value < -90.0 or value > 90.0: + raise ValueError(f"lat paramerer range is [-90.0, 90.0], is set to {value:0.3f}") + self._lat = value + + @property + def elv(self) -> float: + return self._elv + + @elv.setter + def elv(self, value: float): + if self.astropy is not None: + raise RuntimeError(f"Cannot elv when this.astropy is not None") + + self._elv = value + + @property + def astropy(self) -> EarthLocation: + return self._astropy + + def __str__(self): + """ + geo_posn object print/str method. + """ + + return f"{self.lng:0.3f} {self.lat:0.3f} {self.elv:0.3f}" + + def __repr__(self): + """ + geo_posn object repr string method + """ + + return "%s.%s(%s,%s,%s)" % (type(self).__module__, type(self).__name__, repr(self.lng), repr(self.lat), repr(self.elv)) + + def __reduce__(self): + """ + geo_posn object pickle reduce method. + """ + + return (geo_posn, (self.lng, self.lat, self.elv)) + + def __getitem__(self, key): + """ + Get members by subscript index. + """ + + if key == 0: + return self.lng + elif key == 1: + return self.lat + elif key == 2: + return self.elv + else: + raise ValueError(f"subscript {key} out of range") + + def __setitem__(self, key, value): + """ + Set members by subscript index. + """ + + if self.astropy is not None: + raise RuntimeError(f"Cannot set index {key} when this.astropy is not None") + + if key == 0: + self.lng = value + elif key == 1: + self.lat = value + elif key == 2: + self.elv = value + else: + raise ValueError(f"subscript {key} out of range") + + def __eq__(self, other): + """ + geo_posn equality test. + """ + + if not isinstance(other, geo_posn): + raise TypeError(f"comparison not supported for type {type(other).__name__}") + + return (self.lng == other.lng) and (self.lat == other.lat) and (self.elv == other.elv) + + def __ne__(self, other): + """ + geo_posn non-equality test. + """ + + if not isinstance(other, geo_posn): + raise TypeError(f"comparison not supported for type {type(other).__name__}") + + return (self.lng != other.lng) or (self.lat != other.lat) or (self.elv != other.elv) + + def format(self) -> Tuple[dms,dms,float]: + """ + Return a tuple (lng, lat) where lng is an dms object and + lat is a dms object representing longitude and latitude + position coordinates. + """ + + return (deg_to_dms(self.lng), deg_to_dms(self.lat), self.elv) + + ###################################################################### # Transformation of Coordinates Functions ###################################################################### -def get_hrz_from_equ(target, observer, jD): +def get_hrz_from_equ(target: equ_posn, observer: geo_posn, jD: float) -> hrz_posn: """ Get local horizontal coordinates from equatorial/celestial coordinates. @@ -2082,7 +2266,7 @@ def get_hrz_from_equ(target, observer, jD): return hrz_posn.from_astropy(sc) -def get_equ_from_hrz(target, observer, jD): +def get_equ_from_hrz(target: hrz_posn, observer: geo_posn, jD: float) -> equ_posn: """ Get equatorial/celestial coordinates from local horizontal coordinates. @@ -2110,7 +2294,7 @@ def get_equ_from_hrz(target, observer, jD): return equ_posn.from_astropy(sc) -def get_ecl_from_rect(rect): +def get_ecl_from_rect(rect: rect_posn) -> ecl_posn: """ Get ecliptical coordinates from rectangular coordinates. @@ -2119,13 +2303,17 @@ def get_ecl_from_rect(rect): Returns object of type ecl_posn representing ecliptical position. """ - _posn = ecl_posn() - _posn.lng = 1*rect.lng - _posn.lat = 1*rect.lat - return _posn + x = rect.X + y = rect.Y + z = rect.Z + + sc = GeocentricTrueEcliptic(CartesianRepresentation(x*astrounits.au, y*astrounits.au, z*astrounits.au), + equinox='J2000') + + return ecl_posn.from_astropy(sc) -def get_equ_from_ecl(target, jD): +def get_equ_from_ecl(target: ecl_posn, jD: float) -> equ_posn: """ Get J2000 equatorial coordinates from ecliptical coordinates for a given time. @@ -2147,7 +2335,7 @@ def get_equ_from_ecl(target, jD): return equ_posn.from_astropy(sc) -def get_ecl_from_equ(target, jD): +def get_ecl_from_equ(target: equ_posn, jD: float) -> ecl_posn: """ Get ecliptical coordinates from J2000 equatorial coordinates for a given time. @@ -2171,7 +2359,7 @@ def get_ecl_from_equ(target, jD): return ecl_posn.from_astropy(sc) -def get_equ_from_gal(target): +def get_equ_from_gal(target: gal_posn) -> equ_posn: """ Get J2000 equatorial coordinates from galactic coordinates. @@ -2193,7 +2381,7 @@ def get_equ_from_gal(target): return equ_posn.from_astropy(sc) -def get_gal_from_equ(target): +def get_gal_from_equ(target: equ_posn) -> gal_posn: """ Get galactic coordinates from J2000 equatorial coordinates. @@ -2220,7 +2408,7 @@ def get_gal_from_equ(target): # Sidereal Time Functions ###################################################################### -def get_apparent_sidereal_time(jD): +def get_apparent_sidereal_time(jD: float) -> float: """ Get apparent sidereal time from Julian day. @@ -2235,7 +2423,7 @@ def get_apparent_sidereal_time(jD): return gast.hourangle -def get_mean_sidereal_time(jD): +def get_mean_sidereal_time(jD: float) -> float: """ Get mean sidereal time from Julian day. @@ -2255,7 +2443,7 @@ def get_mean_sidereal_time(jD): # Angular Separation Functions ###################################################################### -def get_angular_separation(posn1, posn2): +def get_angular_separation(posn1: equ_posn, posn2: equ_posn) -> float: """ Get angular separation from equatorial positions. @@ -2279,7 +2467,7 @@ def get_angular_separation(posn1, posn2): return sep.deg -def get_rel_posn_angle(posn1, posn2): +def get_rel_posn_angle(posn1: equ_posn, posn2: equ_posn): """ Get relative position angle from equatorial positions. @@ -2309,7 +2497,7 @@ def get_rel_posn_angle(posn1, posn2): _DEFAULT_PROPER_MOTION = equ_posn(0.0, 0.0) -def get_apparent_posn(mean_position, jD, proper_motion = None): +def get_apparent_posn(mean_position: equ_posn, jD: float, proper_motion: Optional[Tuple[Union[float,None],Union[float,None]]]=None) -> equ_posn: """ Get apparent position of celestial object accounting for precession, nutation, aberration, and optionally proper motion. @@ -2326,15 +2514,17 @@ def get_apparent_posn(mean_position, jD, proper_motion = None): """ if proper_motion is None: - proper_motion = [mean_position.pm_ra, mean_position.pm_dec] - if proper_motion[0] is None or proper_motion[1] is None: - proper_motion = _DEFAULT_PROPER_MOTION + proper_motion = (mean_position.pm_ra, mean_position.pm_dec) + if proper_motion[0] is None: + proper_motion = (_DEFAULT_PROPER_MOTION[0], proper_motion[1]) + if proper_motion[1] is None: + proper_motion = (proper_motion[0], _DEFAULT_PROPER_MOTION[1]) t = AstroTime(jD, format='jd', scale='utc') sc = mean_position.astropy if sc is None: sc = SkyCoord(mean_position.ra*astrounits.deg, mean_position.dec*astrounits.deg, - pm_ra_cosdec=proper_motion[0]*math.cos(proper_motion[1]/1000/3600*math.pi/180)*astrounits.mas/astrounits.yr, + pm_ra_cosdec=proper_motion[0]*math.cos(proper_motion[1]/1000/3600*math.pi/180)*astrounits.mas/astrounits.yr, # type: ignore pm_dec= proper_motion[1]*astrounits.mas/astrounits.yr, frame='fk5', equinox='J2000') sc = sc.transform_to(PrecessedGeocentric(equinox=t, obstime=t)) @@ -2346,7 +2536,7 @@ def get_apparent_posn(mean_position, jD, proper_motion = None): # Precession Functions ###################################################################### -def get_equ_prec(mean_position, jD): +def get_equ_prec(mean_position: equ_posn, jD: float) -> equ_posn: """ Get position of celestial object accounting for precession. Only works for converting to and from J2000 epoch. @@ -2368,7 +2558,7 @@ def get_equ_prec(mean_position, jD): return equ_posn.from_astropy(sc) -def get_equ_prec2(mean_position, fromJD, toJD): +def get_equ_prec2(mean_position: equ_posn, fromJD: float, toJD: float) -> equ_posn: """ Get position of celestial object accounting for precession. @@ -2395,7 +2585,7 @@ def get_equ_prec2(mean_position, fromJD, toJD): # Rise, Set, Transit functions ###################################################################### -def get_object_rst(jD, observer, target): +def get_object_rst(jD: float, observer: geo_posn, target: equ_posn) -> Union[rst_time,None]: """ Get rise, set, and transit times of a celstial object. @@ -2488,7 +2678,7 @@ def get_object_rst(jD, observer, target): SOLAR_SYSTEM_EPHEMERIS_TO_USE = 'de432s' -def _get_solar_system_rst(jD, observer, body): +def _get_solar_system_rst(jD: float, observer: geo_posn, body: str) -> Union[rst_time,None]: """ Get rise, set, and transit times of a solar system body. @@ -2591,7 +2781,7 @@ def _get_solar_system_rst(jD, observer, body): # Solar Functions ###################################################################### -def get_solar_equ_coords(jD): +def get_solar_equ_coords(jD: float) -> equ_posn: """ Get Sun's apparent equatorial coordinates from Julian day. Accounts for aberration and precession, and nutation. @@ -2611,7 +2801,7 @@ def get_solar_equ_coords(jD): return equ_posn.from_astropy(b) -def get_solar_rst(jD, observer): +def get_solar_rst(jD: float, observer: geo_posn) -> Union[rst_time,None]: """ Get Sun's rise, transit, set times from Julian day. @@ -2629,7 +2819,7 @@ def get_solar_rst(jD, observer): # Jupiter Functions ###################################################################### -def get_jupiter_equ_coords(jD): +def get_jupiter_equ_coords(jD: float) -> equ_posn: """ Get Jupiter's apparent equatorial coordinates from Julian day. Accounts for aberration and precession, but not nutation. @@ -2649,7 +2839,7 @@ def get_jupiter_equ_coords(jD): return equ_posn.from_astropy(b) -def get_jupiter_rst(jD, observer): +def get_jupiter_rst(jD: float, observer: geo_posn) -> Union[rst_time,None]: """ Get Jupiter's rise, transit, set times from Julian day. @@ -2667,7 +2857,7 @@ def get_jupiter_rst(jD, observer): # Saturn Functions ###################################################################### -def get_saturn_equ_coords(jD): +def get_saturn_equ_coords(jD: float) -> equ_posn: """ Get Saturn's apparent equatorial coordinates from Julian day. Accounts for aberration and precession, but not nutation. @@ -2687,7 +2877,7 @@ def get_saturn_equ_coords(jD): return equ_posn.from_astropy(b) -def get_saturn_rst(jD, observer): +def get_saturn_rst(jD: float, observer: geo_posn) -> Union[rst_time,None]: """ Get Saturn's rise, transit, set times from Julian day. @@ -2705,7 +2895,7 @@ def get_saturn_rst(jD, observer): # Lunar Functions ###################################################################### -def get_lunar_equ_coords(jD): +def get_lunar_equ_coords(jD: float) -> equ_posn: """ Get the Moon's apparent equatorial coordinates from Julian day. Accounts for aberration and precession, but not nutation. @@ -2725,7 +2915,7 @@ def get_lunar_equ_coords(jD): return equ_posn.from_astropy(b) -def get_lunar_rst(jD, observer): +def get_lunar_rst(jD: float, observer: geo_posn) -> Union[rst_time,None]: """ Get the Moon's rise, transit, set times from Julian day. @@ -2743,7 +2933,7 @@ def get_lunar_rst(jD, observer): # Venus Functions ###################################################################### -def get_venus_equ_coords(jD): +def get_venus_equ_coords(jD: float) -> equ_posn: """ Get Venus' apparent equatorial coordinates from Julian day. Accounts for aberration and precession, but not nutation. @@ -2763,7 +2953,7 @@ def get_venus_equ_coords(jD): return equ_posn.from_astropy(b) -def get_venus_rst(jD, observer): +def get_venus_rst(jD: float, observer: geo_posn) -> Union[rst_time,None]: """ Get Venus' rise, transit, set times from Julian day. @@ -2781,7 +2971,7 @@ def get_venus_rst(jD, observer): # Mars Functions ###################################################################### -def get_mars_equ_coords(jD): +def get_mars_equ_coords(jD: float) -> equ_posn: """ Get Mars' apparent equatorial coordinates from Julian day. Accounts for aberration and precession, but not nutation. @@ -2801,7 +2991,7 @@ def get_mars_equ_coords(jD): return equ_posn.from_astropy(b) -def get_mars_rst(jD, observer): +def get_mars_rst(jD: float, observer: geo_posn) -> Union[rst_time,None]: """ Get Mars' rise, transit, set times from Julian day. @@ -2860,7 +3050,7 @@ def get_mars_rst(jD, observer): VELOCITY_OF_LIGHT = 2.99792458e8 -def sec_to_jd(secs): +def sec_to_jd(secs: float) -> float: """ Convert seconds into julian days. @@ -2872,7 +3062,7 @@ def sec_to_jd(secs): return secs / SECS_IN_DAY -def jd_to_sec(jD): +def jd_to_sec(jD: float) -> float: """ Convert Julian days into seconds. @@ -2888,7 +3078,7 @@ def jd_to_sec(jD): # Time utility functions ###################################################################### -def range_hours(hours): +def range_hours(hours: float) -> float: """ Put an hour time value into the correct range [0.0, 24.0]. """ @@ -2904,7 +3094,7 @@ def range_hours(hours): return hours - temp -def jd_to_mjd(jd): +def jd_to_mjd(jd: float) -> float: """ Get modified julian day value from julian day value. @@ -2919,7 +3109,7 @@ def jd_to_mjd(jd): return (jd - MJD_OFFSET) -def mjd_to_jd(mjd): +def mjd_to_jd(mjd: float) -> float: """ Get julian day value from modified julian day value. @@ -2938,7 +3128,7 @@ def mjd_to_jd(mjd): _UNIX_EPOCH_AT = AstroTime(0, format='unix', scale='utc') -def leap_secs(utcJD): +def leap_secs(utcJD: float) -> float: """ Get the number of leap seconds for given UTC time value. @@ -2957,7 +3147,7 @@ def leap_secs(utcJD): return round(diff_jd - diff_unix, 1) + 8 # +8 for initial 10 s offset between TAI and UTC -def utc_to_tai(utcJD): +def utc_to_tai(utcJD: float) -> float: """ Get the TAI JD time value for a given UTC JD time value. @@ -2971,7 +3161,7 @@ def utc_to_tai(utcJD): return t.tai.jd -def tai_to_utc(taiJD): +def tai_to_utc(taiJD: float) -> float: """ Get the UTC JD time value for a given TAI JD time value. @@ -2985,7 +3175,7 @@ def tai_to_utc(taiJD): return t.utc.jd -def taimjd_to_utcjd(taiMJD): +def taimjd_to_utcjd(taiMJD: float) -> float: """ Get the UTC JD time value for a given TAI MJD value. @@ -2998,7 +3188,7 @@ def taimjd_to_utcjd(taiMJD): return t.utc.jd -def utcjd_to_taimjd(utcJD): +def utcjd_to_taimjd(utcJD: float) -> float: """ Get the TAI MJD time value for a given UTC JD value. @@ -3011,7 +3201,7 @@ def utcjd_to_taimjd(utcJD): return t.tai.mjd -def unix_to_utcjd(unixTime): +def unix_to_utcjd(unixTime: Union[int,float]) -> float: """ Get the UTC JD time value for a given UNIX time value. @@ -3024,7 +3214,7 @@ def unix_to_utcjd(unixTime): return t.jd -def unix_to_taimjd(unixTime): +def unix_to_taimjd(unixTime: Union[int,float]) -> float: """ Get the TAI MJD time value for a given UNIX time value. @@ -3037,7 +3227,7 @@ def unix_to_taimjd(unixTime): return t.tai.mjd -def utcjd_to_unix(utcJD): +def utcjd_to_unix(utcJD: float) -> float: """ Get UNIX time value for a given UTC JD value. @@ -3050,7 +3240,7 @@ def utcjd_to_unix(utcJD): return t.unix -def taimjd_to_unix(taiMJD): +def taimjd_to_unix(taiMJD: float) -> float: """ Get UNIX time value for a given TAI MJDvalue. @@ -3063,7 +3253,7 @@ def taimjd_to_unix(taiMJD): return t.utc.unix -def tai_to_tt(taiJD): +def tai_to_tt(taiJD: float) -> float: """ Get the TT JD time value for a given TAI JD time value. @@ -3076,7 +3266,7 @@ def tai_to_tt(taiJD): return t.tt.jd -def tt_to_tai(ttJD): +def tt_to_tai(ttJD: float) -> float: """ Get the TAI JD time value for a given TT JD time value. @@ -3085,11 +3275,11 @@ def tt_to_tai(ttJD): Returns: The TAI JD value (float). """ - t = AstroTime(taiJD, format='jd', scale='tt') + t = AstroTime(ttJD, format='jd', scale='tt') return t.tai.jd -def utc_to_tt(utcJD): +def utc_to_tt(utcJD: float) -> float: """ Get the TT JD time value for a given UTC JD time value. @@ -3102,7 +3292,7 @@ def utc_to_tt(utcJD): return t.tt.jd -def tt_to_utc(ttJD): +def tt_to_utc(ttJD: float) -> float: """ Get the UTC JD time value for a given TT JD time value. @@ -3115,7 +3305,7 @@ def tt_to_utc(ttJD): return t.utc.jd -def tt_to_tdb(ttJD): +def tt_to_tdb(ttJD: float) -> float: """ Get the TDB JD time value for a given TT JD time value. Adopted from "Astronomical Almanac Supplement", Seidelmann 1992, 2.222-1. @@ -3129,7 +3319,7 @@ def tt_to_tdb(ttJD): return t.tdb.jd -def get_tai_from_sys(): +def get_tai_from_sys() -> float: """ Return the current time taken from the system clock as a TAI MJD (float). @@ -3139,7 +3329,7 @@ def get_tai_from_sys(): return t0.tai.mjd -def hms_to_sec(hms): +def hms_to_sec(hms: hms) -> float: """ Convert hours, minutes, seconds to seconds. @@ -3151,7 +3341,7 @@ def hms_to_sec(hms): return hms.hours*3600.0 + hms.minutes*60.0 + hms.seconds -def deg_to_sec(degrees): +def deg_to_sec(degrees: float) -> float: """ Convert longitude degrees into time seconds. @@ -3164,7 +3354,7 @@ def deg_to_sec(degrees): return hours*3600.0 -def get_local_sidereal_time(lng, jD): +def get_local_sidereal_time(lng: float, jD: float) -> float: """ Get apparent local sidereal time from Julian day. @@ -3179,192 +3369,11 @@ def get_local_sidereal_time(lng, jD): return range_hours(gast + off) -###################################################################### -# Position utility classes -###################################################################### - -class geo_posn(object): - """ - Class to represent geographical position in terms of longitude, lattitude, - and elevation. This is a set of geodetic coordinates based on the WGS84 model. - - Public members: - lng - longitude (float) - lat - latitude (float) - elv - elevation (float) - - Members may also be accessed by subscript: - geo_posn[0] = lng - geo_posn[1] = lat - geo_posn[2] = elv - """ - - _astropy = None - - def __init__(self, lng = 0.0, lat = 0.0, elv = 0.0): - """ - Create a geo_posn object. - - Param: lng - longitude coordinate - Object of type dms or float degrees (-360.0, 360.0). - Param: lat - Position latitude coordinate - Object of type dms or float degrees [-90.0, 90.0]. - Param: elv - elevation (float meters) - """ - - if lng is not None: - self.lng = lng - - if lat is not None: - self.lat = lat - - if elv is not None: - self.elv = elv - - @classmethod - def from_astropy(kls, value): - if not isinstance(value, EarthLocation): - raise TypeError("Expected an object of type EarthLocation") - - _posn = kls() - _posn._astropy = value - _posn._lng = value.lon.wrap_at(360*astrounits.deg).deg - _posn._lat = value.lat.deg - _posn._elv = value.height.to('m').value - return _posn - - @property - def lng(self): - return self._lng - - @lng.setter - def lng(self, value): - if self.astropy is not None: - raise RuntimeError(f"Cannot set lng when this.astropy is not None") - - if isinstance(value, dms): - value = value.to_deg() - if value <= -360.0 or value >= 360.0: - raise ValueError(f"lng parameter range is (-360.0, 360.0), is set to {value:0.3f}") - self._lng = value - - @property - def lat(self): - return self._lat - - @lat.setter - def lat(self, value): - if self.astropy is not None: - raise RuntimeError(f"Cannot set lat when this.astropy is not None") - - if isinstance(value, dms): - value = value.to_deg() - if value < -90.0 or value > 90.0: - raise ValueError(f"lat paramerer range is [-90.0, 90.0], is set to {value:0.3f}") - self._lat = value - - @property - def elv(self): - return self._elv - - @elv.setter - def elv(self, value): - if self.astropy is not None: - raise RuntimeError(f"Cannot elv when this.astropy is not None") - - self._elv = value - - @property - def astropy(self): - return self._astropy - - def __str__(self): - """ - geo_posn object print/str method. - """ - - return f"{self.lng:0.3f} {self.lat:0.3f} {self.elv:0.3f}" - - def __repr__(self): - """ - geo_posn object repr string method - """ - - return "%s.%s(%s,%s,%s)" % (type(self).__module__, type(self).__name__, repr(self.lng), repr(self.lat), repr(self.elv)) - - def __reduce__(self): - """ - geo_posn object pickle reduce method. - """ - - return (geo_posn, (self.lng, self.lat, self.elv)) - - def __getitem__(self, key): - """ - Get members by subscript index. - """ - - if key == 0: - return self.lng - elif key == 1: - return self.lat - elif key == 2: - return self.elv - else: - raise ValueError(f"subscript {key} out of range") - - def __setitem__(self, key, value): - """ - Set members by subscript index. - """ - - if self.astropy is not None: - raise RuntimeError(f"Cannot set index {key} when this.astropy is not None") - - if key == 0: - self.lng = value - elif key == 1: - self.lat = value - elif key == 2: - self.elv = value - else: - raise ValueError(f"subscript {key} out of range") - - def __eq__(self, other): - """ - geo_posn equality test. - """ - - if not isinstance(other, geo_posn): - raise TypeError(f"comparison not supported for type {type(other).__name__}") - - return (self.lng == other.lng) and (self.lat == other.lat) and (self.elv == other.elv) - - def __ne__(self, other): - """ - geo_posn non-equality test. - """ - - if not isinstance(other, geo_posn): - raise TypeError(f"comparison not supported for type {type(other).__name__}") - - return (self.lng != other.lng) or (self.lat != other.lat) or (self.elv != other.elv) - - def format(self): - """ - Return a tuple (lng, lat) where lng is an dms object and - lat is a dms object representing longitude and latitude - position coordinates. - """ - - return (deg_to_dms(self.lng), deg_to_dms(self.lat), self.elv) - - ###################################################################### # Pointing utility functions ###################################################################### -def dir_cos(posn): +def dir_cos(posn: hrz_posn) -> Tuple[float,float,float]: """ Get direction cosines from azimuth and zenith angles. This function calculates the cosine values based on the LWA coordinate system: @@ -3377,8 +3386,8 @@ def dir_cos(posn): Returns a tuple (l,m,n) of float values for the direction cosines. """ - azRad = math.radians(posn.az) - zenRad = math.radians(posn.zen()) + azRad = math.radians(posn.az) # type: ignore + zenRad = math.radians(posn.zen()) # type: ignore szen = math.sin(zenRad) l = (szen * math.sin(azRad)) @@ -3388,7 +3397,7 @@ def dir_cos(posn): return (l, m, n) -def get_rect_from_equ(posn): +def get_rect_from_equ(posn: equ_posn) -> rect_posn: """ Transform equatorial coordinates to rectangular coordinates. @@ -3408,7 +3417,7 @@ def get_rect_from_equ(posn): return rect_posn(x, y, z) -def get_equ_from_rect(posn): +def get_equ_from_rect(posn: rect_posn) -> equ_posn: """ Transform rectangular coordinates to equatorial coordinates. @@ -3426,13 +3435,13 @@ def get_equ_from_rect(posn): return equ_posn.from_astropy(sc) -def get_geo_from_rect(posn): +def get_geo_from_rect(posn: rect_posn) -> geo_posn: """ Transform ECEF rectangular coordinates to geographical coordinates. Adapoted from "Satellite Orbits", Montenbruck and Gill 2005, 5.85 - 5.88. Also see gpstk ECEF::asGeodetic() method. - Param: posn - object of type rect_posn giving position. + Param: posn - object of type rect_posn giving position in m. Returns: object of type geo_posn giving geographical coordinates. """ @@ -3442,7 +3451,7 @@ def get_geo_from_rect(posn): return geo_posn.from_astropy(el) -def get_rect_from_geo(posn): +def get_rect_from_geo(posn: geo_posn) -> rect_posn: """ Transform geographical coordinates to ECEF rectangular coordinates. Adopted from "Satellite Orbits", Montenbruck and Gill 2005, 5.83 - 5.84. @@ -3450,7 +3459,7 @@ def get_rect_from_geo(posn): Param: posn - object of type geo_posn giving geographical coordinates. - Returns: object of type rect_posn giving ECEF position. + Returns: object of type rect_posn giving ECEF position in m. """ el = EarthLocation.from_geodetic(posn.lng*astrounits.deg, posn.lat*astrounits.deg, @@ -3463,7 +3472,7 @@ def get_rect_from_geo(posn): return rect_posn(x, y, z) -def get_precession(jD1, pos, jD2): +def get_precession(jD1: float, pos: equ_posn, jD2: float) -> equ_posn: """ Caculate precession of equatorial coordinates from one epoch to another. @@ -3484,7 +3493,7 @@ def get_precession(jD1, pos, jD2): return equ_posn.from_astropy(sc) -def B1950_to_J2000(pos): +def B1950_to_J2000(pos: equ_posn) -> equ_posn: """ Convert B1950 epoch to J2000 epoch for equatorial coordinates. @@ -3506,7 +3515,7 @@ def B1950_to_J2000(pos): return equ_posn.from_astropy(sc) -def J2000_to_B1950(pos): +def J2000_to_B1950(pos: equ_posn) -> equ_posn: """ Convert J2000 epoch to B1950 epoch for equatorial coordinates. @@ -3528,7 +3537,7 @@ def J2000_to_B1950(pos): return equ_posn.from_astropy(sc) -def resolve_name(name): +def resolve_name(name: str) -> equ_posn: """ Given the name of an astronomical source resolve it into coordinates. @@ -3544,30 +3553,30 @@ def resolve_name(name): result = urlopen('https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-oxp/SNV?%s' % quote_plus(name)) tree = ElementTree.fromstring(result.read()) target = tree.find('Target') - service = target.find('Resolver') - ra = service.find('jradeg') - dec = service.find('jdedeg') + service = target.find('Resolver') # type: ignore + ra = service.find('jradeg') # type: ignore + dec = service.find('jdedeg') # type: ignore try: - pm = service.find('pm') + pm = service.find('pm') # type: ignore except Exception as e: pm = None try: - plx = service.find('plx') + plx = service.find('plx') # type: ignore except Exception as e: plx = None - service = service.attrib['name'].split('=', 1)[1] - ra = float(ra.text) - dec = float(dec.text) + service = service.attrib['name'].split('=', 1)[1] # type: ignore + ra = float(ra.text) # type: ignore + dec = float(dec.text) # type: ignore coordsys = 'J2000' if pm is not None: - pmRA = float(pm.find('pmRA').text) - pmDec = float(pm.find('pmDE').text) + pmRA = float(pm.find('pmRA').text) # type: ignore + pmDec = float(pm.find('pmDE').text) # type: ignore else: pmRA = None pmDec = None if plx is not None: - dist = float(plx.find('v').text) + dist = float(plx.find('v').text) # type: ignore else: dist = None From 79d5b56e2418286cbce03b40f5112ec2a78f6642 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 19:28:48 -0600 Subject: [PATCH 12/18] Switch to context manager. --- lsl/astro.py | 88 ++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/lsl/astro.py b/lsl/astro.py index 4e9826cc..be531c19 100644 --- a/lsl/astro.py +++ b/lsl/astro.py @@ -3550,50 +3550,50 @@ def resolve_name(name: str) -> equ_posn: """ try: - result = urlopen('https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-oxp/SNV?%s' % quote_plus(name)) - tree = ElementTree.fromstring(result.read()) - target = tree.find('Target') - service = target.find('Resolver') # type: ignore - ra = service.find('jradeg') # type: ignore - dec = service.find('jdedeg') # type: ignore - try: - pm = service.find('pm') # type: ignore - except Exception as e: - pm = None - try: - plx = service.find('plx') # type: ignore - except Exception as e: - plx = None - - service = service.attrib['name'].split('=', 1)[1] # type: ignore - ra = float(ra.text) # type: ignore - dec = float(dec.text) # type: ignore - coordsys = 'J2000' - if pm is not None: - pmRA = float(pm.find('pmRA').text) # type: ignore - pmDec = float(pm.find('pmDE').text) # type: ignore - else: - pmRA = None - pmDec = None - if plx is not None: - dist = float(plx.find('v').text) # type: ignore - else: - dist = None - - if pmRA is not None: - pmRA = pmRA*math.cos(dec*math.pi/180)*astrounits.mas/astrounits.yr - if pmDec is not None: - pmDec = pmDec*astrounits.mas/astrounits.yr - if dist is not None: - dist = dist*astrounits.pc - - sc = SkyCoord(ra*astrounits.deg, dec*astrounits.deg, - pm_ra_cosdec=pmRA, pm_dec=pmDec, - distance=dist, - frame='icrs') - _posn = equ_posn.from_astropy(sc) - _posn.resolved_by = service - + with urlopen('https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-oxp/SNV?%s' % quote_plus(name)) as result: + tree = ElementTree.fromstring(result.read()) + target = tree.find('Target') + service = target.find('Resolver') # type: ignore + ra = service.find('jradeg') # type: ignore + dec = service.find('jdedeg') # type: ignore + try: + pm = service.find('pm') # type: ignore + except Exception as e: + pm = None + try: + plx = service.find('plx') # type: ignore + except Exception as e: + plx = None + + service = service.attrib['name'].split('=', 1)[1] # type: ignore + ra = float(ra.text) # type: ignore + dec = float(dec.text) # type: ignore + coordsys = 'J2000' + if pm is not None: + pmRA = float(pm.find('pmRA').text) # type: ignore + pmDec = float(pm.find('pmDE').text) # type: ignore + else: + pmRA = None + pmDec = None + if plx is not None: + dist = float(plx.find('v').text) # type: ignore + else: + dist = None + + if pmRA is not None: + pmRA = pmRA*math.cos(dec*math.pi/180)*astrounits.mas/astrounits.yr + if pmDec is not None: + pmDec = pmDec*astrounits.mas/astrounits.yr + if dist is not None: + dist = dist*astrounits.pc + + sc = SkyCoord(ra*astrounits.deg, dec*astrounits.deg, + pm_ra_cosdec=pmRA, pm_dec=pmDec, + distance=dist, + frame='icrs') + _posn = equ_posn.from_astropy(sc) + _posn.resolved_by = service + except (IOError, AttributeError, ValueError, RuntimeError) as e: raise RuntimeError(f"Failed to resolve source '{name}'") From cfad384b0e5a9806cbc48f5114a9dafb189b16b5 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 19:29:06 -0600 Subject: [PATCH 13/18] Add stub file for lsl.imaging._gridder. --- lsl/imaging/_gridder.pyi | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 lsl/imaging/_gridder.pyi diff --git a/lsl/imaging/_gridder.pyi b/lsl/imaging/_gridder.pyi new file mode 100644 index 00000000..d69acec6 --- /dev/null +++ b/lsl/imaging/_gridder.pyi @@ -0,0 +1,5 @@ +import numpy as np + +from typing import Tuple + +def WProjection(u: np.ndarray, v: np.ndarray, w: np.ndarray, data: np.ndarray, wgt: np.ndarray, uvSize: float=80.0, uvRes: float=0.5, wRes: float=0.1) -> Tuple[np.ndarray,np.ndarray,np.ndarray]: ... From 489429cc71fb4375f58873736fd403330f5bbaef Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 19:29:20 -0600 Subject: [PATCH 14/18] Fix Callable call. --- lsl/correlator/_core.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lsl/correlator/_core.pyi b/lsl/correlator/_core.pyi index 8804e5c8..cb9048d5 100644 --- a/lsl/correlator/_core.pyi +++ b/lsl/correlator/_core.pyi @@ -2,9 +2,9 @@ import numpy as np from typing import Callable, Optional, Tuple -def FEngine(signals: np.ndarray, freqs: np.ndarray, delays: np.ndarray, LFFT: int=64, overlap: int=1, sample_rate: float=196e6, clip_level: int=0, window: Optional[Callable[int]]=None) -> Tuple[np.ndarray,np.ndarray]: ... +def FEngine(signals: np.ndarray, freqs: np.ndarray, delays: np.ndarray, LFFT: int=64, overlap: int=1, sample_rate: float=196e6, clip_level: int=0, window: Optional[Callable[[int],np.ndarray]]=None) -> Tuple[np.ndarray,np.ndarray]: ... -def PFBEngine(signals: np.ndarray, freqs: np.ndarray, delays: np.ndarray, LFFT: int=64, overlap: int=1, sample_rate: float=196e6, clip_level: int=0, window: Optional[Callable[int]]=None) -> Tuple[np.ndarray,np.ndarray]: ... +def PFBEngine(signals: np.ndarray, freqs: np.ndarray, delays: np.ndarray, LFFT: int=64, overlap: int=1, sample_rate: float=196e6, clip_level: int=0, window: Optional[Callable[[int],np.ndarray]]=None) -> Tuple[np.ndarray,np.ndarray]: ... def XEngine2(signals1: np.ndarray, signals2: np.ndarray, valid1: np.ndarray, valid2: np.ndarray) -> np.ndarray: ... From 2f6c564bdd18988fd8afd730f4a99bb65749c108 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 19:32:10 -0600 Subject: [PATCH 15/18] State assumed units. --- lsl/astro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lsl/astro.py b/lsl/astro.py index be531c19..80c4a73c 100644 --- a/lsl/astro.py +++ b/lsl/astro.py @@ -2298,7 +2298,7 @@ def get_ecl_from_rect(rect: rect_posn) -> ecl_posn: """ Get ecliptical coordinates from rectangular coordinates. - Param: rect - Object of type rect_posn representing position. + Param: rect - Object of type rect_posn representing position in AU. Returns object of type ecl_posn representing ecliptical position. """ From 31b681f4a937e0d6c53578cf1a9b007881e6e6d7 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 20:06:06 -0600 Subject: [PATCH 16/18] Return type hint update. --- lsl/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lsl/transform.py b/lsl/transform.py index ebdca7e6..90b14be3 100644 --- a/lsl/transform.py +++ b/lsl/transform.py @@ -841,7 +841,7 @@ def dir_cos(self, time_: Time) -> Tuple[float,float,float]: """ return astro.dir_cos(self.hrz(time_)) - def rst(self, time_: Time) -> astro.rst_time: + def rst(self, time_: Time) -> Union[astro.rst_time,None]: """ Return the rise, set, and transit ephemeris times. The 'time_' parameter should be set to a Time instance providing From 3dc5b1c9c3a0839c427fcb9ae23e1cc50defaafd Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Thu, 11 Jul 2024 20:07:02 -0600 Subject: [PATCH 17/18] More hints. --- lsl/misc/dedispersion.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lsl/misc/dedispersion.py b/lsl/misc/dedispersion.py index 92dab5e4..08a6d7b5 100644 --- a/lsl/misc/dedispersion.py +++ b/lsl/misc/dedispersion.py @@ -8,6 +8,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Dict, Optional, Sequence, Tuple, Union + __version__ = '0.6' __all__ = ['delay', 'incoherent', 'get_coherent_sample_size', 'coherent'] @@ -18,10 +20,10 @@ # Coherent dedispersion N and chirp cache -_coherentCache = {} +_coherentCache: Dict[Tuple[float,float,float,bool,np.dtype],Tuple[int,np.ndarray]] = {} -def delay(freq, dm): +def delay(freq: Union[float,Sequence,np.ndarray], dm:float) -> Union[float,np.ndarray]: """ Calculate the relative delay due to dispersion over a given frequency range in Hz for a particular dispersion measure in pc cm^-3. Return @@ -34,9 +36,7 @@ def delay(freq, dm): # Validate in input frequencies ## Right Type? - try: - freq.size - except AttributeError: + if not isinstance(freq, np.ndarray): freq = np.array(freq, ndmin=1) ## Right size? singleFreq = False @@ -54,7 +54,7 @@ def delay(freq, dm): return tDelay -def incoherent(freq, waterfall, tInt, dm, boundary='wrap', fill_value=np.nan): +def incoherent(freq: np.ndarray, waterfall: np.ndarray, tInt: float, dm: float, boundary: str='wrap', fill_value: float=np.nan) -> np.ndarray: """ Given a list of frequencies in Hz, a 2-D array of spectra as a function of time (time by frequency), and an integration time in seconds, perform @@ -92,7 +92,7 @@ def incoherent(freq, waterfall, tInt, dm, boundary='wrap', fill_value=np.nan): return ddWaterfall -def get_coherent_sample_size(central_freq, sample_rate, dm): +def get_coherent_sample_size(central_freq: float, sample_rate: float, dm: float) -> int: """ Estimate the number of samples needed to successfully apply coherent dedispersion to a data stream. @@ -112,7 +112,7 @@ def get_coherent_sample_size(central_freq, sample_rate, dm): return int(samples) -def _taper(freq): +def _taper(freq: np.ndarray) -> np.ndarray: """ Taper function based Equation (1) of "Pulsar Coherent De-dispersion Experiment at Urumqi Observatory" CJA&A, 2006, S2, 53. @@ -128,7 +128,7 @@ def _taper(freq): return taper -def _chirp(freq, dm, taper=False): +def _chirp(freq: np.ndarray, dm: float, taper: bool=False) -> np.ndarray: """ Chip function for coherent dedispersion for a given set of frequencies (in Hz). Based on Equation (6) of "Pulsar Observations II -- Coherent Dedispersion, @@ -146,7 +146,7 @@ def _chirp(freq, dm, taper=False): return chirp -def coherent(t, timeseries, central_freq, sample_rate, dm, taper=False, previous_time=None, previous_data=None, next_time=None, next_data=None, enable_caching=True): +def coherent(t: np.ndarray, timeseries: np.ndarray, central_freq: float, sample_rate: float, dm: float, taper: bool=False, previous_time: Optional[np.ndarray]=None, previous_data: Optional[np.ndarray]=None, next_time: Optional[np.ndarray]=None, next_data: Optional[np.ndarray]=None, enable_caching: bool=True) -> Tuple[np.ndarray,np.ndarray]: """ Simple coherent dedispersion of complex-valued time-series data at a given central frequency and sample rate. A tapering function can also be applied to the chirp of @@ -162,6 +162,10 @@ def coherent(t, timeseries, central_freq, sample_rate, dm, taper=False, previous dedispersion can be prohibitive. For example, at 74 MHz with 19.6 MS/s and a DM or 10 pc / cm^3 this function uses a window size of about 268 million points. + .. note:: + Both previous_time and previous_data need to be provided for them to be + used in the function call. The same is true for next_time and next_data. + .. versionchanged:: 1.0.1 Added a cache for storing the chrip function between subsequent calls @@ -206,7 +210,7 @@ def coherent(t, timeseries, central_freq, sample_rate, dm, taper=False, previous timeIn = np.zeros(N, dtype=t.dtype) dataIn = np.zeros(N, dtype=timeseries.dtype) - if previous_data is not None: + if previous_time is not None and previous_data is not None: try: timeIn[:-start] = previous_time[start:] dataIn[:-start] = previous_data[start:] @@ -220,7 +224,7 @@ def coherent(t, timeseries, central_freq, sample_rate, dm, taper=False, previous timeIn = np.zeros(N, dtype=t.dtype) dataIn = np.zeros(N, dtype=timeseries.dtype) - if next_data is not None: + if next_time is not None and next_data is not None: try: timeIn[timeseries.size-start:] = next_time[:(dataIn.size-(timeseries.size-start))] dataIn[timeseries.size-start:] = next_data[:(dataIn.size-(timeseries.size-start))] From f8ea041698d58a492f03ea0e78c743ca3abb1394 Mon Sep 17 00:00:00 2001 From: jaycedowell Date: Fri, 12 Jul 2024 07:17:05 -0600 Subject: [PATCH 18/18] More hints. --- lsl/misc/parser.py | 136 ++++++++++++++++++++--------------------- lsl/misc/rfutils.py | 56 +++++++++-------- lsl/misc/scattering.py | 19 +++--- 3 files changed, 105 insertions(+), 106 deletions(-) diff --git a/lsl/misc/parser.py b/lsl/misc/parser.py index d28a05de..e6c2cb1f 100644 --- a/lsl/misc/parser.py +++ b/lsl/misc/parser.py @@ -19,6 +19,8 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import List, Tuple, Union + __version__ = '0.1' __all__ = ['positive_or_zero_int', 'positive_int', 'positive_or_zero_float', @@ -28,7 +30,7 @@ 'csv_baseline_list', 'csv_hostname_list'] -def positive_or_zero_int(string): +def positive_or_zero_int(string: str) -> int: """ Convert a string to a positive (>=0) integer. """ @@ -44,7 +46,7 @@ def positive_or_zero_int(string): return value -def positive_int(string): +def positive_int(string: str) -> int: """ Convert a string to a positive (>0) integer. """ @@ -56,7 +58,7 @@ def positive_int(string): return value -def positive_or_zero_float(string): +def positive_or_zero_float(string: str) -> float: """ Convert a string to a positive (>=0.0) float. """ @@ -72,7 +74,7 @@ def positive_or_zero_float(string): return value -def positive_float(string): +def positive_float(string: str) -> float: """ Convert a string to a positive (>0.0) float. """ @@ -84,7 +86,7 @@ def positive_float(string): return value -def _get_units(string): +def _get_units(string: str) -> Union[str,None]: """ Function to search a string, starting at the end, to find units. """ @@ -102,44 +104,44 @@ def _get_units(string): return units -def _quantitiy_to_hz(value): +def _quantitiy_to_hz(value: str) -> float: """ Convert a string into a frequency. If no unit is provided, MHz is assumed. """ try: - value = float(value) - value *= 1e6 + pvalue = float(value) + pvalue *= 1e6 except ValueError: try: - value = units.quantity.Quantity(value) - value = value.to(units.Hz, equivalencies=units.spectral()).value + qvalue = units.quantity.Quantity(value) + pvalue = qvalue.to(units.Hz, equivalencies=units.spectral()).value except Exception as e: msg = f"{value} {str(e)}" raise ArgumentTypeError(msg) - return value + return pvalue -def _quantitiy_to_m(value): +def _quantitiy_to_m(value: str) -> float: """ Convert a string into a wavelength. If no unit is provided, m is assumed. """ try: - value = float(value) + pvalue = float(value) except ValueError: try: - value = units.quantity.Quantity(value) - value = value.to(units.meter, equivalencies=units.spectral()).value + qvalue = units.quantity.Quantity(value) + pvalue = qvalue.to(units.meter, equivalencies=units.spectral()).value except Exception as e: msg = f"{value} {str(e)}" raise ArgumentTypeError(msg) - return value + return pvalue -def _frequency_conversion_base(string): +def _frequency_conversion_base(string: str) -> Union[float,List[float]]: """ Convert a frequency to a float Hz value. This function accepts a variety of string formats: @@ -156,7 +158,7 @@ def _frequency_conversion_base(string): """ try: - value = float(string)*1e6 + pvalue = float(string)*1e6 except ValueError: fields = string.split('~', 1) try: @@ -170,15 +172,16 @@ def _frequency_conversion_base(string): start = start+units2 except ValueError: start, stop = fields[0], None - value = _quantitiy_to_hz(start) + pvalue = _quantitiy_to_hz(start) if stop is not None: - value = [value, _quantitiy_to_hz(stop)] - if value[1] < value[0]: - value.reverse() - return value + rvalue = [pvalue, _quantitiy_to_hz(stop)] + if rvalue[1] < rvalue[0]: + rvalue.reverse() + pvalue = rvalue # type: ignore + return pvalue -def frequency(string): +def frequency(string: str) -> float: """ Convert a frequency to a float Hz value. This function accepts a variety of string formats: @@ -189,16 +192,13 @@ def frequency(string): """ value = _frequency_conversion_base(string) - try: - len(value) + if isinstance(value, List): msg = f"{string} does not appear to be a single frequency" raise ArgumentTypeError(msg) - except TypeError: - pass return value -def frequency_range(string): +def frequency_range(string: str) -> List[float]: """ Convert a frequency to a float Hz value. This function accepts a variety of string formats: @@ -211,15 +211,13 @@ def frequency_range(string): """ value = _frequency_conversion_base(string) - try: - len(value) - except TypeError: + if not isinstance(value, List): msg = f"{string} does not appear to be a frequency range" raise ArgumentTypeError(msg) return value -def _wavelength_conversion_base(string): +def _wavelength_conversion_base(string: str) -> Union[float,List[float]]: """ Convert a wavelength to a float m value. This function accepts a variety of string formats: @@ -236,7 +234,7 @@ def _wavelength_conversion_base(string): """ try: - value = float(string) + pvalue = float(string) except ValueError: fields = string.split('~', 1) try: @@ -250,15 +248,16 @@ def _wavelength_conversion_base(string): start = start+units2 except ValueError: start, stop = fields[0], None - value = _quantitiy_to_m(start) + pvalue = _quantitiy_to_m(start) if stop is not None: - value = [value, _quantitiy_to_m(stop)] - if value[1] < value[0]: - value.reverse() - return value + rvalue = [pvalue, _quantitiy_to_m(stop)] + if rvalue[1] < rvalue[0]: + rvalue.reverse() + pvalue = rvalue # type: ignore + return pvalue -def wavelength(string): +def wavelength(string: str) -> float: """ Convert a wavelength to a float m value. This function accepts a variety of string formats: @@ -269,16 +268,13 @@ def wavelength(string): """ value = _wavelength_conversion_base(string) - try: - len(value) + if isinstance(value, List): msg = f"{string} does not appear to be a single wavelength" raise ArgumentTypeError(msg) - except TypeError: - pass return value -def wavelength_range(string): +def wavelength_range(string: str) -> List[float]: """ Convert a wavelength to a float m value. This function accepts a variety of string formats: @@ -291,15 +287,13 @@ def wavelength_range(string): """ value = _wavelength_conversion_base(string) - try: - len(value) - except TypeError: + if not isinstance(value, List): msg = f"{string} does not appear to be a wavelength range" raise ArgumentTypeError(msg) return value -def date(string): +def date(string: str) -> str: """ Convert a data as either a YYYY[-/]MM[-/]DD or MJD string into a YYYY/MM/DD string. @@ -320,7 +314,7 @@ def date(string): return date -def mjd(string): +def mjd(string: str) -> int: """ Convert a data as either a YYYY[-/]MM[-/]DD or MJD string into an integer MJD. @@ -340,7 +334,7 @@ def mjd(string): return mjd -def time(string): +def time(string: str) -> str: """ Covnert a time as HH:MM:SS[.SSS] or MPM string into a HH:MM:SS.SSSSSS string. @@ -369,7 +363,7 @@ def time(string): return stime -def mpm(string): +def mpm(string: str) -> int: """ Covnert a time as HH:MM:SS[.SSS] or MPM string into an MPM integer. """ @@ -392,7 +386,7 @@ def mpm(string): return mpm -def hours(string): +def hours(string: str) -> ephem.hours: """ Convert a 'HH[:MM[:SS[.SSS]]]' string into an ephem.hours instance. """ @@ -405,7 +399,7 @@ def hours(string): return value -def csv_hours_list(string): +def csv_hours_list(string:str) -> List[ephem.hours]: """ Convert a comma-separated list of 'HH[:MM[:SS.[SSS]]]' string into a list of ephem.hours instances. @@ -421,7 +415,7 @@ def csv_hours_list(string): return value -def degrees(string): +def degrees(string: str) -> ephem.degrees: """ Convert a 'sDD[:MM[:SS[.SSS]]]' string into an ephem.degrees instance. """ @@ -434,7 +428,7 @@ def degrees(string): return value -def csv_degrees_list(string): +def csv_degrees_list(string: str) -> List[ephem.degrees]: """ Convert a comma-separated list of 'sDD[:MM[:SS.[SSS]]]' string into a list of ephem.degrees instances. @@ -450,17 +444,17 @@ def csv_degrees_list(string): return value -def _int_item_or_range(string): +def _int_item_or_range(string: str) -> List[int]: if string.find('~') != -1: start, stop = string.split('~', 1) - start, stop = int(start, 10), int(stop, 10) - value = list(range(start, stop+1)) + startv, stopv = int(start, 10), int(stop, 10) + value = list(range(startv, stopv+1)) else: value = [int(string, 10),] return value -def csv_int_list(string): +def csv_int_list(string: str) -> Union[List[int],str]: """ Convert a comma-separated list of integers into a list of integers. This function also allows for ranges to be specifed using the '~' character. @@ -472,7 +466,7 @@ def csv_int_list(string): elif string in ('none', ''): value = 'none' else: - value = [] + lvalue = [] for item in string.split(','): item = item.strip().rstrip() if item == '': @@ -482,11 +476,12 @@ def csv_int_list(string): except ValueError: msg = f"{string} contains non-integer values" raise ArgumentTypeError(msg) - value.extend(subvalue) + lvalue.extend(subvalue) + value = lvalue # type: ignore return value -def csv_baseline_list(string): +def csv_baseline_list(string: str) -> Union[List[Tuple[int,int]],str]: """ Convert a comma-separated list of baslines pairs into a list of baseline pairs. Baseline pairs are defined as 'antenna1-antenna2' where 'antenna1' @@ -498,7 +493,7 @@ def csv_baseline_list(string): elif string in ('none', ''): value = 'none' else: - value = [] + lvalue = [] for item in string.split(','): item = item.strip().rstrip() if item == '': @@ -509,14 +504,15 @@ def csv_baseline_list(string): msg = f"{string} contains non-baseline or non-integer values" raise ArgumentTypeError(msg) try: - ant1 = _int_item_or_range(ant1) - ant2 = _int_item_or_range(ant2) + ant1l = _int_item_or_range(ant1) + ant2l = _int_item_or_range(ant2) except ValueError: msg = f"{string} contains non-integer values" raise ArgumentTypeError(msg) - for i in ant1: - for j in ant2: - value.append( (i,j) ) + for i in ant1l: + for j in ant2l: + lvalue.append( (i,j) ) + value = lvalue # type: ignore return value @@ -526,7 +522,7 @@ def csv_baseline_list(string): _HOSTNAME_RANGE_RE=re.compile(r'^(?P[a-zA-Z-]*?)(?P[0-9]+)~(?P[0-9]+)$') -def csv_hostname_list(string): +def csv_hostname_list(string: str) -> List[str]: """ Convert a comma-separated list of IPv4 addresses/hostnames into a list IPv4 addresses/hostnames. This function support indexing with the '~' diff --git a/lsl/misc/rfutils.py b/lsl/misc/rfutils.py index a853b7ef..380ffe91 100644 --- a/lsl/misc/rfutils.py +++ b/lsl/misc/rfutils.py @@ -14,13 +14,15 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Optional, Union + __version__ = '0.1' __all__ = ['dBd_to_dBi', 'dBd_to_dBi', 'dBi_to_gain', 'dBd_to_gain', 'gain_to_dBi', 'gain_to_dBd', 'calculate_sefd', 'calculate_effective_area', 'Jy_to_dBm', 'dBm_to_Jy'] -def _make_quantity(value, default_units): +def _make_quantity(value: Union[str,float,units.quantity.Quantity], default_units: units.Unit) -> Union[units.quantity.Quantity,None]: """ Helper function for taking values and giving them units. """ @@ -33,7 +35,7 @@ def _make_quantity(value, default_units): return value -def dBd_to_dBi(dBd): +def dBd_to_dBi(dBd: float) -> float: """ Convert an antenna gain in dBb (gain relative to the maximum gain of a half-wave dipole) into a gain in dBi (gain relative to an isotropic @@ -43,7 +45,7 @@ def dBd_to_dBi(dBd): return dBd + 2.14844 -def dBi_to_dBd(dBi): +def dBi_to_dBd(dBi: float) -> float: """ Convert and antenna gain dBi (gain relative to an isotropic antenna) into a gain in dBd (gain relative to the maximum gain of a half-wave @@ -53,7 +55,7 @@ def dBi_to_dBd(dBi): return dBi - 2.14844 -def dBi_to_gain(dBi, frequency): +def dBi_to_gain(dBi: float, frequency: Union[str,float,units.quantity.Quantity]) -> float: """ Convert an antenna gain in dBi (gain relative to an isotropic antenna) at a particular frequency in Hz into a gain in K/Jy. @@ -65,13 +67,13 @@ def dBi_to_gain(dBi, frequency): # 10^(dBi/10) is the ratio of the effective area of the antenna relative # to an isotropic antenna where the area of the isotropic antenna is # given by: wavelength^2 / 4 / pi - gain = speedOfLight**2 / (8.0*math.pi*kB*frequency**2) * 10**(dBi/10.0) + gain = speedOfLight**2 / (8.0*math.pi*kB*frequency**2) * 10**(dBi/10.0) # type: ignore gain = gain.to('K/Jy').value return gain -def dBd_to_gain(dBd, frequency): +def dBd_to_gain(dBd: float, frequency: Union[str,float,units.quantity.Quantity]) -> float: """ Convert an antenna gain in dBd (gain relative to the maximum gain of a half-wave dipole) at a particular frequency in Hz into a gain in K/Jy @@ -81,7 +83,7 @@ def dBd_to_gain(dBd, frequency): return dBi_to_gain(dBi, frequency) -def gain_to_dBi(gain, frequency): +def gain_to_dBi(gain: Union[str,float,units.quantity.Quantity], frequency: Union[str,float,units.quantity.Quantity]) -> float: """ Convert an antenna gain in K/Jy at a particular frequency in Hz into a gain in dBi (gain relative to an isotropic antenna). @@ -92,13 +94,13 @@ def gain_to_dBi(gain, frequency): frequency = _make_quantity(frequency, units.Hz) # Calculate the area ratio - areaRatio = 8.0*math.pi*kB*frequency**2 / speedOfLight**2 * gain + areaRatio = 8.0*math.pi*kB*frequency**2 / speedOfLight**2 * gain # type: ignore # Convert to dBi return 10.0*math.log10(areaRatio) -def gain_to_dBd(gain, frequency): +def gain_to_dBd(gain: Union[str,float,units.quantity.Quantity], frequency: Union[str,float,units.quantity.Quantity]) -> float: """ Convert an antenna gain in K/Jy at a particular frequency in Hz into a gain in dBd (gain relative to the maximum gain of a half-wave dipole). @@ -108,7 +110,7 @@ def gain_to_dBd(gain, frequency): return dBi_to_dBd(dBi) -def calculate_sefd(Tsys, gain=None, area=None, efficiency=None): +def calculate_sefd(Tsys: Union[str,float,units.quantity.Quantity], gain: Optional[Union[str,float,units.quantity.Quantity]]=None, area: Optional[Union[str,float,units.quantity.Quantity]]=None, efficiency: Optional[float]=None) -> float: """ Given a variety of parameters, calculate the system equivalent flux density in Jy for an antenna. The parameters are: @@ -122,27 +124,28 @@ def calculate_sefd(Tsys, gain=None, area=None, efficiency=None): """ # Deal with units - Tsys = _make_quantity(Tsys, units.K) - gain = _make_quantity(gain, units.K/units.Jy) - area = _make_quantity(area, units.meter**2) + Tsysq = _make_quantity(Tsys, units.K) + gainq = _make_quantity(gain, units.K/units.Jy) + areaq = _make_quantity(area, units.meter**2) # Have we been supplied a gain or do we need to calculate it? - if gain is None: + if gainq is None: ## Do we have everything we need to calculate the gain? - if area is None or efficiency is None: + if area is not None and efficiency is not None: + ## Looks like it + gainq = efficiency*areaq / 2.0 / kB # type: ignore + else: + ## Can't raise RuntimeError("Too few parameters supplied to calculate the SEFD") - ## Looks like it - gain = efficiency*area / 2.0 / kB - # Calculate the SEFD - SEFD = Tsys / gain + SEFD = Tsysq / gainq SEFD = SEFD.to('Jy').value return SEFD -def calculate_effective_area(gain): +def calculate_effective_area(gain: Union[str,float,units.quantity.Quantity]) -> float: """ Given the gain of an antenna in K/Jy, calculate the effective collecting area in square meters. @@ -158,7 +161,7 @@ def calculate_effective_area(gain): return area -def Jy_to_dBm(flux, bandwidth, gain): +def Jy_to_dBm(flux: Union[str,float,units.quantity.Quantity], bandwidth: Union[str,float,units.quantity.Quantity], gain: Union[str,float,units.quantity.Quantity]) -> float: """ Convert a flux density in Jy into a received signal strength in dBm under the assumptions of: @@ -173,17 +176,17 @@ def Jy_to_dBm(flux, bandwidth, gain): # Antenna parameters area = calculate_effective_area(gain) - area = _make_quantity(area, units.meter**2) + area = _make_quantity(area, units.meter**2) # type: ignore # Power in mW - P = flux*bandwidth*area + P = flux*bandwidth*area # type: ignore P = P.to('mW').value # To dBm return 10.0*math.log10(P) -def dBm_to_Jy(dBm, bandwidth, gain): +def dBm_to_Jy(dBm: float, bandwidth: Union[str,float,units.quantity.Quantity], gain: Union[str,float,units.quantity.Quantity]) -> float: """ Convert a received signal strength in dBm into a flux density in Jy under the assumptions of: @@ -197,14 +200,13 @@ def dBm_to_Jy(dBm, bandwidth, gain): # Antenna parameters area = calculate_effective_area(gain) - area = _make_quantity(area, units.meter**2) + area = _make_quantity(area, units.meter**2) # type: ignore # Power in mW P = 10**(dBm/10.0)*(units.W/1000) # To Jy - flux = P / (bandwidth*area) + flux = P / (bandwidth*area) # type: ignore flux = flux.to('Jy').value return flux - diff --git a/lsl/misc/scattering.py b/lsl/misc/scattering.py index 83009435..63a35cc5 100644 --- a/lsl/misc/scattering.py +++ b/lsl/misc/scattering.py @@ -17,12 +17,14 @@ from lsl.misc import telemetry telemetry.track_module() +from typing import Callable, Tuple + __version__ = "0.1" __all__ = ['thin', 'thick', 'uniform', 'unscatter'] -def thin(t, tau): +def thin(t: np.ndarray, tau: float) -> np.ndarray: """ Pulsar broadening function for multi-path scattering through a thin screen. @@ -35,7 +37,7 @@ def thin(t, tau): return g -def thick(t, tau): +def thick(t: np.ndarray, tau: float) -> np.ndarray: """ Pulse broadening function for multi-path scattering through a thick screen. @@ -54,7 +56,7 @@ def thick(t, tau): return g -def uniform(t, tau): +def uniform(t: np.ndarray, tau: float) -> np.ndarray: """ Pulsr broadening function for multi-path scattering through a uniform screen. @@ -73,7 +75,7 @@ def uniform(t, tau): return g -def _positivity(t, raw, resids, cc): +def _positivity(t: np.ndarray, raw: np.ndarray, resids: np.ndarray, cc: np.ndarray) -> float: """ Compute the positivity of the residual profile following Equation 10 of Bhat, N., Cordes, J., & Chatterjee, S. 2003, ApJ, @@ -95,7 +97,7 @@ def _positivity(t, raw, resids, cc): return f -def _skewness(t, raw, resids, cc): +def _skewness(t: np.ndarray, raw: np.ndarray, resids: np.ndarray, cc: np.ndarray) -> float: """ Compute the skewness for a collection of clean components following Equation 12 of Bhat, N., Cordes, J., & Chatterjee, S. 2003, ApJ, @@ -109,7 +111,7 @@ def _skewness(t, raw, resids, cc): return t3 / (t2 + 1e-15)**(3./2.) -def _figure_of_merit(t, raw, resids, cc): +def _figure_of_merit(t: np.ndarray, raw: np.ndarray, resids: np.ndarray, cc: np.ndarray) -> float: """ Figure of merit for deconvolution that combines the positivity of the residuals, the skewness of the clean components, the RMS of the @@ -124,8 +126,7 @@ def _figure_of_merit(t, raw, resids, cc): s = robust.std(raw) # Find the fraction of noise-like points in the residuals - n = len( np.where( np.abs(resids - m) <= 3*s )[0] ) - n = float(n) / raw.size + n = len( np.where( np.abs(resids - m) <= 3*s )[0] ) / raw.size # Compute the positivity and skewness f = _positivity(t, raw, resids, cc) @@ -140,7 +141,7 @@ def _figure_of_merit(t, raw, resids, cc): return (f + g)/2.0 + r - n -def unscatter(t, raw, tScatMin, tScatMax, tScatStep, gain=0.05, max_iter=10000, screen=thin, verbose=True): +def unscatter(t: np.ndarray, raw: np.ndarray, tScatMin: float, tScatMax: float, tScatStep: float, gain: float=0.05, max_iter: int=10000, screen: Callable[[np.ndarray,float],np.ndarray]=thin, verbose: bool=True) -> Tuple[float,float,np.ndarray]: """ Multi-path scattering deconvolution method based on the method presented in Bhat, N., Cordes, J., & Chatterjee, S. 2003, ApJ,