ArmarX Observers

Observers ease the handling and processing of sensor data by bundling and preprocessing the sensor data for all consumers.

Structure of an Observer

The efficient processing of sensory data is a crucial requirement of ArmarX. Unfortunately, sensory data can be large and can come at very high frequencies. To deal with this problem, ArmarX provides Observers which are responsible for the data stream of one Sensor-Actor Unit each. An Observer monitors the data stream from the Sensor-Actor Unit, processes the data and outputs it in multiple DataFields over multiple Channels. The DataFields that are used to send individual chunks of data containing Variants. Therefore, basic types, such as Bool, Float, Int or String, as well as complex classes, such as Position, Orientation or FramedPose are supported. Each DataField is identified via its unique name within the ArmarX distributed application. Channels subsume datafields which are updated synchronously.

Observers also provide the Condition Checks that are available for the respective data stream. These Condition Checks can then be compiled to more complex Conditions which are evaluated by the ConditionHandler.

The structure of an Observer: An observer consists of Channels and condition checks.

Channels and Datafields

The data inside an Observer is organized in a two level hierarchy: The channels on the top-level and the datafields on the bottom-level. The are two reasons for this: semantic grouping of datafields and better performance. The developer of an Observer should always group the datafields of an observer semantically in channels. For example in the KinematicUnitObserver each channels contains one type of sensor data like joint angle and the datafields in that channel represent the specific joints. The better performance of the channels grounds on the fact that channels are evaluated as a batch. If a datafield is updated the conditions on this datafield are not immediately evaluated. Conditions are only updated if the function updatedChannel() is called. Thus, the Observer implementer needs to call this function, whenever a new batch of datafields was updated (or after each update if needed). The reason for this is: Conditions often range over several datafields. If the conditions would be evaluated on each datafield update, one complex condition over several datafields would be checked too often.

Condition Checks and Conditions

The evaluation of Conditions.

Observers allow to define Condition Checks on sensor channels provided by Sensor-Actor Units. These Condition Checks can be combined to more complex Conditions, which eventually map sensor data channels to events. Conditions installed in the ConditionHandler evaluate the participating Condition Checks and fire events in case the conditions are satisfied. Other components can wait for the respective events and therefore react on certain situations in the sensory data.

Conditions have several convenient characteristics

  • Conditions map sensor data channels onto events
  • Conditions can be spread over multiple different data fields
  • Conditions can be chained deliberately
  • Conditions can be distributed over multiple Observers

ArmarX ships a set of default ConditionChecks that work on the basic Variant data types:

Filtering the data

It is possible to easily install filters for datafields on observers. First the DatafieldRef for a datafield needs to be created:

DatafieldRefPtr datafield = new DatafieldRef(observer.get(), "exampleChannel", "exampleDataFieldInt");

This DatafieldRef can then be used to install the filter on the observer. For this a proxy to the observer is needed.

DatafieldRefPtr filtered = DatafieldRefPtr::dynamicCast(observer->createFilteredDatafield(
new filters::MedianFilter(11 /*windowSize*/),
datafield));

From this point onwards until removal the filter value is updated whenever the orignal datafield is updated. The filter cache starts from the moment the filter is installed. So it might take a few update intervals to receive well filtered values. A filter needs to be created and passed to the function. There are several filters available, like the GaussianFilter or MedianFilter. Some of the filters can also be configured, e.g. the window size of the filter. A list of all available filters can be found at Available filters on datafields.

With the DatafieldRef returned from the Observer::createFilteredDatafield() function the filtered value can be queried from the same observers:

ARMARX_INFO_S << "filtered " << filtered->getDataField()->getInt();

If not needed anymore the filter should be removed, since depending on the filter and the window size the calculation could be costly:

observer->removeFilteredDatafield(filtered);

For a tutorial for sensor-actor units refer to refer to How to Implement a Custom Unit and observers refer to How to Implement a Custom Observer.

armarx::DatafieldRefPtr
IceInternal::Handle< DatafieldRef > DatafieldRefPtr
Definition: Observer.h:44
armarx::VariantType::DatafieldRef
const VariantTypeId DatafieldRef
Definition: DatafieldRef.h:169
ARMARX_INFO_S
#define ARMARX_INFO_S
Definition: Logging.h:195