Created
May 10, 2021 06:32
-
-
Save petebankhead/8a920b2160685a59075af26a209a2156 to your computer and use it in GitHub Desktop.
Set IDs for cells in QuPath v0.2 / v0.3, so cells can be identified after export/import
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
/** | |
* Assign an ID to cells in QuPath using several different methods. | |
* This is useful if you want to export cell data from QuPath, process it elsewhere, | |
* and then bring the results back into QuPath again. | |
* | |
* Note: After checking the advantages/disadvantages, you should only need to use | |
* one of these. If it fits with your uses, my preference would be 3 or 4. | |
*/ | |
// Get the cell objects (all options start here) | |
// Note: this isn't guaranteed to always return cells in the same order | |
def cells = getCellObjects() | |
/* | |
* OPTION 1: Add a new measurement with an integer value | |
* Advantage: Simple, the ID is just a number (like other measurements). | |
* Disadvantage: Not guaranteed to give the same ID each time you run the script: | |
* if more/fewer cells are detected, or getCellObjects() provides them | |
* in a different order, then the numbers associated with each cell will be different. | |
* Possible risk of accidentally using ID as a feature in later classifiers. | |
*/ | |
cells.eachWithIndex { cell, idx -> | |
cell.getMeasurementList().putMeasurement('Cell ID', idx) | |
cell.getMeasurementList().close() | |
} | |
fireHierarchyUpdate() | |
/* | |
* OPTION 2: Set cell name using an integer value | |
* Advantage: Simple, no confusion between ID and measurements / features | |
* Disadvantage: Same ordering problem as with OPTION 1. | |
*/ | |
cells.eachWithIndex { cell, idx -> | |
cell.setName("Cell " + idx) | |
} | |
fireHierarchyUpdate() | |
/* | |
* OPTION 3: Set name based on a UUID if it doesn't already have a name. | |
* Advantage: UUID used for unique identifiers in many places, see | |
* https://en.wikipedia.org/wiki/Universally_unique_identifier | |
* Disadvantage: Same order problem as OPTION 1 - but by not overwriting existing | |
* names the risk of confusion is reduced. You can run the script | |
* multiple times and (provided non-UUID names haven't been set) | |
* IDs shouldn't be changed. | |
*/ | |
cells.eachWithIndex { cell, idx -> | |
if (!cell.getName()) // Conceivably could check for a UUID name, not just any name | |
cell.setName(UUID.randomUUID().toString()) | |
} | |
fireHierarchyUpdate() | |
/* | |
* OPTION 4: Set name based on the cell centroid | |
* Advantage: This should be reproducible (independent of order), and encodes extra | |
* info that can relate cells back to the original image even in the | |
* absence of QuPath. | |
* Disadvantage: Cells *could* have duplicate names - but this is very unlikely | |
* unless an error has been made elsewhere (e.g. detecting cells twice). | |
* The sanity check for this is arguably an advantage. | |
* IDs aren't numbers, which might complicate their use in other software. | |
*/ | |
cells.each { cell -> | |
def roi = PathObjectTools.getROI(cell, true) | |
String name = String.format("Cell (%.1f, %.1f)", roi.getCentroidX(), roi.getCentroidY()) | |
cell.setName(name) | |
} | |
// Quick sanity check to ensure names are unique | |
def names = cells.collect {cell -> cell.getName()} as Set | |
if (names.size() != cells.size()) | |
println "WARNING! There are ${names.size()} unique names for ${cells.size()} cells" | |
fireHierarchyUpdate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment