Last active
October 15, 2024 06:02
-
-
Save Svidro/e00021dff92ea1173e535008854be72e to your computer and use it in GitHub Desktop.
Color changes in QuPath
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
Collections of scripts to alter object colors harvested mainly from Pete, but also picked up from the forums | |
TOC | |
Change IF channel color.groovy - Change the LUT for individual channels. Does not work with OpenSlide servers (check Image tab). | |
Change colors by subclass.groovy - Detection object color adjustment when using subclasses. | |
Change subcellular detection color - Hugely useful when working with subcellular detections as, by default, they are a derived class | |
and cannot be altered directly through the Annotation tab interface. | |
Density heatmap by class.groovy - Add a measurement to cells that can be used to visually identify hotspots per class | |
Density heatmap by class with blur.groovy - better looking heatmap than above. | |
Heatmap for channels.groovy - Apply a LUT to a channel, instead of the default solid colors | |
Invert LUT of selected channels.groovy | |
Measurement map buttons.groovy - create some fixed buttons to make viewing consistent measurement maps much easeier. | |
Measurement Maps color lock.groovy - set fixed values for the measurement maps. Useful when comparing two images to each other and | |
keeping the same relative color display values. | |
Measurement Maps in 0.2.0.groovy - a code example for how to use measurement maps in version 0.2.0 | |
Rename and recolor a class.groovy - <-- what it says | |
Show specific classes of objects v3.groovy - adds in checkboxes for groups of similarly named classes | |
Specific Object Color changes.groovy - A way of cycling through objects and set each object to a different color | |
TMA heatmap by color.groovy - Create detection objects in each TMA core, giving them measurements that are summaries of what is | |
in the core. When Measurement maps are used and one of the summary measurments is selected, the whole TMA turns into a heatmap | |
1 | |
From Gitter: | |
If you have a class already created, you can alter the color for that class (replace pathClass with the class) | |
pathClass.setColor(getColorRGB(0, 200, 0)) | |
This requires having the class as a variable, for example: | |
stroma = getPathClass('Stroma') | |
recolored would be: | |
stroma.setColor(getColorRGB(0, 200, 0)) |
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
// https://groups.google.com/forum/#!topic/qupath-users/rBCRysCZEzM | |
//0.1.2 and 0.2.0 | |
// Access the 'Stroma: Positive' sub-classification | |
stroma = getPathClass('Stroma') | |
stromaPositive = getDerivedPathClass(stroma, 'Positive') | |
// Set the color, using a packed RGB value | |
color = getColorRGB(200, 0, 0) | |
stromaPositive.setColor(color) | |
// Update the GUI | |
fireHierarchyUpdate() |
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
// Get access to the display info for each channel | |
//An even better script from https://forum.image.sc/t/qupath-scripts-dont-detect-updated-colormaps/63618/9?u=research_associate | |
setImageType('FLUORESCENCE') | |
// You can replace the names with your stainings if you want | |
// Define minimum display values, maximum display values and channel names in order | |
def mins = [ 0, 0, 0, 0 ] | |
def maxs = [ 8000, 750, 2000, 2500 ] | |
def names = ['DAPI', 'FITC', 'TRITC', 'CY3' ] | |
// Define colors | |
def color1 = getColorRGB( 0, 128, 255 ) | |
def color2 = getColorRGB( 0, 255, 128 ) | |
def color3 = getColorRGB( 255, 0, 128 ) | |
def color4 = getColorRGB( 255, 128, 255 ) | |
// Build color array | |
def colors = [ color1, color2, color3, color4 ] | |
//Finally set everything for the current image | |
setChannelNames( *names ) | |
setChannelColors( *colors ) | |
[mins, maxs].transpose().eachWithIndex{ mima, i -> setChannelDisplayRange(i, mima[0], mima[1]) } | |
//////////////////////////////////////////// | |
/////////////////////////////////////////// | |
//For 0.2.0+ use: https://forum.image.sc/t/a-bug-in-batch-processing/40956/2?u=research_associate | |
setChannelColors( | |
getColorRGB(0, 0, 255), | |
getColorRGB(0, 255, 0), | |
getColorRGB(255, 0, 255), | |
getColorRGB(255, 0, 0) | |
) | |
//https://forum.image.sc/t/script-to-rename-channels-and-adjust-brightness-contrast/40984/4?u=research_associate | |
//setChannelColor | |
//setChannelNames | |
//setChannelDisplayRange | |
//This function changed between 0.1.2 and 0.2.0, in 0.2.0 use viewer.getImageDisplay().availableChannels() | |
def viewer = getCurrentViewer() | |
def channels = viewer.getImageDisplay().getAvailableChannels() | |
// Set the LUT color for the first channel & repaint | |
channels[0].setLUTColor(0, 0, 255) | |
channels[1].setLUTColor(255, 255, 255) | |
channels[2].setLUTColor(0, 255, 0) | |
channels[3].setLUTColor(255, 0, 0) | |
// Ensure the updates are visible | |
viewer.repaintEntireImage() | |
// Usually a good idea to print something, so we know it finished | |
print 'Done!' |
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
//0.1.2 and 0.2.0 | |
getDerivedPathClass(getPathClass('Subcellular cluster'), 'DAB object').setColor(qupath.lib.common.ColorTools.makeRGB(30, 30, 30)) |
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
// https://forum.image.sc/t/cell-density-map/40050/16 | |
/** | |
* Create a 'counts' image in QuPath that can be used to compute the local density of specific objects. | |
* | |
* This implementation uses ImageJ to create and display the image, which can then be filtered as required. | |
* | |
* Written for QuPath v0.2.0. | |
* | |
* @author Pete Bankhead | |
*/ | |
// Define the resolution at which the image should be generated | |
double requestedPixelSizeMicrons = 50 | |
double sigma = 1.5 | |
double accuracy = 0.01 | |
classList = getCurrentHierarchy().getDetectionObjects().collect{it.getPathClass()} as Set | |
// Get the current image | |
def imageData = getCurrentImageData() | |
def server = imageData.getServer() | |
// Set the downsample directly (without using the requestedPixelSize) if you want; 1.0 indicates the full resolution | |
double downsample = requestedPixelSizeMicrons / server.getPixelCalibration().getAveragedPixelSizeMicrons() | |
def request = RegionRequest.createInstance(server, downsample) | |
def imp = IJTools.convertToImagePlus(server, request).getImage() | |
// Get the objects you want to count | |
// Potentially you can add filters for specific objects, e.g. to get only those with a 'Positive' classification | |
def detections = getDetectionObjects() | |
classList.each{c-> | |
cellList = getCellObjects().findAll{it.getPathClass() == c} | |
// Create a counts image in ImageJ, where each pixel corresponds to the number of centroids at that pixel | |
int width = imp.getWidth() | |
int height = imp.getHeight() | |
def fp = new FloatProcessor(width, height) | |
for (detection in cellList) { | |
// Get ROI for a detection; this method gets the nucleus if we have a cell object (and the only ROI for anything else) | |
def roi = PathObjectTools.getROI(detection, true) | |
int x = (int)(roi.getCentroidX() / downsample) | |
int y = (int)(roi.getCentroidY() / downsample) | |
fp.setf(x, y, fp.getf(x, y) + 1 as float) | |
} | |
//Here we blur fp. Increase sigma for more blurring. | |
def g = new GaussianBlur(); | |
g.blurGaussian(fp, sigma, sigma, accuracy); | |
for (detection in detections) { | |
// Get ROI for a detection; this method gets the nucleus if we have a cell object (and the only ROI for anything else) | |
def roi = PathObjectTools.getROI(detection, true) | |
int x = (int)(roi.getCentroidX() / downsample) | |
int y = (int)(roi.getCentroidY() / downsample) | |
detection.getMeasurementList().putMeasurement(c.toString()+' Density', fp.getf(x, y)) | |
} | |
} | |
import ij.ImagePlus | |
import ij.process.FloatProcessor | |
import ij.plugin.filter.GaussianBlur; | |
//import ij.plugin.filter.PlugInFilter; | |
import qupath.imagej.gui.IJExtension | |
import qupath.lib.objects.PathObjectTools | |
import qupath.lib.regions.RegionRequest | |
import static qupath.lib.gui.scripting.QPEx.* | |
import qupath.imagej.tools.IJTools |
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
// QP 0.2.3 | |
// This script takes the values generated by the Add smoothed features plugin to create a cell density map that can be viewed in Measure->Measurement Maps | |
// For example, Tumor cells would show up in the Measurement List as "Nearby cells - Tumor" | |
// Local density will be based on the radius used during Add smoothed features. | |
//////////CHANGE THIS ////// | |
String smoothedRadius = 15 | |
//////////////////////////// | |
//Alternatively, remove this line and use the Analyze->Calculate Features-> Add smoothed features with Smooth within classes checked. | |
selectAnnotations() | |
runPlugin('qupath.lib.plugins.objects.SmoothFeaturesPlugin', '{"fwhmMicrons": '+smoothedRadius+', "smoothWithinClasses": true}'); | |
classList = getCurrentHierarchy().getDetectionObjects().collect{it.getPathClass()} as Set | |
//Find the smoothed count | |
classList.each{c-> | |
cellList = getCellObjects().findAll{it.getPathClass() == c} | |
cellList.each{ | |
it.getMeasurementList().putMeasurement("Nearby cells - "+ c.toString(), measurement(it,"Smoothed: "+smoothedRadius+" µm: Nearby detection counts")) | |
} | |
//getCellObjects().findAll{it.getPathClass() != c}.each{it.getMeasurementList().putMeasurement("Nearby cells - "+ c.toString(), 0)} | |
} | |
print "Done" |
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
//From https://github.com/qupath/qupath/issues/191 | |
//https://groups.google.com/forum/#!searchin/qupath-users/viewer%7Csort:date/qupath-users/uBMxJ_3JnBM/GkDahJw7EAAJ | |
// Get access to the display info for each channel | |
//0.1.2 | |
//For 0.2.0 use viewer.getImageDisplay().availableChannels() | |
/*def viewer = getCurrentViewer() | |
def channels = viewer.getImageDisplay().getAvailableChannels() | |
// Set the range for the first two channels | |
channels[0].setMinDisplay(0) | |
channels[0].setMaxDisplay(100) | |
channels[1].setMinDisplay(0) | |
channels[1].setMaxDisplay(500) | |
// Ensure the updates are visible | |
viewer.repaintEntireImage()*/ | |
// Usually a good idea to print something, so we know it finished | |
print 'Done!' |
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
//Use a heatmap for a fluorescent channel | |
//https://forum.image.sc/t/qupath-scripting-3-apply-colorlut-to-channel/50368 | |
import qupath.lib.display.ChannelDisplayInfo | |
import qupath.lib.display.DirectServerChannelInfo | |
import qupath.lib.display.ImageDisplay | |
import qupath.lib.gui.QuPathGUI | |
import qupath.lib.gui.tools.MeasurementMapper | |
import qupath.lib.gui.viewer.QuPathViewer | |
import qupath.lib.common.ColorTools | |
import java.awt.image.IndexColorModel | |
byte[] rb = new byte[256]; | |
byte[] gb = new byte[256]; | |
byte[] bb = new byte[256]; | |
// LUT fire | |
int[][] lutFire = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79,82,85,88,91,94,98,101,104,107,110,113,116,119,122,125,128,131,134,137,140,143,146,148,150,152,154,156,158,160,162,163,164,166,167,168,170,171,173,174,175,177,178,179,181,182,184,185,186,188,189,190,192,193,195,196,198,199,201,202,204,205,207,208,209,210,212,213,214,215,217,218,220,221,223,224,226,227,229,230,231,233,234,235,237,238,240,241,243,244,246,247,249,250,252,252,252,253,253,253,254,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255], | |
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,5,7,8,10,12,14,16,19,21,24,27,29,32,35,37,40,43,46,48,51,54,57,59,62,65,68,70,73,76,79,81,84,87,90,92,95,98,101,103,105,107,109,111,113,115,117,119,121,123,125,127,129,131,133,134,136,138,140,141,143,145,147,148,150,152,154,155,157,159,161,162,164,166,168,169,171,173,175,176,178,180,182,184,186,188,190,191,193,195,197,199,201,203,205,206,208,210,212,213,215,217,219,220,222,224,226,228,230,232,234,235,237,239,241,242,244,246,248,248,249,250,251,252,253,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255], | |
[0,7,15,22,30,38,45,53,61,65,69,74,78,82,87,91,96,100,104,108,113,117,121,125,130,134,138,143,147,151,156,160,165,168,171,175,178,181,185,188,192,195,199,202,206,209,213,216,220,220,221,222,223,224,225,226,227,224,222,220,218,216,214,212,210,206,202,199,195,191,188,184,181,177,173,169,166,162,158,154,151,147,143,140,136,132,129,125,122,118,114,111,107,103,100,96,93,89,85,82,78,74,71,67,64,60,56,53,49,45,42,38,35,31,27,23,20,16,12,8,5,4,3,3,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,8,13,17,21,26,30,35,42,50,58,66,74,82,90,98,105,113,121,129,136,144,152,160,167,175,183,191,199,207,215,223,227,231,235,239,243,247,251,255,255,255,255,255,255,255,255]] | |
QuPathViewer viewer = QuPathGUI.getInstance().getViewer() | |
// Get current channel | |
ImageDisplay display = viewer.getImageDisplay() | |
ChannelDisplayInfo channelinfoS = display.selectedChannels().get(0) | |
List<MeasurementMapper.ColorMapper> colormaps = MeasurementMapper.loadColorMappers() | |
int nColorMaps = 0 | |
colormaps.each { map -> | |
print nColorMaps + " : " + map.getName() + '\n' | |
nColorMaps++ | |
} | |
// Select your ColorMap here (or set to -1 for FireLUT) | |
// Currently | |
// 0 : Viridis | |
// 1 : Svidro2 | |
// 2 : Plasma | |
// 3 : Magma | |
// 4 : Inferno | |
// 5 : Jet | |
int useColorMap = 1 | |
if (useColorMap < 0 || useColorMap >= nColorMaps) { | |
// Create FireLUT | |
for (int i = 0; i < 256; i++) { | |
rb[i] = (byte) ColorTools.do8BitRangeCheck(lutFire[0][i]) | |
gb[i] = (byte) ColorTools.do8BitRangeCheck(lutFire[1][i]) | |
bb[i] = (byte) ColorTools.do8BitRangeCheck(lutFire[2][i]) | |
} | |
} | |
else{ | |
// Read ColorMap | |
def theMap = colormaps.get(useColorMap) | |
for (int i = 0; i < 256; i++) { | |
rb[i] = (byte) ColorTools.do8BitRangeCheck(theMap.r[i]) | |
gb[i] = (byte) ColorTools.do8BitRangeCheck(theMap.g[i]) | |
bb[i] = (byte) ColorTools.do8BitRangeCheck(theMap.b[i]) | |
} | |
} | |
if (channelinfoS != null && channelinfoS instanceof DirectServerChannelInfo){ | |
DirectServerChannelInfo directInfo = (DirectServerChannelInfo)channelinfoS | |
directInfo.cm = new IndexColorModel(8, 256, rb, gb, bb); | |
// Optional | |
// Assign histogram color (Brightness&Contrast dialog) | |
directInfo.rgb = ColorTools.makeRGB(200, 200, 200); | |
} | |
viewer.getImageRegionStore().clearCache(false, false) | |
viewer.repaintEntireImage() | |
println('Done!') |
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
// https://forum.image.sc/t/qupath-scripting-3-apply-colorlut-or-invertedlut-to-channel/50368/6?u=research_associate | |
import qupath.lib.display.DirectServerChannelInfo | |
import qupath.lib.display.ImageDisplay | |
import qupath.lib.gui.QuPathGUI | |
import qupath.lib.gui.viewer.QuPathViewer | |
import java.awt.image.IndexColorModel | |
byte[] rb = new byte[256]; | |
byte[] gb = new byte[256]; | |
byte[] bb = new byte[256]; | |
byte[] invLUT = [255,254,253,252,251,250,249,248,247,246,245,244,243,242,241,240,239,238,237,236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221,220,219,218,217,216,215,214,213,212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197,196,195,194,193,192,191,190,189,188,187,186,185,184,183,182,181,180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,151,150,149,148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,133,132,131,130,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0] | |
QuPathViewer viewer = QuPathGUI.getInstance().getViewer() | |
ImageDisplay display = viewer.getImageDisplay() | |
display.selectedChannels().each { channel -> | |
if (channel instanceof DirectServerChannelInfo) { | |
DirectServerChannelInfo directInfo = (DirectServerChannelInfo) channel | |
int color = directInfo.getColor() | |
int r = (color & 0xff0000) >> 16 | |
int g = (color & 0x00ff00) >> 8 | |
int b = (color & 0x0000ff) | |
// Create invertedLUT (depending on the original channel color) | |
for (int i = 0; i < 256; i++) { | |
rb[i] = (byte) invLUT[i]/255*r | |
gb[i] = (byte) invLUT[i]/255*g | |
bb[i] = (byte) invLUT[i]/255*b | |
} | |
directInfo.cm = new IndexColorModel(8, 256, rb, gb, bb); | |
} | |
} | |
viewer.getImageRegionStore().clearCache(false, false) | |
viewer.repaintEntireImage() | |
println('Done!') |
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
/** | |
* Set a MeasurementMapper in QuPath v0.1.2 to control the display in a script. | |
* | |
* Created for https://groups.google.com/d/msg/qupath-users/9kMNlg4sgAs/dUHQpROLDwAJ | |
* | |
* @author Pete Bankhead | |
*/ | |
import qupath.lib.gui.helpers.MeasurementMapper | |
import static qupath.lib.scripting.QPEx.* | |
// Define measurement & display range | |
def name = "Nucleus/Cell area ratio" // Set to null to reset | |
double minValue = 0.0 | |
double maxValue = 1.0 | |
// Request current viewer & objects | |
def viewer = getCurrentViewer() | |
def options = viewer.getOverlayOptions() | |
def detections = getDetectionObjects() | |
// Update the display | |
if (name) { | |
print String.format('Setting measurement map: %s (%.2f - %.2f)', name, minValue, maxValue) | |
def mapper = new MeasurementMapper(name, detections) | |
mapper.setDisplayMinValue(minValue) | |
mapper.setDisplayMaxValue(maxValue) | |
options.setMeasurementMapper(mapper) | |
} else { | |
print 'Resetting measurement map' | |
options.setMeasurementMapper(null) | |
} |
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
// from https://forum.image.sc/t/problem-running-script-from-publication/35542/7 | |
//0.2.0 | |
import qupath.lib.gui.tools.* | |
// Print the names (just to check which you want) | |
println MeasurementMapper.loadDefaultColorMaps() | |
// Choose one of them | |
def colorMapper = MeasurementMapper.loadDefaultColorMaps().find {it.getName() == 'Magma'} | |
// Create a measurement mapper | |
def detections = getDetectionObjects() | |
def measurementMapper = new MeasurementMapper(colorMapper, 'Nucleus: DAB OD mean', detections) | |
// Show the measurement mapper in the current viewer | |
def viewer = getCurrentViewer() | |
def overlayOptions = viewer.getOverlayOptions() | |
overlayOptions.setMeasurementMapper(measurementMapper) |
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
//0.1.2 - This is much easier to do through the interface or scripting in 0.2.0 | |
//To use in 0.2.0 swap the ColorToolsFX import to import qupath.lib.gui.tools.ColorToolsFX | |
import javafx.application.Platform | |
import javafx.beans.property.SimpleLongProperty | |
import javafx.geometry.Insets | |
import javafx.scene.Scene | |
import javafx.geometry.Pos | |
import javafx.scene.control.Button | |
import javafx.scene.control.Label | |
import javafx.scene.control.TextField | |
import javafx.scene.control.ColorPicker | |
import javafx.scene.control.ComboBox | |
import javafx.scene.control.TableColumn | |
import javafx.scene.layout.BorderPane | |
import javafx.scene.layout.GridPane | |
import javafx.scene.control.Tooltip | |
import javafx.stage.Stage | |
import qupath.lib.gui.QuPathGUI | |
import qupath.lib.gui.helpers.ColorToolsFX; | |
import javafx.scene.paint.Color; | |
int col = 0 | |
int row = 0 | |
int textFieldWidth = 120 | |
int labelWidth = 150 | |
def gridPane = new GridPane() | |
gridPane.setPadding(new Insets(10, 10, 10, 10)); | |
gridPane.setVgap(2); | |
gridPane.setHgap(10); | |
def titleLabel = new Label("Alter the color and name of a class of objects") | |
gridPane.add(titleLabel,col, row++, 3, 1) | |
def requestLabel = new Label("Original Name") | |
gridPane.add(requestLabel,col++, row, 1, 1) | |
def requestLabel2 = new Label("New Name") | |
gridPane.add(requestLabel2,col++, row, 1, 1) | |
def requestLabel3 = new Label("New Color") | |
gridPane.add(requestLabel3,col++, row++, 1, 1) | |
//new row | |
col = 0 | |
//generate a list of all in-use classes | |
Set classList = [] | |
for (object in getAllObjects().findAll{it.isDetection() || it.isAnnotation()}) { | |
classList << object.getPathClass() | |
} | |
//place all classes in a combobox | |
def ComboBox classText = new ComboBox(); | |
classList.each{classText.getItems().add(it)} | |
gridPane.add(classText, col++, row, 1, 1) | |
def TextField classText2 = new TextField("MyNewClass"); | |
classText2.setMaxWidth( textFieldWidth); | |
classText2.setAlignment(Pos.CENTER_RIGHT) | |
gridPane.add(classText2, col++, row, 1, 1) | |
def colorPicker = new ColorPicker() | |
gridPane.add(colorPicker, col, row++, 1, 1) | |
//ArrayList<Label> channelLabels | |
Button startButton = new Button() | |
startButton.setText("Alter Class") | |
gridPane.add(startButton, 0, row++, 1, 1) | |
//startButton.setTooltip(new Tooltip("If you need to change the number of classes, re-run the script")); | |
startButton.setOnAction { | |
changeList = getAllObjects().findAll{it.getPathClass() == getPathClass(classText.getValue().toString())} | |
changeList.each{ | |
it.setPathClass(getPathClass(classText2.getText())) | |
newClass = getPathClass(classText2.getText()) | |
newClass.setColor(ColorToolsFX.getRGBA(colorPicker.getValue())) | |
} | |
fireHierarchyUpdate() | |
} | |
//Some stuff that controls the dialog box showing up. I don't really understand it but it is needed. | |
Platform.runLater { | |
def stage = new Stage() | |
stage.initOwner(QuPathGUI.getInstance().getStage()) | |
stage.setScene(new Scene( gridPane)) | |
stage.setTitle("Class editor") | |
stage.setWidth(450); | |
stage.setHeight(200); | |
//stage.setResizable(false); | |
stage.show() | |
} |
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
//Objective: A quicker way to show only certain classes and hide all others | |
//ANY GROUP CLASS CHECKING or UNCHECKED OVERWRITE ANY SINGLE CLASS CHANGES | |
//Written for 0.2.0 and 0.3.0, code fix by @Mark_Zaidi and @petebankhead regarding access to base class names | |
//Removed tokenization, ending backwards compatability with 0.1.2 multiplexing. | |
//Find all classifications of detections | |
/***************************************** | |
If you have subcellular objects, you may want | |
to change this to getDetectionObjects() so that | |
you can turn those on and off as well | |
*****************************************/ | |
def classifications = new ArrayList<>( getCellObjects().collect {it?.getPathClass()} as Set) | |
///////////////////////////////////////////////////////////// | |
List<String> classNames = new ArrayList<String>() | |
classifications.each{ | |
classNames<< it.toString() | |
} | |
Set baseClasses = [] | |
classifications.each{ | |
getCurrentViewer().getOverlayOptions().hiddenClassesProperty().add(it) | |
PathClassTools.splitNames(it).each{str-> | |
baseClasses << str.trim() | |
} | |
} | |
print baseClasses | |
baseList = baseClasses | |
//Find strings with duplicates in baseClasses | |
//baseList = baseClasses.countBy{it}.grep{it.value > 1}.collect{it.key} | |
//Set up GUI | |
int col = 0 | |
int row = 0 | |
int textFieldWidth = 120 | |
int labelWidth = 150 | |
def gridPane = new GridPane() | |
gridPane.setPadding(new Insets(10, 10, 10, 10)); | |
gridPane.setVgap(2); | |
gridPane.setHgap(10); | |
ScrollPane scrollPane = new ScrollPane(gridPane) | |
scrollPane.setFitToHeight(true); | |
BorderPane border = new BorderPane(scrollPane) | |
border.setPadding(new Insets(15)); | |
//Separately set up a checkbox for All classes | |
allOn = new CheckBox("All") | |
allOn.setId("All") | |
gridPane.add( allOn, 1, row++, 1,1) | |
row = 1 | |
ArrayList<CheckBox> boxes = new ArrayList(classifications.size()); | |
//Create the checkboxes for each class | |
for (i=0; i<classifications.size();i++){ | |
name = classNames[i].toString() | |
if (name == "null"){name = "None"} | |
cb = new CheckBox(name) | |
cb.setId(name) | |
boxes.add(cb) | |
gridPane.add( cb, col, row++, 1,1) | |
} | |
//Create checkboxes for base classes, defined as some string that showed up in more than one class entry | |
ArrayList<CheckBox> baseBoxes = new ArrayList(baseList.size()); | |
row = 2 | |
for (i=0; i<baseList.size();i++){ | |
cb = new CheckBox(baseList[i]) | |
cb.setId(baseList[i]) | |
baseBoxes.add(cb) | |
gridPane.add( cb, 1, row++, 1,1) | |
} | |
//behavior for all single class checkboxes | |
//I can't seem to check which checkbox is selected when they are created dynamically, so the results are updated for all classes | |
for (c in boxes){ | |
c.selectedProperty().addListener({o, oldV, newV -> | |
firstCol = gridPane.getChildren().findAll{gridPane.getColumnIndex(it) == 0} | |
for (n in firstCol){ | |
if (n.isSelected()){ | |
getCurrentViewer().getOverlayOptions().hiddenClassesProperty().remove(getPathClass(n.getId())) | |
}else {getCurrentViewer().getOverlayOptions().hiddenClassesProperty().add(getPathClass(n.getId()))} | |
} | |
} as ChangeListener) | |
} | |
//behavior for base class checkboxes | |
//I can't easily figure out which checkbox was last checked, so this overwrites any single class checkboxes that were selected or unselected | |
for (c in baseBoxes){ | |
c.selectedProperty().addListener({o, oldV, newV -> | |
//verify that we are in the second column, and the nodes are selected | |
secondColSel = gridPane.getChildren().findAll{gridPane.getColumnIndex(it) == 1 && it.isSelected()} | |
secondColUnSel = gridPane.getChildren().findAll{gridPane.getColumnIndex(it) == 1 && !it.isSelected()} | |
for (n in secondColUnSel){ | |
batch = gridPane.getChildren().findAll{gridPane.getColumnIndex(it) == 0 && it.getId().contains(n.getId())} | |
batch.each{ | |
it.setSelected(false) | |
getCurrentViewer().getOverlayOptions().hiddenClassesProperty().add(getPathClass(it.getId())) | |
} | |
} | |
for (n in secondColSel){ | |
batch = gridPane.getChildren().findAll{gridPane.getColumnIndex(it) == 0 && it.getId().contains(n.getId())} | |
batch.each{ | |
it.setSelected(true) | |
getCurrentViewer().getOverlayOptions().hiddenClassesProperty().remove(getPathClass(it.getId())) | |
} | |
} | |
} as ChangeListener) | |
} | |
//Turn all on or off based on the All checkbox | |
allOn.selectedProperty().addListener({o, oldV, newV -> | |
if (!allOn.isSelected()){ | |
classifications.each{ | |
getCurrentViewer().getOverlayOptions().hiddenClassesProperty().add(it) | |
} | |
gridPane.getChildren().each{ | |
it.setSelected(false) | |
} | |
}else { | |
classifications.each{ | |
getCurrentViewer().getOverlayOptions().hiddenClassesProperty().remove(it) | |
} | |
gridPane.getChildren().each{ | |
it.setSelected(true) | |
} | |
} | |
}as ChangeListener) | |
//Some stuff that controls the dialog box showing up. I don't really understand it but it is needed. | |
Platform.runLater { | |
def stage = new Stage() | |
stage.initOwner(QuPathGUI.getInstance().getStage()) | |
stage.setScene(new Scene( border)) | |
stage.setTitle("Select classes to display") | |
stage.setWidth(800); | |
stage.setHeight(500); | |
stage.setResizable(true); | |
stage.show() | |
} | |
import javafx.application.Platform | |
import javafx.geometry.Insets | |
import javafx.scene.Scene | |
import javafx.geometry.Pos | |
import javafx.scene.control.TableView | |
import javafx.scene.control.CheckBox | |
import javafx.scene.layout.BorderPane | |
import javafx.scene.layout.GridPane | |
import javafx.scene.control.ScrollPane | |
import javafx.scene.layout.BorderPane | |
import javafx.stage.Stage | |
import javafx.scene.input.MouseEvent | |
import javafx.beans.value.ChangeListener | |
import qupath.lib.gui.QuPathGUI | |
import static qupath.lib.gui.scripting.QPEx.getCurrentViewer |
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
//The information in this script could be generalized to alter the color for any object list, or any individual object (see Selecting things Gist) | |
//Another way to use it might be to create a list of names ["blue","green","red"] along with a list/map of groups of three values | |
// [[0,0,200],[0,200,0],[200,0,0]] and pull from each, which would create a predetermined rainbow of named objects | |
//0.1.2 and 0.2.0 | |
def annotations = getAnnotationObjects() | |
for (i = 0; i<annotations.size(); i++){ | |
def j = i.mod(255) | |
//modulus used to keep the RGB values in the 0-255 range | |
annotations[i].setColorRGB(getColorRGB(255-j,j, j)) | |
} | |
print "done" |
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
//For earlier versions of QuPath, tested in 0.1.3 | |
//Create a detection object the same size and shape as the TMA core | |
//Give it summary measurements for the percentage of cells of each class within the core | |
//When one of the Class % measurements is selected while viewing Measure->Measurement Maps, all other detections will disappear | |
//and only the summary detection objects will be visible. | |
//It may be best to turn off annotation visibility. | |
import qupath.lib.objects.PathDetectionObject | |
import qupath.lib.objects.PathCellObject | |
hierarchy = getCurrentHierarchy() | |
cores = hierarchy.getTMAGrid().getTMACoreList() | |
Set list = [] | |
for (object in getAllObjects().findAll{it.isDetection() /*|| it.isAnnotation()*/}) { | |
list << object.getPathClass().toString() | |
} | |
print list | |
print "before cores" | |
cores.each { | |
print "initiating core" | |
//Find the cell count in this core | |
total = hierarchy.getDescendantObjects(it, null, PathCellObject).size() | |
//Prevent divide by zero errors in empty TMA cores | |
if (total != 0){ | |
for (className in list) { | |
cellType = hierarchy.getDescendantObjects(it,null, PathCellObject).findAll{it.getPathClass() == getPathClass(className)}.size() | |
it.getMeasurementList().putMeasurement(className+" cell %", cellType/(total)*100) | |
} | |
} | |
else { | |
for (className in list) { | |
it.getMeasurementList().putMeasurement(className+" cell %", 0) | |
} | |
} | |
fireHierarchyUpdate() | |
print "core complete" | |
} | |
cores.each { | |
print it | |
roi = it.getROI() | |
coreName = it.getName()+" - Tile" | |
def detection = new PathDetectionObject(roi, getPathClass("Tile")) | |
hierarchy.addPathObject(detection, false) | |
ml = it.getMeasurementList() | |
for (i=0;i<ml.size(); i++){ | |
detection.getMeasurementList().putMeasurement(ml.getMeasurementName(i), measurement(it, ml.getMeasurementName(i))) | |
} | |
fireHierarchyUpdate() | |
} | |
println("Are you done yet?") |
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
// Add percentages by cell class to each TMA core | |
//Confirmed for 0.2.0 | |
print "Wait for the comment indicating that it is done!" | |
print "This process is slow." | |
import qupath.lib.objects.PathCellObject | |
def metadata = getCurrentImageData().getServer().getOriginalMetadata() | |
def pixelSize = metadata.pixelCalibration.pixelWidth.value | |
hierarchy = getCurrentHierarchy() | |
cores = hierarchy.getTMAGrid().getTMACoreList() | |
Set list = [] | |
for (object in getAllObjects().findAll{it.isDetection() /*|| it.isAnnotation()*/}) { | |
list << object.getPathClass() | |
} | |
print list | |
def cellList = [] | |
cores.each { | |
//Find the cell count in this core | |
cellList = [] | |
cellList = qupath.lib.objects.PathObjectTools.getDescendantObjects(it, cellList, PathCellObject) | |
total = cellList.size() | |
//Prevent divide by zero errors in empty TMA cores | |
if (total != 0){ | |
annos=it.getChildObjects()[0] | |
if (annos.isAnnotation()){ | |
for (className in list) { | |
cellType = cellList.findAll{p->p.getPathClass() == className}.size() | |
annotationArea = annos.getROI().getArea()*pixelSize*pixelSize/1000000 | |
println(cellType) | |
println(annotationArea) | |
it.getMeasurementList().putMeasurement(className.toString()+" cells/mm^2", cellType/(annotationArea)) | |
} | |
} | |
for (className in list) { | |
cellType = cellList.findAll{p->p.getPathClass() == className}.size() | |
it.getMeasurementList().putMeasurement(className.toString()+" cell %", cellType/(total)*100) | |
} | |
} | |
else { | |
for (className in list) { | |
it.getMeasurementList().putMeasurement(className.toString()+" cell %", 0) | |
} | |
} | |
} | |
import qupath.lib.objects.PathDetectionObject | |
cores.each { | |
roi = it.getROI() | |
coreName = it.getName()+" - Tile" | |
def detection = new PathDetectionObject(roi, getPathClass("Tile")) | |
hierarchy.addPathObject(detection) | |
ml = it.getMeasurementList() | |
for (i=0;i<ml.size(); i++){ | |
//println(ml.getMeasurementValue(i)) | |
//println(detection) | |
detection.getMeasurementList().putMeasurement(ml.getMeasurementName(i), measurement(it, ml.getMeasurementName(i))) | |
} | |
fireHierarchyUpdate() | |
} | |
println("Now it is done.") |
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
//0.2.0 | |
// https://forum.image.sc/t/qupath-toggle-show-hide-detections-annotations-by-scripting/45916/4 | |
def overlayOptions = getCurrentViewer().getOverlayOptions() | |
overlayOptions.hiddenClassesProperty().addAll( | |
getPathClass('Tumor'), | |
getPathClass('Stroma') | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment