Images registration/alignement, export aligned images

Hello,

Firstable congratulation for the development of this platform, it is very user friendly and super efficient!
I was wondering whether it is possible to register or align two images (with different number of channels, both images contains DAPI staining.) and export the result as transformed/aligned tiff images. Alignment works well using the Image Overlay Alignment tool but when I export my images and open them on imageJ they are not aligned anymore. It there a way to do it?

Thanks!

Fabien

Hi @Fabien_B, glad you like QuPath!

It took me a while to answer this, since it depends upon v0.2.0, released yesterday (QuPath v0.2.0 now available!).

There is no official/built-in way to do this, but I have written a script that provides a provisional way to do what you ask… and potentially more.

Lots of documentation, explanations and caveats are included within the script itself – it will need to be adapted for any specific application. But in summary it can take the affine transforms determined using the interactive command, and:

  • Apply the transform to a single image, and write the result
  • Apply different transforms to multiple images, concatenate them along the ‘channels’ dimension and write the result as a single image (with more channels)
  • Optionally apply color deconvolution when doing either of the above two things

See also Image alignment: use affine matrix from QuPath in other software for other things you might do, outside of QuPath.

4 Likes

This seems to be the most relevant thread for my (possibly embarrassingly obvious) question. I’ve had some luck trying to use this script to align and combine multiple scans of the same slide into a ‘end user friendly’ package for viewing/analysis. As expected the file size gets out of hand at high resolution, presumably because it is uncompressed. I’m slightly embarrassed that casually poking on the qupath git and the documentation I haven’t been able to find where WriteImage is even defined in the source to tell if I can turn on compression or not. Can I? Maybe better yet where am I supposed to be looking to figure that out myself? Thanks!

@Anthony_Santella if you’re writing an OME-TIFF, you’re better off using OMEPyramidWriter

You start by creating a Builder, and then setting the options you want – which can include compression. Here’s the source:

1 Like

Cool, thanks, I’ll look at this.

I’m making some good progress with this script, thanks for the pointers. One oddity I’ve noticed with the script that a quick search through the forum doesn’t turn up anything on, I’m noticing that the color deconvolution (or I suspect more likely the inversion to create a pseudo florescence image) seems to fail when a stained slide is only partially scanned leaving the area outside the scan ROI black. Then all the values seem to be compressed to the range 1-2. I assume this is the problem since it works fine for very similar slides where the entire field of view was scanned. I could totally see how this might happen but is this an expected/desired behavior? If I’m right this is the problem, I wonder if anyone knows is there any known work around for initializing the background to white either in QuPath (or in the Glencoe conversion pipeline, though I don’t see anything in the help for it)

The inversion happens naturally as part of the color deconvolution calculation (some color deconvolution implementations in other software invert back to make a pseudo-brightfield image, but QuPath doesn’t).

But I don’t understand if you mean the values in the unscanned regions are wrong (which is expected) or the values in the scanned impacted by the fact that unscanned regions exist (which is not expected).

Images where part of the image are unscanned are problematic in lots of ways and for lots of commands – not least because there is no straightforward way for QuPath to know where exactly these regions are. We want to address it, but it won’t be easy; there’s a topic here that’s relevant: Pyramidal CZI images and the color of 'unscanned' regions

2 Likes

Thanks for the quick response, which I’m responding to with a lag. I mean the pixels within the scanned region are not looking as expected. I was just speculating it has to do with ROI honestly because it worked fine on another image that was otherwise substantially the same but its equally possible there’s some subtle mistake on my part.

In fact just going over my work to compare the image where it worked and where it did not I see the obvious issue that the output file format of the concatenated tiff is listed a floating point when it worked, and a uint8 when it didn’t, which precisely matches what I’m seeing happen. But of course I didn’t do anything to set it in either case. In both cases all the input images are 8 bit. I got the same behavior using writeImage and an OMEPyramidWriter (and don’t immediately see a way to control the output in the source). Any thought’s on what is going on? Should it be outputting to a floating point or rescaling the unmixed values to fit in a uint8?

Can you confirm what script precisely you are running?

I can’t think of any obvious reason why an identical script would sometimes export 8-bit and sometimes 32-bit for similar input data. The default output of color deconvolution in QuPath is 32-bit.

1 Like

Sure, it is this one, https://gist.github.com/petebankhead/db3a3c199546cadc49a6c73c2da14d6c linked above) In both cases the goal was to unmix IHC and concatenate with IF of the same slide.

I just realized if I try to replicate -exactly- what I did for the first working image. using the original mrxs file of the stain, setting it as the first image in the list and use writeImage I get a correct result on the problem slide.

I can’t see why those other changes should effect anything, I’m testing the OMEPyramidWriter now to confirm this, but I wonder if its an expected behavior that the first image in the list in this script would set the image type for output and so if the second image is a stain the 32 bit result of unmixing would be miscast to an 8 bit output, while if its the first image everything winds up 32 bit?

If so, this (and random ordering of the images as I entered them in the script) would explain all the confusing behavior I’ve been trying to puzzle through.? Thanks.

1 Like

I can confirm that I have seen issues combining brightfield and IF images when the BF image is not first. I did not really put that together when reading this initially.

1 Like

Yes, that sounds expected. I hadn’t realised you were combining images of different types. The first image determines the metadata that will be used (pixel type, pixel size, dimensions…).

1 Like

Thanks both! That makes sense, hopefully I can now pin down the final behavior I really want with that fact in hand.

1 Like

A follow up on this, again something I suspect is my user error (unmixing is now working fine BTW). I was trying to switch to an OMEPyramidWriter with this very simple snippet (my eventual goal is to generate properly compressed 1:1 pyramidal tiffs, but I was having a little difficulty parsing the source code and so was trying to get it to work at all to start)
Strangely, (to me) when I run the combination script (unchanged except for substituting this for the saving line) saving an 8x downsampled result everything works fine and I can open the resulting tiff in qupath. When I save at 1:1 (after a long time) saving seems to complete fine but the resulting tiff gives an exception when opening it after adding it to a qupath project with this trace. I’m almost certain I’ve just left out some critical detail, and tried tinkering with compression and bigtiff options, but given it only breaks on the 1:1 saves it’s kind of slow going to debug and I wonder if the fix is obvious to someone who really understand the logic of configuration setup. Thanks!

snippet:
new OMEPyramidWriter.Builder(server)
.parallelize()
.build()
.writePyramid(pathOutput)

log from loading 1:1 (1:8 gives the initial warning but not the error and opens):

