Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a method to read an image into a pre-allocated array #122

Merged
merged 5 commits into from
Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/FITSIO.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Base: getindex,
length,
show,
read,
read!,
write,
close,
ndims,
Expand Down
83 changes: 83 additions & 0 deletions src/image.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,42 @@ function read(hdu::ImageHDU)
data
end

"""
read!(hdu::ImageHDU, output::AbstractArray)
read!(hdu::ImageHDU, output::AbstractArray, range...)

Read the data array or a subset thereof from disk, and save it in the
preallocated output array.
The first form reads the entire data array.
The second form reads a slice of the array given by the specified ranges or integers.
The output array needs to have the same shape as the data range to be read in.
"""
function read!(hdu::ImageHDU, output::AbstractArray{T,N}) where {T,N}
fits_assert_open(hdu.fitsfile)
fits_movabs_hdu(hdu.fitsfile, hdu.ext)
sz = fits_get_img_size(hdu.fitsfile)
bitpix = fits_get_img_equivtype(hdu.fitsfile)

if TYPE_FROM_BITPIX[bitpix] != T
throw(TypeError(:read!,"",TYPE_FROM_BITPIX[bitpix],T))
end

if ndims(hdu) != N
throw(DimensionMismatch("array dimensions do not match the data. "*
"Data has $(ndims(hdu)) dimensions whereas the output array has $N dimensions."))
end

# Maybe this can be a ShapeMismatch when there's a decision on #16717
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this refers to JuliaLang/julia#16717, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's what I had in mind

if Tuple(sz) != size(output)
throw(DimensionMismatch("size of the array does not "*
"match the data. Data has a size of $(Tuple(sz)) whereas the output array "*
"has a size of $(size(output))"))
end

fits_read_pix(hdu.fitsfile, output)
output
end

# _checkbounds methods copied from Julia v0.4 Base.
_checkbounds(sz, i::Integer) = 1 <= i <= sz
_checkbounds(sz, i::Colon) = true
Expand Down Expand Up @@ -144,11 +180,58 @@ function read_internal(hdu::ImageHDU, I::Union{AbstractRange{Int}, Integer, Colo
data
end

function read_internal!(hdu::ImageHDU, output::AbstractArray{T,N},
I::Union{AbstractRange{Int}, Integer, Colon}...) where {T,N}

# check that the output array has the right type
bitpix = fits_get_img_equivtype(hdu.fitsfile)
if TYPE_FROM_BITPIX[bitpix] != T
throw(TypeError(:read!,"",TYPE_FROM_BITPIX[bitpix],T))
end

fits_assert_open(hdu.fitsfile)
fits_movabs_hdu(hdu.fitsfile, hdu.ext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines should come first in the function, so that fits_get_img_equivtype operates on the intended extension

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have fixed this in the latest commit.

sz = fits_get_img_size(hdu.fitsfile)

# check number of indices and bounds. Note that number of indices and
# array dimension must match, unlike in Arrays. Array-like behavior could
# be supported in the future with care taken in constructing first, last,
if length(I) != length(sz)
throw(DimensionMismatch("number of indices must match dimensions"))
end

for i=1:length(sz)
_checkbounds(sz[i], I[i]) || throw(BoundsError())
end

ninds = _index_shape(sz, I...)
if length(ninds) != N
throw(DimensionMismatch("number of indices must match the "*
"number of dimensions of the output array"))
end

if size(output) != ninds
throw(DimensionMismatch("size of the data slice must match that of the output array. "*
"Data has a size of $ninds whereas the output array has a size of $(size(output))"))
end

# construct first, last and step vectors
firsts = Clong[_first(idx) for idx in I]
lasts = Clong[_last(sz[i], I[i]) for i=1:length(sz)]
steps = Clong[_step(idx) for idx in I]

fits_read_subset(hdu.fitsfile, firsts, lasts, steps, output)
output
end

# general method and version that returns a single value rather than 0-d array
read(hdu::ImageHDU, I::Union{AbstractRange{Int}, Int, Colon}...) =
read_internal(hdu, I...)
read(hdu::ImageHDU, I::Int...) = read_internal(hdu, I...)[1]

read!(hdu::ImageHDU, output::AbstractArray, I::Union{AbstractRange{Int}, Int, Colon}...) =
read_internal!(hdu, output, I...)
read!(hdu::ImageHDU, output::AbstractArray, I::Int...) = read_internal!(hdu, output, I...)[1]

"""
write(f::FITS, data::Array; header=nothing, name=nothing, ver=nothing)
Expand Down
66 changes: 66 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,72 @@ using Random # for `randstring`
rm(fname1, force=true)
rm(fname2, force=true)
end

@testset "non-allocating read" begin
fname1 = tempname() * ".fits"
try
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that instead of using try/finally you can open the FITS file with

FITS(fname, "w") do f
    # ...
end

which will automatically close it at the end. You can find some examples in this file.

The try/finally that you still see here are leftovers of the times where this FITS method was not available, yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you still need this try...finally block to delete the temp file, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the tests to use this method

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kbarbary there is rm(fname1, force=true) after the FITS ... do ... end block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The try finally might still be necessary to clean up if there's an error, in which case the rm won't get called. I have fixed this in the latest commit

f1 = FITS(fname1, "w")
indata = reshape(Float32[1:400;], 20, 20)
write(f1, indata)
close(f1)
f1 = FITS(fname1, "r")
a = read(f1[1])

# Read the entire array
b = zeros(eltype(indata),size(indata))
read!(f1[1],b)
@test a == b
read!(f1[1],b,:,:)
@test a == b

# Read a 2D slice
b = zeros(eltype(indata),2,2)
read!(f1[1],b,1:2,1:2)
@test a[1:2,1:2] == b

# Read an entire 1D slice
b = zeros(eltype(indata),size(indata,1))
read!(f1[1],b,:,1)
@test a[:,1] == b

b = zeros(eltype(indata),size(indata,2))
read!(f1[1],b,1,:)
@test a[1,:] == b

# Read a part of a 1D slice
b = zeros(eltype(indata),1)
read!(f1[1],b,1:1,1)
@test a[1,1] == b[1]
read!(f1[1],b,1,1:1)
@test a[1,1] == b[1]

# Read a single element into a 0-dim array
b = zeros(eltype(indata))
read!(f1[1],b,1,1)
@test a[1,1] == first(b)

# Test for errors
b = zeros(Float64)
# Type is checked before dimensions
@test_throws TypeError read!(f1[1],b,1,1)
@test_throws TypeError read!(f1[1],b)

b = zeros(eltype(indata))
@test_throws DimensionMismatch read!(f1[1],b,1:10,1)
@test_throws DimensionMismatch read!(f1[1],b,1:10,1:10)
@test_throws DimensionMismatch read!(f1[1],b)
@test_throws DimensionMismatch read!(f1[1],b,:,1)
@test_throws DimensionMismatch read!(f1[1],b,1,:)
@test_throws DimensionMismatch read!(f1[1],b,:,:)

b = zeros(eltype(indata),1)
@test_throws DimensionMismatch read!(f1[1],b,1,1)

close(f1)
finally
rm(fname1, force=true)
end
end
end

@testset "Write data to an existing image HDU" begin
Expand Down