Skip to content
Open
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
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ version = "0.2.2"
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
FileWatching = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
IndirectArrays = "9b13fd28-a010-5f03-acff-a1bbcff69959"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
Expand Down
3 changes: 3 additions & 0 deletions src/TiffImages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ end
using ColorTypes
using DocStringExtensions
using FileIO
using FileWatching
using FixedPointNumbers
using IndirectArrays
using OffsetArrays
Expand All @@ -29,6 +30,8 @@ include(joinpath("types", "dense.jl"))
include(joinpath("types", "mmap.jl"))
include("load.jl")

export convert!

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

end # module
81 changes: 68 additions & 13 deletions src/types/mmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ images too large to store in memory.

$(FIELDS)
"""
mutable struct DiskTaggedImage{T <: Colorant, O <: Unsigned, AA <: AbstractArray} <: AbstractDenseTIFF{T, 3}
mutable struct DiskTaggedImage{T <: Colorant, O <: Unsigned, S <: Stream, AA <: AbstractArray} <: AbstractDenseTIFF{T, 3}

"""
Pointer to keep track of the backing file
"""
file::TiffFile{O}
file::TiffFile{O, S}
"""
The associated tags for each slice in this array
"""
Expand All @@ -29,22 +29,53 @@ mutable struct DiskTaggedImage{T <: Colorant, O <: Unsigned, AA <: AbstractArray
"""
cache_index::Int

microcache::Vector{T}

"""
A flag tracking whether this file is editable
"""
readonly::Bool
end

function DiskTaggedImage(file::TiffFile{O}, ifds::Vector{IFD{O}}) where {O}
ifd = ifds[1]
dims = (nrows(ifd), ncols(ifd), length(ifds))
cache = getcache(ifd)
new{eltype(cache), O, typeof(cache)}(file, ifds, dims, cache, -1, false)
end
function DiskTaggedImage(file::TiffFile{O, S}, ifds::Vector{IFD{O}}) where {O, S}
ifd = ifds[1]
dims = (nrows(ifd), ncols(ifd), length(ifds))
cache = getcache(ifd)
microcache = Vector{eltype(cache)}(undef, 1)
img = DiskTaggedImage{eltype(cache), O, S, typeof(cache)}(file, ifds, dims, cache, -1, microcache, true)
@async watch(img)
img
end

Base.size(A::DiskTaggedImage) = A.dims

function Base.getindex(A::DiskTaggedImage{T, O, AA}, i1::Int, i2::Int, i::Int) where {T, O, AA}
function convert!(A::DiskTaggedImage; writeable=false)
if writeable
A.readonly = false
try
close(A.file.io)
catch
end
path = A.file.filepath
A.file.io = Stream(format"TIFF", open(path, append=true, read=true), path)
end
return nothing
end

function watch(A::DiskTaggedImage)
@info "Watching $(A.file.filepath) for changes"
while true
ev = watch_file(A.file.filepath)
if ev.renamed
error("Watched file $(A.file.filepath) has been moved.")
elseif ev.changed
A.cache_index = -1 #invalidate cache
convert!(A; writeable=!A.readonly)
end
end
end

function Base.getindex(A::DiskTaggedImage, i1::Int, i2::Int, i::Int)
# check the loaded cache is already the correct slice
if A.cache_index == i
return A.cache[i2, i1]
Expand All @@ -65,8 +96,32 @@ function Base.getindex(A::DiskTaggedImage{T, O, AA}, i1::Int, i2::Int, i::Int) w
return A.cache[i2, i1]
end

function Base.setindex!(A::DiskTaggedImage, I...)
error("This array is on disk and is read only. Convert to a mutable in-memory version by running "*
"`copy(arr)`. \n\n𝗡𝗼𝘁𝗲: For large files this can be quite expensive. A future PR will add "*
"support for reading and writing to/from disk.")
function Base.setindex!(A::DiskTaggedImage{T, O, S, AA}, value, i1::Int, i2::Int, i3::Int) where {T, O, S, AA}
if A.readonly
error("This is a read only memory-mapped file. Make sure to run convert!(img, writeable=true) first.")
end

linear = LinearIndices((1:size(A, 2), 1:size(A, 1)))
linidx = O(linear[i2, i1] * sizeof(T))::O

ifd = A.ifds[i3]

bytecounts = ifd[STRIPBYTECOUNTS].data::Vector{O}

stripidx = 1
counts = O(0)
for i in 1:length(bytecounts)
stripidx = i
counts = bytecounts[stripidx]::O
(linidx < counts) && break
linidx -= counts
end

A.cache_index = -1

offsets = ifd[STRIPOFFSETS].data::Vector{O}
offset = offsets[stripidx]::O
seek(A.file, Int(offset+linidx))
A.microcache[1] = value
write(A.file.io, A.microcache)
end