QuPath 0.12 exception with dilate annotations - can´t find the reason

Hello Pete, hi everybody,

i try to develop a script that creates several bands around a tumor area.
The script loops through a dilation function and shall add the a higher number to each new band:
band-1
band-2
etc.
But it skips numbers, or names two rounds of bands with the same number.
I can not find the reason for that.

Do you have an idea why this happens?
This is the script:

import qupath.lib.objects.classes.PathClass
import qupath.lib.objects.classes.PathClassFactory

cycles = 3         //please insert number of outer bands for stroma examination
band_width = 40  //please insert width in micrometers
counter = 1

selectObjects { p -> p.getPathClass() == null && p.isAnnotation() }
outer_band_name = band_width+ "-microns_outer_band_" +counter.toString()
 
def class_outer_band_name = PathClassFactory.getPathClass(outer_band_name)
getSelectedObjects().each {it.setPathClass(class_outer_band_name)}
fireHierarchyUpdate()
selectObjects { p -> p.getPathClass() == getPathClass(outer_band_name.toString()) && p.isAnnotation() }
Thread.sleep(100); 
def PolygoneClass = PathClassFactory.getPathClass("Tumor")
Thread.sleep(100); 
runPlugin('qupath.lib.plugins.objects.DilateAnnotationPlugin', '{"radiusMicrons": '+band_width+' ,  "removeInterior": false,  "constrainToParent": false}'); 
getSelectedObjects().each {it.setPathClass(PolygoneClass)}
Thread.sleep(100); 
//counter = 2
//selectObjects { p -> p.getPathClass() == getPathClass(outer_band_name.toString()) && p.isAnnotation() }


selectObjects{p-> p.isAnnotation() && p.getPathClass() == getPathClass("asdf")}
for (i=2; i <= cycles; i++) {
    print("counter is now: " + counter)
    selectObjects { p -> p.getPathClass() == getPathClass(outer_band_name.toString()) && p.isAnnotation() }
    //import qupath.lib.objects.classes.PathClass
    //import qupath.lib.objects.classes.PathClassFactory
    Thread.sleep(100); 
    //oounter = 1
    old_outer_band_name = outer_band_name
    counter = counter + 1 
    outer_band_name = band_width +"-microns_outer_band_" + counter  
    print("outer band name is now: " + outer_band_name)  
    def next_band_name = PathClassFactory.getPathClass(outer_band_name)
    getSelectedObjects().each {it.setPathClass(next_band_name)}
    fireHierarchyUpdate()
    Thread.sleep(200); 
    selectObjects { p -> p.getPathClass() == getPathClass(outer_band_name.toString()) && p.isAnnotation() }
    Thread.sleep(200); 
    runPlugin('qupath.lib.plugins.objects.DilateAnnotationPlugin', '{"radiusMicrons": '+band_width+',  "removeInterior": false,  "constrainToParent": false}');
    def old_outer_band_name = PathClassFactory.getPathClass(old_outer_band_name)
    getSelectedObjects().each {it.setPathClass(old_outer_band_name)}
    Thread.sleep(2000); 
    selectObjects{p-> p.isAnnotation() && p.getPathClass() == getPathClass("asdf")}
    fireHierarchyUpdate();
    Thread.sleep(2000); 
}
fireHierarchyUpdate()

This is one possible exception:

INFO: Refreshing extensions in C:\Users\d-hau\QuPath\extensions
INFO: Added extension: C:\Users\d-hau\QuPath\extensions\bioformats_package.jar
INFO: Added extension: C:\Users\d-hau\QuPath\extensions\qupath-extension-bioformats.jar
INFO: Selected style: Modena Light
INFO: Performing update check...
INFO: Starting QuPath with parameters: [xxx\project.qpproj]
INFO: Trying to load project xxx\project.qpproj
INFO: Project set to Project: CD8_Projekt_01
ERROR: Checking Big TIFF images currently not supported!!!
WARN: Error opening C:\Users\d-hau\Jobs on SSD\xxxCD8.tif with ImageJ: Could not open C:\Users\d-hau\Jobs on SSD\xxxCD8.tif with ImageJ
INFO: Test reading thumbnail with openslide: passed (BufferedImage@55be871: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 194 height = 200 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0)
INFO: Returning server: OpenSlide for C:\Users\d-hau\Jobs on SSD\xxxCD8.tif
INFO: Estimating H-DAB staining
INFO: Image data set to ImageData: Brightfield (H-DAB), xxxCD8
INFO: Reading data from xxxCD8.qpdata...
INFO: Hierarchy with 731471 object(s) read from C:\Users\d-hau\Jobs on SSDxxxCD8.qpdata in 6.09 seconds
ERROR: Checking Big TIFF images currently not supported!!!
WARN: Error opening D:\Job\Auslagerung SSD\xxxCD8.tif with ImageJ: Could not open D:\Job\Auslagerung SSD\xxxCD8.tif with ImageJ
INFO: Test reading thumbnail with openslide: passed (BufferedImage@578bec6c: type = 1 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0 IntegerInterleavedRaster: width = 200 height = 193 #Bands = 3 xOff = 0 yOff = 0 dataOffset[0] 0)
INFO: Returning server: OpenSlide for D:\Job\Auslagerung SSDxxxCD8.tif
INFO: Estimating H-DAB staining
INFO: Image data set to ImageData: Brightfield (H-DAB), xxxCD8
INFO: Reading data from xxxCD8.qpdata...
INFO: Hierarchy with 0 object(s) read from C:\Users\d-hau\Jobs on SSD\xxxCD8.qpdata in 0.01 seconds
INFO: Adding Polygon to hierarchy
INFO: Loading script file C:\Users\d-hau\Jobs on SSD\xxx\CD8_Projekt_01\scripts\test.groovy
INFO: Starting script at Mon Jan 06 17:49:42 CET 2020
INFO: Adding Polygon (40-microns_outer_band_1) to hierarchy
INFO: Processing complete in 0.07 seconds
INFO: Completed!
INFO: 
qupath.lib.plugins.objects.DilateAnnotationPlugin  {"radiusMicrons": 40 ,  "removeInterior": false,  "constrainToParent": false}
INFO: counter is now: 1
INFO: outer band name is now: 40-microns_outer_band_2
INFO: Adding Polygon (40-microns_outer_band_2) to hierarchy
INFO: Adding Polygon (40-microns_outer_band_2) to hierarchy
ERROR: QuPath exception
    at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.subList(ReadOnlyUnbackedObservableList.java:136)
    at javafx.collections.ListChangeListener$Change.getAddedSubList(ListChangeListener.java:242)
    at com.sun.javafx.scene.control.behavior.ListViewBehavior.lambda$new$177(ListViewBehavior.java:269)
    at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
    at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
    at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
    at com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList.callObservers(ReadOnlyUnbackedObservableList.java:75)
    at javafx.scene.control.MultipleSelectionModelBase.selectIndices(MultipleSelectionModelBase.java:529)
    at qupath.lib.gui.panels.PathAnnotationPanel.selectedPathObjectChanged(PathAnnotationPanel.java:762)
    at qupath.lib.gui.panels.PathAnnotationPanel.lambda$selectedPathObjectChanged$15(PathAnnotationPanel.java:704)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)
INFO: Processing complete in 0.36 seconds
INFO: Completed!
INFO: 
qupath.lib.plugins.objects.DilateAnnotationPlugin  {"radiusMicrons": 40,  "removeInterior": false,  "constrainToParent": false}
INFO: counter is now: 2
INFO: outer band name is now: 40-microns_outer_band_3
INFO: Adding Polygon (40-microns_outer_band_3) to hierarchy
INFO: Adding Polygon (40-microns_outer_band_3) to hierarchy
INFO: Processing complete in 0.99 seconds
INFO: Completed!
INFO: 
qupath.lib.plugins.objects.DilateAnnotationPlugin  {"radiusMicrons": 40,  "removeInterior": false,  "constrainToParent": false}

The exception is from a user interface component. These multithreading issues can be extremely awkward to debug.

I remember you’ve encountered similar issues before and it reminds me of this old discussion: in short, it is not a good idea to rely on selecting/deselecting objects in complex QuPath scripts like this.

To achieve this reliably in v0.1.2 you’d likely need a script that delves deeper into QuPath’s internals, avoiding any reliance on selectObjects, getSelectedObjects, runPlugin and fireHierarchyUpdate commands.

ok. that means it shall work if the viewer is closed?

As mentioned before, I think, using repeated selections might not be the best way to go about this. You might want to look at scripts that adjust the annotations without selection.


That script goes in the opposite direction, mostly, but it should give you a base script example for how to manipulate the objects without selecting them.

I am surprised you are leaving removeInterior as false if you are analyzing stromal bands.

I expect it has a better chance of working with the viewer closed but I didn’t follow it all in detail. At least I wouldn’t expect an identical error.

i gonna try. thank you Pete.

@Research_Associate,

thank you for the link. The script sounds for quite some manual interaction. I rely on total automation.

You wouldn’t want to replicate the script, the manual interaction has to do with preventing the expansion from going beyond the SimpleTissueDetection. The main utility here is that it shows how to manipulate (dilations and erosions) annotations without selecting them.

it indeed seems to run with closed viewer!

In case anyone else comes across this looking for ring expansions, this both runs in the window due to the guiscript, I assume, and provides rings rather than expansions (which include the previous areas). Written to be run on a single object, but each instance of [0] (which makes the assumption that there is only one object, thus at the 0 position in the array) could be expanded to run on groups of objects.

guiscript=true

//Loop starting from one annotation object with no other objects present to contstraing it
//Create bands
import qupath.lib.roi.*
import qupath.lib.objects.*

bands = 5

first = getAnnotationObjects()
first[0].setName("0")
firstROI = first[0].getROI()
firstROIClass = first[0].getPathClass()
//test = []
for (i=1; i<=bands; i++){
    j=i-1
    print j
    currentObjects = getAnnotationObjects().findAll{it.getName() == j.toString()}
    currentObjects[0].setName(i.toString())
    selectObjects{it.getName() == i.toString()}
    //Thread.sleep(2000); 
    runPlugin('qupath.lib.plugins.objects.DilateAnnotationPlugin', '{"radiusMicrons": 100.0,  "removeInterior": false,  "constrainToParent": true}');
    
    fireHierarchyUpdate()
    j = j.toString()
    print(currentObjects)
    currentObjects[0].setName(j.toString())

}

for (i=bands; i>0; i--){
    toRingify = getAnnotationObjects().findAll{it.getName() == i.toString()}
    j=i-1
    ringHole = getAnnotationObjects().findAll{it.getName() == j.toString()}
    
    ring = PathROIToolsAwt.combineROIs(toRingify[0].getROI(), ringHole[0].getROI(), PathROIToolsAwt.CombineOp.SUBTRACT)
    addObjects(new PathAnnotationObject(ring, toRingify[0].getPathClass()))
    ring = getAnnotationObjects().findAll{it.getName() == null}
    ring[0].setName(i.toString())
    removeObjects(toRingify,true)
    fireHierarchyUpdate()
}

https://gist.github.com/Svidro/5829ba53f927e79bb6e370a6a6747cfd#file-rings-around-an-annotation-groovy

Thank you. That will probably help as well!