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

Improve inferrabililty #36

Merged
merged 7 commits into from
Feb 25, 2021
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
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