Skip to content

Instantly share code, notes, and snippets.

@petebankhead
Created September 9, 2020 18:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petebankhead/6225c82c5eef42c190808e07bdad186e to your computer and use it in GitHub Desktop.
Save petebankhead/6225c82c5eef42c190808e07bdad186e to your computer and use it in GitHub Desktop.
General template for using ImageJ to detect ROIs from QuPath v0.2
/**
* General template for using ImageJ to detect ROIs from QuPath.
*
* Note the results of this script are terrible; it just applies a threshold per tile (calculated per tile).
* Its purpose is to show the process... not to do something useful in itself.
*
* However, in principle you just need to change the 'detectRois' method to something better to have something
* that might be worthwhile.
*
* @author Pete Bankhead
*/
import ij.ImagePlus
import ij.gui.Roi
import ij.measure.ResultsTable
import ij.plugin.filter.ParticleAnalyzer
import ij.process.AutoThresholder
import qupath.imagej.tools.IJTools
import qupath.lib.images.servers.ImageServer
import qupath.lib.images.servers.ImageServers
import qupath.lib.images.servers.TileRequest
import qupath.lib.objects.PathObject
import qupath.lib.objects.PathObjects
import qupath.lib.regions.RegionRequest
import qupath.lib.roi.interfaces.ROI
import java.awt.image.BufferedImage
import java.util.function.Function
import java.util.stream.Collectors
import static qupath.lib.gui.scripting.QPEx.*
// TODO: Parameters to set
double preferredPixelSize = 10.0 // In calibrated units (whatever they may be)
int tileSize = 512
boolean doParallel = true
boolean createAnnotations = false
boolean removeExistingObjects = true // Generally a good idea...
// Get the main QuPath structures for the current image
def imageData = getCurrentImageData()
def server = imageData.getServer()
def hierarchy = imageData.getHierarchy()
// Create a pyramidalizing server... since then this will do a lot of the work for us in determining appropriate
// tiles and making image requests
double downsample = preferredPixelSize / server.getPixelCalibration().getAveragedPixelSize().doubleValue()
server = ImageServers.pyramidalizeTiled(server, tileSize, tileSize, downsample)
// Apply detection within selected objects (if there are any) or the entire image (if there aren't selections)
def selectedObjects = hierarchy.getSelectionModel().getSelectedObjects()
if (selectedObjects)
selectedObjects = new ArrayList<>(selectedObjects)
else
selectedObjects = [hierarchy.getRootObject()]
Function<ROI, PathObject> detectionCreator = r -> PathObjects.createDetectionObject(r)
Function<ROI, PathObject> annotationCreator = r -> PathObjects.createAnnotationObject(r)
def objectCreator = createAnnotations ? annotationCreator : detectionCreator
for (def parent in selectedObjects) {
// Figure out which image tiles we need for the current object (or entire image)
Collection<TileRequest> tileRequests
Roi roiIJ
if (parent.isRootObject())
tileRequests = server.getTileRequestManager().getTileRequestsForLevel(0)
else {
double ds = server.getDownsampleForResolution(0)
def region = RegionRequest.createInstance(server.getPath(), ds, parent.getROI())
tileRequests = server.getTileRequestManager().getTileRequests(region)
}
// Send image regions to ImageJ, detect Rois, convert Rois to objects & then add them to the hierarchy in QuPath
def stream = tileRequests.stream()
if (doParallel)
stream = stream.parallel()
def allObjects = stream
.flatMap(tile -> detectObjects(server, tile.getRegionRequest(), parent.getROI(), objectCreator)
.stream())
.collect(Collectors.toList())
// Remove existing objects (don't have to... but probably should)
if (removeExistingObjects)
parent.clearPathObjects()
// Add new objects
parent.addPathObjects(allObjects)
}
hierarchy.fireHierarchyChangedEvent(this)
/**
* Run an ImageJ detection method and convert the result to QuPath objects.
* @param server
* @param request
* @return a list of objects created from the ImageJ detection
*/
List<PathObject> detectObjects(ImageServer<BufferedImage> server, RegionRequest request, ROI roi, Function<ROI, PathObject> creator) {
def pathImage = IJTools.convertToImagePlus(server, request)
def imp = pathImage.getImage()
if (roi != null) {
def roiIJ = IJTools.convertToIJRoi(roi, pathImage)
imp.setRoi(roiIJ)
}
def roisDetected = detectRois(imp)
return roisDetected.collect {r -> creator.apply(IJTools.convertToROI(r, pathImage)) }
}
/**
* TODO: Replace with any more meaningful detection method that returns ROIs.
* @param imp the input image
* @return a list of ROIs detected from the image
*/
List<Roi> detectRois(ImagePlus imp) {
def ip = imp.getProcessor().convertToByteProcessor()
ip.setAutoThreshold(AutoThresholder.Method.Otsu, true)
def roiIJ = imp.getRoi()
if (roiIJ)
ip.setRoi(roiIJ)
def pa = new ParticleAnalyzer(ParticleAnalyzer.SHOW_OVERLAY_OUTLINES, 0, new ResultsTable(), 0, Double.POSITIVE_INFINITY)
pa.analyze(imp, ip)
def overlay = imp.getOverlay()
if (overlay)
return overlay.toList()
else
return []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment