1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 from libavg import Point2D
24 from eventList import EventList, Cursor
25 from mathutil import getDistSquared
26
27
28 MAX_ITERATIONS=50
29
34
36 return len(self.__members)==0
37
39 if len(self.__members) == 0:
40 return
41 pointSum = Point2D(0,0)
42 for point in self.__members:
43 pointSum += point.getPos()
44 center = pointSum / len(self.__members)
45 oldPos = self._pos
46 self.setPos(center)
47 return oldPos != self._pos
48
53
58
60 return member in self.__members
61
63 return len(self.__members)
64
66 return "centroid at %s, startpos %s, members %s" % (self._pos, self._startPos, self.__members)
67
68
70 """ implements a variant of k-means.
71 same API as EventList, with the difference that ClusteredEventList
72 will simulate a maximum of 2 cursors (if more actual cursors are
73 on the node, they are clustered.
74 In contrast to EventList, the callbacks provide no EventCursors.
75 """
76 - def __init__(self,
77 node,
78 source,
79 onDown = lambda x: None,
80 onUp = lambda x: None,
81 onMotion = lambda x: None,
82 resetMotion = lambda: None,
83 captureEvents = True):
84 self.__centroids = []
85 self.__centroidByEvent = {}
86 self.__doNewMotion = False
87
88 self.__callback = {
89 'onDown': onDown,
90 'onUp': onUp,
91 'onMotion': onMotion,
92 'resetMotion': resetMotion,
93 }
94 self.__eventList = EventList(
95 node = node,
96 source = source,
97 onDown = self.__onDown,
98 onUp = self.__onUp,
99 onMotion = self.__onMotion,
100 resetMotion = self.__resetMotion,
101 captureEvents = captureEvents)
102
105
107
108 centroids = list(self.__centroids)
109 self.calcClusters()
110 self.__resetMotion()
111 if len(centroids) != len(self.__centroids):
112 if len(centroids) and self.__centroids[0] == centroids[0]:
113 newCentroid = self.__centroids[1]
114 else:
115 newCentroid = self.__centroids[0]
116 self.__callback['onDown']()
117
118 - def __onUp(self, eventCursor):
119 assert eventCursor in self.__centroidByEvent
120 centroid = self.__centroidByEvent[eventCursor]
121 centroid.removeMember(eventCursor)
122 del self.__centroidByEvent[eventCursor]
123
124 self.calcClusters()
125 self.__resetMotion()
126
127 if len(centroid) == 0:
128 self.__callback['onUp']()
129
131 oldPositions = {}
132 for centroid in self.__centroids:
133 oldPositions[centroid] = centroid.getPos()
134 self.calcClusters()
135 self.__callback['onMotion']()
136
138 for centroid in self.__centroids:
139 centroid.resetMotion()
140 self.__callback['resetMotion']()
141
143 self.__centroids = []
144 self.__centroidByEvent = {}
145 self.__eventList.delete()
146 self.__callback = {
147 'onDown': lambda x: None,
148 'onUp': lambda x: None,
149 'onMotion': lambda x: None,
150 'resetMotion': lambda x: None,
151 }
152
154 """ returns True if a membership changed, else False."""
155 changed = False
156 if not len(self.__centroids):
157 return changed
158 for point in self.__eventList.getCursors():
159 closestCentroid = self.__centroids[0]
160 minDist = getDistSquared(point.getPos(), closestCentroid.getPos())
161 for centroid in self.__centroids:
162 distance = getDistSquared (point.getPos(), centroid.getPos())
163 if distance < minDist:
164 minDist = distance
165 closestCentroid = centroid
166 if not closestCentroid.hasMember(point):
167 self.__doNewMotion = True
168 if point in self.__centroidByEvent:
169 self.__centroidByEvent[point].removeMember(point)
170 self.__centroidByEvent[point] = closestCentroid
171 closestCentroid.addMember(point)
172 changed = True
173 return changed
174
176 def __calcInitialCentroids():
177 self.__centroidByEvent = {}
178 self.__centroids = []
179 def createCentroid(point):
180 centroid = Centroid()
181 centroid.addMember(point)
182 self.__centroidByEvent[point] = centroid
183 self.__centroids.append(centroid)
184
185 maxDist = 0
186 points = None
187 if len(self.__eventList)>1:
188 cursors = self.__eventList.getCursors()
189 for p in cursors:
190 for q in cursors:
191 dist = getDistSquared(p.getPos(),q.getPos())
192 if dist >= maxDist and p != q:
193 points = p,q
194 maxDist = dist
195
196 assert(points)
197 for point in points:
198 createCentroid(point)
199 elif len(self.__eventList) == 1:
200 createCentroid(self.__eventList.getCursors()[0])
201
202 def __setCentroids():
203 changed = False
204 for centroid in self.__centroids:
205 if centroid.reposition():
206 changed = True
207 return changed
208
209 if not len(self.__centroids):
210 __calcInitialCentroids()
211 self.__calcMemberships()
212
213 changed = True
214 iterations = 0
215 while changed:
216 changed = False
217 if __setCentroids():
218 changed = True
219 if self.__calcMemberships():
220 changed = True
221 iterations+=1
222 if iterations>MAX_ITERATIONS:
223
224 __setCentroids()
225 break
226
228 def __hasBrokenCentroids():
229 if len(self.__eventList)>1 and len(self.__centroids)!=2:
230 return True
231 for centroid in self.__centroids:
232 if centroid.isBroken():
233 return True
234 if len(self.__centroids)==2:
235 if self.__centroids[0].getPos() == self.__centroids[1].getPos():
236 return True
237 return False
238
239 self.__tryCalcClusters()
240
241 if __hasBrokenCentroids():
242
243 self.__centroids=[]
244 self.__tryCalcClusters()
245 if __hasBrokenCentroids():
246 print "ignoring fatal error: cannot fix broken centroids: %s" % self.__centroids
247 print "self.__eventList =%s " % self.__eventList
248 if self.__doNewMotion:
249 self.__doNewMotion = False
250 self.__resetMotion()
251
253 return self.__centroids
254
256 return min(len(self.__eventList), len(self.__centroids))
257