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

rework ifd handling and deprecate directly accessing ifds #110

Merged
merged 1 commit into from
Sep 20, 2023
Merged
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
5 changes: 2 additions & 3 deletions examples/writing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ dump(img; maxdepth=1)

# Lets take a look at what tags there are:

ifd = first(img.ifds) # since our data is 2D
ifd
ifd = ifds(img) # returns a single IFD since our data is 2D

# ### Manipulating TIFF Tags
#
Expand Down Expand Up @@ -145,7 +144,7 @@ img3 = TiffImages.DenseTaggedImage(colors)
data = rand(-100:100, 5, 5)
#--------------------------
img4 = TiffImages.DenseTaggedImage(reinterpret(Gray{Q0f63}, data))
println(img4.ifds[1])
println(ifds(img4))

# As you can see the `SAMPLEFORMATS` and `BITSPERSAMPLE` tags correctly updated
# to show that this TIFF contains signed integers and 64-bit data, respectively.
Expand Down
2 changes: 1 addition & 1 deletion src/TiffImages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ include(joinpath("types", "lazy.jl"))
include(joinpath("types", "mmapped.jl"))
include("load.jl")

export memmap, LazyBufferedTIFF
export memmap, LazyBufferedTIFF, ifds

@deprecate TiffFile(::Type{O}) where O<:Unsigned TiffFile{O}()

Expand Down
20 changes: 20 additions & 0 deletions src/types/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ abstract type AbstractDenseTIFF{T, N} <: AbstractTIFF{T, N} end

abstract type AbstractStridedTIFF{T, N} <: AbstractTIFF{T, N} end

function Base.getproperty(img::T, sym::Symbol) where {T <: AbstractTIFF}
if sym === :ifds
Base.depwarn("Directly accessing the ifds property is deprecated" *
" and will be removed in TiffImages v0.7.0+. Please use `ifds(img)` instead.",
:AbstractTIFF)
end
getfield(img, sym)
end

"""
ifds(img)

Get the Image File Directories of `img`, see [`TiffImages.IFD`] for details.
Returns a single IFD for a 2D TIFF. Otherwise, returns a list of IFDs with a
length equal to the third dimension of a 3D TIFF.
```
"""
ifds(img::I) where {I <: AbstractTIFF{T, 2} where {T}} = first(getfield(img, :ifds))
ifds(img::I) where {I <: AbstractTIFF} = getfield(img, :ifds)

interpretation(img::AbstractArray) = interpretation(eltype(img))
interpretation(::Type{T}) where {T <: Gray} = PHOTOMETRIC_MINISBLACK
interpretation(::Type{T}) where {T <: AbstractRGB} = PHOTOMETRIC_RGB
Expand Down
15 changes: 7 additions & 8 deletions src/types/dense.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ julia> img = TiffImages.DenseTaggedImage(Gray.(zeros(UInt8, 10, 10)));
julia> size(img)
(10, 10)

julia> img.ifds
1-element Vector{TiffImages.IFD{UInt32}}:
IFD, with tags:
julia> ifds(img)
IFD, with tags:
Tag(IMAGEWIDTH, 10)
Tag(IMAGELENGTH, 10)
Tag(BITSPERSAMPLE, 8)
Tag(PHOTOMETRIC, 1)
Tag(SAMPLESPERPIXEL, 1)
Tag(SAMPLEFORMAT, 1)

julia> first(img.ifds)[TiffImages.XRESOLUTION] = 0x00000014//0x00000064 # write custom data
julia> ifds(img)[TiffImages.XRESOLUTION] = 0x00000014//0x00000064 # write custom data
0x00000001//0x00000005

julia> TiffImages.save(mktemp()[2], img); # write to temp file
Expand Down Expand Up @@ -68,7 +67,7 @@ Base.axes(t::DenseTaggedImage) = axes(t.data)
@propagate_inbounds Base.getindex(img::DenseTaggedImage, i...) = getindex(img.data, i...)
@propagate_inbounds Base.setindex!(img::DenseTaggedImage, i...) = setindex!(img.data, i...)
@propagate_inbounds function Base.getindex(img::DenseTaggedImage{T, 3}, i::Colon, j::Colon, k) where {T}
DenseTaggedImage(getindex(img.data, i, j, k), img.ifds[k])
DenseTaggedImage(getindex(img.data, i, j, k), ifds(img)[k])
end

# Override the fallback convert to get better performance
Expand Down Expand Up @@ -179,16 +178,16 @@ function _writeslice(pagecache, tf::TiffFile{O, S}, slice, ifd, prev_ifd_record)
prev_ifd_record
end

function Base.write(io::Stream, img::DenseTaggedImage)
function Base.write(io::Stream, img::I) where {I <: DenseTaggedImage{T, N}} where {T, N}
O = offset(img)
tf = TiffFile{O}(io)

prev_ifd_record = write(tf) # record that will have be updated

pagecache = Vector{UInt8}(undef, size(img.data, 2) * sizeof(eltype(img.data)) * size(img.data, 1))

# For offseted arrays, `axes(img, 3) == 1:length(img.ifds)` does not hold in general
for (idx, ifd) in zip(axes(img, 3), img.ifds)
# For offseted arrays, `axes(img, 3) == 1:length(ifds(img))` does not hold in general
for (idx, ifd) in zip(axes(img, 3), N == 3 ? ifds(img) : [ifds(img)])
prev_ifd_record = _writeslice(pagecache, tf, view(img, :, :, idx), ifd, prev_ifd_record)
end

Expand Down
14 changes: 7 additions & 7 deletions src/types/lazy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function Base.getindex(A::LazyBufferedTIFF{T, O, AA}, i1::Int, i2::Int, i::Int)
return A.cache[i2, i1]
end

ifd = A.ifds[i]
ifd = ifds(A)[i]

# if the file isn't open, lets open a handle and update it
if !isopen(A.file.io)
Expand Down Expand Up @@ -162,7 +162,7 @@ the file
```jldoctest; setup = :(using TiffImages, ColorTypes, FixedPointNumbers)
julia> slice = TiffImages.DenseTaggedImage(Gray.(zeros(UInt8, 10, 10)));

julia> first(slice.ifds)[TiffImages.IMAGEDESCRIPTION] = "Custom info";
julia> ifds(slice)[TiffImages.IMAGEDESCRIPTION] = "Custom info";

julia> temp_file = joinpath(mktempdir(), "tmp.tif");

Expand All @@ -172,7 +172,7 @@ julia> push!(lazy_tiff, slice);

julia> close(lazy_tiff) # done writing, close open stream

julia> first(TiffImages.load(temp_file).ifds)[TiffImages.IMAGEDESCRIPTION] # read from disk
julia> ifds(TiffImages.load(temp_file))[TiffImages.IMAGEDESCRIPTION] # read from disk
Tag(IMAGEDESCRIPTION, "Custom info")

```
Expand All @@ -182,7 +182,7 @@ function Base.push!(A::LazyBufferedTIFF{T, O, AA}, tiff::D) where {T, O, AA, D <
data = tiff.data

# merge a minimal IFD with the correct offset with any user provided tags
ifd = merge(_constructifd(data, offset(A.file)), tiff.ifds[1])
ifd = merge(_constructifd(data, offset(A.file)), ifds(tiff))

if size(A) == (0, 0, 0) # if this is the initial slice pushed, initialize the size
A.dims = (size(data)..., 0)
Expand All @@ -193,14 +193,14 @@ function Base.push!(A::LazyBufferedTIFF{T, O, AA}, tiff::D) where {T, O, AA, D <
pagecache = Vector{UInt8}(undef, size(data, 2) * sizeof(T) * size(data, 1))

if A.last_ifd_offset + sizeof(pagecache) + sizeof(ifd) > typemax(O)
@info "No more room in file @ N = $(length(A.ifds))"
@info "No more room in file @ N = $(length(ifds(A)))"
end
seekend(A.file.io)
prev_ifd_slice = _writeslice(pagecache, A.file, data, ifd, A.last_ifd_offset)

# update data
A.dims = (A.dims[1], A.dims[2], A.dims[3] + 1)
push!(A.ifds, ifd)
push!(ifds(A), ifd)
A.last_ifd_offset = prev_ifd_slice
A.cache = data
A.cache_index = A.dims[3]
Expand All @@ -220,7 +220,7 @@ function Base.show(io::IO, ::MIME"text/plain", A::LazyBufferedTIFF{T, O, AA}) wh
end
println(io)
ondisk = sizeof(A.file)
ondisk += (size(A) == (0, 0, 0)) ? 0 : sum(sizeof.(A.ifds)) + sizeof(T) * reduce(*, size(A))
ondisk += (size(A) == (0, 0, 0)) ? 0 : sum(sizeof.(ifds(A))) + sizeof(T) * reduce(*, size(A))
println(io, " Current file size on disk: $(Base.format_bytes(ondisk))")
println(io, " Addressable space remaining: $(Base.format_bytes(typemax(O) - ondisk))")
end
Expand Down
8 changes: 4 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ end
@test red(img[92, 21, 2]) == 0.74118N0f16

# make sure IFDs are included if whole slice is grabbed
@test length(img[:, :, 2].ifds) == 1
@test length(img[:, :, 2:5].ifds) == 4
@test typeof(ifds(img[:, :, 2])) <: TiffImages.IFD
@test length(ifds(img[:, :, 2:5])) == 4

# make sure that subslicing in XY drops IFD info
@test typeof(img[:, 50:60, 27]) == Array{RGB{Normed{UInt16,16}},2}
Expand Down Expand Up @@ -166,7 +166,7 @@ end
img = TiffImages.load(filepath)

# verify that strip offsets are correctly bswapped for endianness
img_stripoffsets = Int.(img.ifds[1][TiffImages.STRIPOFFSETS].data)
img_stripoffsets = Int.(ifds(img)[TiffImages.STRIPOFFSETS].data)
@test img_stripoffsets == [8, 129848, 259688, 389528]

# Efficient convert method
Expand All @@ -179,7 +179,7 @@ end
img = TiffImages.load(filepath)

# verify that strip offsets are correctly bswapped for endianness
img_stripoffsets = Int.(img.ifds[1][TiffImages.STRIPOFFSETS].data)
img_stripoffsets = Int.(ifds(img)[TiffImages.STRIPOFFSETS].data)
@test issorted(img_stripoffsets)

# Efficient convert method
Expand Down
2 changes: 1 addition & 1 deletion test/writer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,5 @@ end
path, io = mktemp()
write(io, img2)
img3 = TiffImages.load(path)
@test occursin("test;", img3.ifds[1][TiffImages.SOFTWARE].data)
@test occursin("test;", ifds(img3)[TiffImages.SOFTWARE].data)
end
Loading