Created
September 9, 2020 18:12
-
-
Save petebankhead/6225c82c5eef42c190808e07bdad186e to your computer and use it in GitHub Desktop.
General template for using ImageJ to detect ROIs from QuPath v0.2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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