import qupath.lib.objects.PathObjects import qupath.lib.roi.ROIs import qupath.lib.geom.Point2 import java.util.Random // Current image def imageData = getCurrentImageData() def server = imageData.getServer() // Parameters double squareSizeMicrons = 100.0 int totalSquares = 5 String targetClassName = "peribronchial region" // Maximum number of global attempts int maxAttempts = 50000 // Pixel calibration def cal = server.getPixelCalibration() if (!cal.hasPixelSizeMicrons()) { print "The image does not have micron calibration." return } double pixelWidth = cal.getPixelWidthMicrons() double pixelHeight = cal.getPixelHeightMicrons() double squareWidthPx = squareSizeMicrons / pixelWidth double squareHeightPx = squareSizeMicrons / pixelHeight Random rand = new Random() // Check whether two rectangles overlap boolean rectanglesOverlap( double x1, double y1, double w1, double h1, double x2, double y2, double w2, double h2) { return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2 } // Find annotations classified as "peribronchial region" def peribronchialAnnotations = getAnnotationObjects().findAll { it.getPathClass() != null && it.getPathClass().toString().equalsIgnoreCase(targetClassName) } if (peribronchialAnnotations.isEmpty()) { print "No annotations classified as '${targetClassName}' were found." return } // Keep only annotations large enough to contain the square def validPeribronchialAnnotations = peribronchialAnnotations.findAll { annotation -> def roi = annotation.getROI() roi.getBoundsWidth() >= squareWidthPx && roi.getBoundsHeight() >= squareHeightPx } if (validPeribronchialAnnotations.isEmpty()) { print "No '${targetClassName}' annotation is large enough to contain a ${squareSizeMicrons} µm square." return } // New annotations to be added to QuPath def newAnnotations = [] // Store the squares created within each parent annotation def squaresByParent = [:].withDefault { [] } int created = 0 int attempts = 0 while (created < totalSquares && attempts < maxAttempts) { attempts++ // Randomly select a peribronchial region def parentAnnotation = validPeribronchialAnnotations[ rand.nextInt(validPeribronchialAnnotations.size()) ] def parentROI = parentAnnotation.getROI() double minX = parentROI.getBoundsX() double minY = parentROI.getBoundsY() double maxX = minX + parentROI.getBoundsWidth() double maxY = minY + parentROI.getBoundsHeight() // Generate a random position within its bounds double x = minX + rand.nextDouble() * ((maxX - minX) - squareWidthPx) double y = minY + rand.nextDouble() * ((maxY - minY) - squareHeightPx) // Check that all four corners and the center are inside def pointsToCheck = [ new Point2(x, y), new Point2(x + squareWidthPx, y), new Point2(x, y + squareHeightPx), new Point2(x + squareWidthPx, y + squareHeightPx), new Point2( x + squareWidthPx / 2.0, y + squareHeightPx / 2.0 ) ] boolean inside = pointsToCheck.every { point -> parentROI.contains(point.getX(), point.getY()) } if (!inside) continue // Check that the square does not overlap with another // square already created within the same parent annotation def existingSquares = squaresByParent[parentAnnotation] boolean overlaps = existingSquares.any { square -> rectanglesOverlap( x, y, squareWidthPx, squareHeightPx, square.x, square.y, square.w, square.h ) } if (overlaps) continue // Create the rectangular ROI def roi = ROIs.createRectangleROI( x, y, squareWidthPx, squareHeightPx, parentROI.getImagePlane() ) def annotation = PathObjects.createAnnotationObject(roi) annotation.setName( "Random peribronchial ROI ${created + 1} - ${squareSizeMicrons} µm" ) newAnnotations << annotation existingSquares << [ x: x, y: y, w: squareWidthPx, h: squareHeightPx ] created++ } // Add the ROIs to the QuPath hierarchy addObjects(newAnnotations) if (created < totalSquares) { print "Only ${created} of the requested ${totalSquares} ROIs could be created after ${attempts} attempts." } else { print "Successfully generated ${created} random ROIs, each measuring ${squareSizeMicrons} µm × ${squareSizeMicrons} µm, within annotations classified as '${targetClassName}'." }