Skip to content

Commit aaf4ace

Browse files
committed
Refactor helper-classes using attrs, improve & u-test extract_lsm.
This began as a basic refactoring of SkySource / SkyRegion using attrs, which cuts down on verbosity / duplication and has the added bonus of providing prettyprinting. This led to work on ensuring the extract_lsm script actually works through unit-testing, including several minor improvements to consistency of output: * Only the catalog is output to stdout, so piping will give the same results as specifying a filename. * An empty catalog (with headers) is output even if there are no matches.
1 parent 3f4ac2e commit aaf4ace

File tree

4 files changed

+97
-50
lines changed

4 files changed

+97
-50
lines changed

src/fastimgproto/scripts/extract_lsm.py

+19-14
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,22 @@
66
from fastimgproto.skymodel.helpers import SkyRegion
77
from fastimgproto.skymodel.extraction import (
88
sumss_file_to_dataframe,
9-
lsm_extract
9+
lsm_extract,
10+
SumssSrc,
1011
)
1112
import click
1213
import sys
1314
import csv
1415

16+
1517
def write_catalog(src_list, filehandle):
16-
if src_list:
17-
fieldnames = src_list[0].to_ordereddict().keys()
18-
dw = csv.DictWriter(filehandle,
19-
fieldnames=fieldnames,
20-
delimiter='\t')
21-
dw.writeheader()
22-
for src in src_list:
23-
dw.writerow(src.to_ordereddict())
18+
dw = csv.DictWriter(filehandle,
19+
fieldnames=SumssSrc._list_dictkeys(),
20+
delimiter='\t')
21+
dw.writeheader()
22+
for src in src_list:
23+
dw.writerow(src.to_ordereddict())
24+
2425

2526
@click.command()
2627
@click.argument('ra', type=click.FLOAT)
@@ -42,6 +43,10 @@ def cli(ra, dec, radius, outfile, vcat, catalog_file):
4243
4344
fastimg_extract_lsm --vcat var.csv -- 189.2 -45.6 1.5 lsm.csv
4445
46+
Note: arguments (RA, dec, radius, [outfile]) are separated from options by the
47+
separator `--`, this avoids mistakenly trying to parse a negative
48+
declination as an option flag.
49+
4550
\b
4651
Args:
4752
- ra (float): RA of centre (decimal degrees, J2000)
@@ -64,8 +69,8 @@ def cli(ra, dec, radius, outfile, vcat, catalog_file):
6469
6570
"""
6671

67-
field_of_view = SkyRegion(SkyCoord(ra*u.deg, dec*u.deg),
68-
Angle(radius*u.deg))
72+
field_of_view = SkyRegion(SkyCoord(ra * u.deg, dec * u.deg),
73+
Angle(radius * u.deg))
6974

7075
if catalog_file is None:
7176
catalog_file = download_file(
@@ -80,9 +85,9 @@ def cli(ra, dec, radius, outfile, vcat, catalog_file):
8085
if vcat:
8186
write_catalog(variable_cat, vcat)
8287

83-
click.echo("{} sources matched".format(len(lsm_cat)))
84-
click.echo("Of which {} variable.".format(len(variable_cat)))
85-
sys.exit(0)
88+
click.echo("{} sources matched, of which {} variable".format(
89+
len(lsm_cat), len(variable_cat)),
90+
err=True)
8691

8792

8893
if __name__ == '__main__':

src/fastimgproto/skymodel/extraction.py

+25-11
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
class SumssSrc(SkySource):
3838
"""
39-
Represents an entry from the SUMSS catalog.
39+
Represents a subset of data from a row/source in the SUMSS catalog.
4040
4141
(Sydney University Molonglo Southern Sky Survey)
4242
"""
@@ -51,23 +51,37 @@ def __init__(self, row):
5151
self.peak_flux_err = row.peak_flux_err_mjy * u.mJy
5252
self.variable = row.variable
5353
self.position_err = PositionError(
54-
ra_err=Angle(row.ra_err_arcsec * u.arcsecond),
55-
dec_err=Angle(row.dec_err_arcsec * u.arcsecond)
54+
ra=Angle(row.ra_err_arcsec * u.arcsecond),
55+
dec=Angle(row.dec_err_arcsec * u.arcsecond)
5656
)
5757

5858
super(SumssSrc, self).__init__(
5959
position=position, flux=self.peak_flux, variable=self.variable)
60-
60+
61+
class DictKeys(object):
62+
ra = 'ra'
63+
dec = 'dec'
64+
ra_err = 'ra_err'
65+
dec_err = 'dec_err'
66+
peak_flux = 'peak_flux_mjy'
67+
peak_flux_err = 'peak_flux_err_mjy'
68+
variable = 'variable'
69+
70+
@classmethod
71+
def _list_dictkeys(cls):
72+
innards = vars(cls.DictKeys)
73+
return [innards[k] for k in innards if not k.startswith('__')]
6174

6275
def to_ordereddict(self):
6376
od = OrderedDict()
64-
od['ra'] = self.position.ra.deg
65-
od['dec'] = self.position.dec.deg
66-
od['ra_err'] = self.position_err.ra.deg
67-
od['dec_err'] = self.position_err.dec.deg
68-
od['peak_flux_mjy'] = self.peak_flux.to(u.mJy).value
69-
od['peak_flux_err_mjy'] = self.peak_flux_err.to(u.mJy).value
70-
od['variable'] = self.variable
77+
keys = SumssSrc.DictKeys
78+
od[keys.ra] = self.position.ra.deg
79+
od[keys.dec] = self.position.dec.deg
80+
od[keys.ra_err] = self.position_err.ra.deg
81+
od[keys.dec_err] = self.position_err.dec.deg
82+
od[keys.peak_flux] = self.peak_flux.to(u.mJy).value
83+
od[keys.peak_flux_err] = self.peak_flux_err.to(u.mJy).value
84+
od[keys.variable] = self.variable
7185
return od
7286

7387

src/fastimgproto/skymodel/helpers.py

+18-25
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,42 @@
44

55
from astropy.coordinates import Angle, SkyCoord
66
import astropy.units as u
7+
import attr.validators
8+
from attr import attrs, attrib
79

810

11+
@attrs
912
class SkyRegion(object):
1013
"""
1114
Defines a circular region of the sky.
1215
"""
13-
14-
def __init__(self, centre, radius):
15-
assert isinstance(centre, SkyCoord)
16-
assert isinstance(radius, Angle)
17-
self.centre = centre
18-
self.radius = radius
16+
centre = attrib(validator=attr.validators.instance_of(SkyCoord))
17+
radius = attrib(validator=attr.validators.instance_of(Angle))
1918

2019

20+
@attrs
2121
class PositionError(object):
22-
def __init__(self, ra_err, dec_err):
23-
assert isinstance(ra_err, Angle)
24-
assert isinstance(dec_err, Angle)
25-
self.ra = ra_err
26-
self.dec = dec_err
22+
"""
23+
Represent positional uncertainty.
2724
28-
def __str__(self):
29-
return "<PositionError: (ra, dec) in deg ({}, {})>".format(
30-
self.ra.deg, self.dec.deg)
25+
(Mainly used for representing entries in the SUMSS catalog.)
26+
"""
27+
ra = attrib(validator=attr.validators.instance_of(Angle))
28+
dec = attrib(validator=attr.validators.instance_of(Angle))
3129

3230

31+
@attrs
3332
class SkySource(object):
3433
"""
3534
Basic point source w/ flux modelled at a single frequency
3635
37-
Args:
36+
Attributes:
3837
position (astropy.coordinates.SkyCoord): Sky-coordinates of source.
3938
flux (astropy.units.Quantity): Source flux at measured frequency.
4039
frequency (astropy.units.Quantity): Measurement frequency.
4140
variable (bool): 'Known variable' flag.
4241
"""
43-
def __init__(self, position, flux,
44-
frequency=2.5*u.GHz, variable=False):
45-
assert isinstance(position, SkyCoord)
46-
#This will raise if the flux has wrong units or no units:
47-
flux.to(u.Jy)
48-
frequency.to(u.Hz)
49-
self.position = position
50-
self.flux = flux
51-
self.variable = variable
52-
self.frequency = frequency
42+
position = attrib(validator=attr.validators.instance_of(SkyCoord))
43+
flux = attrib(convert=lambda x: x.to(u.Jy))
44+
frequency = attrib(default=2.5 * u.GHz, convert=lambda x: x.to(u.GHz))
45+
variable = attrib(default=False)
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from __future__ import print_function
2+
import json
3+
import numpy as np
4+
5+
from click.testing import CliRunner
6+
from fastimgproto.resources.testdata import simple_vis_npz_filepath
7+
from fastimgproto.scripts.extract_lsm import cli as extract_lsm_cli
8+
from fastimgproto.skymodel.extraction import SumssSrc
9+
import csv
10+
11+
12+
def test_extract_lsm():
13+
runner = CliRunner()
14+
with runner.isolated_filesystem():
15+
catalog_output_filename = 'foo.csv'
16+
args = '-- 189.2 -45.6 0.2'.split()
17+
# Invoke with output to stdout
18+
result = runner.invoke(extract_lsm_cli,
19+
args=args)
20+
assert result.exit_code == 0
21+
22+
# And output to TSV file:
23+
args.append(catalog_output_filename)
24+
result = runner.invoke(extract_lsm_cli,
25+
args=args)
26+
print(result.output)
27+
28+
with open(catalog_output_filename, 'rb') as tsvfile:
29+
dr=csv.DictReader(tsvfile, delimiter='\t')
30+
rows = [r for r in dr]
31+
32+
assert len(rows)==1
33+
34+
for key in SumssSrc._list_dictkeys():
35+
assert key in rows[0].keys()

0 commit comments

Comments
 (0)