Skip to content

Commit 5646fd8

Browse files
authored
Merge pull request #147 from transientskp/Fix_143
Settles part of the discussion from #143 that we want to settle.
2 parents f3bad7b + eedf56d commit 5646fd8

File tree

7 files changed

+92
-40
lines changed

7 files changed

+92
-40
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ extend-exclude ='''
9191
'''
9292

9393
[tool.mypy]
94+
python_version = "3.12"
9495
ignore_missing_imports = true
9596
check_untyped_defs = true
9697
files = "sourcefinder"

sourcefinder/accessors/casaimage.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from casacore.tables import table as casacore_table
66

7+
from sourcefinder.utils import is_valid_beam_tuple
78
from sourcefinder.accessors.dataaccessor import DataAccessor
89
from sourcefinder.utility.coordinates import WCS
910
from sourcefinder.utility.coordinates import mjd2datetime
@@ -48,12 +49,15 @@ def __init__(self, url, plane=0, beam=None):
4849
self.freq_eff, self.freq_bw = self.parse_frequency(table)
4950
self.pixelsize = self.parse_pixelsize()
5051

51-
if beam:
52-
(bmaj, bmin, bpa) = beam
53-
else:
54-
bmaj, bmin, bpa = self.parse_beam(table)
55-
self.beam = self.degrees2pixels(bmaj, bmin, bpa, self.pixelsize[0],
56-
self.pixelsize[1])
52+
if is_valid_beam_tuple(beam) or not is_valid_beam_tuple(self.beam):
53+
# An argument-supplied beam overrides a beam derived from
54+
# (bmaj, bmin, bpa) in a config.toml. Only if those two options
55+
# are not specified, we parse the beam from the header.
56+
bmaj, bmin, bpa = beam if is_valid_beam_tuple(beam) else (
57+
self.parse_beam(table))
58+
self.beam = self.degrees2pixels(
59+
bmaj, bmin, bpa, self.pixelsize[0], self.pixelsize[1]
60+
)
5761

5862
def parse_data(self, table, plane=0):
5963
"""

sourcefinder/accessors/dataaccessor.py

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import logging
22

3-
import numpy
3+
import numpy as np
44
from math import degrees, sqrt, sin, pi, cos
5-
from dataclasses import dataclass
5+
from dataclasses import dataclass, field
6+
from sourcefinder.utils import is_valid_beam_tuple
67
from sourcefinder.utility.coordinates import WCS
8+
from sourcefinder.config import ImgConf
9+
from typing import Optional, cast
710

811
logger = logging.getLogger(__name__)
912

@@ -16,7 +19,7 @@ class DataAccessor:
1619
Data accessors provide a uniform way for the ImageData class (i.e.,
1720
generic image representation) to access the various ways in which
1821
images may be stored (FITS files, arrays in memory, potentially HDF5,
19-
etc).
22+
etc.).
2023
2124
This class cannot be instantiated directly, but should be subclassed
2225
and the abstract properties provided. Note that all abstract
@@ -66,17 +69,26 @@ class DataAccessor:
6669
provides key info in a simple dict format.
6770
"""
6871

69-
beam: tuple
7072
centre_ra: float
7173
centre_decl: float
72-
data: numpy.ndarray
74+
data: np.ndarray
7375
freq_bw: float
7476
freq_eff: float
7577
pixelsize: tuple
7678
tau_time: float
7779
taustart_ts: float
7880
url: str
7981
wcs: WCS
82+
beam: Optional[tuple[float, float, float]] = field(default=None)
83+
conf: Optional[ImgConf] = field(default=None, repr=False)
84+
85+
def __post_init__(self):
86+
if self.conf is not None:
87+
beam_tuple = (self.conf.bmaj, self.conf.bmin, self.conf.bpa)
88+
if is_valid_beam_tuple(beam_tuple):
89+
deltax, deltay = self.pixelsize
90+
self.beam = DataAccessor.degrees2pixels(*beam_tuple,
91+
deltax, deltay)
8092

8193
def extract_metadata(self) -> dict:
8294
"""
@@ -94,22 +106,30 @@ def extract_metadata(self) -> dict:
94106
A dictionary containing key-value pairs of class attributes
95107
formatted for database storage.
96108
"""
97-
return {
109+
metadata = {
98110
'tau_time': self.tau_time,
99111
'freq_eff': self.freq_eff,
100112
'freq_bw': self.freq_bw,
101113
'taustart_ts': self.taustart_ts,
102114
'url': self.url,
103-
'beam_smaj_pix': self.beam[0],
104-
'beam_smin_pix': self.beam[1],
105-
'beam_pa_rad': self.beam[2],
106115
'centre_ra': self.centre_ra,
107116
'centre_decl': self.centre_decl,
108117
'deltax': self.pixelsize[0],
109118
'deltay': self.pixelsize[1],
110119
}
111120

112-
def parse_pixelsize(self):
121+
122+
if is_valid_beam_tuple(self.beam):
123+
beam = cast(tuple[float, float, float], self.beam)
124+
metadata.update({
125+
'beam_smaj_pix': beam[0],
126+
'beam_smin_pix': beam[1],
127+
'beam_pa_rad': beam[2],
128+
})
129+
130+
return metadata
131+
132+
def parse_pixelsize(self) -> tuple[float, float]:
113133
"""
114134
Returns
115135
-------
@@ -141,7 +161,8 @@ def parse_pixelsize(self):
141161
return deltax, deltay
142162

143163
@staticmethod
144-
def degrees2pixels(bmaj, bmin, bpa, deltax, deltay):
164+
def degrees2pixels(bmaj, bmin, bpa, deltax, deltay) -> (
165+
tuple)[float, float, float]:
145166
"""
146167
Convert beam in degrees to beam in pixels and radians.
147168
For example, FITS beam parameters are in degrees.
@@ -161,12 +182,14 @@ def degrees2pixels(bmaj, bmin, bpa, deltax, deltay):
161182
162183
Returns
163184
-------
164-
semimaj : float
165-
Beam semi-major axis in pixels.
166-
semimin : float
167-
Beam semi-minor axis in pixels.
168-
theta : float
169-
Beam position angle in radians.
185+
tuple
186+
A tuple containing:
187+
- semimaj : float
188+
Beam semi-major axis in pixels.
189+
- semimin : float
190+
Beam semi-minor axis in pixels.
191+
- theta : float
192+
Beam position angle in radians.
170193
"""
171194
theta = pi * bpa / 180
172195
semimaj = (bmaj / 2.) * (sqrt(

sourcefinder/accessors/fitsimage.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import astropy.io.fits as pyfits
99

10+
from sourcefinder.utils import is_valid_beam_tuple
1011
from sourcefinder.accessors.dataaccessor import DataAccessor
1112
from sourcefinder.utility.coordinates import WCS
1213

@@ -41,13 +42,17 @@ def __init__(self, url, plane=None, beam=None, hdu_index=0):
4142
self.taustart_ts, self.tau_time = self.parse_times()
4243
self.freq_eff, self.freq_bw = self.parse_frequency()
4344
self.pixelsize = self.parse_pixelsize()
44-
if beam:
45-
(bmaj, bmin, bpa) = beam
46-
else:
47-
(bmaj, bmin, bpa) = self.parse_beam()
48-
self.beam = self.degrees2pixels(
49-
bmaj, bmin, bpa, self.pixelsize[0], self.pixelsize[1]
50-
)
45+
46+
if is_valid_beam_tuple(beam) or not is_valid_beam_tuple(self.beam):
47+
# An argument-supplied beam overrides a beam derived from
48+
# (bmaj, bmin, bpa) in a config.toml. Only if those two options
49+
# are not specified, we parse the beam from the header.
50+
bmaj, bmin, bpa = beam if is_valid_beam_tuple(beam) else (
51+
self.parse_beam())
52+
self.beam = self.degrees2pixels(
53+
bmaj, bmin, bpa, self.pixelsize[0], self.pixelsize[1]
54+
)
55+
5156
self.centre_ra, self.centre_decl = self.calculate_phase_centre()
5257

5358
# Bonus attribute

sourcefinder/accessors/fitsimageblob.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import numpy
44

5+
from sourcefinder.utils import is_valid_beam_tuple
56
from sourcefinder.accessors.fitsimage import FitsImage
67

78
logger = logging.getLogger(__name__)
@@ -40,13 +41,16 @@ def __init__(self, hdulist, plane=None, beam=None, hdu_index=0):
4041
self.freq_eff, self.freq_bw
4142
self.url = "_".join([str(x) for x in elements])
4243

43-
if beam:
44-
(bmaj, bmin, bpa) = beam
45-
else:
46-
(bmaj, bmin, bpa) = self.parse_beam()
47-
self.beam = self.degrees2pixels(
48-
bmaj, bmin, bpa, self.pixelsize[0], self.pixelsize[1]
49-
)
44+
if is_valid_beam_tuple(beam) or not is_valid_beam_tuple(self.beam):
45+
# An argument-supplied beam overrides a beam derived from
46+
# (bmaj, bmin, bpa) in a config.toml. Only if those two options
47+
# are not specified, we parse the beam from the header.
48+
bmaj, bmin, bpa = beam if is_valid_beam_tuple(beam) else (
49+
self.parse_beam())
50+
self.beam = self.degrees2pixels(
51+
bmaj, bmin, bpa, self.pixelsize[0], self.pixelsize[1]
52+
)
53+
5054
self.centre_ra, self.centre_decl = self.calculate_phase_centre()
5155

5256
# Bonus attribute

sourcefinder/image.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,17 @@ def __init__(self, data, beam, wcs, conf: Conf = Conf(image=ImgConf(),
8888
# single precision is good enough in all cases.
8989
self.rawdata = np.ascontiguousarray(data, dtype=np.float32)
9090
self.wcs = wcs # a utility.coordinates.wcs instance
91-
self.beam = beam # tuple of (semimaj, semimin, theta) with semimaj and
92-
# semimin in pixel coordinates and theta, the position angle, in
93-
# radians.
91+
92+
if utils.is_valid_beam_tuple(beam):
93+
self.beam = beam # tuple of (semimaj, semimin, theta) with
94+
# semimaj and semimin in pixel coordinates and theta, the position
95+
# angle, in radians.
96+
else:
97+
raise ValueError(("Partial beam specification: one or more of "
98+
"(bmaj, bmin, bpa) are not specified, "
99+
"adequately, image processing is not possible.",
100+
RuntimeWarning))
101+
94102
# These three quantities are only dependent on the beam, so should be
95103
# calculated once the beam is known and not for each source separately.
96104
self.fudge_max_pix_factor = utils.fudge_max_pix(beam[0], beam[1],

sourcefinder/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import math
66
import numpy as np
7+
from numbers import Real
78
import scipy.integrate
89
from scipy.ndimage import distance_transform_edt
910

@@ -204,6 +205,12 @@ def generate_result_maps(data, sourcelist):
204205

205206
return gaussian_map, residual_map
206207

208+
def is_valid_beam_tuple(b) -> bool:
209+
return (
210+
isinstance(b, tuple)
211+
and len(b) == 3
212+
and all(isinstance(x, Real) and x is not None for x in b)
213+
)
207214

208215
def calculate_correlation_lengths(semimajor, semiminor):
209216
"""Calculate the Condon correlation lengths.

0 commit comments

Comments
 (0)