diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5684edc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +# Start with a base image containing Miniconda installed +FROM continuumio/miniconda3 + +# Set the working directory in the Docker container to /workspace/tum-traffic-dataset-dev-kit +WORKDIR /workspace/tum-traffic-dataset-dev-kit + +# Install any necessary system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + git \ + wget \ + libncurses5-dev \ + libncursesw5-dev \ + libsm6 \ + libxext6 \ + libgl1-mesa-glx \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Create a new Conda environment with Python 3.8 named tum-traffic-dataset-dev-kit +RUN conda create -n tum-traffic-dataset-dev-kit python=3.8 -y +SHELL ["conda", "run", "-n", "tum-traffic-dataset-dev-kit", "/bin/bash", "-c"] + +# Install Pytorch env +RUN conda install pytorch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 pytorch-cuda=11.8 -c pytorch -c nvidia + +# Copy the application's requirements file and install any additional requirements +COPY requirements_docker.txt . +RUN pip install -r requirements_docker.txt + +# Install additional tools +RUN conda install -c conda-forge jupyterlab -y && \ + conda install -c conda-forge fvcore && \ + conda install -c conda-forge iopath && \ + conda install conda-forge::ros-sensor-msgs && \ + conda install pytorch3d -c pytorch3d + +# Install specific Python packages via pip +RUN pip install -U pip && \ + pip install -v "git+https://github.com/klintan/pypcd.git" + +RUN pip install -v numpy==1.19.5 + +# Copy the rest of your application's source code from the local file system to the filesystem of the container +COPY . /workspace/tum-traffic-dataset-dev-kit/ + +# Set an environment variable to ensure Python scripts find the modules +ENV PYTHONPATH "${PYTHONPATH}:/workspace/tum-traffic-dataset-dev-kit" + +# Expose the port the app runs on +EXPOSE 8888 + +# Set the default command to execute when creating a new container +CMD ["bash"] diff --git a/README.md b/README.md index ad275b7..b48c9e0 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,20 @@ Add dev kit's root directory to `PYTHONPATH`: export PYTHONPATH=$PYTHONPATH:/home//tum-traffic-dataset-dev-kit/ ``` +### 🐳 Using Dev-Kit with Docker + +We provide a Dockerfile to build an image. Make sure your docker version is >= 19.03. + +```bash +# Building a Docker Image +docker build -t tum-traffic-dataset-dev-kit . + +# Run the Docker container, assuming you've set the DATA_DIR environment variable here +docker run --gpus all --shm-size=8g -it -v ${DATA_DIR}:/workspace/tum-traffic-dataset-dev-kit/data tum-traffic-dataset-dev-kit +``` + +Make sure your DATA_DIR environment variable points to the directory(volume name) containing the dataset. Additionally, the Docker commands above assume that your machine has GPU support and is configured with the appropriate NVIDIA driver and CUDA version. + ## 📃 Dataset Structure #### 1) TUM Traffic A9 Highway Dataset (`TUMTraf-A9`) The TUM Traffic A9 Highway Dataset (`TUMTraf-A9`) contains 5 subsets (`s00` to `s04`) and is structured in the following way: @@ -225,9 +239,10 @@ python tum-traffic-dataset-dev-kit/src/visualization/visualize_image_with_3d_box --output_folder_path_visualization \ --detections_coordinate_system_origin [s110_base,s110_lidar_ouster_south] \ --labels_coordinate_system_origin [s110_base,s110_lidar_ouster_south] + --file_path_calibration_data ``` -| Visualization south2 in camera:`--viz_mode box3d,point_cloud` | Visualization south1 camera: `--vis_mode box2d,box3d,mask,track_history` | +| Visualization south2 in camera:`--viz_mode box3d,point_cloud` | Visualization south1 camera: `--viz_mode box2d,box3d,mask,track_history` | |---------------------------------------------------------------|------------------------------------------------------------------------|

