tlnagy / TiffImages.jl

💎 Pure-Julia TIFF I/O with a focus on correctness 🧐
http://tamasnagy.com/TiffImages.jl/
MIT License
51 stars 13 forks source link

JPEG compression support #83

Open johnnychen94 opened 2 years ago

johnnychen94 commented 2 years ago

I found this "quad-jpeg.tif" from the libtiff test images https://libtiff.gitlab.io/libtiff/images.html

I just put up a draft JpegTurbo version to support abbreviated data stream https://github.com/JuliaIO/JpegTurbo.jl/pull/25, I believe jpeg_decode(table_bytes, data_bytes) should make things doable. I'd be glad to support any JpegTurbo features needed here.

Because ColorTypes doesn't support YCbCr{N0f8}, we might need some special trick to work around this.

quad-jpeg.tif.zip

tlnagy commented 2 years ago

I don't see myself having the bandwidth this year for this (trying to wrap up my PhD work right now). Happy to review a PR though.

What would be involved with adding YCbCr{N0f8} to colortypes?

johnnychen94 commented 2 years ago

Oh, I then have to throw myself into another image format then :) I guess that won't happen until Aug.

What would be involved with adding YCbCr{N0f8} to colortypes?

That probably won't fix the problem. -- Which colorspace does JPEG use to compress the data doesn't affect the output type that much, IIUC, libjpeg-turbo always outputs one of Gray, RGB, AGray, ARGB color. Thus we can't use the current interpretation for YCbCr to allocate the output array.

https://github.com/tlnagy/TiffImages.jl/blob/e9cc7076cd60b63f41b700271d8386041aa1e769/src/layout.jl#L42

Is YCbCr used somewhere else?

tlnagy commented 1 year ago

Sorry for the delay here, but interpretation is only used for the mapping of the reported colorway in the TIFF file to the julia equivalent, e.g. if there are 3 Float64s in a row then are they RGB{Float64}s or HSL{Float64}, etc. So that's the only place I explicitly use the ColorType structs.

I'm assuming this is the error you're talking about?

julia> TiffImages.load("/home/tlnagy/Downloads/libtiffpic/quad-jpeg.tif")
ERROR: TypeError: in YCbCr, in T, expected T<:AbstractFloat, got Type{FixedPointNumbers.N0f8}
Stacktrace:
  [1] getcache(ifd::TiffImages.IFD{UInt32})
    @ TiffImages ~/.julia/packages/TiffImages/P86VX/src/layout.jl:92
  [2] load(tf::TiffFile{UInt32, FileIO.Stream{FileIO.DataFormat{:TIFF}, IOStream, String}}, ifds::Vector{TiffImages.IFD{UInt32}}, ::Nothing; verbose::Bool)
    @ TiffImages ~/.julia/packages/TiffImages/P86VX/src/load.jl:66
  [3] load(tf::TiffFile{UInt32, FileIO.Stream{FileIO.DataFormat{:TIFF}, IOStream, String}}; verbose::Bool, mmap::Bool)
    @ TiffImages ~/.julia/packages/TiffImages/P86VX/src/load.jl:31
  [4] load(io::IOStream; verbose::Bool, mmap::Bool)
    @ TiffImages ~/.julia/packages/TiffImages/P86VX/src/load.jl:16
  [5] #10
    @ ~/.julia/packages/TiffImages/P86VX/src/load.jl:12 [inlined]
  [6] open(f::TiffImages.var"#10#11"{Bool, Bool}, args::String; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Base ./io.jl:330
  [7] open
    @ ./io.jl:328 [inlined]
  [8] #load#9
    @ ~/.julia/packages/TiffImages/P86VX/src/load.jl:11 [inlined]
  [9] load(filepath::String)
    @ TiffImages ~/.julia/packages/TiffImages/P86VX/src/load.jl:11
 [10] top-level scope
    @ REPL[2]:1

Lets look at the first IFD in the TIFF file using TiffImages:

julia> io = open("/home/tlnagy/Downloads/libtiffpic/quad-jpeg.tif")
IOStream(<file /home/tlnagy/Downloads/libtiffpic/quad-jpeg.tif>)

julia> tf = read(io, TiffImages.TiffFile);

julia> ifd = first(tf); # get first ifd

julia> TiffImages.load!(tf, ifd); # load remote data

julia> ifd
IFD, with tags: 
    Tag(IMAGEWIDTH, 512)
    Tag(IMAGELENGTH, 384)
    Tag(BITSPERSAMPLE, UInt16[8, 8, 8])
    Tag(COMPRESSION, COMPRESSION_JPEG)
    Tag(PHOTOMETRIC, 6)
    Tag(STRIPOFFSETS, UInt32[8, 175, 342, 633, 2227, ...])
    Tag(SAMPLESPERPIXEL, 3)
    Tag(ROWSPERSTRIP, 16)
    Tag(STRIPBYTECOUNTS, UInt32[167, 167, 291, 1594, 1456, ...])
    Tag(PLANARCONFIG, 1)
    Tag(XPOSITION, 0x00000001//0x00000000)
    Tag(YPOSITION, 0x00000001//0x00000000)
    Tag(JPEGTABLES, Any[255, 216, 255, 219, 0, ...])
    Tag(REFERENCEBLACKWHITE, Rational{UInt32}[0x00000001//0x00000000, 0x00200000//0x1fe00000, 0x00200000//0x10000000, 0x00200000//0x1fe00000, 0x00200000//0x10000000, ...])

A photometric representation of 6 is, according to https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html, equal to YCbCr. Which means that we need to report the final image (after decompression) as YCbCr{N0f8} (BITSPERSAMPLE is 8) so the lack of YCbCr{N0f8} in Julia will prevent us from representing the results faithfully even if decompression support is added.

MendeBadra commented 2 weeks ago

Hello! I've been trying to open a large TIFF file using TiffImages.jl, but I encountered this error:

julia> img_path = "/media/jaki-73/HDD/MendePythonTest/Liver Fat WSI/1_20240522130835.tif";
julia> load(img_path)
Errors encountered while load File{DataFormat{:TIFF}, String}("/media/jaki-73/HDD/MendePythonTest/Liver Fat WSI/1_20240522130835.tif").
All errors:
===========================================
TaskFailedException

    nested task error: Compression COMPRESSION_JPEG is not implemented. Please open an issue against TiffImages.jl.

This is followed by a lengthy stack trace.

Additionally, I ran a code snippet similar to the previous comment:

julia> using TiffImages

julia> tf = read(io, TiffImages.TiffFile);

julia> ifd = first(tf)
IFD, with tags: 
    Tag(IMAGEWIDTH, 121600)
    Tag(IMAGELENGTH, 86272)
    Tag(BITSPERSAMPLE, UInt16[8, 8, 8])
    Tag(COMPRESSION, COMPRESSION_JPEG)
    Tag(PHOTOMETRIC, 2)
    Tag(SAMPLESPERPIXEL, 3)
    Tag(ROWSPERSTRIP, 86272)
    Tag(XRESOLUTION, 0x4b000000//0x00200000)
    Tag(YRESOLUTION, 0x4b000000//0x00200000)
    Tag(PLANARCONFIG, 1)
    Tag(TILEWIDTH, 256)
    Tag(TILELENGTH, 256)
    Tag(TILEOFFSETS, REMOTE@4515844682 UInt64[] len=160075)
    Tag(TILEBYTECOUNTS, REMOTE@4517125282 UInt64[] len=160075)
    Tag(JPEGTABLES, REMOTE@4518405882 Any[] len=289)
    Tag(YCBCRSUBSAMPLING, UInt16[2, 2])

julia> TiffImages.load!(tf, ifd)

It seems that TiffImages.jl does not support the JPEG compression method (COMPRESSION_JPEG), which caused the initial error. Could someone confirm if this is correct? If so, when can we expect support for this feature? As a beginner in Julia, I'm not sure about the effort required to implement this, so please forgive me if my question seems naive.