diff --git a/src/FITSIO.jl b/src/FITSIO.jl index 3c4dde0..344db5c 100644 --- a/src/FITSIO.jl +++ b/src/FITSIO.jl @@ -18,6 +18,7 @@ import Base: getindex, length, show, read, + read!, write, close, ndims, diff --git a/src/image.jl b/src/image.jl index db066e0..5ab2257 100644 --- a/src/image.jl +++ b/src/image.jl @@ -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 @@ -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) diff --git a/src/libcfitsio.jl b/src/libcfitsio.jl index 170c3ee..fd36edf 100644 --- a/src/libcfitsio.jl +++ b/src/libcfitsio.jl @@ -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. " * @@ -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, @@ -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, @@ -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, diff --git a/test/runtests.jl b/test/runtests.jl index be7df01..3a86427 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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