Robotics StackExchange | Archived questions

How to be 100% sure that only one callback is being executed at a time?

Hi all,

I'll try to better explain my problem. I'm writing a node that subscribes to rgb and depth image topics. Since the frame rate of the rgb-d camera is 30Hz, I guess my callback is executed 30 times per second. Now, my problem is that I have to perform heavy computations and the code in the callback is executed in ~0.2s (i.e. 5Hz).

Is there a way to execute the callback not for every frame published by the camera but just at certain time intervals? That is, how can I skip some frames while the callback is still running?

Thanks.

Asked by schizzz8 on 2018-04-05 04:27:50 UTC

Comments

The callback will only be executed at 30Hz if that is possible. If processing takes too long, the queue_size setting will determine what happens with messages that arrive in the meantime.

I'm slightly confused by your question text though: it doesn't seem to correspond to the title. With ..

Asked by gvdhoorn on 2018-06-23 04:22:40 UTC

.. a regular ros::spin(), all callbacks are processed sequentially, as it employs a single-threaded processing model. So that would answer the question in the title.

The question in your text is different though: there you ask how to 'overlay' a synchronous, periodic sampling style on ..

Asked by gvdhoorn on 2018-06-23 04:24:08 UTC

.. an asynchronous, event-based dataflow system like pub-sub in ROS.

That is possible, but less than ideal (due to undersampling, risk of lost events, etc). Setting the queue size for your subscriber to something appropriate already subsamples the event stream, but the exact rate at which that ..

Asked by gvdhoorn on 2018-06-23 04:26:20 UTC

.. happens will depend on how long processing in the callback actually takes, how the process that hosts your node is scheduled, etc.

A more deterministic approach could be to use the Cache, from the message_filters pkg. That will store ..

Asked by gvdhoorn on 2018-06-23 04:27:21 UTC

.. incoming msgs for you in a circular buffer, from which you can retrieve messages based on their timestamp.

If you need determinism (or as much as you can expect from a non-real-time pub-sub middleware), I'd take a look at Cache and try to avoid implementing schemes with flags.

Asked by gvdhoorn on 2018-06-23 04:29:44 UTC

@gvdhoorn: Thanks for your hints. I'll try and let you know!

Asked by schizzz8 on 2018-06-25 03:33:11 UTC

Answers

Hi! Try something like this:

...
ros::Rate rate(<yourFrequency>);
...
while (ros::ok())
{
ros::spinOnce();
rate.sleep();
}

Also, be sure to set the right buffer length for your subscriber, in order to prevent your node to elaborate old data.

Asked by Chaos on 2018-04-05 04:40:15 UTC

Comments

It's the first thing I checked, but I don't think that solves my problem, because spinOnce will call all the pending callbacks (without skipping any) http://wiki.ros.org/roscpp/Overview/Callbacks%20and%20Spinning

Asked by schizzz8 on 2018-04-05 04:42:13 UTC

Also, be sure to set the right buffer length for your subscriber, in order to prevent your node to elaborate old data: do you mean that if I set the buffer length equal to one, I will skip old frames?

Asked by schizzz8 on 2018-04-05 04:45:08 UTC

Yes! The buffer works like a FIFO queue. If you set it to "1" the callback will elaborate only the newest incoming data.

Asked by Chaos on 2018-04-05 04:46:49 UTC

I'll try, thanks!

Asked by schizzz8 on 2018-04-05 05:07:56 UTC

If it works, remember to set the post as solved

Asked by Chaos on 2018-04-05 05:08:43 UTC

I just have one last question, I'm using message_filters to get synchronized rgb and depth frame, where do I set the buffer length?

Asked by schizzz8 on 2018-04-05 05:09:33 UTC

With the message_filters synchronization policy, only the newest incoming data are elaborated by the callback.

Asked by Chaos on 2018-04-05 05:12:43 UTC

found it, no worries!

Asked by schizzz8 on 2018-04-05 05:15:53 UTC

What would be the python version like to reduce callback rate to 10%?

Asked by tolga-uni-lu on 2018-04-05 06:40:38 UTC

I think this is not the solution you want. If your callback is still running when a new message arrives, it will be put into the queue (where it possibly replaces an older message). This approach here also throttles other callbacks in your node.

Asked by NEngelhard on 2018-04-05 06:58:40 UTC

@tolga-uni-lu sorry, but I don't know python programming.

Asked by Chaos on 2018-04-05 07:01:19 UTC

@NEngelhard after some experiments, I realized that it's as you say. Do you have any suggestions to solve this problem?

Asked by schizzz8 on 2018-06-20 06:57:47 UTC

Hi @schizzz8,

I have created a simple publisher at 30hz and a subscriber that simulates a process that takes 0.2 seconds (works at 5hz).

This script demonstrates that you can receive an image and do not have its value updated while you are processing it.

I'm using two variables to control the flow: new_msg and processing. I think it can help you.

It is written in python, but you can use the same logic (basically because it does not rely on ROS Python topic queue, which could be or not different of ROScpp).

Publisher (Simulating your camera publisher)

#!/usr/bin/env python

import rospy
from std_msgs.msg import Int32

def publisher():

    pub = rospy.Publisher('topic_name', Int32, queue_size=10)
    rospy.init_node('publisher')
    r = rospy.Rate(30) # 30hz

    count = 1
    msg = Int32()
    while not rospy.is_shutdown():
        msg.data = count
        count = count + 1 if count < 30 else 1
        pub.publish(msg)
        rospy.loginfo(msg)
        r.sleep()

if __name__ == "__main__":
    publisher()

Subscriber (Simulating your script that processes a given image)

#!/usr/bin/env python

import rospy
from std_msgs.msg import Int32

processing = False
new_msg = False

msg = None
def callback(data):
    global processing, new_msg, msg
    if not processing:
        new_msg = True
        msg = data

def listener():
    global processing, new_msg, msg
    rospy.init_node('subscriber')
    rospy.Subscriber("topic_name", Int32, callback)
    # just to use the sleep method
    r = rospy.Rate(5)
    while not rospy.is_shutdown():
        if new_msg:
            # set processing to True
            new_msg = False
            processing = True
            # simulate a process that takes 0.2 seconds
            rospy.loginfo(msg)
            r.sleep()
            # uncomment below to see the same value being "processed"
            # rospy.loginfo(msg)
            # r.sleep()
            # set processing to False
            processing = False

if __name__ == "__main__":
    listener()

I hope it can help you. You can see it being executed at this video (https://youtu.be/XoirEyK4J2A)

Cheers

Asked by marcoarruda on 2018-06-22 19:12:21 UTC

Comments

What exactly is this supposed to demonstrate? Can you please update your answer with some explanation?

Asked by jayess on 2018-06-22 20:06:24 UTC

Hi, I'm demonstrating that it's possible to have the same value being processed, not updating it even after a callback call, in a process that can take longer than the callback rate I have updated with this explanation. Thanks

Asked by marcoarruda on 2018-06-22 20:17:37 UTC