Skip to content

Debugging Support

Debugging mechanisms are an integral part of the B-Human framework. They are all based on debug message queues. These mechanisms are available in all project configurations except for Release on the actual NAO.

Debug Requests

Debug requests are used to enable and disable parts of the source code. They can be seen as runtime switches for debugging.

The debug requests can be used to trigger certain debug messages to be sent as well as to switch on certain parts of algorithms. They can be sent using the SimRobot software when connected to a NAO (cf. command dr). The following macros (defined in Src/Libs/Debugging/Debugging.h) ease the use of the mechanism as well as hide its implementation details:

  • DEBUG_RESPONSE(<id>) executes the following statement or block if the debug request with the name id is enabled.
  • DEBUG_RESPONSE_ONCE(<id>) executes the following statement or block once when the debug request with the name id is enabled.
  • DEBUG_RESPONSE_NOT(<id>) executes the following statement or block if the debug request with the name id is not enabled.

These macros can be used anywhere in the source code, allowing for easy debugging. For example:

DEBUG_RESPONSE("test") test();

This statement calls the method test() if the debug request with the identifier "test" is enabled. Debug requests are commonly used to send messages on request as the following example shows:

DEBUG_RESPONSE("sayHello") OUTPUT(idText, text, "Hello");

This statement sends the text "Hello" if the debug request with the name "sayHello" is activated. Please note that only those debug requests are usable that are in the current path of execution. This means that only debug requests in those modules can be activated that are currently executed. To determine which debug requests are currently available, a method called polling is employed. It asks each debug response to report the name of the debug request that would activate it. This information is collected and sent to the PC (cf. command poll).

Debug Images

Debug images are used for low level visualization of image processing debug data. They can either be displayed as background image of an image view or in a color space view). Each debug image consists of pixels in one of the formats RGB, BGRA, YUYV, YUV, Grayscale, Hue, Binary, and Edge2, which are defined in Src/Libs/ImageProcessing/PixelTypes.h. In the RGB, BGRA, YUYV, and YUV formats, pixels are made up of four unsigned byte values, each representing one of the color channels of the format. The YUYV format is special as one word of the debug image describes two image pixels by specifying two luminance values, but only one value per U and V channel. Thus, it resembles the YUV422 format of the images supplied by the NAO's cameras. Debug images in the Grayscale format only contain a single channel of unsigned byte values describing the luminance of the associated pixel. The Hue and Binary formats consist of only one channel, too. The Edge2 format has one channel each for the intensity in x and y direction.

Debug images are supposed to be declared as instances of the template class Image, instantiated with one of the pixel formats named above, or the CameraImage class, which is only used for images provided by the NAO's cameras. The following macros are used to transfer debug images to a connected PC:

  • SEND_DEBUG_IMAGE(<id>, <image>, [<method>]) sends the debug image to the PC. The identifier given to this macro is the name by which the image can be requested. The optional parameter is a pixel type that allows to specify how Edge2 pixels are drawn.
  • COMPLEX_IMAGE(<id>) only executes the following statement if the creation of a certain debug image is requested. For debug images that require complex instructions to paint, it can significantly improve the performance to encapsulate the drawing instructions in this macro (and maybe additionally in a separate method).

These macros can be used anywhere in the source code, allowing for easy creation of debug images. For example:

class Test
{
private:
  Image<GrayscaledPixel> testImage;

public:
  void doSomething()
  {
    // [...]
    COMPLEX_IMAGE("test") draw();
    // [...]
  }

  void draw()
  {
    testImage.setResolution(640, 480);
    memset(testImage[0], 0x7F, testImage.width * testImage.height);
    SEND_DEBUG_IMAGE("test", testImage);
  }
};

The example calls the draw() method if the "test" image was requested, which then initializes a grayscale debug image, paints it gray, and sends it to the PC.

Debug Drawings

Debug drawings provide a virtual 2-D drawing canvas and a number of drawing primitives, as well as mechanisms for requesting, sending, and drawing these primitives to the screen of the PC. In contrast to debug images, which are raster-based, debug drawings are vector-based, i.e., they store drawing instructions instead of a rasterized image. Each drawing has an identifier and an associated type that enables the application on the PC to render the drawing to the right kind of drawing canvas. In the B-Human system, two standard drawing canvases are provided, called "drawingOnImage" and "drawingOnField". This refers to the two standard applications of debug drawings, namely drawing in the system of coordinates of an image and drawing in the system of coordinates of the field. Hence, all debug drawings of type "drawingOnImage" can be displayed in an image view and all drawings of type "drawingOnField" can be rendered into a field view.

The creation of debug drawings is encapsulated in a number of macros in Src/Libs/Debugging/DebugDrawings.h. Most of the drawing macros have parameters such as pen style, fill style, or color. Available pen styles (solidPen, dashedPen, dottedPen, and noPen) and fill styles (solidBrush and noBrush) are part of the namespace Drawings. Colors can be specified as ColorRGBA. The class also contains a number of predefined colors such as ColorRGBA::red. A few examples for drawing macros are:

  • DECLARE_DEBUG_DRAWING(<id>, <type>) declares a debug drawing with the specified id and type.
  • COMPLEX_DRAWING(<id>) only executes the following statement or block if the creation of a certain debug drawing is requested. This can significantly improve the performance when a debug drawing is not requested, because for each drawing instruction it has to be tested whether it is currently required or not. By encapsulating them in this macro (and maybe in addition in a separate method), only a single test is required. However, the macro DECLARE_DEBUG_DRAWING must be placed outside of COMPLEX_DRAWING.
  • DEBUG_DRAWING(<id>, <type>) is a combination of DECLARE_DEBUG_DRAWING and COMPLEX_DRAWING. It declares a debug drawing with the specified id and type. It also executes the following statement or block if the debug drawing is requested.
  • CIRCLE(<id>, <x>, <y>, <radius>, <penWidth>, <penStyle>, <penColor>, <fillStyle>, <fillColor>) draws a circle with the specified radius, pen width, pen style, pen color, fill style, and fill color at the coordinates (x, y) on the virtual drawing canvas.
  • LINE(<id>, <x1>, <y1>, <x2>, <y2>, <penWidth>, <penStyle>, <penColor>) draws a line with the pen color, width, and style from the point (x1, y1) to the point (x2, y2) on the virtual drawing canvas.
  • DOT(<id>, <x>, <y>, <penColor>, <fillColor>) draws a dot with the pen color and fill color at the coordinates (x,y) on the virtual drawing canvas. There also exist two macros MID_DOT and LARGE_DOT with the same parameters that draw dots of larger size.
  • DRAW_TEXT(<id>, <x>, <y>, <fontSize>, <color>, <text>) writes a text with a font size in a color to a virtual drawing canvas. The left end of the baseline of the text will be at coordinates (x, y).
  • TIP(<id>, <x>, <y>, <radius>, <text>) adds a tool tip to the drawing that will pop up when the mouse cursor is closer to the coordinates (x, y) than the given radius.
  • ORIGIN(<id>, <x>, <y>, <angle>) changes the system of coordinates. The new origin will be at (x, y) and the system of coordinates will be rotated by angle (given in radians). All further drawing instructions, even in other debug drawings that are rendered afterwards in the same view, will be relative to the new system of coordinates, until the next origin is set. The origin itself is always absolute, i.e. a new origin is not relative to the previous one.
  • THREAD(<id>, <thread>) defines as part of which thread a drawing should be managed. The default is the thread that contains the drawing. However, if a drawing in a thread belongs to data that was received from another thread, it should be linked to that thread by using this macro. This is particularly true for image drawings that always have to be linked to one of the two image processing threads, i.e. Upper and Lower, but it can also be necessary for field drawings.

These macros can be used wherever statements are allowed in the source code. For example:

DECLARE_DEBUG_DRAWING("test", "drawingOnField");
CIRCLE("test", 0, 0, 1000, 10, Drawings::solidPen, ColorRGBA::blue,
       Drawings::solidBrush, ColorRGBA(0, 0, 255, 128));

This example initializes a drawing called test of type drawingOnField that draws a blue circle with a solid border and a semi-transparent inner area.

3-D Debug Drawings

In addition to the aforementioned two-dimensional debug drawings, there is a second set of macros in Src/Libs/Debugging/DebugDrawings3D.h, which provides the ability to create three-dimensional debug drawings.

3-D debug drawings can be declared with the macro DECLARE_DEBUG_DRAWING3D(<id>, <type>). The id can then be used to add three dimensional shapes to this drawing. type defines the coordinate system in which the drawing is displayed. It can be set to "field", "robot", "camera", or any named part of the robot model in the scene description. Note that drawings directly attached to hinges will be drawn relative to the base of the hinge, not relative to the moving part. Drawings of the type "field" are drawn relative to the center of the field, whereas drawings of the type "robot" are drawn relative to the origin of the robot, i.e. the middle between the two hip joints. It is often used as reference frame for 3-D debug drawings in the current code. The type "camera" is an alias for the camera of the perception thread from which the drawing is generated.

The parameters of macros adding shapes to a 3-D debug drawing start with the id of the drawing this shape will be added to, followed, e.g., by the coordinates defining a set of reference points (such as corners of a rectangle), and finally the drawing color. Some shapes also have other parameters such as the thickness of a line. Here are a few examples for shapes that can be used in 3-D debug drawings:

  • LINE3D(<id>, <fromX>, <fromY>, <fromZ>, <toX>, <toY>, <toZ>, <size>, <color>) draws a line between the given points.
  • QUAD3D(<id>, <corner1>, <corner2>, <corner3>, <corner4>, <color>) draws a quadrangle with its four corner points given as 3-D vectors and specified color.
  • SPHERE3D(<id>, <x>, <y>, <z>, <radius>, <color>) draws a sphere with specified radius and color at the coordinates (x, y, z).
  • COORDINATES3D(<id>, <length>, <width>) draws the axes of the coordinate system with specified length and width into positive direction.
  • COMPLEX_DRAWING3D(<id>) only executes the following statement or block if the creation of the debug drawing is requested (similar to COMPLEX_DRAWING(<id>) for 2-D drawings).
  • DEBUG_DRAWING3D(<id>, <type>) is a combination of DECLARE_DEBUG_DRAWING3D and COMPLEX_DRAWING3D. It declares a debug drawing with the specified id and type. It also executes the following statement or block if the debug drawing is requested.

