Skip to content

Commit 6690e94

Browse files
authored
Merge pull request #228 from JuliaAstro/wcs-header
WCS header support
2 parents 23bd408 + efd8b27 commit 6690e94

File tree

9 files changed

+171
-3
lines changed

9 files changed

+171
-3
lines changed

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1010

1111
[weakdeps]
1212
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
13+
WCS = "15f3aee2-9e10-537f-b834-a6fb8bdb944d"
1314

1415
[extensions]
1516
FITSIOTablesExt = "Tables"
17+
WCSExt = "WCS"
1618

1719
[compat]
1820
Aqua = "0.8"
@@ -23,6 +25,7 @@ Random = "<0.0.1, 1"
2325
Reexport = "0.2, 1.0"
2426
Tables = "1"
2527
Test = "<0.0.1, 1"
28+
WCS = "0.6.2"
2629
julia = "1.3"
2730

2831
[extras]
@@ -31,6 +34,7 @@ OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
3134
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
3235
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
3336
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
37+
WCS = "15f3aee2-9e10-537f-b834-a6fb8bdb944d"
3438

3539
[targets]
36-
test = ["Aqua", "OrderedCollections", "Random", "Tables", "Test"]
40+
test = ["Aqua", "OrderedCollections", "Random", "Tables", "Test", "WCS"]

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
[deps]
22
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
33
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
4+
DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656"
45
FITSIO = "525bcba6-941b-5504-bd06-fd0dc1a4d2eb"
6+
WCS = "15f3aee2-9e10-537f-b834-a6fb8bdb944d"
57

68
[compat]
79
Documenter = "1"

docs/make.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
using Documenter, FITSIO
1+
using Documenter, DocumenterInterLinks, FITSIO
2+
using DataFrames, WCS # Precompile package extensions
3+
4+
links = InterLinks(
5+
"WCS" => "https://juliaastro.org/WCS/stable/",
6+
)
27

38
include("pages.jl")
49
makedocs(;
5-
modules = [FITSIO],
10+
modules = [FITSIO, Base.get_extension(FITSIO, :WCSExt)],
611
sitename = "FITSIO.jl",
712
format = Documenter.HTML(
813
prettyurls = get(ENV, "CI", nothing) == "true",
914
canonical = "https://juliaastro.org/FITSIO/stable/",
1015
),
1116
pages = pages,
1217
checkdocs = :exports,
18+
plugins = [links],
1319
)
1420

1521
deploydocs(;

docs/src/api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ read_key
2323
write_key
2424
read_header
2525
FITSHeader
26+
FITSHeader(::AbstractVector{<:NamedTuple})
27+
FITSHeader(::WCS.WCSTransform)
2628
length(::FITSHeader)
2729
haskey(::FITSHeader, ::String)
2830
keys(::FITSHeader)

ext/WCSExt.jl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
module WCSExt
2+
3+
using WCS: WCSTransform, to_header
4+
import FITSIO: FITSIO, FITSHeader
5+
6+
"""
7+
FITSHeader(wcs::WCS.WCSTransform)
8+
9+
Construct a [`FITSHeader`](@ref) from a [`WCSTransform`](@extref WCS.WCSTransform) supplied by [WCS.jl](@extref).
10+
11+
# Examples
12+
13+
```jldoctest
14+
julia> using FITSIO, WCS
15+
16+
julia> wcs = WCSTransform(2;
17+
cdelt = [-0.066667, 0.066667],
18+
ctype = ["RA---AIR", "DEC--AIR"],
19+
crpix = [-234.75, 8.3393],
20+
crval = [0., -90],
21+
pv = [(2, 1, 45.0)],
22+
)
23+
WCSTransform(naxis=2, cdelt=[-0.066667, 0.066667], crval=[0.0, -90.0], crpix=[-234.75, 8.3393])
24+
25+
julia> FITSHeader(wcs)
26+
WCSAXES = '2 ' / Number of coordinate axes
27+
CRPIX1 = '-234.7500' / Pixel coordinate of reference point
28+
CRPIX2 = '8.3393 ' / Pixel coordinate of reference point
29+
CDELT1 = '-0.066667' / [deg] Coordinate increment at reference point
30+
CDELT2 = '0.066667' / [deg] Coordinate increment at reference point
31+
CUNIT1 = 'deg ' / Units of coordinate increment and value
32+
CUNIT2 = 'deg ' / Units of coordinate increment and value
33+
CTYPE1 = 'RA---AIR' / Right ascension, Airys zenithal projection
34+
CTYPE2 = 'DEC--AIR' / Declination, Airys zenithal projection
35+
CRVAL1 = '0.0 ' / [deg] Coordinate value at reference point
36+
CRVAL2 = '-90.0 ' / [deg] Coordinate value at reference point
37+
PV2_1 = '45.0 ' / AIR projection parameter
38+
LONPOLE = '180.0 ' / [deg] Native longitude of celestial pole
39+
LATPOLE = '-90.0 ' / [deg] Native latitude of celestial pole
40+
MJDREF = '0.0 ' / [d] MJD of fiducial time
41+
RADESYS = 'ICRS ' / Equatorial coordinate system
42+
COMMENT WCS header keyrecords produced by WCSLIB 7.7
43+
```
44+
"""
45+
function FITSIO.FITSHeader(wcs::WCSTransform)
46+
# Split string into 80-character card images
47+
card_images = Iterators.partition(to_header(wcs), 80)
48+
49+
# Remove any blank lines
50+
is_empty = isempty strip
51+
card_images = Iterators.filter(card_images) do card_image
52+
!is_empty(card_image)
53+
end
54+
55+
# Split each of those card images into their (key, value, comment) parts
56+
card_image_parts = map(card_images) do card_image
57+
card_image = replace(card_image, "'" => "") # Remove single quotes
58+
map(strip, split(card_image, ['=', '/' ]))
59+
end
60+
61+
# Deal with the special comment case
62+
comment_card = (first pop!)(card_image_parts)
63+
comment = (strip last)(split(comment_card, "COMMENT"))
64+
push!(card_image_parts, ["COMMENT", "", comment])
65+
66+
# Store
67+
k, v, c = eachcol(stack(card_image_parts; dims = 1))
68+
return FITSHeader(string.(k), string.(v), string.(c))
69+
end
70+
71+
end # module

src/FITSIO.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,19 @@ reading from a file. (This is similar to how an `Array` returned by
232232
if it was created by `read_header(::HDU)`. You can, however, write a
233233
`FITSHeader` to a file using the `write(::FITS, ...)` methods that
234234
append a new HDU to a file.
235+
236+
# Examples
237+
238+
239+
```jldoctest
240+
julia> using FITSIO
241+
242+
julia> FITSHeader(["Key1", "Key2"], [1.0, "one"], ["Comment1", "Comment2"])
243+
Key1 = 1.0 / Comment1
244+
Key2 = 'one ' / Comment2
245+
```
246+
247+
If [WCS.jl](@extref) is loaded, then a `FITSHeader` can also be constructed from a [`WCS.WCSTransform`](@extref).
235248
"""
236249
mutable struct FITSHeader
237250
keys::Vector{String}

src/header.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@
66
# Used here and in other files. Functions that operate on FITSFile
77
# start with `fits_`.
88

9+
"""
10+
FITSHeader(cards::AbstractVector{<:NamedTuple})
11+
12+
Construct a [`FITSHeader`](@ref) from a vector of `NamedTuples` with the following fields: `key`, `value`, and `comment`.
13+
14+
# Examples
15+
16+
```jldoctest
17+
julia> using FITSIO
18+
19+
julia> FITSHeader([
20+
(key = "Key1", value = 1.0, comment = "Comment1"),
21+
(key = "Key2", value = "one", comment = "Comment2"),
22+
])
23+
Key1 = 1.0 / Comment1
24+
Key2 = 'one ' / Comment2
25+
```
26+
"""
927
FITSIO.FITSHeader(cards::AbstractVector{<:NamedTuple}) = FITSHeader(
1028
map(x -> x.key, cards),
1129
map(x -> x.value, cards),

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,3 +950,5 @@ end
950950
end
951951
end
952952
end
953+
954+
include("test_wcs.jl")

test/test_wcs.jl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using WCS: WCSTransform
2+
3+
@testset "WCS handling" begin
4+
# Create sample fits data
5+
img = [6 7; 8 9]
6+
wcs = WCSTransform(2;
7+
cdelt = [-0.066667, 0.066667],
8+
ctype = ["RA---AIR", "DEC--AIR"],
9+
crpix = [-234.75, 8.3393],
10+
crval = [0., -90],
11+
pv = [(2, 1, 45.0)],
12+
)
13+
header_wcs = FITSHeader(wcs)
14+
15+
# Check output
16+
header_default_str = """SIMPLE = T / file does conform to FITS standard
17+
BITPIX = 64 / number of bits per data pixel
18+
NAXIS = 2 / number of data axes
19+
NAXIS1 = 2 / length of data axis 1
20+
NAXIS2 = 2 / length of data axis 2
21+
EXTEND = T / FITS dataset may contain extensions
22+
COMMENT FITS (Flexible Image Transport System) format is defined in 'Astronom
23+
COMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H
24+
"""
25+
26+
header_wcs_str = """WCSAXES = '2 ' / Number of coordinate axes
27+
CRPIX1 = '-234.7500' / Pixel coordinate of reference point
28+
CRPIX2 = '8.3393 ' / Pixel coordinate of reference point
29+
CDELT1 = '-0.066667' / [deg] Coordinate increment at reference point
30+
CDELT2 = '0.066667' / [deg] Coordinate increment at reference point
31+
CUNIT1 = 'deg ' / Units of coordinate increment and value
32+
CUNIT2 = 'deg ' / Units of coordinate increment and value
33+
CTYPE1 = 'RA---AIR' / Right ascension, Airys zenithal projection
34+
CTYPE2 = 'DEC--AIR' / Declination, Airys zenithal projection
35+
CRVAL1 = '0.0 ' / [deg] Coordinate value at reference point
36+
CRVAL2 = '-90.0 ' / [deg] Coordinate value at reference point
37+
PV2_1 = '45.0 ' / AIR projection parameter
38+
LONPOLE = '180.0 ' / [deg] Native longitude of celestial pole
39+
LATPOLE = '-90.0 ' / [deg] Native latitude of celestial pole
40+
MJDREF = '0.0 ' / [d] MJD of fiducial time
41+
RADESYS = 'ICRS ' / Equatorial coordinate system
42+
COMMENT WCS header keyrecords produced by WCSLIB 7.7"""
43+
44+
@test string(header_wcs) == header_wcs_str
45+
46+
tempnamefits() do fname
47+
FITSIO.fitswrite(fname, img; header = header_wcs)
48+
@test string(FITSIO.read_header(fname)) == header_default_str * header_wcs_str
49+
end
50+
end

0 commit comments

Comments
 (0)