@@ -247,7 +262,7 @@ python tum-traffic-dataset-dev-kit/src/visualization/visualize_point_cloud_with_ Bird's Eye View | Side View :-------------------------:|:-------------------------: -![](img/1688626050_947334296_s110_lidar_ouster_south_and_vehicle_lidar_robosense_registered_point_cloud_bev.jpg) | ![](img/1688626050_947334296_s110_lidar_ouster_south_and_vehicle_lidar_robosense_registered_point_cloud_custom_view.jpg) +![bev](img/1688626050_947334296_s110_lidar_ouster_south_and_vehicle_lidar_robosense_registered_point_cloud_bev.jpg) | ![side_view](img/1688626050_947334296_s110_lidar_ouster_south_and_vehicle_lidar_robosense_registered_point_cloud_custom_view.jpg) ## ✴️️ Data Split @@ -272,15 +287,26 @@ python tum-traffic-dataset-dev-kit/src/registration/point_cloud_registration.py --save_registered_point_clouds \ --output_folder_path_registered_point_clouds ``` +Example: +``` +python tum-traffic-dataset-dev-kit/src/registration/point_cloud_registration.py --folder_path_point_cloud_source "./tum-traffic-dataset-dev-kit/data/tum/a9_dataset_r02_split/train/point_clouds/s110_lidar_ouster_north" \ + --folder_path_point_cloud_target "./tum-traffic-dataset-dev-kit/data/tum/a9_dataset_r02_split/train/point_clouds/s110_lidar_ouster_south" \ + --save_registered_point_clouds --output_folder_path_registered_point_clouds "./tum-traffic-dataset-dev-kit/data/tum/a9_dataset_r02_reg/s110_lidar_ouster" +``` ![registered_point_cloud](./img/registered_point_cloud.png) ## 🧹 Data Cleaning A LiDAR preprocessing module reduces noise in point cloud scans: ``` -python tum-traffic-dataset-dev-kit/src/preprocessing/remove_noise_from_point_clouds.py --input_folder_path_point_clouds \ +python tum-traffic-dataset-dev-kit/src/preprocessing/filter_noise_point_cloud.py --input_folder_path_point_clouds \ --output_folder_path_point_clouds ``` +Example: +``` +python tum-traffic-dataset-dev-kit/src/preprocessing/filter_noise_point_cloud.py --input_folder_path_point_clouds "./tum-traffic-dataset-dev-kit/data/tum/a9_dataset_r02_split/train/s110_lidar_ouster_north" \ + --output_folder_path_point_clouds "./tum-traffic-dataset-dev-kit/data/tum/a9_dataset_r02_split_no_noise/train/" +``` ![noise_removal](./img/outlier_removal.png) ## ⚡ Label Conversion @@ -290,22 +316,27 @@ In addition, a data converter/exporter enables you to convert the labels from Op ### OpenLABEL to YOLO The following script converts the OpenLABEL labels into YOLO labels: ``` -python tum-traffic-dataset-dev-kit/src/converter/conversion_openlabel_to_yolo.py --input_folder_path_labels \ +python tum-traffic-dataset-dev-kit/src/label_conversion/conversion_openlabel_to_yolo.py --input_folder_path_labels \ --output_folder_path_labels ``` ### OpenLABEL to KITTI The following script converts the OpenLABEL labels into KITTI labels: ``` -python tum-traffic-dataset-dev-kit/src/converter/conversion_openlabel_to_kitti.py --root-dir \ +python tum-traffic-dataset-dev-kit/src/label_conversion/conversion_openlabel_to_kitti.py --root-dir \ --out-dir \ --file-name-format [name,num] ``` - +Example: +``` +python tum-traffic-dataset-dev-kit/src/label_conversion/conversion_openlabel_to_kitti.py --root-dir "./tum-traffic-dataset-dev-kit/data/tum/a9_dataset_r02_split/" \ + --out-dir "./tum-traffic-dataset-dev-kit/data/tum/a9_dataset_r02_split_kitti/" \ + --file-name-format num +``` ### OpenLABEL to nuScenes The following script converts the OpenLABEL labels into nuScenes labels: ``` -python tum-traffic-dataset-dev-kit/src/converter/conversion_openlabel_to_nuscenes.py --root-path \ +python tum-traffic-dataset-dev-kit/src/label_conversion/conversion_openlabel_to_nuscenes.py --root-path \ --out-dir ``` diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..b90af8e --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,35 @@ +# Use the official NVIDIA PyTorch image which includes CUDA and cuDNN. +FROM nvcr.io/nvidia/pytorch:22.04-py3 + +# Setting up the working directory +WORKDIR /workspace/tum-traffic-dataset-dev-kit + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + curl \ + libgl1-mesa-glx \ + libglib2.0-0 \ + && rm -rf /var/lib/apt/lists/* + +# Install specific Python packages via pip +RUN pip install -U pip && \ + pip install fvcore iopath && \ + pip install "git+https://github.com/facebookresearch/pytorch3d.git@stable" && \ + pip install "git+https://github.com/klintan/pypcd.git" + +# Copy the Python requirements file and install Python dependencies +COPY requirements.txt /workspace/tum-traffic-dataset-dev-kit/ +RUN pip install -r requirements.txt + +# Copy the rest of the project files into the working directory +COPY . /workspace/tum-traffic-dataset-dev-kit/ + +# Set the PYTHONPATH environment variable +ENV PYTHONPATH="${PYTHONPATH}:/workspace/tum-traffic-dataset-dev-kit/" + +# Expose any ports the app might need +EXPOSE 8888 + +# Run the Bash Shell when the container starts +CMD ["bash"] diff --git a/requirements_docker.txt b/requirements_docker.txt new file mode 100644 index 0000000..fcafaff --- /dev/null +++ b/requirements_docker.txt @@ -0,0 +1,29 @@ +numpy>=1.17 +opencv-python>=4.5 +matplotlib>=3.3 +scipy>=1.6 +argparse>=1.1 +pyyaml>=5.4 +pandas>=1.2 +tqdm>=4.60 +scikit-learn>=0.0 +bitarray>=1.6 +progressbar +numba>=0.53 + +cython>=0.29.32 +simplejson +scikit-image>=0.19.3 +filterpy >=1.4.5 +prettytable>=3.7.0 +open3d==0.17.0 +flask==3.0.0 +dash==2.14.2 +werkzeug==3.0.1 +pycryptodomex==3.19.0 +gnupg==2.3.1 +rospkg==1.5.0 +protobuf==4.23.4 +rosnumpy==0.0.5.2 +distinctipy==1.3.3 +mmcv==1.6.0 \ No newline at end of file diff --git a/src/preprocessing/create_train_val_split.py b/src/preprocessing/create_train_val_split.py index cdc579d..1087ae1 100644 --- a/src/preprocessing/create_train_val_split.py +++ b/src/preprocessing/create_train_val_split.py @@ -1,6 +1,7 @@ import argparse import glob import os +import shutil from tqdm import tqdm @@ -22,14 +23,22 @@ def copy_to_output( ): for subset in subsets: for sensor in sensors: + # Determine the input folder path input_folder_path = os.path.join(dataset_root_folder_path, subset, sensor_modality, sensor) - file_paths_sub_set = sorted(glob.glob(input_folder_path + "/*")) + + # Get all files under this path + file_paths_sub_set = sorted(glob.glob(os.path.join(input_folder_path, "*"))) + for file_path_sub_set in file_paths_sub_set: file_name_in_sub_set = os.path.basename(file_path_sub_set) + + # If the filenames match, perform a copy if file_name == file_name_in_sub_set: input_file_path = os.path.join(input_folder_path, file_name) output_file_path = os.path.join(output_folder_path, file_name) - os.system(f"cp {input_file_path} {output_file_path}") + + # Use shutil.copy which perform same behavior on Linux and Windows to copy files + shutil.copy(input_file_path, output_file_path) return diff --git a/src/preprocessing/filter_noise_point_cloud.py b/src/preprocessing/filter_noise_point_cloud.py index 6cd84b4..43c4da0 100644 --- a/src/preprocessing/filter_noise_point_cloud.py +++ b/src/preprocessing/filter_noise_point_cloud.py @@ -1,17 +1,56 @@ import open3d as o3d import numpy as np import os - -from pypcd import pypcd +import shutil import argparse from src.utils.point_cloud_registration_utils import read_point_cloud_with_intensity, write_point_cloud_with_intensity -# Description: Remove noise (outliers) from point clouds +# Description: Remove noise (outliers) from point clouds and copy non-point cloud files # Example: python a9-dataset-dev-kit/src/preprocessing/filter_noise_point_cloud.py --input_folder_path_point_clouds \ -# --output_folder_path_point_clouds \ -# --nb_points 1 \ -# --radius 0.4 +# --output_folder_path_point_clouds \ +# --nb_points 1 \ +# --radius 0.4 + +def process_point_cloud(input_path, output_path, nb_points, radius): + point_cloud_array, header = read_point_cloud_with_intensity(input_path) + xyz = point_cloud_array[:, :3] + intensities = point_cloud_array[:, 3] + max_intensity = np.max(intensities) + intensities_norm = intensities / max_intensity + intensities_norm_three_col = np.c_[intensities_norm, intensities_norm, intensities_norm] + + pcd = o3d.geometry.PointCloud() + pcd.points = o3d.utility.Vector3dVector(xyz) + pcd.colors = o3d.utility.Vector3dVector(intensities_norm_three_col) + + print("num points before: ", str(len(pcd.points))) + pcd_filtered, indices_keep = pcd.remove_radius_outlier(nb_points, radius) + print("num points after: ", str(len(pcd.points))) + print("removed ", str(len(pcd.points) - len(indices_keep)), " outliers.") + + points_array = np.asarray(pcd_filtered.points) + intensity_array = np.asarray(pcd.colors) + # filter intensity array + intensity_array = intensity_array[indices_keep] + point_cloud_array = np.c_[points_array, intensity_array[:, 0]] + write_point_cloud_with_intensity(output_path,point_cloud_array, header) + +# Supports traversing multi-layer folders +def process_directory(input_path, output_path, nb_points, radius): + if not os.path.exists(output_path): + # os.mkdir(output_path) + os.makedirs(output_path, exist_ok=True) # can deal with intermediate folders + + for file_name in os.listdir(input_path): + src = os.path.join(input_path, file_name) + dst = os.path.join(output_path, file_name) + if os.path.isdir(src): + process_directory(src, dst, nb_points, radius) + elif src.endswith('.pcd'): + process_point_cloud(src, dst, nb_points, radius) + else: + shutil.copy(src, dst) if __name__ == "__main__": argparser = argparse.ArgumentParser(description=__doc__) @@ -19,56 +58,16 @@ "--input_folder_path_point_clouds", default="point_clouds", type=str, help="Input folder path of point clouds" ) argparser.add_argument( - "--output_folder_path_point_clouds", - default="output", - type=str, - help="Output folder path of filtered point clouds", + "--output_folder_path_point_clouds", default="output", type=str, help="Output folder path of filtered point clouds" ) argparser.add_argument( - "--nb_points", - default="1", - type=int, - help="Pick the minimum amount of points that the sphere should contain. Higher nb_points removes more points.", + "--nb_points", default=1, type=int, help="Minimum number of points in sphere" ) argparser.add_argument( - "--radius", - default="0.4", - type=float, - help="Defines the radius of the sphere that will be used for counting the neighbors.", + "--radius", default=0.4, type=float, help="Radius of sphere to check point density" ) args = argparser.parse_args() - input_folder_path_point_cloud = args.input_folder_path_point_clouds - output_folder_path_point_cloud = args.output_folder_path_point_clouds - nb_points = args.nb_points - radius = args.radius - - if not os.path.exists(output_folder_path_point_cloud): - os.mkdir(output_folder_path_point_cloud) - - for file_name in sorted(os.listdir(input_folder_path_point_cloud)): - point_cloud_array, header = read_point_cloud_with_intensity( - os.path.join(args.input_folder_path_point_clouds, file_name)) - xyz = point_cloud_array[:, :3] - intensities = point_cloud_array[:, 3] - max_intensity = np.max(intensities) - intensities_norm = np.array(intensities / max_intensity) - intensities_norm_two_col = np.c_[intensities_norm, intensities_norm] - intensities_norm_three_col = np.c_[intensities_norm_two_col, intensities_norm] - - pcd = o3d.geometry.PointCloud() - pcd.points = o3d.utility.Vector3dVector(xyz) - pcd.colors = o3d.utility.Vector3dVector(intensities_norm_three_col) + process_directory(args.input_folder_path_point_clouds, args.output_folder_path_point_clouds, args.nb_points, args.radius) - print("num points before: ", str(len(pcd.points))) - pcd_filtered, indices_keep = pcd.remove_radius_outlier(nb_points, radius) - print("num points after: ", str(len(pcd.points))) - print("removed ", str(len(pcd.points) - len(indices_keep)), " outliers.") - points_array = np.asarray(pcd_filtered.points) - intensity_array = np.asarray(pcd.colors) - # filter intensity array - intensity_array = intensity_array[indices_keep] - point_cloud_array = np.c_[points_array, intensity_array[:, 0]] - write_point_cloud_with_intensity(os.path.join(args.output_folder_path_point_clouds, file_name), - point_cloud_array, header)