The header file furthermore defines some macros to scale, rotate, and translate an entire 3-D debug drawing:

  • SCALE3D(<id>, <x>, <y>, <z>) scales all drawing elements by given factors for x, y, and z axis.
  • ROTATE3D(<id>, <x>, <y>, <z>) rotates the drawing counterclockwise around the three axes by given radians.
  • TRANSLATE3D(<id>, <x>, <y>, <z>) translates the drawing according to the given coordinates.

An example for 3-D debug drawings (analogous to the example for regular 2-D debug drawings):

DECLARE_DEBUG_DRAWING3D("test3D", "field");
SPHERE3D("test3D", 0, 0, 250, 75, ColorRGBA::blue);

This example initializes a 3-D debug drawing called "test3D", which draws a blue sphere. Because the drawing is of type "field" and the origin of the field coordinate system is located in the center of the field, the sphere's center will appear 250 mm above the center point.

In order to add a drawing to the scene, a debug request for a 3-D debug drawing with the ID of the desired drawing prefixed by debugDrawing3d: must be sent. For example, to see the drawing generated by the code above, the command would be dr debugDrawing3d:test3D. The rendering of debug drawings can be configured for individual scene views by right-clicking on the view and selecting the desired shading and occlusion mode in the "Drawings Rendering" submenu.

Plots

The macro PLOT(<id>, <number>) allows plotting data over time. The plot view) will keep a history of predefined size of the values sent by the macro PLOT and plot them in different colors. Hence, the previous development of certain values can be observed as a time series. Each plot has an identifier that is used to distinguish the different plots from each other. A plot view can be created with the console commands vp and vpd.

For example, the following code statement plots the measurements of the gyro for the pitch axis in degrees. It should be placed in a part of the code that is executed regularly, e.g. inside the update method of a module.

PLOT("gyroY", theInertialSensorData.gyro.y().toDegrees());

The macro DECLARE_PLOT(<id>) allows using the PLOT(<id>, <number>) macro within a part of code that is not regularly executed as long as the DECLARE_PLOT(<id>) macro is executed regularly.

Modify

The macro MODIFY(<id>, <object>) allows inspecting and modifying data on the actual robot during runtime. Every streamable data type can be manipulated and read, because its inner structure was gathered in the TypeRegistry. This allows generic manipulation of runtime data using the console commands get and set. The first parameter of MODIFY specifies the identifier that is used to refer to the object from the PC, the second parameter is the object to be manipulated itself. When an object is modified using the console command set, it will be overridden each time the MODIFY macro is executed.

int i = 3;
MODIFY("i", i);
MotionRequest m;
MODIFY("representation:MotionRequest", m);

The macro PROVIDES of the module framework includes the MODIFY macro for the representation provided. For instance, if a representation Foo is provided by PROVIDES(Foo), it is modifiable under the name representation:Foo. If a representation provided should not be modifiable, e.g., because its serialization does not register all member variables, it must be provided using PROVIDES_WITHOUT_MODIFY.

Stopwatches

Stopwatches allow the measurement of the execution time of parts of the code. The macro STOPWATCH(<id>) (declared in Src/Libs/Debugging/Stopwatch.h) measures the runtime of the statement or block that follows. id is a string used to identify the time measurement. To activate the time measurement of all stopwatches, the debug request dr timing has to be sent. The measured times can be seen in a timing view. By default, a stopwatch is already defined for each representation that is currently provided, and for the overall execution of all modules in each thread.

An example to measure the runtime of a method called myCode:

STOPWATCH("myCode") myCode();

Using the STOPWATCH macro also adds a plot with the stopwatch identifier prefixed by stopwatch:.

Annotations

To enhance the usage of log files, there is the possibility for our modules to annotate individual frames of a log file with important information. This is, for example, information about a change of game state, the execution of a kick, or other information that may help us to debug our code. Thereby, when replaying the log file, we may consult a list of those annotations to see whether specific events actually did happen during the game. In addition, if an annotation was recorded, we are able to directly jump to the corresponding frame of the log file to review the emergence and effects of the event without having to search through the whole log file.

This feature is accessed via the ANNOTATION macro. An example is given below:

#include "Debugging/Annotation.h"
...
ANNOTATION("GroundContactDetector", "Lost GroundContact");

It is advised to be careful to not send an annotation in each frame because this will clutter the log file. When using annotations inside the behavior, the skill Annotation should be used to make sure annotations are not sent multiple times.

The annotations recorded can be displayed while replaying the log file in the annotation view.


Last update: October 12, 2023