Skip to content

Instantly share code, notes, and snippets.

@lacan
Last active July 30, 2023 14:32
Show Gist options
  • Save lacan/d284ef91160f9242ab8ce3a65b72cbd6 to your computer and use it in GitHub Desktop.
Save lacan/d284ef91160f9242ab8ce3a65b72cbd6 to your computer and use it in GitHub Desktop.
[QuPath Script To Export Annotations For Stardist] this script will take rectangular annotations in QuPath and export them and their containing objects as images and masks for deep neural netowrk training #qupath #stardist #BIOP
/*
// ABOUT
Exports Annotations for StarDist (Or other Deep Learning frameworks)
// INPUTS
You need rectangular annotations that have classes "Training" and "Validation"
After you have placed these annotations, lock them and start drawing the objects inside
// OUTPUTS
----------
The script will export each annotation and whatever is contained within as an image-label pair
These will be placed in the folder specified by the user in the main project directory.
Inside that directory, you will find 'train' and 'test' directories that contain the images with
class 'Training' and 'Validation', respectively.
Inside each, you will find 'images' and 'masks' folders containing the exported image and the labels,
respectively. The naming convention was chosen to match the one used for the StarDist DSBdataset
//PARAMETERS
------------
- channel_of_interest: You can export a single channel or all of them, currently no option for _some_ channels only
- downsample: you can downsample your image in case it does not make sense for you to train on the full resolution
- export_directory: name of the directory which will contain the 'train' and 'test' subdirectories
Authors: Olivier Burri, Romain Guiet, BioImaging & Optics Platform (EPFL BIOP)
Tested on QuPath 0.2.3, October 21st 2020
Due to the simple nature of this code, no copyright is applicable
*/
// USER SETTINGS
def channel_of_interest = 1 // null to export all the channels
def downsample = 1
// START OF SCRIPT
println("Image: "+getProjectEntry().getImageName())
def training_regions = getAnnotationObjects().findAll { it.getPathClass() == getPathClass("Training") }
def validation_regions = getAnnotationObjects().findAll { it.getPathClass() == getPathClass("Validation") }
if (training_regions.size() > 0 ) saveRegions( training_regions, channel_of_interest, downsample, 'train')
if (validation_regions.size() > 0 ) saveRegions( validation_regions, channel_of_interest, downsample, 'test')
println "done"
def saveRegions( def regions, def channel, def downsample, def type ) {
// Randomize names
def is_randomized = getProject().getMaskImageNames()
getProject().setMaskImageNames(true)
def rm = RoiManager.getRoiManager() ?: new RoiManager()
// Get the image name
def image_name = getProjectEntry().getImageName()
regions.eachWithIndex{ region, region_idx ->
println("Processing Region #"+( region_idx + 1 ) )
def file_name = image_name+"_r"+( region_idx + 1 )
imageData = getCurrentImageData();
server = imageData.getServer();
viewer = getCurrentViewer();
hierarchy = getCurrentHierarchy();
pathImage = null;
request = RegionRequest.createInstance(imageData.getServerPath(), downsample, region.getROI())
pathImage = IJExtension.extractROIWithOverlay(server, region, hierarchy, request, false, viewer.getOverlayOptions());
image = pathImage.getImage()
//println("Image received" )
//image.show()
// Create the Labels image
def labels = IJ.createImage( "Labels", "16-bit black", image.getWidth(), image.getHeight() ,1 );
rm.reset()
IJ.run(image, "To ROI Manager", "")
def rois = rm.getRoisAsArray() as List
//println("Creating Labels" )
def label_ip = labels.getProcessor()
def idx = 0
print( "Ignoring Rectangles: " )
rois.each{ roi ->
if (roi.getType() == Roi.RECTANGLE) {
print( "." )
} else {
label_ip.setColor( ++idx )
label_ip.setRoi( roi )
label_ip.fill( roi )
}
}
print("\n")
labels.setProcessor( label_ip )
//labels.show()
// Split to keep only channel of interest
def output = image
if ( channel != null ){
imp_chs = ChannelSplitter.split( image )
output = imp_chs[ channel - 1 ]
}
saveImages(output, labels, file_name, type)
//println( file_name + " Image and Mask Saved." )
// Save some RAM
output.close()
labels.close()
image.close()
}
// Return Project setup as it was before
getProject().setMaskImageNames( is_randomized )
}
// This will save the images in the selected folder
def saveImages(def images, def labels, def name, def type) {
def source_folder = new File ( buildFilePath( PROJECT_BASE_DIR, 'ground_truth', type, 'images' ) )
def target_folder = new File ( buildFilePath( PROJECT_BASE_DIR, 'ground_truth', type, 'masks' ) )
mkdirs( source_folder.getAbsolutePath() )
mkdirs( target_folder.getAbsolutePath() )
IJ.save( images , new File ( source_folder, name ).getAbsolutePath()+'.tif' )
IJ.save( labels , new File ( target_folder, name ).getAbsolutePath()+'.tif' )
}
// Manage Imports
import qupath.lib.roi.RectangleROI
import qupath.imagej.gui.IJExtension;
import ij.IJ
import ij.gui.Roi
import ij.plugin.ChannelSplitter
import ij.plugin.frame.RoiManager
@frankcoumans
Copy link

Thank you for sharing this script!

When I run it on QuPath 0.2.3 the script exports all the files in my project, but each gets the masks for the currently opened image (or an empty mask if no image is open).

@its-jd
Copy link

its-jd commented Jan 14, 2021

Hi, did you find the solution?,

I am also facing the same problem. All of my label images are empty (all black)

@frankcoumans
Copy link

frankcoumans commented Jan 14, 2021 via email

@lacan
Copy link
Author

lacan commented Jan 14, 2021

This is quite strange. I noticed that there is a line missing
request = RegionRequest.createInstance(imageData.getServerPath(), downsample, region.getROI())
I guess QuPath creates a request by default for the current image.

Hopefully this was causing the error.

Please try now and let me know.

Best

Oli

@frankcoumans
Copy link

That fixed it, thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment