Skip to content

Commit 04253ac

Browse files
d3v-nullcjordan
andauthored
fixes #27 fits sourcelist support (#33)
* Add support for reading sky models in FITS files. * implement fits srclist writing * tests for parsing jack, gleam and lobes sourcelists * tests for shapelets, lists, multi-components * add fits source list documentation --------- Co-authored-by: Christopher H. Jordan <christopherjordan87@gmail.com>
1 parent f2013ca commit 04253ac

File tree

22 files changed

+2050
-52
lines changed

22 files changed

+2050
-52
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ and this project adheres to [Semantic
77
Versioning](https://semver.org/spec/v2.0.0.html).
88

99
## [0.4.0] - 2024-06-19
10+
### Added
11+
- fits sourcelist support (including shapelets for Jack-style fits)
12+
- hyperbeam@0.9.3 built@0.7 marlu@0.11.0 mwalib@1.3.3 birli@0.11.0
13+
1014
### Fixed
1115
- rocm6 support
12-
- a bunch of really nasty segfaults that took a big toll of my sanity
16+
- a bunch of really nasty segfaults that took a big toll on my sanity
1317
- Huge thanks to @robotopia for fixing https://github.com/MWATelescope/mwa_hyperbeam/issues/9
1418
via hyperbeam 0.9.0
1519
- performance optimizations in hyperbeam 0.9.3
16-
### Added
17-
- hyperbeam@0.9.3 built@0.7 marlu@0.11.0 mwalib@1.3.3 birli@0.11.0
1820

1921
## [0.3.0] - 2023-09-27
2022
### Added

build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ mod gpu {
184184
"cargo:warning=HIP_FLAGS set from env {}",
185185
p.to_string_lossy()
186186
);
187-
hip_target.flag(&p.to_string_lossy());
187+
hip_target.flag(&*p.to_string_lossy());
188188
}
189189

190190
println!("cargo:rerun-if-env-changed=ROCM_VER");

examples/read_fits_srclist.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from astropy.io import fits
2+
import sys
3+
from tabulate import tabulate
4+
5+
for hdu in fits.open(sys.argv[-1])[1:]:
6+
print(tabulate(hdu.data, headers=[c.name for c in hdu.columns], tablefmt="github"))

mdbook/src/defs/source_list_fits.md

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# FITS source list formats
2+
3+
There are three supported fits file formats:
4+
- LoBES: used in LoBES catalogue <https://doi.org/10.1017/pasa.2021.50>
5+
- Jack: extended LoBES format for Jack Line's sourcelist repository, <https://github.com/JLBLine/srclists/>.
6+
- Gleam: used in GLEAM-X pipeline <https://github.com/GLEAM-X/GLEAM-X-pipeline/tree/master/models>
7+
8+
These formats differ mostly in the names of columns, and component and flux types
9+
supported. *LoBES* fits files support point, and Gaussian components with list,
10+
power law and curved power law flux density models. *Jack* fits files extend the
11+
LoBES format with an additional table for shapelet coefficients. *Gleam* fits are
12+
similar to LoBES fits, but with different column names, and combine power law and
13+
curved power law flux density models into a just two columns.
14+
15+
More info from [woden docs](https://woden.readthedocs.io/en/latest/operating_principles/skymodel.html)
16+
17+
## Source posititons
18+
19+
Coordinates are right ascension (RA) and declination, both with units of degrees
20+
in the J2000 epoch. All frequencies are in Hz and all flux densities are in Jy.
21+
22+
Jack and LoBES fits formats use the columns `RA` and `DEC` for source positions,
23+
while Gleam fits files use `RAJ2000` and `DEJ2000`.
24+
25+
## Component types
26+
27+
Jack and LoBES fits formats use the column `COMP_TYPE` for component types:
28+
- `P` for point
29+
- `G` for Gaussian
30+
- `S` for shapelet (Jack only)
31+
32+
Jack and LoBES fits formats use the columns `MAJOR_DC`, `MINOR_DC` and `PA_DC`
33+
for Gaussian component sizes and position angles (in degrees), while Gleam
34+
fits files use `a`, `b` (arcseconds) and `pa` (degrees).
35+
36+
In an image space where RA increases from right to left (i.e. bigger RA
37+
values are on the left), position angles rotate counter clockwise. A
38+
position angle of 0 has the major axis aligned with the declination axis.
39+
40+
## Flux density models
41+
42+
Jack and LoBES fits formats use the column `MOD_TYPE` for flux density types:
43+
- `pl` for power law
44+
- `cpl` for curved power law
45+
- `nan` for lists
46+
47+
Jack and LoBES fits formats use the columns `NORM_COMP_PL` and `ALPHA_PL` for
48+
power law flux density normalisation and spectral index; and `NORM_COMP_CPL`,
49+
`ALPHA_CPL` and `CURVE_CPL` for curved power law flux density normalisation,
50+
while Gleam fits files use `S_200`, `alpha` and `beta`.
51+
52+
A reference frequency of 200MHz is assumed in all fits files.
53+
54+
Jack and LoBES fits formats use the columns `INT_FLXnnn` for integrated flux
55+
densities in Jy at frequencies `nnn` MHz, while Gleam fits files use only `s_200`.
56+
These columns are used to construct flux lists if power law information is
57+
missing, or `MOD_TYPE` is `nan`.
58+
59+
Only Stokes I can be specified in fits sourcelists, Stokes Q, U and V are
60+
assumed to have values of 0.
61+
62+
## Examples
63+
64+
Example Python code to display these files is in the [examples
65+
directory](https://github.com/MWATelescope/mwa_hyperdrive/tree/main/examples).
66+
67+
e.g. `python examples/read_fits_srclist.py test_files/jack.fits`
68+
69+
| UNQ_SOURCE_ID | NAME | RA | DEC | INT_FLX100 | INT_FLX150 | INT_FLX200 | MAJOR_DC | MINOR_DC | PA_DC | MOD_TYPE | COMP_TYPE | NORM_COMP_PL | ALPHA_PL | NORM_COMP_CPL | ALPHA_CPL | CURVE_CPL |
70+
|-----------------|---------------|------|-------|--------------|--------------|--------------|------------|------------|---------|------------|-------------|----------------|------------|-----------------|-------------|-------------|
71+
| point-list | point-list_C0 | 0 | 1 | 3 | 2 | 1 | 0 | 0 | 0 | nan | P | 1 | 0 | 0 | 0 | 0 |
72+
| point-pl | point-pl_C0 | 1 | 2 | 3.5 | 2.5 | 2 | 0 | 0 | 0 | pl | P | 2 | -0.8 | 0 | 0 | 0 |
73+
| point-cpl | point-cpl_C0 | 3 | 4 | 5.6 | 3.8 | 3 | 0 | 0 | 0 | cpl | P | 0 | 0 | 3 | -0.9 | 0.2 |
74+
| gauss-list | gauss-list_C0 | 0 | 1 | 3 | 2 | 1 | 20 | 10 | 75 | nan | G | 1 | 0 | 0 | 0 | 0 |
75+
| gauss-pl | gauss-pl_C0 | 1 | 2 | 3.5 | 2.5 | 2 | 20 | 10 | 75 | pl | G | 2 | -0.8 | 0 | 0 | 0 |
76+
| gauss-cpl | gauss-cpl_C0 | 3 | 4 | 5.6 | 3.8 | 3 | 20 | 10 | 75 | cpl | G | 0 | 0 | 3 | -0.9 | 0.2 |
77+
| shape-pl | shape-pl_C0 | 1 | 2 | 3.5 | 2.5 | 2 | 20 | 10 | 75 | pl | S | 2 | -0.8 | 0 | 0 | 0 |
78+
| shape-pl | shape-pl_C1 | 1 | 2 | 3.5 | 2.5 | 2 | 20 | 10 | 75 | pl | S | 2 | -0.8 | 0 | 0 | 0 |
79+
80+
| NAME | N1 | N2 | COEFF |
81+
|-------------|------|------|---------|
82+
| shape-pl_C0 | 0 | 0 | 0.9 |
83+
| shape-pl_C0 | 0 | 1 | 0.2 |
84+
| shape-pl_C0 | 1 | 0 | -0.2 |
85+
| shape-pl_C1 | 0 | 0 | 0.8 |
86+
87+
e.g. `python examples/read_fits_srclist.py test_files/gleam.fits`
88+
89+
| Name | RAJ2000 | DEJ2000 | S_200 | alpha | beta | a | b | pa |
90+
|-----------|-----------|-----------|---------|---------|--------|-------|-------|------|
91+
| point-pl | 1 | 2 | 2 | -0.8 | 0 | 0 | 0 | 0 |
92+
| point-cpl | 3 | 4 | 3 | -0.9 | 0.2 | 0 | 0 | 0 |
93+
| gauss-pl | 1 | 2 | 2 | -0.8 | 0 | 72000 | 36000 | 75 |
94+
| gauss-cpl | 3 | 4 | 3 | -0.9 | 0.2 | 72000 | 36000 | 75 |
95+
96+
these are both equivalent to the following YAML file (ignoring shapelets and
97+
lists for the gleam example):
98+
99+
```yaml
100+
point-list:
101+
- ra: 0.0
102+
dec: 1.0
103+
comp_type: point
104+
flux_type:
105+
list:
106+
- freq: 100000000.0
107+
i: 3.0
108+
- freq: 150000000.0
109+
i: 2.0
110+
- freq: 200000000.0
111+
i: 1.0
112+
point-pl:
113+
- ra: 1.0
114+
dec: 2.0
115+
comp_type: point
116+
flux_type:
117+
power_law:
118+
si: -0.8
119+
fd:
120+
freq: 200000000.0
121+
i: 2.0
122+
point-cpl:
123+
- ra: 3.0000000000000004
124+
dec: 4.0
125+
comp_type: point
126+
flux_type:
127+
curved_power_law:
128+
si: -0.9
129+
fd:
130+
freq: 200000000.0
131+
i: 3.0
132+
q: 0.2
133+
gauss-list:
134+
- ra: 0.0
135+
dec: 1.0
136+
comp_type:
137+
gaussian:
138+
maj: 72000.0
139+
min: 36000.0
140+
pa: 75.0
141+
flux_type:
142+
list:
143+
- freq: 100000000.0
144+
i: 3.0
145+
- freq: 150000000.0
146+
i: 2.0
147+
- freq: 200000000.0
148+
i: 1.0
149+
gauss-pl:
150+
- ra: 1.0
151+
dec: 2.0
152+
comp_type:
153+
gaussian:
154+
maj: 72000.0
155+
min: 36000.0
156+
pa: 75.0
157+
flux_type:
158+
power_law:
159+
si: -0.8
160+
fd:
161+
freq: 200000000.0
162+
i: 2.0
163+
gauss-cpl:
164+
- ra: 3.0000000000000004
165+
dec: 4.0
166+
comp_type:
167+
gaussian:
168+
maj: 72000.0
169+
min: 36000.0
170+
pa: 75.0
171+
flux_type:
172+
curved_power_law:
173+
si: -0.9
174+
fd:
175+
freq: 200000000.0
176+
i: 3.0
177+
q: 0.2
178+
shape-pl:
179+
- ra: 1.0
180+
dec: 2.0
181+
comp_type:
182+
shapelet:
183+
maj: 72000.0
184+
min: 36000.0
185+
pa: 75.0
186+
coeffs:
187+
- n1: 0
188+
n2: 0
189+
value: 0.9
190+
- n1: 0
191+
n2: 1
192+
value: 0.2
193+
- n1: 1
194+
n2: 0
195+
value: -0.2
196+
flux_type:
197+
power_law:
198+
si: -0.8
199+
fd:
200+
freq: 200000000.0
201+
i: 2.0
202+
- ra: 1.0
203+
dec: 2.0
204+
comp_type:
205+
shapelet:
206+
maj: 72000.0
207+
min: 36000.0
208+
pa: 75.0
209+
coeffs:
210+
- n1: 0
211+
n2: 0
212+
value: 0.8
213+
flux_type:
214+
power_law:
215+
si: -0.8
216+
fd:
217+
freq: 200000000.0
218+
i: 2.0
219+
```

mdbook/src/defs/source_lists.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ types.
2323
- [`hyperdrive` format](source_list_hyperdrive.md)
2424
- [André Offringa (`ao`) format](source_list_ao.md)
2525
- [`RTS` format](source_list_rts.md)
26+
- [Jack, Gleam or LoBES style fits](source_list_fits.md)
2627
~~~
2728

2829
~~~admonish info title="Conversion"

src/cli/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,10 @@ impl From<WriteSourceListError> for HyperdriveError {
368368
| WriteSourceListError::InvalidHyperdriveFormat(_)
369369
| WriteSourceListError::Sexagesimal(_) => Self::Srclist(s),
370370
WriteSourceListError::IO(e) => Self::from(e),
371-
WriteSourceListError::Yaml(_) | WriteSourceListError::Json(_) => Self::Generic(s),
371+
WriteSourceListError::Yaml(_)
372+
| WriteSourceListError::Json(_)
373+
| WriteSourceListError::Fitsio(_)
374+
| WriteSourceListError::Fits(_) => Self::Generic(s),
372375
}
373376
}
374377
}

src/cli/srclist/verify.rs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use log::info;
1616
use crate::{
1717
cli::common::{display_warnings, SOURCE_LIST_INPUT_TYPE_HELP},
1818
srclist::{
19-
ao, hyperdrive, read::read_source_list_file, rts, woden, ComponentCounts, SourceListType,
20-
SrclistError,
19+
ao, fits, hyperdrive, read::read_source_list_file, rts, woden, ComponentCounts,
20+
SourceListType, SrclistError,
2121
},
2222
HyperdriveError,
2323
};
@@ -67,24 +67,45 @@ fn verify<P: AsRef<Path>>(
6767
info!("{}:", source_list.as_ref().display());
6868

6969
let (sl, sl_type) = if let Some(input_type) = input_type {
70-
let mut buf = std::io::BufReader::new(File::open(source_list)?);
7170
let result = match input_type {
72-
SourceListType::Hyperdrive => crate::misc::expensive_op(
73-
|| hyperdrive::source_list_from_yaml(&mut buf),
74-
"Still reading source list file",
75-
),
76-
SourceListType::AO => crate::misc::expensive_op(
77-
|| ao::parse_source_list(&mut buf),
78-
"Still reading source list file",
79-
),
80-
SourceListType::Rts => crate::misc::expensive_op(
81-
|| rts::parse_source_list(&mut buf),
82-
"Still reading source list file",
83-
),
84-
SourceListType::Woden => crate::misc::expensive_op(
85-
|| woden::parse_source_list(&mut buf),
86-
"Still reading source list file",
87-
),
71+
SourceListType::Hyperdrive => {
72+
let mut buf = std::io::BufReader::new(File::open(source_list)?);
73+
crate::misc::expensive_op(
74+
|| hyperdrive::source_list_from_yaml(&mut buf),
75+
"Still reading source list file",
76+
)
77+
}
78+
SourceListType::Fits => {
79+
let source_list = source_list.as_ref();
80+
let sl = crate::misc::expensive_op(
81+
|| fits::parse_source_list(source_list),
82+
"Still reading source list file",
83+
)
84+
.unwrap();
85+
// TODO: Proper error handling
86+
Ok(sl)
87+
}
88+
SourceListType::AO => {
89+
let mut buf = std::io::BufReader::new(File::open(source_list)?);
90+
crate::misc::expensive_op(
91+
|| ao::parse_source_list(&mut buf),
92+
"Still reading source list file",
93+
)
94+
}
95+
SourceListType::Rts => {
96+
let mut buf = std::io::BufReader::new(File::open(source_list)?);
97+
crate::misc::expensive_op(
98+
|| rts::parse_source_list(&mut buf),
99+
"Still reading source list file",
100+
)
101+
}
102+
SourceListType::Woden => {
103+
let mut buf = std::io::BufReader::new(File::open(source_list)?);
104+
crate::misc::expensive_op(
105+
|| woden::parse_source_list(&mut buf),
106+
"Still reading source list file",
107+
)
108+
}
88109
};
89110
match result {
90111
Ok(sl) => (sl, input_type),

src/srclist/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@ pub(crate) enum WriteSourceListError {
309309
#[error(transparent)]
310310
Sexagesimal(#[from] marlu::sexagesimal::SexagesimalError),
311311

312+
#[error(transparent)]
313+
Fitsio(#[from] fitsio::errors::Error),
314+
315+
#[error(transparent)]
316+
Fits(#[from] crate::io::read::fits::FitsError),
317+
312318
/// An IO error.
313319
#[error(transparent)]
314320
IO(#[from] std::io::Error),

src/srclist/fits/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
//! Code to handle FITS source list files.
6+
7+
// The reference frequency of the power laws.
8+
const REF_FREQ_HZ: f64 = 200e6;
9+
10+
mod read;
11+
mod write;
12+
13+
// Re-exports.
14+
pub(crate) use read::parse_source_list;
15+
pub(crate) use write::write_source_list_jack;

0 commit comments

Comments
 (0)