Skip to content

Instantly share code, notes, and snippets.

@petebankhead
Last active December 2, 2020 19:04
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/db8548a0112bad089492061bf8046430 to your computer and use it in GitHub Desktop.
Save petebankhead/db8548a0112bad089492061bf8046430 to your computer and use it in GitHub Desktop.
Demo script showing one way to use the DeepCell Kiosk ImageJ plugin from QuPath
/**
* Run cell detection using the DeepCell Kiosk ImageJ plugin from
* https://github.com/vanvalenlab/kiosk-imagej-plugin
*
* For authorship & license of Deep Cell see http://deepcell.org
*
* To use the script, drag the Kiosk plugin on top of QuPath to make it available as an extension.
* Then select a (small!) region of interest in a compatible image and run this script from QuPath's script editor.
*
* Note that the image needs to be in a compatible format -
* see https://github.com/vanvalenlab/intro-to-deepcell/tree/master/deepcell_dot_org#formatting-data-for-web-based-models
*
* This is really just a proof-of-concept, which doesn't do any fancy tiling / error-checking.
*
* See https://twitter.com/NoahGreenwald/status/1334153336178704384 for beginnings.
*
* @author Pete Bankhead (just QuPath & this script... not DeepCell)
*/
import ij.ImagePlus
import ij.io.Opener
import org.vanvalenlab.KioskJob
import org.vanvalenlab.KioskJobManager
import org.vanvalenlab.exceptions.KioskJobFailedException
import qupath.imagej.processing.RoiLabeling
import qupath.imagej.tools.IJTools
import qupath.lib.analysis.features.ObjectMeasurements
import qupath.lib.common.ThreadTools
import qupath.lib.gui.prefs.PathPrefs
import qupath.lib.images.servers.ImageServer
import qupath.lib.images.servers.TransformedServerBuilder
import qupath.lib.objects.PathObject
import qupath.lib.objects.PathObjects
import qupath.lib.regions.RegionRequest
import java.awt.image.BufferedImage
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import static qupath.lib.gui.scripting.QPEx.*
/**
* Set the main parameters
*/
def options = new KioskOptions(jobType: "multiplex") // Specify jobType (may be multiplex or segmentation)
boolean includeMeasurements = true // Optionally include default shape/intensity measurements
int[] channels = [] // Optionally specify channels to extract (0-based)
double requestedPixelSize = -1 // Optionally specify the target pixel size for resizing the image
int nThreads = PathPrefs.numCommandThreadsProperty().value // Do multithreading
// Get Current image
def imageData = getCurrentImageData()
def server = imageData.getServer()
// Extract channels if necessary
if (channels)
server = new TransformedServerBuilder(server).extractChannels(channels as int[]).build()
// Determine downsample
double downsample = 1
if (requestedPixelSize > 0)
downsample = requestedPixelSize / server.getPixelCalibration().getAveragedPixelSize()
// Loop through selected annotations, or use the full image if nothing is selected
def hierarchy = imageData.getHierarchy()
def selected = new ArrayList<>(hierarchy.getSelectionModel().getSelectedObjects())
if (selected.isEmpty())
selected = [hierarchy.getRootObject()]
// Run multithreaded
def pool = Executors.newFixedThreadPool(nThreads, ThreadTools.createThreadFactory("deepCell", true))
def newObjects = new LinkedHashMap<PathObject, Future<List<PathObject>>>()
for (def parent in selected) {
def parent2 = parent
def future = pool.submit({ ->
return detectCells(server, parent2, downsample, includeMeasurements, options)
} as Callable)
newObjects.put(parent2, future)
}
print "Waiting for ${selected.size()} task(s) to complete"
pool.shutdown()
pool.awaitTermination(24, TimeUnit.HOURS)
// Don't update objects until everything is done
for (entry in newObjects.entrySet()) {
def future = entry.getValue()
def parent = entry.getKey()
if (future.isCancelled()) {
println 'Cancelled ' + parent
continue
}
def detections = future.get()
println 'Returned: ' + detections.size()
parent.clearPathObjects()
parent.addPathObjects(detections)
}
hierarchy.fireHierarchyChangedEvent(this)
print "Done!"
/**
* Detect cells corresponding to an image region in QuPath
*
* @param server
* @param parent
* @param downsample
* @param includeMeasurements
* @param options
* @return
*/
static List<PathObject> detectCells(ImageServer<BufferedImage> server, PathObject parent, double downsample, boolean includeMeasurements, KioskOptions options) {
RegionRequest request
def parentROI = parent.getROI()
if (parentROI)
request = RegionRequest.createInstance(server.getPath(), downsample, parentROI)
else
request = RegionRequest.createInstance(server, downsample)
def pathImage = IJTools.convertToImagePlus(server, request)
def imp = pathImage.getImage()
imp.setFileInfo(null)
def impLabeled = runJob(KioskJobManager.getFilePath(imp), options)
def ip = impLabeled.getProcessor()
int n = ip.getStatistics().max
def roisIJ = RoiLabeling.labelsToConnectedROIs(impLabeled.getProcessor(), n)
getQuPath().logger.info('ROIs: ' + roisIJ.size())
def measurements = ObjectMeasurements.Measurements.values()
def compartments = ObjectMeasurements.Compartments.values()
def detections = []
for (r in roisIJ) {
def roi = IJTools.convertToROI(r, pathImage)
if (parentROI != null && !parentROI.contains(roi.getCentroidX(), roi.getCentroidY()))
continue
def detection = PathObjects.createDetectionObject(roi)
if (includeMeasurements) {
ObjectMeasurements.addShapeMeasurements(detection, server.getPixelCalibration(), ObjectMeasurements.ShapeFeatures.values())
ObjectMeasurements.addIntensityMeasurements(server, detection, downsample, measurements as List, compartments as List)
}
detections.add(detection)
}
getQuPath().logger.info('Detections: ' + detections.size())
return detections
}
/**
* Run the job using the Kiosk plugin
*
* @param imageFile
* @param options
* @return
* @throws IOException
* @throws KioskJobFailedException
*/
static ImagePlus runJob(String imageFile, KioskOptions options) throws IOException, KioskJobFailedException {
def job = new KioskJob(options.host, options.jobType)
job.create(imageFile)
String finalStatus = job.waitForFinalStatus(options.statusUpdateMillis)
job.expire(options.jobExpiresSec)
if (finalStatus.equals("failed")) {
throw new KioskJobFailedException(job.getErrorReason())
} else if (finalStatus.equals("done")) {
def outputPath = job.getOutputPath()
Opener opener = new Opener()
return opener.openURL(outputPath)
} else {
throw new RuntimeException("Unknown final status: " + finalStatus)
}
}
/**
* Helper class for storing key options
*/
class KioskOptions {
String jobType = "Multiplex"
String host = "http://deepcell.org"
int statusUpdateMillis = 5000
int jobExpiresSec = 3600
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment