Reading individual slices of a TIFF stack (aka multipage TIFF)

Hello,

I am having trouble finding a way to access individual slices of a TIFF stack. I have a bunch of large TIFF stacks that are all too big to load into memory. In Matlab you can load a specific index of a TIFF stack through imread(filename,index). With Images.jl an entire image stack can be read into memory with the load() function. I have tried using load("image.tif";view=true) but it still appears to be reading the whole stack into memory (actually adding in the keyword view=true makes things even worse. It took up ~12GB of memory while reading in 2GB file).

If anyone can provide advice I would quite appreciate it.

2 Likes

If you can’t find a nice Julia solution, perhaps you could just split your TIFF stack into individual image files and then load the appropriate file corresponding to the image you want? Something like: imagemagick - Creating and splitting large multipage TIFF images - Super User

You can do that with GMT.jl but unfortunately we added an unnoticed bug in GMT5 (the C library, not the Julia wrapper) that is fixed only on the GMT trunk version. So access that facility you will need to have to build GMT dev version yourself.
GDAL.jl is probably another option.

Images.jl certainly supports the ability to do out-of-memory loading, but it’s highly dependent on the file reader. For example, my package for loading OME-TIFFs (Multipage TIFFs) does not support out-of-memory loading while NRRD.jl does

TL;DR: IMO the best solution is to use NRRD files for now with Images.jl

Thank you for the replies!

Interesting, I am having trouble finding documentation for loading in tiffs with either of the packages. Could you point me to where this information is/may be?

That is quite unfortunate that the package doesn’t support out-of-memory loading. Due to the number/size of the files and the metadata they contain is important for downstream applications, I would strongly prefer avoiding splitting up or converting the files. Would it be feasible for me to use functions in your package to implement a solution similar to looping with PIL’s seek function?

Sorry, new modules addded recently and docs not fully updated (and I’m in vacations now).
There is a new module now called gmtread that lets do what you want and has an on line help. Basically you do

I = gmtread(“fname.tif”, img=true, band=0);

To read the first band, and so on for the other bands. As I said before, this needs GMT6dev to work and there is another thing you must do. Edit the GMT.jl file and change GMTver from 5 to 6.

If you are on Windows you can use the GMT binaries from here

To answer your question: Yes, you should be able to use the functions from my package to do that. I currently load all slices inside a multipage TIFF here. You could modify that to only load a specific slice.

More in general: I haven’t had time to work out the details for memory mapping multipage OME-TIFF files, but I am working on it. The main rub is that I need to create a performant way of accessing arbitrary slices through the data. Your case isn’t too hard since multipage TIFFs store slices in a XY format so it’s fast to access that. Just get the slice’s location and load that single one in. However, what about if the user wants XZ or ZT or CZY? That’s when it gets a little hairy. Julia’s standard mmap doesn’t work well for discontiguous data so I’ll need to write a custom Array type to do this quickly (@tim.holy suggested this here).

The problem is mostly finding the time to work this out and make it fast!

For anyone who may run into this problem in the future here is the most straight forward solution I’ve been able to figure out in Julia. Due to the one or more conversion steps, it probably isn’t the fastest solution, but it doesn’t require modifying packages:

using ImageMagick

sliceindex = 2 #careful to note ImageMagick's starting index is 0 
cimgslice = ImageMagick.load(string("imagefile.tif",sliceindex)) #loads slice as a ColorType array
nimgslice = float.(cimgslice) #convert slice to a number array. 

#Note, the values are normalized to  a range of [0 1] with 1 being the maximum value of the image's bit depth. 
imgslice = trunc.(Int,nimgslice.*(2^16-1))#Example of converting to the actual pixel intensity values for a 16 bit gray scale image.
1 Like

cimgslice = ImageMagick.load(string("imagefile.tif",[1]))
works for me rather than
cimgslice = ImageMagick.load(string("imagefile.tif",1))

Julia 1.5 with ImageMagick v1.1.6