Skip to content

Commit

Permalink
Merge pull request #36 from timholy/teh/inference
Browse files Browse the repository at this point in the history
Improve inferrabililty
  • Loading branch information
tlnagy authored Feb 25, 2021
2 parents 5f7e640 + a93818a commit 98a617c
Show file tree
Hide file tree
Showing 10 changed files with 97 additions and 74 deletions.
2 changes: 2 additions & 0 deletions src/TiffImages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ include(joinpath("types", "dense.jl"))
include(joinpath("types", "mmap.jl"))
include("load.jl")

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

end # module
18 changes: 9 additions & 9 deletions src/files.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ Wrap `io` with helper parameters to keep track of file attributes.
$(FIELDS)
"""
mutable struct TiffFile{O <: Unsigned}
mutable struct TiffFile{O <: Unsigned, S <: Stream}
"""The relative path to this file"""
filepath::String

"""The file stream"""
io::Stream
io::S

"""Location of the first IFD in the file stream"""
first_offset::Int
Expand All @@ -19,18 +19,18 @@ mutable struct TiffFile{O <: Unsigned}
need_bswap::Bool
end

function TiffFile(offset_size::Type{O}) where O <: Unsigned
TiffFile{offset_size}("", Stream(format"TIFF", IOBuffer()), -1, false)
end
TiffFile{O}(s::Stream) where O <: Unsigned = TiffFile{O, typeof(s)}("", s, -1, false)
TiffFile{O}(io::IO) where O <: Unsigned = TiffFile{O}(Stream(format"TIFF", io))
TiffFile{O}() where O <: Unsigned = TiffFile{O}(IOBuffer())

function Base.read(io::Stream, ::Type{TiffFile})
seekstart(io)
filepath = extract_filename(io)
filepath = String(extract_filename(io))::String
bs = need_bswap(io)
offset_size = offsetsize(io)
first_offset_raw = read(io, offset_size)
first_offset = Int(bs ? bswap(first_offset_raw) : first_offset_raw)
TiffFile{offset_size}(filepath, io, first_offset, bs)
TiffFile{offset_size, typeof(io)}(filepath, io, first_offset, bs)
end

Base.read(io::IOStream, t::Type{TiffFile}) = read(Stream(format"TIFF", io, extract_filename(io)), t)
Expand Down Expand Up @@ -103,8 +103,8 @@ function Base.read!(file::TiffFile, arr::BitArray)
arr
end

Base.write(file::TiffFile, t) = write(file.io.io, t)
Base.write(file::TiffFile, arr::AbstractVector{Any}) = write(file.io.io, Array{UInt8}(arr))
Base.write(file::TiffFile, t) = write(file.io.io, t)::Int
Base.write(file::TiffFile, arr::AbstractVector{Any}) = write(file.io.io, Array{UInt8}(arr))::Int

Base.seek(file::TiffFile, n::Integer) = seek(file.io, n)

Expand Down
48 changes: 29 additions & 19 deletions src/ifds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ IFD(o::Type{O}) where {O <: Unsigned} = IFD{O}(OrderedDict{UInt16, Tag}())

Base.length(ifd::IFD) = length(ifd.tags)
Base.keys(ifd::IFD) = keys(ifd.tags)
Base.iterate(ifd::IFD) = iterate(ifd.tags)
Base.iterate(ifd::IFD, n::Integer) = iterate(ifd.tags, n)
Base.iterate(ifd::IFD) = iterate(ifd.tags)::Union{Nothing,Tuple{Pair{UInt16,<:Tag},Int}}
Base.iterate(ifd::IFD, n::Int) = iterate(ifd.tags, n)::Union{Nothing,Tuple{Pair{UInt16,<:Tag},Int}}
Base.getindex(ifd::IFD, key::TiffTag) = getindex(ifd, UInt16(key))
Base.getindex(ifd::IFD{O}, key::UInt16) where {O} = getindex(ifd.tags, key)
Base.in(key::TiffTag, v::IFD) = in(UInt16(key), v)
Expand Down Expand Up @@ -110,22 +110,22 @@ struct IFDLayout
end

function output(ifd::IFD)
nrows = Int(ifd[IMAGELENGTH].data)
ncols = Int(ifd[IMAGEWIDTH].data)
nrows = Int(ifd[IMAGELENGTH].data)::Int
ncols = Int(ifd[IMAGEWIDTH].data)::Int

samplesperpixel = Int(ifd[SAMPLESPERPIXEL].data)
samplesperpixel = Int(ifd[SAMPLESPERPIXEL].data)::Int
sampleformats = fill(UInt16(0x01), samplesperpixel)
if SAMPLEFORMAT in ifd
sampleformats = ifd[SAMPLEFORMAT].data
sampleformats = ifd[SAMPLEFORMAT].data # can a type be specified for this?
end

interpretation = Int(ifd[PHOTOMETRIC].data)
interpretation = Int(ifd[PHOTOMETRIC].data)::Int

strip_nbytes = ifd[STRIPBYTECOUNTS].data
nbytes = Int(sum(strip_nbytes))
bitsperpixel = ifd[BITSPERSAMPLE].data
rawtypes = Set{DataType}()
mappedtypes = Set{DataType}()
nbytes = Int(sum(strip_nbytes))::Int
bitsperpixel = ifd[BITSPERSAMPLE].data::Union{Int,UInt16,Vector{UInt16}}
rawtypes = Base.IdSet{Any}()
mappedtypes = Base.IdSet{Any}()
for i in 1:samplesperpixel
rawtype = rawtype_mapping[(SampleFormats(sampleformats[i]), bitsperpixel[i])]
push!(rawtypes, rawtype)
Expand All @@ -138,7 +138,7 @@ function output(ifd::IFD)
end
end
(length(rawtypes) > 1) && error("Variable per-pixel storage types are not yet supported")
rawtype = first(rawtypes)
rawtype = first(rawtypes)::DataType
readtype = rawtype

compression = COMPRESSION_NONE
Expand All @@ -158,7 +158,7 @@ function output(ifd::IFD)
nbytes,
readtype,
rawtype,
first(mappedtypes),
first(mappedtypes)::DataType,
compression,
PhotometricInterpretations(interpretation))
end
Expand All @@ -175,7 +175,7 @@ function Base.read!(target::AbstractArray{T, N}, tf::TiffFile, ifd::IFD) where {
if layout.compression != COMPRESSION_NONE
# strip_nbytes is the number of bytes pre-inflation so we need to
# calculate the expected size once decompressed and update the values
strip_nbytes = fill(rowsperstrip*layout.ncols, length(strip_nbytes))
strip_nbytes = fill(rowsperstrip*layout.ncols, length(strip_nbytes)::Int)
strip_nbytes[end] = (layout.nrows - (rowsperstrip * (nstrips-1))) * layout.ncols
end

Expand All @@ -189,13 +189,13 @@ function Base.read!(target::AbstractArray{T, N}, tf::TiffFile, ifd::IFD) where {
if nstrips > 1
startbyte = 1
for i in 1:nstrips
seek(tf, strip_offsets[i])
nbytes = Int(strip_nbytes[i] / sizeof(T))
seek(tf, strip_offsets[i]::Core.BuiltinInts)
nbytes = Int(strip_nbytes[i]::Core.BuiltinInts / sizeof(T))
read!(tf, view(target, startbyte:(startbyte+nbytes-1)), layout.compression)
startbyte += nbytes
end
else
seek(tf, strip_offsets[1])
seek(tf, strip_offsets[1]::Core.BuiltinInts)
read!(tf, target, layout.compression)
end
end
Expand All @@ -222,14 +222,24 @@ function Base.write(tf::TiffFile{O}, ifd::IFD{O}) where {O <: Unsigned}
write(tf, O(0))

for (tag, poses) in remotedata
tag = tag::Tag
data_pos = position(tf.io)
data = tag.data
# add NUL terminator to the end of Strings that don't have it already
data = (eltype(tag) == String && !endswith(tag.data, '\0')) ? tag.data * "\0" : tag.data
write(tf, data)
if eltype(tag) === String
data = data::SubString{String}
if !endswith(data, '\0')
data *= "\0"
end
write(tf, data) # compile-time dispatch
else
write(tf, data) # run-time dispatch
end
push!(poses, data_pos)
end

for (tag, poses) in remotedata
tag = tag::Tag
orig_pos, data_pos = poses
seek(tf, orig_pos)
write(tf, tag, data_pos)
Expand Down
27 changes: 14 additions & 13 deletions src/layout.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
nrows(ifd::IFD) = Int(ifd[IMAGELENGTH].data)
ncols(ifd::IFD) = Int(ifd[IMAGEWIDTH].data)
nsamples(ifd::IFD) = Int(ifd[SAMPLESPERPIXEL].data)
nrows(ifd::IFD) = Int(ifd[IMAGELENGTH].data)::Int
ncols(ifd::IFD) = Int(ifd[IMAGEWIDTH].data)::Int
nsamples(ifd::IFD) = Int(ifd[SAMPLESPERPIXEL].data)::Int

"""
interpretation(ifd)
Expand Down Expand Up @@ -36,25 +36,25 @@ interpretation(::Val{PHOTOMETRIC_YCBCR}) = YCbCr
interpretation(::Val{PHOTOMETRIC_CIELAB}) = Lab

