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.
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.
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:
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.