Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name = "FITSFiles"
uuid = "358a0a88-3548-4ad6-b652-8bdbf64af8e5"
repo = "https://github.com/JuliaAstro/FITSFiles.jl"
authors = ["Paul Barrett <pebarrett@gmail.com> and contributors"]
version = "0.3.2"
version = "0.4.0"

[deps]
CodecZlib = "944b1d66-785c-5afd-91f1-9de20f533193"
Expand Down
23 changes: 9 additions & 14 deletions src/card.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1027,14 +1027,15 @@ end
Determine the type of a number as a Float32, Float64, or Integer
"""
function parse_number(real::AbstractString)
if occursin('D', real) || (occursin('E', real) && overflow(real))
value = parse(Float64, replace(real, "E" => "e", "D" => "e"))
elseif occursin("E", real)
value = parse(Float32, replace(real, "E" => "e"))
elseif occursin(r"\.0*[1-9]+", real) && length(split(real, ".")[2]) >= 10
value = parse(Float64, real)
elseif occursin(".", real)
value = parse(Float32, real)
if occursin('D', real)
value = parse(Float64, replace(real, "D" => "e"))
elseif occursin('E', real) || occursin('.', real)
# Parse at full precision, then narrow to Float32 only when the
# narrowed value represents the written decimal exactly as well as a
# Float64 does.
value64 = parse(Float64, replace(real, "E" => "e"))
value32 = Float32(value64)
value = Float64(value32) == value64 ? value32 : value64
else
value = try
parse(Int64, real)
Expand Down Expand Up @@ -1063,12 +1064,6 @@ function least_float_type(value::Union{Real, Nothing})
value
end

function overflow(real::AbstractString)
# test for overflow or precision
n = findlast('E', real)
abs(parse(Int, real[(n+1):end])) >= 39 || n >= 14
end

function parse_complex(real::S, imag::S) where S <: AbstractString
if occursin("D", real) || occursin("D", imag)
real_, imag_ = replace(real, "D" => "e"), replace(imag, "D" => "e")
Expand Down
34 changes: 21 additions & 13 deletions src/fitscore.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,37 @@ function fits(file::AbstractString; kwds...)
end

"""
Base.write(io::IO, hdus::Vector{HDU})
Base.write(filename::AbstractString, hdus::Vector{HDU})
Base.write(io::IO, hdus::AbstractVector{<:HDU})
Base.write(filename::AbstractString, hdus::AbstractVector{<:HDU})

Write a vector of header-data units (HDUs) to a file.
"""
function Base.write(io::IO, hdus::Vector{HDU})
function Base.write(io::IO, hdus::AbstractVector{<:HDU})
for hdu in hdus
write(io, hdu)
end
end

function Base.write(file::AbstractString, hdus::Vector{HDU})
# Repeated for `Vector` so this method, not `Base.write(::IO, ::Array)`, wins
# dispatch for concrete HDU vectors such as `[HDU(data, cards)]`.
function Base.write(io::IO, hdus::Vector{<:HDU})
for hdu in hdus
write(io, hdu)
end
end

function Base.write(file::AbstractString, hdus::AbstractVector{<:HDU})
io = open(file, write=true)
write(io, hdus)
close(io)
end

"""
info(hdus::Vector{HDU})
info(hdus::AbstractVector{<:HDU})

Briefly describe the list of header-data units (HDUs).
"""
function info(hdus::Vector{HDU})
function info(hdus::AbstractVector{<:HDU})
typ = rpad("HDU", INFOTYPELEN)
nam = rpad("Name", INFONAMELEN)
ver = lpad("Ver", INFOVERSLEN)
Expand All @@ -68,7 +76,7 @@ function info(hdus::Vector{HDU})
end
end

function Base.show(io::IO, ::MIME"text/plain", hdus::AbstractVector{HDU})
function Base.show(io::IO, ::MIME"text/plain", hdus::AbstractVector{<:HDU})
typ = rpad("HDU", INFOTYPELEN)
nam = rpad("Name", INFONAMELEN)
ver = lpad("Ver", INFOVERSLEN)
Expand Down Expand Up @@ -96,7 +104,7 @@ end

#### Dictionary-like key function for HDUs

function Base.haskey(hdus::Vector{HDU}, key::Type{<:AbstractHDU})::Bool
function Base.haskey(hdus::AbstractVector{<:HDU}, key::Type{<:AbstractHDU})::Bool
for hdu in hdus
if typeofhdu(hdu) == key
return true
Expand All @@ -105,7 +113,7 @@ function Base.haskey(hdus::Vector{HDU}, key::Type{<:AbstractHDU})::Bool
false
end

function Base.getindex(hdus::Vector{HDU}, key::Type{<:AbstractHDU})::HDU
function Base.getindex(hdus::AbstractVector{<:HDU}, key::Type{<:AbstractHDU})::HDU
for hdu in hdus
if typeofhdu(hdu) == key
return hdu
Expand All @@ -114,7 +122,7 @@ function Base.getindex(hdus::Vector{HDU}, key::Type{<:AbstractHDU})::HDU
throw(KeyError(key))
end

function Base.getindex(hdus::Vector{HDU}, name::U)::HDU where
function Base.getindex(hdus::AbstractVector{<:HDU}, name::U)::HDU where
U<:Union{AbstractString, Symbol}

for hdu in hdus
Expand All @@ -127,7 +135,7 @@ function Base.getindex(hdus::Vector{HDU}, name::U)::HDU where
throw(KeyError(name))
end

function Base.get(hdus::Vector{HDU}, name::U, default::V = "")::Union{HDU, Nothing} where
function Base.get(hdus::AbstractVector{<:HDU}, name::U, default::V = "")::Union{HDU, Nothing} where
{U<:Union{AbstractString, Symbol}, V<:Union{AbstractString, Symbol}}

value::Union{HDU, Nothing} = nothing
Expand All @@ -144,7 +152,7 @@ function Base.get(hdus::Vector{HDU}, name::U, default::V = "")::Union{HDU, Nothi
value
end

function Base.get(hdus::Vector{HDU}, names::K, defaults::D)::Vector where
function Base.get(hdus::AbstractVector{<:HDU}, names::K, defaults::D)::Vector where
{K<:Union{Vector, Tuple}, D<:Union{Vector, Tuple}}

values = Any[fill(nothing, length(names))...]
Expand All @@ -164,7 +172,7 @@ function Base.get(hdus::Vector{HDU}, names::K, defaults::D)::Vector where
values
end

function Base.findfirst(key::AbstractString, hdus::Vector{HDU})::Union{Integer, Nothing}
function Base.findfirst(key::AbstractString, hdus::AbstractVector{<:HDU})::Union{Integer, Nothing}
for (j, hdu) in enumerate(hdus)
if uppercase(rstrip(hdu.cards[key])) == uppercase(key)
return j
Expand Down
7 changes: 7 additions & 0 deletions src/hdu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,13 @@ end

typeofhdu(::HDU{T}) where T = T

"""
Base.size(hdu::HDU{<:Union{Primary, Image}})

Dimensions of the HDU's data array, taken from the `NAXISn` cards without reading the data.
"""
Base.size(hdu::HDU{<:Union{Primary, Image}}) = datasize(getfield(hdu, :cards))

"""
typeofhdu(data)
typeofhdu(dict)
Expand Down
10 changes: 5 additions & 5 deletions test/bintable_hdu_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -824,23 +824,23 @@
"TTYPE5 = 'par5' "),
("TZERO1", 1.0f0, "",
"TZERO1 = 1.0 "),
("TSCAL1", 0.1f0, "",
("TSCAL1", 0.1, "",
"TSCAL1 = 0.1 "),
("TZERO2", 1.0f0, "",
"TZERO2 = 1.0 "),
("TSCAL2", 0.1f0, "",
("TSCAL2", 0.1, "",
"TSCAL2 = 0.1 "),
("TZERO3", 1.0f0, "",
"TZERO3 = 1.0 "),
("TSCAL3", 0.1f0, "",
("TSCAL3", 0.1, "",
"TSCAL3 = 0.1 "),
("TZERO4", 1.0f0, "",
"TZERO4 = 1.0 "),
("TSCAL4", 0.1f0, "",
("TSCAL4", 0.1, "",
"TSCAL4 = 0.1 "),
("TZERO5", 1.0f0, "",
"TZERO5 = 1.0 "),
("TSCAL5", 0.1f0, "",
("TSCAL5", 0.1, "",
"TSCAL5 = 0.1 ")])

@test (length(hdu.data) == 5 && length(hdu.data[:par1]) == 3 &&
Expand Down
23 changes: 22 additions & 1 deletion test/card_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,7 @@

@test isequal(showfields(parse(Card,
"HIERARCH ABC DEF GH IJKLM_- = -99.9 / [m] abcdef ghijklm nopqrstu vw xyzab ")),
("ABC DEF GH IJKLM_-", -99.9f0, "[m] abcdef ghijklm nopqrstu vw xyzab",
("ABC DEF GH IJKLM_-", -99.9, "[m] abcdef ghijklm nopqrstu vw xyzab",
"HIERARCH ABC DEF GH IJKLM_- = -99.9 / [m] abcdef ghijklm nopqrstu vw xyzab "))


Expand Down Expand Up @@ -964,4 +964,25 @@

# test raw keyword

@testset "real value precision" begin
# values narrow to Float32 only when that represents the written
# decimal exactly as well as a Float64 does
@test FITSFiles.parse_number("1.0") === 1.0f0
@test FITSFiles.parse_number("0.5") === 0.5f0
@test FITSFiles.parse_number("-99.9") === -99.9
@test FITSFiles.parse_number("0.1") === 0.1
@test FITSFiles.parse_number("266.400000") === 266.4
@test FITSFiles.parse_number("1.0E2") === 100.0f0
@test FITSFiles.parse_number("1E30") === 1.0e30

# D exponents always parse as Float64
@test FITSFiles.parse_number("1.0D2") === 100.0

# integers are unaffected
@test FITSFiles.parse_number("42") === Int64(42)

# the written decimal survives a card parse at full precision
card = parse(Card, rpad("CRVAL1 = 266.400000", 80))
@test card.value === 266.4
end
end
51 changes: 38 additions & 13 deletions test/fits_tests.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
@testset "FITS" begin

# test fits with IO type


# test fits with fieldnames


# test fits with scale keyword


# test fits with record keyword



# test write/read round-trip of a concrete HDU vector (dispatch previously
# fell through to `Base.write(::IO, ::Array)` for e.g. Vector{HDU{Primary}})
data = reshape(Float32[1:100;], 5, 20)
cards = [Card("FLTKEY", 1.0, "floating point keyword"),
Card("STRKEY", "string value")]
hdus = [HDU(data, cards)]
@test hdus isa Vector{HDU{Primary}}

path = joinpath(mktempdir(), "roundtrip.fits")
write(path, hdus)
back = fits(path)
@test length(back) == 1
@test back[1] isa HDU{Primary}
@test back[1].data == data

# test the same round-trip through an in-memory IO
io = IOBuffer()
write(io, hdus)
seekstart(io)
@test fits(io)[1].data == data

# test multiple HDUs, including an Image extension
write(path, [HDU(data, cards), HDU(Image, data, cards)])
back = fits(path)
@test FITSFiles.typeofhdu.(back) == [Primary, Image]
@test back[2].data == data

# test Base.size from the NAXIS cards, without touching the data
@test size(HDU(data, cards)) == (5, 20)
@test size(back[2]) == (5, 20)

# test size of a lazily-loaded HDU (file-backed data not yet read)
lazy = fits(path)[1]
@test getfield(lazy, :data) isa FITSFiles.LazyArray
@test size(lazy) == (5, 20)

# test size of a header-only HDU
@test size(HDU(Primary)) == ()
end
10 changes: 5 additions & 5 deletions test/image_hdu_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@
"NAXIS2 = 3 "),
("BZERO", 1.0f0, "",
"BZERO = 1.0 "),
("BSCALE", 0.1f0, "",
("BSCALE", 0.1, "",
"BSCALE = 0.1 ")])

@test (ndims(hdu.data) == 2 && size(hdu.data) == (3, 3) && length(hdu.data) == 9 &&
eltype(hdu.data) == Float32 && all(hdu.data .== 1.1f0))
eltype(hdu.data) == Float64 && all(hdu.data .== 1.1))

# test Image type with scale == false
data = ones(Int32, (3,3))
Expand Down Expand Up @@ -162,7 +162,7 @@
"NAXIS2 = 3 "),
("BZERO", 1.0f0, "",
"BZERO = 1.0 "),
("BSCALE", 0.1f0, "",
("BSCALE", 0.1, "",
"BSCALE = 0.1 ")])

@test (ndims(hdu.data) == 2 && size(hdu.data) == (3, 3) && length(hdu.data) == 9 &&
Expand Down Expand Up @@ -199,11 +199,11 @@
"NAXIS2 = 3 "),
("BZERO", 1.0f0, "",
"BZERO = 1.0 "),
("BSCALE", 0.1f0, "",
("BSCALE", 0.1, "",
"BSCALE = 0.1 ")])

@test (ndims(hdu.data) == 2 && size(hdu.data) == (3, 3) && length(hdu.data) == 9 &&
eltype(hdu.data) == Float32 && all(hdu.data .== 1.1f0))
eltype(hdu.data) == Float64 && all(hdu.data .== 1.1))

rm(temppath)

Expand Down
10 changes: 5 additions & 5 deletions test/primary_hdu_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@
"NAXIS2 = 3 "),
("BZERO", 1.0f0, "",
"BZERO = 1.0 "),
("BSCALE", 0.1f0, "",
("BSCALE", 0.1, "",
"BSCALE = 0.1 ")])

@test (ndims(hdu.data) == 2 && size(hdu.data) == (3, 3) && length(hdu.data) == 9 &&
eltype(hdu.data) == Float32 && all(hdu.data .== 1.1f0))
eltype(hdu.data) == Float64 && all(hdu.data .== 1.1))

# test Primary type with scale == false
data = ones(Int32, (3,3))
Expand Down Expand Up @@ -173,7 +173,7 @@
"NAXIS2 = 3 "),
("BZERO", 1.0f0, "",
"BZERO = 1.0 "),
("BSCALE", 0.1f0, "",
("BSCALE", 0.1, "",
"BSCALE = 0.1 ")])

@test (ndims(hdu.data) == 2 && size(hdu.data) == (3, 3) && length(hdu.data) == 9 &&
Expand Down Expand Up @@ -210,11 +210,11 @@
"NAXIS2 = 3 "),
("BZERO", 1.0f0, "",
"BZERO = 1.0 "),
("BSCALE", 0.1f0, "",
("BSCALE", 0.1, "",
"BSCALE = 0.1 ")])

@test (ndims(hdu.data) == 2 && size(hdu.data) == (3, 3) && length(hdu.data) == 9 &&
eltype(hdu.data) == Float32 && all(hdu.data .== 1.1f0))
eltype(hdu.data) == Float64 && all(hdu.data .== 1.1))

rm(temppath)

Expand Down
Loading
Loading