function interpretation(p::PhotometricInterpretations, extrasamples::ExtraSamples, samplesperpixel::Int)
interp = interpretation(p)
len = length(interp)
interp = interpretation(p)::Type{<:Colorant}
len = length(interp)::Int
if len + 1 == samplesperpixel
return interpretation(p, extrasamples, Val(samplesperpixel))
elseif len == samplesperpixel
return interp, false
elseif len < samplesperpixel
return interp, true
else
error("TIFF file says it contains $interp values, but only has $samplesperpixel samples per pixel instead of the minimum required $(length(interp))")
error("TIFF file says it contains $interp values, but only has $samplesperpixel samples per pixel instead of the minimum required $len")
end
end
_pad(::Type{RGB}) = RGBX
_pad(::Type{T}) where {T} = T

interpretation(p::PhotometricInterpretations, extrasamples::ExtraSamples, nsamples::Val) = interpretation(p, Val(extrasamples), nsamples)
interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNSPECIFIED}, ::Val) = interpretation(p), true
interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNSPECIFIED}, @nospecialize(::Val)) = interpretation(p), true
interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNSPECIFIED}, ::Val{4}) = _pad(interpretation(p)), false
interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_ASSOCALPHA}, ::Val) = coloralpha(interpretation(p)), false
interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_ASSOCALPHA}, @nospecialize(::Val)) = coloralpha(interpretation(p)), false
interpretation(p::PhotometricInterpretations, ::Val{EXTRASAMPLE_UNASSALPHA}, nsamples::Val) = interpretation(p, Val(EXTRASAMPLE_ASSOCALPHA), nsamples)

_mappedtype(::Type{T}) where {T} = T
Expand All @@ -76,10 +76,11 @@ end
Allocate a cache for this IFD with correct type and size.
"""
getcache(ifd::IFD) = getcache(ifd, Val(rawtype(ifd)))
getcache(ifd::IFD, ::Val{Bool}) = BitArray(undef, ncols(ifd), nrows(ifd))
function getcache(ifd::IFD, ::Val{T}) where {T}
function getcache(ifd::IFD)
T = rawtype(ifd)
if T === Bool
return BitArray(undef, ncols(ifd), nrows(ifd))
end
colortype, extras = interpretation(ifd)

Array{colortype{_mappedtype(T)}}(undef, ncols(ifd), nrows(ifd))
return Array{colortype{_mappedtype(T)}}(undef, ncols(ifd), nrows(ifd))
end
17 changes: 10 additions & 7 deletions src/load.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ function load(filepath::String; verbose=true, mmap = false)
end
end

function load(io::IOStream; verbose=true, mmap = false)
tf = read(io, TiffFile)

load(io::IOStream; verbose=true, mmap = false) = load(read(io, TiffFile); verbose=verbose, mmap=mmap)
function load(tf::TiffFile; verbose=true, mmap = false)
isdense = true
ifds = IFD{offset(tf)}[]

Expand All @@ -33,7 +32,11 @@ function load(io::IOStream; verbose=true, mmap = false)
if mmap
loaded = DiskTaggedImage(tf, ifds)
else
loaded = load(tf, ifds, Val(nplanes); verbose=verbose)
if nplanes == 1
loaded = load(tf, ifds, nothing; verbose=verbose)
else
loaded = load(tf, ifds, nplanes; verbose=verbose)
end
end

if eltype(loaded) <: Palette
Expand All @@ -54,15 +57,15 @@ function load(io::IOStream; verbose=true, mmap = false)
return DenseTaggedImage(data, ifds)
end

function load(tf::TiffFile, ifds, ::Val{1}; verbose = true)
function load(tf::TiffFile, ifds::AbstractVector{<:IFD}, ::Nothing; verbose = true)
ifd = ifds[1]
cache = getcache(ifd)
read!(cache, tf, ifd)

return Array(cache')
return Matrix(cache')
end

function load(tf::TiffFile, ifds, ::Val{N}; verbose = true) where {N}
function load(tf::TiffFile, ifds::AbstractVector{<:IFD}, N; verbose = true)
ifd = ifds[1]

cache = getcache(ifd)
Expand Down
29 changes: 18 additions & 11 deletions src/tags.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ load(tf::TiffFile, t::Tag) = t
function load(tf::TiffFile{O}, t::Tag{RemoteData{O}}) where {O <: Unsigned}
T = t.data.datatype
N = t.data.count
rawdata = Vector{UInt8}(undef, bytes(T)*N)
nb = bytes(T)::Int
rawdata = Vector{UInt8}(undef, nb*N)

pos = position(tf.io)
seek(tf, t.data.position)
Expand All @@ -54,7 +55,7 @@ function load(tf::TiffFile{O}, t::Tag{RemoteData{O}}) where {O <: Unsigned}
# if this datatype is comprised of multiple bytes and this file needs to be
# bitswapped then we'll need to reverse the byte order inside each datatype
# unit
if tf.need_bswap && bytes(T) >= 2
if tf.need_bswap && nb >= 2
reverse!(rawdata)
data = Array{T}(reverse(reinterpret(T, rawdata)))
elseif T == String
Expand All @@ -70,7 +71,7 @@ function load(tf::TiffFile{O}, t::Tag{RemoteData{O}}) where {O <: Unsigned}
end
seek(tf, pos)

Tag(t.tag, data)
Tag(t.tag, data)::Tag
end

bytes(x::Type) = sizeof(x)
Expand All @@ -91,19 +92,19 @@ function Base.read(tf::TiffFile{O}, ::Type{Tag}) where O <: Unsigned
T = tiff_to_julian[datatype]
end

nbytes = bytes(T) * count
nbytes = bytes(T)::Int * count
if nbytes <= sizeof(O)
if tf.need_bswap
reverse!(view(data, 1:nbytes))
end
if T == String
if T === String
return Tag(tag, String(data))
elseif T == Any
return Tag(tag, Array{Any}(data))
elseif T === Any
return Tag(tag, Vector{Any}(data))
elseif count == 1
return Tag(tag, first(reinterpret(T, data)))
return Tag(tag, first(reinterpret(T, data)))::Tag
else
return Tag(tag, Array(reinterpret(T, data)[1:Int(count)]))
return Tag(tag, reinterpret(T, data)[1:Int(count)])
end
else
(tf.need_bswap) && reverse!(data)
Expand Down Expand Up @@ -145,13 +146,19 @@ function Base.write(tf::TiffFile{O}, t::Tag{T}) where {O <: Unsigned, T}
return false
end

data = t.data
# add NUL terminator to the end of Strings that don't have it already
data = (T == String && !endswith(t.data, '\0')) ? t.data * "\0" : t.data
if T === String
data = data::SubString{String}
if !endswith(data, '\0')
data *= "\0"
end
end

write(tf, t.tag)
write(tf, julian_to_tiff[eltype(t)])
write(tf, O(length(t)))
nbytes = write(tf.io, data)
nbytes = write(tf.io, data)::Int

# write padding
if nbytes < sizeof(O)
Expand Down
5 changes: 2 additions & 3 deletions src/types/dense.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@ Base.write(io::IOStream, img::DenseTaggedImage) = write(Stream(format"TIFF", io,

function Base.write(io::Stream, img::DenseTaggedImage)
O = offset(img)
tf = TiffFile(O)
tf.io = io
tf = TiffFile{O}(io)

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

Expand All @@ -104,7 +103,7 @@ function Base.write(io::Stream, img::DenseTaggedImage)
ifd[COMPRESSION] = COMPRESSION_NONE
ifd[STRIPOFFSETS] = O(data_pos)
ifd[STRIPBYTECOUNTS] = O(ifd_pos-data_pos)
ifd[SOFTWARE] = "$(parentmodule(IFD)).jl v$PKGVERSION"
ifd[SOFTWARE] = "$(parentmodule(IFD)::Module).jl v$PKGVERSION"
sort!(ifd.tags)

seek(tf.io, ifd_pos)
Expand Down
9 changes: 5 additions & 4 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ const fdpattern = r"<fd (.*)>"
Extract the name of the file backing a stream
"""
function extract_filename(io::IOStream)
filename = match(filepattern, io.name)
name = String(io.name)
filename = match(filepattern, name)
if filename !== nothing
return filename[1]
elseif match(fdpattern, io.name) !== nothing
return String(filename[1])
elseif match(fdpattern, name) !== nothing
return ""
else
error("Can't extract filename from the given stream")
Expand Down Expand Up @@ -156,7 +157,7 @@ const julian_to_tiff = Dict(
)

# sampleformat, bitspersample => Julian type
const rawtype_mapping = Dict(
const rawtype_mapping = Dict{Tuple{TiffImages.SampleFormats, UInt16}, DataType}(
(SAMPLEFORMAT_UINT, 1) => Bool,
(SAMPLEFORMAT_UINT, 8) => UInt8,
(SAMPLEFORMAT_UINT, 16) => UInt16,
Expand Down
Loading

0 comments on commit 98a617c

Please sign in to comment.