├── python ├── pandaset │ ├── __init__.py │ ├── utils.py │ ├── dataset.py │ ├── geometry.py │ ├── meta.py │ ├── sequence.py │ ├── annotations.py │ └── sensors.py ├── setup.py └── requirements.txt ├── docs ├── annotation_instructions_semseg.pdf ├── annotation_instructions_cuboids.pdf └── static_extrinsic_calibration.yaml ├── LICENSE.md ├── tutorials ├── README.md ├── requirements.txt ├── raw_depth_projection │ ├── pandar64_channel_distribution.csv │ └── raw_depth_projection.ipynb ├── pointcloud_concat_world_frames │ └── pointcloud_concat_world_frames.ipynb ├── map_route │ └── map_route.ipynb └── pointcloud_world_to_ego │ └── pointcloud_world_to_ego.ipynb ├── .gitignore └── README.md /python/pandaset/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from .dataset import DataSet 3 | from .geometry import projection 4 | -------------------------------------------------------------------------------- /docs/annotation_instructions_semseg.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleapi/pandaset-devkit/HEAD/docs/annotation_instructions_semseg.pdf -------------------------------------------------------------------------------- /docs/annotation_instructions_cuboids.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleapi/pandaset-devkit/HEAD/docs/annotation_instructions_cuboids.pdf -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | with open('requirements.txt', 'r') as f: 4 | requirements = f.read().splitlines() 5 | 6 | setup( 7 | name='pandaset', 8 | version='0.3dev', 9 | author='Nisse Knudsen, Pengchuan Xiao', 10 | author_email='nisse@scale.com, xiaopengchuan_intern@hesaitech.com', 11 | packages=['pandaset'], 12 | python_requires='>=3.6', 13 | long_description='Pandaset Devkit for Python3', 14 | install_requires=requirements 15 | ) 16 | -------------------------------------------------------------------------------- /python/pandaset/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | from typing import List 4 | 5 | 6 | def subdirectories(directory: str) -> List[str]: 7 | """List all subdirectories of a directory. 8 | 9 | Args: 10 | directory: Relative or absolute path 11 | 12 | Returns: 13 | List of path strings for every subdirectory in `directory`. 14 | """ 15 | return [d.path for d in os.scandir(directory) if d.is_dir()] 16 | 17 | 18 | if __name__ == '__main__': 19 | pass 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Scale AI, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | 6 | 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | -------------------------------------------------------------------------------- /tutorials/README.md: -------------------------------------------------------------------------------- 1 | # Tutorial Creation Guidelines 2 | 3 | We are super excited to see new tutorials and playground coming in from our community! 4 | 5 | 6 | Here are the guidelines for tutorial creation, which we kindly ask to adhere to: 7 | - For every tutorial, please create a subfolder 8 | - Add all necessary dependencies to the `requirements.txt` in this folder, which are not in the `pandaset` python package. We would like to keep the Devkit dependencies _lean_, but can add anything we need to the tutorial requirements.txt 9 | - If you think your code contains methods that should rather be part of the data set objects from the Devkit, feel free to add a PR for it, too, so we can review and decide where to best place it. 10 | - Add markdown cells between code cells, or at least use code comments so that everyone in the community can follow your steps. 11 | 12 | 13 | If you have other tutorials with other dependencies or build systems (for ex: Docker), feel free to include them in your tutorial's subfolder including a README.md for build instructions. 14 | 15 | 16 | -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | appnope>=0.1.0 2 | attrs>=19.3.0 3 | backcall>=0.1.0 4 | bleach>=3.1.4 5 | certifi>=2019.11.28 6 | chardet>=3.0.4 7 | decorator>=4.4.2 8 | defusedxml>=0.6.0 9 | entrypoints>=0.3 10 | gmplot>=1.2.0 11 | idna>=2.9 12 | importlib-metadata>=1.5.2 13 | ipykernel>=5.2.0 14 | ipython>=7.13.0 15 | ipython-genutils>=0.2.0 16 | ipywidgets>=7.5.1 17 | jedi>=0.16.0 18 | Jinja2>=2.11.1 19 | jsonschema>=3.2.0 20 | jupyter>=1.0.0 21 | jupyter-client>=6.1.2 22 | jupyter-console>=6.1.0 23 | jupyter-core>=4.6.3 24 | MarkupSafe>=1.1.1 25 | mistune>=0.8.4 26 | nbconvert>=5.6.1 27 | nbformat>=5.0.4 28 | notebook>=6.0.3 29 | numpy>=1.18.2 30 | pandas>=1.0.3 31 | pandocfilters>=1.4.2 32 | parso>=0.6.2 33 | pexpect>=4.8.0 34 | pickleshare>=0.7.5 35 | Pillow>=7.0.0 36 | prometheus-client>=0.7.1 37 | prompt-toolkit>=3.0.5 38 | ptyprocess>=0.6.0 39 | Pygments>=2.6.1 40 | pyrsistent>=0.16.0 41 | python-dateutil>=2.8.1 42 | pytz>=2019.3 43 | pyzmq>=19.0.0 44 | qtconsole>=4.7.2 45 | QtPy>=1.9.0 46 | requests>=2.23.0 47 | Send2Trash>=1.5.0 48 | six>=1.14.0 49 | terminado>=0.8.3 50 | testpath>=0.4.4 51 | tornado>=6.0.4 52 | traitlets>=4.3.3 53 | urllib3>=1.25.8 54 | wcwidth>=0.1.9 55 | webencodings>=0.5.1 56 | widgetsnbextension>=3.5.1 57 | zipp>=3.1.0 58 | transforms3d>=0.3.1 59 | -------------------------------------------------------------------------------- /tutorials/requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.0 2 | attrs==19.3.0 3 | backcall==0.1.0 4 | bleach==3.1.4 5 | certifi==2019.11.28 6 | chardet==3.0.4 7 | cycler==0.10.0 8 | decorator==4.4.2 9 | defusedxml==0.6.0 10 | entrypoints==0.3 11 | gmplot==1.2.0 12 | idna==2.9 13 | importlib-metadata==1.5.2 14 | ipykernel==5.2.0 15 | ipython==7.13.0 16 | ipython-genutils==0.2.0 17 | ipywidgets==7.5.1 18 | jedi==0.16.0 19 | Jinja2==2.11.1 20 | jsonschema==3.2.0 21 | jupyter==1.0.0 22 | jupyter-client==6.1.2 23 | jupyter-console==6.1.0 24 | jupyter-core==4.6.3 25 | kiwisolver==1.2.0 26 | MarkupSafe==1.1.1 27 | matplotlib==3.2.1 28 | mistune==0.8.4 29 | nbconvert==5.6.1 30 | nbformat==5.0.4 31 | notebook==6.0.3 32 | numpy==1.18.2 33 | open3d==0.9.0.0 34 | pandas==1.0.3 35 | pandaset==0.2.dev0 36 | pandocfilters==1.4.2 37 | parso==0.6.2 38 | pexpect==4.8.0 39 | pickleshare==0.7.5 40 | Pillow==7.0.0 41 | prometheus-client==0.7.1 42 | prompt-toolkit==3.0.5 43 | ptyprocess==0.6.0 44 | Pygments==2.6.1 45 | pyparsing==2.4.7 46 | pyrsistent==0.16.0 47 | python-dateutil==2.8.1 48 | pytz==2019.3 49 | pyzmq==19.0.0 50 | qtconsole==4.7.2 51 | QtPy==1.9.0 52 | requests==2.23.0 53 | Send2Trash==1.5.0 54 | six==1.14.0 55 | terminado==0.8.3 56 | testpath==0.4.4 57 | tornado==6.0.4 58 | traitlets==4.3.3 59 | transforms3d==0.3.1 60 | urllib3==1.25.8 61 | wcwidth==0.1.9 62 | webencodings==0.5.1 63 | widgetsnbextension==3.5.1 64 | zipp==3.1.0 65 | -------------------------------------------------------------------------------- /tutorials/raw_depth_projection/pandar64_channel_distribution.csv: -------------------------------------------------------------------------------- 1 | channel,horizontal_angle_offset,vertical_angle 2 | 1,-1.042,14.87 3 | 2,-1.042,11.02 4 | 3,-1.042,8.047 5 | 4,-1.042,5.045 6 | 5,-1.042,3.028 7 | 6,-1.042,2.016 8 | 7,1.042,1.848 9 | 8,3.125,1.676 10 | 9,5.208,1.51 11 | 10,-5.208,1.339 12 | 11,-3.125,1.172 13 | 12,-1.042,1.001 14 | 13,1.042,0.834 15 | 14,3.125,0.663 16 | 15,5.208,0.496 17 | 16,-5.208,0.325 18 | 17,-3.125,0.157 19 | 18,-1.042,-0.012 20 | 19,1.042,-0.181 21 | 20,3.125,-0.349 22 | 21,5.208,-0.52 23 | 22,-5.208,-0.687 24 | 23,-3.125,-0.857 25 | 24,-1.042,-1.025 26 | 25,1.042,-1.196 27 | 26,3.125,-1.363 28 | 27,5.208,-1.534 29 | 28,-5.208,-1.7 30 | 29,-3.125,-1.872 31 | 30,-1.042,-2.04 32 | 31,1.042,-2.21 33 | 32,3.125,-2.377 34 | 33,5.208,-2.548 35 | 34,-5.208,-2.712 36 | 35,-3.125,-2.885 37 | 36,-1.042,-3.052 38 | 37,1.042,-3.222 39 | 38,3.125,-3.387 40 | 39,5.208,-3.56 41 | 40,-5.208,-3.724 42 | 41,-3.125,-3.896 43 | 42,-1.042,-4.062 44 | 43,1.042,-4.233 45 | 44,3.125,-4.397 46 | 45,5.208,-4.57 47 | 46,-5.208,-4.732 48 | 47,-3.125,-4.904 49 | 48,-1.042,-5.069 50 | 49,1.042,-5.241 51 | 50,3.125,-5.403 52 | 51,5.208,-5.577 53 | 52,-5.208,-5.738 54 | 53,-3.125,-5.91 55 | 54,-1.042,-6.073 56 | 55,-1.042,-7.075 57 | 56,-1.042,-8.071 58 | 57,-1.042,-9.072 59 | 58,-1.042,-9.897 60 | 59,-1.042,-11.044 61 | 60,-1.042,-12.018 62 | 61,-1.042,-12.986 63 | 62,-1.042,-13.942 64 | 63,-1.042,-18.901 65 | 64,-1.042,-24.909 66 | -------------------------------------------------------------------------------- /docs/static_extrinsic_calibration.yaml: -------------------------------------------------------------------------------- 1 | back_camera: 2 | extrinsic: 3 | transform: 4 | rotation: {w: 0.713789231075861, x: 0.7003585531940812, y: -0.001595758695393934, 5 | z: -0.0005330311533742299} 6 | translation: {x: -0.0004217634029916384, y: -0.21683144949675118, z: -1.0553445472201475} 7 | intrinsic: 8 | D: [-0.1619, 0.0113, -0.00028815, -7.9827e-05, 0.0067] 9 | K: [933.4667, 0, 896.4692, 0, 934.6754, 507.3557, 0, 0, 1] 10 | front_camera: 11 | extrinsic: 12 | transform: 13 | rotation: {w: 0.016213200031258722, x: 0.0030578899383849464, y: 0.7114721800418571, 14 | z: -0.7025205466606356} 15 | translation: {x: 0.0002585796504896516, y: -0.03907777167811011, z: -0.0440125762408362} 16 | intrinsic: 17 | D: [-0.5894, 0.66, 0.0011, -0.001, -1.0088] 18 | K: [1970.0131, 0, 970.0002, 0, 1970.0091, 483.2988, 0, 0, 1] 19 | front_gt: 20 | extrinsic: 21 | transform: 22 | rotation: {w: 0.021475754959146356, x: -0.002060907279494794, y: 0.01134678181520767, 23 | z: 0.9997028534282365} 24 | translation: {x: -0.000451117754, y: -0.605646431446, z: -0.301525235176} 25 | front_left_camera: 26 | extrinsic: 27 | transform: 28 | rotation: {w: 0.33540022607039827, x: 0.3277491469609924, y: -0.6283486651480494, 29 | z: 0.6206973014480826} 30 | translation: {x: -0.25842240863267835, y: -0.3070654284505582, z: -0.9244245686318884} 31 | intrinsic: 32 | D: [-0.165, 0.0099, -0.00075376, 5.3699e-05, 0.01] 33 | K: [929.8429, 0, 972.1794, 0, 930.0592, 508.0057, 0, 0, 1] 34 | front_right_camera: 35 | extrinsic: 36 | transform: 37 | rotation: {w: 0.3537633879725252, x: 0.34931795852655334, y: 0.6120314641083645, 38 | z: -0.6150170047424814} 39 | translation: {x: 0.2546935700219631, y: -0.24929449717803095, z: -0.8686597280810242} 40 | intrinsic: 41 | D: [-0.1614, -0.0027, -0.00029662, -0.00028927, 0.0181] 42 | K: [930.0407, 0, 965.0525, 0, 930.0324, 463.4161, 0, 0, 1] 43 | left_camera: 44 | extrinsic: 45 | transform: 46 | rotation: {w: 0.5050391917998245, x: 0.49253073152800625, y: -0.4989265501075421, 47 | z: 0.503409565706149} 48 | translation: {x: 0.23864835336611942, y: -0.2801448284013492, z: -0.5376795959387791} 49 | intrinsic: 50 | D: [-0.1582, -0.0266, -0.00015221, 0.00059011, 0.0449] 51 | K: [930.4514, 0, 991.6883, 0, 930.0891, 541.6057, 0, 0, 1] 52 | main_pandar64: 53 | extrinsic: 54 | transform: 55 | rotation: {w: 1.0, x: 0.0, y: 0.0, z: 0.0} 56 | translation: {x: 0.0, y: 0.0, z: 0.0} 57 | right_camera: 58 | extrinsic: 59 | transform: 60 | rotation: {w: 0.5087448402081216, x: 0.4947520981649951, y: 0.4977829953071897, 61 | z: -0.49860920419297333} 62 | translation: {x: -0.23097163411257893, y: -0.30843497058841024, z: -0.6850441215571058} 63 | intrinsic: 64 | D: [-0.1648, 0.0191, 0.0027, -8.5282e-07, -9.6983e-05] 65 | K: [922.5465, 0, 945.057, 0, 922.4229, 517.575, 0, 0, 1] 66 | -------------------------------------------------------------------------------- /python/pandaset/dataset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from typing import overload, List, Dict 3 | 4 | from .sequence import Sequence 5 | from .utils import subdirectories 6 | 7 | 8 | class DataSet: 9 | """Top-level class to load PandaSet 10 | 11 | ``DataSet`` prepares and loads ``Sequence`` objects for every sequence found in provided directory. 12 | Access to a specific sequence is provided by using the sequence name as a key on the ``DataSet`` object. 13 | 14 | Args: 15 | directory: Absolute or relative path where PandaSet has been extracted to. 16 | 17 | Examples: 18 | >>> pandaset = DataSet('/data/pandaset') 19 | >>> s = pandaset['002'] 20 | """ 21 | 22 | def __init__(self, directory: str) -> None: 23 | self._directory: str = directory 24 | self._sequences: Dict[str, Sequence] = None 25 | self._load_sequences() 26 | 27 | def __getitem__(self, item) -> Sequence: 28 | return self._sequences[item] 29 | 30 | def _load_sequences(self) -> None: 31 | self._sequences = {} 32 | sequence_directories = subdirectories(self._directory) 33 | for sd in sequence_directories: 34 | seq_id = sd.split('/')[-1].split('\\')[-1] 35 | self._sequences[seq_id] = Sequence(sd) 36 | 37 | def sequences(self, with_semseg: bool = False) -> List[str]: 38 | """ Lists all available sequence names 39 | 40 | Args: 41 | with_semseg: Set `True` if only sequences with semantic segmentation annotations should be returned. Set `False` to return all sequences (with or without semantic segmentation). 42 | 43 | Returns: 44 | List of sequence names. 45 | 46 | Examples: 47 | >>> pandaset = DataSet('/data/pandaset') 48 | >>> print(pandaset.sequences()) 49 | ['002','004','080'] 50 | 51 | 52 | """ 53 | if with_semseg: 54 | return [s for s in list(self._sequences.keys()) if self._sequences[s].semseg] 55 | else: 56 | return list(self._sequences.keys()) 57 | 58 | def unload(self, sequence: str): 59 | """ Removes all sequence file data from memory if previously loaded from disk. 60 | 61 | This is useful if you intend to iterate over all sequences and perform some 62 | operation. If you do not unload the sequences, it quickly leads to sigkill. 63 | 64 | Args: 65 | sequence: The sequence name 66 | 67 | Returns: 68 | None 69 | 70 | Examples: 71 | >>> pandaset = DataSet('...') 72 | >>> for sequence in pandaset.sequences(): 73 | >>> seq = pandaset[sequence] 74 | >>> seq.load() 75 | >>> # do operations on sequence here... 76 | >>> # when finished, unload the sequence from memory 77 | >>> pandaset.unload(sequence) 78 | 79 | """ 80 | if sequence in self._sequences: 81 | del self._sequences[sequence] 82 | 83 | 84 | if __name__ == '__main__': 85 | pass 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ 140 | 141 | # static files generated from Django application using `collectstatic` 142 | media 143 | static 144 | 145 | # pycharm 146 | .idea/ 147 | 148 | # vscode 149 | .vscode/ 150 | 151 | debug/ -------------------------------------------------------------------------------- /python/pandaset/geometry.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import transforms3d as t3d 3 | 4 | from .sensors import Lidar 5 | from .sensors import Camera 6 | 7 | 8 | def _heading_position_to_mat(heading, position): 9 | quat = np.array([heading["w"], heading["x"], heading["y"], heading["z"]]) 10 | pos = np.array([position["x"], position["y"], position["z"]]) 11 | transform_matrix = t3d.affines.compose(np.array(pos), 12 | t3d.quaternions.quat2mat(quat), 13 | [1.0, 1.0, 1.0]) 14 | return transform_matrix 15 | 16 | 17 | def projection(lidar_points, camera_data, camera_pose, camera_intrinsics, filter_outliers=True): 18 | camera_heading = camera_pose['heading'] 19 | camera_position = camera_pose['position'] 20 | camera_pose_mat = _heading_position_to_mat(camera_heading, camera_position) 21 | 22 | trans_lidar_to_camera = np.linalg.inv(camera_pose_mat) 23 | points3d_lidar = lidar_points 24 | points3d_camera = trans_lidar_to_camera[:3, :3] @ (points3d_lidar.T) + \ 25 | trans_lidar_to_camera[:3, 3].reshape(3, 1) 26 | 27 | K = np.eye(3, dtype=np.float64) 28 | K[0, 0] = camera_intrinsics.fx 29 | K[1, 1] = camera_intrinsics.fy 30 | K[0, 2] = camera_intrinsics.cx 31 | K[1, 2] = camera_intrinsics.cy 32 | 33 | inliner_indices_arr = np.arange(points3d_camera.shape[1]) 34 | if filter_outliers: 35 | condition = points3d_camera[2, :] > 0.0 36 | points3d_camera = points3d_camera[:, condition] 37 | inliner_indices_arr = inliner_indices_arr[condition] 38 | 39 | points2d_camera = K @ points3d_camera 40 | points2d_camera = (points2d_camera[:2, :] / points2d_camera[2, :]).T 41 | 42 | if filter_outliers: 43 | image_w, image_h = camera_data.size 44 | condition = np.logical_and( 45 | (points2d_camera[:, 1] < image_h) & (points2d_camera[:, 1] > 0), 46 | (points2d_camera[:, 0] < image_w) & (points2d_camera[:, 0] > 0)) 47 | points2d_camera = points2d_camera[condition] 48 | points3d_camera = (points3d_camera.T)[condition] 49 | inliner_indices_arr = inliner_indices_arr[condition] 50 | return points2d_camera, points3d_camera, inliner_indices_arr 51 | 52 | 53 | def lidar_points_to_ego(points, lidar_pose): 54 | lidar_pose_mat = _heading_position_to_mat( 55 | lidar_pose['heading'], lidar_pose['position']) 56 | transform_matrix = np.linalg.inv(lidar_pose_mat) 57 | return (transform_matrix[:3, :3] @ points.T + transform_matrix[:3, [3]]).T 58 | 59 | 60 | def center_box_to_corners(box): 61 | pos_x, pos_y, pos_z, dim_x, dim_y, dim_z, yaw = box 62 | half_dim_x, half_dim_y, half_dim_z = dim_x/2.0, dim_y/2.0, dim_z/2.0 63 | corners = np.array([[half_dim_x, half_dim_y, -half_dim_z], 64 | [half_dim_x, -half_dim_y, -half_dim_z], 65 | [-half_dim_x, -half_dim_y, -half_dim_z], 66 | [-half_dim_x, half_dim_y, -half_dim_z], 67 | [half_dim_x, half_dim_y, half_dim_z], 68 | [half_dim_x, -half_dim_y, half_dim_z], 69 | [-half_dim_x, -half_dim_y, half_dim_z], 70 | [-half_dim_x, half_dim_y, half_dim_z]]) 71 | transform_matrix = np.array([ 72 | [np.cos(yaw), -np.sin(yaw), 0, pos_x], 73 | [np.sin(yaw), np.cos(yaw), 0, pos_y], 74 | [0, 0, 1.0, pos_z], 75 | [0, 0, 0, 1.0], 76 | ]) 77 | corners = (transform_matrix[:3, :3] @ corners.T + transform_matrix[:3, [3]]).T 78 | return corners 79 | 80 | 81 | if __name__ == '__main__': 82 | pass 83 | -------------------------------------------------------------------------------- /tutorials/pointcloud_concat_world_frames/pointcloud_concat_world_frames.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": true, 7 | "pycharm": { 8 | "name": "#%% md\n" 9 | } 10 | }, 11 | "source": [ 12 | "# Concatenation of Pointclouds into one World Frame Tutorial\n" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "source": [ 18 | "### Instead of visualizing only one point cloud at a time, we can simply aggregate all point cloud data frames into a single large one for visualization." 19 | ], 20 | "metadata": { 21 | "collapsed": false 22 | } 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "source": [ 27 | "#### Load a sequence and its LiDAR point clouds" 28 | ], 29 | "metadata": { 30 | "collapsed": false, 31 | "pycharm": { 32 | "name": "#%% md\n" 33 | } 34 | } 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 28, 39 | "outputs": [ 40 | { 41 | "name": "stdout", 42 | "text": [ 43 | "Populating the interactive namespace from numpy and matplotlib\n" 44 | ], 45 | "output_type": "stream" 46 | }, 47 | { 48 | "data": { 49 | "text/plain": "" 50 | }, 51 | "metadata": {}, 52 | "output_type": "execute_result", 53 | "execution_count": 28 54 | } 55 | ], 56 | "source": [ 57 | "import pandaset\n", 58 | "\n", 59 | "dataset = pandaset.DataSet('/data/PandaSet')\n", 60 | "seq002 = dataset['002']\n", 61 | "seq002.load_lidar()" 62 | ], 63 | "metadata": { 64 | "collapsed": false, 65 | "pycharm": { 66 | "name": "#%%\n", 67 | "is_executing": false 68 | } 69 | } 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "source": [ 74 | "#### Use pandas concat method to concatenate all frames in selected slice" 75 | ], 76 | "metadata": { 77 | "collapsed": false, 78 | "pycharm": { 79 | "name": "#%% md\n" 80 | } 81 | } 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 29, 86 | "outputs": [], 87 | "source": [ 88 | "import pandas as pd\n", 89 | "\n", 90 | "selected_data = seq002.lidar[::5] # Take every 5th frame from sequence\n", 91 | "_ = list(map(lambda xy: xy[1].insert(3,'f', xy[0]), enumerate(selected_data)))# Add column 'f' to each data frame in order\n", 92 | "\n", 93 | "selected_data = pd.concat(selected_data) # Concatenate in order" 94 | ], 95 | "metadata": { 96 | "collapsed": false, 97 | "pycharm": { 98 | "name": "#%%\n", 99 | "is_executing": false 100 | } 101 | } 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "source": [ 106 | "#### For better visualization we can scale the values in column `f` to `[0,1]` so it can be used for better point cloud colors." 107 | ], 108 | "metadata": { 109 | "collapsed": false, 110 | "pycharm": { 111 | "name": "#%% md\n" 112 | } 113 | } 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 30, 118 | "outputs": [], 119 | "source": [ 120 | "import matplotlib.pyplot as plt\n", 121 | "\n", 122 | "selected_data['f'] = (selected_data['f'] - selected_data['f'].min()) + 0.1*(selected_data['f'].max() - selected_data['f'].min()) # Add 10% of color range as base color (otherwise frame0 has white points)\n", 123 | "selected_data['f'] /= selected_data['f'].max()" 124 | ], 125 | "metadata": { 126 | "collapsed": false, 127 | "pycharm": { 128 | "name": "#%%\n", 129 | "is_executing": false 130 | } 131 | } 132 | }, 133 | { 134 | "cell_type": "markdown", 135 | "source": [ 136 | "#### Now we can use the concatenated point clouds with open3d visualizer" 137 | ], 138 | "metadata": { 139 | "collapsed": false, 140 | "pycharm": { 141 | "name": "#%% md\n", 142 | "is_executing": false 143 | } 144 | } 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 45, 149 | "outputs": [], 150 | "source": [ 151 | "import open3d as o3d\n", 152 | "import numpy as np\n", 153 | "\n", 154 | "o3d_pc = o3d.geometry.PointCloud()\n", 155 | "o3d_pc.points = o3d.utility.Vector3dVector(selected_data.to_numpy()[:, :3])\n", 156 | "blue_colors = np.zeros((selected_data['f'].size,3))\n", 157 | "blue_colors[:,2] = selected_data['f'].transpose()\n", 158 | "o3d_pc.colors = o3d.utility.Vector3dVector(blue_colors)\n", 159 | "o3d.visualization.draw_geometries([o3d_pc], window_name=\"concat frame\")\n" 160 | ], 161 | "metadata": { 162 | "collapsed": false, 163 | "pycharm": { 164 | "name": "#%%\n", 165 | "is_executing": false 166 | } 167 | } 168 | } 169 | ], 170 | "metadata": { 171 | "kernelspec": { 172 | "display_name": "Python 3", 173 | "language": "python", 174 | "name": "python3" 175 | }, 176 | "language_info": { 177 | "codemirror_mode": { 178 | "name": "ipython", 179 | "version": 2 180 | }, 181 | "file_extension": ".py", 182 | "mimetype": "text/x-python", 183 | "name": "python", 184 | "nbconvert_exporter": "python", 185 | "pygments_lexer": "ipython2", 186 | "version": "2.7.6" 187 | }, 188 | "pycharm": { 189 | "stem_cell": { 190 | "cell_type": "raw", 191 | "source": [], 192 | "metadata": { 193 | "collapsed": false 194 | } 195 | } 196 | } 197 | }, 198 | "nbformat": 4, 199 | "nbformat_minor": 0 200 | } -------------------------------------------------------------------------------- /tutorials/map_route/map_route.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "ExecuteTime": { 8 | "end_time": "2020-05-14T14:57:24.364782Z", 9 | "start_time": "2020-05-14T14:57:23.979245Z" 10 | }, 11 | "pycharm": { 12 | "is_executing": false 13 | } 14 | }, 15 | "outputs": [], 16 | "source": [ 17 | "import os\n", 18 | "import webbrowser\n", 19 | "from gmplot import gmplot\n", 20 | "from pandaset import DataSet" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 2, 26 | "metadata": { 27 | "ExecuteTime": { 28 | "end_time": "2020-05-14T14:57:25.404339Z", 29 | "start_time": "2020-05-14T14:57:24.375197Z" 30 | }, 31 | "pycharm": { 32 | "is_executing": false, 33 | "name": "#%%\n" 34 | } 35 | }, 36 | "outputs": [ 37 | { 38 | "data": { 39 | "text/plain": [ 40 | "" 41 | ] 42 | }, 43 | "execution_count": 2, 44 | "metadata": {}, 45 | "output_type": "execute_result" 46 | } 47 | ], 48 | "source": [ 49 | "dataset = DataSet('/data/PandaSet')\n", 50 | "seq_id = dataset.sequences()[0]\n", 51 | "seq = dataset[seq_id]\n", 52 | "seq.load_gps()" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 3, 58 | "metadata": { 59 | "ExecuteTime": { 60 | "end_time": "2020-05-14T14:43:47.811451Z", 61 | "start_time": "2020-05-14T14:43:47.806607Z" 62 | }, 63 | "pycharm": { 64 | "is_executing": false, 65 | "name": "#%%\n" 66 | } 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "lats = [x['lat'] for x in seq.gps]\n", 71 | "longs = [x['long'] for x in seq.gps]\n", 72 | "\n", 73 | "mean_lat = lats[len(lats)//2]\n", 74 | "mean_long = longs[len(longs)//2]" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 4, 80 | "metadata": { 81 | "ExecuteTime": { 82 | "end_time": "2020-05-14T14:43:48.330265Z", 83 | "start_time": "2020-05-14T14:43:48.327206Z" 84 | }, 85 | "pycharm": { 86 | "is_executing": false, 87 | "name": "#%%\n" 88 | } 89 | }, 90 | "outputs": [], 91 | "source": [ 92 | "gmap = gmplot.GoogleMapPlotter(mean_lat, mean_long, 18)\n", 93 | "gmap.plot(lats, longs, 'cornflowerblue', edge_width=10)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 5, 99 | "metadata": { 100 | "ExecuteTime": { 101 | "end_time": "2020-05-14T14:43:49.002129Z", 102 | "start_time": "2020-05-14T14:43:48.998907Z" 103 | }, 104 | "pycharm": { 105 | "is_executing": false, 106 | "name": "#%%\n" 107 | } 108 | }, 109 | "outputs": [], 110 | "source": [ 111 | "gmap.draw(f'{seq_id}_map.html')" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 6, 117 | "metadata": { 118 | "ExecuteTime": { 119 | "end_time": "2020-05-14T14:43:49.750109Z", 120 | "start_time": "2020-05-14T14:43:49.571779Z" 121 | }, 122 | "pycharm": { 123 | "is_executing": false, 124 | "name": "#%%\n" 125 | } 126 | }, 127 | "outputs": [ 128 | { 129 | "data": { 130 | "text/plain": [ 131 | "True" 132 | ] 133 | }, 134 | "execution_count": 6, 135 | "metadata": {}, 136 | "output_type": "execute_result" 137 | } 138 | ], 139 | "source": [ 140 | "webbrowser.open(f'file://{os.getcwd()}/{seq_id}_map.html')" 141 | ] 142 | }, 143 | { 144 | "cell_type": "code", 145 | "execution_count": null, 146 | "metadata": { 147 | "pycharm": { 148 | "name": "#%%\n" 149 | } 150 | }, 151 | "outputs": [], 152 | "source": [ 153 | "\n" 154 | ] 155 | } 156 | ], 157 | "metadata": { 158 | "kernelspec": { 159 | "display_name": "Python 3", 160 | "language": "python", 161 | "name": "python3" 162 | }, 163 | "language_info": { 164 | "codemirror_mode": { 165 | "name": "ipython", 166 | "version": 3 167 | }, 168 | "file_extension": ".py", 169 | "mimetype": "text/x-python", 170 | "name": "python", 171 | "nbconvert_exporter": "python", 172 | "pygments_lexer": "ipython3", 173 | "version": "3.7.7" 174 | }, 175 | "pycharm": { 176 | "stem_cell": { 177 | "cell_type": "raw", 178 | "source": [], 179 | "metadata": { 180 | "collapsed": false 181 | } 182 | } 183 | }, 184 | "toc": { 185 | "base_numbering": 1, 186 | "nav_menu": {}, 187 | "number_sections": true, 188 | "sideBar": true, 189 | "skip_h1_title": false, 190 | "title_cell": "Table of Contents", 191 | "title_sidebar": "Contents", 192 | "toc_cell": false, 193 | "toc_position": {}, 194 | "toc_section_display": true, 195 | "toc_window_display": false 196 | }, 197 | "varInspector": { 198 | "cols": { 199 | "lenName": 16, 200 | "lenType": 16, 201 | "lenVar": 40 202 | }, 203 | "kernels_config": { 204 | "python": { 205 | "delete_cmd_postfix": "", 206 | "delete_cmd_prefix": "del ", 207 | "library": "var_list.py", 208 | "varRefreshCmd": "print(var_dic_list())" 209 | }, 210 | "r": { 211 | "delete_cmd_postfix": ") ", 212 | "delete_cmd_prefix": "rm(", 213 | "library": "var_list.r", 214 | "varRefreshCmd": "cat(var_dic_list()) " 215 | } 216 | }, 217 | "types_to_exclude": [ 218 | "module", 219 | "function", 220 | "builtin_function_or_method", 221 | "instance", 222 | "_Feature" 223 | ], 224 | "window_display": false 225 | } 226 | }, 227 | "nbformat": 4, 228 | "nbformat_minor": 1 229 | } -------------------------------------------------------------------------------- /python/pandaset/meta.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import json 3 | import os.path 4 | from abc import ABCMeta, abstractmethod 5 | from typing import TypeVar, List, overload, Dict 6 | 7 | T = TypeVar('T') 8 | 9 | 10 | class Meta: 11 | """Meta class inherited by subclasses for more specific meta data types. 12 | 13 | ``Meta`` provides generic preparation and loading methods for PandaSet folder structures. Subclasses 14 | for specific meta data types must implement certain methods, as well as can override existing ones for extension. 15 | 16 | Args: 17 | directory: Absolute or relative path where annotation files are stored 18 | 19 | Attributes: 20 | data: List of meta data objects. The type of list elements depends on the subclass specific meta data type. 21 | """ 22 | __metaclass__ = ABCMeta 23 | 24 | @property 25 | @abstractmethod 26 | def _filename(self) -> str: 27 | ... 28 | 29 | @property 30 | def data(self) -> List[T]: 31 | """Returns meta data array. 32 | 33 | Subclasses can use any type inside array. 34 | """ 35 | return self._data 36 | 37 | def __init__(self, directory: str) -> None: 38 | self._directory: str = directory 39 | self._data_structure: str = None 40 | self._data: List[T] = None 41 | self._load_data_structure() 42 | 43 | @overload 44 | def __getitem__(self, item: int) -> T: 45 | ... 46 | 47 | @overload 48 | def __getitem__(self, item: slice) -> List[T]: 49 | ... 50 | 51 | def __getitem__(self, item): 52 | return self._data[item] 53 | 54 | def load(self) -> None: 55 | """Loads all meta data files from disk into memory. 56 | 57 | All meta data files are loaded into memory in filename order. 58 | """ 59 | self._load_data() 60 | 61 | def _load_data_structure(self) -> None: 62 | meta_file = f'{self._directory}/{self._filename}' 63 | if os.path.isfile(meta_file): 64 | self._data_structure = meta_file 65 | 66 | def _load_data(self) -> None: 67 | self._data = [] 68 | with open(self._data_structure, 'r') as f: 69 | file_data = json.load(f) 70 | for entry in file_data: 71 | self._data.append(entry) 72 | 73 | 74 | class GPS(Meta): 75 | """GPS data for each timestamp in this sequence. 76 | 77 | ``GPS`` provides GPS data for each timestamp. GPS data can be retrieved by slicing an instanced ``GPS`` class. (see example) 78 | 79 | Args: 80 | directory: Absolute or relative path where annotation files are stored 81 | 82 | Attributes: 83 | data: List of meta data objects. The type of list elements depends on the subclass specific meta data type. 84 | 85 | Examples: 86 | Assuming an instance `s` of class ``Sequence``, you can get GPS data for the first 5 frames in the sequence as follows: 87 | >>> s.load_gps() 88 | >>> gps_data_0_5 = s.gps[:5] 89 | >>> print(gps_data_0_5) 90 | [{'lat': 37.776089291519924, 'long': -122.39931707791749, 'height': 2.950900131607181, 'xvel': 0.0014639192106827986, 'yvel': 0.15895995994754034}, ...] 91 | """ 92 | @property 93 | def _filename(self) -> str: 94 | return 'gps.json' 95 | 96 | @property 97 | def data(self) -> List[Dict[str, float]]: 98 | """Returns GPS data array. 99 | 100 | For every timestamp in the sequence, the GPS data contains vehicle latitude, longitude, height and velocity. 101 | 102 | Returns: 103 | List of dictionaries. Each dictionary has `str` keys and return types as follows: 104 | - `lat`: `float` 105 | - Latitude in decimal degree format. Positive value corresponds to North, negative value to South. 106 | - `long`: `float` 107 | - Longitude in decimal degree format. Positive value indicates East, negative value to West. 108 | - `height`: `float` 109 | - Measured height in meters. 110 | - `xvel`: `float` 111 | - Velocity in m/s 112 | - `yvel`: `float` 113 | - Velocity in m/s 114 | 115 | """ 116 | return self._data 117 | 118 | def __init__(self, directory: str) -> None: 119 | Meta.__init__(self, directory) 120 | 121 | @overload 122 | def __getitem__(self, item: int) -> Dict[str, T]: 123 | ... 124 | 125 | @overload 126 | def __getitem__(self, item: slice) -> List[Dict[str, T]]: 127 | ... 128 | 129 | def __getitem__(self, item): 130 | return self._data[item] 131 | 132 | 133 | class Timestamps(Meta): 134 | @property 135 | def _filename(self) -> str: 136 | return 'timestamps.json' 137 | 138 | @property 139 | def data(self) -> List[float]: 140 | """Returns timestamp array. 141 | 142 | For every frame in this sequence, this property stores the recorded timestamp. 143 | 144 | Returns: 145 | List of timestamps as `float` 146 | """ 147 | return self._data 148 | 149 | def __init__(self, directory: str) -> None: 150 | Meta.__init__(self, directory) 151 | 152 | @overload 153 | def __getitem__(self, item: int) -> float: 154 | ... 155 | 156 | @overload 157 | def __getitem__(self, item: slice) -> List[float]: 158 | ... 159 | 160 | def __getitem__(self, item): 161 | return self._data[item] 162 | 163 | 164 | if __name__ == '__main__': 165 | pass 166 | -------------------------------------------------------------------------------- /python/pandaset/sequence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from typing import Dict 3 | 4 | from .annotations import Cuboids 5 | from .annotations import SemanticSegmentation 6 | from .meta import GPS 7 | from .meta import Timestamps 8 | from .sensors import Camera 9 | from .sensors import Lidar 10 | from .utils import subdirectories 11 | 12 | 13 | class Sequence: 14 | """Provides all sensor and annotations for a single sequence. 15 | 16 | ``Sequence`` provides generic preparation and loading methods for a single PandaSet sequence folder structure. 17 | 18 | Args: 19 | directory: Absolute or relative path where annotation files are stored 20 | """ 21 | 22 | @property 23 | def lidar(self) -> Lidar: 24 | """ Stores ``Lidar`` object for sequence 25 | 26 | Returns: 27 | Instance of ``Lidar`` class. 28 | """ 29 | return self._lidar 30 | 31 | @property 32 | def camera(self) -> Dict[str, Camera]: 33 | """ Stores all ``Camera`` objects for sequence. 34 | 35 | Access data by entering the key of a specific camera (see example). 36 | 37 | Returns: 38 | Dictionary of all cameras available for sequence. 39 | 40 | Examples: 41 | >>> print(s.camera.keys()) 42 | dict_keys(['front_camera', 'left_camera', 'back_camera', 'right_camera', 'front_left_camera', 'front_right_camera']) 43 | >>> cam_front = s.camera['front_camera'] 44 | """ 45 | return self._camera 46 | 47 | @property 48 | def gps(self) -> GPS: 49 | """ Stores ``GPS`` object for sequence 50 | 51 | Returns: 52 | Instance of ``GPS`` class. 53 | """ 54 | return self._gps 55 | 56 | @property 57 | def timestamps(self) -> Timestamps: 58 | """ Stores ``Timestamps`` object for sequence 59 | 60 | Returns: 61 | Instance of ``Timestamps`` class. 62 | """ 63 | return self._timestamps 64 | 65 | @property 66 | def cuboids(self) -> Cuboids: 67 | """ Stores ``Cuboids`` object for sequence 68 | 69 | Returns: 70 | Instance of ``Cuboids`` class. 71 | """ 72 | return self._cuboids 73 | 74 | @property 75 | def semseg(self) -> SemanticSegmentation: 76 | """ Stores ``SemanticSegmentation`` object for sequence 77 | 78 | Returns: 79 | Instance of ``SemanticSegmentation`` class. 80 | """ 81 | return self._semseg 82 | 83 | def __init__(self, directory: str) -> None: 84 | self._directory: str = directory 85 | self._lidar: Lidar = None 86 | self._camera: Dict[str, Camera] = None 87 | self._gps: GPS = None 88 | self._timestamps: Timestamps = None 89 | self._cuboids: Cuboids = None 90 | self._semseg: SemanticSegmentation = None 91 | self._load_data_structure() 92 | 93 | def _load_data_structure(self) -> None: 94 | data_directories = subdirectories(self._directory) 95 | 96 | for dd in data_directories: 97 | if dd.endswith('lidar'): 98 | self._lidar = Lidar(dd) 99 | elif dd.endswith('camera'): 100 | self._camera = {} 101 | camera_directories = subdirectories(dd) 102 | for cd in camera_directories: 103 | camera_name = cd.split('/')[-1].split('\\')[-1] 104 | self._camera[camera_name] = Camera(cd) 105 | elif dd.endswith('meta'): 106 | self._gps = GPS(dd) 107 | self._timestamps = Timestamps(dd) 108 | elif dd.endswith('annotations'): 109 | annotation_directories = subdirectories(dd) 110 | for ad in annotation_directories: 111 | if ad.endswith('cuboids'): 112 | self._cuboids = Cuboids(ad) 113 | elif ad.endswith('semseg'): 114 | self._semseg = SemanticSegmentation(ad) 115 | 116 | def load(self) -> 'Sequence': 117 | """Loads all sequence files from disk into memory. 118 | 119 | All sequence files are loaded into memory, including sensor, meta and annotation data. 120 | 121 | Returns: 122 | Current instance of ``Sequence`` 123 | """ 124 | self.load_lidar() 125 | self.load_camera() 126 | self.load_gps() 127 | self.load_timestamps() 128 | self.load_cuboids() 129 | self.load_semseg() 130 | return self 131 | 132 | def load_lidar(self) -> 'Sequence': 133 | """Loads all LiDAR files from disk into memory. 134 | 135 | All LiDAR point cloud files are loaded into memory. 136 | 137 | Returns: 138 | Current instance of ``Sequence`` 139 | """ 140 | self._lidar.load() 141 | return self 142 | 143 | def load_camera(self) -> 'Sequence': 144 | """Loads all camera files from disk into memory. 145 | 146 | All camera image files are loaded into memory. 147 | 148 | Returns: 149 | Current instance of ``Sequence`` 150 | """ 151 | for cam in self._camera.values(): 152 | cam.load() 153 | return self 154 | 155 | def load_gps(self) -> 'Sequence': 156 | """Loads all gps files from disk into memory. 157 | 158 | All gps data files are loaded into memory. 159 | 160 | Returns: 161 | Current instance of ``Sequence`` 162 | """ 163 | self._gps.load() 164 | return self 165 | 166 | def load_timestamps(self) -> 'Sequence': 167 | """Loads all timestamp files from disk into memory. 168 | 169 | All timestamp files are loaded into memory. 170 | 171 | Returns: 172 | Current instance of ``Sequence`` 173 | """ 174 | self._timestamps.load() 175 | return self 176 | 177 | def load_cuboids(self) -> 'Sequence': 178 | """Loads all cuboid annotation files from disk into memory. 179 | 180 | All cuboid annotation files are loaded into memory. 181 | 182 | Returns: 183 | Current instance of ``Sequence`` 184 | """ 185 | self._cuboids.load() 186 | return self 187 | 188 | def load_semseg(self) -> 'Sequence': 189 | """Loads all semantic segmentation files from disk into memory. 190 | 191 | All semantic segmentation files are loaded into memory. 192 | 193 | Returns: 194 | Current instance of ``Sequence`` 195 | """ 196 | if self.semseg: 197 | self.semseg.load() 198 | return self 199 | 200 | 201 | if __name__ == '__main__': 202 | pass 203 | -------------------------------------------------------------------------------- /tutorials/pointcloud_world_to_ego/pointcloud_world_to_ego.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Pointcloud World to Ego Coordinates Tutorial" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "### This tutorials show how to plot pointcloud in the world coordinate and ego coordinate.\n", 15 | "#### 1.Import required python modules and load sequence data." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "ExecuteTime": { 23 | "end_time": "2020-05-14T14:52:19.627445Z", 24 | "start_time": "2020-05-14T14:52:14.839155Z" 25 | }, 26 | "pycharm": { 27 | "is_executing": false 28 | } 29 | }, 30 | "outputs": [], 31 | "source": [ 32 | "import pandaset\n", 33 | "import os\n", 34 | "\n", 35 | "# load dataset\n", 36 | "dataset = pandaset.DataSet(\"/data/PandaSet\")\n", 37 | "seq002 = dataset[\"002\"]\n", 38 | "seq002.load_lidar().load_semseg()" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "#### 2.Plot LIDAR points for Pandar64 and PandarGT in the world coordinate.\n", 46 | "- Plot Pandar64 pointcloud by points' labels ```d=0``` colorized as blue\n", 47 | "- Plot PandarGT pointcloud by points' labels ```d=1``` colorized as red" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": { 54 | "ExecuteTime": { 55 | "start_time": "2020-05-14T14:52:38.946Z" 56 | }, 57 | "pycharm": { 58 | "is_executing": false 59 | } 60 | }, 61 | "outputs": [], 62 | "source": [ 63 | "import open3d as o3d\n", 64 | "\n", 65 | "seq_idx = 40\n", 66 | "\n", 67 | "# get Pandar64 points\n", 68 | "seq002.lidar.set_sensor(0)\n", 69 | "pandar64_points = seq002.lidar[seq_idx].to_numpy()\n", 70 | "print(\"Pandar64 has points: \", pandar64_points.shape)\n", 71 | "\n", 72 | "# get PandarGT points\n", 73 | "seq002.lidar.set_sensor(1)\n", 74 | "pandarGT_points = seq002.lidar[seq_idx].to_numpy()\n", 75 | "print(\"PandarGT has points: \", pandarGT_points.shape)\n", 76 | "\n", 77 | "axis_pcd = o3d.geometry.TriangleMesh.create_coordinate_frame(size=2.0, origin=[0, 0, 0])\n", 78 | "\n", 79 | "p64_pc = o3d.geometry.PointCloud()\n", 80 | "p64_pc.points = o3d.utility.Vector3dVector(pandar64_points[:, :3])\n", 81 | "p64_pc.colors = o3d.utility.Vector3dVector([[0, 0, 1] for _ in range(pandar64_points.shape[0])])\n", 82 | "\n", 83 | "gt_pc = o3d.geometry.PointCloud()\n", 84 | "gt_pc.points = o3d.utility.Vector3dVector(pandarGT_points[:, :3])\n", 85 | "gt_pc.colors = o3d.utility.Vector3dVector([[1, 0, 0] for _ in range(pandarGT_points.shape[0])])\n", 86 | "\n", 87 | "o3d.visualization.draw_geometries([axis_pcd, p64_pc, gt_pc], window_name=\"world frame\")\n" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "#### 3.Plot LIDAR points for Pandar64 and PandarGT in the ego coordinate.\n", 95 | "- Use geometry.lidar_points_to_ego to transform points in the world coordinate to the ego coordinate.\n", 96 | "- ***geometry.lidar_points_to_ego***\n", 97 | " - input\n", 98 | " - ***lidar_points***(np.array(\\[N, 3\\])): lidar points in the world coordinates.\n", 99 | " - ***lidar_pose***: pose in the world coordinates for one camera in one frame.\n", 100 | " - output\n", 101 | " - ***lidar_points_in_ego***(np.array(\\[N, 2\\])): lidar points in the ego coordinates." 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": null, 107 | "metadata": { 108 | "pycharm": { 109 | "is_executing": false 110 | } 111 | }, 112 | "outputs": [], 113 | "source": [ 114 | "import open3d as o3d\n", 115 | "from pandaset import geometry\n", 116 | "\n", 117 | "ego_pandar64_points = geometry.lidar_points_to_ego(pandar64_points[:, :3], seq002.lidar.poses[seq_idx])\n", 118 | "p64_pc = o3d.geometry.PointCloud()\n", 119 | "p64_pc.points = o3d.utility.Vector3dVector(ego_pandar64_points)\n", 120 | "p64_pc.colors = o3d.utility.Vector3dVector([[0, 0, 1] for _ in range(pandar64_points.shape[0])])\n", 121 | "\n", 122 | "ego_pandarGT_points = geometry.lidar_points_to_ego(pandarGT_points[:, :3], seq002.lidar.poses[seq_idx])\n", 123 | "gt_pc = o3d.geometry.PointCloud()\n", 124 | "gt_pc.points = o3d.utility.Vector3dVector(ego_pandarGT_points)\n", 125 | "gt_pc.colors = o3d.utility.Vector3dVector([[1, 0, 0] for _ in range(pandarGT_points.shape[0])])\n", 126 | "\n", 127 | "o3d.visualization.draw_geometries([axis_pcd, p64_pc, gt_pc], window_name=\"ego frame\")" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": null, 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [] 136 | } 137 | ], 138 | "metadata": { 139 | "kernelspec": { 140 | "display_name": "python3.7", 141 | "language": "python", 142 | "name": "python3.7" 143 | }, 144 | "language_info": { 145 | "codemirror_mode": { 146 | "name": "ipython", 147 | "version": 3 148 | }, 149 | "file_extension": ".py", 150 | "mimetype": "text/x-python", 151 | "name": "python", 152 | "nbconvert_exporter": "python", 153 | "pygments_lexer": "ipython3", 154 | "version": "3.7.6" 155 | }, 156 | "pycharm": { 157 | "stem_cell": { 158 | "cell_type": "raw", 159 | "metadata": { 160 | "collapsed": false 161 | }, 162 | "source": [] 163 | } 164 | }, 165 | "toc": { 166 | "base_numbering": 1, 167 | "nav_menu": {}, 168 | "number_sections": true, 169 | "sideBar": true, 170 | "skip_h1_title": false, 171 | "title_cell": "Table of Contents", 172 | "title_sidebar": "Contents", 173 | "toc_cell": false, 174 | "toc_position": {}, 175 | "toc_section_display": true, 176 | "toc_window_display": false 177 | }, 178 | "varInspector": { 179 | "cols": { 180 | "lenName": 16, 181 | "lenType": 16, 182 | "lenVar": 40 183 | }, 184 | "kernels_config": { 185 | "python": { 186 | "delete_cmd_postfix": "", 187 | "delete_cmd_prefix": "del ", 188 | "library": "var_list.py", 189 | "varRefreshCmd": "print(var_dic_list())" 190 | }, 191 | "r": { 192 | "delete_cmd_postfix": ") ", 193 | "delete_cmd_prefix": "rm(", 194 | "library": "var_list.r", 195 | "varRefreshCmd": "cat(var_dic_list()) " 196 | } 197 | }, 198 | "types_to_exclude": [ 199 | "module", 200 | "function", 201 | "builtin_function_or_method", 202 | "instance", 203 | "_Feature" 204 | ], 205 | "window_display": false 206 | } 207 | }, 208 | "nbformat": 4, 209 | "nbformat_minor": 2 210 | } 211 | -------------------------------------------------------------------------------- /python/pandaset/annotations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import glob 3 | import json 4 | import os 5 | from abc import ABCMeta, abstractmethod 6 | from typing import overload, List, TypeVar, Dict 7 | 8 | import pandas as pd 9 | 10 | T = TypeVar('T') 11 | 12 | 13 | class Annotation: 14 | """Meta class inherited by subclasses for more specific annotation types. 15 | 16 | ``Annotation`` provides generic preparation and loading methods for PandaSet folder structures. Subclasses 17 | for specific annotation styles must implement certain methods, as well as can override existing ones for extension. 18 | 19 | Args: 20 | directory: Absolute or relative path where annotation files are stored 21 | 22 | Attributes: 23 | data: List of annotation data objects. The type of list elements depends on the subclass implementation of protected method ``_load_data_file`` 24 | """ 25 | __metaclass__ = ABCMeta 26 | 27 | @property 28 | @abstractmethod 29 | def _data_file_extension(self) -> str: 30 | ... 31 | 32 | @property 33 | def data(self) -> List[T]: 34 | """Returns annotation data array. 35 | 36 | Subclasses can use any type inside array. 37 | """ 38 | return self._data 39 | 40 | def __init__(self, directory: str) -> None: 41 | self._directory: str = directory 42 | self._data_structure: List[str] = None 43 | self._data: List[T] = None 44 | self._load_structure() 45 | 46 | @overload 47 | def __getitem__(self, item: int) -> T: 48 | ... 49 | 50 | @overload 51 | def __getitem__(self, item: slice) -> List[T]: 52 | ... 53 | 54 | def __getitem__(self, item): 55 | return self.data[item] 56 | 57 | def _load_structure(self) -> None: 58 | self._load_data_structure() 59 | 60 | def _load_data_structure(self) -> None: 61 | self._data_structure = sorted( 62 | glob.glob(f'{self._directory}/*.{self._data_file_extension}')) 63 | 64 | def load(self) -> None: 65 | """Loads all annotation files from disk into memory. 66 | 67 | All annotation files are loaded into memory in filename order. 68 | """ 69 | self._load_data() 70 | 71 | def _load_data(self) -> None: 72 | self._data = [] 73 | for fp in self._data_structure: 74 | self._data.append(self._load_data_file(fp)) 75 | 76 | @abstractmethod 77 | def _load_data_file(self, fp: str) -> None: 78 | ... 79 | 80 | 81 | class Cuboids(Annotation): 82 | """Loads and provides Cuboid annotations. Subclass of ``Annotation``. 83 | 84 | ``Cuboids`` loads files in `{sequence_id}/annotations/annotations/cuboids/` containing cuboid annotations. 85 | 86 | Args: 87 | directory: Absolute or relative path where annotation files are stored 88 | 89 | Attributes: 90 | data: List of cuboids for each frame of scene. 91 | """ 92 | 93 | @property 94 | def _data_file_extension(self) -> str: 95 | return 'pkl.gz' 96 | 97 | @property 98 | def data(self) -> List[pd.DataFrame]: 99 | """Returns annotation data array. 100 | 101 | Returns: 102 | List of cuboid data frames. Each data frame has columns as follows: 103 | - index: `int` 104 | - Each row corresponds to one cuboid. The index order is arbitrary. 105 | - `uuid`: `str 106 | - Unique identifier for an object. If object is tracked within the sequence, the `uuid` stays the same on every frame. 107 | - `label`: `str` 108 | - Contains name of object class associated with drawn cuboid. 109 | - `yaw`: `str` 110 | - Rotation of cuboid around the z-axis. Given in _radians_ from which the cuboid is rotated along the z-axis. 0 radians is equivalent to the direction of the vector `(0, 1, 0)`. The vector points at the length-side. Rotation happens counter-clockwise, i.e., PI/2 is pointing in the same direction as the vector `(-1, 0, 0)`. 111 | - `stationary`: `bool` 112 | - `True` if object is stationary in the whole scene, e.g., a parked car or traffic light. Otherwise `False`. 113 | - `camera_used`: `int` 114 | - Reference to the camera which was used to validate cuboid position in projection. If no camera was explicitly used, value is set to `-1`. 115 | - `position.x`: `float` 116 | - Position of the cuboid expressed as the center of the cuboid. Value is in world-coordinate system. 117 | - `position.y`: `float` 118 | - Position of the cuboid expressed as the center of the cuboid. Value is in world-coordinate system. 119 | - `position.z`: `float` 120 | - Position of the cuboid expressed as the center of the cuboid. Value is in world-coordinate system. 121 | - `dimensions.x`: `float` 122 | - The dimensions of the cuboid based on the world dimensions. Width of the cuboid from left to right. 123 | - `dimensions.y`: `float` 124 | - The dimensions of the cuboid based on the world dimensions. Length of the cuboid from front to back. 125 | - `dimensions.z`: `float` 126 | - The dimensions of the cuboid based on the world dimensions. Height of the cuboid from top to bottom. 127 | - `attributes.object_motion`: `str` 128 | - Values are `Parked`, `Stopped` or `Moving`. 129 | - Set for cuboids with `label` values in 130 | - _Car_ 131 | - _Pickup Truck_ 132 | - _Medium-sized Truck_ 133 | - _Semi-truck_ 134 | - _Towed Object_ 135 | - _Motorcycle_ 136 | - _Other Vehicle - Construction Vehicle_ 137 | - _Other Vehicle - Uncommon_ 138 | - _Other Vehicle - Pedicab_ 139 | - _Emergency Vehicle_ 140 | - _Bus_ 141 | - _Personal Mobility Device_ 142 | - _Motorized Scooter_ 143 | - _Bicycle_ 144 | - _Train_ 145 | - _Trolley_ 146 | - _Tram / Subway_ 147 | - `attributes.rider_status`: `str` 148 | - Values are `With Rider` or `Without Rider`. 149 | - Set for cuboids with `label` values in 150 | - _Motorcycle_ 151 | - _Personal Mobility Device_ 152 | - _Motorized Scooter_ 153 | - _Bicycle_ 154 | - _Animals - Other_ 155 | - `attributes.pedestrian_behavior`: `str` 156 | - Value are `Sitting`, `Lying`, `Walking` or `Standing` 157 | - Set for cuboids with `label` values in 158 | - _Pedestrian_ 159 | - _Pedestrian with Object_ 160 | - `attributes.pedestrian_age`: `str` 161 | - Value are `Adult` or `Child` (less than ~18 years old) 162 | - Set for cuboids with `label` values in 163 | - _Pedestrian_ 164 | - _Pedestrian with Object_ 165 | - `cuboids.sensor_id`: `int` 166 | - For the overlap area between mechanical 360° LiDAR and front-facing LiDAR, moving objects received two cuboids to compensate for synchronization differences of both sensors. If cuboid is in this overlapping area and moving, this value is either `0` (mechanical 360° LiDAR) or `1` (front-facing LiDAR). All other cuboids have value `-1`. 167 | - `cuboids.sibling_id`: `str` 168 | - For cuboids which have `cuboids.sensor_id` set to `0` or `1`: this field stores the `uuid` of the sibling cuboid, i.e., measuring the same object in the overlap region, but with the other respective sensor. 169 | 170 | """ 171 | return self._data 172 | 173 | def __init__(self, directory: str) -> None: 174 | Annotation.__init__(self, directory) 175 | 176 | @overload 177 | def __getitem__(self, item: int) -> pd.DataFrame: 178 | ... 179 | 180 | @overload 181 | def __getitem__(self, item: slice) -> List[pd.DataFrame]: 182 | ... 183 | 184 | def __getitem__(self, item): 185 | return super().__getitem__(item) 186 | 187 | def _load_data_file(self, fp: str) -> None: 188 | return pd.read_pickle(fp) 189 | 190 | 191 | class SemanticSegmentation(Annotation): 192 | """Loads and provides Semantic Segmentation annotations. Subclass of ``Annotation``. 193 | 194 | ``SemanticSegmentation`` loads files in `{sequence_id}/annotations/annotations/semseg/` containing semantic segmentation annotations for point clouds and class name mapping. 195 | 196 | Args: 197 | directory: Absolute or relative path where annotation files are stored 198 | 199 | Attributes: 200 | data: List of points and their class ID for each frame. 201 | classes: Dict containing class ID to class name mapping. 202 | """ 203 | 204 | @property 205 | def _data_file_extension(self) -> str: 206 | return 'pkl.gz' 207 | 208 | @property 209 | def data(self) -> List[pd.DataFrame]: 210 | """Returns annotation data array. 211 | 212 | Returns: 213 | List of semantic segmentation data frames. Each data frame has columns as follows: 214 | - index: `int` 215 | - Index order corresponds to the order of point cloud in ``lidar`` property. 216 | - `class`: `str` 217 | - Class ID as a number in string format. Can be used to find class name from ``classes`` property. 218 | """ 219 | return self._data 220 | 221 | @property 222 | def classes(self) -> Dict[str, str]: 223 | """Returns class id to class name mapping. 224 | 225 | Returns: 226 | Dictionary with class ID as key and class name as value. Valid for the complete scene. 227 | """ 228 | return self._classes 229 | 230 | def __init__(self, directory: str) -> None: 231 | self._classes_structure: str = None 232 | self._classes: Dict[str, str] = None 233 | Annotation.__init__(self, directory) 234 | 235 | @overload 236 | def __getitem__(self, item: int) -> pd.DataFrame: 237 | ... 238 | 239 | @overload 240 | def __getitem__(self, item: slice) -> List[pd.DataFrame]: 241 | ... 242 | 243 | def __getitem__(self, item): 244 | return super().__getitem__(item) 245 | 246 | def load(self) -> None: 247 | super().load() 248 | self._load_classes() 249 | 250 | def _load_structure(self) -> None: 251 | super()._load_structure() 252 | self._load_classes_structure() 253 | 254 | def _load_classes_structure(self) -> None: 255 | classes_file = f'{self._directory}/classes.json' 256 | if os.path.isfile(classes_file): 257 | self._classes_structure = classes_file 258 | 259 | def _load_data_file(self, fp: str) -> None: 260 | return pd.read_pickle(fp) 261 | 262 | def _load_classes(self) -> None: 263 | with open(self._classes_structure, 'r') as f: 264 | file_data = json.load(f) 265 | self._classes = file_data 266 | 267 | 268 | if __name__ == '__main__': 269 | pass 270 | -------------------------------------------------------------------------------- /python/pandaset/sensors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import glob 3 | import json 4 | import os.path 5 | from typing import List, overload, TypeVar, Dict 6 | from abc import ABCMeta, abstractmethod 7 | 8 | import pandas as pd 9 | from PIL import Image 10 | from PIL.JpegImagePlugin import JpegImageFile 11 | from pandas.core.frame import DataFrame 12 | 13 | T = TypeVar('T') 14 | 15 | 16 | class Sensor: 17 | """Meta class inherited by subclasses for more specific sensor types. 18 | 19 | ``Sensor`` provides generic preparation and loading methods for PandaSet folder structures. Subclasses 20 | for specific sensor types must implement certain methods, as well as can override existing ones for extension. 21 | 22 | Args: 23 | directory: Absolute or relative path where sensor files are stored 24 | 25 | Attributes: 26 | data: List of sensor data objects. The type of list elements depends on the subclass implementation of protected method ``_load_data_file`` 27 | poses: List of sensor poses in world-coordinates 28 | timestamps: List of recording timestamps for sensor 29 | """ 30 | __metaclass__ = ABCMeta 31 | 32 | @property 33 | @abstractmethod 34 | def _data_file_extension(self) -> str: 35 | ... 36 | 37 | @property 38 | def data(self) -> List[T]: 39 | """Returns sensor data array. 40 | 41 | Subclasses can use any type inside array. 42 | """ 43 | return self._data 44 | 45 | @property 46 | def poses(self) -> List[T]: 47 | """Returns sensor pose array. 48 | 49 | Subclasses can use any type inside array. 50 | """ 51 | return self._poses 52 | 53 | @property 54 | def timestamps(self) -> List[T]: 55 | """Returns sensor timestamp array. 56 | 57 | Subclasses can use any type inside array. 58 | """ 59 | return self._timestamps 60 | 61 | def __init__(self, directory: str) -> None: 62 | self._directory: str = directory 63 | self._data_structure: List[str] = None 64 | self._data: List[T] = None 65 | self._poses_structure: str = None 66 | self._poses: List[Dict[str, T]] = None 67 | self._timestamps_structure: str = None 68 | self._timestamps: List[float] = None 69 | self._load_structure() 70 | 71 | @overload 72 | def __getitem__(self, item: int) -> T: 73 | ... 74 | 75 | @overload 76 | def __getitem__(self, item: slice) -> List[T]: 77 | ... 78 | 79 | def __getitem__(self, item): 80 | return self.data[item] 81 | 82 | def _load_structure(self) -> None: 83 | self._load_data_structure() 84 | self._load_poses_structure() 85 | self._load_timestamps_structure() 86 | 87 | def _load_data_structure(self) -> None: 88 | self._data_structure = sorted( 89 | glob.glob(f'{self._directory}/*.{self._data_file_extension}')) 90 | 91 | def _load_poses_structure(self) -> None: 92 | poses_file = f'{self._directory}/poses.json' 93 | if os.path.isfile(poses_file): 94 | self._poses_structure = poses_file 95 | 96 | def _load_timestamps_structure(self) -> None: 97 | timestamps_file = f'{self._directory}/timestamps.json' 98 | if os.path.isfile(timestamps_file): 99 | self._timestamps_structure = timestamps_file 100 | 101 | def load(self) -> None: 102 | """Loads all sensor files from disk into memory. 103 | 104 | All sensor and associated meta data files are loaded into memory in filename order. 105 | """ 106 | self._load_data() 107 | self._load_poses() 108 | self._load_timestamps() 109 | 110 | def _load_data(self) -> None: 111 | self._data = [] 112 | for fp in self._data_structure: 113 | self._data.append(self._load_data_file(fp)) 114 | 115 | def _load_poses(self) -> None: 116 | self._poses = [] 117 | with open(self._poses_structure, 'r') as f: 118 | file_data = json.load(f) 119 | for entry in file_data: 120 | self._poses.append(entry) 121 | 122 | def _load_timestamps(self) -> None: 123 | self._timestamps = [] 124 | with open(self._timestamps_structure, 'r') as f: 125 | file_data = json.load(f) 126 | for entry in file_data: 127 | self._timestamps.append(entry) 128 | 129 | @abstractmethod 130 | def _load_data_file(self, fp: str) -> None: 131 | ... 132 | 133 | 134 | class Lidar(Sensor): 135 | @property 136 | def _data_file_extension(self) -> str: 137 | return 'pkl.gz' 138 | 139 | @property 140 | def data(self) -> List[pd.DataFrame]: 141 | """Returns (filtered) LiDAR point cloud array. 142 | 143 | Point cloud data is in a world-coordinate system, i.e., a static object which is a position `(10,10,0)` in frame 1, will be at position `(10,10,0)` in all other frames, too. 144 | 145 | Returns: 146 | List of point cloud data frames for each timestamp. Each data frame has columns as follows: 147 | - index: `int` 148 | - Ordered point cloud. When joining the raw point cloud with data from ``SemanticSegmentation``, it is important to keep the index order. 149 | - `x`: `float` 150 | - Position of point in world-coordinate system (x-axis) in meter 151 | - `y`: `float` 152 | - Position of point in world-coordinate system (y-axis) in meter 153 | - `z`: `float` 154 | - Position of point in world-coordinate system (z-axis) in meter 155 | - `i`: `float` 156 | - Reflection intensity in a range `[0,255]` 157 | - `t`: `float` 158 | - Recorded timestamp for specific point 159 | - `d`: `int` 160 | - Sensor ID. `0` -> mechnical 360° LiDAR, `1` -> forward-facing LiDAR 161 | """ 162 | if self._sensor_id in [0, 1]: 163 | return [df.loc[df['d'] == self._sensor_id] for df in self._data] 164 | else: 165 | return self._data 166 | 167 | @property 168 | def poses(self) -> List[Dict[str, Dict[str, float]]]: 169 | """Returns LiDAR sensor pose array. 170 | 171 | Returns: 172 | A pose dictionary of the LiDAR sensor in world-coordinates for each frame. The dictionary keys return the following types: 173 | - `position`: `dict` 174 | - `x`: `float` 175 | - Position of LiDAR sensor in world-coordinate system (x-axis) in meter 176 | - `y`: `float` 177 | - Position of LiDAR sensor in world-coordinate system (y-axis) in meter 178 | - `z`: `float` 179 | - Position of LiDAR sensor in world-coordinate system (z-axis) in meter 180 | - `heading`: `dict` 181 | - `w`: `float` 182 | - Real part of _Quaternion_ 183 | - `x`: `float` 184 | - First imaginary part of _Quaternion_ 185 | - `y`: `float` 186 | - Second imaginary part of _Quaternion_ 187 | - `z`: `float` 188 | - Third imaginary part of _Quaternion_ 189 | """ 190 | return self._poses 191 | 192 | @property 193 | def timestamps(self) -> List[float]: 194 | """Returns LiDAR sensor recording timestamps array. 195 | 196 | Returns: 197 | A list of timestamps in `float` format for each point cloud recorded in this sequence. To get point-wise timestamps, please refer to column `t` in `data` property return values. 198 | """ 199 | return self._timestamps 200 | 201 | def __init__(self, directory: str) -> None: 202 | self._sensor_id = -1 203 | Sensor.__init__(self, directory) 204 | 205 | @overload 206 | def __getitem__(self, item: int) -> DataFrame: 207 | ... 208 | 209 | @overload 210 | def __getitem__(self, item: slice) -> List[DataFrame]: 211 | ... 212 | 213 | def __getitem__(self, item): 214 | return super().__getitem__(item) 215 | 216 | def set_sensor(self, sensor_id: int) -> None: 217 | """Specifies a sensor which should be returned exclusively in the data objects 218 | 219 | Args: 220 | sensor_id: Set `-1` for both LiDAR sensors, set `0` for mechanical 360° LiDAR, set `1` for front-facing LiDAR. 221 | 222 | """ 223 | self._sensor_id = sensor_id 224 | 225 | def _load_data_file(self, fp: str) -> DataFrame: 226 | return pd.read_pickle(fp) 227 | 228 | 229 | class Camera(Sensor): 230 | @property 231 | def _data_file_extension(self) -> str: 232 | return 'jpg' 233 | 234 | @property 235 | def data(self) -> List[JpegImageFile]: 236 | """Returns Camera image array. 237 | 238 | Returns: 239 | List of camera images for each timestamp. Camera images are loaded as [``JpegImageFile``](https://pillow.readthedocs.io/en/stable/reference/plugins.html#PIL.JpegImagePlugin.JpegImageFile). 240 | """ 241 | return self._data 242 | 243 | @property 244 | def poses(self) -> List[Dict[str, Dict[str, float]]]: 245 | """Returns Camera sensor pose array. 246 | 247 | Returns: 248 | A pose dictionary of the Camera sensor in world-coordinates for each frame. The dictionary keys return the following types: 249 | - `position`: `dict` 250 | - `x`: `float` 251 | - Position of LiDAR sensor in world-coordinate system (x-axis) in meter 252 | - `y`: `float` 253 | - Position of LiDAR sensor in world-coordinate system (y-axis) in meter 254 | - `z`: `float` 255 | - Position of LiDAR sensor in world-coordinate system (z-axis) in meter 256 | - `heading`: `dict` 257 | - `w`: `float` 258 | - Real part of _Quaternion_ 259 | - `x`: `float` 260 | - First imaginary part of _Quaternion_ 261 | - `y`: `float` 262 | - Second imaginary part of _Quaternion_ 263 | - `z`: `float` 264 | - Third imaginary part of _Quaternion_ 265 | """ 266 | return self._poses 267 | 268 | @property 269 | def timestamps(self) -> List[float]: 270 | """Returns Camera sensor recording timestamps array. 271 | 272 | Returns: 273 | A list of timestamps in `float` format for each camera image recorded in this sequence. To get point-wise timestamps, please refer to column `t` in `data` property return values. 274 | """ 275 | return self._timestamps 276 | 277 | @property 278 | def intrinsics(self) -> 'Intrinsics': 279 | """Camera specific intrinsic data. 280 | 281 | Returns: 282 | Instance of class ``Intrinsics`` 283 | """ 284 | return self._intrinsics 285 | 286 | def __init__(self, directory: str) -> None: 287 | self._intrinsics_structure: str = None 288 | self._intrinsics: Intrinsics = None 289 | Sensor.__init__(self, directory) 290 | 291 | @overload 292 | def __getitem__(self, item: int) -> JpegImageFile: 293 | ... 294 | 295 | @overload 296 | def __getitem__(self, item: slice) -> List[JpegImageFile]: 297 | ... 298 | 299 | def __getitem__(self, item): 300 | return super().__getitem__(item) 301 | 302 | def load(self) -> None: 303 | super().load() 304 | self._load_intrinsics() 305 | 306 | def _load_structure(self) -> None: 307 | super()._load_structure() 308 | self._load_intrinsics_structure() 309 | 310 | def _load_intrinsics_structure(self) -> None: 311 | intrinsics_file = f'{self._directory}/intrinsics.json' 312 | if os.path.isfile(intrinsics_file): 313 | self._intrinsics_structure = intrinsics_file 314 | 315 | def _load_data_file(self, fp: str) -> JpegImageFile: 316 | # solve this bug: https://github.com/python-pillow/Pillow/issues/1237 317 | img = Image.open(fp) 318 | image = img.copy() 319 | img.close() 320 | return image 321 | 322 | def _load_intrinsics(self) -> None: 323 | with open(self._intrinsics_structure, 'r') as f: 324 | file_data = json.load(f) 325 | self._intrinsics = Intrinsics(fx=file_data['fx'], 326 | fy=file_data['fy'], 327 | cx=file_data['cx'], 328 | cy=file_data['cy']) 329 | 330 | 331 | class Intrinsics: 332 | """Camera intrinsics 333 | 334 | Contains camera intrinsics with properties `fx`, `fy`, `cx`, `cy`, for easy usage with [OpenCV framework](https://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html). 335 | There is no `skew` factor in the camera recordings. 336 | """ 337 | 338 | @property 339 | def fx(self) -> float: 340 | """Focal length x-axis 341 | 342 | Returns: 343 | Focal length x-axis component 344 | """ 345 | return self._fx 346 | 347 | @property 348 | def fy(self) -> float: 349 | """Focal length y-axis 350 | 351 | Returns: 352 | Focal length y-axis component 353 | """ 354 | return self._fy 355 | 356 | @property 357 | def cx(self) -> float: 358 | """Principal point x-axis 359 | 360 | Returns: 361 | Principal point x-axis component 362 | """ 363 | return self._cx 364 | 365 | @property 366 | def cy(self) -> float: 367 | """Principal point y-axis 368 | 369 | Returns: 370 | Principal point y-axis component 371 | """ 372 | return self._cy 373 | 374 | def __init__(self, fx: float, fy: float, cx: float, cy: float): 375 | self._fx: float = fx 376 | self._fy: float = fy 377 | self._cx: float = cx 378 | self._cy: float = cy 379 | 380 | 381 | if __name__ == '__main__': 382 | pass 383 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pandaset-devkit 2 | 3 | ![Header Animation](../assets/animations/semseg-photo-labels.gif) 4 | 5 | 6 | ## Overview 7 | 8 | Welcome to the repository of the [PandaSet](https://pandaset.org/ "Pandaset Official Website") Devkit. 9 | 10 | ## Dataset 11 | ### Download 12 | 13 | To download the dataset, please visit the official [PandaSet](https://pandaset.org/ "Pandaset Official Website") webpage and sign up through the form. 14 | You will then be forwarded to a page with download links to the raw data and annotations. 15 | 16 | ### Unpack 17 | 18 | Unpack the archive into any directory on your hard disk. The path will be referenced in usage of `pandaset-devkit` later, and does not have to be in the same directory as your scripts. 19 | 20 | ### Structure 21 | 22 | #### Files & Folders 23 | 24 | ```text 25 | . 26 | ├── LICENSE.txt 27 | ├── annotations 28 | │   ├── cuboids 29 | │   │   ├── 00.pkl.gz 30 | │   │   . 31 | │   │   . 32 | │   │   . 33 | │   │   └── 79.pkl.gz 34 | │  └── semseg // Semantic Segmentation is available for specific scenes 35 | │   ├── 00.pkl.gz 36 | │   . 37 | │   . 38 | │   . 39 | │   ├── 79.pkl.gz 40 | │   └── classes.json 41 | ├── camera 42 | │   ├── back_camera 43 | │   │   ├── 00.jpg 44 | │   │   . 45 | │   │   . 46 | │   │   . 47 | │   │   ├── 79.jpg 48 | │   │   ├── intrinsics.json 49 | │   │   ├── poses.json 50 | │   │   └── timestamps.json 51 | │   ├── front_camera 52 | │   │   └── ... 53 | │   ├── front_left_camera 54 | │   │   └── ... 55 | │   ├── front_right_camera 56 | │   │   └── ... 57 | │   ├── left_camera 58 | │   │   └── ... 59 | │   └── right_camera 60 | │   └── ... 61 | ├── lidar 62 | │   ├── 00.pkl.gz 63 | │   . 64 | │   . 65 | │   . 66 | │   ├── 79.pkl.gz 67 | │   ├── poses.json 68 | │   └── timestamps.json 69 | └── meta 70 | ├── gps.json 71 | └── timestamps.json 72 | ``` 73 | 74 | ## Instructions 75 | 76 | ### Setup 77 | 78 | 1. Create a Python>=3.6 environment with `pip` installed. 79 | 2. Clone the repository `git clone git@github.com:scaleapi/pandaset-devkit.git` 80 | 3. `cd` into `pandaset-devkit/python` 81 | 4. Execute `pip install .` 82 | 83 | The `pandaset-devkit` is now installed in your Python>=3.6 environment and can be used. 84 | 85 | ### Usage 86 | 87 | To get familiar with the API you can point directly to the downloaded dataset. 88 | 89 | #### Initialization 90 | First, we need to create a `DataSet` object that searches for sequences. 91 | ``` 92 | >>> from pandaset import DataSet 93 | >>> dataset = DataSet('/data/pandaset') 94 | ``` 95 | Afterwards we can list all the sequence IDs that have been found in the data folder. 96 | ``` 97 | >>> print(dataset.sequences()) 98 | ['002',...] 99 | ``` 100 | 101 | Since semantic segmentation annotations are not always available for scenes, we can filter to get only scenes that have both semantic segmentation as well as cuboid annotations. 102 | ``` 103 | >>> print(dataset.sequences(with_semseg=True)) 104 | ['002',...] 105 | ``` 106 | 107 | Now, we access a specific sequence by choosing its key from the previously returned list, in this case sequence ID `'002'` 108 | ``` 109 | >>> seq002 = dataset['002'] 110 | ``` 111 | 112 | 113 | 114 | API Reference: [DataSet class](https://scaleapi.github.io/pandaset-devkit/dataset.html#pandaset.dataset.DataSet) 115 | 116 | #### Loading 117 | The devkit will automatically search the sequence directory for available sensor data, metadata and annotations and prepare the directory to be loaded explicitly. At this point no point clouds or images have been loaded into memory. 118 | To execute the loading of sensor data and metadata into memory, we simply call the `load()` method on the sequence object. This will load all available sensor data and metadata. 119 | ``` 120 | >>> seq002.load() 121 | ``` 122 | 123 | If only certain data is required for analysis, there are more specific methods available, which can also be chained to each other. 124 | ``` 125 | >>> seq002.load_lidar().load_cuboids() 126 | ``` 127 | 128 | API Reference: [Sequence class](https://scaleapi.github.io/pandaset-devkit/sequence.html#pandaset.sequence.Sequence) 129 | 130 | #### Data Access 131 | 132 | ##### LiDAR 133 | The LiDAR point clouds are stored as [pandas.DataFrames](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html#pandas.DataFrame) and therefore you are able to leverage their extensive API for data manipulation. This includes the simple return as a [numpy.ndarray](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html). 134 | ``` 135 | >>> pc0 = seq002.lidar[0] 136 | >>> print(pc0) 137 | x y z i t d 138 | index 139 | 0 -75.131138 -79.331690 3.511804 7.0 1.557540e+09 0 140 | 1 -112.588306 -118.666002 1.423499 31.0 1.557540e+09 0 141 | 2 -42.085902 -44.384891 0.593491 7.0 1.557540e+09 0 142 | 3 -27.329435 -28.795053 -0.403781 0.0 1.557540e+09 0 143 | 4 -6.196208 -6.621082 1.130009 3.0 1.557540e+09 0 144 | ... ... ... ... ... .. 145 | 166763 27.670526 17.159726 3.778677 25.0 1.557540e+09 1 146 | 166764 27.703935 17.114063 3.780626 27.0 1.557540e+09 1 147 | 166765 27.560664 16.955518 3.767948 18.0 1.557540e+09 1 148 | 166766 27.384433 16.783824 3.752670 22.0 1.557540e+09 1 149 | 166767 27.228821 16.626038 3.739154 20.0 1.557540e+09 1 150 | [166768 rows x 6 columns] 151 | ``` 152 | ``` 153 | >>> pc0_np = seq002.lidar[0].values # Returns the first LiDAR frame in the sequence as an numpy ndarray 154 | >>> print(pc0_np) 155 | [[-7.51311379e+01 -7.93316897e+01 3.51180427e+00 7.00000000e+00 156 | 1.55753996e+09 0.00000000e+00] 157 | [-1.12588306e+02 -1.18666002e+02 1.42349938e+00 3.10000000e+01 158 | 1.55753996e+09 0.00000000e+00] 159 | [-4.20859017e+01 -4.43848908e+01 5.93490847e-01 7.00000000e+00 160 | 1.55753996e+09 0.00000000e+00] 161 | ... 162 | [ 2.75606640e+01 1.69555183e+01 3.76794770e+00 1.80000000e+01 163 | 1.55753996e+09 1.00000000e+00] 164 | [ 2.73844334e+01 1.67838237e+01 3.75266969e+00 2.20000000e+01 165 | 1.55753996e+09 1.00000000e+00] 166 | [ 2.72288210e+01 1.66260378e+01 3.73915448e+00 2.00000000e+01 167 | 1.55753996e+09 1.00000000e+00]] 168 | ``` 169 | 170 | The LiDAR points are stored in a world coordinate system; therefore it is not required to transform them using the vehicle's pose graph. This allows you to query all LiDAR frames in the sequence or a certain sampling rate and simply visualize them using your preferred library. 171 | 172 | Instead of using always all of the point clouds available, it is also possible to simply slice the `lidar` property as one is used from python lists. 173 | ``` 174 | >>> pc_all = seq002.lidar[:] # Returns all LiDAR frames from the sequence 175 | ``` 176 | ``` 177 | >>> pc_sampled = seq002.lidar[::2] # Returns every second LiDAR frame from the sequence 178 | ``` 179 | 180 | In addition to the LiDAR points, the `lidar` property also holds the sensor pose (`lidar.poses`) in world coordinate system and timestamp (`lidar.timestamps`) for every LiDAR frame recorded. Both objects can be sliced in the same way as the `lidar` property holding the point clouds. 181 | ``` 182 | >>> sl = slice(None, None, 5) # Equivalent to [::5] # Extract every fifth frame including sensor pose and timestamps 183 | >>> lidar_obj = seq002.lidar 184 | >>> pcs = lidar_obj[sl] 185 | >>> poses = lidar_obj.poses[sl] 186 | >>> timestamps = lidar_obj.timestamps[sl] 187 | >>> print( len(pcs) == len(poses) == len(timestamps) ) 188 | True 189 | ``` 190 | 191 | The LiDAR point clouds include by default the points from both the mechanical 360° LiDAR and the front-facing LiDAR. To select only one of the sensors, the `set_sensor` method is available. 192 | ``` 193 | >>> pc0 = s002.lidar[0] 194 | >>> print(pc0.shape) 195 | (166768, 6) 196 | >>> s002.lidar.set_sensor(0) # set to include only mechanical 360° LiDAR 197 | >>> pc0_sensor0 = s002.lidar[0] 198 | >>> print(pc0_sensor0.shape) 199 | (106169, 6) 200 | >>> s002.lidar.set_sensor(1) # set to include only front-facing LiDAR 201 | >>> pc0_sensor1 = s002.lidar[0] 202 | >>> print(pc0_sensor1.shape) 203 | (60599, 6) 204 | ``` 205 | Since the applied filter operation leaves the original row index intact for each point (relevant for joining with `SemanticSegmentation`), one can easily test that no point was left out in filtering: 206 | ``` 207 | >>> import pandas as pd 208 | >>> pc0_concat = pd.concat([pc0_sensor0, pc0_sensor1]) 209 | >>> print(pc0_concat.shape) 210 | (166768, 6) 211 | >>> print(pc0 == pc0_concat) 212 | x y z i t d 213 | index 214 | 0 True True True True True True 215 | 1 True True True True True True 216 | 2 True True True True True True 217 | 3 True True True True True True 218 | 4 True True True True True True 219 | ... ... ... ... ... ... 220 | 166763 True True True True True True 221 | 166764 True True True True True True 222 | 166765 True True True True True True 223 | 166766 True True True True True True 224 | 166767 True True True True True True 225 | [166768 rows x 6 columns] 226 | >>> print((~(pc0 == pc0_concat)).sum()) # Counts the number of cells with `False` value, i.e., the ones where original point cloud and concatenated filtered point cloud differentiate 227 | x 0 228 | y 0 229 | z 0 230 | i 0 231 | t 0 232 | d 0 233 | dtype: int64 234 | ``` 235 | 236 | API Reference: [Lidar class](https://scaleapi.github.io/pandaset-devkit/sensors.html#pandaset.sensors.Lidar) 237 | 238 | ##### Cameras 239 | Since the recording vehicle was equipped with multiple cameras, first we need to list which cameras have been used to record the sequence. 240 | ``` 241 | >>> print(seq002.camera.keys()) 242 | ['front_camera', 'left_camera', 'back_camera', 'right_camera', 'front_left_camera', 'front_right_camera'] 243 | ``` 244 | The camera count and names should be equal for all sequences. 245 | 246 | Each camera name has its recordings loaded as [Pillow Image](https://pillow.readthedocs.io/en/stable/reference/Image.html) object, and can be accessed via normal list slicing. In the following example, we select the first image from the front camera and display it using the Pillow library in Python. 247 | ``` 248 | >>> front_camera = seq002.camera['front_camera'] 249 | >>> img0 = front_camera[0] 250 | >>> img0.show() 251 | ``` 252 | Afterwards the extensive Pillow Image API can be used for image manipulation, conversion or export. 253 | 254 | Similar to the `Lidar` object, each `Camera` object has properties that hold the camera pose (`camera.poses`) and timestamp (`camera.timestamps`) for every recorded frame, as well as the camera intrinsics (`camera.intrinsics`). 255 | Again, the objects can be sliced the same way as the `Camera` object: 256 | 257 | ``` 258 | >>> sl = slice(None, None, 5) # Equivalent to [::5] 259 | >>> camera_obj = seq002.camera['front_camera'] 260 | >>> pcs = camera_obj[sl] 261 | >>> poses = camera_obj.poses[sl] 262 | >>> timestamps = camera_obj.timestamps[sl] 263 | >>> intrinsics = camera_obj.intrinsics 264 | ``` 265 | 266 | API Reference: [Camera class](https://scaleapi.github.io/pandaset-devkit/sensors.html#pandaset.sensors.Camera) 267 | 268 | #### Meta 269 | In addition to the sensor data, the loaded dataset also contains the following meta information: 270 | * GPS Positions 271 | * Timestamps 272 | 273 | These can be directly accessed through the known list slicing operations, and read in their dict format. The following example shows how to get the GPS coordinates of the vehicle on the first frame. 274 | ``` 275 | >>> pose0 = seq002.gps[0] 276 | >>> print(pose0['lat']) 277 | 37.776089291519924 278 | >>> print(pose0['long']) 279 | -122.39931707791749 280 | ``` 281 | 282 | API Reference: [GPS class](https://scaleapi.github.io/pandaset-devkit/meta.html#pandaset.meta.GPS) 283 | 284 | API Reference: [Timestamps class](https://scaleapi.github.io/pandaset-devkit/meta.html#pandaset.meta.Timestamps) 285 | 286 | #### Annotations 287 | 288 | ##### Cuboids 289 | The LiDAR Cuboid annotations are also stored inside the sequence object as a [pandas.DataFrames](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html#pandas.DataFrame) for each timestamp. 290 | The position coordinates (`position.x`,`position.y`,`position.z`) are located at the center of a cuboid. `dimensions.x` is the width of the cuboid from left to right, `dimensions.y` is the length of the cuboid from front to back and `dimensions.z` is the height of the cuboid from top to bottom. 291 | 292 | ``` 293 | >>> cuboids0 = seq002.cuboids[0] # Returns the cuboid annotations for the first LiDAR frame in the sequence 294 | >>> print(cuboids0.columns) 295 | Index(['uuid', 'label', 'yaw', 'stationary', 'camera_used', 'position.x', 296 | 'position.y', 'position.z', 'dimensions.x', 'dimensions.y', 297 | 'dimensions.z', 'attributes.object_motion', 'cuboids.sibling_id', 298 | 'cuboids.sensor_id', 'attributes.rider_status', 299 | 'attributes.pedestrian_behavior', 'attributes.pedestrian_age'], 300 | dtype='object') 301 | ``` 302 | 303 | API Reference: [Cuboids class](https://scaleapi.github.io/pandaset-devkit/annotations.html#pandaset.annotations.Cuboids) 304 | 305 | ##### Semantic Segmentation 306 | Analogous to the cuboid annotations, the Semantic Segmentation can be accessed using the `semseg` property on the sequence object. The index of each Semantic Segmentation data frame corresponds to the index of each LiDAR point cloud data frame, and can be joined using the index. 307 | ``` 308 | >>> semseg0 = seq002.semseg[0] # Returns the semantic segmentation for the first LiDAR frame in the sequence 309 | >>> print(semseg0.columns) 310 | Index(['class'], dtype='object') 311 | >>> print(seq002.semseg.classes) 312 | {'1': 'Smoke', '2': 'Exhaust', '3': 'Spray or rain', '4': 'Reflection', '5': 'Vegetation', '6': 'Ground', '7': 'Road', '8': 'Lane Line Marking', '9': 'Stop Line Marking', '10': 'Other Road Marking', '11': 'Sidewalk', '12': 'Driveway', '13': 'Car', '14': 'Pickup Truck', '15': 'Medium-sized Truck', '16': 'Semi-truck', '17': 'Towed Object', '18': 'Motorcycle', '19': 'Other Vehicle - Construction Vehicle', '20': 'Other Vehicle - Uncommon', '21': 'Other Vehicle - Pedicab', '22': 'Emergency Vehicle', '23': 'Bus', '24': 'Personal Mobility Device', '25': 'Motorized Scooter', '26': 'Bicycle', '27': 'Train', '28': 'Trolley', '29': 'Tram / Subway', '30': 'Pedestrian', '31': 'Pedestrian with Object', '32': 'Animals - Bird', '33': 'Animals - Other', '34': 'Pylons', '35': 'Road Barriers', '36': 'Signs', '37': 'Cones', '38': 'Construction Signs', '39': 'Temporary Construction Barriers', '40': 'Rolling Containers', '41': 'Building', '42': 'Other Static Object'} 313 | ``` 314 | 315 | API Reference: [SemanticSegmentation class](https://scaleapi.github.io/pandaset-devkit/annotations.html#pandaset.annotations.SemanticSegmentation) 316 | 317 | 318 | 319 | ![Header Animation](../assets/static/montage-semseg-projection.jpg) -------------------------------------------------------------------------------- /tutorials/raw_depth_projection/raw_depth_projection.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Raw Depth Projection Tutorial" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "### This tutorial shows how to do a cylindrical depth projection of the 3D LiDAR point clouds" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "#### 1. Download the raw point cloud data\n", 22 | "You can find the link to download the raw data for the LiDAR point clouds [here](https://github.com/scaleapi/pandaset-devkit/issues/67#issuecomment-674403708).\n", 23 | "In the following we define `/data/pandaset` as the location of the data." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "#### 2. Import required python modules" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 1, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "import csv\n", 40 | "import gzip\n", 41 | "import pickle\n", 42 | "import numpy as np\n", 43 | "import matplotlib.pyplot as plt" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "#### 3. Load the raw sensor data\n", 51 | "The description of the raw sensor data is provided in [Data.Instructions.pdf](https://github.com/scaleapi/pandaset-devkit/files/5078794/PandaSet.Raw.Data.Instructions.pdf).\n", 52 | "The data directly provides the laser and column id for each measured point." 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 2, 58 | "metadata": {}, 59 | "outputs": [ 60 | { 61 | "name": "stdout", 62 | "output_type": "stream", 63 | "text": [ 64 | " laser_id column_id elevation azimuth_col azimuth_col_corrected \\\n", 65 | "0 8 37793 1.510 354.850006 360.058014 \n", 66 | "1 14 37793 0.496 354.850006 360.058014 \n", 67 | "2 20 37793 -0.520 354.850006 360.058014 \n", 68 | "3 26 37793 -1.534 354.850006 360.058014 \n", 69 | "4 32 37793 -2.548 354.850006 360.058014 \n", 70 | "... ... ... ... ... ... \n", 71 | "106881 13 39644 0.663 5.050000 8.175000 \n", 72 | "106882 18 39644 -0.181 5.050000 6.092000 \n", 73 | "106883 39 39644 -3.724 5.050000 -0.158000 \n", 74 | "106884 45 39644 -4.732 5.050000 -0.158000 \n", 75 | "106885 51 39644 -5.738 5.050000 -0.158000 \n", 76 | "\n", 77 | " distance intensity \n", 78 | "0 84.695999 39 \n", 79 | "1 103.564003 9 \n", 80 | "2 61.416000 56 \n", 81 | "3 45.952000 3 \n", 82 | "4 14.436000 0 \n", 83 | "... ... ... \n", 84 | "106881 174.084000 10 \n", 85 | "106882 50.796001 156 \n", 86 | "106883 23.176001 2 \n", 87 | "106884 19.440001 1 \n", 88 | "106885 16.832001 2 \n", 89 | "\n", 90 | "[106886 rows x 7 columns]\n" 91 | ] 92 | } 93 | ], 94 | "source": [ 95 | "file = \"/data/pandaset/001/lidar/00.pkl.gz\"\n", 96 | "with gzip.open(file, \"rb\") as fin:\n", 97 | " data = pickle.load(fin)\n", 98 | "\n", 99 | "num_rows = 64 # the number of lasers\n", 100 | "num_columns = int(360 / 0.2) # horizontal field of view / horizontal angular resolution" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "#### 3. Load the horizontal angle offets\n", 108 | "The column ids of the raw data are those collected at the same motor rotation angle. This is not the same horizontal angle, due to horizontal angle offsets in the mounting position of the lasers.\n", 109 | "With the data provided in the `csv` file, taken from the Pandar64 User Manual, we can correct the column ids to sort the points accordingly." 110 | ] 111 | }, 112 | { 113 | "cell_type": "code", 114 | "execution_count": 3, 115 | "metadata": {}, 116 | "outputs": [], 117 | "source": [ 118 | "with open(\"pandar64_channel_distribution.csv\", \"r\") as fin:\n", 119 | " reader = csv.DictReader(fin)\n", 120 | " horizontal_angle_offset = np.array([float(r[\"horizontal_angle_offset\"]) for r in reader])\n", 121 | "\n", 122 | "column_shift = (num_columns / 360 * horizontal_angle_offset).astype(np.int64)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "#### 4. Retrieve the laser and column ids\n", 130 | "The column ids are given in absolute values, therefore we have to substract the smallest value to obtain the indices relative to the current frame. Due to the angle offets, the span from minimum to maximum value is exactly 1852 per frame instead of 1800. This is since the maximum offset in both positive and negative is 26 `>> 2 * 26 = 52`. With the offsets we can compute the correct column ids." 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 4, 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "rows_list = data[\"laser_id\"].values\n", 140 | "\n", 141 | "cols_list = data[\"column_id\"].values\n", 142 | "cols_list -= np.min(cols_list)\n", 143 | "cols_list = np.mod(cols_list + column_shift[rows_list] + num_columns, num_columns)" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "#### 5. Create the depth projection\n", 151 | "First, we initialize an empty image. All locations without measurements will have the value `-1`. Then we scatter the point cloud into the image." 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 5, 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "name": "stdout", 161 | "output_type": "stream", 162 | "text": [ 163 | "(64, 1800)\n", 164 | "[[ -1. -1. -1. ... -1. 16.24 16.304]\n", 165 | " [ -1. -1. -1. ... 42.98 43.068 43.112]\n", 166 | " [101.408 101.288 101.24 ... 101.504 101.456 101.4 ]\n", 167 | " ...\n", 168 | " [ 8.08 8.076 8.064 ... 8.096 8.084 8.072]\n", 169 | " [ -1. -1. -1. ... -1. -1. -1. ]\n", 170 | " [ 1.008 0.964 0.956 ... -1. 1.032 0.908]]\n" 171 | ] 172 | } 173 | ], 174 | "source": [ 175 | "depth_img = np.full((num_rows, num_columns), fill_value=-1, dtype=np.float32)\n", 176 | "depth_img[rows_list, cols_list] = data[\"distance\"].values" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 6, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "data": { 186 | "image/png": "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\n", 187 | "text/plain": [ 188 | "
" 189 | ] 190 | }, 191 | "metadata": { 192 | "needs_background": "light" 193 | }, 194 | "output_type": "display_data" 195 | } 196 | ], 197 | "source": [ 198 | "plt.figure(figsize=(20, 4))\n", 199 | "plt.imshow(depth_img, vmin=0.5, vmax=80)\n", 200 | "plt.show()" 201 | ] 202 | } 203 | ], 204 | "metadata": { 205 | "kernelspec": { 206 | "display_name": "Python 3", 207 | "language": "python", 208 | "name": "python3" 209 | }, 210 | "language_info": { 211 | "codemirror_mode": { 212 | "name": "ipython", 213 | "version": 3 214 | }, 215 | "file_extension": ".py", 216 | "mimetype": "text/x-python", 217 | "name": "python", 218 | "nbconvert_exporter": "python", 219 | "pygments_lexer": "ipython3", 220 | "version": "3.6.9" 221 | } 222 | }, 223 | "nbformat": 4, 224 | "nbformat_minor": 4 225 | } 226 | --------------------------------------------------------------------------------