-
Notifications
You must be signed in to change notification settings - Fork 1
/
DBSCAN.groovy
141 lines (115 loc) · 5.08 KB
/
DBSCAN.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// DBScan implementation for QuPath 0.2.0 - Michael Nelson, May 2020
// Based heavily on code from https://bhanuchander210.github.io/Tutorial-Machine-Learning-With-Groovy/
// With major suggestion from Sara Mcardle
// Instigated by Colt Egelston
// Version 2.0 Added cluster size as a measurement
//Probably should read this stuff the second time after it doesn't work quite right the first time.
////////////////////////////////////////////////////////////////////////////////
//micronsBetweenCentroids (which is converted into "eps" and minPts to adjust the behavior of the clustering.
//eps and minPts are as described in the DBSCAN wiki
//baseClasses to true if you want to ignore complex classes and use subclasses from multiplexing classifications
/////////////////////////////////////////////////////////////////////////////////
import org.apache.commons.math3.ml.clustering.DBSCANClusterer
import org.apache.commons.math3.ml.clustering.DoublePoint
//distance to search around an object for another centroid.
double micronsBetweenCentroids = 30.0
//Minimum number of objects needed to be considered a cluster
int minPts = 5
boolean baseClasses = false
double eps = micronsBetweenCentroids/getCurrentServer().getPixelCalibration().getPixelWidthMicrons()
print eps
//Get the classes you want to analyze. Avoids Negative and no class by default.
Set classSet = []
List classList = []
if (!baseClasses){
for (object in getCellObjects()) {
c = object.getPathClass()
if (c != getPathClass("Negative")){
classSet << c
}
}
classList.addAll(classSet.findAll{
//If you only want one class, use it == getPathClass("MyClassHere") instead
it != null
})
print classList
}else{
for (object in getCellObjects()) {
parts = PathClassTools.splitNames(object.getPathClass())
parts.each{
if (it != "Negative"){
classSet << it
}
}
}
classList.addAll(classSet.findAll{
//If you only want one sub-class, use it == getPathClass("MyClassHere") instead
it != null
})
}
classList.each{ c->
//Storage for stuff we do later. points will hold the XY coordinates as DoublePoint objects
List<DoublePoint> points = new ArrayList<DoublePoint>()
//The Map allows us to use the DoublePoint to match the list of coordinates output by DBScan to the QuPath object
Map< DoublePoint, double> pointMap = [:]
//Get the objects of interest for this class or sub-class
if(baseClasses){
batch = getDetectionObjects().findAll{it.getPathClass().toString().contains(c)}
text = c
}else{
batch = getDetectionObjects().findAll{it.getPathClass() == c}
text = c.getName()
}
//print batch.size()
//Prep each object being analyzed for clustering.
batch.eachWithIndex{d,x->
//create the unique identifier, if you want to look back at details
//d.getMeasurementList().putMeasurement("ID",(double)x)
//Reset previous cluster analyses for the given cell
d?.getMeasurementList().removeMeasurements("Cluster "+text)
//create the linker between the ID and the centroid
double [] point = [d.getROI().getCentroidX(), d.getROI().getCentroidY()]
DoublePoint dpoint = new DoublePoint(point)
//print dpoint
points[x] = dpoint
//Key point here, each index (cell in most cases) is tracked and matched to it's XY coordinates
pointMap[dpoint]= (double)x
}
//print points if you want to see them all
def showClosure = {detail ->
//println "Cluster : " + detail.cluster + " Point : " + detail.point + " Label : "+ detail.labels
//print "labels "+(int)detail.labels
//print "cluster"+detail.cluster
//this uses the label (the index from the "batch") to access the correct cell, and apply a measurement with the correct cluster number
batch[detail.labels]?.getMeasurementList()?.putMeasurement("Cluster "+text,detail.cluster )
batch[detail.labels]?.getMeasurementList()?.putMeasurement("Cluster Size "+text,detail.clusterSize )
}
//Main run statements
DBSCANClusterer DBScan = new DBSCANClusterer(eps, minPts)
collectDetails(DBScan.cluster(points), pointMap).each(showClosure)
}
print "Done!"
//Things from the website linked at the top that I messed with very little.
//Used to extract information from the result of DBScan, might be useful if I play with other kinds of clustering in the future.
List<ClusterDetail> collectDetails(def clusters, pointMap)
{
List<ClusterDetail> ret = []
clusters.eachWithIndex{ c, ci ->
c.getPoints().each { pnt ->
DoublePoint pt = pnt as DoublePoint
ret.add new ClusterDetail (ci +1 as Integer, pt, pointMap[pnt], c.getPoints().size())
}
}
ret
}
class ClusterDetail
{
int cluster
DoublePoint point
double labels
int clusterSize
ClusterDetail(int no, DoublePoint pt, double labs, int size)
{
cluster = no; point= pt; labels = labs; clusterSize = size
}
}