Ome.tif (multi-channel, tiled, pyramid) from individual large tiff raw files

I need help writing an ome.tif such as in LuCa-7color_Scan1.ome.tiff QuPath can open these ome.tif files.

I have 7 individual tiff files (size each: 35134x31358 pixel, bit-depth: 16-bit) how do I make an ome.tif (35134x31358x1x7, 16-bit or 8-bit ), so I can use these data for analysis in QuPath.

2 Likes

Hi @Ajay_Zalavadia,

Are the pyramidal levels already pre-calculated and stored according to the OME-TIFF sub-resolutions specification?

If OME-TIFF pyramidal levels do not exist, they will need to be recalculated so that applications supporting sub-resolutions like QuPath can consume them. Using Bio-Formats, there are two ways to do that:

  • either using the API directly e.g as in the GeneratePyramidResolutions.java example

  • or using Bio-Formats command line tools to perform the conversion. You should be able to use a pattern file to concatenate the channels if the files are named accordingly and generate pyramidal levels using the -pyramidal-resolutions option of bfconvert. For instance

    echo "image_C<0-6>.tiff" > image.pattern
    bfconvert -pyramid-resolutions 5 -pyramid-scale 2 -noflat image.pattern image.ome.tiff
    

If the pyramidal levels already exist as SubIFDs in the TIFF files, it might be possible to tie the different channels together without rewriting any bytes using a companion OME-XML file.

1 Like

@s.besson
Sébastien, thank you for these instructions. I will try the bfconvert solution and report back.

These files are just raw tiff data without any tiling or pyramids.

I ran the bfconvert command on my 7GB file set (7 images 1GB each, 35134x31358 pixel, 8-bit b-w).

bfconvert is only using 4% CPU 100mb RAM and few mbps disk speed, its been going on for a while now on the command prompt.

Is this normal?

I have not tried bfconvert, but after building pyramidal mrxs images, I found that it was best to parallelize the process as much as possible with different images since everything runs on a single thread.

Also use whichever computer you have available with the fastest single thread speed. Often times image processing computers are really bad for this since they have slow single thread speed and a large number of cores.

I am getting Java memory error (machine with 64GB RAM), any suggestions !

TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch06.tif
Reading IFDs
Populating metadata
Checking comment style
Populating OME metadata
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch06.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch06.tif
Reading IFDs
Populating metadata
Checking comment style
Populating OME metadata
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch06.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
        Converted 6/6 planes (100%)
TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\scaled_ch01.tif
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at loci.common.DataTools.allocate(DataTools.java:1260)
        at loci.formats.FileStitcher.openBytes(FileStitcher.java:466)
        at loci.formats.WrappedReader.openBytes(WrappedReader.java:233)
        at loci.formats.ImageReader.openBytes(ImageReader.java:445)
        at loci.formats.tools.ImageConverter.getTile(ImageConverter.java:1038)
        at loci.formats.tools.ImageConverter.convertPlane(ImageConverter.java:767)
        at loci.formats.tools.ImageConverter.testConvert(ImageConverter.java:691)
        at loci.formats.tools.ImageConverter.main(ImageConverter.java:1051)

Have you tried one of the newer versions of QuPath with the Export as ome-tiff option?


Not sure if it became available in m4 or not. And this won’t really help if you are unable to open the image in QuPath in the first place. And it might run into the same problems anyway! I don’t really know.

When in doubt, try 200+GB of RAM.

Yes, export to OME-TIFF should be in QuPath v0.2.0-m4. You can customize the options in the preferences (enter ‘ome’ in the search bar under Edit → Preferences…), e.g. to change the compression type, tile size and whether or not tiles are exported in parallel.

Parallelization can help a bit, although ultimately the tiles need to be written sequentially. Therefore it really helps to interleave requests for pixels from the file with writing to disk so that writing is the bottleneck.

In m4 you can also increase the available RAM, and also the proportion of memory given to tile caching - which will be especially important if you need to dynamically calculate the resolutions (as opposed to them being stored in the original file). If the tiles from the highest resolution are still present when writing the lower resolution then they can be used.

I still find writing images slower than I’d like, and I don’t know if it will handle your case well. But it’s probably worth a try to see how it compares with bfconvert.

For more information, see https://petebankhead.github.io/qupath/2019/08/20/fourth-milestone.html
Note also this script (described on the blog) that shows how to write an OME-TIFF in QuPath via a Groovy script, which may give more useful options (e.g. if channels should be interleaved or not).

1 Like

Stealing this to stick at the end of the guide!

It may change a bit since just today I was updating this code to be able to write multiple series in an OME-TIFF…

Luckily I can update my posts whenever I… oh, wait.

I think I got it to work using combination of both, merge channels with bfconvert first and then open the merged file in QuPath and export as ome.tiff (pyramid).

When I used the script “QuPath-Merge unmixed files to pyramid” to create ome.tiff from individual TIFF files in QuPath I ran in to some error (I will try to reproduce it and take a screenshot).

I am glad that now we can export ome.tiff with pyramid from within QuPath, I will continue to do more testing.

Thank you @Research_Associate for providing all the help in these forum posts, thank you @petebankhead for all your work in building this functionality in QuPath. Lets’ get more citations on your way.

Edit:
Here is the error when using the script

INFO: Refreshing extensions in C:\Users\zalavaa\QuPath\extensions
INFO: Added extension: C:\Users\zalavaa\QuPath\extensions\qupath-matlab-extension.jar
INFO: Bio-Formats version 6.2.0
INFO: Loaded extension Bio-Formats server options (Bio-Formats 6.2.0) (83 ms)
INFO: Loaded extension Experimental commands (21 ms)
INFO: Loaded extension ImageJ extension (121 ms)
INFO: Loaded extension JPen extension (37 ms)
INFO: Loaded extension OpenCV extensions (9 ms)
INFO: /MATLAB_Detection_k_means.groovy
INFO: /MATLAB_Hello.groovy
INFO: /MATLAB_Superpixels.groovy
INFO: /QuPathMATLAB.groovy
INFO: Loaded extension QuPath MATLAB extension (33 ms)
INFO: Loaded extension Rich script editor extension (1364 ms)
INFO: OpenSlide version 3.4.1
INFO: Selected style: null
INFO: Performing update check...
INFO: Starting QuPath with parameters: []
INFO: Loading script file C:\QuPath_Scripts\ome-tiff-export.groovy
INFO: Parsing regions from 7 files...
INFO: WARN: Could not parse region for C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch06.tif
INFO: WARN: Could not parse region for C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch05.tif
INFO: WARN: Could not parse region for C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch00.tif
INFO: WARN: Could not parse region for C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch04.tif
INFO: WARN: Could not parse region for C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch01.tif
INFO: WARN: Could not parse region for C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch03.tif
INFO: WARN: Could not parse region for C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch02.tif
INFO: Building server...
ERROR: Error at line 88: null

ERROR: Script error
    at qupath.lib.images.servers.ImageServerMetadata$Builder.<init>(ImageServerMetadata.java:145)
    at qupath.lib.images.servers.SparseImageServer.<init>(SparseImageServer.java:126)
    at qupath.lib.images.servers.SparseImageServer$Builder.build(SparseImageServer.java:307)
    at qupath.lib.images.servers.SparseImageServer$Builder$build.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:119)
    at Script1.run(Script1.groovy:89)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:317)
    at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:155)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:766)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:696)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:676)
    at qupath.lib.gui.scripting.DefaultScriptEditor$2.run(DefaultScriptEditor.java:1033)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.base/java.lang.Thread.run(Unknown Source)
2 Likes

Good it works! Not sure exactly what script you used; only the end part of the one in the blog might be useful - the rest attempts to merge images from different spatial locations to act as a single file, i.e. different fields of view.

This one attempts to merge images of the same field of view but different channels: QuPath multiple channel in separate files - how to merge them?

It should be possible to have several ‘ImageServers wrapped in ImageServers’ that perform various transforms (e.g. merging, concatenating, translating, cropping) images without a need to write a new file. This part of QuPath is a work in progress but it should be possible to try it out already.

1 Like

Is this error a result of 16-bit TIFF image ?

INFO: Checking comment style
INFO: Populating OME metadata
INFO: TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch05.tif
INFO: Reading IFDs
INFO: Populating metadata
INFO: Checking comment style
INFO: Populating OME metadata
INFO: TiffDelegateReader initializing C:\Users\zalavaa\Desktop\Tonsil_merged\raw\Tonsil_TileScan_1_Merged_ch06.tif
INFO: Reading IFDs
INFO: Populating metadata
INFO: Checking comment style
INFO: Populating OME metadata
ERROR: Error opening image 0 for BioFormatsImageServer: file:/C:/Users/zalavaa/Desktop/Tonsil_merged/raw/Tonsil_TileScan_1_Merged_ch00.tif[--series, 0]: x=0, y=0, w=35134, h=31358, z=0, t=0, downsample=1
    at loci.formats.FormatReader.openBytes(FormatReader.java:872)
    at loci.formats.ImageReader.openBytes(ImageReader.java:445)
    at loci.formats.ReaderWrapper.openBytes(ReaderWrapper.java:334)
    at qupath.lib.images.servers.bioformats.BioFormatsImageServer.readTile(BioFormatsImageServer.java:793)
    at qupath.lib.images.servers.AbstractTileableImageServer.getTile(AbstractTileableImageServer.java:157)
    at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:263)
    at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:32)
    at qupath.lib.images.servers.ConcatChannelsImageServer.readBufferedImage(ConcatChannelsImageServer.java:94)
    at qupath.lib.images.servers.ConcatChannelsImageServer.readBufferedImage(ConcatChannelsImageServer.java:25)
    at qupath.lib.images.servers.AbstractImageServer.getDefaultThumbnail(AbstractImageServer.java:313)
    at qupath.lib.gui.commands.ProjectImportImagesCommand.getThumbnailRGB(ProjectImportImagesCommand.java:570)
    at qupath.lib.gui.commands.ProjectImportImagesCommand.initializeEntry(ProjectImportImagesCommand.java:507)
    at qupath.lib.gui.commands.ProjectImportImagesCommand.addSingleImageToProject(ProjectImportImagesCommand.java:487)
    at qupath.lib.gui.commands.ProjectImportImagesCommand$addSingleImageToProject.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:143)
    at Script6$_run_closure2.doCall(Script6.groovy:34)
    at Script6$_run_closure2.doCall(Script6.groovy)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.base/java.lang.reflect.Method.invoke(Unknown Source)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:101)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
    at groovy.lang.Closure.call(Closure.java:405)
    at groovy.lang.Closure.call(Closure.java:399)
    at groovy.lang.Closure.run(Closure.java:486)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
    at java.base/java.security.AccessController.doPrivileged(Unknown Source)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
    at java.base/java.lang.Thread.run(Unknown Source)
  Caused by Array size too large: 35134 x 31358 x 1 x 2        at loci.common.DataTools.safeMultiply32(DataTools.java:1286)
        at loci.common.DataTools.allocate(DataTools.java:1259)
        at loci.formats.FormatReader.openBytes(FormatReader.java:869)
        at loci.formats.ImageReader.openBytes(ImageReader.java:445)
        at loci.formats.ReaderWrapper.openBytes(ReaderWrapper.java:334)
        at qupath.lib.images.servers.bioformats.BioFormatsImageServer.readTile(BioFormatsImageServer.java:793)
        at qupath.lib.images.servers.AbstractTileableImageServer.getTile(AbstractTileableImageServer.java:157)
        at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:263)
        at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:32)
        at qupath.lib.images.servers.ConcatChannelsImageServer.readBufferedImage(ConcatChannelsImageServer.java:94)
        at qupath.lib.images.servers.ConcatChannelsImageServer.readBufferedImage(ConcatChannelsImageServer.java:25)
        at qupath.lib.images.servers.AbstractImageServer.getDefaultThumbnail(AbstractImageServer.java:313)
        at qupath.lib.gui.commands.ProjectImportImagesCommand.getThumbnailRGB(ProjectImportImagesCommand.java:570)
        at qupath.lib.gui.commands.ProjectImportImagesCommand.initializeEntry(ProjectImportImagesCommand.java:507)
        at qupath.lib.gui.commands.ProjectImportImagesCommand.addSingleImageToProject(ProjectImportImagesCommand.java:487)
        at qupath.lib.gui.commands.ProjectImportImagesCommand$addSingleImageToProject.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:143)
        at Script6$_run_closure2.doCall(Script6.groovy:34)
        at Script6$_run_closure2.doCall(Script6.groovy)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.base/java.lang.reflect.Method.invoke(Unknown Source)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:101)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
        at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
        at groovy.lang.Closure.call(Closure.java:405)
        at groovy.lang.Closure.call(Closure.java:399)
        at groovy.lang.Closure.run(Closure.java:486)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
        at java.base/java.security.AccessController.doPrivileged(Unknown Source)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
        at java.base/java.lang.Thread.run(Unknown Source)

This time I used script from this post:

Somewhat… the main thing is that the plane is too big to open in one go. If it was 8-bit it might be more successful… albeit horribly slowly.

QuPath needs to open it all in one go if you open through the user interface, since it requires generating a thumbnail for the image - which is where this is going wrong. If you access the same image through scripting, there will be no need for the thumbnail and it may work better (although I’m not sure…).

Hi Pete, @petebankhead

I am wondering if your “parallelExporter” in the " [OMEPyramidWriter]" class works, since it is false by default.

Is it possible to call your " [OMEPyramidWriter]" class from python (in the python-bioformats way) so that we have another way to accelerate the bfconvert process.

Many thanks!
Best regards,
John

Hi @John_Xu, it works by looping through each image plane (and pyramid level), and then attempting to write the tiles for that plane in parallel. This appears to work - with the caveat that the (0,0) tile must be written first.

Ultimately, synchronization within Bio-Formats means that the actual writing happens sequentially - but at least this alone becomes the bottleneck, rather than requesting tiles and all aspects of writing being sequential.

It is off by default because I was a bit fuzzy on whether it would work consistently or if there was a good argument against this approach… but it does seem to be working reliably on every image I’ve tested.

I have a suspicion that it should be possible to do better, by reducing when synchronization occurs and how Bio-Formats writes the tiles (could they be compressed in-memory first?) but I’m far from an expert in TIFF and there may be very good reasons for why Bio-Formats does things as it does.

I would really like to improve TIFF-writing performance more if possible - very open to ideas for how to do it.

Hi @petebankhead, thanks for the detailed explanation. I am not familiar with Tiff-writing at all. I am trying to understand how they the pyramid ome tiff works, but still struggling to understand how they do the subIFD.

synchronization within Bio-Formats means that the actual writing happens sequentially
Is this has something to do with the subIFD?

ps: I am wondering is there any (qupath.jar file that can be use for python javabridge?

Many thanks!

I’m afraid not, my focus has been on Groovy scripting within QuPath. I looked into directly connecting with Python before, but I didn’t find it useful enough to battle on with the complexity. It has been some years since I looked at javabridge but I imagine there might be quite a lot of classpath pain involved in calling QuPath through Python currently.

For the Bio-Formats pyramidal TIFF specification, these might be useful:

I also found myself needing to get deeper into the general TIFF specification than I’d ever wanted to go… although it was interesting in a I-hope-I-don’t-need-to-remember-this kind of way.

Really really appreciated for the help and information.
I think the best solution for me now is to use qupath for conversion install of the bfconvert tools.
Thanks again!