Error of IJTools.convertToImagePlus when request's downsample is close to 1

I have a large slide with dimension below(QuPath 0.2.0-m2):

//INFO: Width:50729
//INFO: Height:51406
//INFO: ZSlices:1
//INFO: nChannels:3
//INFO: nTimepoints:1

I want to convert the slide to imageplus object. I use the following code, and found that:
(1) if the downsample is high enough,such as 5,7,10, the imageplus object can be created and saved as a tif file.

(2) if the downsample is lower such as 2,3, OutOfMemory error take place in the following line.This is reasonable because the memory of my laptop is not high enough:
imp = IJTools.convertToImagePlus(server, request).getImage()

(3) However, if the downsample is close to 1, such as 0.999,1.003,1, unexpected error take place in the same line:
It’s strange because all the other argument (imgpath, [downsample], xi, yi, wi, hi, z, t) is the same except for modifying downsample argument a little bit.

(4) What’s more, with downsmpling of 1, if I request a tiny portion of the slide(a small tile) but not the whole slide, then imageplus can be saved as tif files(a crop from the original highest resolution):
(imgpath, [downsample], xi, yi, 500, 800, z, t)
Using tiny tiles I can get anywhere in the raw image.

However, if the tile is close to the full-size, the same strange error appeared.

`(imgpath, [downsample], xi, yi, wi-1, hi-1, z, t)`
`when adjusting the size,the result also varies:`
`(imgpath, [downsample], xi, yi, wi-1, hi-1, z, t)`: //same strange error
`(imgpath, [downsample], xi, yi, wi-3000, hi-3000, z, t)`//same strange error
`(imgpath, [downsample], xi, yi, wi-50000, hi-50000, z, t)`//export tif success 
`(imgpath, [downsample], xi, yi, wi-30000, hi-30000, z, t)`//groovy hangs and stop run without any message, no image exported

INFO: ...............imp creating.
ERROR: Error at line 555: -1687192322

ERROR: Script error
    at java.desktop/java.awt.image.DataBufferInt.<init>(DataBufferInt.java:75)
    at java.desktop/java.awt.image.Raster.createPackedRaster(Raster.java:467)
    at java.desktop/java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1032)
    at java.desktop/java.awt.image.BufferedImage.<init>(BufferedImage.java:324)
    at qupath.lib.images.servers.AbstractTileableImageServer.createDefaultRGBImage(AbstractTileableImageServer.java:79)
    at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:123)
    at qupath.lib.images.servers.AbstractTileableImageServer.readBufferedImage(AbstractTileableImageServer.java:19)
    at qupath.imagej.helpers.IJTools.convertToImagePlus(IJTools.java:536)
    at qupath.imagej.helpers.IJTools.convertToImagePlus(IJTools.java:573)
    at qupath.imagej.helpers.IJTools$convertToImagePlus.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:135)
    at Script5.run(Script5.groovy:556)
    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:767)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:697)
    at qupath.lib.gui.scripting.DefaultScriptEditor.executeScript(DefaultScriptEditor.java:677)
    at qupath.lib.gui.scripting.DefaultScriptEditor$2.run(DefaultScriptEditor.java:1034)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:834)

Below is the code and output:

println("...............region request creating...imgpath["+imgpath+"], downsample["+downsample+"], xi["+xi+"], yi["+yi+"], wi["+wi+"], hi["+hi+"], z["+z+"], t["+t+"]")
//...............region request creating...imgpath[D:/QMDownload/4/CDF34.mrxs], downsample[1.0], xi[0], yi[0], wi[50729], hi[51406], z[0], t[0]

RegionRequest request = RegionRequest.createInstance(imgpath, downsample, xi, yi, wi, hi, z, t)
println("...............region request created. Now check request info:")
int request_width = (int)Math.round(request.getWidth() / request.getDownsample());
int request_height = (int)Math.round(request.getHeight() / request.getDownsample());
println("..................request_width = " + request_width)
println("..................request_height = " + request_height)
//INFO: ..................request_width = 50729
//INFO: ..................request_height = 51406

imp = IJTools.convertToImagePlus(server, request).getImage()


...then save imp as tif

I wasn’t able to replicate this, but I only had time to look extremely briefly. It would really help if you could provide a script that is fully self-contained, with imports & variables defined - and ideally with example image (perhaps one publicly available).

Anyhow, this is the script that I ran without problems, with a region drawn on OS-2.ndpi.

import qupath.lib.regions.*
import qupath.imagej.helpers.IJTools
// Note! As part of refactoring to improve consistency, in m3 this import should be
// import qupath.imagej.tools.IJTools


def server = getCurrentServer()
imgpath = getCurrentServer().getPath()
downsample = 0.9999
def roi = getSelectedROI()

RegionRequest request = RegionRequest.createInstance(imgpath, downsample, roi)
println("...............region request created. Now check request info:")
int request_width = (int)Math.round(request.getWidth() / request.getDownsample());
int request_height = (int)Math.round(request.getHeight() / request.getDownsample());
println("..................request_width = " + request_width)
println("..................request_height = " + request_height)
//INFO: ..................request_width = 50729
//INFO: ..................request_height = 51406

imp = IJTools.convertToImagePlus(server, request).getImage()

Also, I am curious as to whether this is only strange or if it is causing bigger problems? If the latter, can you explain in more detail what you want to achieve?

Also, note that Java arrays are indexed with 32-bit signed integers, so the maximum number of values in an array is (around) 2^31… somewhere around 46340 x 46340 pixels.

This is then the upper limit for the number of pixels that can be stored in a single BufferedImage*. The limit holds even if your computer would have enough memory to handle it.

I can get a similar error to you if I exceed this size. I can even skip the QuPath step:

import java.awt.image.BufferedImage

size = 50000
def img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB)
print img

This gives me

ERROR: Error at line 4: -1794967296

Curiously, when I tried this first I could the script with size = 42000 and it works (with a bit of a delay), but with size = 43000 the script terminated but without printing anything.

My guess is that this is related to the amount of memory available. Running QuPath with a lot of memory, I find that it fails exactly as predicted… that is, I can run with size = 46340 but get an error with size = 46341.

So it seems to me the behaviour you’re seeing is because BufferedImages images of the type & size you’re requesting just aren’t supported by Java.

*-Can be more subtle depending on how it’s stored, but I think the limit is appropriate in this case.

The reason was that the .mrxs slide has patches of background(value 128 ) which I want to fill with zero. To fill it, I need to get the entire raw slide as a single imageplus object, then seach for patches and forge polygon ROI objects and fill the ROIs with zero.
From your answer it seems that the raw image was too large(>int32 limit) so that java imageplus object cannot hold it and cause error, unless downsampled. The memory is also important.
So I downsampled the image.
Although the border of background pathes in the downsampled image was not as clear as raw image, I can enlarge the polygon:

   aROI = ij.gui.PolygonRoi(a(:,1)',a(:,2)',size(a,1),ij.gui.Roi.POLYGON);
   aROI_enlarge = ij.plugin.RoiEnlarger.enlarge(aROI,10);

The enlarged polygon will safely cover the downsample-induced blurred boundarybetween background patch and non-background image content.
So the problem is solved now.

QuPath exists mostly because whole slide images are too large to work with directly. You may already have seen https://github.com/qupath/qupath/wiki/Working-with-ImageJ#send-roi-to-qupath

There are methods within QuPath (interactively or in the code) to translate between QuPath annotations and ImageJ ROIs - translation and rescaling as appropriate. These should be more generic than depending on RoiEnlarger.

If QuPath can handle ROI,is there a way to fill an ROI that defined by (x,y) coordiantes on boundary.
For example, in a slide file of size(52345x52756), there are several ROIs. Each ROI is a ploygon that formed by only horizontal and vertical lines(not exactly rectangle). Is there a way to fill these roi with zero, and then downsample and convert the filled slice to imageplus? What I have been doing is the opposite way: downsampling to imageplus first, then fill roi in imageplus.

You can’t edit the images unless you want to export them to ImageJ first, but it might be easier to export a mask of a region, or several regions.

You can create imageplus objects though, as used in a few of the available online scripts. For example, when calculating colocalization, an ImagePlus was used to define the area of interest.

**Sorry, actually I think I misunderstood, after reading the previous posts again. If you want to do that, you should probably define your region request with the bounding box of the ROI.

1 Like

@mendel I don’t understand why you’d want to do that. In a general way it won’t be possible because it will require creating an in-memory image (potentially) the size of the whole slide - and that won’t work because of Java’s array size limit as mentioned above. And as @Research_Associate says, you can’t change the original image in QuPath.

1 Like

Yep, though I suppose you could tile it, treat each of those tiles as a full resolution imageplus object… mask them, downsample them, and then rebuild the downsampled set of tiles into a full image… but that sounds like a terrible amount of work to me. =/

1 Like

This is a good idea and deserves a try

If the imageplus object impose a upper size limit on the image that can be created by java, then is there other ways to directly convert the large-size slice image to tif file directly by QuPath, instread of going through imageplus object?Thanks.

You can write an OME-TIFF.