diff --git a/sourcefinder/accessors/aartfaaccasaimage.py b/sourcefinder/accessors/aartfaaccasaimage.py index 0dd2b5d..0298e9b 100644 --- a/sourcefinder/accessors/aartfaaccasaimage.py +++ b/sourcefinder/accessors/aartfaaccasaimage.py @@ -9,7 +9,7 @@ class AartfaacCasaImage(CasaImage): def __init__(self, url, plane=0, beam=None): - super(AartfaacCasaImage, self).__init__(url, plane=0, beam=None) + super().__init__(url, plane=0, beam=None) table = casacore_table(self.url, ack=False) self.taustart_ts = self.parse_taustartts(table) self.telescope = table.getkeyword('coords')['telescope'] diff --git a/sourcefinder/accessors/amicasaimage.py b/sourcefinder/accessors/amicasaimage.py index bb53fb7..b836934 100644 --- a/sourcefinder/accessors/amicasaimage.py +++ b/sourcefinder/accessors/amicasaimage.py @@ -30,7 +30,7 @@ class AmiCasaImage(CasaImage): """ def __init__(self, url, plane=0, beam=None): - super(AmiCasaImage, self).__init__(url, plane, beam) + super().__init__(url, plane, beam) table = casacore_table(self.url, ack=False) self.taustart_ts = self.parse_taustartts(table) self.tau_time = 1 # Placeholder value until properly implemented diff --git a/sourcefinder/accessors/casaimage.py b/sourcefinder/accessors/casaimage.py index 920e481..98f3ca9 100644 --- a/sourcefinder/accessors/casaimage.py +++ b/sourcefinder/accessors/casaimage.py @@ -24,7 +24,7 @@ class CasaImage(DataAccessor): """ def __init__(self, url, plane=0, beam=None): - super(CasaImage, self).__init__() + # super().__init__() self.url = url # we don't want the table as a property since it makes the accessor diff --git a/sourcefinder/accessors/dataaccessor.py b/sourcefinder/accessors/dataaccessor.py index a96ad5a..140860d 100644 --- a/sourcefinder/accessors/dataaccessor.py +++ b/sourcefinder/accessors/dataaccessor.py @@ -1,80 +1,80 @@ import logging -from math import degrees, sqrt, sin, pi, cos -from sourcefinder.accessors.requiredatts import RequiredAttributesMetaclass +import numpy +from math import degrees, sqrt, sin, pi, cos +from dataclasses import dataclass +from sourcefinder.utility.coordinates import WCS logger = logging.getLogger(__name__) -class DataAccessor(object, metaclass=RequiredAttributesMetaclass): - _required_attributes = [ - 'beam', - 'centre_ra', - 'centre_decl', - 'data', - 'freq_bw', - 'freq_eff', - 'pixelsize', - 'tau_time', - 'taustart_ts', - 'url', - 'wcs', - ] - - def __init__(self): - # Sphinx only picks up the class docstring if it's under an __init__ - # *le sigh* - """ - Base class for accessors used with - :class:`sourcefinder.image.ImageData`. - - Data accessors provide a uniform way for the ImageData class (ie, - generic image representation) to access the various ways in which - images may be stored (FITS files, arrays in memory, potentially HDF5, - etc). - - This class cannot be instantiated directly, but should be subclassed - and the abstract properties provided. Note that all abstract - properties are required to provide a valid accessor. - - Additional properties may also be provided by subclasses. However, - TraP components are required to degrade gracefully in the absence of - this optional properties. - - The required attributes are as follows: - - Attributes: - beam (tuple): Restoring beam. Tuple of three floats: - semi-major axis (in pixels), semi-minor axis (pixels) - and position angle (radians). - centre_ra (float): Right ascension at the central pixel of the image. - Units of J2000 decimal degrees. - centre_decl (float): Declination at the central pixel of the image. - Units of J2000 decimal degrees. - data(numpy.ndarray): Two dimensional numpy.ndarray of floating point - pixel values. - (TODO: Definitive statement on orientation/transposing.) - freq_bw(float): The frequency bandwidth of this image in Hz. - freq_eff(float): Effective frequency of the image in Hz. - That is, the mean frequency of all the visibility data which - comprises this image. - pixelsize(tuple): (x, y) tuple representing the size of a pixel - along each axis in units of degrees. - tau_time(float): Total time on sky in seconds. - taustart_ts(float): Timestamp of the first integration which - constitutes part of this image. MJD in seconds. - url(string): A (string) URL representing the location of the image - at time of processing. - wcs(:class:`sourcefinder.utility.coordinates.WCS`): An instance of - :py:class:`sourcefinder.utility.coordinates.WCS`, - describing the mapping from data pixels to sky-coordinates. - - The class also provides some common functionality: - static methods used for parsing datafiles, and an 'extract_metadata' - function which provides key info in a simple dict format. - """ - - def extract_metadata(self): +@dataclass +class DataAccessor: + # Sphinx only picks up the class docstring if it's under an __init__ + # *le sigh* + """ + Base class for accessors used with + :class:`sourcefinder.image.ImageData`. + + Data accessors provide a uniform way for the ImageData class (ie, + generic image representation) to access the various ways in which + images may be stored (FITS files, arrays in memory, potentially HDF5, + etc). + + This class cannot be instantiated directly, but should be subclassed + and the abstract properties provided. Note that all abstract + properties are required to provide a valid accessor. + + Additional properties may also be provided by subclasses. However, + TraP components are required to degrade gracefully in the absence of + this optional properties. + + The required attributes are as follows: + + Attributes: + beam (tuple): Restoring beam. Tuple of three floats: + semi-major axis (in pixels), semi-minor axis (pixels) + and position angle (radians). + centre_ra (float): Right ascension at the central pixel of the image. + Units of J2000 decimal degrees. + centre_decl (float): Declination at the central pixel of the image. + Units of J2000 decimal degrees. + data(numpy.ndarray): Two dimensional numpy.ndarray of floating point + pixel values. + (TODO: Definitive statement on orientation/transposing.) + freq_bw(float): The frequency bandwidth of this image in Hz. + freq_eff(float): Effective frequency of the image in Hz. + That is, the mean frequency of all the visibility data which + comprises this image. + pixelsize(tuple): (x, y) tuple representing the size of a pixel + along each axis in units of degrees. + tau_time(float): Total time on sky in seconds. + taustart_ts(float): Timestamp of the first integration which + constitutes part of this image. MJD in seconds. + url(string): A (string) URL representing the location of the image + at time of processing. + wcs(:class:`sourcefinder.utility.coordinates.WCS`): An instance of + :py:class:`sourcefinder.utility.coordinates.WCS`, + describing the mapping from data pixels to sky-coordinates. + + The class also provides some common functionality: + static methods used for parsing datafiles, and an 'extract_metadata' + function which provides key info in a simple dict format. + """ + + beam: tuple + centre_ra: float + centre_decl: float + data: numpy.ndarray + freq_bw: float + freq_eff: float + pixelsize: tuple + tau_time: float + taustart_ts: float + url: str + wcs: WCS + + def extract_metadata(self) -> dict: """ Massage the class attributes into a flat dictionary with database-friendly values. @@ -155,4 +155,4 @@ def degrees2pixels(bmaj, bmin, bpa, deltax, deltay): (sin(pi * bpa / 180.) ** 2) / (deltay ** 2)) ) theta = pi * bpa / 180 - return (semimaj, semimin, theta) + return semimaj, semimin, theta diff --git a/sourcefinder/accessors/fitsimage.py b/sourcefinder/accessors/fitsimage.py index d4e34e2..d654fef 100644 --- a/sourcefinder/accessors/fitsimage.py +++ b/sourcefinder/accessors/fitsimage.py @@ -12,6 +12,7 @@ logger = logging.getLogger(__name__) + class FitsImage(DataAccessor): """ Use PyFITS to pull image data out of a FITS file. @@ -21,7 +22,6 @@ class FitsImage(DataAccessor): header. """ def __init__(self, url, plane=None, beam=None, hdu_index=0): - super(FitsImage, self).__init__() self.url = url self.header = self._get_header(hdu_index) self.wcs = self.parse_coordinates() @@ -69,7 +69,6 @@ def read_data(self, hdu_index, plane): data = data.transpose() return data - def parse_coordinates(self): """Returns a WCS object""" header = self.header diff --git a/sourcefinder/accessors/fitsimageblob.py b/sourcefinder/accessors/fitsimageblob.py index 3de1ce9..54c05bd 100644 --- a/sourcefinder/accessors/fitsimageblob.py +++ b/sourcefinder/accessors/fitsimageblob.py @@ -16,7 +16,7 @@ class FitsImageBlob(FitsImage): def __init__(self, hdulist, plane=None, beam=None, hdu_index=0): # set the URL in case we need it during header parsing for error loggign self.url = "AARTFAAC streaming image" - super(FitsImage, self).__init__() + # super(FitsImage, self).__init__() self.header = self._get_header(hdulist, hdu_index) self.wcs = self.parse_coordinates() @@ -42,10 +42,15 @@ def __init__(self, hdulist, plane=None, beam=None, hdu_index=0): if 'TELESCOP' in self.header: self.telescope = self.header['TELESCOP'] - def _get_header(self, hdulist, hdu_index): + def _get_header(self, *args): + hdulist = args[0] + hdu_index = args[1] return hdulist[hdu_index].header - def read_data(self, hdulist, hdu_index, plane): + def read_data(self, *args): + hdulist = args[0] + hdu_index = args[1] + plane = args[2] hdu = hdulist[hdu_index] data = numpy.float64(hdu.data.squeeze()) if plane is not None and len(data.shape) > 2: diff --git a/sourcefinder/accessors/kat7casaimage.py b/sourcefinder/accessors/kat7casaimage.py index 4503eb6..70af4ff 100644 --- a/sourcefinder/accessors/kat7casaimage.py +++ b/sourcefinder/accessors/kat7casaimage.py @@ -29,7 +29,7 @@ class Kat7CasaImage(CasaImage): """ def __init__(self, url, plane=0, beam=None): - super(Kat7CasaImage, self).__init__(url, plane, beam) + super().__init__(url, plane, beam) table = casacore_table(self.url, ack=False) self.taustart_ts = self.parse_taustartts(table) self.tau_time = 1 # Placeholder value diff --git a/sourcefinder/accessors/lofaraccessor.py b/sourcefinder/accessors/lofaraccessor.py index 348b83b..bde48c2 100644 --- a/sourcefinder/accessors/lofaraccessor.py +++ b/sourcefinder/accessors/lofaraccessor.py @@ -1,7 +1,8 @@ -from sourcefinder.accessors.dataaccessor import RequiredAttributesMetaclass +from dataclasses import dataclass -class LofarAccessor(object, metaclass=RequiredAttributesMetaclass): +@dataclass +class LofarAccessor(): """ Additional metadata required for processing LOFAR images through QC checks. @@ -15,11 +16,10 @@ class LofarAccessor(object, metaclass=RequiredAttributesMetaclass): subbandwidth(float): Width of a subband in Hz. subbands(int): Number of subbands. """ - _required_attributes = [ - 'antenna_set', - 'ncore', - 'nremote', - 'nintl', - 'subbandwidth', - 'subbands', - ] + + antenna_set: str + ncore: int + nremote: int + nintl: int + subbandwidth: float + subbands: int diff --git a/sourcefinder/accessors/lofarcasaimage.py b/sourcefinder/accessors/lofarcasaimage.py index 6fbc4fc..3b234a1 100644 --- a/sourcefinder/accessors/lofarcasaimage.py +++ b/sourcefinder/accessors/lofarcasaimage.py @@ -44,7 +44,7 @@ class LofarCasaImage(CasaImage, LofarAccessor): """ def __init__(self, url, plane=0, beam=None): - super(LofarCasaImage, self).__init__(url, plane, beam) + super().__init__(url, plane, beam) table = casacore_table(self.url, ack=False) subtables = self.open_subtables(table) self.taustart_ts = self.parse_taustartts(subtables) diff --git a/sourcefinder/accessors/lofarfitsimage.py b/sourcefinder/accessors/lofarfitsimage.py index 124172e..d21e23f 100644 --- a/sourcefinder/accessors/lofarfitsimage.py +++ b/sourcefinder/accessors/lofarfitsimage.py @@ -4,7 +4,7 @@ class LofarFitsImage(FitsImage, LofarAccessor): def __init__(self, url, plane=False, beam=False, hdu=0): - super(LofarFitsImage, self).__init__(url, plane, beam, hdu) + super().__init__(url, plane, beam, hdu) header = self._get_header(hdu) self.antenna_set = header['ANTENNA'] self.ncore = header['NCORE'] diff --git a/sourcefinder/accessors/lofarhdf5image.py b/sourcefinder/accessors/lofarhdf5image.py index cecbd41..246f366 100644 --- a/sourcefinder/accessors/lofarhdf5image.py +++ b/sourcefinder/accessors/lofarhdf5image.py @@ -6,7 +6,6 @@ class LofarHdf5Image(DataAccessor): # Not currently instantiable; LOFAR HDF5 images are not in use def __init__(self, source, plane=False, beam=False): - super(LofarHdf5Image, self).__init__() # Set defaults self.plane = plane diff --git a/sourcefinder/accessors/requiredatts.py b/sourcefinder/accessors/requiredatts.py deleted file mode 100644 index db2e986..0000000 --- a/sourcefinder/accessors/requiredatts.py +++ /dev/null @@ -1,77 +0,0 @@ -from abc import ABCMeta - - -class RequiredAttributesMetaclass(ABCMeta): - """ - Provides instantiation-time checking for 'required attributes'. - - This can be used to define an 'interface' of sorts. Use it to define - an abstract base class that specifies all the attributes you expect - to see from user-defined classes in a given situation. - - Usage: - Set this as the metaclass for an abstract base class which defines an - iterable, ``_required_attributes``, as a class-attribute, - e.g. (in Python 2 syntax):: - - class BasicRequirements(object): - __metaclass__ = RequiredAttributesMetaclass - _required_attributes = [ - 'foo', - 'bar', - ] - - Then, any inheriting classes will only instantiate if all the required - attributes are both defined and not None. - Defining the inheriting class works as normal, e.g.:: - - class SomeBasicClass(BasicRequirements): - def __init__(self): - self.foo = 123 - self.bar = 456 - - The metaclass supports use of 'mixin' abstract - classes that specify additional (possibly overlapping) - requirement sets, so e.g. we could have create an extra - set of requirements:: - - class ExtraRequirements(object): - __metaclass__ = RequiredAttributesMetaclass - _required_attributes = [ - 'bar', - 'baz', - ] - - and use it to provide instantiation-time checking for - a class that requires all of the above:: - - class SomeSpecialClass(BasicRequirements,ExtraRequirements): - def __init__(self): - self.foo = 12 - self.bar = 34 - self.baz = 56 - - - Finally, note that we inherit from metaclass ABCMeta, - so any use of e.g. @abstractmethod, @abstractproperty - decorators will work the same as with a regular - ABCMeta abstract base class. - - """ - - def __call__(cls, *args, **kwargs): - obj = type.__call__(cls, *args, **kwargs) - required_atts = set() - for cls in obj.__class__.__mro__: - if hasattr(cls, '_required_attributes'): - required_atts = required_atts.union(cls._required_attributes) - for attr in required_atts: - if not hasattr(obj, attr) or (getattr(obj, attr) is None): - raise NotImplementedError( - "Uninitialized attribute: {}\n" - "(Class '{}' must initialize attributes {})".format( - attr, - obj.__class__.__name__, - required_atts) - ) - return obj diff --git a/test/test_image.py b/test/test_image.py index 6176d02..e760644 100644 --- a/test/test_image.py +++ b/test/test_image.py @@ -244,7 +244,8 @@ def testForcedFitAtNans(self): nanbox_radius *= boxsize_proportion nandata[int(x0 - nanbox_radius):int(x0 + nanbox_radius + 1), - int(y0 - nanbox_radius):int(y0 + nanbox_radius + 1)] = float('nan') + int(y0 - nanbox_radius):int(y0 + nanbox_radius + 1)] = \ + float('nan') # Dump image data for manual inspection: # import astropy.io.fits as fits @@ -253,7 +254,6 @@ def testForcedFitAtNans(self): # hdu = fits.PrimaryHDU((output_data).transpose()) # hdu.writeto('/tmp/nandata.fits',clobber=True) - nan_image = ImageData(nandata, beam=self.image.beam, wcs=self.image.wcs)