diff --git a/Project.toml b/Project.toml index cc23fe9e..247c5292 100644 --- a/Project.toml +++ b/Project.toml @@ -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" diff --git a/src/TiffImages.jl b/src/TiffImages.jl index 7d7f4c90..5b9622cb 100644 --- a/src/TiffImages.jl +++ b/src/TiffImages.jl @@ -7,6 +7,7 @@ end using ColorTypes using DocStringExtensions using FileIO +using FileWatching using FixedPointNumbers using IndirectArrays using OffsetArrays @@ -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 diff --git a/src/types/mmap.jl b/src/types/mmap.jl index c9b8c368..de53ea3a 100644 --- a/src/types/mmap.jl +++ b/src/types/mmap.jl @@ -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 """ @@ -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] @@ -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