Dumping the Training Data (for Debugging/Demonstration)

For debugging/demonstration purposes, the training data can be dumped to a file. This is not strictly needed to use the DALI pipeline, but it is useful to have the option to do so. For that, we use the accvlab.optim_test_tools.TensorDumper. Please also see the Optimization Testing Tools documentation for more details.

Here, we will focus on two points regarding the dumping:
  • How to enable the dumping and configure the dumping (e.g. dump custom data types).

  • How to perform the dumping, including some advanced features (e.g. draw bounding boxes into the images using custom code which is only executed when the dumping is enabled).

Enable & Configure the Dumping

Note that one key point of the optim_test_tools package is that the functionality can be centrally controlled (enabled/disabled and configured; also see the Optimization Testing Tools documentation). Here, this is done in the main training script (demos/streampetr/tools/train.py) as demonstrated in the following snippet.

demos/streampetr/tools/train.py
50# ===== Profiling and Dumping Configuration =====
51
52#@NOTE 
53# Set `enable_dumper` to `True` to enable the dumper. The number of iterations 
54# to dump is set by `dumper_iter_count`. Note that this is hard-coded here instead
55# of being part of the config because the dumper is only used for debugging purposes 
56# and should be kept disabled during actual training.
57ENABLE_DUMPER = False
58DUMPER_ITER_COUNT = 10
59
60#@NOTE 
61# If the dumper should be enabled, do so and also register custom converters for the non-standard 
62# types that need to be dumped.
63if ENABLE_DUMPER:
64    rank = os.environ['RANK'] if 'RANK' in os.environ else (
65        os.environ['LOCAL_RANK'] if 'LOCAL_RANK' in os.environ else 0
66    )
67    #@NOTE 
68    # Enable the dumper and set the dump directory according to the rank. This will allow dumping from all
69    # worker processes, without introducing conflicts.
70    dumper.enable(f"/streampetr/dumps/rank{rank}")
71    #@NOTE: Perform dumping for a fixed number of iterations and then exit.
72    dumper.perform_after_dump_count(DUMPER_ITER_COUNT, exit)
73    #@NOTE: Register custom converters for the non-standard types that need to be dumped.
74    from mmdet3d.models.detectors.base import DC
75    from mmdet3d.core.bbox.structures.lidar_box3d import LiDARInstance3DBoxes
76    dumper.register_custom_converter(LiDARInstance3DBoxes, lambda x: {"tensor": x.tensor})
77    dumper.register_custom_converter(DC, lambda x: {"data": x.data})
78
79# ===== End of Profiling and Dumping Configuration =====

Note that the configuration whether to perform dumping and for how many iterations could be easily done using config files as well. However, as this is a debugging tool, we keep the configuration in the main training script, and leave it disabled by default.

Perform the Dumping

The dumping itself is performed in the forward_train method of the model (demos/streampetr/projects/mmdet3d_plugin/models/detectors/petr3d.py). This demonstrates how we can separate the actual dumping from the configuration. Note that here, we can focus on the dumping itself, without caring where or how the configuration is done.

demos/streampetr/projects/mmdet3d_plugin/models/detectors/petr3d.py
284            # ===== Data dumping =====
285            #@NOTE: Adding data and bounding boxes to be dumped
286            #@NOTE 
287            # First, add the data as-is. Note that we set up 'img' as a special case, as it should be dumped 
288            # as an image and needs to be permuted to be in the correct format (color channels last).
289            dumper.add_tensor_data(
290                "data", 
291                data, 
292                dump_type=TensorDumper.Type.JSON, 
293                #@NOTE 
294                # Set up 'img' as a special case, as it should be dumped as an image and needs to be permuted 
295                # to be in the correct format (color channels last).
296                dump_type_override={"img": TensorDumper.Type.IMAGE_BGR}, 
297                permute_axes_override={"img": (0, 1, 2, 4, 5, 3)}
298            )
299            # NOTE 
300            # Then, add the drawn bboxes to the dump. There is a custom implementation for drawing the 
301            # bboxes & centers into the image, and it is called here, but only if the dumper is enabled.
302            # Note that `add_drawn_bboxes_to_dump` creates the images and adds them to the dump. If
303            # dumping is not enabled, the function is not called, avoiding unnecessary overhead.
304            # Also note that as the function expects the current data as input, we use a lambda function 
305            # to pass that input and obtain a function without arguments, which is passed to `run_if_enabled`.
306            dumper.run_if_enabled(lambda: Petr3D.add_drawn_bboxes_to_dump(data))
307            # NOTE: Dump the data (if enabled, and at the location specified when enabling the dumper).
308            dumper.dump()
309            # ===== End of data dumping =====

The function add_drawn_bboxes_to_dump is a custom function that draws the bounding boxes into the images and adds them to the dump. Note that it is only called if the dumper is enabled, avoiding unnecessary overhead. The function is implemented as follows:

demos/streampetr/projects/mmdet3d_plugin/models/detectors/petr3d.py
424    # ===== Custom code for dumping the bounding boxes =====
425    @staticmethod
426    def add_drawn_bboxes_to_dump(data_dict):
427        import cv2
428        import numpy as np
429
430        #@NOTE: This is a function that draws bouding boxed into an iamge.
431        def draw_bboxes_into_image(image, bboxes, centers):
432            img_min = image.min()
433            img_max = image.max()
434            image = (image - img_min) / max(img_max - img_min, 1e-6) * 255
435            image_np = np.ascontiguousarray(image.detach().cpu().numpy().astype(np.uint8))
436            for b in range(bboxes.shape[0]):
437                try:
438                    bboxes = np.array(bboxes)
439                except:
440                    bboxes = np.array(bboxes.cpu())
441                try:
442                    centers = np.array(centers)
443                except:
444                    centers = np.array(centers.cpu())
445                color = [np.random.randint(50, 255) for _ in range(3)]
446                image_np = cv2.rectangle(image_np, (bboxes[b, :2] + 0.5).astype(int), (bboxes[b, 2:] + 0.5).astype(int), color=color)
447                image_np = cv2.circle(image_np, (centers[b, :] + 0.5).astype(int), 2, color, -1)
448                image = torch.from_numpy(image_np)
449            return image
450
451        #@NOTE: Perform the drawing
452        img = data_dict["img"]
453        img_shape = img.shape
454        #@NOTE: Generate a nested list for the individual images
455        res = [[[None] * img_shape[2] for _ in range(img_shape[1])] for _ in range(img_shape[0])]
456        #@NOTE: Iterate over the individual images
457        for i in range(img_shape[0]):
458            for j in range(img_shape[1]):
459                for k in range(img_shape[2]):
460                    #@NOTE: Ensure the expected format (channels last)
461                    img_channels_last = img[i, j, k].permute(1, 2, 0)
462                    #@NOTE: Draw the bboxes into the image and store the result
463                    drawn_image = draw_bboxes_into_image(
464                        img_channels_last, data_dict["gt_bboxes"][j][i][k], data_dict["centers2d"][j][i][k]
465                    )
466                    res[i][j][k] = drawn_image
467
468        #@NOTE: Add the drawn images to the dump. Note that we can directly add the nested list.
469        dumper.add_tensor_data("data_dict.drawn_gt_bboxes", res, dump_type=TensorDumper.Type.IMAGE_BGR)
470        # ===== End of custom code for dumping the bounding boxes =====

As demonstrated in the snippet above, the function is called in the forward_train method of the model. Here, it is only called if the dumper is enabled, avoiding unnecessary overhead.