00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029 import roslib; roslib.load_manifest('projector_interface')
00030 import rospy
00031 from sensor_msgs.msg import CameraInfo, PointCloud2
00032 from std_msgs.msg import Empty, String as String, Duration
00033 from geometry_msgs.msg import PointStamped
00034 import image_geometry
00035 from projector_interface._point_cloud import read_points_np
00036 from projector_interface.treeable import TreeCircleInfo, GraphicsItemInfo
00037 from pr2_python.pointclouds import xyz_array_to_pointcloud2
00038 from projector_interface import srv
00039 import tf
00040
00041 import cv2
00042 import numpy as np
00043 from collections import deque
00044 from threading import RLock
00045 import sys
00046
00047
00048
00049 from PySide import QtGui, QtCore
00050
00051 from dynamic_reconfigure.server import Server as DynamicReconfigureServer
00052 from projector_interface.cfg import InterfaceConfig
00053
00054 import kdtree
00055
00056 from functools import partial
00057
00058
00059
00060
00061 X_OFFSET = 0
00062 Y_OFFSET = 0
00063
00064 CURSOR_RADIUS = 10
00065
00066 SAME_OBJ_THRESH = 0.03
00067
00068 class Colors:
00069 WHITE = QtGui.QColor(255, 255, 255)
00070 GREEN = QtGui.QColor( 0, 255, 0 )
00071 BLUE = QtGui.QColor( 0, 135, 189)
00072
00073 class Circler(QtGui.QGraphicsView):
00074 H = None
00075 POLYGON_PEN = QtGui.QPen(Colors.WHITE, 5)
00076 ACTIVE_POLYGON_PEN = QtGui.QPen(Colors.GREEN, 5)
00077 CLICKED_OBJECT_PEN = QtGui.QPen(Colors.BLUE, 5)
00078 CIRCLE_RADIUS = 150
00079
00080 objects = None
00081 projected_objects = None
00082 int_objects = None
00083 int_projected_objects = None
00084 click_loc = np.float64([-1, -1, -1])
00085 click_duration = rospy.Duration(1.0)
00086
00087 selected_pt = np.array([])
00088
00089 use_selected_thresh = True
00090
00091 cursor_pts = None
00092
00093 model = None
00094 object_header = None
00095 cursor_header = None
00096
00097 object_lock = RLock()
00098 intersected_lock = RLock()
00099 cursor_lock = RLock()
00100 polygon_lock = RLock()
00101
00102 polygons = dict()
00103 polygon_pens = dict()
00104
00105 key_handlers = dict()
00106
00107 click_stale = False
00108 config_inited = False
00109
00110 click = QtCore.Signal()
00111 polygonAdded = QtCore.Signal(srv.DrawPolygonRequest)
00112 polygonsCleared = QtCore.Signal()
00113 cursorMoved = QtCore.Signal(PointCloud2)
00114 objectsChanged = QtCore.Signal(PointCloud2)
00115 objectHighlighted = QtCore.Signal(srv.HilightObject)
00116 hilightsCleared = QtCore.Signal()
00117
00118 def keyPressEvent(self, e):
00119 for key, fn in self.key_handlers.items():
00120 if key == e.key():
00121 fn(e)
00122
00123 def addKeyHandler(self, key, fn):
00124 self.key_handlers[key] = fn
00125
00126 def escHandler(self, _):
00127 QtGui.QApplication.quit()
00128
00129 def initUI(self):
00130 self.resetClickedObject()
00131 self.resetActiveObject()
00132 self.active_poly = GraphicsItemInfo(QtGui.QGraphicsPolygonItem(), -1, label='\x00')
00133 self.active_object = TreeCircleInfo(QtCore.QRect(), self.POLYGON_PEN, (0, 0, 0))
00134 gfx_scene = QtGui.QGraphicsScene()
00135 gfx_scene.setBackgroundBrush(QtGui.QColor(0, 0, 0))
00136 self.last_click_time = rospy.Time(0)
00137
00138
00139 pen = QtGui.QPen(Colors.BLUE, 5)
00140
00141
00142 cursor_rect = QtCore.QRect(self.width(), self.height(), CURSOR_RADIUS, CURSOR_RADIUS)
00143 self.obj_cursor = gfx_scene.addEllipse(cursor_rect, pen)
00144 self.obj_cursor.setCacheMode(QtGui.QGraphicsItem.CacheMode.NoCache)
00145 self.obj_cursor.setZValue(2000)
00146 self.last_cursor_rect = self.obj_cursor.boundingRect()
00147
00148
00149 self.obj_cursor_hint = gfx_scene.addLine(0, 0, 0, 0, pen=pen)
00150 self.obj_cursor_hint.hide()
00151 self.obj_cursor_hint.setZValue(2000)
00152
00153 self.setScene(gfx_scene)
00154
00155 self.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing)
00156
00157 self.addKeyHandler(16777216, self.escHandler)
00158
00159 self.showFullScreen()
00160
00161
00162
00163 self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
00164 self.setSceneRect(0, 0, self.width(), self.height())
00165
00166 self.circles = kdtree.create(dimensions=3)
00167
00168 def resetClick(self, obj):
00169 obj.item.setPen(self.polygon_pens[obj.uid])
00170 obj.item.setZValue(0)
00171 obj.clicked = False
00172 self.scene().invalidate(obj.item.boundingRect())
00173 self.resetClickedObject()
00174
00175 def resetClickedObject(self):
00176 self.clicked_object = GraphicsItemInfo(QtGui.QGraphicsPolygonItem(), -1, label='\x00')
00177
00178 def resetActiveObject(self):
00179 self.active_poly = GraphicsItemInfo(QtGui.QGraphicsPolygonItem(), -1, label='\x00')
00180
00181 def info_cb(self, info):
00182 tmp_model = image_geometry.PinholeCameraModel()
00183 tmp_model.fromCameraInfo(info)
00184 self.model = tmp_model
00185 self.info_sub.unregister()
00186
00187
00188 def handleClick(self):
00189
00190 self.last_click_time = rospy.Time.now()
00191
00192
00193 if (self.active_poly.label != '\x00') and (self.clicked_object.label == '\x00'):
00194 self.clicked_object = self.active_poly
00195 self.clicked_object.item.setPen(self.CLICKED_OBJECT_PEN)
00196 self.clicked_object.item.setZValue(1000)
00197 self.clicked_object.clicked = True
00198 self.clicked_object_pub.publish(self.clicked_object.uid)
00199 self.scene().invalidate(self.clicked_object.item.boundingRect())
00200 QtCore.QTimer.singleShot(
00201 1000*self.click_duration.to_sec(),
00202 partial(self.resetClick, self.clicked_object)
00203 )
00204
00205
00206 if self.active_object.active:
00207 self.clicked_object = self.active_object
00208 self.clicked_object.item.setPen(self.CLICKED_OBJECT_PEN)
00209 self.clicked_object.item.setZValue(1000)
00210 self.clicked_object.clicked = True
00211 self.clicked_object_pub.publish(self.clicked_object.uid)
00212 self.scene().invalidate(self.clicked_object.item.boundingRect())
00213 QtCore.QTimer.singleShot(
00214 1000*self.click_duration.to_sec(),
00215 partial(self.resetClick, self.clicked_object)
00216 )
00217
00218 def click_cb(self, msg):
00219 self.click_stale = False
00220 self.click.emit()
00221
00222 with self.cursor_lock:
00223 self.click_loc = self.selected_pt
00224 msg = xyz_array_to_pointcloud2(np.array(self.cursor_pts_xyz))
00225 msg.header.frame_id = self.model.tf_frame
00226 msg.header.stamp = rospy.Time.now()
00227 self.click_stats_pub.publish(msg)
00228
00229 def object_cb(self, msg):
00230 self.objectsChanged.emit(msg)
00231
00232 def updateObjects(self, obj_msg):
00233 with self.object_lock:
00234 for obj in self.circles.inorder():
00235 self.scene().removeItem(obj.data)
00236 self.scene().invalidate(obj.data.boundingRect())
00237 self.circles = kdtree.create(dimensions=3)
00238
00239 objects = read_points_np(obj_msg, masked=False)
00240 if objects.shape[1] == 0:
00241 return
00242
00243 self.object_header = obj_msg.header
00244 self.objects = objects
00245 transformed_objects = self.projectPoints(self.objects, obj_msg.header)
00246 self.projected_objects = cv2.perspectiveTransform(transformed_objects, self.H)
00247 for pt, xformed in zip(self.objects[0], self.projected_objects[0]):
00248 coords = self.maybe_flip((xformed[1], xformed[0]))
00249 rect = QtCore.QRectF(
00250 coords[0]-self.CIRCLE_RADIUS/2 + X_OFFSET,
00251 coords[1]-self.CIRCLE_RADIUS/2 + Y_OFFSET,
00252 self.CIRCLE_RADIUS,
00253 self.CIRCLE_RADIUS
00254 )
00255 circle = TreeCircleInfo(rect, self.POLYGON_PEN, pt)
00256 if self.circles:
00257 nearest = self.circles.search_nn(circle)
00258 if np.sqrt(nearest.dist(circle)) > SAME_OBJ_THRESH:
00259 self.circles.add(circle)
00260 self.scene().addItem(circle)
00261 self.scene().invalidate(rect)
00262 else:
00263 self.circles.add(circle)
00264 self.scene().addItem(circle)
00265 self.scene().invalidate(rect)
00266
00267 def intersected_cb(self, msg):
00268 with self.intersected_lock:
00269 objects = read_points_np(msg, masked=False)
00270 if objects.shape[1] == 0:
00271 return
00272
00273 self.int_objects = objects
00274 transformed_objects = self.projectPoints(objects, msg.header)
00275 self.int_projected_objects = cv2.perspectiveTransform(transformed_objects, self.H)
00276
00277 def cursor_cb(self, msg):
00278 self.cursorMoved.emit(msg)
00279
00280 def hideCursor(self):
00281 self.obj_cursor.hide()
00282 self.scene().invalidate(self.obj_cursor.boundingRect())
00283
00284 def showCursor(self):
00285 self.obj_cursor.show()
00286 self.scene().invalidate(self.obj_cursor.boundingRect())
00287
00288 def hideCursorHint(self):
00289 self.obj_cursor_hint.hide()
00290 self.scene().invalidate(self.obj_cursor_hint.boundingRect())
00291
00292 def showCursorHint(self):
00293 self.obj_cursor_hint.show()
00294 self.scene().invalidate(self.obj_cursor_hint.boundingRect())
00295
00296 def update_cursor(self, cursor_msg):
00297 with self.cursor_lock:
00298 objects = read_points_np(cursor_msg, masked=False)
00299 if objects.shape[1] == 0:
00300 return
00301
00302 self.cursor_header = cursor_msg.header
00303 self.cursor_pts = objects
00304 transformed_pts = self.projectPoints(self.cursor_pts, cursor_msg.header)
00305 self.projected_cursor.extend(cv2.perspectiveTransform(transformed_pts, self.H)[0])
00306
00307
00308 with self.cursor_lock:
00309 xformed = np.median(self.projected_cursor, 0)
00310 coords = self.maybe_flip((xformed[1], xformed[0]))
00311 cursor_x = coords[0]
00312 cursor_y = coords[1]
00313
00314
00315 if cursor_x > 0 and cursor_y > 0 \
00316 and cursor_x < self.width() \
00317 and cursor_y < self.height():
00318
00319 if self.obj_cursor_hint.isVisible():
00320 self.hideCursorHint()
00321
00322 cursor_rect = QtCore.QRectF(
00323 cursor_x-CURSOR_RADIUS/2,
00324 cursor_y-CURSOR_RADIUS/2,
00325 CURSOR_RADIUS,
00326 CURSOR_RADIUS
00327 )
00328 self.showCursor()
00329
00330 self.obj_cursor.setRect(cursor_rect)
00331 self.scene().invalidate(cursor_rect)
00332 self.scene().invalidate(self.last_cursor_rect)
00333 self.last_cursor_rect = self.obj_cursor.boundingRect()
00334
00335 else:
00336 if self.obj_cursor.isVisible:
00337 self.hideCursor()
00338
00339 hint_x = cursor_x
00340 if cursor_x < 0:
00341 hint_x = 0
00342 if cursor_x > self.width():
00343 hint_x = self.width()
00344
00345 hint_y = cursor_y
00346 if cursor_y < 0:
00347 hint_y = 0
00348 if cursor_y > self.height():
00349 hint_y = self.height()
00350
00351 head = np.array([hint_x, hint_y])
00352 tail_x, tail_y = 0, 0
00353 if hint_x == 0:
00354 tail_x = 50
00355 if hint_x == self.width():
00356 tail_x = -50
00357 if hint_y == 0:
00358 tail_y = 50
00359 if hint_y == self.height():
00360 tail_y = -50
00361 tail = head + [tail_x, tail_y]
00362 self.scene().invalidate(self.obj_cursor_hint.boundingRect())
00363 self.obj_cursor_hint.setLine(head[0], head[1], tail[0], tail[1])
00364 self.showCursorHint()
00365
00366 self.updateIntersectedPolys()
00367 self.updateIntersectedCircles()
00368
00369
00370
00371 def updateIntersectedPolys(self):
00372 with self.polygon_lock:
00373 with self.cursor_lock:
00374 cursor_center = self.last_cursor_rect.center()
00375 self.resetActiveObject()
00376 for info in self.polygons.values():
00377 if not info.clicked:
00378 dirty = False
00379 if info.item.polygon().containsPoint(cursor_center, QtCore.Qt.OddEvenFill):
00380 dirty = not info.active
00381 info.active = True
00382 info.item.setPen(self.ACTIVE_POLYGON_PEN)
00383 info.item.setZValue(500)
00384 self.active_poly = info
00385 else:
00386 dirty = info.active
00387 info.active = False
00388 info.item.setPen(self.polygon_pens[info.uid])
00389 info.item.setZValue(0)
00390 self.resetClickedObject()
00391 if dirty:
00392 self.scene().invalidate(info.item.boundingRect())
00393
00394 def updateIntersectedCircles(self):
00395 if self.circles:
00396
00397 if not self.active_object.clicked:
00398 self.active_object.active = False
00399 self.active_object.item.setPen(self.POLYGON_PEN)
00400
00401 self.scene().invalidate(self.active_object.item.boundingRect())
00402 self.resetActiveObject()
00403 with self.object_lock:
00404 with self.cursor_lock:
00405 cursor_center = self.last_cursor_rect.center()
00406 cursor_center_3d = np.median(self.cursor_pts, 0).flatten().tolist()
00407 nearest = self.circles.search_nn(cursor_center_3d)
00408
00409
00410 if self.use_selected_thresh:
00411 nearest = nearest if nearest.data.contains(cursor_center) else None
00412
00413 dirty = False
00414
00415 if nearest and not nearest.data.clicked:
00416 item = nearest.data
00417 dirty = not item.active
00418 item.active = True
00419 item.setPen(self.ACTIVE_POLYGON_PEN)
00420 item.setZValue(500)
00421 self.active_object = item
00422 if dirty:
00423 self.scene().invalidate(item.boundingRect())
00424
00425
00426 def projectPoints(self, points, point_header):
00427 pts_out = []
00428 for point in points[0]:
00429 pt = PointStamped()
00430 try:
00431 pt.point.x, pt.point.y, pt.point.z = point.tolist()
00432 except Exception:
00433 print 'Error: ', point
00434
00435 if point_header.frame_id != self.model.tf_frame:
00436 stamp = rospy.Time(0)
00437 pt.header = point_header
00438 pt.header.stamp = stamp
00439 pt_out = self.tfl.transformPoint(self.model.tf_frame, pt).point
00440 else:
00441 pt_out = pt.point
00442
00443 px = self.model.project3dToPixel((pt_out.x, pt_out.y, pt_out.z))
00444 pts_out.append(px)
00445 return np.array([pts_out])
00446
00447 def maybe_flip(self, coords):
00448 if self.flip:
00449 return self.width() - coords[0], self.height() - coords[1]
00450 return coords
00451
00452 def hilight_object(self, req):
00453 pt = self.tfl.transformPoint(self.object_header.frame_id, req.object).point
00454 color = QtGui.QColor(req.color.r, req.color.g, req.color.b)
00455 if self.circles:
00456 pt_arr = [pt.x, pt.y, pt.z]
00457 to_hilight = self.circles.search_nn([pt.x, pt.y, pt.z])
00458
00459 if to_hilight.dist(pt_arr) < 0.09:
00460 to_hilight.data.showHilight(color)
00461
00462 def clear_hilights(self):
00463 for obj in self.circles.inorder():
00464 obj.data.clearHilight()
00465
00466 def handle_hilight(self, req):
00467 self.objectHighlighted.emit(req)
00468 return srv.HilightObjectResponse()
00469
00470 def handle_clear_hilight(self, _):
00471 self.hilightsCleared.emit()
00472 return srv.ClearHilightsResponse()
00473
00474 def handle_get_cursor_stats(self, _):
00475
00476 while not (rospy.Time.now() - self.last_click_time) < self.click_duration:
00477 rospy.sleep(0.05)
00478 with self.cursor_lock:
00479 msg = xyz_array_to_pointcloud2(np.array([list(p) + [0] for p in self.projected_cursor]))
00480 msg.header.frame_id = '/table_marker'
00481 msg.header.stamp = rospy.Time.now()
00482
00483 pt_msg = PointStamped()
00484 pt_msg.header = msg.header
00485 try:
00486 pt_msg.point.x, pt_msg.point.y, pt_msg.point.z = self.clicked_object.point3d
00487 except:
00488 pass
00489 return srv.GetCursorStatsResponse(msg, pt_msg)
00490
00491 def set_selection_method(self, req):
00492 if req.method == req.THRESH:
00493 self.use_selected_thresh = True
00494 elif req.method == req.CLOSEST:
00495 self.use_selected_thresh = False
00496 return srv.SetSelectionMethodResponse()
00497
00498 def handle_draw_polygon(self, req):
00499 self.polygonAdded.emit(req)
00500 return srv.DrawPolygonResponse()
00501
00502 def draw_polygon(self, req):
00503 with self.polygon_lock:
00504 if req.id in self.polygons:
00505 self.clear_polygon(req.id)
00506
00507
00508 poly = QtGui.QPolygon()
00509 pts_arr = np.array([[(p.x, p.y, p.z) for p in req.polygon.polygon.points]])
00510 points = self.projectPoints(pts_arr, req.polygon.header)
00511 points = cv2.perspectiveTransform(points, self.H)
00512 if self.flip:
00513 points[0] = ([self.height(), self.width()] - points[0])
00514 for point in points[0]:
00515 poly.push_back(QtCore.QPoint(point[1], point[0]))
00516
00517 my_pen = QtGui.QPen(QtGui.QColor(req.color.r, req.color.g, req.color.b), 5)
00518
00519 poly_item = self.scene().addPolygon(poly, pen=my_pen)
00520
00521
00522 textRect = poly.boundingRect()
00523
00524 if len(req.text_rect.points) > 0:
00525 text_rect_points = self.projectPoints(
00526 np.array([[(p.x,p.y,p.z) for p in req.text_rect.points]]),
00527 req.polygon.header
00528 )
00529 text_rect_points = cv2.perspectiveTransform(text_rect_points, self.H)
00530 if self.flip:
00531 text_rect_points[0] = ([self.height(), self.width()] - text_rect_points[0])
00532 text_poly = QtGui.QPolygon()
00533 for point in text_rect_points[0]:
00534 text_poly.push_back(QtCore.QPoint(point[1], point[0]))
00535 textRect = text_poly.boundingRect()
00536 textRect = poly.boundingRect()
00537
00538 if req.label:
00539 font = QtGui.QFont('Decorative', 30)
00540 text_item = QtGui.QGraphicsSimpleTextItem(
00541 req.label, parent=poly_item,
00542 scene=self.scene()
00543 )
00544 text_item.setFont(font)
00545 text_item.setBrush(QtGui.QBrush(Colors.WHITE))
00546 text_item.setPos(textRect.topLeft())
00547
00548 self.polygon_pens[req.id] = my_pen
00549 self.polygons[req.id] = GraphicsItemInfo(poly_item, req.id, req.label)
00550 self.scene().invalidate(self.polygons[req.id].item.boundingRect())
00551
00552 def clear_polygon(self, poly_id, remove=True):
00553 poly = self.polygons[poly_id]
00554 self.scene().removeItem(poly.item)
00555 QtCore.QTimer.singleShot(100, partial(self.scene().invalidate, poly.item.boundingRect()))
00556 QtCore.QTimer.singleShot(100, partial(self.scene().invalidate, poly.item.childrenBoundingRect()))
00557 if remove: del self.polygons[poly_id]
00558
00559 def clear_polygons(self):
00560 with self.polygon_lock:
00561 for poly_id in self.polygons.keys():
00562 self.clear_polygon(poly_id, remove=False)
00563 self.polygons.clear()
00564
00565 def handle_clear_polygons(self, _):
00566 self.polygonsCleared.emit()
00567 return srv.ClearPolygonsResponse()
00568
00569 def reconfig_cb(self, config, level):
00570
00571 if self.config_inited:
00572 self.projected_cursor = deque(self.projected_cursor, config['window_size'])
00573 self.cursor_pts_xyz = deque(self.cursor_pts_xyz, config['window_size'])
00574
00575 else:
00576 config['window_size'] = self.projected_cursor.maxlen
00577 self.config_inited = True
00578
00579 return config
00580
00581 def checkShutdownRequest(self):
00582 if rospy.is_shutdown():
00583 QtGui.QApplication.quit()
00584
00585 def __init__(self):
00586 super(Circler, self).__init__()
00587 self.tfl = tf.TransformListener()
00588 r = rospy.Rate(10)
00589 rospy.loginfo('waiting for homography...')
00590 while (not rospy.has_param('/homography')) and (not rospy.is_shutdown()):
00591 r.sleep()
00592 self.H = np.float64(rospy.get_param('/homography')).reshape(3, 3)
00593 self.flip = rospy.get_param('~flip', default=False)
00594
00595 self.projected_cursor = deque([], rospy.get_param('~window_size', 10))
00596 self.cursor_pts_xyz = deque([], rospy.get_param('~window_size', 10))
00597 self.circle_objects = rospy.get_param('~circle_objects', True)
00598
00599
00600
00601 self.click.connect(self.handleClick)
00602 self.polygonAdded.connect(self.draw_polygon)
00603 self.polygonsCleared.connect(self.clear_polygons)
00604 self.cursorMoved.connect(self.update_cursor)
00605 self.objectsChanged.connect(self.updateObjects)
00606 self.objectHighlighted.connect(self.hilight_object)
00607 self.hilightsCleared.connect(self.clear_hilights)
00608
00609 rospy.loginfo('Window size = %s', rospy.get_param('~window_size', 10))
00610 rospy.loginfo('Flip = %s', self.flip)
00611 rospy.loginfo('Circle = %s', self.circle_objects)
00612
00613 rospy.loginfo('got homography')
00614 print self.H
00615 self.initUI()
00616 rospy.Subscriber('object_cloud', PointCloud2, self.object_cb)
00617 rospy.Subscriber('click', Empty, self.click_cb)
00618 rospy.Subscriber('intersected_points', PointCloud2, self.intersected_cb, queue_size=1)
00619 rospy.Subscriber('intersected_points_cursor', PointCloud2, self.cursor_cb)
00620 self.info_sub = rospy.Subscriber('camera_info', CameraInfo, self.info_cb)
00621 rospy.Service('hilight_object', srv.HilightObject, self.handle_hilight)
00622 rospy.Service('clear_hilights', srv.ClearHilights, self.handle_clear_hilight)
00623 rospy.Service('get_cursor_stats', srv.GetCursorStats, self.handle_get_cursor_stats)
00624 rospy.Service('set_selection_method', srv.SetSelectionMethod, self.set_selection_method)
00625 rospy.Service('draw_polygon', srv.DrawPolygon, self.handle_draw_polygon)
00626 rospy.Service('clear_polygons', srv.ClearPolygons, self.handle_clear_polygons)
00627 self.selected_pub = rospy.Publisher('selected_point', PointStamped)
00628
00629 self.rate_pub = rospy.Publisher('rate', Empty)
00630 self.dur_pub = rospy.Publisher('duration', Duration)
00631
00632 self.click_stats_pub = rospy.Publisher('click_stats', PointCloud2)
00633 self.clicked_object_pub = rospy.Publisher('clicked_object', String)
00634 DynamicReconfigureServer(InterfaceConfig, self.reconfig_cb)
00635
00636 self.interrupt_timer = QtCore.QTimer(self)
00637 self.interrupt_timer.setInterval(30)
00638 self.interrupt_timer.timeout.connect(self.checkShutdownRequest)
00639 self.interrupt_timer.start()
00640
00641 rospy.loginfo('interface started')
00642 app.exec_()
00643 rospy.spin()
00644
00645 if __name__ == '__main__':
00646 rospy.init_node('object_circler')
00647 print 'Window size is %s' % rospy.get_param('~window_size', 1)
00648 app = QtGui.QApplication(sys.argv)
00649 c = Circler()