Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save petebankhead/8a920b2160685a59075af26a209a2156 to your computer and use it in GitHub Desktop.
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
/**
* 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