Writing QuPath readable large images (with bio-formats?)

I’ve asked related questions about this before but as I’ve continued to poke at it in spare time have hit a bit of a wall.

prior conversations for ref:

I have large (~30x50k x 8 channel) image data, (the result of aligning whole scanned slides in Matlab). I have been trying, without success, to get these saved in a format readable by QuPath.

Bioformats bfsave for matlab and attempting to use the bioformats writer class directly as suggested here: MATLAB example of writing tiled pyramidal OME-TIFF using the Bio-Formats API · GitHub give a max size related error. (As does using the matlab tiff class directly) For calling the bioformats class directly from matlab the specific error is : Error using loci.formats.out.PyramidOMETiffWriter/saveBytes
MATLAB array exceeds an internal Java limit.

I can save the image to a set of tiled standard tiffs one per channel using blocproc or bigimage in matlab.With bigimage I can save either a single or a pyramidal version according to whatever the mysterious matlab pyramid convention is. Though this is not my goal, but for reference, none of these single channel images will consistently open in QuPath. Usually at least, they all fail without any error message on an attempt to add them to the project.

I’ve also tried pyramidalizing and concatenating these individual channels with bfconvert and a pattern file on the command line. E.g.

echo “bigimagesavedtiff_channel_nopyramid_<1-8>.tiff” > image.pattern
bfconvert -tilex 512 -tiley 512 -pyramid-resolutions 4 -pyramid-scale 2 -noflat images.pattern image.ome.tiff

The tiffs generated by matlab blocproc converted (after a very long wait) but were not recognized by QuPath as a pyramid, nor as multichannel. QuPath loaded the lower resolution levels as separate single channel images and failed to load the larger levels into the project (giving an error message that x images were not added).

To be fair I have no good reason to think the concatenated images would be recognized as channels beyond I read it on the internet, but I would think the pyramid would work.

Converting (with the same command) the images generated from matlab by bigimage gives a different failure. These load as an apparent single image in QuPath, however I’m only actually seeing one of the 8 channels at (I suspect) some middle resolution (definitely the high resolution is not there).

If I pyramidalize each channel separately with bfconvert and don’t try to concatenate them they will load properly in QuPath individually. So I could probably then concatenate them with a QuPath script, but its such an awkward workflow I’ve been reluctant to accept it, this is something I’ll want to set up for other people to do regularly. I’d love of course to have it all in matlab.

I know of course this is probably some subtle meta data or formatting thing (in fact none of these images have any meta data at all beyond what bigimage or bfconvert puts it in). However, I’ve now tried just about anything I can think of/google and hit different dead ends each time. I can’t even seem to quite find the detailed spec of what makes a valid bioformats pyramidal tiff to try to comply with it.

This is slightly exotic, but not -that- exotic. Has anyone actually built (big) images from scratch for QuPath with or without bioformats, especially from matlab, that could weigh in on where I’m going wrong?

1 Like

Any chance you could crop and share a tile of the 8channel matlab output? That might help others look at the metadata (or lack thereof).

@smcardle may have some MATLAB experience, but not sure if it pertains to anything like this.

2 Likes

Maybe this? OME-TIFF specification — OME Data Model and File Formats 6.0.1 documentation

And I realise you might not want to incorporate sth else in your workflow, but libvips might be worth a try: libvips

I find that, if it solves the problem you’re working on, libvips can be is extremely fast. I remember seeing v8.10.0 improved Bio-Formats read/write compatibility with pyramids: Release v8.10.0 · libvips/libvips · GitHub

2 Likes

Can you post this code?

2 Likes

Hi, Sara
There’s not too much to it, on the matlab side I just create then write a bigimage with one channel of my giant matlab array. e.g.
testimage=bigimage(fullsizewarped(:,:,1));
write(testimage,outputlocation);

then on the command line just use the same bfconvert command to make a pyramid from it, and it opens perfectly in QuPath.

./bfconvert.bat -tilex 512 -tiley 512 -bigtiff -pyramid-resolutions 4 -pyramid-scale 2 -noflat bigimagesavedtiff_nopyramidnoblock_channel_1.tiff image_channel1only.ome.tiff

I think bigimage appeared in 2019b which is the one I’m using, there’s some other big image handling infrastructure in the most recent matlab release but I haven’t looked into it.

If bigimage supported high channel counts I’d be done I think, but it only does single or rgb so need to get the channels into one properly formatted file some other way…

2 Likes

Regarding the generation of pyramids onto an OME TIFF, I made a quick and dirty Python script that would do it, since I was having some trouble generating them during import into QuPath. Maybe give this a shot and let me know how it turns out?

# -*- coding: utf-8 -*-
"""
Created on Wed Nov 18 13:59:50 2020

@author: Mark Zaidi

Basically slaps on a 2x, 4x, and 8x downsampled image on any large, single or multi-channel tif,
and writes it out as a pyramided .ome.tiff which QuPath can read. JPEG compression is lossy, but
reduces file size by up to 90%. Deflate is lossless, but only reduced by about 25%. Going with deflate.

I tried carrying over the resolution metadata from the original tif, however QuPath doesn't seem to recognize it.
So, just caluclate it manually from the original resolution metadata in irfanview stored as dots-per-inch (dpi)
and convert to micrometers per dot (um/pixel).
"""
#%% Import packages
import tifffile
import os
#%% declare variables
filelist=[]
input_folder=r'C:\Paste\Your\Path\Here'
#%% Get list of ome.tif to read and rewrite
for file in os.listdir(input_folder):
    if file.endswith(".tif"):
        print(os.path.join(input_folder, file))
        filelist.append(os.path.join(input_folder, file))
#%% perform read and rewrite        
for curr_path in filelist:
    # with tifffile.TiffFile(curr_path) as tif:
    #     resx = tif.pages[0].tags['XResolution']
    #     resy = tif.pages[0].tags['YResolution']
    #     unit=tif.pages[0].tags['ResolutionUnit']
    img=tifffile.imread(curr_path)
    
    with tifffile.TiffWriter(curr_path.replace('.tif','.ome.tif'), bigtiff=True) as tif:
        options = dict(tile=(256, 256), compression='deflate')
        tif.write(img, subifds=3, **options)
     # save pyramid levels to the two subifds
     # in production use resampling to generate sub-resolutions
        tif.write(img[::2, ::2], subfiletype=1, **options)
        tif.write(img[::4, ::4], subfiletype=1, **options)
        tif.write(img[::8, ::8], subfiletype=1, **options)
    # with tifffile.TiffFile(curr_path.replace('.tif','2ndtry.ome.tif'), mode='r+b') as tif:
    #     _ = tif.pages[0].tags['XResolution'].overwrite(tif,resx.value)
    #     _ = tif.pages[0].tags['YResolution'].overwrite(tif,resy.value)
    #     _ = tif.pages[0].tags['ResolutionUnit'].overwrite(tif,unit.value)
2 Likes

I’ll definitely give this a shot if an all Matlab solution doesn’t surface soon, thanks.

You should be able to dynamically concatenate channels in QuPath: QuPath multiple channel in separate files - how to merge them? - #22 by petebankhead

It’s not a very streamlined process (the script would need adapted), and I’d recommend then writing the pyramidal OME-TIFF from QuPath to preserve the result. This may well be slow.

2 Likes

It may be that you simply need to rename the files here so that they end with _C1.tiff, _C2.tiff etc. This way the pattern file should recognise that each file is a channel, otherwise it will be unsure how to group the files (Grouping files using a pattern file — Bio-Formats 6.6.1 documentation).

2 Likes

Have you tried posting this on the Mathworks forum? It’s not quite as awesome as this one, but sometimes mathworks employees will jump into a thread to help.

1 Like

This totally worked, I’m kind of embarrassed that I spent so long looking for other solutions and scratching my chin reading the bfconvert documentation thinking ‘but how could it know they’re channels’ but somehow overlooked the documentation for the pattern file altogether.

3 Likes

When I tidy the matlab script up a little I’ll post it for the curious.

2 Likes

9 posts were split to a new topic: Writing QuPath/Bio-Formats compatible pyramidal image with libvips

@Anthony_Santella found this thread thanks to @petebankhead .

I looked at the OME-TIFF spec and I have attached a MATLAB script which shows how to write a channel’s into IFDs, and the corresponding sub-levels as SubIFDs. This should create a valid OME-TIFF, am yet to test it out though.

If you have the latest MATLAB, blockedImage replaces bigimage and extends the idea to ND, so multi-channel support is available out of the box. The default TIFF adapters still only supports 1 or 3 channels, but there is a HDF5 based adapter which can handle ND pyramid data. There is also a way to write custom adapters to extend support to arbitrary formats. Here is one example showing how to use OpenSlide as the underlying adapter (for MIRAX files, but this ought to be extensible to any supported OpenSlide format).

OME-TIFF is a bit of a challenge to write an adapter for, since it splits the channels and writes sublevels of that channel first (blockedImage was designed to treat each level as ‘one unit’). I might try adopting the attached code into an adapter, which will make your usecase a lot easier (~5 lines of code).

BTW - Is the data currently all ‘in-memory’ as an array?

exampleOMETIFF.m (3.3 KB)

1 Like

A post was merged into an existing topic: Writing QuPath/Bio-Formats compatible pyramidal image with libvips