def __init__(self, api, axis, flip_vertically=False, flip_horizontally=False, mirror_horizontally=False): """Create the controller. :param api: The VAPIX instance to be used to communicate with the camera. :type api: :py:class:`axis_camera.VAPIX` :param axis: The parameter holding class. :type axis: axis.Axis :param flip_vertically: If True, flip the controls vertically (for ceiling mounted cameras). :type flip_vertically: bool :param flip_horizontally: If True, flip the controls horizontally (for ceiling mounted cameras). :type flip_horizontally: bool :param mirror_horizontally: If True, mirror the controls horizontally (for backwards mounted cameras). :type mirror_horizontally: bool """ self._api = api self._axis = axis self._flip_vertically = flip_vertically self._flip_horizontally = flip_horizontally self._mirror_horizontally = mirror_horizontally self._parameter_update_mutex = threading.Lock() self._executing_parameter_update = False self._reconfigure_mutex = threading.Lock() self._executing_reconfigure = False self._autofocus = True self._focus = 0 self._autoiris = True self._iris = 0 self._brightness = 5000 self._backlight = False self._ir_cut_filter = True self._dynamic_reconfigure_server = Server(CameraConfig, self._reconfigure, "axis_ptz_driver") self._diagnostic_updater = Updater() self._diagnostic_updater.setHardwareID(api.hostname) self._diagnostic_updater.add(FunctionDiagnosticTask("Last command status", self._diagnostic_last_command)) self._last_command_error = None timer = rospy.Timer(rospy.Duration(1), lambda (event): self._diagnostic_updater.update()) # set up the topics this node listens on # the callbacks are created as lambda wrappers around the Python API functions of this controller so that we # do not blow this file with more unnecessary methods if self._api.has_capabilities('AbsolutePan', 'AbsoluteTilt', 'AbsoluteZoom'): self._absolute_ptz_subscriber = rospy.Subscriber( "control/pan_tilt_zoom/absolute", PTZ, self._call_with_ptz_message(self.set_ptz), queue_size=100) if self._api.has_capabilities('RelativePan', 'RelativeTilt', 'RelativeZoom'): self._relative_ptz_subscriber = rospy.Subscriber( "control/pan_tilt_zoom/relative", PTZ, self._call_with_ptz_message(self.adjust_ptz), queue_size=100) if self._api.has_capabilities('ContinuousPan', 'ContinuousTilt', 'ContinuousZoom'): self._velocity_ptz_subscriber = rospy.Subscriber( "control/pan_tilt_zoom/velocity", PTZ, self._call_with_ptz_message(self.set_ptz_velocity), queue_size=100) if self._api.has_capabilities('AbsolutePan', 'AbsoluteTilt'): self._absolute_pan_tilt_subscriber = rospy.Subscriber( "control/pan_tilt/absolute", PTZ, self._call_with_pt_message(self.set_pan_tilt), queue_size=100) if self._api.has_capabilities('RelativePan', 'RelativeTilt'): self._relative_pan_tilt_subscriber = rospy.Subscriber( "control/pan_tilt/relative", PTZ, self._call_with_pt_message(self.adjust_pan_tilt), queue_size=100) if self._api.has_capabilities('ContinuousPan', 'ContinuousTilt'): self._velocity_pan_tilt_subscriber = rospy.Subscriber( "control/pan_tilt/velocity", PTZ, self._call_with_pt_message(self.set_pan_tilt_velocity), queue_size=100) if self._api.has_capability('AbsolutePan'): self._absolute_pan_subscriber = rospy.Subscriber( "control/pan/absolute", Float32, self._call_with_simple_message_data(self.set_pan), queue_size=100) if self._api.has_capability('AbsoluteTilt'): self._absolute_tilt_subscriber = rospy.Subscriber( "control/tilt/absolute", Float32, self._call_with_simple_message_data(self.set_tilt), queue_size=100) if self._api.has_capability('AbsoluteZoom'): self._absolute_zoom_subscriber = rospy.Subscriber( "control/zoom/absolute", Int32, self._call_with_simple_message_data(self.set_zoom), queue_size=100) if self._api.has_capability('RelativePan'): self._relative_pan_subscriber = rospy.Subscriber( "control/pan/relative", Float32, self._call_with_simple_message_data(self.adjust_pan), queue_size=100) if self._api.has_capability('RelativeTilt'): self._relative_tilt_subscriber = rospy.Subscriber( "control/tilt/relative", Float32, self._call_with_simple_message_data(self.adjust_tilt), queue_size=100) if self._api.has_capability('RelativeZoom'): self._relative_zoom_subscriber = rospy.Subscriber( "control/zoom/relative", Int32, self._call_with_simple_message_data(self.adjust_zoom), queue_size=100) if self._api.has_capability('ContinuousPan'): self._pan_velocity_subscriber = rospy.Subscriber( "control/pan/velocity", Int32, self._call_with_simple_message_data(self.set_pan_velocity), queue_size=100) if self._api.has_capability('ContinuousTilt'): self._tilt_velocity_subscriber = rospy.Subscriber( "control/tilt/velocity", Int32, self._call_with_simple_message_data(self.set_tilt_velocity), queue_size=100) if self._api.has_capability('ContinuousZoom'): self._zoom_velocity_subscriber = rospy.Subscriber( "control/zoom/velocity", Int32, self._call_with_simple_message_data(self.set_zoom_velocity), queue_size=100) if self._api.has_capabilities('AbsolutePan', 'AbsoluteTilt', 'AbsoluteZoom'): self._look_at_subscriber = rospy.Subscriber( "control/look_at", PointInRectangle, self._call_with_pir_message(self.look_at), queue_size=100) if self._api.has_capability('AutoFocus'): self._autofocus_subscriber = rospy.Subscriber( "control/camera/focus/auto", Bool, self._call_with_simple_message_data(self.set_autofocus), queue_size=100) if self._api.has_capability('AbsoluteFocus'): self._focus_subscriber = rospy.Subscriber( "control/camera/focus/absolute", Int32, self._call_with_simple_message_data(self.set_focus), queue_size=100) if self._api.has_capability('RelativeFocus'): self._focus_relative_subscriber = rospy.Subscriber( "control/camera/focus/relative", Int32, self._call_with_simple_message_data(self.adjust_focus), queue_size=100) if self._api.has_capability('ContinuousFocus'): self._focus_velocity_subscriber = rospy.Subscriber( "control/camera/focus/velocity", Int32, self._call_with_simple_message_data(self.set_focus_velocity), queue_size=100) if self._api.has_capability('AutoIris'): self._autoiris_subscriber = rospy.Subscriber( "control/camera/iris/auto", Bool, self._call_with_simple_message_data(self.set_autoiris), queue_size=100) if self._api.has_capability('AbsoluteIris'): self._iris_subscriber = rospy.Subscriber( "control/camera/iris/absolute", Int32, self._call_with_simple_message_data(self.set_iris), queue_size=100) if self._api.has_capability('RelativeIris'): self._iris_relative_subscriber = rospy.Subscriber( "control/camera/iris/relative", Int32, self._call_with_simple_message_data(self.adjust_iris), queue_size=100) if self._api.has_capability('ContinuousIris'): self._iris_velocity_subscriber = rospy.Subscriber( "control/camera/iris/velocity", Int32, self._call_with_simple_message_data(self.set_iris_velocity), queue_size=100) if self._api.has_capability('AbsoluteBrightness'): self._brightness_subscriber = rospy.Subscriber( "control/camera/brightness/absolute", Int32, self._call_with_simple_message_data(self.set_brightness), queue_size=100) if self._api.has_capability('RelativeBrightness'): self._brightness_relative_subscriber = rospy.Subscriber( "control/camera/brightness/relative", Int32, self._call_with_simple_message_data(self.adjust_brightness), queue_size=100) if self._api.has_capability('ContinuousBrightness'): self._brightness_velocity_subscriber = rospy.Subscriber( "control/camera/brightness/velocity", Int32, self._call_with_simple_message_data(self.set_brightness_velocity), queue_size=100) if self._api.has_capability('BackLight'): self._use_backlight_subscriber = rospy.Subscriber( "control/camera/backlight_compensation", Bool, self._call_with_simple_message_data(self.use_backlight_compensation), queue_size=100) if self._api.has_capabilities('IrCutFilter', 'AutoIrCutFilter'): self._use_ir_cut_filter_auto_subscriber = rospy.Subscriber( "control/camera/ir_cut_filter/auto", Bool, self._call_with_simple_message_data(self.set_ir_cut_filter_auto), queue_size=100) if self._api.has_capabilities('IrCutFilter'): self._use_ir_cut_filter_use_subscriber = rospy.Subscriber( "control/camera/ir_cut_filter/use", Bool, self._call_with_simple_message_data(self.set_ir_cut_filter_use), queue_size=100)
class AxisCameraController(object): """A class serving as the Python and ROS controller of the Axis camera. It provides numerous topics/methods using which the camera can be controlled via VAPIX. Each topic is only subscribed if the camera reports capabilities required for executing the topic's functionality. Currently provided topics are: - control/pan_tilt_zoom/absolute - control/pan_tilt_zoom/relative - control/pan_tilt_zoom/velocity - control/pan_tilt/absolute - control/pan_tilt/relative - control/pan_tilt/velocity - control/pan/absolute - control/tilt/absolute - control/zoom/absolute - control/pan/relative - control/tilt/relative - control/zoom/relative - control/pan/velocity - control/tilt/velocity - control/zoom/velocity - control/look_at - control/camera/focus/auto - control/camera/focus/absolute - control/camera/focus/relative - control/camera/focus/velocity - control/camera/iris/auto - control/camera/iris/absolute - control/camera/iris/relative - control/camera/iris/velocity - control/camera/brightness/absolute - control/camera/brightness/relative - control/camera/brightness/velocity - control/camera/backlight_compensation - control/camera/ir_cut_filter/auto - control/camera/ir_cut_filter/use """ def __init__(self, api, axis, flip_vertically=False, flip_horizontally=False, mirror_horizontally=False): """Create the controller. :param api: The VAPIX instance to be used to communicate with the camera. :type api: :py:class:`axis_camera.VAPIX` :param axis: The parameter holding class. :type axis: axis.Axis :param flip_vertically: If True, flip the controls vertically (for ceiling mounted cameras). :type flip_vertically: bool :param flip_horizontally: If True, flip the controls horizontally (for ceiling mounted cameras). :type flip_horizontally: bool :param mirror_horizontally: If True, mirror the controls horizontally (for backwards mounted cameras). :type mirror_horizontally: bool """ self._api = api self._axis = axis self._flip_vertically = flip_vertically self._flip_horizontally = flip_horizontally self._mirror_horizontally = mirror_horizontally self._parameter_update_mutex = threading.Lock() self._executing_parameter_update = False self._reconfigure_mutex = threading.Lock() self._executing_reconfigure = False self._autofocus = True self._focus = 0 self._autoiris = True self._iris = 0 self._brightness = 5000 self._backlight = False self._ir_cut_filter = True self._dynamic_reconfigure_server = Server(CameraConfig, self._reconfigure, "axis_ptz_driver") self._diagnostic_updater = Updater() self._diagnostic_updater.setHardwareID(api.hostname) self._diagnostic_updater.add(FunctionDiagnosticTask("Last command status", self._diagnostic_last_command)) self._last_command_error = None timer = rospy.Timer(rospy.Duration(1), lambda (event): self._diagnostic_updater.update()) # set up the topics this node listens on # the callbacks are created as lambda wrappers around the Python API functions of this controller so that we # do not blow this file with more unnecessary methods if self._api.has_capabilities('AbsolutePan', 'AbsoluteTilt', 'AbsoluteZoom'): self._absolute_ptz_subscriber = rospy.Subscriber( "control/pan_tilt_zoom/absolute", PTZ, self._call_with_ptz_message(self.set_ptz), queue_size=100) if self._api.has_capabilities('RelativePan', 'RelativeTilt', 'RelativeZoom'): self._relative_ptz_subscriber = rospy.Subscriber( "control/pan_tilt_zoom/relative", PTZ, self._call_with_ptz_message(self.adjust_ptz), queue_size=100) if self._api.has_capabilities('ContinuousPan', 'ContinuousTilt', 'ContinuousZoom'): self._velocity_ptz_subscriber = rospy.Subscriber( "control/pan_tilt_zoom/velocity", PTZ, self._call_with_ptz_message(self.set_ptz_velocity), queue_size=100) if self._api.has_capabilities('AbsolutePan', 'AbsoluteTilt'): self._absolute_pan_tilt_subscriber = rospy.Subscriber( "control/pan_tilt/absolute", PTZ, self._call_with_pt_message(self.set_pan_tilt), queue_size=100) if self._api.has_capabilities('RelativePan', 'RelativeTilt'): self._relative_pan_tilt_subscriber = rospy.Subscriber( "control/pan_tilt/relative", PTZ, self._call_with_pt_message(self.adjust_pan_tilt), queue_size=100) if self._api.has_capabilities('ContinuousPan', 'ContinuousTilt'): self._velocity_pan_tilt_subscriber = rospy.Subscriber( "control/pan_tilt/velocity", PTZ, self._call_with_pt_message(self.set_pan_tilt_velocity), queue_size=100) if self._api.has_capability('AbsolutePan'): self._absolute_pan_subscriber = rospy.Subscriber( "control/pan/absolute", Float32, self._call_with_simple_message_data(self.set_pan), queue_size=100) if self._api.has_capability('AbsoluteTilt'): self._absolute_tilt_subscriber = rospy.Subscriber( "control/tilt/absolute", Float32, self._call_with_simple_message_data(self.set_tilt), queue_size=100) if self._api.has_capability('AbsoluteZoom'): self._absolute_zoom_subscriber = rospy.Subscriber( "control/zoom/absolute", Int32, self._call_with_simple_message_data(self.set_zoom), queue_size=100) if self._api.has_capability('RelativePan'): self._relative_pan_subscriber = rospy.Subscriber( "control/pan/relative", Float32, self._call_with_simple_message_data(self.adjust_pan), queue_size=100) if self._api.has_capability('RelativeTilt'): self._relative_tilt_subscriber = rospy.Subscriber( "control/tilt/relative", Float32, self._call_with_simple_message_data(self.adjust_tilt), queue_size=100) if self._api.has_capability('RelativeZoom'): self._relative_zoom_subscriber = rospy.Subscriber( "control/zoom/relative", Int32, self._call_with_simple_message_data(self.adjust_zoom), queue_size=100) if self._api.has_capability('ContinuousPan'): self._pan_velocity_subscriber = rospy.Subscriber( "control/pan/velocity", Int32, self._call_with_simple_message_data(self.set_pan_velocity), queue_size=100) if self._api.has_capability('ContinuousTilt'): self._tilt_velocity_subscriber = rospy.Subscriber( "control/tilt/velocity", Int32, self._call_with_simple_message_data(self.set_tilt_velocity), queue_size=100) if self._api.has_capability('ContinuousZoom'): self._zoom_velocity_subscriber = rospy.Subscriber( "control/zoom/velocity", Int32, self._call_with_simple_message_data(self.set_zoom_velocity), queue_size=100) if self._api.has_capabilities('AbsolutePan', 'AbsoluteTilt', 'AbsoluteZoom'): self._look_at_subscriber = rospy.Subscriber( "control/look_at", PointInRectangle, self._call_with_pir_message(self.look_at), queue_size=100) if self._api.has_capability('AutoFocus'): self._autofocus_subscriber = rospy.Subscriber( "control/camera/focus/auto", Bool, self._call_with_simple_message_data(self.set_autofocus), queue_size=100) if self._api.has_capability('AbsoluteFocus'): self._focus_subscriber = rospy.Subscriber( "control/camera/focus/absolute", Int32, self._call_with_simple_message_data(self.set_focus), queue_size=100) if self._api.has_capability('RelativeFocus'): self._focus_relative_subscriber = rospy.Subscriber( "control/camera/focus/relative", Int32, self._call_with_simple_message_data(self.adjust_focus), queue_size=100) if self._api.has_capability('ContinuousFocus'): self._focus_velocity_subscriber = rospy.Subscriber( "control/camera/focus/velocity", Int32, self._call_with_simple_message_data(self.set_focus_velocity), queue_size=100) if self._api.has_capability('AutoIris'): self._autoiris_subscriber = rospy.Subscriber( "control/camera/iris/auto", Bool, self._call_with_simple_message_data(self.set_autoiris), queue_size=100) if self._api.has_capability('AbsoluteIris'): self._iris_subscriber = rospy.Subscriber( "control/camera/iris/absolute", Int32, self._call_with_simple_message_data(self.set_iris), queue_size=100) if self._api.has_capability('RelativeIris'): self._iris_relative_subscriber = rospy.Subscriber( "control/camera/iris/relative", Int32, self._call_with_simple_message_data(self.adjust_iris), queue_size=100) if self._api.has_capability('ContinuousIris'): self._iris_velocity_subscriber = rospy.Subscriber( "control/camera/iris/velocity", Int32, self._call_with_simple_message_data(self.set_iris_velocity), queue_size=100) if self._api.has_capability('AbsoluteBrightness'): self._brightness_subscriber = rospy.Subscriber( "control/camera/brightness/absolute", Int32, self._call_with_simple_message_data(self.set_brightness), queue_size=100) if self._api.has_capability('RelativeBrightness'): self._brightness_relative_subscriber = rospy.Subscriber( "control/camera/brightness/relative", Int32, self._call_with_simple_message_data(self.adjust_brightness), queue_size=100) if self._api.has_capability('ContinuousBrightness'): self._brightness_velocity_subscriber = rospy.Subscriber( "control/camera/brightness/velocity", Int32, self._call_with_simple_message_data(self.set_brightness_velocity), queue_size=100) if self._api.has_capability('BackLight'): self._use_backlight_subscriber = rospy.Subscriber( "control/camera/backlight_compensation", Bool, self._call_with_simple_message_data(self.use_backlight_compensation), queue_size=100) if self._api.has_capabilities('IrCutFilter', 'AutoIrCutFilter'): self._use_ir_cut_filter_auto_subscriber = rospy.Subscriber( "control/camera/ir_cut_filter/auto", Bool, self._call_with_simple_message_data(self.set_ir_cut_filter_auto), queue_size=100) if self._api.has_capabilities('IrCutFilter'): self._use_ir_cut_filter_use_subscriber = rospy.Subscriber( "control/camera/ir_cut_filter/use", Bool, self._call_with_simple_message_data(self.set_ir_cut_filter_use), queue_size=100) def set_ptz(self, pan, tilt, zoom): """Command the PTZ unit with an absolute pose taking into account flips and mirroring. :param pan: The desired pan. In None, pan is not commanded at all. The value is automatically normalized to <-180,+180> :type pan: float :param tilt: The desired tilt. In None, tilt is not commanded at all. The value is automatically normalized to <-180,+180> :type tilt: float :param zoom: The desired zoom level. In None, zoom is not commanded at all. :type pan: int :return: The pan, tilt and zoom values that were really applied (e.g. the cropped and normalized input) :rtype: tuple :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move from -170 to +170 pan is requested). .. note:: Since the pan and tilt values are periodic and normalization takes place in this function, you can simply call this command in loops like e.g. ``for pan in range(0,3600,30): move_ptz_absolute(pan)`` to rotate the camera 10 times. """ (pan, tilt) = self._apply_flip_and_mirror_absolute(pan, tilt) return self._api.move_ptz_absolute(pan, tilt, zoom) def adjust_ptz(self, pan, tilt, zoom): """Command the PTZ unit with a relative pose shift taking into account flips and mirroring. :param pan: The pan change. In None or 0, pan remains unchanged. The value is automatically normalized to <-360,+360>. May be negative. :type pan: float :param tilt: The tilt change. In None or 0, tilt remains unchanged. The value is automatically normalized to <-360,+360>. May be negative. :type tilt: float :param zoom: The zoom change. In None or 0, zoom remains unchanged. May be negative. :type pan: int :return: The pan, tilt and zoom change values that were really applied (e.g. the cropped and normalized input) :rtype: tuple :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move by 300 deg pan is requested). """ (pan, tilt) = self._apply_flip_and_mirror_relative(pan, tilt) return self._api.move_ptz_relative(pan, tilt, zoom) def set_ptz_velocity(self, pan, tilt, zoom): """Command the PTZ unit velocity in terms of pan, tilt and zoom taking into account flips and mirroring. :param pan: Pan speed. In None or 0, pan remains unchanged. Pan speed is aperiodic (can be higher than 360). May be negative. :type pan: int :param tilt: Tilt speed. In None or 0, tilt remains unchanged. Tilt speed is aperiodic (can be higher than 360). May be negative. :type tilt: int :param zoom: Zoom speed. In None or 0, zoom remains unchanged. May be negative. :type pan: int :return: The pan, tilt and zoom change values that were really applied (e.g. the cropped and normalized input) :rtype: tuple :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution. .. note:: It is not clearly stated in the API, but practically any following absolute/relative pose command sets velocity to zero. """ (pan, tilt) = self._apply_flip_and_mirror_velocity(pan, tilt) return self._api.set_ptz_velocity(pan, tilt, zoom) def set_pan_tilt(self, pan, tilt): """Command the PTZ unit with an absolute pan and tilt taking into account flips and mirroring. :param pan: The desired pan. In None, pan is not commanded at all. The value is automatically normalized to <-180,+180> :type pan: float :param tilt: The desired tilt. In None, tilt is not commanded at all. The value is automatically normalized to <-180,+180> :type tilt: float :return: The pan and tilt values that were really applied (e.g. the cropped and normalized input) :rtype: tuple :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move from -170 to +170 pan is requested). .. note:: Since the pan and tilt values are periodic and normalization takes place in this function, you can simply call this command in loops like e.g. ``for pan in range(0,3600,30): move_ptz_absolute(pan)`` to rotate the camera 10 times. """ (pan, tilt) = self._apply_flip_and_mirror_absolute(pan, tilt) return self._api.move_ptz_absolute(pan=pan, tilt=tilt)[slice(0, 2)] def adjust_pan_tilt(self, pan, tilt): """Command the PTZ unit with a relative pan and tilt taking into account flips and mirroring. :param pan: The pan change. In None or 0, pan remains unchanged. The value is automatically normalized to <-360,+360>. May be negative. :type pan: float :param tilt: The tilt change. In None or 0, tilt remains unchanged. The value is automatically normalized to <-360,+360>. May be negative. :type tilt: float :return: The pan and tilt change values that were really applied (e.g. the cropped and normalized input) :rtype: tuple :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move by 300 deg pan is requested). """ (pan, tilt) = self._apply_flip_and_mirror_relative(pan, tilt) return self._api.move_ptz_relative(pan=pan, tilt=tilt)[slice(0, 2)] def set_pan_tilt_velocity(self, pan, tilt): """Command the PTZ unit velocity in terms of pan, tilt and zoom taking into account flips and mirroring. :param pan: Pan speed. In None or 0, pan remains unchanged. Pan speed is aperiodic (can be higher than 360). May be negative. :type pan: int :param tilt: Tilt speed. In None or 0, tilt remains unchanged. Tilt speed is aperiodic (can be higher than 360). May be negative. :type tilt: int :return: The pan and tilt velocity values that were really applied (e.g. the cropped and normalized input) :rtype: tuple :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution. .. note:: It is not clearly stated in the API, but practically any following absolute/relative pose command sets velocity to zero. """ (pan, tilt) = self._apply_flip_and_mirror_velocity(pan, tilt) return self._api.set_ptz_velocity(pan=pan, tilt=tilt)[slice(0, 2)] def set_pan(self, pan): """Command an absolute pan taking into account flips and mirroring. :param pan: The desired pan. The value is automatically normalized to <-180,+180> :type pan: float :return: The pan value that was really applied (e.g. the cropped and normalized input) :rtype: float :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move from -170 to +170 pan is requested). .. note:: Since the pan and tilt values are periodic and normalization takes place in this function, you can simply call this command in loops like e.g. ``for pan in range(0,3600,30): move_ptz_absolute(pan)`` to rotate the camera 10 times. """ (pan, tilt) = self._apply_flip_and_mirror_absolute(pan, 0) return self._api.move_ptz_absolute(pan=pan)[0] def set_tilt(self, tilt): """Command an absolute tilt taking into account flips and mirroring. :param tilt: The desired tilt. The value is automatically normalized to <-180,+180> :type tilt: float :return: The tilt value that was really applied (e.g. the cropped and normalized input) :rtype: float :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move from -170 to +170 pan is requested). .. note:: Since the pan and tilt values are periodic and normalization takes place in this function, you can simply call this command in loops like e.g. ``for pan in range(0,3600,30): move_ptz_absolute(pan)`` to rotate the camera 10 times. """ (pan, tilt) = self._apply_flip_and_mirror_absolute(0, tilt) return self._api.move_ptz_absolute(tilt=tilt)[1] def set_zoom(self, zoom): """Command an absolute zoom. :param zoom: The desired zoom level. :type zoom: int :return: The zoom value that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move from -170 to +170 pan is requested). .. note:: Since the pan and tilt values are periodic and normalization takes place in this function, you can simply call this command in loops like e.g. ``for pan in range(0,3600,30): move_ptz_absolute(pan)`` to rotate the camera 10 times. """ return self._api.move_ptz_absolute(zoom=zoom)[2] def adjust_pan(self, pan): """Command a relative pan taking into account flips and mirroring. :param pan: The pan change. The value is automatically normalized to <-360,+360>. May be negative. :type pan: float :return: The pan change value that was really applied (e.g. the cropped and normalized input) :rtype: float :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move by 300 deg pan is requested). """ (pan, _) = self._apply_flip_and_mirror_relative(pan, 0) return self._api.move_ptz_relative(pan=pan)[0] def adjust_tilt(self, tilt): """Command a relative tilt taking into account flips and mirroring. :param tilt: The tilt change. The value is automatically normalized to <-360,+360>. May be negative. :type tilt: float :return: The tilt change value that was really applied (e.g. the cropped and normalized input) :rtype: float :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move by 300 deg pan is requested). """ (_, tilt) = self._apply_flip_and_mirror_relative(0, tilt) return self._api.move_ptz_relative(tilt=tilt)[1] def adjust_zoom(self, zoom): """Command a relative zoom. :param zoom: The zoom change. In None or 0, zoom remains unchanged. May be negative. :type zoom: int :return: The zoom change value that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution (which might take a while if e.g. a move by 300 deg pan is requested). """ return self._api.move_ptz_relative(zoom=zoom)[2] def set_pan_velocity(self, pan): """Command pan velocity. :param pan: Pan speed. Pan speed is aperiodic (can be higher than 360). May be negative. :type pan: int :return: The pan velocity that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution. .. note:: It is not clearly stated in the API, but practically any following absolute/relative pose command sets velocity to zero. """ (pan, _) = self._apply_flip_and_mirror_velocity(pan, 0) return self._api.set_ptz_velocity(pan=pan)[0] def set_tilt_velocity(self, tilt): """Command tilt velocity. :param tilt: Tilt speed. Tilt speed is aperiodic (can be higher than 360). May be negative. :type tilt: int :return: The tilt velocity that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution. .. note:: It is not clearly stated in the API, but practically any following absolute/relative pose command sets velocity to zero. """ (_, tilt) = self._apply_flip_and_mirror_velocity(0, tilt) return self._api.set_ptz_velocity(tilt=tilt)[1] def set_zoom_velocity(self, zoom): """Command zoom velocity. :param zoom: Zoom speed. May be negative. :type zoom: int :return: The zoom velocity that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: This call doesn't wait for the command to finish execution. .. note:: It is not clearly stated in the API, but practically any following absolute/relative pose command sets velocity to zero. """ return self._api.set_ptz_velocity(zoom=zoom)[2] def look_at(self, x, y, image_width, image_height): """Point the camera center to a point with the given coordinates in the camera image. :param x: X coordinate of the look-at point. :type x: int :param y: X coordinate of the look-at point. :type y: int :param image_width: Width of the image in pixels. :type image_width: int :param image_height: Height of the image in pixels. :type image_height: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ self._api.look_at(x, y, image_width, image_height) def set_autofocus(self, use): """Command the camera to use/stop using autofocus. :param use: True: use autofocus; False: do not use it. :type use: bool :return: use :rtype: :py:class:`bool` :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ self._api.use_autofocus(use) self._autofocus = use self._send_parameter_update("autofocus", use) return use def set_focus(self, focus, set_also_autofocus=True): """Set focus to the desired value (implies turning off autofocus). :param focus: The desired focus value. :type focus: int :param set_also_autofocus: If True and autofocus is on, turn it off first. :type set_also_autofocus: bool :return: The focus value that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ focus = self._api.set_focus(focus) self._focus = focus self._send_parameter_update("focus", focus) if set_also_autofocus: self._autofocus = False self._send_parameter_update("autofocus", False) return focus def adjust_focus(self, amount, set_also_autofocus=True): """Add the desired amount to the focus value (implies turning off autofocus). :param amount: The desired focus change amount. :type amount: int :param set_also_autofocus: If True and autofocus is on, turn it off first. :type set_also_autofocus: bool :return: The focus change amount that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ amount = self._api.adjust_focus(amount) self._focus += amount self._send_parameter_update("focus", self._focus) if set_also_autofocus: self._autofocus = False self._send_parameter_update("autofocus", False) return amount def set_focus_velocity(self, velocity): """Set the focus "speed" (implies turning off autofocus). :param velocity: The desired focus velocity. :type velocity: int :return: The focus velocity that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ return self._api.set_focus_velocity(velocity) # TODO self.focus updating def set_autoiris(self, use): """Command the camera to use/stop using auto iris adjustment. :param use: True: use auto iris adjustment; False: do not use it. :type use: bool :return: use :rtype: :py:class:`bool` :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ self._api.use_autoiris(use) self._autoiris = use self._send_parameter_update("autoiris", use) return use def set_iris(self, iris, set_also_autoiris=True): """Set iris to the desired value (implies turning off autoiris). :param iris: The desired iris value. :type iris: int :param set_also_autoiris: If True and autoiris is on, turn it off first. :type set_also_autoiris: bool :return: The iris value that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ iris = self._api.set_iris(iris) self._iris = iris self._send_parameter_update("iris", iris) if set_also_autoiris: self._autoiris = False self._send_parameter_update("autoiris", False) return iris def adjust_iris(self, amount, set_also_autoiris=True): """Add the desired amount to the iris value (implies turning off autoiris). :param amount: The desired iris change amount. :type amount: int :param set_also_autoiris: If True and autoiris is on, turn it off first. :type set_also_autoiris: bool :return: The iris change amount that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ amount = self._api.adjust_iris(amount) self._iris += amount self._send_parameter_update("iris", self._iris) if set_also_autoiris: self._autoiris = False self._send_parameter_update("autoiris", False) return amount def set_iris_velocity(self, velocity): """Set the iris "speed" (implies turning off autoiris). :param velocity: The desired iris velocity. :type velocity: int :return: The iris velocity that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ return self._api.set_iris_velocity(velocity) # TODO self.iris updating def set_brightness(self, brightness): """Set brightness to the desired value. :param brightness: The desired brightness value. :type brightness: int :return: The brightness value that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: The brightness setting has no effect on Axis 214 PTZ. """ brightness = self._api.set_brightness(brightness) self._brightness = brightness self._send_parameter_update("brightness", brightness) return brightness def adjust_brightness(self, amount): """Add the desired amount to the brightness value. :param amount: The desired brightness change amount. :type amount: int :return: The brightness change amount that was really applied (e.g. the cropped and normalized input) :rtype: int :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: The brightness setting has no effect on Axis 214 PTZ. """ amount = self._api.adjust_brightness(amount) self._brightness += amount self._send_parameter_update("brightness", self._brightness) return amount def set_brightness_velocity(self, velocity): """Set the brightness "speed". :param velocity: The desired brightness velocity. :type velocity: int :return: The brightness velocity that was really applied (e.g. the cropped and normalized input) :rtype: :py:class:`int` :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error .. note:: The brightness setting has no effect on Axis 214 PTZ. """ return self._api.set_brightness_velocity(velocity) # TODO self.brightness updating def use_backlight_compensation(self, use): """Command the camera to use/stop using backlight compensation (requires autoiris=on set before). :param use: True: use backlight compensation; False: do not use it. :type use: bool :return: use :rtype: :py:class:`bool` :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ if self._autoiris: # the compensation needs autoiris to be active self._api.use_backlight_compensation(use) self._backlight = use else: use = False self._send_parameter_update("backlight", use) return use def set_ir_cut_filter_auto(self, use): """Command the camera to use auto infrared filter (requires autoiris=on set before). :param use: True: use automatic infrared filter; False: use always. :type use: bool :return: True if the filter is always used, None if it is set to auto. :rtype: :py:class:`bool` :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ use = (None if use else True) # auto IR filter requires autoiris to be active if self._autoiris or use is not None: self._api.use_ir_cut_filter(use) self._ir_cut_filter = use else: use = True self._send_parameter_update("ircutfilter", "auto" if (use is None) else "on") return use def set_ir_cut_filter_use(self, use): """Command the camera to use/stop using infrared filter. :param use: Whether to use the filter. :type use: bool :return: use :rtype: :py:class:`bool` :raises: :py:class:`RuntimeError` if the camera doesn't have capabilities to execute the given command. :raises: :py:class:`IOError`, :py:class:`urllib2.URLError` on network communication error """ self._api.use_ir_cut_filter(use) self._ir_cut_filter = use self._send_parameter_update("ircutfilter", "on" if use else "off") return use def _reconfigure(self, config, level, subname): """Dynamic reconfigure callback. :param config: The config to be used. :type config: dict :return: The config with really used values. :rtype: dict """ if self._executing_reconfigure or self._executing_parameter_update or self._axis._executing_reconfigure: return config with self._reconfigure_mutex: self._executing_reconfigure = True try: if config.autofocus != self._autofocus: if self._api.has_capability('AutoFocus'): self.set_autofocus(config.autofocus) else: rospy.loginfo("Camera on host %s doesn't support autofocus." % self._api.hostname) except (IOError, ValueError, RuntimeError) as e: rospy.logwarn("Could not apply dynamic reconfigure for autofocus. Cause: %r" % e) try: if config.focus != self._focus: if self._api.has_capability('AbsoluteFocus'): self.set_focus(config.focus) else: rospy.loginfo("Camera on host %s doesn't support absolute focus control." % self._api.hostname) except (IOError, ValueError, RuntimeError) as e: rospy.logwarn("Could not apply dynamic reconfigure for focus. Cause: %r" % e) try: if config.autoiris != self._autoiris: if self._api.has_capability('AutoIris'): self.set_autoiris(config.autoiris) else: rospy.loginfo("Camera on host %s doesn't support autoiris." % self._api.hostname) except (IOError, ValueError, RuntimeError) as e: rospy.logwarn("Could not apply dynamic reconfigure for autoiris. Cause: %r" % e) try: if config.iris != self._iris: if self._api.has_capability('AbsoluteIris'): self.set_iris(config.iris) else: rospy.loginfo("Camera on host %s doesn't support absolute iris control." % self._api.hostname) except (IOError, ValueError, RuntimeError) as e: rospy.logwarn("Could not apply dynamic reconfigure for iris. Cause: %r" % e) try: if config.brightness != self._brightness: # there is no capability for brightness self.set_brightness(config.brightness) except (IOError, ValueError, RuntimeError) as e: rospy.logwarn("Could not apply dynamic reconfigure for brightness. Cause: %r" % e) try: if config.backlight != self._backlight: if self._api.has_capability('BackLight'): self.use_backlight_compensation(config.backlight) else: rospy.loginfo("Camera on host %s doesn't support backlight compensation." % self._api.hostname) except (IOError, ValueError, RuntimeError) as e: rospy.logwarn("Could not apply dynamic reconfigure for backlight compenstaion. Cause: %r" % e) try: ir = None if config.ircutfilter == "auto" else (config.ircutfilter == "on") if ir != self._ir_cut_filter: if self._api.has_capability('IrCutFilter'): if ir is None or self._ir_cut_filter is None: self.set_ir_cut_filter_auto(ir is None) if ir is not None: self.set_ir_cut_filter_use(ir) else: rospy.loginfo("Camera on host %s doesn't support IR cut filter control." % self._api.hostname) except (IOError, ValueError, RuntimeError) as e: rospy.logwarn("Could not apply dynamic reconfigure for IR cut filter. Cause: %r" % e) config.autofocus = self._autofocus config.focus = self._focus config.autoiris = self._autoiris config.iris = self._iris config.brightness = self._brightness config.backlight = self._backlight config.ircutfilter = ("auto" if self._ir_cut_filter is None else ("on" if self._ir_cut_filter else "off")) self._executing_reconfigure = False return config # Helper functions def _send_parameter_update(self, parameter, value): """Notify the parameter change via dynamic reconfigure. :param parameter: The parameter that has changed. :type parameter: basestring :param value: New value of the parameter. :type value: Any """ if self._executing_parameter_update: return with self._parameter_update_mutex: update = dict() update[parameter] = value self._executing_parameter_update = True if hasattr(self._axis, 'srv') and self._axis.srv is not None and not self._axis._executing_reconfigure: # axis.srv is instantiated after camera controller self._axis.srv.update_configuration(update) if hasattr(self, '_dynamic_reconfigure_server') and self._dynamic_reconfigure_server is not None \ and not self._executing_reconfigure: # this server can also be initialized later self._dynamic_reconfigure_server.update_configuration(update) self._executing_parameter_update = False def _apply_flip_and_mirror_absolute(self, pan, tilt): """Apply flipping and mirroring to absolute pan and tilt command. :param pan: The input pan. :type pan: float :param tilt: The input tilt. :type tilt: float :return: The corrected pan and tilt. :rtype: tuple """ if self._flip_vertically: tilt = -tilt if self._flip_horizontally: pan = 180 - pan if self._mirror_horizontally: pan = -pan return pan, tilt def _apply_flip_and_mirror_relative(self, pan, tilt): """Apply flipping and mirroring to relative pan and tilt command. :param pan: The input pan. :type pan: float :param tilt: The input tilt. :type tilt: float :return: The corrected pan and tilt. :rtype: tuple """ if self._flip_vertically: tilt = -tilt if self._flip_horizontally: pan = -pan if self._mirror_horizontally: pan = -pan return pan, tilt def _apply_flip_and_mirror_velocity(self, pan, tilt): """Apply flipping and mirroring to velocity pan and tilt command. :param pan: The input pan. :type pan: float :param tilt: The input tilt. :type tilt: float :return: The corrected pan and tilt. :rtype: tuple """ if self._flip_vertically: tilt = -tilt if self._flip_horizontally: pan = -pan if self._mirror_horizontally: pan = -pan return pan, tilt def _record_diagnostics(self, func): """Wrap a function call and record diagnostics about if it has thrown an exception or not. :param func: The function to be wrapped. :type func: function :return: The wrapped function. :rtype: function """ def new_func(msg): try: func(msg) self._last_command_error = None except Exception, e: self._last_command_error = e raise e return new_func