Skip to content

Commit

Permalink
Added a method to read an image into a pre-allocated array (#122)
Browse files Browse the repository at this point in the history
* added a method for inplace read! into a pre-allocated array

* Fixed TypeError on v1.0.4

* Fixed typo and improved variable names

* Updated tests to use the FITS do method, and implemented reading into views

* Reintroduced try finally and fixed the order of function calls in read_internal!
  • Loading branch information
jishnub authored and giordano committed Sep 12, 2019
1 parent 450ddb6 commit 06df61a
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 5 deletions.
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
84 changes: 84 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
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,59 @@ 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}

fits_assert_open(hdu.fitsfile)
fits_movabs_hdu(hdu.fitsfile, hdu.ext)

# 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

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 dimensions to be read 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
17 changes: 12 additions & 5 deletions src/libcfitsio.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ export FITSFile,
# Deal with compatibility issues.
using Libdl

import Base: FastContiguousSubArray
const ArrayOrFastContiguousSubArray{T} =
Union{Array{T},FastContiguousSubArray{T,N,<:Array{T}} where N}

const depsjl_path = joinpath(@__DIR__, "..", "deps", "deps.jl")
if !isfile(depsjl_path)
@error "FITSIO not properly installed. " *
Expand Down Expand Up @@ -762,7 +766,7 @@ end

function fits_read_pix(f::FITSFile, fpixel::Vector{S},
nelements::Int, nullval::T,
data::Array{T}) where {S<:Integer,T}
data::ArrayOrFastContiguousSubArray{T}) where {S<:Integer,T}
anynull = Ref{Cint}(0)
status = Ref{Cint}(0)
ccall((:ffgpxvll, libcfitsio), Cint,
Expand All @@ -780,7 +784,8 @@ end
Read pixels from the FITS file into `data`.
"""
function fits_read_pix(f::FITSFile, fpixel::Vector{S},
nelements::Int, data::Array{T}) where {S<:Integer,T}
nelements::Int,
data::ArrayOrFastContiguousSubArray{T}) where {S<:Integer,T}
anynull = Ref{Cint}(0)
status = Ref{Cint}(0)
ccall((:ffgpxvll, libcfitsio), Cint,
Expand All @@ -792,13 +797,15 @@ function fits_read_pix(f::FITSFile, fpixel::Vector{S},
anynull[]
end

function fits_read_pix(f::FITSFile, data::Array)
function fits_read_pix(f::FITSFile, data::ArrayOrFastContiguousSubArray)
fits_read_pix(f, ones(Int64,length(size(data))), length(data), data)
end

function fits_read_subset(
f::FITSFile, fpixel::Vector{S1}, lpixel::Vector{S2},
inc::Vector{S3}, data::Array{T}) where {S1<:Integer,S2<:Integer,S3<:Integer,T}
f::FITSFile, fpixel::Vector{S1}, lpixel::Vector{S2},
inc::Vector{S3},
data::ArrayOrFastContiguousSubArray{T}) where {S1<:Integer,S2<:Integer,S3<:Integer,T}

anynull = Ref{Cint}(0)
status = Ref{Cint}(0)
ccall((:ffgsv, libcfitsio), Cint,
Expand Down
85 changes: 85 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,91 @@ using Random # for `randstring`
rm(fname1, force=true)
rm(fname2, force=true)
end

@testset "non-allocating read" begin
fname1 = tempname() * ".fits"
try
FITS(fname1, "r+") do f

indata = reshape(Float32[1:400;], 20, 20)
write(f, indata)

# Read the entire array using read to compare with read!
a = read(f[1])

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

# Read a 2D slice
b = zeros(eltype(indata),2,2)
read!(f[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!(f[1],b,:,1)
@test a[:,1] == b

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

# Read a part of a 1D slice
b = zeros(eltype(indata),1)
read!(f[1],b,1:1,1)
@test a[1,1] == b[1]
read!(f[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!(f[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!(f[1],b,1,1)
@test_throws TypeError read!(f[1],b)

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

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

@testset "read into views" begin
b = zeros(eltype(indata),size(indata))

# Entire array
b_view = view(b,:,:)
read!(f[1],b_view)
@test a == b

for ax2 in axes(b,2)
b_view = view(b,:,ax2)
read!(f[1],b_view,:,ax2)
@test a[:,ax2] == b[:,ax2]
end

# Non-contiguous views can not be read into
b_view = view(b,1,:)
@test_throws MethodError read!(f[1],b_view,1,:)
end
end
finally
rm(fname1, force=true)
end
end
end

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

0 comments on commit 06df61a

Please sign in to comment.