Skip to content

Instantly share code, notes, and snippets.

@adyprat
Last active January 3, 2024 14:48
Show Gist options
  • Save adyprat/1903bc182fc3b6ac4ac8df5239d3dd82 to your computer and use it in GitHub Desktop.
Save adyprat/1903bc182fc3b6ac4ac8df5239d3dd82 to your computer and use it in GitHub Desktop.
Export annotation rendered ROI script with channel names and scale bar for QuPath v0.5.0
import qupath.lib.gui.images.servers.RenderedImageServer
import qupath.lib.gui.viewer.overlays.HierarchyOverlay
import qupath.lib.gui.viewer.Scalebar
import qupath.lib.gui.QuPathGUI
import qupath.lib.gui.viewer.OverlayOptions
import qupath.lib.regions.*
import ij.*
import java.awt.Color
import java.awt.image.BufferedImage
import qupath.lib.roi.RectangleROI
import qupath.fx.dialogs.*
import java.awt.Font
//CUSTOM: change this to 1.0 for original resolution -- may be slow depending on the ROI size
double downsample=1
def imageData = getCurrentImageData()
def viewer = getCurrentViewer()
def server = new RenderedImageServer.Builder(imageData)
.display(viewer.getImageDisplay())
.downsamples(downsample)
.layers(new HierarchyOverlay(viewer.getImageRegionStore(), viewer.getOverlayOptions(), imageData))
.build()
def roi = getSelectedROI()
// change this to value to a larger number if the yellow box from the original ROI still shows up
def removeBox = 10
// HACK! make a new ROI within the first one to remove the yellow border from showing up
// will lost about 10 pixels but should be fine
roi2 = new RectangleROI(roi.x+removeBox , roi.y+removeBox , roi.getBoundsWidth()-removeBox*2, roi.getBoundsHeight()-removeBox*2)
def requestROI = RegionRequest.createInstance(server.getPath(), downsample, roi2)
def img = server.readRegion(requestROI)
//find the channels and keep them in the starting order (C1-CX)
def imageDisplay = viewer.getImageDisplay()
def availableChannels = imageDisplay.availableChannels()
def channels = imageDisplay.selectedChannels()
def sortedChannels = channels.sorted((c1, c2) -> {
// Compare in a better way here...
int i1 = availableChannels.indexOf(c1)
int i2 = availableChannels.indexOf(c2)
return Integer.compare(i1, i2)
})
def img2 = new ImagePlus("Image", img)//.show()
def imY = img2.getHeight()
def imX = img2.getWidth()
int fScale = (imX+imY)/2//Math.max(imY,imX)
// CUSTOM: Change this to a higher value to get smaller font.
// Essentially sets the font size to 1/ScaleFactor of the image size
// TODO: may be there are better ways to do this?
def ScaleFactor = 45
def fSize = (fScale/ScaleFactor).round() as int
Font font = new Font("Calibri", Font.PLAIN,fSize);
print(font)
ij.process.ImageProcessor ip = img2.getProcessor();
// Change this if the text is too close to origin
ij.Prefs.setTransparentIndex(50)
def offsetDefault = 5
def offsetX = offsetDefault
def offset = offsetDefault
def bxheight = []
for (x in sortedChannels){
def y = x
if (x==sortedChannels[sortedChannels.size()-1]){
y = x.toString().split('\\(')[0].toString()
}else{
y = x.toString().split('\\(')[0].toString() +' '
}
ip.setFont(font);
//ip.setAntialiasedText(true)
bxheight.add(ip.getStringBounds(y).getHeight().toInteger())
offsetX+= ip.getStringWidth(y)
}
Color bx = new Color(0,0,0)
//ip.setColor( bx)
//ip.fillRect(0, imY-bxheight.max()-10*offsetDefault, offsetX-2*offsetDefault, bxheight.max()-offsetDefault)
// To remove the black text box, comment lines 87-93
// Or remove ", Color.black" in line 92
def y = ''
for (x in sortedChannels){
ip.setFont(font);
ip.setColor(x.getColor());
y = x.toString().split('\\(')[0].toString() +' '
ip.drawString(y, offset, imY-offsetDefault*4, Color.black);
offset+= ip.getStringWidth(y)
}
println(sortedChannels)
offset=5
for (x in sortedChannels){
ip.setFont(font);
println()
bx = new Color(ColorTools.red(x.getColor()),ColorTools.green(x.getColor()),ColorTools.blue(x.getColor()))
ip.setColor(bx);
//ip.setColor(Color.WHITE);
println(x)
y = x.toString().split('\\(')[0].toString() +' '
ip.drawString(y, offset, imY-offsetDefault*4, );
offset+= ip.getStringWidth(y)
}
// SCALEBAR
def scLen = 0.1
def roundUp = { int x, int roundTo ->
def remainder = x%roundTo
if (remainder>0){
x+= (roundTo - remainder)
}
return x
}
def cal = server.getPixelCalibration()
Color bx2 = new Color(255,255,255,1)
int scaleRect = scLen*imX
// account for downsampling. the numbers are accurate if downsample=1.0
// for ds=2, the imX is half the size so need to multiply with downsample
int scaleValum = downsample*scLen*imX*cal.getPixelWidthMicrons()
println scaleValum
def scaleVal = 0
if (scaleValum<=50) {
println "Less than 50"
scaleVal = roundUp(scaleValum,10).toString()+ ' µm'
}else if (scaleValum>50 & scaleValum<=100) {
println "Less than 100"
scaleVal = roundUp(scaleValum,20).toString()+ ' µm'
} else {
println "Greater than than 100"
scaleVal = roundUp(scaleValum,50).toString()+ ' µm'
}
if (roundUp(scaleValum,50)>1000){
scaleVal = (roundUp(scaleValum,50)/1000).round().toString()+ ' mm'
}
ip.setColor(bx2)
//CUSTOM: To remove Scale bar comment ines 122-124
ip.fillRect(imX-scaleRect-2*offsetDefault, imY-offsetDefault*8, scaleRect, offsetDefault*2)
ip.setColor(Color.WHITE)
ip.drawString(scaleVal, imX-scaleRect-2*offsetDefault, imY-offsetDefault*10);
println(scaleVal)
//img2.show()
// By default, saves as a png
//CUSTOM: To change from png to something else, edit .png in line 128
fileName = FileChoosers.promptToSaveFile("Export to file", null,FileChoosers.createExtensionFilter("PNG image", ".png"))
println(fileName.toString())
IJ.save(img2,fileName.toString())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment