Interactive image alignment

Ah yes, that makes sense… changing the part you posted to this might solve it (or at least progress to a new error):

for (pathObject in pathObjects) {
   tempObject = transformObject(pathObject, transform)
   if (tempObject != null)
       newObjects << tempObject

Hi Pete

Great, Yeah the above works without crashes and transfers only the tiled annotation, this way I managed to over come this issue also.

/**
 * Script to transfer QuPath objects from one image to another, applying an AffineTransform to any ROIs.
 */

// SET ME! Define transformation matrix
// Get this from 'Interactive image alignment (experimental)
def matrix = [
      0.993, -0.122, 49200.353,
      0.122, 0.993, 2433.906
]

// SET ME! Define image containing the original objects (must be in the current project)
def otherImageName = 'TGU124.czi'

// SET ME! Delete existing objects
def deleteExisting = false

// SET ME! Change this if things end up in the wrong place
def createInverse = false


import qupath.lib.gui.helpers.DisplayHelpers
import qupath.lib.objects.PathCellObject
import qupath.lib.objects.PathDetectionObject
import qupath.lib.objects.PathObject
import qupath.lib.objects.PathObjects
import qupath.lib.objects.PathTileObject
import qupath.lib.roi.PathROIToolsAwt
import qupath.lib.roi.interfaces.ROI

import java.awt.geom.AffineTransform

import static qupath.lib.gui.scripting.QPEx.*

if (otherImageName == null) {
    DisplayHelpers.showErrorNotification("Transform objects", "Please specify an image name in the script!")
    return
}

// Get the project & the requested image name
def project = getProject()
def entry = project.getImageList().find {it.getImageName() == otherImageName}
if (entry == null) {
    print 'Could not find image with name ' + otherImageName
    return
}

def otherHierarchy = entry.readHierarchy()
def pathObjects = otherHierarchy.getRootObject().getChildObjects()

// Define the transformation matrix
def transform = new AffineTransform(
        matrix[0], matrix[3], matrix[1],
        matrix[4], matrix[2], matrix[5]
)
if (createInverse)
    transform = transform.createInverse()

if (deleteExisting)
    clearAllObjects()

def newObjects = []
for (pathObject in pathObjects) {
   tempObject = transformObject(pathObject, transform)
   if (tempObject != null)
       newObjects << tempObject
}
addObjects(newObjects)

print 'Done!'

/**
 * Transform object, recursively transforming all child objects
 *
 * @param pathObject
 * @param transform
 * @return
 */
PathObject transformObject(PathObject pathObject, AffineTransform transform) {
    // Create a new object with the converted ROI
    def roi = pathObject.getROI()
    def roi2 = transformROI(roi, transform)
    def newObject = null
      if (pathObject instanceof PathCellObject) {
    }
      else if (pathObject instanceof PathTileObject) {
        newObject = PathObjects.createTileObject(roi2, pathObject.getPathClass(), pathObject.getMeasurementList())
    } else {
        newObject = PathObjects.createAnnotationObject(roi2, pathObject.getPathClass(), pathObject.getMeasurementList())
    }
    return newObject
}

/**
 * Transform ROI (via conversion to Java AWT shape)
 *
 * @param roi
 * @param transform
 * @return
 */
ROI transformROI(ROI roi, AffineTransform transform) {
    def shape = PathROIToolsAwt.getShape(roi) // Should be able to use roi.getShape() - but there's currently a bug in it for rectangles/ellipses!
    shape2 = transform.createTransformedShape(shape)
    return PathROIToolsAwt.getShapeROI(shape2, roi.getC(), roi.getZ(), roi.getT(), 0.5)
    }

Mustafa

Is there away to import the annotation names and description since were overlaying them to compare each ROI (tile) can the names be imported currently it imports them as Polygons

Mustafa

That is all probably doable with enough scripting, but have you tried classifying the annotations? That should transfer directly.

I just changed the names in their description eg ROI-1 and wanted the same region to be named the same on the other slide obviously in the same location. Haven’t classified the tiles annotation, I didn’t think I need to

Yeah, I understand that that would be convenient! As a temporary measure, though, you could create classes with the names of the annotations, and those would transfer over, since the current script saves the classifications.
A quick script like this transfers the name into a class, which then transfers:

getAnnotationObjects().each{it.setPathClass(getPathClass(it.getName()))}
fireHierarchyUpdate()

You could further modify the code above to

for (pathObject in pathObjects) {
   tempObject = transformObject(pathObject, transform)
   if (tempObject != null) {
       tempObject.setName(pathObject.getName())
       newObjects << tempObject
   }

(I haven’t tested it…)

ERROR: Error at line 69: null
when I try and run with get.Name

getName(), not get.Name

sorry it was getName

I’d need the full script to know what is at line 69.

Line 69 is just below the addObjects(newObjects)

/**
 * Script to transfer QuPath objects from one image to another, applying an AffineTransform to any ROIs.
 */

// SET ME! Define transformation matrix
// Get this from 'Interactive image alignment (experimental)
def matrix = [
      0.993, -0.122, 49200.353,
      0.122, 0.993, 2433.906
]

// SET ME! Define image containing the original objects (must be in the current project)
def otherImageName = 'TGU124_'

// SET ME! Delete existing objects
def deleteExisting = false

// SET ME! Change this if things end up in the wrong place
def createInverse = false


import qupath.lib.gui.helpers.DisplayHelpers
import qupath.lib.objects.PathCellObject
import qupath.lib.objects.PathDetectionObject
import qupath.lib.objects.PathObject
import qupath.lib.objects.PathObjects
import qupath.lib.objects.PathTileObject
import qupath.lib.roi.PathROIToolsAwt
import qupath.lib.roi.interfaces.ROI

import java.awt.geom.AffineTransform

import static qupath.lib.gui.scripting.QPEx.*

if (otherImageName == null) {
    DisplayHelpers.showErrorNotification("Transform objects", "Please specify an image name in the script!")
    return
}

// Get the project & the requested image name
def project = getProject()
def entry = project.getImageList().find {it.getImageName() == otherImageName}
if (entry == null) {
    print 'Could not find image with name ' + otherImageName
    return
}

def otherHierarchy = entry.readHierarchy()
def pathObjects = otherHierarchy.getRootObject().getChildObjects()

// Define the transformation matrix
def transform = new AffineTransform(
        matrix[0], matrix[3], matrix[1],
        matrix[4], matrix[2], matrix[5]
)
if (createInverse)
    transform = transform.createInverse()

if (deleteExisting)
    clearAllObjects()

def newObjects = []
for (pathObject in pathObjects) {
   tempObject = transformObject(pathObject, transform)
   if (tempObject != null)
   tempObject.setName(pathObject.getName())
       newObjects << tempObject
}
addObjects(newObjects)

print 'Done!'

/**
 * Transform object, recursively transforming all child objects
 *
 * @param pathObject
 * @param transform
 * @return
 */
PathObject transformObject(PathObject pathObject, AffineTransform transform) {
    // Create a new object with the converted ROI
    def roi = pathObject.getROI()
    def roi2 = transformROI(roi, transform)
    def newObject = null
      if (pathObject instanceof PathCellObject) {
    }
      else if (pathObject instanceof PathTileObject) {
        newObject = PathObjects.createTileObject(roi2, pathObject.getPathClass(), pathObject.getMeasurementList())
    } else {
        newObject = PathObjects.createAnnotationObject(roi2, pathObject.getPathClass(), pathObject.getMeasurementList())
    }
    return newObject
}

/**
 * Transform ROI (via conversion to Java AWT shape)
 *
 * @param roi
 * @param transform
 * @return
 */
ROI transformROI(ROI roi, AffineTransform transform) {
    def shape = PathROIToolsAwt.getShape(roi) // Should be able to use roi.getShape() - but there's currently a bug in it for rectangles/ellipses!
    shape2 = transform.createTransformedShape(shape)
    return PathROIToolsAwt.getShapeROI(shape2, roi.getC(), roi.getZ(), roi.getT(), 0.5)
    }

Ah, check again the code I posted. There is a { and } missing here, which are necessary to make sure that the tempObject is only added if it isn’t null (which in this case means it wasn’t originally a cell).

if (tempObject != null) {
       tempObject.setName(pathObject.getName())
       newObjects << tempObject
   }

Hi Pete

The extra ‘}’ was needed, now it works

if (tempObject != null) {
       tempObject.setName(pathObject.getName())
       newObjects << tempObject
   }}

Hi Pete,

I have implemented the code provided by yourself and Mustafa however I get the annotations transferred as one polygon - I attempted the correction with the bracket however this did not alter the output.

I would be greatly appreciative of any help you can offer on this matter.

My script is below:

/**

  • Script to transfer QuPath objects from one image to another, applying an AffineTransform to any ROIs.
    */

// SET ME! Define transformation matrix
// Get this from 'Interactive image alignment (experimental)
def matrix = [
1.000, 0.017, -20.334
-0.017, 1.000, 6.263

]

// SET ME! Define image containing the original objects (must be in the current project)
def otherImageName = ‘1b_cycIF_PDAC5_MDSC_[10061,46046]_component_data.tif - resolution #1

// SET ME! Delete existing objects
def deleteExisting = false

// SET ME! Change this if things end up in the wrong place
def createInverse = false

import qupath.lib.gui.helpers.DisplayHelpers
import qupath.lib.objects.PathCellObject
import qupath.lib.objects.PathDetectionObject
import qupath.lib.objects.PathObject
import qupath.lib.objects.PathObjects
import qupath.lib.objects.PathTileObject
import qupath.lib.roi.PathROIToolsAwt
import qupath.lib.roi.interfaces.ROI

import java.awt.geom.AffineTransform

import static qupath.lib.gui.scripting.QPEx.*

if (otherImageName == null) {
DisplayHelpers.showErrorNotification(“Transform objects”, “Please specify an image name in the script!”)
return
}

// Get the project & the requested image name
def project = getProject()
def entry = project.getImageList().find {it.getImageName() == otherImageName}
if (entry == null) {
print 'Could not find image with name ’ + otherImageName
return
}

def otherHierarchy = entry.readHierarchy()
def pathObjects = otherHierarchy.getRootObject().getChildObjects()

// Define the transformation matrix
def transform = new AffineTransform(
matrix[0], matrix[3], matrix[1],
matrix[4], matrix[2], matrix[5]
)
if (createInverse)
transform = transform.createInverse()

if (deleteExisting)
clearAllObjects()

def newObjects =
for (pathObject in pathObjects) {
tempObject = transformObject(pathObject, transform)
if (tempObject != null){
tempObject.setName(pathObject.getName())
newObjects << tempObject
}}
addObjects(newObjects)

print ‘Done!’

/**

  • Transform object, recursively transforming all child objects
  • @param pathObject
  • @param transform
  • @return
    */
    PathObject transformObject(PathObject pathObject, AffineTransform transform) {
    // Create a new object with the converted ROI
    // Create a new object with the converted ROI
    def roi = pathObject.getROI()
    def roi2 = transformROI(roi, transform)
    def newObject = null
    if (pathObject instanceof PathCellObject) {
    }
    else if (pathObject instanceof PathTileObject) {
    newObject = PathObjects.createTileObject(roi2, pathObject.getPathClass(), pathObject.getMeasurementList())
    } else {
    newObject = PathObjects.createAnnotationObject(roi2, pathObject.getPathClass(), pathObject.getMeasurementList())
    }
    return newObject
    }

/**

  • Transform ROI (via conversion to Java AWT shape)
  • @param roi
  • @param transform
  • @return
    */
    ROI transformROI(ROI roi, AffineTransform transform) {
    def shape = PathROIToolsAwt.getShape(roi) // Should be able to use roi.getShape() - but there’s currently a bug in it for rectangles/ellipses!
    shape2 = transform.createTransformedShape(shape)
    return PathROIToolsAwt.getShapeROI(shape2, roi.getC(), roi.getZ(), roi.getT(), 0.5)
    }

Is the polygon incorrect, or could you split the polygon into component pieces again? There are two different script options there, not sure which one would fit your project better.

Also, not sure what happened, but your code does not appear to have copied over correctly. Though it may also be my browser.
image

The annotations are supposed to be cell annotations not polygons.

The square brackets code is correct on my script - i think it just pasted differently in the browser.

Ah, ok, it looks like the code is missing for transforming cells, it just does objects. So you are correct, the script will not work for those as is. Note the instanceof PathCellObject has no code in the brackets.
image

In Pete’s script, that code is still included.
image
At least that’s what I’m seeing from a brief overview. There may be other issues.

Good spot!

I have changed this and get the cell annotations but in the wrong place:

As you can see, should be a rectangle annotation object with cell annotations inside…

@Research_Associate maybe it is something to do with the transformation matrix?

// Define the transformation matrix
def transform = new AffineTransform(
matrix[0], matrix[3], matrix[1],
matrix[4], matrix[2], matrix[5]
)
if (createInverse)
transform = transform.createInverse()