This document aims to compile the documents that were created during the QuPath Workshop and Hackathon which took place from April 17th till April 20th
= Questions & Answers =
== Is it possible to open confocal images (lsm files)? ==
Yes. But you'll need the QuPath BioFormats Extension, which you'll need to install [[ https://github.com/qupath/qupath-bioformats-extension | according to the instructions on GitHub ]]
== Can there be more than 2 stains? ==
The current color deconvolution can technically work with 3 stains. This however will be the future job of the pixel classification extension.
To try it with different stains, you need to set the Image Type to **Brigthfield (Other)** under the Image Tab. You will need to estimate the stain vectors using ImageJ's Color Deconvolution plugin and enter them manually. Otherwise you can create a **Recangular Annotation** and then double click on the Stain # value you want to set.
Careful to choose regions that have a "pure" component.
== Are Z-Stacks supported? ==
Yes, QuPath can read Z Stacks as well as time series. It will not, however, perform 3D detections and does not support 3D annotations
== Are bit depths other than 8-bit supported? ==
Yes, but the support is currently limited, as QuPath was originally meant to work on RGB images only. The capacity top open images other than 8-bit will depend on the image reader, and currently it is possible with BioFormats, but not ideal. Expect improved support in the future.
== Can a custom version of ImageJ (e.g. FIJI) be used instead of the one shipped with QuPath ==
Like ICY, QuPath runs its own instance of ImageJ. You could replace the JAR files with other 'simple' ImageJ flavors, but Fiji, which contains both ImageJ1 and ImageJ2 is currently too contrived to be supported.
The **suggested** way to use and extend the ImageJ functionality is to point QuPath to your current ImageJ's plugins folder. Any dependencies would have to be in the `jars` folder of QuPAth.
== Can the macro runner run ImageJ macros (.ijm) only or also scripts written in a different language? ==
Currently the Macro Runner only runs ImageJ1 macros.
== Can we set a different threshold of cell detection in a specific area ? ==
Yes, but through a script. To make it work, you would define different classes for your annotations. then going through them, for each annotation you would launch a new 'cell detection' based on the class.
== What is the Cell Detection behaviour on fluorescence images? ==
If the image type is set to Fluorescence (from the Image tab), Cell detection will ask for the channel to use for detecting and offer identical parameters as for brightfield. The only difference will usually be in the value of the threshold, which will typically be higher than the one used for Brigthfield.
NOTE: The Positive Cell Detection extension will **not** allow you to select the threshold on another channel to classify your detections. You can currently do this through a script though.
=== Example Script ===
```lang=java
// Set the cells with an average nuclear intensity on channel 3 above 40 as positive
setCellIntensityClassifications("Nucleus: Channel 3 mean",40)
// The name of the measurement corresponds to the column name in Measure > Show Detection Measurements
```
== Would it be possible to duplicate an annotation from one z plane to another z plane in the same image? ==
It is rather contrived but we have a script for that
```lang=java
import qupath.lib.objects.PathAnnotationObject
import qupath.lib.roi.PathROIToolsAwt
// Duplicate an object to another Z Slice (Or timepoint)
def newZ = 2
// Get the selected object
def currentObject = getSelectedObject()
// Get the current image's hierarchy
def hierarchy = getCurrentHierarchy()
// We cannot just duplicate and set the Z right now, as it is set the by the ROI
// The ROI cannot be modified (it is immutable) so
// We must create a new PathAnnotaionObject
// Get the object's ROI
def roi = currentObject.getROI()
// Convert it to a shape (the most basic way to define an object)
def shape = PathROIToolsAwt.getShape(roi)
// There is a method to create a ROI from a shape which allows us to (finally) set the Z (or T)
def roi2 = PathROIToolsAwt.getShapeROI(shape, -1, newZ, roi.getT(), 0.5)
// We can now make a new annotation
def annotation = new PathAnnotationObject(roi2)
// Add it to the current hierarchy. When we move in Z to the desired slice, we should see the annotation
hierarchy.addPathObject(annotation, false)
```
NOTE: This script was offered to users who had issues with VSI files where channels were being treated as different Z slices. A fix of the QuPath Bioformats Extension should fix the need for this in that particular case.
== How many channels are supported by QuPath in Fluorescence? ==
QuPath supports an arbitrary number of channels, but there is a known bug where it will not be possible to set the brightness and contrast to the 4th channel of a 4 channel image!
== Does changing brightness/contrast affect measured values? ==
This does not affect the values of the measurements. The only effect adjusting B/C has is in the behavior of the Magic Wand tool. If you feel the tool is being too 'strict' (Selecting less than you would expect), try loweing the contrast by setting the min and max values from the B/C farther apart. Accordingly, if the magic wand is too 'inclusive', try increasing the contrast to make it more strict.
== Is there a batch mode? ==
Anything that you wish to automate will involve a script. Cell Detection and most Extensions are recordable as a workflow steps which can then be made into scripts. These scripts can be run for new iamges or for the entire project (using **Run > Run for Project...** from the script editor window
== Can we measure inside the overlay that was sent from imageJ? ==
Annotations have few measurement computed automatically, and one must run Analyze > Calculate Features > ...
== Can we do pixel classification? ==
Pixel classification is currently experimental and on its waay to a new version of QuPath, but currently it is not supported.
== What other objects besides cells can be classified? ==
All **PathObjects** can be classified (that is, Annotations, Detections and Superpixels for now). Annotations can have their classes set using the GUI. Detections have their classes set by different tools but not via the GUI. It can be done via a script if necessary.
=== Example Script ===
``` lang= java
// Create a new class or select an existing class by name
def myClass = getPathClass('My Class')
// Get currently selected object and set its class
def currentObject = getSelectedObject()
currentObject.setPathClass(myClass)
```
== Can make an annotation out of a classification result? ==
== Ho do I set the resolution of an image manually? ==
Unfortunately, this is currently not possible. If you really need to set a resolution (pixel size) of the image, you'd need to open it in ImageJ and resave it as a TIFF from there.
== Is there a way to make “close project” also close the currently open image? ==
Unfortunately there currently is no way. You'd have to close the image first or after you close the project with {nav Right click > MultiView > Close viewer}
== Is it possible to add measurements to regions from ImageJ? ==
Yes, a good workflow can be found [[ https://petebankhead.github.io/qupath/scripting/2018/03/08/script-imagej-to-qupath.html | on Pete Bankhead's blog ]].
This is not currently possible with the Macro Runner unfortunately. You will only get the annotation and no associated results.
== How do we detect other things like fibers or differently shaped cells? ==
There is currently no implementation of tools that perform such analyses as this point. If you have a plugin in ImageJ, a solution is to send the region to ImageJ for processing or using the Macro runner.
= Scripts =
Here is the collection of scripts that were produced during the workshop. In case you notice missing ones or have scripts to contribute yourself, please get in touch with either Romain or Oli.
== Reset Classification Of Cell Detections ==
```jang=java
resetIntensityClassification()
```
== Set The Classification For All Cells ==
```lang=java
// Define the classification with getPathClass (will create the classification if it does not exist
classification = getPathClass('Other')
getCellObjects().each {
it.setPathClass(classification)
}
fireHierarchyUpdate()
```
== Select All Child Objects In A Parent ==
```lang=java
hierarchy = getCurrentHierarchy()
// It is simply a matter of calling 'getChildObjects() from the currently selected one
annotations = getSelectedObject().getChildObjects()
// The line below makes the selection become active in the QuPath GUI
hierarchy.getSelectionModel().selectObjects(annotations)
```
== Prompting The User For The Location Where To Save a File ==
```lang=java
import qupath.lib.gui.QuPathGUI
// promptToSaveFile(title,base_directory,default_file_name, file_filter, extension)
def file = QuPathGUI.getSharedDialogHelper().promptToSaveFile("Title", null, null, "Text file", ".txt")
print file
```
== Create Detection Objects ==
```lang=java
/*
* Create detection objects.
* This is intended for use with OS-2.ndpi.
*/
import qupath.lib.roi.RectangleROI
import qupath.lib.objects.PathAnnotationObject
// Create a new rectangle annotation & add it to the hierarchy
def roi = new RectangleROI(55500, 35000, 5000, 5000)
def annotation = new PathAnnotationObject(roi)
addObject(annotation)
// Make sure the annotation is selected, and run nucleus detection
setSelectedObject(annotation)
runPlugin('qupath.imagej.detect.nuclei.PositiveCellDetection', '{"detectionImageBrightfield": "Hematoxylin OD", "requestedPixelSizeMicrons": 0.5, "backgroundRadiusMicrons": 8.0, "medianRadiusMicrons": 0.0, "sigmaMicrons": 1.5, "minAreaMicrons": 10.0, "maxAreaMicrons": 400.0, "threshold": 0.1, "maxBackground": 2.0, "watershedPostProcess": true, "excludeDAB": false, "cellExpansionMicrons": 0.0, "includeNuclei": true, "smoothBoundaries": true, "makeMeasurements": true, "thresholdCompartment": "Nucleus: DAB OD mean", "thresholdPositive1": 0.2, "thresholdPositive2": 0.4, "thresholdPositive3": 0.6, "singleThreshold": true}');
// Show what we've now got
print 'I now have ' + getDetectionObjects().size() + ' detection objects and ' + getAnnotationObjects().size() + ' annotation objects'
```
== Converting Detections To Annotations ==
```lang=java
Convert detection to annotations
/*
* Convert annotation objects to detections.
* Warning! This may be very slow, and a bad idea!
* It is intended to show the different behavior between
* detection & annotation objects.
*/
import qupath.lib.objects.PathAnnotationObject
// Create new annotations with the same ROIs and classifications as the detections
def detections = getDetectionObjects()
def newAnnotations = detections.collect {detection -> new PathAnnotationObject(detection.getROI(), detection.getPathClass())}
// Remove the detections, add the annotations
removeObjects(detections, false)
addObjects(newAnnotations)
```
== Fluorescence: Detect DAPI, Measure other channels ==
```lang=java
// Access Cells
cells = getCellObjects()
// Define Channel names and threshlold values
ch2Name = 'Nucleus: Channel 2 mean'
ch3Name = 'Nucleus: Channel 3 mean'
ch2Threshold = 20
ch3Threshold = 40
// Get or create classifications we will be using
doublePositive = getPathClass('Double-positive', getColorRGB(150,150,0))
ch2Positive = getPathClass('Ch2-Positive', getColorRGB(0,150,0))
ch3Positive = getPathClass('Ch3-Positive', getColorRGB(150,0,0))
negative = getPathClass('Negative')
// Loop through them and requestmeasurements
for(cell in cells) {
// Extract measurements for channels measurement(theAnnotation, measurementName)
ch2Measurement = measurement(cell, ch2Name)
ch3Measurement = measurement(cell, ch3Name)
if( ch2Measurement > ch2Threshold && ch3Measurement > ch3Threshold ) {
// Make double positive
cell.setPathClass(doublePositive)
} else if( ch2Measurement > ch2Threshold ) {
// Make ch2 positive
cell.setPathClass(ch2Positive)
} else if( ch3Measurement > ch3Threshold ) {
// Make ch3 positive
cell.setPathClass(ch3Positive)
} else {
// Make negative
cell.setPathClass(negative)
}
}
fireHierarchyUpdate()
```
== Save Cell Classification To Tab-Separated File ==
This script will add one row to the results file each time it is run with the count of each cell that matches the classifications we requested
```lang=java
// Create Output Path
outputPath = buildFilePath(PROJECT_BASE_DIR, 'export')
// Make directory in case it does not exist
mkdirs(outputPath )
// Give the results file a name
fileName = 'classification-result.txt'
// Get or create classifications and set colors
doublePositive = getPathClass('Double-positive', getColorRGB(150,150,0))
ch2Positive = getPathClass('Ch2-Positive', getColorRGB(0,150,0))
ch3Positive = getPathClass('Ch3-Positive', getColorRGB(150,0,0))
negative = getPathClass('Negative')
// Access Cells
cells = getCellObjects()
// User Groovy FindAll to get only the cells that match the desired classifications
doublePositiveCells = cells.findAll{ cell -> cell.getPathClass() == doublePositive }
ch2PositiveCells = cells.findAll{ cell -> cell.getPathClass() == ch2Positive }
ch3PositiveCells = cells.findAll{ cell -> cell.getPathClass() == ch3Positive }
negativeCells = cells.findAll{ cell -> cell.getPathClass() == negative }
// Write results in the desired directory to the desired filename
file = new File(outputPath, fileName)
// Define the delimiter to use, typically a comma or a tab as below
delimiter = '\t'
// Make sure the file exists, otherwise create and initialize it (write the column names)
if(!file.exists() ) {
file.text = 'Image Name' << delimiter <<
'Double Positive' << delimiter <<
'Ch2 Positive' << delimiter <<
'Ch3 Positive' << delimiter <<
'Negative' << delimiter << System.lineSeparator()
}
// Append the results to the end of the file
file << getProjectEntry().getImageName() << delimiter <<
doublePositiveCells.size() << delimiter <<
ch2PositiveCells.size() << delimiter <<
ch3PositiveCells.size() << delimiter <<
negativeCells.size() << delimiter << System.lineSeparator()
```