diff --git "a/Chapter07-\350\257\276\347\250\213\345\261\225\346\234\233\344\270\216\346\200\273\347\273\223/README.md" "b/Chapter07-\350\257\276\347\250\213\345\261\225\346\234\233\344\270\216\346\200\273\347\273\223/README.md" index 26f0ca8..af396a9 100644 --- "a/Chapter07-\350\257\276\347\250\213\345\261\225\346\234\233\344\270\216\346\200\273\347\273\223/README.md" +++ "b/Chapter07-\350\257\276\347\250\213\345\261\225\346\234\233\344\270\216\346\200\273\347\273\223/README.md" @@ -2,8 +2,8 @@ * @Author: Charmve yidazhang1@gmail.com * @Date: 2023-10-10 10:49:13 * @LastEditors: Charmve yidazhang1@gmail.com - * @LastEditTime: 2023-10-10 10:53:07 - * @FilePath: /OccNet-Course/Chapter06-课程展望与总结/README.md + * @LastEditTime: 2024-01-22 19:55:01 + * @FilePath: /OccNet-Course/Chapter07-课程展望与总结/README.md * @Version: 1.0.1 * @Blogs: charmve.blog.csdn.net * @GitHub: https://github.com/Charmve @@ -13,16 +13,27 @@ * Licensed under the MIT License. --> -在本专题课程的课程展望和总结中,主要从OCC与Nerf的关系、端到端的实现、大模型三个方面做未来展望,以及对本课程做一个总结。 +在本专题课程的课程展望和总结中,主要从算法框架、数据、仿真和其他四个方面做未来展望,以及对本课程做一个总结。 -1. OCC与Nerf的关系 +-

算法框架

+ - 数据驱动的端到端 UniAD + - 大模型 LMDrive [关于大模型和自动驾驶的几个迷思](关于大模型和自动驾驶的几个迷思.md) + - 世界模型:Drive-WM、DriveDreamer + - 矢量地图在线建图:MapTRv2 + - BEV-OCC-Transformer: OccFormer、OccWorld、Occupancy Flow -- []() +-

数据

+ - 4D数据自动标注: + - OCC与Nerf联合标注 + - [面向BEV感知的4D标注方案](https://zhuanlan.zhihu.com/p/642735557?utm_psn=1706841959639998464) + - 数据生成:DrivingDiffusion、[MagicDrive](https://zhuanlan.zhihu.com/p/675303127)、UrbanSyn -2. 端到端的实现 +-

仿真

+ - UniSim + - DRIVE Sim -- []() +-

其他

+ - 舱驾一体 + - AI 编译器: MLIR、、XLA、Triton + - 模型剪枝、模型蒸馏、模型压缩、模型量化(PTQ、QAT) -3. 自动驾驶大模型 - -- [关于大模型和自动驾驶的几个迷思](关于大模型和自动驾驶的几个迷思.md) \ No newline at end of file diff --git a/code/BEVFormer/docs/prepare_dataset.md b/code/BEVFormer/docs/prepare_dataset.md index fc73fd8..bc85dff 100644 --- a/code/BEVFormer/docs/prepare_dataset.md +++ b/code/BEVFormer/docs/prepare_dataset.md @@ -15,6 +15,7 @@ unzip can_bus.zip *We genetate custom annotation files which are different from mmdet3d's* ``` +export PYTHONPATH=$PYTHONPATH:./ python tools/create_data.py nuscenes --root-path ./data/nuscenes --out-dir ./data/nuscenes --extra-tag nuscenes --version v1.0 --canbus ./data ``` diff --git a/code/BEVFormer/projects/configs/bevformer/bevformer_base.py b/code/BEVFormer/projects/configs/bevformer/bevformer_base.py index 2915c6f..bf482bb 100644 --- a/code/BEVFormer/projects/configs/bevformer/bevformer_base.py +++ b/code/BEVFormer/projects/configs/bevformer/bevformer_base.py @@ -2,7 +2,7 @@ '../datasets/custom_nus-3d.py', '../_base_/default_runtime.py' ] -# + plugin = True plugin_dir = 'projects/mmdet3d_plugin/' @@ -11,8 +11,6 @@ point_cloud_range = [-51.2, -51.2, -5.0, 51.2, 51.2, 3.0] voxel_size = [0.2, 0.2, 8] - - img_norm_cfg = dict( mean=[103.530, 116.280, 123.675], std=[1.0, 1.0, 1.0], to_rgb=False) # For nuScenes we usually do 10-class detection diff --git a/code/BEVFormer/projects/configs/bevformer/bevformer_small.py b/code/BEVFormer/projects/configs/bevformer/bevformer_small.py index 6380462..901c353 100644 --- a/code/BEVFormer/projects/configs/bevformer/bevformer_small.py +++ b/code/BEVFormer/projects/configs/bevformer/bevformer_small.py @@ -1,10 +1,10 @@ # BEvFormer-small consumes at lease 10500M GPU memory # compared to bevformer_base, bevformer_small has -# smaller BEV: 200*200 -> 150*150 -# less encoder layers: 6 -> 3 -# smaller input size: 1600*900 -> (1600*900)*0.8 -# multi-scale feautres -> single scale features (C5) -# with_cp of backbone = True +# - smaller BEV: 200*200 -> 150*150 +# - less encoder layers: 6 -> 3 +# - smaller input size: 1600*900 -> (1600*900)*0.8 +# - multi-scale feautres -> single scale features (C5) +# - with_cp of backbone = True _base_ = [ '../datasets/custom_nus-3d.py', diff --git a/code/BEVFormer/projects/configs/bevformer/bevformer_tiny.py b/code/BEVFormer/projects/configs/bevformer/bevformer_tiny.py index 5cea65d..b007671 100644 --- a/code/BEVFormer/projects/configs/bevformer/bevformer_tiny.py +++ b/code/BEVFormer/projects/configs/bevformer/bevformer_tiny.py @@ -1,10 +1,10 @@ # BEvFormer-tiny consumes at lease 6700M GPU memory # compared to bevformer_base, bevformer_tiny has -# smaller backbone: R101-DCN -> R50 -# smaller BEV: 200*200 -> 50*50 -# less encoder layers: 6 -> 3 -# smaller input size: 1600*900 -> 800*450 -# multi-scale feautres -> single scale features (C5) +# - smaller backbone: R101-DCN -> R50 +# - smaller BEV: 200*200 -> 50*50 +# - less encoder layers: 6 -> 3 +# - smaller input size: 1600*900 -> 800*450 +# - multi-scale feautres -> single scale features (C5) _base_ = [ diff --git a/code/BEVFormer/tests/bevformer_base/20240122/pts_bbox/results_nusc.json b/code/BEVFormer/tests/bevformer_base/20240122/pts_bbox/results_nusc.json new file mode 100644 index 0000000..4935069 --- /dev/null +++ b/code/BEVFormer/tests/bevformer_base/20240122/pts_bbox/results_nusc.json @@ -0,0 +1,5 @@ +{ + "sample": [ + {"configurations": "test1"} + ] +} \ No newline at end of file diff --git a/code/BEVFormer/tools/analysis_tools/visual.py b/code/BEVFormer/tools/analysis_tools/visual.py index f711b75..f6e286a 100644 --- a/code/BEVFormer/tools/analysis_tools/visual.py +++ b/code/BEVFormer/tools/analysis_tools/visual.py @@ -4,21 +4,18 @@ # --------------------------------------------- import mmcv -from nuscenes.nuscenes import NuScenes + from PIL import Image -from nuscenes.utils.geometry_utils import view_points, box_in_image, BoxVisibility, transform_matrix from typing import Tuple, List, Iterable -import matplotlib.pyplot as plt + import numpy as np -from PIL import Image -from matplotlib import rcParams -from matplotlib.axes import Axes -from pyquaternion import Quaternion -from PIL import Image from matplotlib import rcParams +import matplotlib.pyplot as plt from matplotlib.axes import Axes from pyquaternion import Quaternion from tqdm import tqdm + +from nuscenes.nuscenes import NuScenes from nuscenes.utils.data_classes import LidarPointCloud, RadarPointCloud, Box from nuscenes.utils.geometry_utils import view_points, box_in_image, BoxVisibility, transform_matrix from nuscenes.eval.common.data_classes import EvalBoxes, EvalBox @@ -26,9 +23,6 @@ from nuscenes.eval.detection.utils import category_to_detection_name from nuscenes.eval.detection.render import visualize_sample - - - cams = ['CAM_FRONT', 'CAM_FRONT_RIGHT', 'CAM_BACK_RIGHT', @@ -36,13 +30,6 @@ 'CAM_BACK_LEFT', 'CAM_FRONT_LEFT'] -import numpy as np -import matplotlib.pyplot as plt -from nuscenes.utils.data_classes import LidarPointCloud, RadarPointCloud, Box -from PIL import Image -from matplotlib import rcParams - - def render_annotation( anntoken: str, margin: float = 10, @@ -84,6 +71,7 @@ def render_annotation( fig, axes = plt.subplots(1, num_cam + 1, figsize=(18, 9)) select_cams = [sample_record['data'][cam] for cam in select_cams] print('bbox in cams:', select_cams) + # Plot LIDAR view. lidar = sample_record['data']['LIDAR_TOP'] data_path, boxes, camera_intrinsic = nusc.get_sample_data(lidar, selected_anntokens=[anntoken]) @@ -142,8 +130,6 @@ def render_annotation( if out_path is not None: plt.savefig(out_path) - - def get_sample_data(sample_data_token: str, box_vis_level: BoxVisibility = BoxVisibility.ANY, selected_anntokens=None, @@ -205,8 +191,6 @@ def get_sample_data(sample_data_token: str, return data_path, box_list, cam_intrinsic - - def get_predicted_data(sample_data_token: str, box_vis_level: BoxVisibility = BoxVisibility.ANY, selected_anntokens=None, @@ -269,9 +253,6 @@ def get_predicted_data(sample_data_token: str, return data_path, box_list, cam_intrinsic - - - def lidiar_render(sample_token, data,out_path=None): bbox_gt_list = [] bbox_pred_list = [] @@ -316,7 +297,6 @@ def lidiar_render(sample_token, data,out_path=None): print('blue is the predited result') visualize_sample(nusc, sample_token, gt_annotations, pred_annotations, savepath=out_path+'_bev') - def get_color(category_name: str): """ Provides the default colors based on the category names. @@ -347,7 +327,6 @@ def get_color(category_name: str): return nusc.colormap[key] return [0, 0, 0] - def render_sample_data( sample_toekn: str, with_anns: bool = True, @@ -471,7 +450,7 @@ def render_sample_data( if __name__ == '__main__': nusc = NuScenes(version='v1.0-trainval', dataroot='./data/nuscenes', verbose=True) # render_annotation('7603b030b42a4b1caa8c443ccc1a7d52') - bevformer_results = mmcv.load('test/bevformer_base/Thu_Jun__9_16_22_37_2022/pts_bbox/results_nusc.json') + bevformer_results = mmcv.load('tests/bevformer_base/20240122/pts_bbox/results_nusc.json') sample_token_list = list(bevformer_results['results'].keys()) for id in range(0, 10): render_sample_data(sample_token_list[id], pred_data=bevformer_results, out_path=sample_token_list[id]) diff --git a/code/BEVFusion/docker/notebook b/code/BEVFusion/docker/notebook index 15686d3..17deada 100644 --- a/code/BEVFusion/docker/notebook +++ b/code/BEVFusion/docker/notebook @@ -2,7 +2,7 @@ echo $BASE_URL if [ `whoami` = "root" ];then - # 启动jupyter并设置对应路径 + # 启动jupyter并设置对应路径 /opt/conda/bin/jupyter lab --ip=0.0.0.0 --LabApp.base_url=$BASE_URL --allow-root else /opt/conda/bin/jupyter lab --ip=0.0.0.0 --LabApp.base_url=$BASE_URL diff --git a/tools/app/config.json b/tools/app/config.json index 11d7c22..790439b 100644 --- a/tools/app/config.json +++ b/tools/app/config.json @@ -1,6 +1,6 @@ { - "name": "Qbot VIPs", - "url": "ufund-me.github.io/Qbot", + "name": "OccCourse VIPs", + "url": "maiweiai.site", "author": "Charmve", "isNonProfit": true, "address": { diff --git a/tools/app/myapp.py b/tools/app/myapp.py index 3cb8b7b..9120561 100644 --- a/tools/app/myapp.py +++ b/tools/app/myapp.py @@ -24,7 +24,7 @@ # mail_license = os.getenv("MAIL_LICENSE") mail_license = "szfiwfywaakhieda" # 收件人邮箱,可以为多个收件人 -mail_receivers = ["yidazhang1@gmail.com", "zhangwei@qcraft.ai"] +mail_receivers = ["yidazhang1@gmail.com", "zhangwei@maiwei.ai"] @app.route('/') diff --git a/tools/app/pull_issues.py b/tools/app/pull_issues.py index aba8800..5150aff 100644 --- a/tools/app/pull_issues.py +++ b/tools/app/pull_issues.py @@ -18,8 +18,8 @@ def pull_issues(data = data): # 发送 POST 请求 # https://github.com/Charmve/100days - repo_owner = "UFund-Me" - repo_name = "Qbot" + repo_owner = "Charmve" + repo_name = "OccNet-Course" url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/issues" response = re.post(url, headers=headers, json=data) @@ -52,7 +52,7 @@ def close_github_issues(issue_url): if __name__ == '__main__': issues_data = { - "title": "🌈 {today} 来自Qbot的今日AI选股推送", + "title": "💡 {today} 来自OccCource更新提醒", "body": "{content}" } pull_issues(issues_data) \ No newline at end of file diff --git a/tools/pointcloud_viewer.py b/tools/pointcloud_viewer.py new file mode 100644 index 0000000..a344806 --- /dev/null +++ b/tools/pointcloud_viewer.py @@ -0,0 +1,1883 @@ +__author__ = "Martin Hahner" +__contact__ = "martin.hahner@pm.me" +__license__ = "CC BY-NC 4.0 (https://creativecommons.org/licenses/by-nc/4.0/)" + +# GUI adapted from +# https://memotut.com/create-a-3d-model-viewer-with-pyqt5-and-pyqtgraph-b3916/ and +# https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_qt_sgskip.html + +import os +import copy +import gzip +import socket +import pandas +import logging +import argparse + +import numpy as np +import pickle as pkl +import matplotlib as mpl +import matplotlib.cm as cm +import multiprocessing as mp +import pyqtgraph.opengl as gl + +from glob import glob +from typing import List +from pathlib import Path +from plyfile import PlyData + +from PyQt5.QtGui import * +from PyQt5.QtCore import * +from PyQt5.QtWidgets import * +from pyqtgraph.Qt import QtGui +from fog_simulation import ParameterSet, RNG, simulate_fog + +from SeeingThroughFog.tools.DatasetViewer.dataset_viewer import load_calib_data, read_label +from SeeingThroughFog.tools.DatasetFoggification.beta_modification import BetaRadomization +from SeeingThroughFog.tools.DatasetFoggification.lidar_foggification import haze_point_cloud + + # R, G, B, alpha +COLORS = [( 0, 255, 0, 255), # cars in green + (255, 0, 0, 255), # pedestrian in red + (255, 255, 0, 255)] # cyclists in yellow + + +parser = argparse.ArgumentParser() +parser.add_argument('-d', '--datasets', type=str, help='path to where you store your datasets', + default=str(Path.home() / 'datasets')) +parser.add_argument('-e', '--experiments', type=str, help='path to where you store your OpenPCDet experiments', + default=str(Path.home() / 'repositories/PCDet/output')) +args = parser.parse_args() + +DATASETS_ROOT = Path(args.datasets) +EXPERIMENTS_ROOT = Path(args.experiments) + +FOG = DATASETS_ROOT / 'DENSE/SeeingThroughFog/lidar_hdl64_strongest_fog_extraction' +AUDI = DATASETS_ROOT / 'A2D2/camera_lidar_semantic_bboxes' +LYFT = DATASETS_ROOT / 'LyftLevel5/Perception/train_lidar' +ARGO = DATASETS_ROOT / 'Argoverse' +PANDA = DATASETS_ROOT / 'PandaSet' +DENSE = DATASETS_ROOT / 'DENSE/SeeingThroughFog/lidar_hdl64_strongest' +KITTI = DATASETS_ROOT / 'KITTI/3D/training/velodyne' +WAYMO = DATASETS_ROOT / 'WaymoOpenDataset/WOD/train/velodyne' +HONDA = DATASETS_ROOT / 'Honda_3D/scenarios' +APOLLO = DATASETS_ROOT / 'Apollo3D' +NUSCENES = DATASETS_ROOT / 'nuScenes/sweeps/LIDAR_TOP' + +if socket.gethostname() == 'beast': + DENSE = Path.home() / 'datasets_local' / 'DENSE/SeeingThroughFog/lidar_hdl64_strongest' + + + +def get_extracted_fog_file_list(dirname: str) -> List[str]: + + file_list = [y for x in os.walk(dirname) for y in glob(os.path.join(x[0], f'*.bin'))] + + return sorted(file_list) + + +class Namespace: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +class MyWindow(QMainWindow): + + def __init__(self) -> None: + + super(MyWindow, self).__init__() + + self.boxes = {} + self.predictions = {} + self.result_dict = {} + self.show_predictions = True + self.prediction_threshold = 50 + + self.gain = True + self.noise_variant = 'v4' + + self.noise = 10 + self.noise_min = 0 + self.noise_max = 20 + + self.min_fog_response = -1 + self.max_fog_response = -1 + self.num_fog_responses = -1 + + self.num_cpus = mp.cpu_count() + self.pool = mp.Pool(self.num_cpus) + + self.p = ParameterSet(gamma=0.000001, + gamma_min=0.0000001, + gamma_max=0.00001, + gamma_scale=10000000) + + self.p.beta_0 = self.p.gamma / np.pi + self.row_height = 20 + + hostname = socket.gethostname() + + if hostname == 'beast': + + self.monitor = QDesktopWidget().screenGeometry(1) + self.monitor.setHeight(int(0.45 * self.monitor.height())) + + elif hostname == 'hox': + + self.monitor = QDesktopWidget().screenGeometry(2) + self.monitor.setHeight(int(0.45 * self.monitor.height())) + + else: + + self.monitor = QDesktopWidget().screenGeometry(0) + self.monitor.setHeight(self.monitor.height()) + + self.setGeometry(self.monitor) + + self.setAcceptDrops(True) + + self.simulated_fog = False + self.simulated_fog_pc = None + + self.simulated_fog_dense = False + + self.extracted_fog = False + self.extracted_fog_pc = None + self.extracted_fog_index = -1 + self.extracted_fog_mesh = None + self.extracted_fog_file_list = None + + self.color_dict = {0: 'x', + 1: 'y', + 2: 'z', + 3: 'intensity', + 4: 'distance', + 5: 'angle', + 6: 'channel'} + + self.min_value = 0 + self.max_value = 63 + self.num_features = 5 + self.color_feature = 2 + self.point_size = 3 + self.threshold = 50 + self.dataset = None + self.success = False + self.extension = 'bin' + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_name = self.color_dict[self.color_feature] + + self.lastDir = None + self.current_pc = None + self.fogless_pc = None + self.current_mesh = None + self.droppedFilename = None + + self.file_name = None + self.file_list = None + self.index = -1 + + self.centerWidget = QWidget() + self.setCentralWidget(self.centerWidget) + + self.layout = QGridLayout() + self.centerWidget.setLayout(self.layout) + + self.grid_dimensions = 20 + self.viewer = gl.GLViewWidget() + self.viewer.setWindowTitle('drag & drop point cloud viewer') + self.viewer.setCameraPosition(distance=2 * self.grid_dimensions) + self.layout.addWidget(self.viewer, 0, 0, 1, 6) + + self.grid = gl.GLGridItem() + self.grid.setSize(self.grid_dimensions, self.grid_dimensions) + self.grid.setSpacing(1, 1) + self.grid.translate(0, 0, -2) + self.viewer.addItem(self.grid) + + self.reset_btn = QPushButton("reset") + self.reset_btn.clicked.connect(self.reset) + self.layout.addWidget(self.reset_btn, 1, 3, 1, 2) + + self.load_kitti_btn = QPushButton("KITTI") + self.load_kitti_btn.clicked.connect(self.load_kitti) + self.layout.addWidget(self.load_kitti_btn, 2, 3) + + self.load_dense_btn = QPushButton("DENSE") + self.load_dense_btn.clicked.connect(self.load_dense) + self.layout.addWidget(self.load_dense_btn, 3, 3) + + self.load_honda_btn = QPushButton("H3D") + self.load_honda_btn.clicked.connect(self.load_honda) + self.layout.addWidget(self.load_honda_btn, 4, 3) + + self.load_audi_btn = QPushButton("A2D2") + self.load_audi_btn.clicked.connect(self.load_audi) + self.layout.addWidget(self.load_audi_btn, 5, 3) + + self.load_panda_btn = QPushButton("PandaSet") + self.load_panda_btn.clicked.connect(self.load_panda) + self.layout.addWidget(self.load_panda_btn, 6, 3) + + self.load_nuscenes_btn = QPushButton("nuScenes") + self.load_nuscenes_btn.clicked.connect(self.load_nuscenes) + self.layout.addWidget(self.load_nuscenes_btn, 2, 4) + + self.load_lyft_btn = QPushButton("LyftL5") + self.load_lyft_btn.clicked.connect(self.load_lyft) + self.layout.addWidget(self.load_lyft_btn, 3, 4) + + self.load_kitti_btn = QPushButton("Argoverse") + self.load_kitti_btn.clicked.connect(self.load_argo) + self.layout.addWidget(self.load_kitti_btn, 4, 4) + + self.load_waymo_btn = QPushButton("Waymo") + self.load_waymo_btn.clicked.connect(self.load_waymo) + self.layout.addWidget(self.load_waymo_btn, 5, 4) + + self.load_apollo_btn = QPushButton("Apollo") + self.load_apollo_btn.clicked.connect(self.load_apollo) + self.layout.addWidget(self.load_apollo_btn, 6, 4) + + self.load_fog_btn = QPushButton("extracted fog samples") + self.load_fog_btn.clicked.connect(self.load_extracted_fog_samples) + self.layout.addWidget(self.load_fog_btn, 7, 3, 1, 2) + + if self.extracted_fog: + self.toggle_extracted_fog_btn = QPushButton("remove extracted fog") + else: + self.toggle_extracted_fog_btn = QPushButton("add extracted fog") + + self.toggle_extracted_fog_btn.clicked.connect(self.toggle_extracted_fog) + self.layout.addWidget(self.toggle_extracted_fog_btn, 8, 3, 1, 2) + + if self.simulated_fog: + self.toggle_simulated_fog_btn = QPushButton("remove our fog simulation") + else: + self.toggle_simulated_fog_btn = QPushButton("add our fog simulation") + + self.toggle_simulated_fog_btn.clicked.connect(self.toggle_simulated_fog) + self.layout.addWidget(self.toggle_simulated_fog_btn, 9, 3, 1, 2) + + self.choose_dir_btn = QPushButton("choose custom directory") + self.choose_dir_btn.clicked.connect(self.show_directory_dialog) + self.layout.addWidget(self.choose_dir_btn, 2, 1) + + self.prev_btn = QPushButton("<-") + self.next_btn = QPushButton("->") + + self.prev_btn.clicked.connect(self.decrement_index) + self.next_btn.clicked.connect(self.increment_index) + + self.layout.addWidget(self.prev_btn, 2, 0) + self.layout.addWidget(self.next_btn, 2, 2) + + self.color_title = QLabel("color code") + self.color_title.setAlignment(Qt.AlignCenter) + self.layout.addWidget(self.color_title, 3, 0) + + self.color_label = QLabel(self.color_name) + self.color_label.setAlignment(Qt.AlignCenter) + self.layout.addWidget(self.color_label, 3, 2) + + self.color_slider = QSlider(Qt.Horizontal) + self.color_slider.setMinimum(0) + self.color_slider.setMaximum(6) + self.color_slider.setValue(self.color_feature) + self.color_slider.setTickPosition(QSlider.TicksBelow) + self.color_slider.setTickInterval(1) + + self.layout.addWidget(self.color_slider, 3, 1) + self.color_slider.valueChanged.connect(self.color_slider_change) + + self.threshold_title = QLabel("fog threshold") + self.threshold_title.setAlignment(Qt.AlignCenter) + self.layout.addWidget(self.threshold_title, 4, 0) + + self.threshold_label = QLabel(str(self.threshold)) + self.threshold_label.setAlignment(Qt.AlignCenter) + self.layout.addWidget(self.threshold_label, 4, 2) + + self.threshold_slider = QSlider(Qt.Horizontal) + self.threshold_slider.setMinimum(0) + self.threshold_slider.setMaximum(255) + self.threshold_slider.setValue(self.threshold) + self.threshold_slider.setTickPosition(QSlider.TicksBelow) + self.threshold_slider.setTickInterval(1) + + self.layout.addWidget(self.threshold_slider, 4, 1) + self.threshold_slider.valueChanged.connect(self.threshold_slider_change) + + self.file_name_label = QLabel() + self.file_name_label.setAlignment(Qt.AlignCenter) + self.file_name_label.setMaximumSize(self.monitor.width(), 20) + self.layout.addWidget(self.file_name_label, 1, 1, 1, 1) + + self.reset_btn.setEnabled(False) + self.next_btn.setEnabled(False) + self.prev_btn.setEnabled(False) + self.toggle_extracted_fog_btn.setEnabled(False) + self.toggle_simulated_fog_btn.setEnabled(False) + + ########################### + # fog simulation controls # + ########################### + + self.current_row = 5 + + self.mor_label = QLabel(f'meteorological optical range (MOR) = {round(self.p.mor, 2)}m') + self.mor_label.setAlignment(Qt.AlignCenter) + self.mor_label.setMaximumSize(self.monitor.width(), self.row_height) + self.layout.addWidget(self.mor_label, self.current_row, 1) + self.current_row += 1 + + self.alpha_title = QLabel('attenuation coefficient') + self.alpha_title.setAlignment(Qt.AlignRight) + self.layout.addWidget(self.alpha_title, self.current_row, 0) + + self.alpha_slider = QSlider(Qt.Horizontal) + self.alpha_slider.setMinimum(int(self.p.alpha_min * self.p.alpha_scale)) + self.alpha_slider.setMaximum(int(self.p.alpha_max * self.p.alpha_scale)) + self.alpha_slider.setValue(int(self.p.alpha * self.p.alpha_scale)) + + self.layout.addWidget(self.alpha_slider, self.current_row, 1) + self.alpha_slider.valueChanged.connect(self.update_labels) + + self.alpha_label = QLabel(f"\u03B1 = {self.p.alpha}") + self.alpha_label.setAlignment(Qt.AlignLeft) + self.layout.addWidget(self.alpha_label, self.current_row, 2) + self.current_row += 1 + + self.beta_title = QLabel('backscattering coefficient') + self.beta_title.setAlignment(Qt.AlignRight) + self.layout.addWidget(self.beta_title, self.current_row, 0) + + self.beta_slider = QSlider(Qt.Horizontal) + self.beta_slider.setMinimum(int(self.p.beta_min * self.p.beta_scale)) + self.beta_slider.setMaximum(int(self.p.beta_max * self.p.beta_scale)) + self.beta_slider.setValue(int(self.p.beta * self.p.beta_scale)) + + self.layout.addWidget(self.beta_slider, self.current_row, 1) + self.beta_slider.valueChanged.connect(self.update_labels) + + self.beta_label = QLabel(f"\u03B2 = {round(self.p.beta * self.p.mor, 3)} / MOR") + self.beta_label.setAlignment(Qt.AlignLeft) + self.layout.addWidget(self.beta_label, self.current_row, 2) + self.current_row += 1 + + self.gamma_title = QLabel("reflextivity of the hard target") + self.gamma_title.setAlignment(Qt.AlignRight) + self.layout.addWidget(self.gamma_title, self.current_row, 0) + + self.gamma_slider = QSlider(Qt.Horizontal) + self.gamma_slider.setMinimum(int(self.p.gamma_min * self.p.gamma_scale)) + self.gamma_slider.setMaximum(int(self.p.gamma_max * self.p.gamma_scale)) + self.gamma_slider.setValue(int(self.p.gamma * self.p.gamma_scale)) + + self.layout.addWidget(self.gamma_slider, self.current_row, 1) + self.gamma_slider.valueChanged.connect(self.update_labels) + + self.gamma_label = QLabel(f"\u0393 = {self.p.gamma}") + self.gamma_label.setAlignment(Qt.AlignLeft) + self.layout.addWidget(self.gamma_label, self.current_row, 2) + self.current_row += 1 + + self.noise_title = QLabel("spread of relative noise") + self.noise_title.setAlignment(Qt.AlignRight) + self.layout.addWidget(self.noise_title, self.current_row, 0) + + self.noise_slider = QSlider(Qt.Horizontal) + self.noise_slider.setMinimum(self.noise_min) + self.noise_slider.setMaximum(self.noise_max) + self.noise_slider.setValue(self.noise) + + self.layout.addWidget(self.noise_slider, self.current_row, 1) + self.noise_slider.valueChanged.connect(self.update_labels) + + self.noise_label = QLabel(f"{self.noise}m") + self.noise_label.setAlignment(Qt.AlignLeft) + self.layout.addWidget(self.noise_label, self.current_row, 2) + self.current_row += 1 + + self.num_info = QLabel("") + self.num_info.setAlignment(Qt.AlignLeft) + self.num_info.setMaximumSize(self.monitor.width(), self.row_height) + self.layout.addWidget(self.num_info, self.current_row, 0) + + self.log_info = QLabel("") + self.log_info.setAlignment(Qt.AlignLeft) + self.log_info.setMaximumSize(self.monitor.width(), self.row_height) + self.layout.addWidget(self.log_info, self.current_row, 1, 1, 3) + + if self.extracted_fog: + self.threshold_slider.setEnabled(True) + else: + self.threshold_slider.setEnabled(False) + + if self.simulated_fog: + + self.toggle_simulated_fog_btn.setText('remove our fog simulation') + self.alpha_slider.setEnabled(True) + self.beta_slider.setEnabled(True) + self.gamma_slider.setEnabled(True) + # self.noise_slider.setEnabled(True) + + else: + + self.toggle_simulated_fog_btn.setText('add our fog simulation') + self.alpha_slider.setEnabled(False) + self.beta_slider.setEnabled(False) + self.gamma_slider.setEnabled(False) + # self.noise_slider.setEnabled(False) + + self.dense_split_paths = [] + + self.cb = QComboBox() + # self.cb.setEditable(True) + # self.cb.lineEdit().setReadOnly(True) + # self.cb.lineEdit().setAlignment(Qt.AlignCenter) + self.cb.addItems(self.populate_dense_splits()) + self.cb.currentIndexChanged.connect(self.selection_change) + self.cb.setEnabled(False) + self.layout.addWidget(self.cb, self.current_row, 3, 1, 2) + self.current_row += 1 + + if self.simulated_fog_dense: + self.toggle_simulated_fog_dense_btn = QPushButton("remove STF fog simulation") + else: + self.toggle_simulated_fog_dense_btn = QPushButton("add STF fog simulation") + + self.toggle_simulated_fog_dense_btn.clicked.connect(self.toggle_simulated_fog_dense) + self.layout.addWidget(self.toggle_simulated_fog_dense_btn, self.current_row, 3, 1, 2) + self.toggle_simulated_fog_dense_btn.setEnabled(False) + + # Create textbox + if self.show_predictions: + self.visualize_predictions_path_btn = QPushButton('hide predictions', self) + else: + self.visualize_predictions_path_btn = QPushButton('show predictions', self) + self.visualize_predictions_path_btn.setEnabled(False) + self.layout.addWidget(self.visualize_predictions_path_btn, self.current_row, 0, 1, 1) + self.visualize_predictions_path_btn.clicked.connect(self.toggle_predictions) + self.experiment_path_box = QLineEdit(self) + self.experiment_path_box.setText('dense_models/pv_rcnn/' + '2021-03-13_01-29-41_98d97b57_dense_models_batch_size_24_pv_rcnn') + self.layout.addWidget(self.experiment_path_box, self.current_row, 1, 1, 1) + self.load_experiment_path_btn = QPushButton('load results', self) + self.layout.addWidget(self.load_experiment_path_btn, self.current_row, 2, 1, 1) + self.load_experiment_path_btn.clicked.connect(self.load_results) + + self.current_row += 1 + + self.prediction_threshold_title = QLabel("prediction confidence") + self.prediction_threshold_title.setAlignment(Qt.AlignCenter) + self.layout.addWidget(self.prediction_threshold_title, self.current_row, 0) + + self.prediction_threshold_label = QLabel(str(self.prediction_threshold)) + self.prediction_threshold_label.setAlignment(Qt.AlignCenter) + self.layout.addWidget(self.prediction_threshold_label, self.current_row, 2) + + self.prediction_threshold_slider = QSlider(Qt.Horizontal) + self.prediction_threshold_slider.setMinimum(0) + self.prediction_threshold_slider.setMaximum(100) + self.prediction_threshold_slider.setValue(self.prediction_threshold) + self.prediction_threshold_slider.setTickPosition(QSlider.TicksBelow) + self.prediction_threshold_slider.setTickInterval(10) + self.prediction_threshold_slider.setEnabled(False) + + self.layout.addWidget(self.prediction_threshold_slider, self.current_row, 1) + self.prediction_threshold_slider.valueChanged.connect(self.prediction_threshold_slider_change) + + self.current_row += 1 + + # hide broken functionality for now + self.toggle_extracted_fog_btn.setVisible(False) + self.load_fog_btn.setVisible(False) + + self.threshold_title.setVisible(False) + self.threshold_slider.setVisible(False) + self.threshold_label.setVisible(False) + + self.noise_title.setVisible(False) + self.noise_slider.setVisible(False) + self.noise_label.setVisible(False) + + + def load_results(self) -> None: + + exp_dir = EXPERIMENTS_ROOT / self.experiment_path_box.text() + + test_folders = [x[0] for x in os.walk(exp_dir) if 'epoch' in x[0] and 'test' in x[0]] + + self.result_dict = {} + + for test_folder in test_folders: + key = test_folder.split('/')[-1] + + pkl_path = Path(test_folder) / 'result.pkl' + + with open(pkl_path, 'rb') as f: + self.result_dict[key] = pkl.load(f) + + if self.result_dict is not None: + self.visualize_predictions_path_btn.setEnabled(True) + self.prediction_threshold_slider.setEnabled(True) + + + def visualize_predictions(self) -> None: + + split = self.cb.currentText() + + if 'test' in split: + + pred_dict = self.result_dict[split][self.index] + + assert self.file_name.split('/')[-1].split('.')[0] == pred_dict['frame_id'], f'frame missmatch ' \ + f"{self.file_name.split('/')[-1].split('.')[0]} != {pred_dict['frame_id']}" + + lookup = {'Car': 0, + 'Pedestrian': 1, + 'Cyclist': 2} + + predictions = np.zeros((pred_dict['boxes_lidar'].shape[0], 9)) + predictions[:, 0:-2] = pred_dict['boxes_lidar'] + predictions[:, 7] = np.array([lookup[name] for name in pred_dict['name']]) + predictions[:, 8] = pred_dict['score'] + + for prediction in predictions: + + x, y, z, w, l, h, rotation, category, score = prediction + + if score*100 > self.prediction_threshold: + + dist = np.sqrt(x**2 + y**2 + z**2) + + rotation = np.rad2deg(rotation) + 90 + + color = (255, 255, 255, 255) # white + + box = gl.GLBoxItem(QtGui.QVector3D(1, 1, 1), color=color) + box.setSize(l, w, h) + + box.translate(-l / 2, -w / 2, -h / 2) + box.rotate(angle=rotation, x=0, y=0, z=1) + box.translate(x, y, z) + + self.viewer.addItem(box) + + self.predictions[dist] = box + + + def populate_dense_splits(self) -> List[str]: + + split_folder = Path(__file__).parent.absolute() / 'SeeingThroughFog' / 'splits' + + splits = [] + + for file in os.listdir(split_folder): + + if file.endswith('.txt'): + + splits.append(file.replace('.txt', '')) + + self.dense_split_paths.append(split_folder / file) + + self.dense_split_paths = sorted(self.dense_split_paths) + + return sorted(splits) + + + def selection_change(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + split = self.cb.currentText() + + # open file and read the content in a list + with open(f'SeeingThroughFog/splits/{split}.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(DENSE) / f'{line[:-1].replace(",", "_")}.bin' + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_dense() + self.show_pointcloud(self.file_list[self.index]) + + + def update_labels(self) -> None: + + self.p.alpha = self.alpha_slider.value() / self.p.alpha_scale + self.alpha_label.setText(f"\u03B1 = {self.p.alpha}") + + self.p.mor = np.log(20) / self.p.alpha + self.mor_label.setText(f'meteorological optical range (MOR) = {round(self.p.mor, 2)}m') + + self.p.beta_scale = 1000 * self.p.mor + self.p.beta = self.beta_slider.value() / self.p.beta_scale + self.beta_label.setText(f"\u03B2 = {round(self.p.beta * self.p.mor, 3)} / MOR") + + self.p.gamma = self.gamma_slider.value() / self.p.gamma_scale + self.gamma_label.setText(f"\u0393 = {self.p.gamma}") + self.p.beta_0 = self.p.gamma / np.pi + + self.noise = self.noise_slider.value() + self.noise_label.setText(f"{self.noise}m") + + if self.file_list: + self.show_pointcloud(self.file_list[self.index]) + + + def reset_fog_buttons(self) -> None: + + self.boxes = {} + + self.threshold_slider.setEnabled(False) + self.alpha_slider.setEnabled(False) + self.beta_slider.setEnabled(False) + self.gamma_slider.setEnabled(False) + self.noise_slider.setEnabled(False) + + self.simulated_fog = False + self.simulated_fog_dense = False + self.toggle_simulated_fog_btn.setText('add our fog simulation') + self.toggle_simulated_fog_dense_btn.setText('add STF fog simulation') + + self.extracted_fog = False + self.toggle_extracted_fog_btn.setText('add extraced fog') + + + def reset(self) -> None: + + self.reset_viewer() + self.reset_fog_buttons() + self.reset_custom_values() + + self.cb.setEnabled(False) + self.toggle_extracted_fog_btn.setEnabled(False) + self.toggle_simulated_fog_btn.setEnabled(False) + self.toggle_simulated_fog_dense_btn.setEnabled(False) + + self.file_list = None + + self.current_pc = None + self.current_mesh = None + + self.simulated_fog = False + self.simulated_fog_dense = False + + self.extracted_fog = False + self.extracted_fog_pc = None + self.extracted_fog_mesh = None + self.extracted_fog_index = -1 + + + def reset_custom_values(self) -> None: + + self.min_value = 0 + self.max_value = 63 + self.num_features = 5 + self.dataset = None + self.success = False + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_name = self.color_dict[self.color_feature] + + + def threshold_slider_change(self) -> None: + + self.threshold = self.threshold_slider.value() + self.threshold_label.setText(str(self.threshold)) + + if self.current_mesh and Path(self.file_name).suffix != '.pickle': + self.show_pointcloud(self.file_name) + + + def prediction_threshold_slider_change(self) -> None: + + self.prediction_threshold = self.prediction_threshold_slider.value() + self.prediction_threshold_label.setText(str(self.prediction_threshold)) + + if self.file_list: + self.show_pointcloud(self.file_list[self.index]) + + + def color_slider_change(self) -> None: + + self.color_feature = self.color_slider.value() + + self.color_name = self.color_dict[self.color_feature] + self.color_label.setText(self.color_name) + + if self.current_mesh: + + if Path(self.file_name).suffix == '.pickle': + self.show_pcdet_dict(self.file_name) + else: + self.show_pointcloud(self.file_name) + + + def check_index_overflow(self) -> None: + + if self.index == -1: + self.index = len(self.file_list) - 1 + + if self.index >= len(self.file_list): + self.index = 0 + + + def decrement_index(self) -> None: + + if self.index != -1: + + self.index -= 1 + self.check_index_overflow() + + if Path(self.file_list[self.index]).suffix == ".pickle": + self.show_pcdet_dict(self.file_list[self.index]) + else: + self.show_pointcloud(self.file_list[self.index]) + + + def increment_index(self) -> None: + + if self.index != -1: + + self.index += 1 + self.check_index_overflow() + + if Path(self.file_list[self.index]).suffix == ".pickle": + self.show_pcdet_dict(self.file_list[self.index]) + else: + self.show_pointcloud(self.file_list[self.index]) + + + def set_pc_det(self, before: bool) -> None: + + self.min_value = 0 + self.max_value = 63 + self.extension = 'pickle' + self.d_type = np.float32 + self.intensity_multiplier = 1 + + if before: + + self.dataset = 'before' + self.num_features = 5 + self.color_dict[6] = 'channel' + + else: + + self.dataset = 'after' + self.num_features = 4 + self.color_dict[6] = 'not available' + + + def set_extracted_fog_samples(self): + + self.threshold_slider.setEnabled(True) + + self.dataset = 'FOG' + self.min_value = 0 + self.max_value = 63 + self.num_features = 5 + self.extension = 'bin' + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_dict[6] = 'channel' + + + + def set_kitti(self) -> None: + self.dataset = 'KITTI' + self.min_value = -1 + self.max_value = -1 + self.num_features = 4 + self.extension = 'bin' + self.d_type = np.float32 + self.intensity_multiplier = 255 + self.color_dict[6] = 'not available' + + + def load_kitti(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/KITTI.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(KITTI) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_kitti() + self.show_pointcloud(self.file_list[self.index]) + + + def set_audi(self) -> None: + + self.dataset = 'A2D2' + self.min_value = 0 + self.max_value = 4 + self.num_features = 5 + self.extension = 'npz' + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_dict[6] = 'lidar_id' + + + def load_audi(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/A2D2.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(AUDI) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_audi() + self.show_pointcloud(self.file_list[self.index]) + + + def set_honda(self) -> None: + + self.dataset = 'Honda3D' + self.min_value = 0 + self.max_value = 63 + self.num_features = 5 + self.extension = 'ply' + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_dict[6] = 'channel' + + + def load_honda(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/Honda3D.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(HONDA) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_honda() + self.show_pointcloud(self.file_list[self.index]) + + + def set_argo(self) -> None: + + self.dataset = 'Argoverse' + self.min_value = 0 + self.max_value = 31 + self.num_features = 5 + self.extension = 'ply' + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_dict[6] = 'channel' + + + def load_argo(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/Argoverse.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(ARGO) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_argo() + self.show_pointcloud(self.file_list[self.index]) + + + def set_dense(self) -> None: + + self.dataset = 'DENSE' + self.min_value = 0 + self.max_value = 63 + self.num_features = 5 + self.extension = 'bin' + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_dict[6] = 'channel' + + self.cb.setEnabled(True) + + + def load_dense(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/DENSE.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(DENSE) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_dense() + self.cb.setCurrentText('all') + self.show_pointcloud(self.file_list[self.index]) + + + def set_nuscenes(self) -> None: + + self.dataset = 'nuScenes' + self.min_value = 0 + self.max_value = 31 + self.num_features = 5 + self.extension = 'bin' + self.intensity_multiplier = 1 + self.color_dict[6] = 'channel' + + + def load_nuscenes(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + with open('file_lists/nuScenes.pkl', 'rb') as f: + file_list = pkl.load(f) + + for file in file_list: + self.file_list.append(NUSCENES + file) + + self.index = 0 + self.set_nuscenes() + self.show_pointcloud(self.file_list[self.index]) + + + def set_lyft(self) -> None: + + self.dataset = 'LyftL5' + self.min_value = 0 + self.max_value = 16 + self.num_features = 5 + self.extension = 'bin' + self.intensity_multiplier = 1 + self.color_dict[6] = 'channel' + + + def load_lyft(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/LyftL5.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(LYFT) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_lyft() + self.show_pointcloud(self.file_list[self.index]) + + + def set_waymo(self) -> None: + + self.dataset = 'WaymoOpenDataset' + self.min_value = -1 + self.max_value = -1 + self.num_features = 4 + self.extension = 'bin' + self.d_type = np.float32 + self.intensity_multiplier = 255 + self.color_dict[6] = 'not available' + + + def load_waymo(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/WAYMO.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(WAYMO) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_waymo() + self.show_pointcloud(self.file_list[self.index]) + + + def set_panda(self) -> None: + + self.dataset = 'PandaSet' + self.min_value = 0 + self.max_value = 1 + self.num_features = 5 + self.extension = 'pkl.gz' + self.d_type = np.float32 + self.intensity_multiplier = 1 + self.color_dict[6] = 'lidar_id' + + + def load_panda(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/PandaSet.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(PANDA) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_panda() + self.show_pointcloud(self.file_list[self.index]) + + + def set_apollo(self) -> None: + + self.dataset = 'Apollo' + self.min_value = -1 + self.max_value = -1 + self.num_features = 4 + self.extension = 'bin' + self.d_type = np.float32 + self.intensity_multiplier = 255 + self.color_dict[6] = 'not available' + + + def load_apollo(self) -> None: + + self.reset_fog_buttons() + + self.file_list = [] + + # open file and read the content in a list + with open('file_lists/Apollo.txt', 'r') as filehandle: + for line in filehandle: + # remove linebreak which is the last character of the string + file_path = Path(APOLLO) / line[:-1] + + # add item to the list + self.file_list.append(str(file_path)) + + self.index = 0 + self.set_apollo() + self.show_pointcloud(self.file_list[self.index]) + + + def show_directory_dialog(self) -> None: + + self.reset_fog_buttons() + + directory = Path(os.getenv("HOME")) / 'Downloads' + + if self.lastDir: + directory = self.lastDir + + dir_name = QFileDialog.getExistingDirectory(self, "Open Directory", str(directory), + QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) + + if dir_name: + self.create_file_list(dir_name) + self.lastDir = Path(dir_name) + + + def get_index(self, filename: str) -> int: + + try: + return self.file_list.index(str(filename)) + + except ValueError: + logging.warning(f'{filename} not found in self.file_list') + return -1 + + + def load_extracted_fog_samples(self): + + self.set_extracted_fog_samples() + + self.create_file_list(FOG) + + + def toggle_simulated_fog(self) -> None: + + if self.file_list is not None and 'extraction' not in self.file_name: + + self.simulated_fog = not self.simulated_fog + + if self.simulated_fog: + + self.toggle_simulated_fog_btn.setText('remove our fog simulation') + self.alpha_slider.setEnabled(True) + self.beta_slider.setEnabled(True) + self.gamma_slider.setEnabled(True) + self.noise_slider.setEnabled(True) + + else: + + self.toggle_simulated_fog_btn.setText('add our fog simulation') + self.alpha_slider.setEnabled(False) + self.beta_slider.setEnabled(False) + self.gamma_slider.setEnabled(False) + self.noise_slider.setEnabled(False) + + self.show_pointcloud(self.file_list[self.index]) + + def toggle_simulated_fog_dense(self) -> None: + + if self.file_list is not None and 'extraction' not in self.file_name: + + self.simulated_fog_dense = not self.simulated_fog_dense + + if self.simulated_fog_dense: + + self.toggle_simulated_fog_dense_btn.setText('remove STF fog simulation') + self.alpha_slider.setEnabled(True) + self.beta_slider.setEnabled(True) + self.gamma_slider.setEnabled(True) + self.noise_slider.setEnabled(True) + + else: + + self.toggle_simulated_fog_dense_btn.setText('add STF fog simulation') + self.alpha_slider.setEnabled(False) + self.beta_slider.setEnabled(False) + self.gamma_slider.setEnabled(False) + self.noise_slider.setEnabled(False) + + self.show_pointcloud(self.file_list[self.index]) + + def toggle_predictions(self) -> None: + + self.show_predictions = not self.show_predictions + + if self.show_predictions: + self.visualize_predictions_path_btn.setText('hide predictions') + else: + self.visualize_predictions_path_btn.setText('show predictions') + + if self.file_list: + self.show_pointcloud(self.file_list[self.index]) + + + def toggle_extracted_fog(self) -> None: + + if self.file_list is not None and 'extraction' not in self.file_name: + + if self.extracted_fog_file_list is None: + self.extracted_fog_file_list = get_extracted_fog_file_list(FOG) + + self.extracted_fog = not self.extracted_fog + + if self.extracted_fog: + + self.threshold_slider.setEnabled(True) + self.toggle_extracted_fog_btn.setText('remove extracted fog') + + else: + + self.threshold_slider.setEnabled(False) + self.toggle_extracted_fog_btn.setText('add extracted fog') + self.extracted_fog_index = -1 + + self.show_pointcloud(self.file_list[self.index]) + + # TODO: workaround because the slider has no effect yet + if self.extension == 'pickle': + self.threshold_slider.setEnabled(False) + + def create_file_list(self, dirname: str, filename: str = None, extension: str =None) -> None: + + if extension: + file_list = [y for x in os.walk(dirname) for y in glob(os.path.join(x[0], f'*.{extension}'))] + else: + file_list = [y for x in os.walk(dirname) for y in glob(os.path.join(x[0], f'*.{self.extension}'))] + + self.file_list = sorted(file_list) + + # with open('file_lists/Argoverse.txt', 'w') as filehandle: + # filehandle.writelines(f'{Path(file).parent.parent.parent.parent.name}/' + # f'{Path(file).parent.parent.parent.name}/' + # f'{Path(file).parent.parent.name}/' + # f'{Path(file).parent.name}/' + # f'{Path(file).name}\n' for file in self.file_list) + + if len(self.file_list) > 0: + + if filename is None: + filename = self.file_list[0] + + self.index = self.get_index(filename) + + if Path(self.file_list[self.index]).suffix == ".pickle": + self.show_pcdet_dict(self.file_list[self.index]) + else: + self.show_pointcloud(self.file_list[self.index]) + + + def reset_viewer(self) -> None: + + if self.file_name: + + if 'extraction' in self.file_name or self.extracted_fog: + self.threshold_slider.setEnabled(True) + else: + self.threshold_slider.setEnabled(False) + + self.num_info.setText(f'sequence_size: {len(self.file_list)}') + + self.min_fog_response = np.inf + self.max_fog_response = 0 + self.num_fog_responses = 0 + + self.viewer.items = [] + self.viewer.addItem(self.grid) + + + def show_pcdet_dict(self, filename: str) -> None: + + self.reset_viewer() + self.simulated_fog = False + self.simulated_fog_dense = False + + if self.simulated_fog: + + self.toggle_simulated_fog_btn.setText('remove our fog simulation') + self.alpha_slider.setEnabled(True) + self.beta_slider.setEnabled(True) + self.gamma_slider.setEnabled(True) + self.noise_slider.setEnabled(True) + + else: + + self.toggle_simulated_fog_btn.setText('add our fog simulation') + self.alpha_slider.setEnabled(False) + self.beta_slider.setEnabled(False) + self.gamma_slider.setEnabled(False) + self.noise_slider.setEnabled(False) + + self.file_name = filename + + self.set_pc_det('before' in filename) + + self.cb.setEnabled(False) + self.reset_btn.setEnabled(False) + self.toggle_extracted_fog_btn.setEnabled(True) + self.toggle_simulated_fog_btn.setEnabled(True) + self.toggle_simulated_fog_dense_btn.setEnabled(True) + + if len(self.file_list) > 1: + self.next_btn.setEnabled(True) + self.prev_btn.setEnabled(True) + else: + self.next_btn.setEnabled(False) + self.prev_btn.setEnabled(False) + + pcdet_dict = pkl.load(open(filename, "rb")) + + ########## + # points # + ########## + + pc = pcdet_dict['points'] + + self.log_string(pc) + + colors = self.get_colors(pc) + + mesh = gl.GLScatterPlotItem(pos=np.asarray(pc[:, 0:3]), size=self.point_size, color=colors) + self.current_mesh = mesh + self.current_pc = copy.deepcopy(pc) + self.fogless_pc = copy.deepcopy(pc) + + self.viewer.addItem(mesh) + + ######### + # boxes # + ######### + + self.boxes = {} + + self.create_boxes(pcdet_dict['gt_boxes']) + + + def create_boxes(self, annotations): + + # create annotation boxes + for annotation in annotations: + + x, y, z, w, l, h, rotation, category = annotation + + rotation = np.rad2deg(rotation) + 90 + + try: + color = COLORS[int(category) - 1] + except IndexError: + color = (255, 255, 255, 255) + + box = gl.GLBoxItem(QtGui.QVector3D(1, 1, 1), color=color) + + box.setSize(l, w, h) + box.translate(-l / 2, -w / 2, -h / 2) + box.rotate(angle=rotation, x=0, y=0, z=1) + box.translate(x, y, z) + + self.viewer.addItem(box) + + ################# + # heading lines # + ################# + + p1 = [-l / 2, -w / 2, -h / 2] + p2 = [l / 2, -w / 2, h / 2] + + pts = np.array([p1, p2]) + + l1 = gl.GLLinePlotItem(pos=pts, width=2 / 3, color=color, antialias=True, mode='lines') + l1.rotate(angle=rotation, x=0, y=0, z=1) + l1.translate(x, y, z) + + self.viewer.addItem(l1) + + p3 = [-l / 2, -w / 2, h / 2] + p4 = [l / 2, -w / 2, -h / 2] + + pts = np.array([p3, p4]) + + l2 = gl.GLLinePlotItem(pos=pts, width=2 / 3, color=color, antialias=True, mode='lines') + l2.rotate(angle=rotation, x=0, y=0, z=1) + l2.translate(x, y, z) + + self.viewer.addItem(l2) + + distance = np.linalg.norm([x, y, z], axis=0) + self.boxes[distance] = (box, l1, l2) + + + def show_pointcloud(self, filename: str) -> None: + + self.reset_viewer() + + self.cb.setEnabled(False) + self.reset_btn.setEnabled(False) + self.next_btn.setEnabled(False) + self.prev_btn.setEnabled(False) + self.toggle_extracted_fog_btn.setEnabled(False) + self.toggle_simulated_fog_btn.setEnabled(False) + self.toggle_simulated_fog_dense_btn.setEnabled(False) + + if self.file_name == filename and self.current_pc is not None: + + # reuse the current pointcloud if the filename stays the same + pc = self.current_pc + + else: + + self.file_name = filename + pc = self.load_pointcloud(filename) + + self.success = False + + min_dist_mask = np.linalg.norm(pc[:, 0:3], axis=1) > 1.75 # in m + pc = pc[min_dist_mask, :] + + self.current_pc = copy.deepcopy(pc) + self.fogless_pc = copy.deepcopy(pc) + + if 'extraction' in filename: + intensity_mask = pc[:, 3] <= self.threshold + pc = pc[intensity_mask, :] + + colors = self.get_colors(pc) + + mesh = gl.GLScatterPlotItem(pos=np.asarray(pc[:, 0:3]), size=self.point_size, color=colors) + self.current_mesh = mesh + + if self.success: + + self.viewer.addItem(mesh) + + self.reset_btn.setEnabled(True) + self.next_btn.setEnabled(True) + self.prev_btn.setEnabled(True) + + if 'extraction' not in filename: + + self.toggle_extracted_fog_btn.setEnabled(True) + self.toggle_simulated_fog_btn.setEnabled(True) + self.toggle_simulated_fog_dense_btn.setEnabled(True) + + if self.extracted_fog and self.success and 'extraction' not in filename: + + self.toggle_simulated_fog_btn.setEnabled(False) + self.toggle_simulated_fog_dense_btn.setEnabled(False) + + fog_points = self.load_fog_points() + self.extracted_fog_pc = fog_points + + intensity_mask = fog_points[:, 3] <= self.threshold + + fog_points = fog_points[intensity_mask, :] + fog_colors = self.get_colors(fog_points) + + fog = gl.GLScatterPlotItem(pos=np.asarray(fog_points[:, 0:3]), size=self.point_size, color=fog_colors) + self.extracted_fog_mesh = fog + + self.viewer.addItem(fog) + + if self.simulated_fog and self.success and 'extraction' not in filename: + + self.toggle_extracted_fog_btn.setEnabled(False) + self.toggle_simulated_fog_dense_btn.setEnabled(False) + + self.reset_viewer() + + pc, simulated_fog_pc, info_dict = simulate_fog(self.p, self.current_pc, self.noise, self.gain, + self.noise_variant) + + self.simulated_fog_pc = simulated_fog_pc + + self.min_fog_response = info_dict['min_fog_response'] + self.max_fog_response = info_dict['max_fog_response'] + self.num_fog_responses = info_dict['num_fog_responses'] + + colors = self.get_colors(pc) + mesh = gl.GLScatterPlotItem(pos=np.asarray(pc[:, 0:3]), size=self.point_size, color=colors) + self.viewer.addItem(mesh) + + if self.simulated_fog_dense and self.success and 'extraction' not in filename: + + self.toggle_simulated_fog_btn.setEnabled(False) + self.toggle_extracted_fog_btn.setEnabled(False) + + self.reset_viewer() + + B = BetaRadomization(beta=float(self.p.alpha), seed=0) + B.propagate_in_time(10) + + arguments = Namespace(sensor_type='Velodyne HDL-64E S3D', fraction_random=0.05) + n_features = pc.shape[1] + + pc = haze_point_cloud(pc, B, arguments) + pc = pc[:, :n_features] + + colors = self.get_colors(pc) + mesh = gl.GLScatterPlotItem(pos=np.asarray(pc[:, 0:3]), size=self.point_size, color=colors) + self.viewer.addItem(mesh) + + distance = np.linalg.norm(pc[:, 0:3], axis=1) + + try: + self.p.r_range = max(distance) + self.log_string(pc=pc) + except ValueError: + self.p.r_range = 0 + + if self.dataset == 'DENSE': + self.cb.setEnabled(True) + self.populate_dense_boxes(filename) + else: + self.cb.setCurrentText('all') + self.cb.setEnabled(False) + + if self.boxes: + + try: # if heading lines are available + for box_distance, (box, l1, l2) in self.boxes.items(): + if box_distance < self.p.r_range: + self.viewer.addItem(box) + self.viewer.addItem(l1) + self.viewer.addItem(l2) + except TypeError: + for box_distance, box in self.boxes.items(): + if box_distance < self.p.r_range: + self.viewer.addItem(box) + + if self.result_dict and self.show_predictions: + + self.visualize_predictions() + + + def populate_dense_boxes(self, filename): + + root = str(Path.home()) + '/repositories/PCDet/lib/SeeingThroughFog/tools/DatasetViewer/calibs' + tf_tree = 'calib_tf_tree_full.json' + + name_camera_calib = 'calib_cam_stereo_left.json' + + rgb_calib = load_calib_data(root, name_camera_calib, tf_tree) + + camera_to_velodyne_rgb = rgb_calib[1] + + label_path = Path(filename).parent.parent / 'gt_labels' / 'cam_left_labels_TMP' + + recording = Path(filename).stem # here without '.txt' as it will be added in read_label function + label_file = os.path.join(label_path, recording) + label = read_label(label_file, label_path, camera_to_velodyne=camera_to_velodyne_rgb) + + size = QtGui.QVector3D(1, 1, 1) + + self.boxes = {} + + # create annotation boxes + for annotation in label: + + if annotation['identity'] in ['PassengerCar', 'Pedestrian', 'RidableVehicle']: + + x = annotation['posx_lidar'] + y = annotation['posy_lidar'] + z = annotation['posz_lidar'] + + if annotation['identity'] == 'PassengerCar': + color = COLORS[0] + elif annotation['identity'] == 'Pedestrian': + color = COLORS[1] + else: + color = COLORS[2] + + distance = np.sqrt(x**2 + y**2 + z**2) + + box = gl.GLBoxItem(size, color=color) + box.setSize(annotation['length'], annotation['width'], annotation['height']) + box.translate(-annotation['length'] / 2, -annotation['width'] / 2, -annotation['height'] / 2) + box.rotate(angle=-annotation['rotz'] * 180 / 3.14159265359, x=0, y=0, z=1) + box.rotate(angle=-annotation['roty'] * 180 / 3.14159265359, x=0, y=1, z=0) + box.rotate(angle=-annotation['rotx'] * 180 / 3.14159265359, x=1, y=0, z=0) + box.translate(0, 0, annotation['height'] / 2) + box.translate(x, y, z) + + self.boxes[distance] = box + + + + def log_string(self, pc: np.ndarray) -> None: + + log_string = f'max_dist ' + f'{int(self.p.r_range)}'.rjust(3, ' ') + ' m | ' + \ + f'intensity [ ' + f'{int(min(pc[:, 3]))}'.rjust(3, ' ') + \ + f', ' + f'{int(max(pc[:, 3]))}'.rjust(3, ' ') + ']' + ' ' + \ + f'median ' + f'{int(np.round(np.median(pc[:, 3])))}'.rjust(3, ' ') + ' ' + \ + f'mean ' + f'{int(np.round(np.mean(pc[:, 3])))}'.rjust(3, ' ') + ' ' + \ + f'std ' + f'{int(np.round(np.std(pc[:, 3])))}'.rjust(3, ' ') + + if self.num_fog_responses > 0: + range_fog_response_string = f'fog [ ' + f'{int(self.min_fog_response)}'.rjust(3, ' ') + \ + f', ' + f'{int(self.max_fog_response)}'.rjust(3, ' ') + ']' + num_fog_responses_string = f'num_soft ' + f'{int(self.num_fog_responses)}'.rjust(6, ' ') + num_remaining_string = f'num_hard ' + f'{int(len(self.current_pc) - self.num_fog_responses)}'.rjust(6, ' ') + + log_string = log_string + ' | ' + \ + range_fog_response_string + ' ' + num_fog_responses_string + ' ' + num_remaining_string + + self.log_info.setText(log_string) + + + def get_colors(self, pc: np.ndarray) -> np.ndarray: + + # create colormap + if self.color_feature == 0: + + self.success = True + feature = pc[:, 0] + min_value = np.min(feature) + max_value = np.max(feature) + + elif self.color_feature == 1: + + self.success = True + feature = pc[:, 1] + min_value = np.min(feature) + max_value = np.max(feature) + + elif self.color_feature == 2: + + self.success = True + feature = pc[:, 2] + min_value = -1.5 + max_value = 0.5 + + elif self.color_feature == 3: + + self.success = True + feature = pc[:, 3] + min_value = 0 + max_value = 255 + + elif self.color_feature == 4: + + self.success = True + feature = np.linalg.norm(pc[:, 0:3], axis=1) + + try: + min_value = np.min(feature) + max_value = np.max(feature) + except ValueError: + min_value = 0 + max_value = np.inf + + elif self.color_feature == 5: + + self.success = True + feature = np.arctan2(pc[:, 1], pc[:, 0]) + np.pi + min_value = 0 + max_value = 2 * np.pi + + else: # self.color_feature == 6: + + try: + feature = pc[:, 4] + self.success = True + + except IndexError: + feature = pc[:, 3] + + min_value = self.min_value + max_value = self.max_value + + norm = mpl.colors.Normalize(vmin=min_value, vmax=max_value) + + if self.color_feature == 5: + cmap = cm.hsv # cyclic + else: + cmap = cm.jet # sequential + + m = cm.ScalarMappable(norm=norm, cmap=cmap) + + colors = m.to_rgba(feature) + colors[:, [2, 1, 0, 3]] = colors[:, [0, 1, 2, 3]] + colors[:, 3] = 0.5 + + return colors + + + def load_fog_points(self, index: int = None) -> np.ndarray: + + if index is None and self.extracted_fog_index == -1: + index = RNG.integers(low=0, high=len(self.extracted_fog_file_list), size=1)[0] + self.extracted_fog_index = index + + filename = self.extracted_fog_file_list[self.extracted_fog_index] + fog_points = np.fromfile(filename, dtype=self.d_type) + + return fog_points.reshape((-1, 5)) + + + def load_pointcloud(self, filename: str) -> np.ndarray: + + self.reset_custom_values() + + if 'KITTI' in filename: + self.set_kitti() + + if 'DENSE' in filename: + self.set_dense() + + if 'nuScenes' in filename: + self.set_nuscenes() + + if 'Lyft' in filename: + self.set_lyft() + + if 'Waymo' in filename: + self.set_waymo() + + if 'Honda' in filename: + self.set_honda() + + if 'A2D2' in filename: + self.set_audi() + + if 'PandaSet' in filename: + self.set_panda() + + if 'Apollo' in filename: + self.set_apollo() + + if 'Argoverse' in filename: + self.set_argo() + + self.color_name = self.color_dict[self.color_feature] + self.color_label.setText(self.color_name) + + # /srv/beegfs-benderdata/scratch/tracezuerich/data/datasets/nuScenes/sweeps/LIDAR_TOP + # /srv/beegfs-benderdata/scratch/tracezuerich/data/datasets/nuScenes/lidarseg/v1.0-trainval + + if self.extension == 'ply': + + pc = self.load_from_ply(filename) + + elif self.extension == 'npz': + + pc = self.load_from_npz(filename) + + elif 'pkl' in self.extension: + + pc = self.load_from_pkl(filename) + + else: # assume bin file + + pc = np.fromfile(filename, dtype=self.d_type) + pc = pc.reshape((-1, self.num_features)) + + pc[:,3] = np.round(pc[:,3] * self.intensity_multiplier) + + if self.dataset == 'Honda3D': + + self.file_name_label.setText(f'{Path(filename).parent.name}/' + f'{Path(filename).name}') + + elif self.dataset == 'PandaSet' or self.dataset == 'Apollo': + + self.file_name_label.setText(f'{Path(filename).parent.parent.name}/' + f'{Path(filename).parent.name}/' + f'{Path(filename).name}') + + else: + + self.file_name_label.setText(str(Path(filename).name)) + + return pc + + + def load_from_pkl(self, filename: str) -> np.ndarray: + + if filename.endswith('gz'): + + with gzip.open(filename, 'rb') as f: + data = pkl.load(f) + + else: + + with open(filename, 'rb') as f: + data = pkl.load(f) + + if self.dataset == 'PandaSet': + pc = data.drop(columns=['t']).values + else: + pc = data.values + + return pc + + + def load_from_ply(self, filename: str) -> np.ndarray: + + with open(filename, 'rb') as f: + plydata = PlyData.read(f) + + pc = np.array(plydata.elements[0].data.tolist())[:] + + if self.dataset == 'Honda3D': + pc = np.delete(pc, [3, 4, 5, 6, 7, 8, 9, 12], 1) + elif self.dataset == 'Argoverse': + pc = pc + else: + pc = np.delete(pc, [4, 5, 6], 1) + + return pc + + + def load_from_npz(self, filename: str) -> np.ndarray: + + npz = np.load(filename) + + pc_dict = {} + + for key in npz.keys(): + + pc_dict[key] = npz[key] + + pc = None + + if self.dataset == 'A2D2': + + pc = np.column_stack((pc_dict['points'], + pc_dict['reflectance'], + pc_dict['lidar_id'])) + + return pc + + + def dragEnterEvent(self, e: QDragEnterEvent) -> None: + + logging.debug("enter") + + mimeData = e.mimeData() + mimeList = mimeData.formats() + filename = None + + if "text/uri-list" in mimeList: + filename = mimeData.data("text/uri-list") + filename = str(filename, encoding="utf-8") + filename = filename.replace("file://", "").replace("\r\n", "").replace("%20", " ") + filename = Path(filename) + + if filename.exists() and (filename.suffix == ".bin" or + filename.suffix == ".ply" or + filename.suffix == ".pickle"): + e.accept() + self.droppedFilename = filename + self.extension = filename.suffix.replace('.', '') + else: + e.ignore() + self.droppedFilename = None + + + def dropEvent(self, e: QDropEvent) -> None: + + if self.droppedFilename: + self.create_file_list(Path(self.droppedFilename).parent, self.droppedFilename) + + +if __name__ == '__main__': + + logging.basicConfig(format='%(message)s', level=logging.INFO) + logging.debug(pandas.__version__) + + app = QtGui.QApplication([]) + window = MyWindow() + window.show() + app.exec_()