From 4dcf33b7226db450805876a4282741326e7d9114 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Tue, 8 Mar 2022 16:35:37 -0800 Subject: [PATCH 01/22] Enable tracking scenario and add kwcococ/MOTS annotations --- PythonAPI/util/data_collector/Dockerfile | 20 +++ PythonAPI/util/data_collector/Makefile | 122 +++++++++++++ .../data_collector/carla_coco_converter.py | 164 +++++++++++++++--- .../util/data_collector/carla_data_saver.py | 135 +++++++++----- .../util/data_collector/config_schema.py | 11 +- .../util/data_collector/configs/config.yaml | 35 ++-- .../configs/config_pedestrian_spawn.yaml | 46 +++++ .../configs/config_sensors_hero.yaml | 25 ++- .../configs/config_tracking.yaml | 65 +++++++ .../util/data_collector/kwcoco_to_mots.py | 141 +++++++++++++++ .../util/data_collector/requirements.txt | 6 +- 11 files changed, 665 insertions(+), 105 deletions(-) create mode 100644 PythonAPI/util/data_collector/Dockerfile create mode 100644 PythonAPI/util/data_collector/Makefile create mode 100644 PythonAPI/util/data_collector/configs/config_pedestrian_spawn.yaml create mode 100644 PythonAPI/util/data_collector/configs/config_tracking.yaml create mode 100644 PythonAPI/util/data_collector/kwcoco_to_mots.py diff --git a/PythonAPI/util/data_collector/Dockerfile b/PythonAPI/util/data_collector/Dockerfile new file mode 100644 index 0000000000..e082b2503e --- /dev/null +++ b/PythonAPI/util/data_collector/Dockerfile @@ -0,0 +1,20 @@ +FROM carlasim/carla:0.9.13 +USER root +RUN apt-get update +RUN apt-get install -y libjpeg-dev libtiff-dev +RUN apt-get install -y python3-pip +RUN apt-get install -y git +RUN apt-get install -y net-tools +RUN apt-get install -y xdg-user-dirs xdg-utils +RUN apt-get install -y fontconfig +RUN apt-get install -y zip +RUN apt-get clean +USER carla +RUN python3 -m pip install Cython +RUN python3 -m pip install --upgrade pip +RUN python3 -m pip install -r /home/carla/PythonAPI/examples/requirements.txt +ENV https_proxy=http://proxy-us.intel.com:912 +COPY requirements.txt /tmp/requirements.txt +RUN python3 -m pip install -r /tmp/requirements.txt +ENV PYTHONPATH "${PYTHONPATH}:/home/carla/PythonAPI/carla/dist/carla-0.9.13-py3.7-linux-x86_64.egg" +WORKDIR /home/carla diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile new file mode 100644 index 0000000000..6399479cfa --- /dev/null +++ b/PythonAPI/util/data_collector/Makefile @@ -0,0 +1,122 @@ +DATASETS ?= /raid/datasets/data_collector +PORT := $(shell seq 49152 65535 | shuf | head -n1) +TM_PORT := $(shell echo "$(PORT) + 2" | bc) +CONFIG_FILE ?= config_tracking +NV_GPU ?= "device=0" + +DOCKER_TAG = carla-client:0.9.13 +DOCKER_OPTIONS += --rm +DOCKER_OPTIONS += --net=host +DOCKER_OPTIONS += --volume $(shell pwd)\:/home/carla/PythonAPI/util/data_collector\:ro +DOCKER_OPTIONS += --volume $(DATASETS)\:$(DATASETS)\:rw +DOCKER_OPTIONS += --gpus $(NV_GPU) +DOCKER_RUN = docker run $(DOCKER_OPTIONS) $(DOCKER_TAG) +DOCKER_RUND = docker run -d $(DOCKER_OPTIONS) --name $(UUID) $(DOCKER_TAG) +X11_UNIX = /tmp/.X11-unix + +CVAT_HOME = $(HOME)/cvat +KWCOCO_JSON_FILE = $(DATASETS)/config_tracking/kwcoco_annotations.json +LABELS = Pedestrian +CVAT_SHARE_VOLUME = cvat_share +OUTPUT_DIR = outputs +MOTS_DIR = config_tracking/MOTS-CARLA +UUID := $(shell uuidgen) + +# Colors +BLACK := $(shell tput -Txterm setaf 0) +RED := $(shell tput -Txterm setaf 1) +GREEN := $(shell tput -Txterm setaf 2) +YELLOW := $(shell tput -Txterm setaf 3) +PURPLE := $(shell tput -Txterm setaf 5) +BLUE := $(shell tput -Txterm setaf 6) +WHITE := $(shell tput -Txterm setaf 7) +RESET := $(shell tput -Txterm sgr0) + +# Taken from https://tech.davis-hansson.com/p/make/ +ifeq ($(origin .RECIPEPREFIX), undefined) + $(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later) +endif +.RECIPEPREFIX = > + +# Taken from https://suva.sh/posts/well-documented-makefiles/ +.PHONY: help +help: ## Display this help +> @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[1-9a-zA-Z_-]+:.*?##/ { printf " \033[36m%-36s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +# +# General Targets +# +.PHONY: carla_server +carla_server: ## Run CARLA server docker image +> @echo "$(GREEN)Starting CARLA server$(RESET)" +> $(DOCKER_RUN) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) + +.PHONY: carla_client +carla_client: ## Run CARLA client +> @echo "$(GREEN)Starting CARLA client$(RESET)" +> $(DOCKER_RUN) /bin/bash + +.PHONY: data_collector +data_collector: ## Run CARLA data collector +> @echo "$(GREEN)Starting CARLA data collector$(RESET)" +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name $(CONFIG_FILE) carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) + +.PHONY: docker_image +docker_image: Dockerfile requirements.txt +> docker build -f $< -t $(DOCKER_TAG) . + +.PHONY: generate_mots_annotations +generate_mots_annotations: # Generate kwcoco annotations and convert to MOTS format +> echo @ "Generating annotations in MOTS format" +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_coco_converter.py --output $(KWCOCO_JSON_FILE) --dataset_parent_dir $(DATASETS) --labels $(LABELS) +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/kwcoco_to_mots.py --mots_dir_name $(MOTS_DIR) --input_annotation_file $(KWCOCO_JSON_FILE) --output_dir $(DATASETS) + +.PHONY: launch_cvat +launch_cvat: # Launch CVAT UI +> @echo "Starting CVAT UI and mounting directories" +> docker volume rm cvat_${CVAT_SHARE_VOLUME} +> cd ${CVAT_HOME}; MOUNT_PATH=${DATASETS} docker-compose up -d + +.PHONY: terminate_cvat +terminate_cvat: # Terminate CVAT UI +> @echo "Terminate CVAT UI" +> cd ${CVAT_HOME}; docker-compose down + +.PHONY: kill_carla_servers +kill_carla_servers: ## Kill all CARLA server docker instances +> docker ps --filter="ancestor=carla-client:0.9.13" -q | xargs docker kill + + +.PHONY: clean_annotations +clean_annotations: +> @echo "Clearing annotations " +> $(DOCKER_RUN) rm -rf $(DATASETS)/*/*/kwcoco_annotations.json +> $(DOCKER_RUN) rm -rf $(DATASETS)/*/*/instances* + +.PHONY: clean +clean: +> @echo "Clearing all directories" +> $(DOCKER_RUN) rm -rf $(DATASETS)/* + +$(DATASETS)/%.yaml: +> $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) +> while ! nc -z localhost $(PORT); do sleep 1; done +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name $(@F) carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)/$*/$(UUID)" +> docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill + +.PRECIOUS: $(DATASETS)/config_tracking/%/collection_done +$(DATASETS)/config_tracking/%/collection_done: +> $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) +> while ! nc -z localhost $(PORT); do sleep 1; done +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name config_tracking.yaml carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)" +> docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill +> $(DOCKER_RUN) /bin/bash -c "echo $(UUID) > $@" + +.PRECIOUS: $(DATASETS)/%/kwcoco_annotations.json +$(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_coco_converter.py --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) + +.PRECIOUS: $(DATASETS)/%/instances.zip +$(DATASETS)/%/instances.zip: $(DATASETS)/%/kwcoco_annotations.json +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/kwcoco_to_mots.py --input_annotation_file $< --output_dir $(@D)/instances +> $(DOCKER_RUN) /bin/bash -c "cd $(@D) ; zip -r instances.zip instances" diff --git a/PythonAPI/util/data_collector/carla_coco_converter.py b/PythonAPI/util/data_collector/carla_coco_converter.py index 083cc0bb2d..d88c564aae 100644 --- a/PythonAPI/util/data_collector/carla_coco_converter.py +++ b/PythonAPI/util/data_collector/carla_coco_converter.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 - -# Copyright (c) 2021 Intel Labs. # -# This work is licensed under the terms of the MIT license. -# For a copy, see . +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: BSD-3-Clause +# from PIL import Image import json @@ -16,6 +16,7 @@ import cv2 from tqdm import tqdm from pycococreatortools import pycococreatortools +import pycocotools.mask as rletools INFO = { "description": "Carla Dataset", @@ -81,6 +82,8 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede if not labels: continue + + is_crowd = 0 two_wheeled_labels = [carla_labels.index("Vehicle"), carla_labels.index("Pedestrian")] if combine_twowheeled and sorted(labels) == sorted(two_wheeled_labels): @@ -93,30 +96,29 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede else: label = carla_labels.index("Vehicle") if np.sum(extract_obj_binary) > 0: - masks.append((label, extract_obj_binary)) + masks.append((label, extract_obj_binary, is_crowd, obj_id)) continue for label in labels: - extract_obj_binary = np.zeros_like(img_label) + extract_obj_binary = np.zeros_like(img_label) #.shape, dtype=np.uint8, order='F') # Fortran order required for RLE tools extract_obj_binary[np.where((img_label == label) & (obj_ids == obj_id))] = 1 - + if np.sum(extract_obj_binary) > 0: contours, _ = cv2.findContours(extract_obj_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) - # TODO: Support partial occlusion using IoU of segmentation mask area and 3d bounding boxes from carla projected into 2d space - is_crowd = 0 + # TBD: Support partial occlusion using IoU of segmentation mask area and 3d bounding boxes from carla projected into 2d space if len(contours) > 1: is_crowd = 1 - if label == carla_labels.index("TrafficLight"): # Treat TrafficLight differently as we want to only extract lights (do not include poles) - contours, _ = cv2.findContours(extract_obj_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) for contour in contours: - binary_mask = np.zeros_like(img_label) - binary_mask = cv2.fillPoly(binary_mask, pts = [contour], color = (1, 1, 1)) - masks.append((label, binary_mask, is_crowd)) + # Valid polygons have >=6 coordinates (3 points) + if contour.size >= 6: + binary_mask = np.zeros_like(img_label) + binary_mask = cv2.fillPoly(binary_mask, pts = [contour], color = (1, 1, 1)) + masks.append((label, binary_mask, is_crowd, obj_id)) else: - masks.append((label, extract_obj_binary, is_crowd)) + masks.append((label, extract_obj_binary, is_crowd, obj_id)) return masks @@ -193,24 +195,132 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int binary_mask = mask[1] is_crowd = mask[2] category_id = [category['id'] for category in categories if category['name'] == carla_labels[class_id]] + # We have not implemented a way to enable is_crowd for cases where there is occlusion and only one part of an object is visible. category_info = {'id': category_id[0], 'is_crowd': is_crowd} annotation_info = pycococreatortools.create_annotation_info( - annot_id, image_id, category_info, binary_mask, (width, height), tolerance=2) + annot_id, image_id, category_info, binary_mask, (width, height), tolerance=0) + + if annotation_info is not None: + annotations.append(annotation_info) + + annot_id = annot_id + 1 + + annotation_obj = {} + annotation_obj['annotations'] = annotations + annotation_obj['images'] = images + annotation_obj['categories'] = categories + annotation_obj['info'] = INFO + annotation_obj["licenses"] = LICENSES + + with open(output, 'w+') as json_file: + print(f"Saving output file: {output}") + json.dump(annotation_obj, json_file, indent=4) + + +def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, interval=5, output="out.json", combine_twowheeled=False, twowheeled_as_pedestrian=False): + """ + Create COCO format annotations for CARLA dataset + + Parameters + ---------- + dataset_dir_name: str + The root directory where the data is stored + dir_pairs: List(Tuple) + The directory mappings of where Instance segmentation images and sensor images are stored in raw format + labels: List(str) + A list of CARLA labels to generate annotations + interval: int + The interval for generating annotations + output: str + Output file for COCO annotations in json format. + combine_twowheeled: bool + If True, combine the driver and the underlying 2-wheeled vehicle into one single instance with same semantic label (default label: 'Vehilce'); Otherwise, separate them into two instances (the driver labeled as 'Pedestrian' and 2-wheeled vehicle labeled as 'Vehilce') + twowheeled_as_pedestrian: bool + If True, label the combined 2-wheeled instance as 'Pedestrian'; Otherwise label it as 'Vehicle' + """ + + # Currently annotating Pedestrian, Vehicle, TrafficLight + category_ids = [] + for label in labels: + if label not in carla_labels: + print(f"Skip label {label} as it is not a CARLA semantic label") + continue + category_ids.append(carla_labels.index(label)) + + categories = [{'supercategory': carla_labels[category], "id": i+1, "name": carla_labels[category]} for i, category in enumerate(category_ids)] + + annotations = [] + images = [] + annot_id = 1 + + for dir_pair in tqdm(dir_pairs, desc='Annotation progress'): + dict_annot = {} + obj_id_count = 1 + labels_dir, camera_dir = dir_pair + print(f"\nGenerating annotations for {camera_dir}") + # Check path to RGB and instance segmentation image directories + for l in [camera_dir, labels_dir]: + if not os.path.exists(l): + raise FileNotFoundError("{0} folder does not exist!".format(l)) + + # Only select the RGB images with corresponding instance segmentation images. + fnames = [] + label_fnames = os.listdir(labels_dir) + for fname in os.listdir(camera_dir): + if fname in label_fnames: + fnames.append(fname) + camera_dir_name = camera_dir.split(".")[-1] + + for fname in tqdm(fnames, desc="Frames annotated in directory"): + im_id = int(fname.split(".")[0]) + # Generate annotation every "interval" frames + if im_id % interval == 0: + fpath_label = os.path.join(labels_dir, fname) + fpath_rgb = os.path.join(camera_dir, fname) + im = Image.open(fpath_label) + fpath = fpath_rgb[fpath_rgb.index(dataset_dir_name) + len(dataset_dir_name) + 1 :] + image_id = int(hashlib.sha256(fpath.encode('utf-8')).hexdigest(), 16) % 10**8 + labels_mat = np.array(im) + height, width, _ = labels_mat.shape + + image_info = pycococreatortools.create_image_info(image_id, fpath, (width, height)) + image_info["video_id"] = str(camera_dir_name) + image_info["frame_index"] = str(im_id) + images.append(image_info) + masks = extract_masks(labels_mat, category_ids, combine_twowheeled, twowheeled_as_pedestrian) + + for mask in masks: + class_id = mask[0] + binary_mask = mask[1] + is_crowd = mask[2] + category_id = [category['id'] for category in categories if category['name'] == carla_labels[class_id]] + # We have not implemented a way to enable is_crowd for cases where there is occlusion and only one part of an object is visible. + category_info = {'id': category_id[0], 'is_crowd': is_crowd} + annotation_info = pycococreatortools.create_annotation_info( + annot_id, image_id, category_info, binary_mask, (width, height), tolerance=2) + if annotation_info is not None: + # Map UE object IDs to IDs starting from 1 + if mask[3] not in dict_annot.keys(): + dict_annot[mask[3]] = obj_id_count + obj_id_count += 1 + + annotation_info["track_id"] = str(dict_annot[mask[3]]) annotations.append(annotation_info) annot_id = annot_id + 1 annotation_obj = {} annotation_obj['annotations'] = annotations + annotation_obj['videos'] = [] annotation_obj['images'] = images annotation_obj['categories'] = categories annotation_obj['info'] = INFO annotation_obj["licenses"] = LICENSES - with open(output, 'w') as json_file: + with open(output, 'w+') as json_file: print(f"Saving output file: {output}") json.dump(annotation_obj, json_file, indent=4) @@ -244,7 +354,7 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int argparser.add_argument( '--interval', metavar='annotation_interval', - default=5, + default=1, type=int, help='Frame interval for annotation') argparser.add_argument( @@ -269,6 +379,12 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int default=['rgb'], type=str, help='Types of camera to generate annotations') + argparser.add_argument( + '--exclude_dirs', + metavar='ignored_dataset_subdirectories', + nargs='*', + type=str, + help='Dataset subdirectories to be excluded from annotations') args = argparser.parse_args() @@ -280,7 +396,14 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int camera_patterns.append("*sensor.camera." + camera + ".*") dir_pairs = [] - recursive_dirs = [x[0] for x in os.walk(args.dataset_parent_dir)] + recursive_dirs = set(x[0] for x in os.walk(args.dataset_parent_dir)) + + if args.exclude_dirs: + removed = set() + for excluded_dir in args.exclude_dirs: + matched = filter(lambda dir: excluded_dir in dir, recursive_dirs) + removed = removed.union(set(matched)) + recursive_dirs = recursive_dirs.difference(set(removed)) # Currently there is no information about the relation between spawned sensors. So this tool only annotates images from a CARLA simulation where only one set of sensors are spawned (one RGB sensor and/or one depth sensor, one instance segmentation sensor spawned at the same transform with same sensor attributes). # TODO: Save sensor relations in 'carla_data_saver.py' at the simulation time and then retrieve that information when generating annotations. @@ -302,4 +425,5 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int dir_pairs.append((instance_dir, camera_dir)) camera_dir = None - convert_instancemaps_to_coco_format(args.dataset_parent_dir, dir_pairs, args.labels, args.interval, args.output, args.combine_twowheeled, args.twowheeled_as_pedestrian) + #convert_instancemaps_to_coco_format(args.dataset_parent_dir, dir_pairs, args.labels, args.interval, args.output, args.combine_twowheeled, args.twowheeled_as_pedestrian) + convert_instancemaps_to_kwcoco_format(args.dataset_parent_dir, dir_pairs, args.labels, interval=1, output=args.output, combine_twowheeled=False, twowheeled_as_pedestrian=False) diff --git a/PythonAPI/util/data_collector/carla_data_saver.py b/PythonAPI/util/data_collector/carla_data_saver.py index 0c66968555..68bc208448 100644 --- a/PythonAPI/util/data_collector/carla_data_saver.py +++ b/PythonAPI/util/data_collector/carla_data_saver.py @@ -1,9 +1,9 @@ #!/usr/bin/env python - -# Copyright (c) 2021 Intel Labs. # -# This work is licensed under the terms of the MIT license. -# For a copy, see . +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: BSD-3-Clause +# from __future__ import print_function from config_schema import ConfigSchema @@ -105,16 +105,23 @@ def spawn_actors(conf, world, parent=None): actors = {} #{id: (actor, should_destroy)} for actor_conf in conf: - actor = conf_to_actor(actor_conf, world, parent=parent) - logger.info(f"Spawned actor: {actor.type_id} with id: {actor.id}") - - # Save actor to dictionary - actors[actor.id] = (actor, True) - derived_parent = actor.parent - if derived_parent is not None and fnmatch.fnmatch(derived_parent.type_id, "vehicle.*"): - # If actor has a parent and it's parent is not the default parent, then it was spawned by another script and we should not destroy this actor - actors[derived_parent.id] = (derived_parent, derived_parent == parent) - logger.info(f"Found vehicle : {derived_parent.type_id} with id: {derived_parent.id} to enable autopilot") + # Pass parent because... + actor_list = conf_to_actor(actor_conf, world, parent=parent) + logger.info(f"Spawned actor: {actor_list[0].type_id} with id: {actor_list[0].id}") + + for actor in actor_list: + # Save actor to dictionary + # For pedestrians, the ai walker controller is returned as a tuple (walker_controller, destination_transform) + if type(actor) is tuple and fnmatch.fnmatch(actor[0].type_id, "controller.ai.walker"): + actors[actor[0].id] = (actor[0], True, actor[1]) + derived_parent = actor[0].parent + else: + actors[actor.id] = (actor, True) + derived_parent = actor.parent + if derived_parent is not None and (fnmatch.fnmatch(derived_parent.type_id, "vehicle.*") or fnmatch.fnmatch(derived_parent.type_id, "walker.pedestrian.*")): + # If actor has a parent and it's parent is not the default parent, then it was spawned by another script and we should not destroy this actor + actors[derived_parent.id] = (derived_parent, derived_parent == parent) + logger.info(f"Found actor : {derived_parent.type_id} with id: {derived_parent.id} to enable autopilot") attached_actors = spawn_actors(actor_conf.get("sensors", []), world, parent=actor) actors.update(attached_actors) @@ -134,9 +141,29 @@ def conf_to_actor(conf, world, parent=None): transform = conf_to_carla_transform(conf.transform) attach_to = conf_to_attach_to(conf, world, default_attach_to=parent) attachment = conf_to_attachment(conf) - - return world.spawn_actor(blueprint, transform, attach_to=attach_to, attachment_type=attachment) - + logger.info("Config: "+str(conf)) + if "destination_transform" not in conf: + dest_transform = world.get_random_location_from_navigation() + logger.info( + f"Selected random destination point (none provided) x: {dest_transform.x}, y: {dest_transform.y}, z: {dest_transform.z} \n") + else: + dest_transform = conf_to_carla_transform(conf.destination_transform).location + + actor = world.spawn_actor(blueprint, transform, attach_to=attach_to, attachment_type=attachment) + # Spawn a walker controller to control the walker + if fnmatch.fnmatch(actor.type_id, "walker.pedestrian.*"): + walker_controller_bp = world.get_blueprint_library().find('controller.ai.walker') + walker_controller = world.spawn_actor(walker_controller_bp, carla.Transform(), attach_to=actor) + if conf.blueprint.get("pace") == "run": + print("Speed run: ", blueprint.get_attribute('speed').recommended_values[2]) + print("Speed walk: ", blueprint.get_attribute('speed').recommended_values[1]) + walker_controller.set_max_speed(float(blueprint.get_attribute('speed').recommended_values[2])) + else: + walker_controller.set_max_speed(float(blueprint.get_attribute('speed').recommended_values[1])) + + return [actor, (walker_controller, dest_transform)] + + return [actor] def conf_to_blueprint(conf, library): blueprints = library.filter(conf.name) @@ -217,14 +244,14 @@ def save_sensor_static_metadata(sensors): camera_data['type'] = sensor.type_id camera_data['fov'] = sensor.attributes['fov'] sensors_metadata[sensor.id] = camera_data - + return sensors_metadata def save_sensor_dynamic_metadata(sensor_name, sensorsnapshot, sensor): sensor_metadata = {} sensor_metadata['type'] = sensor_name.rsplit('.', 1)[0] - # Note that there is a small discrepancy between 'Actor' data and 'Actorsnapshot', so here we save both in case postprocessing needs any of them. + # Note that there is a small discrepancy between 'Actor' data and 'Actorsnapshot', so here we save both in case postprocessing needs any of them. snapshot_transform = sensorsnapshot.get_transform() sensor_metadata['actorsnapshot_location'] = (snapshot_transform.location.x, snapshot_transform.location.y, snapshot_transform.location.z) sensor_metadata['actorsnapshot_rotation'] = (snapshot_transform.rotation.pitch, snapshot_transform.rotation.yaw, snapshot_transform.rotation.roll) @@ -311,21 +338,35 @@ def data_saver_loop(conf): if conf.carla.get("seed"): random.seed(conf.carla.seed) - client = carla.Client(conf.carla.host, conf.carla.port) + # Try connecting 10 times + client = None + for i in range(10): + try: + client = carla.Client(conf.carla.host, conf.carla.port) + client.set_timeout(conf.carla.timeout) - logger.info(f"CARLA server version: {client.get_server_version()}") - logger.info(f"CARLA client version: {client.get_client_version()}") - client_version = client.get_client_version().split('-')[0] - server_version = client.get_server_version().split('-')[0] - assert client_version == server_version, "CARLA server and client should have same version" - assert version.parse(client_version) >= version.parse('0.9.13'), "CARLA version needs be >= '0.9.13'" + logger.info(f"CARLA server version: {client.get_server_version()}") + logger.info(f"CARLA client version: {client.get_client_version()}") - client.set_timeout(conf.carla.timeout) + client_version = client.get_client_version().split('-')[0] + server_version = client.get_server_version().split('-')[0] + assert client_version == server_version, "CARLA server and client should have same version" + assert version.parse(client_version) >= version.parse('0.9.13'), "CARLA version needs be >= '0.9.13'" - if conf.carla.get("townmap"): - logger.info(f"Loading map {conf.carla.townmap}.") - world = client.load_world(conf.carla.townmap) - world = client.get_world() + if conf.carla.get("townmap"): + logger.info(f"Loading map {conf.carla.townmap}.") + world = client.load_world(conf.carla.townmap) + world = client.get_world() + + except RuntimeError: + client = None + logger.info("CARLA connection failed on attempt {i+1} of 10") + time.sleep(5) + + max_frames = conf.get("max_frames", sys.maxsize) + tm_port = conf.carla.get("traffic_manager_port", 8000) + fps = conf.carla.get("fps", 30) + respawn = conf.carla.get("respawn", True) logger.info("Setting weather.") weather = carla.WeatherParameters(cloudiness=conf.weather.cloudiness, precipitation=conf.weather.precipitation, \ @@ -338,22 +379,22 @@ def data_saver_loop(conf): try: actor_dict = spawn_actors(conf.spawn_actors, world) # actor_dict: {actor_id: (actor, should_destroy)} - for actor, _ in actor_dict.values(): + for actor_tuple in actor_dict.values(): # Skip non-vehicle actors - if not fnmatch.fnmatch(actor.type_id, "vehicle.*"): - continue - actor.set_autopilot(True) - actor.set_light_state(carla.VehicleLightState.NONE) + #if not fnmatch.fnmatch(actor.type_id, "vehicle.*"): + # continue + actor = actor_tuple[0] + if fnmatch.fnmatch(actor.type_id, "controller.ai.walker"): + actor.start() + destination = actor_tuple[2] + actor.go_to_location(destination) + if fnmatch.fnmatch(actor.type_id, "vehicle.*"): + actor.set_autopilot(True, tm_port) + actor.set_light_state(carla.VehicleLightState.NONE) sensor_list = list(map(lambda x: x[0], filter(lambda a: fnmatch.fnmatch(a[0].type_id, "sensor.*"), actor_dict.values()))) sensor_id_to_sensor_type = {actor.id: actor.type_id for actor in sensor_list} - local_frame_num = 0 - max_frames = conf.get("max_frames", sys.maxsize) - tm_port = conf.carla.get("traffic_manager_port", 8000) - fps = conf.carla.get("fps", 30) - respawn = conf.carla.get("respawn", True) - metadata_path = os.path.join(conf.output_dir, 'metadata') if not os.path.exists(metadata_path): os.makedirs(metadata_path) @@ -368,7 +409,7 @@ def data_saver_loop(conf): with open(metadata_static_file, 'w') as f: json.dump(metadata_static, f, indent=4) - + local_frame_num = 0 # Create a synchronous mode context. with CarlaSyncMode(client, *sensor_list, tm_port=tm_port, fps=fps, respawn=respawn) as sync_mode: while True: @@ -415,10 +456,11 @@ def data_saver_loop(conf): logger.exception(error) finally: logger.info("Destroying actors") - for actor, should_destroy in actor_dict.values(): + for actor in actor_dict.values(): + should_destroy = actor[1] if not should_destroy: continue - actor.destroy() + actor[0].destroy() print('done.') @@ -432,8 +474,9 @@ def main(conf: DictConfig): schema = OmegaConf.structured(ConfigSchema) OmegaConf.merge(schema, conf) logger.info('listening to server %s:%s', conf.carla.host, conf.carla.port) + logger.info(OmegaConf.to_yaml(conf)) - + data_saver_loop(conf) diff --git a/PythonAPI/util/data_collector/config_schema.py b/PythonAPI/util/data_collector/config_schema.py index ed51ca97af..0b3669c79d 100644 --- a/PythonAPI/util/data_collector/config_schema.py +++ b/PythonAPI/util/data_collector/config_schema.py @@ -1,7 +1,8 @@ -# Copyright (c) 2021 Intel Labs. # -# This work is licensed under the terms of the MIT license. -# For a copy, see . +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: BSD-3-Clause +# from dataclasses import dataclass, field from typing import Optional @@ -58,11 +59,13 @@ class Transform: class Blueprint: name: str = MISSING attr: Optional[Dict[str, str]] = field(default_factory=lambda: {"image_size_x": 800, "image_size_y": 600, "gamma": 2.2}) - + pace: Optional[str] = MISSING + @dataclass class Actor: blueprint: Blueprint = Blueprint() transform: Transform = Transform() + destination_transform: Transform = Transform() attach_to: Optional[str] = MISSING attachment: str = "rigid" diff --git a/PythonAPI/util/data_collector/configs/config.yaml b/PythonAPI/util/data_collector/configs/config.yaml index 784d8a0118..c328e57e53 100644 --- a/PythonAPI/util/data_collector/configs/config.yaml +++ b/PythonAPI/util/data_collector/configs/config.yaml @@ -6,8 +6,10 @@ carla: fps: 30 timeout: 2.0 seed: 23 + traffic_manager_port: 8000 output_dir: "_out" +max_frames: 100 weather: cloudiness: 0.0 @@ -21,43 +23,26 @@ weather: wetness: 0.0 spawn_actors: - - blueprint: + - blueprint: name: "vehicle.*" attr: {role_name: "hero"} sensors: - - blueprint: + - blueprint: name: "sensor.camera.rgb" attr: {"image_size_x": "800", "image_size_y": "600"} - transform: - location: {x: -6.0, z: 1.5} + transform: + location: {x: -6.0, z: 1.5} attachment: "rigid" - - blueprint: + - blueprint: name: "sensor.camera.depth" attr: {"image_size_x": "800", "image_size_y": "600"} - transform: - location: {x: -6.0, z: 1.5} + transform: + location: {x: -6.0, z: 1.5} attachment: "rigid" - blueprint: name: "sensor.camera.instance_segmentation" attr: {"image_size_x": "800", "image_size_y": "600"} - transform: - location: {x: -6.0, z: 1.5} - - blueprint: - name: "sensor.lidar.ray_cast_semantic" - attr: {'range': '100', 'upper_fov': '35.0', 'lower_fov': '-30.0', 'channels': '64.0', 'points_per_second': '400000'} transform: location: {x: -6.0, z: 1.5} - - blueprint: - name: "sensor.camera.rgb" - attr: {"image_size_x": "800", "image_size_y": "600"} - transform: - location: {x: -6.0, z: 1.5} - attach_to: "hero" - - blueprint: - name: "sensor.camera.instance_segmentation" - attr: {"image_size_x": "800", "image_size_y": "600"} - transform: - location: {x: -6.0, z: 1.5} - attach_to: "hero" - + attachment: "rigid" diff --git a/PythonAPI/util/data_collector/configs/config_pedestrian_spawn.yaml b/PythonAPI/util/data_collector/configs/config_pedestrian_spawn.yaml new file mode 100644 index 0000000000..d3cc01e743 --- /dev/null +++ b/PythonAPI/util/data_collector/configs/config_pedestrian_spawn.yaml @@ -0,0 +1,46 @@ +carla: + host: '127.0.0.1' + port: 2000 + timeout: 5.0 + sync: + fps: 30 + timeout: 2.0 + seed: 90 + townmap: "Town10HD" + +output_dir: "_out" +max_frames: 50 + +weather: + cloudiness: 0.0 + precipitation: 0.0 + precipitation_deposits: 0.0 + wind_intensity: 0.0 + sun_azimuth_angle: 0.0 + sun_altitude_angle: 10.0 + fog_density: 0.0 + fog_distance: 0.0 + wetness: 0.0 + +spawn_actors: + - blueprint: + name: "walker.pedestrian.*" + attr: {role_name: "hero1", is_invincible: "false"} + pace: "run" + transform: + location: {x: -113, y: -25, z: 0.6} + rotation: {yaw: 90.0} + - blueprint: + name: "sensor.camera.rgb" + attr: {"image_size_x": "800", "image_size_y": "600"} + transform: + location: {x: -114, y: -24.6, z: 1.6} + rotation: {yaw: 180} + - blueprint: + name: "sensor.camera.instance_segmentation" + attr: {"image_size_x": "800", "image_size_y": "600"} + transform: + location: {x: -114, y: -24.6, z: 1.6} + rotation: {yaw: 180} + + diff --git a/PythonAPI/util/data_collector/configs/config_sensors_hero.yaml b/PythonAPI/util/data_collector/configs/config_sensors_hero.yaml index 9d18b5de3f..a5e3d0ffac 100644 --- a/PythonAPI/util/data_collector/configs/config_sensors_hero.yaml +++ b/PythonAPI/util/data_collector/configs/config_sensors_hero.yaml @@ -3,11 +3,12 @@ carla: port: 2000 timeout: 5.0 sync: - fps: 10 + fps: 30 timeout: 2.0 traffic_manager_port: 8000 output_dir: "_out" +max_frames: 10 weather: cloudiness: 0.0 @@ -21,16 +22,24 @@ weather: wetness: 0.0 spawn_actors: - - blueprint: + - blueprint: name: "sensor.camera.instance_segmentation" attr: {"image_size_x": "800", "image_size_y": "600"} transform: - location: {x: -6.0, z: 1.5} - attach_to: "hero" - - blueprint: + location: {x: -6.0, z: 1.5} + attach_to: "hero" + attachment: "rigid" + - blueprint: name: "sensor.camera.rgb" attr: {"image_size_x": "800", "image_size_y": "600", "gamma": "2.2"} - transform: - location: {x: -6.0, z: 1.5} - attach_to: "hero" + transform: + location: {x: -6.0, z: 1.5} + attach_to: "hero" + attachment: "rigid" + - blueprint: + name: "sensor.camera.depth" + attr: {"image_size_x": "800", "image_size_y": "600"} + transform: + location: {x: -6.0, z: 1.5} + attach_to: "hero" attachment: "rigid" diff --git a/PythonAPI/util/data_collector/configs/config_tracking.yaml b/PythonAPI/util/data_collector/configs/config_tracking.yaml new file mode 100644 index 0000000000..06d3cb2585 --- /dev/null +++ b/PythonAPI/util/data_collector/configs/config_tracking.yaml @@ -0,0 +1,65 @@ +carla: + host: '127.0.0.1' + port: 2000 + timeout: 5.0 + sync: + fps: 30 + timeout: 2.0 + seed: 30 + traffic_manager_port: 8000 + +output_dir: "_out" +max_frames: 300 + +weather: + cloudiness: 0.0 + precipitation: 0.0 + precipitation_deposits: 0.0 + wind_intensity: 0.0 + sun_azimuth_angle: 0.0 + sun_altitude_angle: 10.0 + fog_density: 0.0 + fog_distance: 0.0 + wetness: 0.0 + +spawn_actors: + - blueprint: + name: "walker.pedestrian.*" + attr: {role_name: "hero1", is_invincible: "false"} + pace: "walk" + transform: + location: {x: -91, y: 170, z: 0.6} + rotation: {yaw: -90.0} + destination_transform: + location: {x: -91, y: 150, z: 0.6} + + - blueprint: + name: "sensor.camera.rgb" + attr: {"image_size_x": "800", "image_size_y": "600"} + transform: + location: {x: -95, y: 160, z: 1.6} + rotation: {yaw: 0.0} + - blueprint: + name: "sensor.camera.depth" + attr: {"image_size_x": "800", "image_size_y": "600"} + transform: + location: {x: -95, y: 160, z: 1.6} + rotation: {yaw: 0.0} + - blueprint: + name: "sensor.camera.instance_segmentation" + attr: {"image_size_x": "800", "image_size_y": "600"} + transform: + location: {x: -95, y: 160, z: 1.6} + rotation: {yaw: 0.0} + + - blueprint: + name: "walker.pedestrian.*" + attr: {role_name: "hero2", is_invincible: "false"} + pace: "walk" + transform: + location: {x: -91, y: 150, z: 0.6} + rotation: {yaw: 90.0} + destination_transform: + location: {x: -91, y: 170, z: 0.6} + + diff --git a/PythonAPI/util/data_collector/kwcoco_to_mots.py b/PythonAPI/util/data_collector/kwcoco_to_mots.py new file mode 100644 index 0000000000..abc4ef5df5 --- /dev/null +++ b/PythonAPI/util/data_collector/kwcoco_to_mots.py @@ -0,0 +1,141 @@ +import json +import numpy as np +import pycocotools.mask as rletools +from pycocotools.coco import COCO +import argparse +import os +import cv2 +import logging +import coloredlogs +import imageio +import errno +import pathlib +from tqdm import tqdm + +logger = logging.getLogger(__name__) +coloredlogs.install(level=logging.INFO) + + +def kwcoco_to_mots(input_annotations_file, out_dir, format="png"): + with open(input_annotations_file, "r") as annotations_file: + labels = json.load(annotations_file) + + coco = COCO(input_annotations_file) + annotations = labels["annotations"] + videos = labels["videos"] + images = labels["images"] + categories = labels["categories"] + info = labels["info"] + licenses = labels["licenses"] + + dir_map_seq = {} + sequence_id = 1 + + for image in images: + file_path = image["file_name"] + dir_name, file_name = os.path.split(file_path) + if dir_name not in dir_map_seq.keys(): + dir_map_seq[dir_name] = (sequence_id, [image]) + sequence_id += 1 + else: + dir_map_seq[dir_name][1].append(image) + + for dir in tqdm(dir_map_seq.keys(), desc="Annotation progress"): + labels_path = "" + logger.info(f"\nGenerating annotations for {dir}") + + for image in tqdm(dir_map_seq[dir][1], desc="Annotating frame in directory"): + im_id = image["id"] + im_width = image["width"] + im_height = image["height"] + + file_path = image["file_name"] + dir_name, file_name = os.path.split(file_path) + + frame_id = file_name.split(".")[0] # Extract the frame id to save images in MOTS format + + if not os.path.exists(out_dir): + logger.info("Making dir: " + out_dir) + pathlib.Path(out_dir).mkdir(parents=True, exist_ok=True) + + labels_path = os.path.join(out_dir, "labels.txt") + + if format == "txt": + + with open(os.path.join(out_dir, "instances.txt"), "w+") as ann_file: + for ann in annotations: + if im_id == ann["image_id"]: + class_id = ann["category_id"] + object_id = int(class_id * 1000) + int(ann["track_id"]) + + mask = coco.annToMask(ann) # .astype(np.uint16) + rle = rletools.encode(np.asfortranarray(mask))["counts"] + + logger.debug("RLE: " + str(rle, "utf-8")) + ann_str = ( + str(frame_id) + + " " + + str(object_id) + + " " + + str(class_id) + + " " + + str(im_height) + + " " + + str(im_width) + + " " + + str(rle, "utf-8") + + "\n" + ) + ann_file.write(ann_str) + else: + dest_mots_gt = os.path.join(out_dir, file_name) + + # Create image annotations with 16 bit png channel + mots_png_output = np.zeros((im_height, im_width), dtype=np.uint16) + + for ann in annotations: + if im_id == ann["image_id"]: + class_id = ann["category_id"] + object_id = int(class_id) * 1000 + int(ann["track_id"]) + mask = coco.annToMask(ann).astype(np.uint16) + idx = np.where(mask == 1) + mots_png_output[idx] = object_id + (unique, counts) = np.unique(mots_png_output, return_counts=True) + frequencies = np.asarray((unique, counts)).T + logger.debug("frequencies: " + str(frequencies)) + + # Write annotation images to instances/ folder + imageio.imwrite(dest_mots_gt, mots_png_output.astype(np.uint16)) + + with open(labels_path, "w+") as labels_file: + for category in categories: + labels_file.write(category["name"] + "\n") + + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(description="KWCOCO to MOTS converter") + argparser.add_argument( + "--input_annotation_file", + metavar="input_annotation_file", + default="out_kwcoco_track.json", + type=str, + help="The input annotation file in kwcoco format", + ) + argparser.add_argument( + "--output_dir", + metavar="output_dir", + default="", + type=str, + help="The directory where the output annotations will be stored", + ) + argparser.add_argument( + "--mots_format", + metavar="mots_format", + default="png", + type=str, + help="MOTS annotation format - choose between 'png' or 'txt'", + ) + + args = argparser.parse_args() + + kwcoco_to_mots(args.input_annotation_file, args.output_dir, format=args.mots_format) diff --git a/PythonAPI/util/data_collector/requirements.txt b/PythonAPI/util/data_collector/requirements.txt index 4ed1804ee2..817b29bbe1 100644 --- a/PythonAPI/util/data_collector/requirements.txt +++ b/PythonAPI/util/data_collector/requirements.txt @@ -1,4 +1,6 @@ hydra-core==1.1.1 +coloredlogs cython -git+git://github.com/waspinator/coco.git@2.1.0 -git+git://github.com/waspinator/pycococreator.git@0.2.0 +git+https://github.com/waspinator/coco.git@2.1.0 +git+https://github.com/waspinator/pycococreator.git@0.2.0 +opencv-python From 5ac9b943a15d9e6944806a06ceef9257537c5240 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Tue, 8 Mar 2022 16:57:43 -0800 Subject: [PATCH 02/22] Update license --- PythonAPI/util/data_collector/carla_coco_converter.py | 6 +++--- PythonAPI/util/data_collector/carla_data_saver.py | 7 ++++--- PythonAPI/util/data_collector/config_schema.py | 8 ++++---- PythonAPI/util/data_collector/kwcoco_to_mots.py | 6 ++++++ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/PythonAPI/util/data_collector/carla_coco_converter.py b/PythonAPI/util/data_collector/carla_coco_converter.py index d88c564aae..c283b0dc13 100644 --- a/PythonAPI/util/data_collector/carla_coco_converter.py +++ b/PythonAPI/util/data_collector/carla_coco_converter.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 Intel Corporation -# -# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2022 Intel Corporation. # +# This work is licensed under the terms of the MIT license. +# For a copy, see . from PIL import Image import json diff --git a/PythonAPI/util/data_collector/carla_data_saver.py b/PythonAPI/util/data_collector/carla_data_saver.py index 68bc208448..316a418e72 100644 --- a/PythonAPI/util/data_collector/carla_data_saver.py +++ b/PythonAPI/util/data_collector/carla_data_saver.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # -# Copyright (C) 2020 Intel Corporation -# -# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2022 Intel Corporation. # +# This work is licensed under the terms of the MIT license. +# For a copy, see . + from __future__ import print_function from config_schema import ConfigSchema diff --git a/PythonAPI/util/data_collector/config_schema.py b/PythonAPI/util/data_collector/config_schema.py index 0b3669c79d..91f149c506 100644 --- a/PythonAPI/util/data_collector/config_schema.py +++ b/PythonAPI/util/data_collector/config_schema.py @@ -1,8 +1,8 @@ +# Copyright (c) 2022 Intel Corporation. # -# Copyright (C) 2020 Intel Corporation -# -# SPDX-License-Identifier: BSD-3-Clause -# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + from dataclasses import dataclass, field from typing import Optional diff --git a/PythonAPI/util/data_collector/kwcoco_to_mots.py b/PythonAPI/util/data_collector/kwcoco_to_mots.py index abc4ef5df5..ec09d78e3b 100644 --- a/PythonAPI/util/data_collector/kwcoco_to_mots.py +++ b/PythonAPI/util/data_collector/kwcoco_to_mots.py @@ -1,3 +1,9 @@ +# Copyright (c) 2022 Intel Corporation. +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . + + import json import numpy as np import pycocotools.mask as rletools From 879d2909123dc492c8bb22f1de3de14875a3c01e Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Wed, 9 Mar 2022 14:05:43 -0800 Subject: [PATCH 03/22] Clean up Dockerfile --- PythonAPI/util/data_collector/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/PythonAPI/util/data_collector/Dockerfile b/PythonAPI/util/data_collector/Dockerfile index e082b2503e..b749563d2c 100644 --- a/PythonAPI/util/data_collector/Dockerfile +++ b/PythonAPI/util/data_collector/Dockerfile @@ -4,7 +4,6 @@ RUN apt-get update RUN apt-get install -y libjpeg-dev libtiff-dev RUN apt-get install -y python3-pip RUN apt-get install -y git -RUN apt-get install -y net-tools RUN apt-get install -y xdg-user-dirs xdg-utils RUN apt-get install -y fontconfig RUN apt-get install -y zip @@ -13,7 +12,6 @@ USER carla RUN python3 -m pip install Cython RUN python3 -m pip install --upgrade pip RUN python3 -m pip install -r /home/carla/PythonAPI/examples/requirements.txt -ENV https_proxy=http://proxy-us.intel.com:912 COPY requirements.txt /tmp/requirements.txt RUN python3 -m pip install -r /tmp/requirements.txt ENV PYTHONPATH "${PYTHONPATH}:/home/carla/PythonAPI/carla/dist/carla-0.9.13-py3.7-linux-x86_64.egg" From fa2fe67b2670a90031622a06ec7d4cfd3dfc946c Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Wed, 9 Mar 2022 14:45:35 -0800 Subject: [PATCH 04/22] clean up coco converter --- PythonAPI/util/data_collector/carla_coco_converter.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/PythonAPI/util/data_collector/carla_coco_converter.py b/PythonAPI/util/data_collector/carla_coco_converter.py index c283b0dc13..73b7e3eebf 100644 --- a/PythonAPI/util/data_collector/carla_coco_converter.py +++ b/PythonAPI/util/data_collector/carla_coco_converter.py @@ -82,7 +82,7 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede if not labels: continue - + is_crowd = 0 two_wheeled_labels = [carla_labels.index("Vehicle"), carla_labels.index("Pedestrian")] @@ -103,7 +103,7 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede for label in labels: extract_obj_binary = np.zeros_like(img_label) #.shape, dtype=np.uint8, order='F') # Fortran order required for RLE tools extract_obj_binary[np.where((img_label == label) & (obj_ids == obj_id))] = 1 - + if np.sum(extract_obj_binary) > 0: contours, _ = cv2.findContours(extract_obj_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # TBD: Support partial occlusion using IoU of segmentation mask area and 3d bounding boxes from carla projected into 2d space @@ -290,7 +290,7 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i images.append(image_info) masks = extract_masks(labels_mat, category_ids, combine_twowheeled, twowheeled_as_pedestrian) - + for mask in masks: class_id = mask[0] binary_mask = mask[1] @@ -300,7 +300,7 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i category_info = {'id': category_id[0], 'is_crowd': is_crowd} annotation_info = pycococreatortools.create_annotation_info( annot_id, image_id, category_info, binary_mask, (width, height), tolerance=2) - + if annotation_info is not None: # Map UE object IDs to IDs starting from 1 if mask[3] not in dict_annot.keys(): @@ -425,5 +425,4 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i dir_pairs.append((instance_dir, camera_dir)) camera_dir = None - #convert_instancemaps_to_coco_format(args.dataset_parent_dir, dir_pairs, args.labels, args.interval, args.output, args.combine_twowheeled, args.twowheeled_as_pedestrian) convert_instancemaps_to_kwcoco_format(args.dataset_parent_dir, dir_pairs, args.labels, interval=1, output=args.output, combine_twowheeled=False, twowheeled_as_pedestrian=False) From 3975614f4c15d06cc3dbab89650757370cf5e7a0 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Wed, 9 Mar 2022 15:16:37 -0800 Subject: [PATCH 05/22] cleanup data saver --- .../util/data_collector/carla_data_saver.py | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/PythonAPI/util/data_collector/carla_data_saver.py b/PythonAPI/util/data_collector/carla_data_saver.py index 316a418e72..64dcf4dae7 100644 --- a/PythonAPI/util/data_collector/carla_data_saver.py +++ b/PythonAPI/util/data_collector/carla_data_saver.py @@ -109,7 +109,7 @@ def spawn_actors(conf, world, parent=None): # Pass parent because... actor_list = conf_to_actor(actor_conf, world, parent=parent) logger.info(f"Spawned actor: {actor_list[0].type_id} with id: {actor_list[0].id}") - + for actor in actor_list: # Save actor to dictionary # For pedestrians, the ai walker controller is returned as a tuple (walker_controller, destination_transform) @@ -117,11 +117,11 @@ def spawn_actors(conf, world, parent=None): actors[actor[0].id] = (actor[0], True, actor[1]) derived_parent = actor[0].parent else: - actors[actor.id] = (actor, True) + actors[actor.id] = (actor, True, None) derived_parent = actor.parent if derived_parent is not None and (fnmatch.fnmatch(derived_parent.type_id, "vehicle.*") or fnmatch.fnmatch(derived_parent.type_id, "walker.pedestrian.*")): # If actor has a parent and it's parent is not the default parent, then it was spawned by another script and we should not destroy this actor - actors[derived_parent.id] = (derived_parent, derived_parent == parent) + actors[derived_parent.id] = (derived_parent, derived_parent == parent, None) logger.info(f"Found actor : {derived_parent.type_id} with id: {derived_parent.id} to enable autopilot") attached_actors = spawn_actors(actor_conf.get("sensors", []), world, parent=actor) @@ -161,9 +161,9 @@ def conf_to_actor(conf, world, parent=None): walker_controller.set_max_speed(float(blueprint.get_attribute('speed').recommended_values[2])) else: walker_controller.set_max_speed(float(blueprint.get_attribute('speed').recommended_values[1])) - + return [actor, (walker_controller, dest_transform)] - + return [actor] def conf_to_blueprint(conf, library): @@ -245,7 +245,7 @@ def save_sensor_static_metadata(sensors): camera_data['type'] = sensor.type_id camera_data['fov'] = sensor.attributes['fov'] sensors_metadata[sensor.id] = camera_data - + return sensors_metadata @@ -379,15 +379,10 @@ def data_saver_loop(conf): actor_dict = {} try: actor_dict = spawn_actors(conf.spawn_actors, world) - # actor_dict: {actor_id: (actor, should_destroy)} - for actor_tuple in actor_dict.values(): - # Skip non-vehicle actors - #if not fnmatch.fnmatch(actor.type_id, "vehicle.*"): - # continue - actor = actor_tuple[0] + # actor_dict: {actor_id: (actor, should_destroy, destination)} + for actor, _, destination in actor_dict.values(): if fnmatch.fnmatch(actor.type_id, "controller.ai.walker"): actor.start() - destination = actor_tuple[2] actor.go_to_location(destination) if fnmatch.fnmatch(actor.type_id, "vehicle.*"): actor.set_autopilot(True, tm_port) @@ -457,11 +452,10 @@ def data_saver_loop(conf): logger.exception(error) finally: logger.info("Destroying actors") - for actor in actor_dict.values(): - should_destroy = actor[1] + for actor, should_destroy, _ in actor_dict.values(): if not should_destroy: continue - actor[0].destroy() + actor.destroy() print('done.') From e7d092a237a694e69c7ad714f4a7344969f1ae92 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Wed, 9 Mar 2022 15:23:59 -0800 Subject: [PATCH 06/22] make retry configurable --- PythonAPI/util/data_collector/carla_data_saver.py | 4 ++-- PythonAPI/util/data_collector/config_schema.py | 1 + PythonAPI/util/data_collector/configs/config_tracking.yaml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/PythonAPI/util/data_collector/carla_data_saver.py b/PythonAPI/util/data_collector/carla_data_saver.py index 64dcf4dae7..fdfa1d1301 100644 --- a/PythonAPI/util/data_collector/carla_data_saver.py +++ b/PythonAPI/util/data_collector/carla_data_saver.py @@ -339,9 +339,9 @@ def data_saver_loop(conf): if conf.carla.get("seed"): random.seed(conf.carla.seed) - # Try connecting 10 times + # Try connecting "retry" (default = 10) times client = None - for i in range(10): + for i in range(conf.carla.get("retry", 10)): try: client = carla.Client(conf.carla.host, conf.carla.port) client.set_timeout(conf.carla.timeout) diff --git a/PythonAPI/util/data_collector/config_schema.py b/PythonAPI/util/data_collector/config_schema.py index 91f149c506..e453e3847a 100644 --- a/PythonAPI/util/data_collector/config_schema.py +++ b/PythonAPI/util/data_collector/config_schema.py @@ -25,6 +25,7 @@ class Carla: traffic_manager_port: int = 8000 respawn: bool = True townmap: Optional[str] = MISSING + retry: Optional[int] = MISSING @dataclass class Location: diff --git a/PythonAPI/util/data_collector/configs/config_tracking.yaml b/PythonAPI/util/data_collector/configs/config_tracking.yaml index 06d3cb2585..d16be382af 100644 --- a/PythonAPI/util/data_collector/configs/config_tracking.yaml +++ b/PythonAPI/util/data_collector/configs/config_tracking.yaml @@ -7,6 +7,7 @@ carla: timeout: 2.0 seed: 30 traffic_manager_port: 8000 + retry: 10 output_dir: "_out" max_frames: 300 From 93426ef773fa4a9a32afd10addeec8c8bf06a29e Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Wed, 9 Mar 2022 15:36:46 -0800 Subject: [PATCH 07/22] Update change log --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c995517935..beb5e8bb58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## Latest + * Added native ackermann controller: + - `apply_ackermann_control`: to apply an ackermann control command to a vehicle + - `get_ackermann_controller_settings`: to get the last ackermann controller settings applied + - `apply_ackermann_controller_settings`: to apply new ackermann controller settings + * Fixed bug causing the Traffic Manager to not be deterministic when using hybrid mode + * Added `NormalsSensor`, a new sensor with normals information + * Added support for N wheeled vehicles + +## CARLA sissiok:data_collector + * Added a data collection tool + - `carla_data_saver.py`: saves sensor data and simulation metadata. Can be used for generate synthetic object detection and tracking datasets. + - `carla_coco_converter.py`: generates annotations in COCO format for camera images saved by `carla_data_saver.py` + - `kwcoco_to_mots.py`: converts kwcoco annotation format to MOTS annotation format. + ## CARLA 0.9.13 * Added new **instance aware semantic segmentation** sensor `sensor.camera.instance_segmentation` From 6d1d138baa3576389a2c2485d18e4685a8f5b96b Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Wed, 9 Mar 2022 16:16:05 -0800 Subject: [PATCH 08/22] clean up makefile --- PythonAPI/util/data_collector/Makefile | 72 ++++---------------------- 1 file changed, 11 insertions(+), 61 deletions(-) diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile index 6399479cfa..dd06ef6a19 100644 --- a/PythonAPI/util/data_collector/Makefile +++ b/PythonAPI/util/data_collector/Makefile @@ -1,8 +1,9 @@ -DATASETS ?= /raid/datasets/data_collector +DATASETS ?= /datasets/data_collector PORT := $(shell seq 49152 65535 | shuf | head -n1) TM_PORT := $(shell echo "$(PORT) + 2" | bc) CONFIG_FILE ?= config_tracking NV_GPU ?= "device=0" +UUID := $(shell uuidgen) DOCKER_TAG = carla-client:0.9.13 DOCKER_OPTIONS += --rm @@ -12,25 +13,6 @@ DOCKER_OPTIONS += --volume $(DATASETS)\:$(DATASETS)\:rw DOCKER_OPTIONS += --gpus $(NV_GPU) DOCKER_RUN = docker run $(DOCKER_OPTIONS) $(DOCKER_TAG) DOCKER_RUND = docker run -d $(DOCKER_OPTIONS) --name $(UUID) $(DOCKER_TAG) -X11_UNIX = /tmp/.X11-unix - -CVAT_HOME = $(HOME)/cvat -KWCOCO_JSON_FILE = $(DATASETS)/config_tracking/kwcoco_annotations.json -LABELS = Pedestrian -CVAT_SHARE_VOLUME = cvat_share -OUTPUT_DIR = outputs -MOTS_DIR = config_tracking/MOTS-CARLA -UUID := $(shell uuidgen) - -# Colors -BLACK := $(shell tput -Txterm setaf 0) -RED := $(shell tput -Txterm setaf 1) -GREEN := $(shell tput -Txterm setaf 2) -YELLOW := $(shell tput -Txterm setaf 3) -PURPLE := $(shell tput -Txterm setaf 5) -BLUE := $(shell tput -Txterm setaf 6) -WHITE := $(shell tput -Txterm setaf 7) -RESET := $(shell tput -Txterm sgr0) # Taken from https://tech.davis-hansson.com/p/make/ ifeq ($(origin .RECIPEPREFIX), undefined) @@ -46,77 +28,45 @@ help: ## Display this help # # General Targets # -.PHONY: carla_server -carla_server: ## Run CARLA server docker image -> @echo "$(GREEN)Starting CARLA server$(RESET)" -> $(DOCKER_RUN) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) - -.PHONY: carla_client -carla_client: ## Run CARLA client -> @echo "$(GREEN)Starting CARLA client$(RESET)" -> $(DOCKER_RUN) /bin/bash - -.PHONY: data_collector -data_collector: ## Run CARLA data collector -> @echo "$(GREEN)Starting CARLA data collector$(RESET)" -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name $(CONFIG_FILE) carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) - .PHONY: docker_image -docker_image: Dockerfile requirements.txt +docker_image: Dockerfile requirements.txt ## Build docker image > docker build -f $< -t $(DOCKER_TAG) . -.PHONY: generate_mots_annotations -generate_mots_annotations: # Generate kwcoco annotations and convert to MOTS format -> echo @ "Generating annotations in MOTS format" -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_coco_converter.py --output $(KWCOCO_JSON_FILE) --dataset_parent_dir $(DATASETS) --labels $(LABELS) -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/kwcoco_to_mots.py --mots_dir_name $(MOTS_DIR) --input_annotation_file $(KWCOCO_JSON_FILE) --output_dir $(DATASETS) - -.PHONY: launch_cvat -launch_cvat: # Launch CVAT UI -> @echo "Starting CVAT UI and mounting directories" -> docker volume rm cvat_${CVAT_SHARE_VOLUME} -> cd ${CVAT_HOME}; MOUNT_PATH=${DATASETS} docker-compose up -d - -.PHONY: terminate_cvat -terminate_cvat: # Terminate CVAT UI -> @echo "Terminate CVAT UI" -> cd ${CVAT_HOME}; docker-compose down - .PHONY: kill_carla_servers kill_carla_servers: ## Kill all CARLA server docker instances > docker ps --filter="ancestor=carla-client:0.9.13" -q | xargs docker kill .PHONY: clean_annotations -clean_annotations: +clean_annotations: ## Clean existing annotations > @echo "Clearing annotations " > $(DOCKER_RUN) rm -rf $(DATASETS)/*/*/kwcoco_annotations.json > $(DOCKER_RUN) rm -rf $(DATASETS)/*/*/instances* .PHONY: clean -clean: +clean: ## Clean all directories > @echo "Clearing all directories" > $(DOCKER_RUN) rm -rf $(DATASETS)/* -$(DATASETS)/%.yaml: +$(DATASETS)/%.yaml: ## Run data collection with specified configuration > $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) > while ! nc -z localhost $(PORT); do sleep 1; done > $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name $(@F) carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)/$*/$(UUID)" > docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill -.PRECIOUS: $(DATASETS)/config_tracking/%/collection_done -$(DATASETS)/config_tracking/%/collection_done: +.PRECIOUS: $(DATASETS)/$(CONFIG_FILE)/%/collection_done +$(DATASETS)/$(CONFIG_FILE)/%/collection_done: ## Run data collection with config_tracking configuration > $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) > while ! nc -z localhost $(PORT); do sleep 1; done -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name config_tracking.yaml carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)" +> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name $(CONFIG_FILE).yaml carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)" > docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill > $(DOCKER_RUN) /bin/bash -c "echo $(UUID) > $@" .PRECIOUS: $(DATASETS)/%/kwcoco_annotations.json -$(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done +$(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done ## Generate kwcoco annotations > $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_coco_converter.py --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) .PRECIOUS: $(DATASETS)/%/instances.zip -$(DATASETS)/%/instances.zip: $(DATASETS)/%/kwcoco_annotations.json +$(DATASETS)/%/instances.zip: $(DATASETS)/%/kwcoco_annotations.json ## Convert kwcoco annotation format to MOTS annotation format > $(DOCKER_RUN) python3 PythonAPI/util/data_collector/kwcoco_to_mots.py --input_annotation_file $< --output_dir $(@D)/instances > $(DOCKER_RUN) /bin/bash -c "cd $(@D) ; zip -r instances.zip instances" From ec5842fc2c78c547253909ee7ee9297499a9aeb9 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Thu, 10 Mar 2022 14:18:50 -0800 Subject: [PATCH 09/22] Update to python3.7 --- PythonAPI/util/data_collector/Dockerfile | 14 +++++++++----- PythonAPI/util/data_collector/Makefile | 11 ++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/PythonAPI/util/data_collector/Dockerfile b/PythonAPI/util/data_collector/Dockerfile index b749563d2c..20aa8ca310 100644 --- a/PythonAPI/util/data_collector/Dockerfile +++ b/PythonAPI/util/data_collector/Dockerfile @@ -1,18 +1,22 @@ FROM carlasim/carla:0.9.13 USER root RUN apt-get update +RUN apt-get install -y build-essential RUN apt-get install -y libjpeg-dev libtiff-dev -RUN apt-get install -y python3-pip +RUN apt-get install -y python3-pip python3.7-dev python3-distutils +RUN apt-get install -y wget +RUN wget https://bootstrap.pypa.io/get-pip.py +RUN python3.7 get-pip.py RUN apt-get install -y git RUN apt-get install -y xdg-user-dirs xdg-utils RUN apt-get install -y fontconfig RUN apt-get install -y zip RUN apt-get clean USER carla -RUN python3 -m pip install Cython -RUN python3 -m pip install --upgrade pip -RUN python3 -m pip install -r /home/carla/PythonAPI/examples/requirements.txt +RUN python3.7 -m pip install Cython +RUN python3.7 -m pip install --upgrade pip +RUN python3.7 -m pip install -r /home/carla/PythonAPI/examples/requirements.txt COPY requirements.txt /tmp/requirements.txt -RUN python3 -m pip install -r /tmp/requirements.txt +RUN python3.7 -m pip install -r /tmp/requirements.txt ENV PYTHONPATH "${PYTHONPATH}:/home/carla/PythonAPI/carla/dist/carla-0.9.13-py3.7-linux-x86_64.egg" WORKDIR /home/carla diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile index dd06ef6a19..937669bd79 100644 --- a/PythonAPI/util/data_collector/Makefile +++ b/PythonAPI/util/data_collector/Makefile @@ -4,8 +4,9 @@ TM_PORT := $(shell echo "$(PORT) + 2" | bc) CONFIG_FILE ?= config_tracking NV_GPU ?= "device=0" UUID := $(shell uuidgen) +PYTHON = python3.7 -DOCKER_TAG = carla-client:0.9.13 +DOCKER_TAG ?= carla-client:0.9.13 DOCKER_OPTIONS += --rm DOCKER_OPTIONS += --net=host DOCKER_OPTIONS += --volume $(shell pwd)\:/home/carla/PythonAPI/util/data_collector\:ro @@ -51,22 +52,22 @@ clean: ## Clean all directories $(DATASETS)/%.yaml: ## Run data collection with specified configuration > $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) > while ! nc -z localhost $(PORT); do sleep 1; done -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name $(@F) carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)/$*/$(UUID)" +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_data_saver.py --config-name $(@F) carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)/$*/$(UUID)" > docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill .PRECIOUS: $(DATASETS)/$(CONFIG_FILE)/%/collection_done $(DATASETS)/$(CONFIG_FILE)/%/collection_done: ## Run data collection with config_tracking configuration > $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) > while ! nc -z localhost $(PORT); do sleep 1; done -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_data_saver.py --config-name $(CONFIG_FILE).yaml carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)" +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_data_saver.py --config-name $(CONFIG_FILE).yaml carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)" > docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill > $(DOCKER_RUN) /bin/bash -c "echo $(UUID) > $@" .PRECIOUS: $(DATASETS)/%/kwcoco_annotations.json $(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done ## Generate kwcoco annotations -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/carla_coco_converter.py --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_coco_converter.py --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) .PRECIOUS: $(DATASETS)/%/instances.zip $(DATASETS)/%/instances.zip: $(DATASETS)/%/kwcoco_annotations.json ## Convert kwcoco annotation format to MOTS annotation format -> $(DOCKER_RUN) python3 PythonAPI/util/data_collector/kwcoco_to_mots.py --input_annotation_file $< --output_dir $(@D)/instances +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/kwcoco_to_mots.py --input_annotation_file $< --output_dir $(@D)/instances > $(DOCKER_RUN) /bin/bash -c "cd $(@D) ; zip -r instances.zip instances" From 7067e03fc19b5a5b05eab24f1f21fba5eb1d2b59 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Thu, 10 Mar 2022 14:37:57 -0800 Subject: [PATCH 10/22] update dockerfile --- PythonAPI/util/data_collector/Dockerfile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/PythonAPI/util/data_collector/Dockerfile b/PythonAPI/util/data_collector/Dockerfile index 20aa8ca310..6e8483f399 100644 --- a/PythonAPI/util/data_collector/Dockerfile +++ b/PythonAPI/util/data_collector/Dockerfile @@ -3,10 +3,8 @@ USER root RUN apt-get update RUN apt-get install -y build-essential RUN apt-get install -y libjpeg-dev libtiff-dev -RUN apt-get install -y python3-pip python3.7-dev python3-distutils -RUN apt-get install -y wget -RUN wget https://bootstrap.pypa.io/get-pip.py -RUN python3.7 get-pip.py +RUN apt-get install -y python3.7 python3.7-dev python3-distutils +RUN apt-get install -y python3-pip RUN apt-get install -y git RUN apt-get install -y xdg-user-dirs xdg-utils RUN apt-get install -y fontconfig From 4c5e0c7cad636b357485a47aa5c50cbb2976015f Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Thu, 10 Mar 2022 16:35:07 -0800 Subject: [PATCH 11/22] Update change log --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index beb5e8bb58..08c6a2faf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,8 @@ ## CARLA sissiok:data_collector * Added a data collection tool - `carla_data_saver.py`: saves sensor data and simulation metadata. Can be used for generate synthetic object detection and tracking datasets. - - `carla_coco_converter.py`: generates annotations in COCO format for camera images saved by `carla_data_saver.py` - - `kwcoco_to_mots.py`: converts kwcoco annotation format to MOTS annotation format. + - `carla_coco_converter.py`: generates annotations in kwcoco format and MOTS format for camera images saved by `carla_data_saver.py` + - `Dockerfile`: builds a docker image for CARLA client upon the current CARLA docker image which only supports CARLA server. ## CARLA 0.9.13 From 66df7258773a255368129b2297cfa5382ad4ece3 Mon Sep 17 00:00:00 2001 From: Shibani Singh Date: Mon, 14 Mar 2022 19:10:11 -0700 Subject: [PATCH 12/22] Annotator cleaned up --- PythonAPI/util/data_collector/Makefile | 14 +- ...a_coco_converter.py => carla_annotator.py} | 423 ++++++++++++------ 2 files changed, 288 insertions(+), 149 deletions(-) rename PythonAPI/util/data_collector/{carla_coco_converter.py => carla_annotator.py} (52%) diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile index 937669bd79..36d6e4ddd1 100644 --- a/PythonAPI/util/data_collector/Makefile +++ b/PythonAPI/util/data_collector/Makefile @@ -2,9 +2,11 @@ DATASETS ?= /datasets/data_collector PORT := $(shell seq 49152 65535 | shuf | head -n1) TM_PORT := $(shell echo "$(PORT) + 2" | bc) CONFIG_FILE ?= config_tracking +FORMAT = mots_png NV_GPU ?= "device=0" UUID := $(shell uuidgen) PYTHON = python3.7 +LABELS = Pedestrian DOCKER_TAG ?= carla-client:0.9.13 DOCKER_OPTIONS += --rm @@ -63,11 +65,11 @@ $(DATASETS)/$(CONFIG_FILE)/%/collection_done: ## Run data collection with config > docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill > $(DOCKER_RUN) /bin/bash -c "echo $(UUID) > $@" -.PRECIOUS: $(DATASETS)/%/kwcoco_annotations.json -$(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done ## Generate kwcoco annotations -> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_coco_converter.py --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) +.PRECIOUS: $(DATASETS)/%/kwcoco_annotations.json ## Generate kwcoco annotations +$(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(FORMAT) --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) -.PRECIOUS: $(DATASETS)/%/instances.zip -$(DATASETS)/%/instances.zip: $(DATASETS)/%/kwcoco_annotations.json ## Convert kwcoco annotation format to MOTS annotation format -> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/kwcoco_to_mots.py --input_annotation_file $< --output_dir $(@D)/instances +.PRECIOUS: $(DATASETS)/%/instances.zip ## Convert kwcoco annotation format to MOTS annotation format +$(DATASETS)/%/instances.zip: $(DATASETS)/%/collection_done +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(FORMAT) --output $(@D)/instances --dataset_parent_dir $(@D) --labels $(LABELS) > $(DOCKER_RUN) /bin/bash -c "cd $(@D) ; zip -r instances.zip instances" diff --git a/PythonAPI/util/data_collector/carla_coco_converter.py b/PythonAPI/util/data_collector/carla_annotator.py similarity index 52% rename from PythonAPI/util/data_collector/carla_coco_converter.py rename to PythonAPI/util/data_collector/carla_annotator.py index 73b7e3eebf..9d192eb251 100644 --- a/PythonAPI/util/data_collector/carla_coco_converter.py +++ b/PythonAPI/util/data_collector/carla_annotator.py @@ -1,22 +1,31 @@ #!/usr/bin/env python3 # -# Copyright (c) 2022 Intel Corporation. +# Copyright (C) 2020 Intel Corporation +# +# SPDX-License-Identifier: BSD-3-Clause # -# This work is licensed under the terms of the MIT license. -# For a copy, see . from PIL import Image import json import numpy as np import os +import pathlib import datetime import argparse import fnmatch import hashlib import cv2 +import imageio from tqdm import tqdm from pycococreatortools import pycococreatortools import pycocotools.mask as rletools +import logging +import coloredlogs +from contextlib import nullcontext +from scipy import ndimage + +logger = logging.getLogger(__name__) +coloredlogs.install(level=logging.INFO) INFO = { "description": "Carla Dataset", @@ -24,27 +33,40 @@ "version": "0.9.12", "year": 2021, "contributor": "", - "date_created": datetime.datetime.utcnow().isoformat(' ') + "date_created": datetime.datetime.utcnow().isoformat(" "), } -LICENSES = [ - { - "id": 1, - "name": "", - "url": "" - } -] +LICENSES = [{"id": 1, "name": "", "url": ""}] # Define Carla semantic categories - as per Carla v0.9.13 -carla_labels = ["Unlabeled", "Building", "Fence", "Other", \ - "Pedestrian", "Pole", "RoadLine", "Road", \ - "Sidewalk", "Vegetation", "Vehicle", "Wall", \ - "TrafficSign", "Sky", "Ground", "Bridge", \ - "RailTrack", "GuardRail", "TrafficLight", \ - "Static", "Dynamic", "Water", "Terrain"] +carla_labels = [ + "Unlabeled", + "Building", + "Fence", + "Other", + "Pedestrian", + "Pole", + "RoadLine", + "Road", + "Sidewalk", + "Vegetation", + "Vehicle", + "Wall", + "TrafficSign", + "Sky", + "Ground", + "Bridge", + "RailTrack", + "GuardRail", + "TrafficLight", + "Static", + "Dynamic", + "Water", + "Terrain", +] -def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pedestrian=False): +def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pedestrian=False, closing=False): """Extract binary masks from CARLA's instance segmentation sensor outputs. Parameters @@ -57,7 +79,8 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede If True, combine the driver and the underlying 2-wheeled vehicle into one single instance with same semantic label (default label: 'Vehilce'); Otherwise, separate them into two instances (the driver labeled as 'Pedestrian' and 2-wheeled vehicle labeled as 'Vehilce') twowheeled_as_pedestrian: bool If True, label the combined 2-wheeled instance as 'Pedestrian'; Otherwise label it as 'Vehicle' - + closing: bool + If True, fill holes in binary mask of every object Returns ------- masks: List[(int, np.array, int)] @@ -101,8 +124,12 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede continue for label in labels: - extract_obj_binary = np.zeros_like(img_label) #.shape, dtype=np.uint8, order='F') # Fortran order required for RLE tools + extract_obj_binary = np.zeros_like( + img_label + ) # .shape, dtype=np.uint8, order='F') # Fortran order required for RLE tools extract_obj_binary[np.where((img_label == label) & (obj_ids == obj_id))] = 1 + if closing: + extract_obj_binary = ndimage.binary_fill_holes(extract_obj_binary).astype(np.uint8) if np.sum(extract_obj_binary) > 0: contours, _ = cv2.findContours(extract_obj_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) @@ -115,21 +142,29 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede # Valid polygons have >=6 coordinates (3 points) if contour.size >= 6: binary_mask = np.zeros_like(img_label) - binary_mask = cv2.fillPoly(binary_mask, pts = [contour], color = (1, 1, 1)) + binary_mask = cv2.fillPoly(binary_mask, pts=[contour], color=(1, 1, 1)) masks.append((label, binary_mask, is_crowd, obj_id)) else: masks.append((label, extract_obj_binary, is_crowd, obj_id)) return masks -def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, interval=5, output="out.json", combine_twowheeled=False, twowheeled_as_pedestrian=False): + +def convert_instancemaps_to_mots_format( + dir_pairs, + labels, + interval=5, + format="png", + out_dir="instances", + combine_twowheeled=False, + twowheeled_as_pedestrian=False, + closing=True, +): """ Create COCO format annotations for CARLA dataset Parameters ---------- - dataset_dir_name: str - The root directory where the data is stored dir_pairs: List(Tuple) The directory mappings of where Instance segmentation images and sensor images are stored in raw format labels: List(str) @@ -142,8 +177,11 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int If True, combine the driver and the underlying 2-wheeled vehicle into one single instance with same semantic label (default label: 'Vehilce'); Otherwise, separate them into two instances (the driver labeled as 'Pedestrian' and 2-wheeled vehicle labeled as 'Vehilce') twowheeled_as_pedestrian: bool If True, label the combined 2-wheeled instance as 'Pedestrian'; Otherwise label it as 'Vehicle' + closing: bool + If True, fill holes in binary masks """ + # TBD: Shorten this if possible # Currently annotating Pedestrian, Vehicle, TrafficLight category_ids = [] for label in labels: @@ -152,19 +190,29 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int continue category_ids.append(carla_labels.index(label)) - categories = [{'supercategory': carla_labels[category], "id": i+1, "name": carla_labels[category]} for i, category in enumerate(category_ids)] + categories = [ + {"supercategory": carla_labels[category], "id": i + 1, "name": carla_labels[category]} + for i, category in enumerate(category_ids) + ] - annotations = [] - images = [] annot_id = 1 - for dir_pair in tqdm(dir_pairs, desc='Annotation progress'): + for dir_pair in tqdm(dir_pairs, desc="Annotation progress"): + + if not os.path.exists(out_dir): + logger.info("Making dir: " + out_dir) + pathlib.Path(out_dir).mkdir(parents=True, exist_ok=True) + + labels_path = os.path.join(out_dir, "labels.txt") + + dict_annot = {} + obj_id_count = 1 labels_dir, camera_dir = dir_pair - print(f"\nGenerating annotations for {camera_dir}") + logger.info(f"\nGenerating annotations for {camera_dir}") # Check path to RGB and instance segmentation image directories - for l in [camera_dir, labels_dir]: - if not os.path.exists(l): - raise FileNotFoundError("{0} folder does not exist!".format(l)) + for dir in [camera_dir, labels_dir]: + if not os.path.exists(dir): + raise FileNotFoundError("{0} folder does not exist!".format(dir)) # Only select the RGB images with corresponding instance segmentation images. fnames = [] @@ -173,52 +221,94 @@ def convert_instancemaps_to_coco_format(dataset_dir_name, dir_pairs, labels, int if fname in label_fnames: fnames.append(fname) - for fname in fnames: - im_id = int(fname.split(".")[0]) - # Generate annotation every "interval" frames - if im_id % interval == 0: - fpath_label = os.path.join(labels_dir, fname) - fpath_rgb = os.path.join(camera_dir, fname) - im = Image.open(fpath_label) - fpath = fpath_rgb[fpath_rgb.index(dataset_dir_name) + len(dataset_dir_name) + 1 :] - image_id = int(hashlib.sha256(fpath.encode('utf-8')).hexdigest(), 16) % 10**8 - labels_mat = np.array(im) - height, width, _ = labels_mat.shape - - image_info = pycococreatortools.create_image_info(image_id, fpath, (width, height)) - images.append(image_info) - - masks = extract_masks(labels_mat, category_ids, combine_twowheeled, twowheeled_as_pedestrian) - - for mask in masks: - class_id = mask[0] - binary_mask = mask[1] - is_crowd = mask[2] - category_id = [category['id'] for category in categories if category['name'] == carla_labels[class_id]] - # We have not implemented a way to enable is_crowd for cases where there is occlusion and only one part of an object is visible. - category_info = {'id': category_id[0], 'is_crowd': is_crowd} - - annotation_info = pycococreatortools.create_annotation_info( - annot_id, image_id, category_info, binary_mask, (width, height), tolerance=0) - - if annotation_info is not None: - annotations.append(annotation_info) - - annot_id = annot_id + 1 - - annotation_obj = {} - annotation_obj['annotations'] = annotations - annotation_obj['images'] = images - annotation_obj['categories'] = categories - annotation_obj['info'] = INFO - annotation_obj["licenses"] = LICENSES - - with open(output, 'w+') as json_file: - print(f"Saving output file: {output}") - json.dump(annotation_obj, json_file, indent=4) - - -def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, interval=5, output="out.json", combine_twowheeled=False, twowheeled_as_pedestrian=False): + _context_manager = open(os.path.join(out_dir, "instances.txt"), "w+") if format == "mots_txt" else nullcontext() + + with _context_manager as ann_file: + for fname in tqdm(fnames, desc="Frames annotated in directory"): + im_id = int(fname.split(".")[0]) + # Generate annotation every "interval" frames + if im_id % interval == 0: + fpath_label = os.path.join(labels_dir, fname) + im = Image.open(fpath_label) + labels_mat = np.array(im) + height, width, _ = labels_mat.shape + masks = extract_masks(labels_mat, category_ids, combine_twowheeled, + twowheeled_as_pedestrian, closing) + + if format == "mots_png": + dest_mots_gt = os.path.join(out_dir, fname) + # Create image annotations with 16 bit png channel + mots_png_output = np.zeros((height, width), dtype=np.uint16) + + for mask in masks: + class_id = mask[0] + binary_mask = mask[1] + is_crowd = mask[2] + category_id = [ + category["id"] for category in categories if category["name"] == carla_labels[class_id] + ] + category_info = {"id": category_id[0], "is_crowd": is_crowd} + logger.info("Category id: " + str(category_id[0])) + # We have not implemented a way to enable is_crowd for cases where there is occlusion and only one part of an object is visible. + annotation_info = pycococreatortools.create_annotation_info( + annot_id, im_id, category_info, binary_mask, (width, height) + ) + + if annotation_info is not None: + # Map UE object IDs to IDs starting from 1 + if mask[3] not in dict_annot.keys(): + dict_annot[mask[3]] = obj_id_count + obj_id_count += 1 + + object_id = int(category_id[0]) * 1000 + int(dict_annot[mask[3]]) + logger.info("Object ID: " + str(object_id)) + if format == "mots_png": + idx = np.where(binary_mask == 1) + mots_png_output[idx] = object_id + else: + rle = rletools.encode(np.asfortranarray(binary_mask))["counts"] + logger.debug("RLE: " + str(rle, "utf-8")) + ann_str = ( + str(im_id) + + " " + + str(object_id) + + " " + + str(class_id) + + " " + + str(height) + + " " + + str(width) + + " " + + str(rle, "utf-8") + + "\n" + ) + ann_file.write(ann_str) + + if format == "mots_png": + (unique, counts) = np.unique(mots_png_output, return_counts=True) + frequencies = np.asarray((unique, counts)).T + logger.debug("frequencies: " + str(frequencies)) + + # Write annotation images to instances/ folder + imageio.imwrite(dest_mots_gt, mots_png_output.astype(np.uint16)) + + logger.info("All objects: " + str(dict_annot)) + + with open(labels_path, "w+") as labels_file: + for category in categories: + labels_file.write(category["name"] + "\n") + + +def convert_instancemaps_to_kwcoco_format( + dataset_dir_name, + dir_pairs, + labels, + interval=5, + output="out.json", + combine_twowheeled=False, + twowheeled_as_pedestrian=False, + tolerance=2, +): """ Create COCO format annotations for CARLA dataset @@ -238,6 +328,8 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i If True, combine the driver and the underlying 2-wheeled vehicle into one single instance with same semantic label (default label: 'Vehilce'); Otherwise, separate them into two instances (the driver labeled as 'Pedestrian' and 2-wheeled vehicle labeled as 'Vehilce') twowheeled_as_pedestrian: bool If True, label the combined 2-wheeled instance as 'Pedestrian'; Otherwise label it as 'Vehicle' + tolerance: int + Tolerance as required by skimage.measure.approximate_polygon. Tolerance is the maximum distance from original points of polygon to approximated polygonal chain. If tolerance is 0, the original coordinate array is returned. """ # Currently annotating Pedestrian, Vehicle, TrafficLight @@ -248,21 +340,24 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i continue category_ids.append(carla_labels.index(label)) - categories = [{'supercategory': carla_labels[category], "id": i+1, "name": carla_labels[category]} for i, category in enumerate(category_ids)] + categories = [ + {"supercategory": carla_labels[category], "id": i + 1, "name": carla_labels[category]} + for i, category in enumerate(category_ids) + ] annotations = [] images = [] annot_id = 1 - for dir_pair in tqdm(dir_pairs, desc='Annotation progress'): + for dir_pair in tqdm(dir_pairs, desc="Annotation progress"): dict_annot = {} obj_id_count = 1 labels_dir, camera_dir = dir_pair print(f"\nGenerating annotations for {camera_dir}") # Check path to RGB and instance segmentation image directories - for l in [camera_dir, labels_dir]: - if not os.path.exists(l): - raise FileNotFoundError("{0} folder does not exist!".format(l)) + for dir in [camera_dir, labels_dir]: + if not os.path.exists(dir): + raise FileNotFoundError("{0} folder does not exist!".format(dir)) # Only select the RGB images with corresponding instance segmentation images. fnames = [] @@ -279,8 +374,8 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i fpath_label = os.path.join(labels_dir, fname) fpath_rgb = os.path.join(camera_dir, fname) im = Image.open(fpath_label) - fpath = fpath_rgb[fpath_rgb.index(dataset_dir_name) + len(dataset_dir_name) + 1 :] - image_id = int(hashlib.sha256(fpath.encode('utf-8')).hexdigest(), 16) % 10**8 + fpath = fpath_rgb[fpath_rgb.index(dataset_dir_name) + len(dataset_dir_name) + 1:] + image_id = int(hashlib.sha256(fpath.encode("utf-8")).hexdigest(), 16) % 10**8 labels_mat = np.array(im) height, width, _ = labels_mat.shape @@ -295,17 +390,20 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i class_id = mask[0] binary_mask = mask[1] is_crowd = mask[2] - category_id = [category['id'] for category in categories if category['name'] == carla_labels[class_id]] + category_id = [ + category["id"] for category in categories if category["name"] == carla_labels[class_id] + ] # We have not implemented a way to enable is_crowd for cases where there is occlusion and only one part of an object is visible. - category_info = {'id': category_id[0], 'is_crowd': is_crowd} + category_info = {"id": category_id[0], "is_crowd": is_crowd} annotation_info = pycococreatortools.create_annotation_info( - annot_id, image_id, category_info, binary_mask, (width, height), tolerance=2) + annot_id, image_id, category_info, binary_mask, (width, height), tolerance=tolerance + ) if annotation_info is not None: # Map UE object IDs to IDs starting from 1 if mask[3] not in dict_annot.keys(): dict_annot[mask[3]] = obj_id_count - obj_id_count += 1 + obj_id_count += 1 annotation_info["track_id"] = str(dict_annot[mask[3]]) annotations.append(annotation_info) @@ -313,78 +411,95 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i annot_id = annot_id + 1 annotation_obj = {} - annotation_obj['annotations'] = annotations - annotation_obj['videos'] = [] - annotation_obj['images'] = images - annotation_obj['categories'] = categories - annotation_obj['info'] = INFO + annotation_obj["annotations"] = annotations + annotation_obj["videos"] = [] + annotation_obj["images"] = images + annotation_obj["categories"] = categories + annotation_obj["info"] = INFO annotation_obj["licenses"] = LICENSES - with open(output, 'w+') as json_file: + with open(output, "w+") as json_file: print(f"Saving output file: {output}") json.dump(annotation_obj, json_file, indent=4) -if __name__ == '__main__': +if __name__ == "__main__": - argparser = argparse.ArgumentParser( - description='COCO converter') - argparser.add_argument( - '--list', - action='store_true', - help='List CARLA semantic labels') + argparser = argparse.ArgumentParser(description="COCO converter") + argparser.add_argument("--list", action="store_true", help="List CARLA semantic labels") argparser.add_argument( - '--dataset_parent_dir', - metavar='dataset_parent_directory', - default='outputs', + "--dataset_parent_dir", + metavar="dataset_parent_directory", + default="outputs", type=str, - help='The directory where all the data is stored') + help="The directory where all the data is stored", + ) argparser.add_argument( - '--sensor_out_dir', - metavar='sensor_output_directory', - default='_out', + "--sensor_out_dir", + metavar="sensor_output_directory", + default="_out", type=str, - help='The directory where the sensor data is stored') + help="The directory where the sensor data is stored", + ) argparser.add_argument( - '--output', - metavar='annotation_output', - default='out.json', + "--output", + metavar="annotation_output", + default="out.json", type=str, - help='The output annotation file stored in JSON format') + help="The output annotation file stored in JSON format", + ) argparser.add_argument( - '--interval', - metavar='annotation_interval', + "--interval", + metavar="annotation_interval", default=1, type=int, - help='Frame interval for annotation') + help="Frame interval for annotation" + ) argparser.add_argument( - '--combine_twowheeled', - action='store_true', - help='Combine the driver and the underlying vehicle into one single instance with same semantic label (default label: Vehicle)') + "--combine_twowheeled", + action="store_true", + help="Combine the driver and the underlying vehicle into one single instance with same semantic label (default label: Vehicle)", + ) argparser.add_argument( - '--twowheeled_as_pedestrian', - action='store_true', - help='Combine the driver and the underlying vehicle into one single instance with semantic label Pedestrian') + "--twowheeled_as_pedestrian", + action="store_true", + help="Combine the driver and the underlying vehicle into one single instance with semantic label Pedestrian", + ) argparser.add_argument( - '--labels', - metavar='annotation_labels', - nargs='+', + "--labels", + metavar="annotation_labels", + nargs="+", default=["Pedestrian", "Vehicle", "TrafficLight"], type=str, - help='Labels used to generate annotation.') + help="Labels used to generate annotation.", + ) + argparser.add_argument( + "--cameras", + metavar="cameras_to_annotate", + nargs="+", + default=["rgb"], + type=str, + help="Types of camera to generate annotations", + ) argparser.add_argument( - '--cameras', - metavar='cameras_to_annotate', - nargs='+', - default=['rgb'], + "--exclude_dirs", + metavar="ignored_dataset_subdirectories", + nargs="*", type=str, - help='Types of camera to generate annotations') + help="Dataset subdirectories to be excluded from annotations", + ) argparser.add_argument( - '--exclude_dirs', - metavar='ignored_dataset_subdirectories', - nargs='*', + "--format", + default="kwcoco", type=str, - help='Dataset subdirectories to be excluded from annotations') + help="Annotation format - choose between 'kwcoco', 'mots_txt' or 'mots_png'", + ) + argparser.add_argument( + "--binary_fill_holes", + default=True, + type=bool, + help="Close holes in binary mask - using ndimage.binary_fill_holes operation", + ) args = argparser.parse_args() @@ -405,24 +520,46 @@ def convert_instancemaps_to_kwcoco_format(dataset_dir_name, dir_pairs, labels, i removed = removed.union(set(matched)) recursive_dirs = recursive_dirs.difference(set(removed)) - # Currently there is no information about the relation between spawned sensors. So this tool only annotates images from a CARLA simulation where only one set of sensors are spawned (one RGB sensor and/or one depth sensor, one instance segmentation sensor spawned at the same transform with same sensor attributes). - # TODO: Save sensor relations in 'carla_data_saver.py' at the simulation time and then retrieve that information when generating annotations. + # Currently there is no information about the relation between spawned sensors. So this tool only annotates images from a CARLA simulation where only one set of sensors are spawned (one RGB sensor and/or one depth sensor, one instance segmentation sensor spawned at the same transform with same sensor attributes). + # TODO: Save sensor relations in 'carla_data_saver.py' at the simulation time and then retrieve that information when generating annotations. for out_dir in recursive_dirs: - if fnmatch.fnmatch(out_dir, "*/"+args.sensor_out_dir): + if fnmatch.fnmatch(out_dir, "*/" + args.sensor_out_dir): dirs = [x[0] for x in os.walk(out_dir)] instance_dir = None camera_dir = None for subdir in dirs: - if fnmatch.fnmatch(subdir,"*instance_segmentation*"): + if fnmatch.fnmatch(subdir, "*instance_segmentation*"): instance_dir = subdir break for subdir in dirs: for camera in camera_patterns: - if(fnmatch.fnmatch(subdir, camera)): + if fnmatch.fnmatch(subdir, camera): camera_dir = subdir if instance_dir is not None and camera_dir is not None: dir_pairs.append((instance_dir, camera_dir)) camera_dir = None - - convert_instancemaps_to_kwcoco_format(args.dataset_parent_dir, dir_pairs, args.labels, interval=1, output=args.output, combine_twowheeled=False, twowheeled_as_pedestrian=False) + logger.info(dir_pairs) + + if args.format in ["mots_png", "mots_txt"]: + convert_instancemaps_to_mots_format( + args.dataset_parent_dir, + dir_pairs, + list(args.labels), + args.interval, + format=args.format, + out_dir=args.output, + combine_twowheeled=False, + twowheeled_as_pedestrian=False, + closing=args.binary_fill_holes, + ) + elif args.format == "kwcoco": + convert_instancemaps_to_kwcoco_format( + args.dataset_parent_dir, + dir_pairs, + list(args.labels), + interval=1, + output=args.output, + combine_twowheeled=False, + twowheeled_as_pedestrian=False, + ) From bcef2cec93ab7f40d1a41d6354e13306f86eced9 Mon Sep 17 00:00:00 2001 From: Shibani Singh Date: Mon, 14 Mar 2022 19:11:09 -0700 Subject: [PATCH 13/22] Remove kwcoco_to_mots conversion --- .../util/data_collector/kwcoco_to_mots.py | 147 ------------------ 1 file changed, 147 deletions(-) delete mode 100644 PythonAPI/util/data_collector/kwcoco_to_mots.py diff --git a/PythonAPI/util/data_collector/kwcoco_to_mots.py b/PythonAPI/util/data_collector/kwcoco_to_mots.py deleted file mode 100644 index ec09d78e3b..0000000000 --- a/PythonAPI/util/data_collector/kwcoco_to_mots.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright (c) 2022 Intel Corporation. -# -# This work is licensed under the terms of the MIT license. -# For a copy, see . - - -import json -import numpy as np -import pycocotools.mask as rletools -from pycocotools.coco import COCO -import argparse -import os -import cv2 -import logging -import coloredlogs -import imageio -import errno -import pathlib -from tqdm import tqdm - -logger = logging.getLogger(__name__) -coloredlogs.install(level=logging.INFO) - - -def kwcoco_to_mots(input_annotations_file, out_dir, format="png"): - with open(input_annotations_file, "r") as annotations_file: - labels = json.load(annotations_file) - - coco = COCO(input_annotations_file) - annotations = labels["annotations"] - videos = labels["videos"] - images = labels["images"] - categories = labels["categories"] - info = labels["info"] - licenses = labels["licenses"] - - dir_map_seq = {} - sequence_id = 1 - - for image in images: - file_path = image["file_name"] - dir_name, file_name = os.path.split(file_path) - if dir_name not in dir_map_seq.keys(): - dir_map_seq[dir_name] = (sequence_id, [image]) - sequence_id += 1 - else: - dir_map_seq[dir_name][1].append(image) - - for dir in tqdm(dir_map_seq.keys(), desc="Annotation progress"): - labels_path = "" - logger.info(f"\nGenerating annotations for {dir}") - - for image in tqdm(dir_map_seq[dir][1], desc="Annotating frame in directory"): - im_id = image["id"] - im_width = image["width"] - im_height = image["height"] - - file_path = image["file_name"] - dir_name, file_name = os.path.split(file_path) - - frame_id = file_name.split(".")[0] # Extract the frame id to save images in MOTS format - - if not os.path.exists(out_dir): - logger.info("Making dir: " + out_dir) - pathlib.Path(out_dir).mkdir(parents=True, exist_ok=True) - - labels_path = os.path.join(out_dir, "labels.txt") - - if format == "txt": - - with open(os.path.join(out_dir, "instances.txt"), "w+") as ann_file: - for ann in annotations: - if im_id == ann["image_id"]: - class_id = ann["category_id"] - object_id = int(class_id * 1000) + int(ann["track_id"]) - - mask = coco.annToMask(ann) # .astype(np.uint16) - rle = rletools.encode(np.asfortranarray(mask))["counts"] - - logger.debug("RLE: " + str(rle, "utf-8")) - ann_str = ( - str(frame_id) - + " " - + str(object_id) - + " " - + str(class_id) - + " " - + str(im_height) - + " " - + str(im_width) - + " " - + str(rle, "utf-8") - + "\n" - ) - ann_file.write(ann_str) - else: - dest_mots_gt = os.path.join(out_dir, file_name) - - # Create image annotations with 16 bit png channel - mots_png_output = np.zeros((im_height, im_width), dtype=np.uint16) - - for ann in annotations: - if im_id == ann["image_id"]: - class_id = ann["category_id"] - object_id = int(class_id) * 1000 + int(ann["track_id"]) - mask = coco.annToMask(ann).astype(np.uint16) - idx = np.where(mask == 1) - mots_png_output[idx] = object_id - (unique, counts) = np.unique(mots_png_output, return_counts=True) - frequencies = np.asarray((unique, counts)).T - logger.debug("frequencies: " + str(frequencies)) - - # Write annotation images to instances/ folder - imageio.imwrite(dest_mots_gt, mots_png_output.astype(np.uint16)) - - with open(labels_path, "w+") as labels_file: - for category in categories: - labels_file.write(category["name"] + "\n") - - -if __name__ == "__main__": - argparser = argparse.ArgumentParser(description="KWCOCO to MOTS converter") - argparser.add_argument( - "--input_annotation_file", - metavar="input_annotation_file", - default="out_kwcoco_track.json", - type=str, - help="The input annotation file in kwcoco format", - ) - argparser.add_argument( - "--output_dir", - metavar="output_dir", - default="", - type=str, - help="The directory where the output annotations will be stored", - ) - argparser.add_argument( - "--mots_format", - metavar="mots_format", - default="png", - type=str, - help="MOTS annotation format - choose between 'png' or 'txt'", - ) - - args = argparser.parse_args() - - kwcoco_to_mots(args.input_annotation_file, args.output_dir, format=args.mots_format) From 20814eabd4600b8a2a265bc3f22aedc77b990975 Mon Sep 17 00:00:00 2001 From: Shibani Singh Date: Mon, 14 Mar 2022 20:14:30 -0700 Subject: [PATCH 14/22] Cleanup --- PythonAPI/util/data_collector/Makefile | 2 +- PythonAPI/util/data_collector/carla_annotator.py | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile index 36d6e4ddd1..b985891de1 100644 --- a/PythonAPI/util/data_collector/Makefile +++ b/PythonAPI/util/data_collector/Makefile @@ -69,7 +69,7 @@ $(DATASETS)/$(CONFIG_FILE)/%/collection_done: ## Run data collection with config $(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done > $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(FORMAT) --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) -.PRECIOUS: $(DATASETS)/%/instances.zip ## Convert kwcoco annotation format to MOTS annotation format +.PRECIOUS: $(DATASETS)/%/instances.zip ## Generate annotation in MOTS format $(DATASETS)/%/instances.zip: $(DATASETS)/%/collection_done > $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(FORMAT) --output $(@D)/instances --dataset_parent_dir $(@D) --labels $(LABELS) > $(DOCKER_RUN) /bin/bash -c "cd $(@D) ; zip -r instances.zip instances" diff --git a/PythonAPI/util/data_collector/carla_annotator.py b/PythonAPI/util/data_collector/carla_annotator.py index 9d192eb251..ff3ca74eb6 100644 --- a/PythonAPI/util/data_collector/carla_annotator.py +++ b/PythonAPI/util/data_collector/carla_annotator.py @@ -154,7 +154,7 @@ def convert_instancemaps_to_mots_format( dir_pairs, labels, interval=5, - format="png", + format="mots_png", out_dir="instances", combine_twowheeled=False, twowheeled_as_pedestrian=False, @@ -248,7 +248,6 @@ def convert_instancemaps_to_mots_format( category["id"] for category in categories if category["name"] == carla_labels[class_id] ] category_info = {"id": category_id[0], "is_crowd": is_crowd} - logger.info("Category id: " + str(category_id[0])) # We have not implemented a way to enable is_crowd for cases where there is occlusion and only one part of an object is visible. annotation_info = pycococreatortools.create_annotation_info( annot_id, im_id, category_info, binary_mask, (width, height) @@ -261,7 +260,6 @@ def convert_instancemaps_to_mots_format( obj_id_count += 1 object_id = int(category_id[0]) * 1000 + int(dict_annot[mask[3]]) - logger.info("Object ID: " + str(object_id)) if format == "mots_png": idx = np.where(binary_mask == 1) mots_png_output[idx] = object_id @@ -292,7 +290,7 @@ def convert_instancemaps_to_mots_format( # Write annotation images to instances/ folder imageio.imwrite(dest_mots_gt, mots_png_output.astype(np.uint16)) - logger.info("All objects: " + str(dict_annot)) + logger.info("All objects annotated dict mapping (UE_ID: instance_id): " + str(dict_annot)) with open(labels_path, "w+") as labels_file: for category in categories: @@ -543,7 +541,6 @@ def convert_instancemaps_to_kwcoco_format( if args.format in ["mots_png", "mots_txt"]: convert_instancemaps_to_mots_format( - args.dataset_parent_dir, dir_pairs, list(args.labels), args.interval, @@ -551,7 +548,7 @@ def convert_instancemaps_to_kwcoco_format( out_dir=args.output, combine_twowheeled=False, twowheeled_as_pedestrian=False, - closing=args.binary_fill_holes, + closing=args.binary_fill_holes ) elif args.format == "kwcoco": convert_instancemaps_to_kwcoco_format( From d86325d11582cfd69451113ba4ddf9fe5dfc18a1 Mon Sep 17 00:00:00 2001 From: Shibani Singh Date: Tue, 15 Mar 2022 11:05:04 -0700 Subject: [PATCH 15/22] Remove commented code --- PythonAPI/util/data_collector/carla_annotator.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/PythonAPI/util/data_collector/carla_annotator.py b/PythonAPI/util/data_collector/carla_annotator.py index ff3ca74eb6..3dc8a62511 100644 --- a/PythonAPI/util/data_collector/carla_annotator.py +++ b/PythonAPI/util/data_collector/carla_annotator.py @@ -124,9 +124,7 @@ def extract_masks(im, category_ids, combine_twowheeled=False, twowheeled_as_pede continue for label in labels: - extract_obj_binary = np.zeros_like( - img_label - ) # .shape, dtype=np.uint8, order='F') # Fortran order required for RLE tools + extract_obj_binary = np.zeros_like(img_label) extract_obj_binary[np.where((img_label == label) & (obj_ids == obj_id))] = 1 if closing: extract_obj_binary = ndimage.binary_fill_holes(extract_obj_binary).astype(np.uint8) @@ -537,7 +535,6 @@ def convert_instancemaps_to_kwcoco_format( if instance_dir is not None and camera_dir is not None: dir_pairs.append((instance_dir, camera_dir)) camera_dir = None - logger.info(dir_pairs) if args.format in ["mots_png", "mots_txt"]: convert_instancemaps_to_mots_format( From d3f040a18369634c1df4bba9f2152fd504d4847f Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Tue, 15 Mar 2022 12:54:46 -0700 Subject: [PATCH 16/22] update license --- PythonAPI/util/data_collector/Dockerfile | 4 ++++ PythonAPI/util/data_collector/carla_annotator.py | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/PythonAPI/util/data_collector/Dockerfile b/PythonAPI/util/data_collector/Dockerfile index 6e8483f399..66702b0a6c 100644 --- a/PythonAPI/util/data_collector/Dockerfile +++ b/PythonAPI/util/data_collector/Dockerfile @@ -1,3 +1,7 @@ +# Copyright (c) 2022 Intel Corporation. +# +# This work is licensed under the terms of the MIT license. +# For a copy, see . FROM carlasim/carla:0.9.13 USER root RUN apt-get update diff --git a/PythonAPI/util/data_collector/carla_annotator.py b/PythonAPI/util/data_collector/carla_annotator.py index 3dc8a62511..0d7fa62abc 100644 --- a/PythonAPI/util/data_collector/carla_annotator.py +++ b/PythonAPI/util/data_collector/carla_annotator.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 Intel Corporation +# Copyright (c) 2022 Intel Corporation. # -# SPDX-License-Identifier: BSD-3-Clause +# This work is licensed under the terms of the MIT license. +# For a copy, see . # from PIL import Image From 5f6f466d3547bb91d767d6de089350aea89eafab Mon Sep 17 00:00:00 2001 From: Michael Beale Date: Tue, 15 Mar 2022 14:18:18 -0700 Subject: [PATCH 17/22] updated license You just need the SPDX-License identifier for this. :D --- PythonAPI/util/data_collector/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PythonAPI/util/data_collector/Dockerfile b/PythonAPI/util/data_collector/Dockerfile index 66702b0a6c..5355616e02 100644 --- a/PythonAPI/util/data_collector/Dockerfile +++ b/PythonAPI/util/data_collector/Dockerfile @@ -1,7 +1,7 @@ # Copyright (c) 2022 Intel Corporation. # -# This work is licensed under the terms of the MIT license. -# For a copy, see . +# SPDX-License-Identifier: MIT + FROM carlasim/carla:0.9.13 USER root RUN apt-get update From 03ac3b021d7a337de178f4f0f070578582f138ba Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Tue, 15 Mar 2022 14:58:14 -0700 Subject: [PATCH 18/22] update change log --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c6a2faf0..db45d6fe64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ ## CARLA sissiok:data_collector * Added a data collection tool - `carla_data_saver.py`: saves sensor data and simulation metadata. Can be used for generate synthetic object detection and tracking datasets. - - `carla_coco_converter.py`: generates annotations in kwcoco format and MOTS format for camera images saved by `carla_data_saver.py` + - `carla_annotator.py`: generates annotations in kwcoco format and MOTS format for camera images saved by `carla_data_saver.py` - `Dockerfile`: builds a docker image for CARLA client upon the current CARLA docker image which only supports CARLA server. ## CARLA 0.9.13 From c17f05b43363f609d8a44d2abafb568f27c1adb8 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Tue, 15 Mar 2022 16:40:14 -0700 Subject: [PATCH 19/22] clean makefile --- PythonAPI/util/data_collector/Makefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile index b985891de1..49810caf93 100644 --- a/PythonAPI/util/data_collector/Makefile +++ b/PythonAPI/util/data_collector/Makefile @@ -2,11 +2,11 @@ DATASETS ?= /datasets/data_collector PORT := $(shell seq 49152 65535 | shuf | head -n1) TM_PORT := $(shell echo "$(PORT) + 2" | bc) CONFIG_FILE ?= config_tracking -FORMAT = mots_png +TRACKING_FORMAT ?= mots_png +TRACKING_LABELS ?= Pedestrian NV_GPU ?= "device=0" UUID := $(shell uuidgen) PYTHON = python3.7 -LABELS = Pedestrian DOCKER_TAG ?= carla-client:0.9.13 DOCKER_OPTIONS += --rm @@ -67,9 +67,9 @@ $(DATASETS)/$(CONFIG_FILE)/%/collection_done: ## Run data collection with config .PRECIOUS: $(DATASETS)/%/kwcoco_annotations.json ## Generate kwcoco annotations $(DATASETS)/%/kwcoco_annotations.json: $(DATASETS)/%/collection_done -> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(FORMAT) --output $@ --dataset_parent_dir $(@D) --labels $(LABELS) +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(TRACKING_FORMAT) --output $@ --dataset_parent_dir $(@D) --labels $(TRACKING_LABELS) .PRECIOUS: $(DATASETS)/%/instances.zip ## Generate annotation in MOTS format -$(DATASETS)/%/instances.zip: $(DATASETS)/%/collection_done -> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(FORMAT) --output $(@D)/instances --dataset_parent_dir $(@D) --labels $(LABELS) +$(DATASETS)/%/instances.zip: $(DATASETS)/%/collection_done +> $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_annotator.py --format $(TRACKING_FORMAT) --output $(@D)/instances --dataset_parent_dir $(@D) --labels $(TRACKING_LABELS) > $(DOCKER_RUN) /bin/bash -c "cd $(@D) ; zip -r instances.zip instances" From 37e4391b81813ab1100ea945fe8f2e113ac14902 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Tue, 15 Mar 2022 16:48:28 -0700 Subject: [PATCH 20/22] Add link for semantic tags --- PythonAPI/util/data_collector/carla_annotator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PythonAPI/util/data_collector/carla_annotator.py b/PythonAPI/util/data_collector/carla_annotator.py index 0d7fa62abc..50b8c9a5af 100644 --- a/PythonAPI/util/data_collector/carla_annotator.py +++ b/PythonAPI/util/data_collector/carla_annotator.py @@ -40,6 +40,7 @@ LICENSES = [{"id": 1, "name": "", "url": ""}] # Define Carla semantic categories - as per Carla v0.9.13 +# https://carla.readthedocs.io/en/0.9.13/ref_sensors/#semantic-segmentation-camera carla_labels = [ "Unlabeled", "Building", From ec214bb4b752c5782776fd8412cf2e7d1ec148fa Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Thu, 17 Mar 2022 13:11:37 -0700 Subject: [PATCH 21/22] Add CARLA quality level configuration --- PythonAPI/util/data_collector/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile index 49810caf93..55e88c56d8 100644 --- a/PythonAPI/util/data_collector/Makefile +++ b/PythonAPI/util/data_collector/Makefile @@ -1,6 +1,7 @@ DATASETS ?= /datasets/data_collector PORT := $(shell seq 49152 65535 | shuf | head -n1) TM_PORT := $(shell echo "$(PORT) + 2" | bc) +CARLA_QUALITY_LEVEL ?= Epic CONFIG_FILE ?= config_tracking TRACKING_FORMAT ?= mots_png TRACKING_LABELS ?= Pedestrian @@ -52,7 +53,7 @@ clean: ## Clean all directories > $(DOCKER_RUN) rm -rf $(DATASETS)/* $(DATASETS)/%.yaml: ## Run data collection with specified configuration -> $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) +> $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) -quality-level=$(CARLA_QUALITY_LEVEL) > while ! nc -z localhost $(PORT); do sleep 1; done > $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_data_saver.py --config-name $(@F) carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)/$*/$(UUID)" > docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill From 9fd8ddfac99d586db0049263c13645b27a2544f7 Mon Sep 17 00:00:00 2001 From: Xiruo Liu Date: Thu, 17 Mar 2022 14:14:54 -0700 Subject: [PATCH 22/22] Add CARLA quality level --- PythonAPI/util/data_collector/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PythonAPI/util/data_collector/Makefile b/PythonAPI/util/data_collector/Makefile index 55e88c56d8..ad495ebcd9 100644 --- a/PythonAPI/util/data_collector/Makefile +++ b/PythonAPI/util/data_collector/Makefile @@ -60,7 +60,7 @@ $(DATASETS)/%.yaml: ## Run data collection with specified configuration .PRECIOUS: $(DATASETS)/$(CONFIG_FILE)/%/collection_done $(DATASETS)/$(CONFIG_FILE)/%/collection_done: ## Run data collection with config_tracking configuration -> $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) +> $(DOCKER_RUND) /bin/bash ./CarlaUE4.sh -RenderOffScreen -carla-port=$(PORT) -quality-level=$(CARLA_QUALITY_LEVEL) > while ! nc -z localhost $(PORT); do sleep 1; done > $(DOCKER_RUN) $(PYTHON) PythonAPI/util/data_collector/carla_data_saver.py --config-name $(CONFIG_FILE).yaml carla.port=$(PORT) carla.traffic_manager_port=$(TM_PORT) hydra.run.dir="$(@D)" > docker ps --quiet --filter="name=$(UUID)" -q | xargs docker kill