capture_server.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 # Software License Agreement (BSD License)
00003 #
00004 # Copyright (c) 2010, Willow Garage, Inc.
00005 # All rights reserved.
00006 #
00007 # Redistribution and use in source and binary forms, with or without
00008 # modification, are permitted provided that the following conditions
00009 # are met:
00010 #
00011 #  * Redistributions of source code must retain the above copyright
00012 #    notice, this list of conditions and the following disclaimer.
00013 #  * Redistributions in binary form must reproduce the above
00014 #    copyright notice, this list of conditions and the following
00015 #    disclaimer in the documentation and/or other materials provided
00016 #    with the distribution.
00017 #  * Neither the name of Willow Garage, Inc. nor the names of its
00018 #    contributors may be used to endorse or promote products derived
00019 #    from this software without specific prior written permission.
00020 #
00021 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 # POSSIBILITY OF SUCH DAMAGE.
00033 #
00034 # Revision $Id: capture_server.py 47238 2010-12-17 22:23:01Z kwc $
00035 """
00036 Panorama capture action server.
00037 
00038 Authors: Ethan Rublee, Ken Conley
00039 
00040 Sample run command for using the kinect as a capture device:
00041 
00042 rosrun pano_ros capture_server.py camera:=/kinect/rgb
00043 """
00044 
00045 #import roslib; roslib.load_manifest('pano_ros')
00046 import os
00047 import tempfile
00048 import threading
00049 import rospy
00050 
00051 from pano_ros.msg import PanoCaptureAction, PanoCaptureResult, PanoCaptureFeedback
00052 from std_msgs.msg import Empty
00053 from sensor_msgs.msg import Image, CameraInfo
00054 
00055 import actionlib
00056 import rosbag
00057 import message_filters
00058 
00059 import pano_py as pano
00060 import pano_cv as pcv
00061 import cv2
00062 from cv2 import cv
00063 import numpy
00064 from cv_bridge import CvBridge, CvBridgeError
00065 
00066 class ImageGrabber:
00067     """
00068     Helper class for subscribing to synchronized camera and camera
00069     info data.
00070     """
00071 
00072     def __init__(self, image_topic, camera_info_topic, capture_fn):
00073         
00074         self._image_sub = message_filters.Subscriber(image_topic, Image)
00075         self._info_sub = message_filters.Subscriber(camera_info_topic, CameraInfo)
00076         self._ts = message_filters.TimeSynchronizer([self._image_sub, self._info_sub], 3)
00077         self._ts.registerCallback(capture_fn) 
00078 
00079     def stop(self):
00080         # stop subscribing
00081         self._image_sub.sub.unregister()
00082         self._info_sub.sub.unregister()
00083 
00084         del self._image_sub
00085         del self._info_sub        
00086         del self._ts
00087     
00088 class PanoCaptureJob(object):
00089 
00090     def __init__(self, pano_id, capture_fn):
00091         self.pano_id = pano_id
00092         self.capture_fn = capture_fn
00093         self.result = None
00094 
00095     def __call__(self, image, info):
00096         """
00097         Call job with latest captured imaged
00098         """
00099         # passthrough
00100         self.capture_fn(image, info)
00101         
00102     def _get_n_captures(self):
00103         return self.capture_fn.n_captures if self.capture_fn else self.result.n_captures
00104 
00105     n_captures = property(_get_n_captures)
00106     
00107     def cancel(self):
00108         if self.capture_fn is None:
00109             return
00110         capturer = self.capture_fn
00111         self.capture_fn = None
00112         capturer.cancel()
00113         
00114     def stop(self):
00115         if self.capture_fn is None:
00116             raise Exception("already stopped")
00117         capturer = self.capture_fn
00118         capturer.stop()
00119         #todo get rid of capture_fn.bag_filename
00120         self.result = PanoCaptureResult(pano_id=self.pano_id,
00121                                         n_captures=capturer.n_captures,
00122                                         bag_filename=capturer.bag_filename)
00123         self.capture_fn = None
00124 
00125 class CaptureInterface(object):
00126     """
00127     Common capture interface, like capture to bag, capture and republish, etc...
00128     """
00129     def start(self,pano_id, goal):
00130         """
00131         set up a new pano.
00132         """
00133         raise NotImplemented
00134     def __call__(self, image, camera_info):
00135         """
00136         capture your synced image and camera_info here
00137         """
00138         raise NotImplemented
00139 
00140     def stop(self):
00141         """
00142         done capturing, do clean up, close bags, etc.
00143         """
00144         raise NotImplemented
00145     
00146     def cancel(self):
00147         """
00148         no matter what stop capturing, you've been prempted
00149         """
00150         raise NotImplemented
00151 class PanoCaptureServer(object):
00152     def __init__(self, name, capture_interface = CaptureInterface):
00153         self._action_name = name
00154         self._server = actionlib.SimpleActionServer(self._action_name, 
00155                                                     PanoCaptureAction, execute_cb=self.pano_capture)
00156         
00157         # triggers during action execution
00158         self._snap_sub = rospy.Subscriber(rospy.names.ns_join(self._action_name, 'snap'), Empty, self.capture_snap)
00159         self._stop_sub = rospy.Subscriber(rospy.names.ns_join(self._action_name, 'stop'), Empty, self.capture_stop)
00160 
00161         # initialized during active job
00162         self._capture_job = None
00163         self._snap_requested = False
00164         self._capture_interface = capture_interface
00165 
00166         
00167     def pano_capture(self, goal):
00168         if self._capture_job is not None:
00169             raise Exception("cannot run multile capture jobs. TODO: pre-eempt existing job")
00170         
00171         rospy.loginfo('%s: Pano Capture Goal: \n\tCamera [%s]'%(self._action_name, goal.camera_topic))
00172                              
00173         pano_id = int(rospy.get_time())
00174         #TODO make this a parameter, bag, publisher, etc...
00175         capturer = self._capture_interface()#= BagCapture(pano_id,goal.bag_filename or None)
00176         capturer.start(pano_id,goal)
00177                 
00178         self._snap_requested = False #reset
00179         capture_job = self._capture_job = PanoCaptureJob(pano_id, capturer )
00180         camera_topic = goal.camera_topic or rospy.resolve_name('camera')
00181         #TODO: FIX ONCE OPENNI API IS FIXED
00182         image_topic = rospy.names.ns_join(camera_topic, 'image_color')
00183         camera_info_topic = rospy.names.ns_join(camera_topic, 'camera_info')
00184 
00185         rospy.loginfo('%s: Starting capture of pano_id %d.\n\tImage [%s]\n\tCamera Info[%s]'
00186                       %(self._action_name, pano_id, image_topic, camera_info_topic) )
00187         grabber = ImageGrabber(image_topic, camera_info_topic, self.capture_fn)
00188 
00189         # local vars
00190         server = self._server
00191         preempted = False        
00192 
00193         rospy.loginfo('%s: Executing capture of pano_id %d' % (self._action_name, pano_id))  
00194         # this will become true 
00195         while capture_job.result is None and not preempted and not rospy.is_shutdown():
00196             if server.is_preempt_requested():
00197                 rospy.loginfo('%s: Preempted' % self._action_name)
00198                 server.set_preempted()
00199                 capture_job.cancel()
00200                 preempted = True
00201             else:
00202                 rospy.sleep(0.001) #let the node rest a bit
00203                 
00204         result = capture_job.result
00205         grabber.stop()
00206         
00207         if result:
00208             rospy.loginfo('%s: Succeeded, [%s] images.\nResult: %s' % (self._action_name, result.n_captures, result))
00209             server.set_succeeded(result)
00210   
00211         self._capture_job = None
00212         
00213     def capture_fn(self, image, info):
00214         if self._capture_job is None:
00215             rospy.logerr("cannot capture photo while capture job is not active")
00216             return
00217         # not really concerned by the possible race condition here
00218         if self._snap_requested:
00219             rospy.loginfo("snap!")
00220             self._snap_requested = False
00221             self._capture_job.capture_fn(image, info)
00222             #only send feedback on snap
00223             feedback = PanoCaptureFeedback()
00224             feedback.n_captures = self._capture_job.n_captures
00225             rospy.loginfo("captured %d images so far" % feedback.n_captures)
00226             self._server.publish_feedback(feedback)
00227             
00228     def capture_snap(self, empty):
00229         if self._capture_job is None:
00230             rospy.logerr("cannot snap photo while capture job is not active")
00231             return
00232         self._snap_requested = True
00233         
00234     def capture_stop(self, empty):
00235         if self._capture_job is None:
00236             rospy.logerr("no active capture job to stop")
00237             return
00238 
00239         self._snap_requested = False
00240         self._capture_job.stop()
00241         
00242 
00243 
00244 class BagCapture(CaptureInterface):
00245     """
00246     Bag capture is not thread-safe.
00247     """     
00248     def start(self, pano_id, goal):
00249         """
00250         set up a new pano.
00251         """
00252         self.pano_id = pano_id
00253         if goal.bag_filename is None:
00254             self.bag_filename = os.path.abspath(os.path.join(tempfile.gettempdir(), "pano_%d.bag" % pano_id))
00255         else:
00256             self.bag_filename = goal.bag_filename
00257         self.bag = rosbag.Bag(self.bag_filename, 'w')
00258         self.n_captures = 0
00259         self.lock = threading.Lock()
00260 
00261     def __call__(self, image, camera_info):
00262         with self.lock:
00263             if self.bag is not None:
00264                 self.bag.write("image", image)
00265                 self.bag.write("camera_info", camera_info)
00266                 self.n_captures += 1
00267                           
00268     def stop(self):
00269         with self.lock:
00270             if self.bag is not None:
00271                 self.bag.close()
00272                 self.bag = None
00273             else:
00274                 raise IOError("already closed")
00275         
00276     def cancel(self):
00277         self.bag.close()
00278         self.bag = None
00279 
00280 class StitchCapture(CaptureInterface):
00281     """
00282     Bag capture is not thread-safe.
00283     """     
00284     def start(self, pano_id, goal):
00285         """
00286         set up a new pano.
00287         """
00288         self.bridge = CvBridge()
00289         self._bag_capture = BagCapture()
00290         self._bag_capture.start(pano_id, goal)
00291         self.img_pub = rospy.Publisher('~stitch',Image, queue_size=5)
00292         
00293 
00294     def __call__(self, image, camera_info):
00295         if self._bag_capture.n_captures < 1:
00296             self.setupCamera(camera_info)
00297             self.setupStitch()
00298         try:
00299             self.img_pub.publish(image)         
00300             cv_image = self.bridge.imgmsg_to_cv2(image, "rgb8")
00301             pano_image_t = cv.fromarray(cv_image)
00302             pano_image = pcv.convertCvMat2Mat(pano_image_t)     
00303             self.stitcher.addNewImage(pano_image)
00304         except CvBridgeError, e:
00305             print e
00306         #capture to bag for back up and later stitching        
00307         self._bag_capture(image,camera_info)
00308         
00309     def setupStitch(self):
00310         options = pano.Options()
00311         options.camera = self.camera
00312         options.stitch_size = pcv.Size(4000,2000)
00313         #options.image_names.assign(image_names)
00314         params = options.fitter_params
00315         params.error_thresh = 6;
00316         params.inliers_thresh = 15;
00317         params.maxiters = 100;
00318         params.nNeeded = 2;
00319         self.stitcher = pano.StitchEngine(options)
00320           
00321     def _get_n_captures(self):
00322         return self._bag_capture.n_captures
00323     def _get_bag_filename(self):
00324         return self._bag_capture.bag_filename
00325     
00326     n_captures = property(_get_n_captures)
00327     bag_filename = property(_get_bag_filename)
00328     
00329     def setupCamera(self,camera_info):
00330         self.camera = pano.Camera()
00331         K = pcv.Mat(3,3,pcv.CV_32FC1)
00332         K.fromarray(camera_info.K)
00333         img_size = pcv.Size( camera_info.width, camera_info.height)                
00334         self.camera.setCameraIntrinsics(K,pcv.Mat(),img_size) 
00335         
00336     def stitch_cb(self, x):
00337         print "progress ",x
00338         return 0 
00339           
00340     def stop(self):
00341         #allocate a cv mat, this will be stitched to inplace
00342         #2000 rows = 2000 height
00343         #4000 cols = 4000 width (this is backwards from widthxheight
00344         if(self.stitcher):
00345             blended_t = cv.CreateMat(1500,3000,cv.CV_8UC3)
00346             blended_num = numpy.asarray(blended_t) 
00347             blended = pcv.convertCvMat2Mat(blended_t)  
00348             self.stitcher.stitch(blended, self.stitch_cb)
00349             img_msg = self.bridge.cv2_to_imgmsg(blended_num, "rgb8")
00350             self.img_pub.publish(img_msg) 
00351         self._bag_capture.stop()
00352         
00353     def cancel(self):
00354         self._bag_capture.cancel()
00355     
00356 if __name__ == '__main__':
00357     rospy.init_node('pano_capture')
00358     PanoCaptureServer(rospy.get_name(), StitchCapture)
00359     rospy.spin()
00360 


pano_ros
Author(s): Ethan Rublee, Ken Conley
autogenerated on Mon Mar 14 2016 10:57:02