WARN: Unable to obtain full image format info for file:/C:/Users/santella/Desktop/test/R3-S1_IF_IHC_merge_mrxstestomewriter_8downsample_noparam_full.tif (class java.util.NoSuchElementException)
ERROR: java.io.IOException: loci.formats.FormatException: java.util.zip.ZipException: unknown compression method
WARN: Fallback to requesting thumbnail directly…
ERROR: Unable to obtain thumbnail for BioFormatsImageServer: file:/C:/Users/santella/Desktop/test/R3-S1_IF_IHC_merge_mrxstestomewriter_8downsample_noparam_full.tif[–series, 0]: x=0, y=0, w=98931, h=204349, z=0, t=0, downsample=204.35
at qupath.lib.images.servers.bioformats.BioFormatsImageServer.readTile(BioFormatsImageServer.java:875)
at qupath.lib.images.servers.AbstractTileableImageServer.getTile(AbstractTileableImageServer.java:184)
at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:319)
at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:56)
at qupath.lib.gui.images.stores.AbstractImageRegionStore.getThumbnail(AbstractImageRegionStore.java:419)
at qupath.lib.gui.images.stores.DefaultImageRegionStore.getThumbnail(DefaultImageRegionStore.java:57)
at qupath.lib.gui.viewer.QuPathViewer.updateThumbnail(QuPathViewer.java:1277)
at qupath.lib.gui.viewer.QuPathViewer.updateThumbnail(QuPathViewer.java:1262)
at qupath.lib.gui.viewer.QuPathViewer.initializeForServer(QuPathViewer.java:972)
at qupath.lib.gui.viewer.QuPathViewerPlus.initializeForServer(QuPathViewerPlus.java:229)
at qupath.lib.gui.viewer.QuPathViewer.setImageData(QuPathViewer.java:1515)
at qupath.lib.gui.QuPathGUI.openImageEntry(QuPathGUI.java:2696)
at qupath.lib.gui.panes.ProjectBrowser.lambda$new$3(ProjectBrowser.java:190)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3597)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3899)
at javafx.scene.Scene.processMouseEvent(Scene.java:1885)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2618)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:409)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:299)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:447)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:412)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:446)
at com.sun.glass.ui.View.handleMouseEvent(View.java:556)
at com.sun.glass.ui.View.notifyMouse(View.java:942)
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 java.util.zip.ZipException: unknown compression method at ome.codecs.BaseCodec.decompress(BaseCodec.java:199)
at loci.formats.codec.WrappedCodec.decompress(WrappedCodec.java:86)
at loci.formats.codec.ZlibCodec.decompress(ZlibCodec.java:48)
at loci.formats.tiff.TiffCompression.decompress(TiffCompression.java:281)
at loci.formats.tiff.TiffParser.getTile(TiffParser.java:829)
at loci.formats.tiff.TiffParser.getSamples(TiffParser.java:1107)
at loci.formats.tiff.TiffParser.getSamples(TiffParser.java:869)
at loci.formats.in.OMETiffReader.openBytes(OMETiffReader.java:349)
at loci.formats.FormatReader.openBytes(FormatReader.java:878)
at loci.formats.ImageReader.openBytes(ImageReader.java:449)
at loci.formats.ReaderWrapper.openBytes(ReaderWrapper.java:334)
at qupath.lib.images.servers.bioformats.BioFormatsImageServer.readTile(BioFormatsImageServer.java:871)
at qupath.lib.images.servers.AbstractTileableImageServer.getTile(AbstractTileableImageServer.java:184)
at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:319)
at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:56)
at qupath.lib.gui.images.stores.AbstractImageRegionStore.getThumbnail(AbstractImageRegionStore.java:419)
at qupath.lib.gui.images.stores.DefaultImageRegionStore.getThumbnail(DefaultImageRegionStore.java:57)
at qupath.lib.gui.viewer.QuPathViewer.updateThumbnail(QuPathViewer.java:1277)
at qupath.lib.gui.viewer.QuPathViewer.updateThumbnail(QuPathViewer.java:1262)
at qupath.lib.gui.viewer.QuPathViewer.initializeForServer(QuPathViewer.java:972)
at qupath.lib.gui.viewer.QuPathViewerPlus.initializeForServer(QuPathViewerPlus.java:229)
at qupath.lib.gui.viewer.QuPathViewer.setImageData(QuPathViewer.java:1515)
at qupath.lib.gui.QuPathGUI.openImageEntry(QuPathGUI.java:2696)
at qupath.lib.gui.panes.ProjectBrowser.lambda$new$3(ProjectBrowser.java:190)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3597)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3899)
at javafx.scene.Scene.processMouseEvent(Scene.java:1885)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2618)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:409)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:299)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:447)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:412)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:446)
at com.sun.glass.ui.View.handleMouseEvent(View.java:556)
at com.sun.glass.ui.View.notifyMouse(View.java:942)
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)
WARN: Tile request exception
ERROR: Load ImageData: QuPath has encountered a problem, sorry.
If you can replicate it, please report it with ‘Help -> Report bug (web)’.

java.lang.NullPointerException
ERROR: Load ImageData
at qupath.lib.display.ImageDisplay.applyTransforms(ImageDisplay.java:527)
at qupath.lib.display.ImageDisplay.applyTransforms(ImageDisplay.java:511)
at qupath.lib.gui.viewer.QuPathViewer.createThumbnailRGB(QuPathViewer.java:1300)
at qupath.lib.gui.viewer.QuPathViewer.updateThumbnail(QuPathViewer.java:1279)
at qupath.lib.gui.viewer.QuPathViewer.updateThumbnail(QuPathViewer.java:1262)
at qupath.lib.gui.viewer.QuPathViewer.initializeForServer(QuPathViewer.java:972)
at qupath.lib.gui.viewer.QuPathViewerPlus.initializeForServer(QuPathViewerPlus.java:229)
at qupath.lib.gui.viewer.QuPathViewer.setImageData(QuPathViewer.java:1515)
at qupath.lib.gui.QuPathGUI.openImageEntry(QuPathGUI.java:2696)
at qupath.lib.gui.panes.ProjectBrowser.lambda$new$3(ProjectBrowser.java:190)
at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.event.Event.fireEvent(Event.java:198)
at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3597)
at javafx.scene.Scene$MouseHandler.process(Scene.java:3899)
at javafx.scene.Scene.processMouseEvent(Scene.java:1885)
at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2618)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:409)
at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:299)
at java.base/java.security.AccessController.doPrivileged(Unknown Source)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:447)
at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:412)
at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:446)
at com.sun.glass.ui.View.handleMouseEvent(View.java:556)
at com.sun.glass.ui.View.notifyMouse(View.java:942)
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)
INFO: Loading script file D:\core\alignment_slide\qupath_concatenate_aligned_script_writecompressed.groovy
ERROR: Error applying syntax highlighting: StackOverflowError

I don’t know what exactly you mean by this, or how to interpret the snippet (I suspect the important stuff isn’t there?).

Sorry but I don’t understand the issue – if you can make it as simple as possible to replicate the problem it will be easier to investigate.

1 Like

Sorry if I didn’t specify a critical detail, I’m not quite sure what the critical details are being inexperienced with these big images. I have two large (20x) mrxs scans 2,3 gb compressed which I’m using this script to try to align and merge into one image ideally at full resolution. Using the downsampling parameter in the script to create a test downsampled image everything works fine, If I don’t downsample the file (uncompressed and 60 odd gig in size) won’t open. I am fairly sure I’m just missing some piece of configuration to create a usable output tiff, I tinkered with various compression and bigTiff configuration options, but I admit to not having kept rigorous notes and nothing I tried worked. If this is a clearer way to phrase the question what lines should be in a OMEPyramidWriter.Builder call if your goal is to save a really big high channel count image out at full resolution and produce a pyramidal tiff you can then reopen in QuPath. Sorry if that is still not the right information I’ve spent a lot of time doing image processing but am a newbie to both QuPath and large images.

Maybe I should add that (now) that the first image is an unmixed stain the output is floating point format.

You may want to try to look into some of Pete’s responses in this thread:


It looks like, from your snippit, you are not using .bigtif(), so that may be at least one thing to try.

1 Like

Yes, replacing the snippet with something like this might do it

// Ensure the server is pyramidal
List<Double> downsamples = [outputDownsample]
double d = outputDownsample * 4
while (Math.min(server.getWidth()/d, server.getHeight()/d) >= 512) {
    downsamples << d
    d *= 4
}
server = ImageServers.pyramidalize(server, downsamples as double[])

// Write the pyramid
new OMEPyramidWriter.Builder(server)
        .parallelize()
        .downsamples(downsamples as double[])
        .bigTiff()
        .channelsInterleaved()
        .build()
        .writePyramid(path)

The main thing is that you need to specify the pyramid levels. You can avoid calculating them and just use something like .downsamples(1, 4, 16, 64) if you want a quick test.

1 Like

Thanks all, this was indeed my mistake, I was misunderstanding the log and thinking it was writing multiple resolutions when it wasn’t. I am curious about one thing, cycling through the compression flags I was finding results seemed to all be the same except for LZW which was bigger. Does anyone know is compression just unsupported for floating point images? The behavior seems to be a bit different if I comment out the unmixing and hence have all 8 bit channels but haven’t quite mapped it out.

1 Like