diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 9197c81a..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Build native artifacts - -on: - workflow_dispatch: - pull_request: - -jobs: - cuda: - uses: CAVISE/pipelines/.github/workflows/python-cmake-cuda.yml@main - with: - requirements-file: requirements-cuda.txt - cuda-architectures: "86" - pythonpath-subdir: OpenCOOD - python-import-modules: >- - ["opencood.pcdet_utils.iou3d_nms.iou3d_nms_cuda", - "opencood.pcdet_utils.pointnet2.pointnet2_batch.pointnet2_batch_cuda", - "opencood.pcdet_utils.pointnet2.pointnet2_stack.pointnet2_stack_cuda", - "opencood.pcdet_utils.roiaware_pool3d.roiaware_pool3d_cuda"] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 442edc51..710bac6d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,14 +23,13 @@ repos: - id: check-json - id: check-merge-conflict args: ["--assume-in-merge"] - exclude: '(docs/.*|OpenCOOD/docs/.*)' + exclude: 'docs/.*' - id: check-shebang-scripts-are-executable - id: check-symlinks - id: check-toml - id: check-vcs-permalinks - id: check-xml - id: check-yaml - exclude: opencda/coperception_models/.* - id: destroyed-symlinks - id: end-of-file-fixer - id: name-tests-test @@ -46,6 +45,8 @@ repos: description: '' entry: mypy language: python + additional_dependencies: + - mypy 'types_or': [python, pyi] args: [".", "--follow-imports=silent"] pass_filenames: false diff --git a/AIM/models/mtp/GNN_mtl_gnn/GNN_mtl_gnn.py b/AIM/models/mtp/GNN_mtl_gnn/GNN_mtl_gnn.py index 49bc8fab..26d395e7 100644 --- a/AIM/models/mtp/GNN_mtl_gnn/GNN_mtl_gnn.py +++ b/AIM/models/mtp/GNN_mtl_gnn/GNN_mtl_gnn.py @@ -31,7 +31,7 @@ def __init__(self, hidden_channels: int) -> None: self.linear4: nn.Linear = nn.Linear(hidden_channels, hidden_channels) self.linear5: nn.Linear = nn.Linear(hidden_channels, 30 * 2) - def forward( + def forward( # noqa: DC04 - invoked dynamically by torch.nn.Module.__call__ self, x: torch.Tensor, edge_index: torch.Tensor, diff --git a/CMakeLists.txt b/CMakeLists.txt index c7ed1b2e..0d0d5454 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,102 +2,10 @@ cmake_minimum_required(VERSION 3.31) project(OpenCDA LANGUAGES NONE) -option(OPENCDA_BUILD_CUDA "Build OpenCOOD CUDA extensions" OFF) -option(OPENCDA_BUILD_PROTOBUF "Generate OpenCDA protobuf Python modules" OFF) +option(OPENCDA_BUILD_PROTOBUF "Generate OpenCDA protobuf Python modules" ON) -if(NOT OPENCDA_BUILD_CUDA AND NOT OPENCDA_BUILD_PROTOBUF) - message(FATAL_ERROR - "No build component selected. Enable OPENCDA_BUILD_CUDA or " - "OPENCDA_BUILD_PROTOBUF." - ) +if(NOT OPENCDA_BUILD_PROTOBUF) + message(FATAL_ERROR "No OpenCDA build component selected") endif() -if(OPENCDA_BUILD_PROTOBUF) - add_subdirectory(opencda/core/common/communication) -endif() - -if(OPENCDA_BUILD_CUDA) - if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES OR CMAKE_CUDA_ARCHITECTURES STREQUAL "") - message(FATAL_ERROR - "CMAKE_CUDA_ARCHITECTURES must be set explicitly when building " - "the OpenCOOD CUDA extensions." - ) - endif() - - set(OPENCDA_CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES}") - set(OPENCDA_TORCH_CUDA_ARCH_LIST) - foreach(cuda_architecture IN LISTS OPENCDA_CUDA_ARCHITECTURES) - if(NOT cuda_architecture MATCHES "^[0-9]+$") - message(FATAL_ERROR - "Unsupported CUDA architecture '${cuda_architecture}'. " - "Use numeric values such as 86 or 75;86;89." - ) - endif() - - math(EXPR cuda_architecture_major "${cuda_architecture} / 10") - math(EXPR cuda_architecture_minor "${cuda_architecture} % 10") - list(APPEND OPENCDA_TORCH_CUDA_ARCH_LIST - "${cuda_architecture_major}.${cuda_architecture_minor}" - ) - endforeach() - - enable_language(CXX) - enable_language(CUDA) - - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - set(CMAKE_CXX_EXTENSIONS OFF) - set(CMAKE_CUDA_STANDARD 17) - set(CMAKE_CUDA_STANDARD_REQUIRED ON) - set(CMAKE_CUDA_EXTENSIONS OFF) - set(CMAKE_POSITION_INDEPENDENT_CODE ON) - - find_package(Python3 3.12 REQUIRED COMPONENTS Interpreter Development.Module) - find_package(CUDAToolkit REQUIRED) - - execute_process( - COMMAND - "${Python3_EXECUTABLE}" -c - "import torch; print(torch.utils.cmake_prefix_path)" - OUTPUT_VARIABLE TORCH_CMAKE_PREFIX_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND_ERROR_IS_FATAL ANY - ) - list(APPEND CMAKE_PREFIX_PATH "${TORCH_CMAKE_PREFIX_PATH}") - - # PyTorch's CMake package uses TORCH_CUDA_ARCH_LIST and otherwise adds a - # broad architecture list that includes targets removed from recent CUDA - # toolkits. Disable CMake's duplicate architecture flags and let PyTorch - # emit the requested gencode flags. - unset(CMAKE_CUDA_ARCHITECTURES CACHE) - unset(CMAKE_CUDA_ARCHITECTURES) - set(TORCH_CUDA_ARCH_LIST "${OPENCDA_TORCH_CUDA_ARCH_LIST}") - message(STATUS - "Building OpenCOOD CUDA extensions for ${TORCH_CUDA_ARCH_LIST}" - ) - find_package(Torch REQUIRED) - - execute_process( - COMMAND - "${Python3_EXECUTABLE}" -c - "from pathlib import Path; import torch; print(Path(torch.__file__).parent / 'lib')" - OUTPUT_VARIABLE OPENCDA_DETECTED_TORCH_LIBRARY_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - COMMAND_ERROR_IS_FATAL ANY - ) - set( - OPENCDA_TORCH_RUNTIME_LIBRARY_DIR - "${OPENCDA_DETECTED_TORCH_LIBRARY_DIR}" - CACHE PATH - "Runtime directory containing the PyTorch shared libraries" - ) - find_library( - OPENCDA_TORCH_PYTHON_LIBRARY - NAMES torch_python - PATHS "${OPENCDA_DETECTED_TORCH_LIBRARY_DIR}" - NO_DEFAULT_PATH - REQUIRED - ) - - add_subdirectory(OpenCOOD/opencood/pcdet_utils) -endif() +add_subdirectory(opencda/core/common/communication) diff --git a/CMakePresets.json b/CMakePresets.json index 6aa31715..eeab2b4a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -3,10 +3,6 @@ { "configurePreset": "protobuf", "name": "protobuf" - }, - { - "configurePreset": "cuda", - "name": "cuda" } ], "cmakeMinimumRequired": { @@ -18,23 +14,11 @@ { "binaryDir": "${sourceDir}/build/protobuf", "cacheVariables": { - "OPENCDA_BUILD_CUDA": "OFF", "OPENCDA_BUILD_PROTOBUF": "ON" }, "displayName": "OpenCDA protobuf modules", "generator": "Ninja", "name": "protobuf" - }, - { - "binaryDir": "${sourceDir}/build/cuda", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release", - "OPENCDA_BUILD_CUDA": "ON", - "OPENCDA_BUILD_PROTOBUF": "OFF" - }, - "displayName": "OpenCOOD CUDA extensions", - "generator": "Ninja", - "name": "cuda" } ], "version": 6 diff --git a/Dockerfile b/Dockerfile index 8aea47ee..249771d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,8 @@ ENV HOME=${HOME} ENV PATH="/opt/venv/bin:${PATH}" ENV XDG_RUNTIME_DIR=/tmp/runtime-${USER} ENV OPENCDA_WORKSPACE=${HOME}/cavise/opencda +ENV OPENCOOD_WORKSPACE=${HOME}/cavise/opencood +ENV CAVISE_MODELS_ROOT=${HOME}/cavise/models RUN apt-get update && \ apt-get install -y --no-install-recommends \ @@ -39,7 +41,7 @@ RUN if id -u ubuntu >/dev/null 2>&1; then userdel -r ubuntu; fi && \ chown -R ${UID}:${UID} ${XDG_RUNTIME_DIR} ${HOME} -FROM runtime-base AS python-dependencies +FROM runtime-base AS python-opencda-dependencies RUN apt-get update && \ apt-get install -y --no-install-recommends python3-venv=3.12.* && \ @@ -57,6 +59,14 @@ RUN python3 -m venv /opt/venv && \ -r /tmp/opencda-requirements/requirements.txt +FROM python-opencda-dependencies AS python-opencood-dependencies + +COPY opencood/requirements.txt /tmp/opencood-requirements/requirements.txt + +RUN /opt/venv/bin/python -m pip install --no-cache-dir \ + -r /tmp/opencood-requirements/requirements.txt + + FROM ubuntu:${UBUNTU_VERSION}@${UBUNTU_DIGEST} AS protobuf-builder ARG PROTOC_VERSION=36.0 @@ -98,14 +108,13 @@ COPY opencda/opencda/core/common/communication/messages/ \ RUN cmake -S . -B /tmp/opencda-protobuf-build -G Ninja \ -DOPENCDA_BUILD_PROTOBUF=ON \ - -DOPENCDA_BUILD_CUDA=OFF \ && \ cmake --build /tmp/opencda-protobuf-build && \ cmake --install /tmp/opencda-protobuf-build \ --prefix /opt/opencda-artifacts/protobuf -FROM docker.io/nvidia/cuda:${CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION}@${CUDA_DEVEL_DIGEST} AS cuda-builder +FROM docker.io/nvidia/cuda:${CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION}@${CUDA_DEVEL_DIGEST} AS opencood-cuda-builder ARG CUDA_ARCHITECTURES=86 @@ -120,7 +129,7 @@ RUN apt-get update && \ && \ rm -rf /var/lib/apt/lists/* -COPY opencda/requirements-cuda.txt /tmp/requirements-cuda.txt +COPY opencood/requirements-cuda.txt /tmp/requirements-cuda.txt RUN python3 -m venv /opt/cuda-build-venv && \ python -m pip install --no-cache-dir \ @@ -128,22 +137,19 @@ RUN python3 -m venv /opt/cuda-build-venv && \ ninja==1.13.2 \ -r /tmp/requirements-cuda.txt -WORKDIR /src/opencda +WORKDIR /src/opencood -COPY opencda/CMakeLists.txt ./ -COPY opencda/OpenCOOD/opencood/pcdet_utils/ \ - OpenCOOD/opencood/pcdet_utils/ +COPY opencood/CMakeLists.txt ./ +COPY opencood/opencood/pcdet_utils/ opencood/pcdet_utils/ -RUN cmake -S . -B /tmp/opencda-cuda-build -G Ninja \ - -DOPENCDA_BUILD_PROTOBUF=OFF \ - -DOPENCDA_BUILD_CUDA=ON \ +RUN cmake -S . -B /tmp/opencood-cuda-build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CUDA_ARCHITECTURES=${CUDA_ARCHITECTURES} \ - -DOPENCDA_TORCH_RUNTIME_LIBRARY_DIR=/opt/venv/lib/python3.12/site-packages/torch/lib \ + -DOPENCOOD_TORCH_RUNTIME_LIBRARY_DIR=/opt/venv/lib/python3.12/site-packages/torch/lib \ && \ - cmake --build /tmp/opencda-cuda-build && \ - cmake --install /tmp/opencda-cuda-build \ - --prefix /opt/opencda-artifacts/cuda + cmake --build /tmp/opencood-cuda-build && \ + cmake --install /tmp/opencood-cuda-build \ + --prefix /opt/opencood-artifacts/cuda FROM runtime-base AS opencda-base @@ -152,13 +158,12 @@ ARG USER=opencda ARG UID=1000 ARG HOME=/home/${USER} -COPY --from=python-dependencies /opt/venv /opt/venv +COPY --from=python-opencda-dependencies /opt/venv /opt/venv WORKDIR ${HOME}/cavise/opencda COPY --chown=${UID}:${UID} opencda/opencda.py ./ COPY --chown=${UID}:${UID} opencda/opencda/ opencda/ -COPY --chown=${UID}:${UID} opencda/OpenCOOD/ OpenCOOD/ COPY --chown=${UID}:${UID} opencda/AIM/ AIM/ COPY opencda/docker/sync-build-artifacts.sh \ /usr/local/bin/opencda-sync-build-artifacts @@ -184,19 +189,47 @@ COPY --from=protobuf-builder /opt/opencda-artifacts/protobuf \ /opt/opencda-artifacts/protobuf -FROM opencda-base AS opencda-cuda +FROM opencda-base AS opencda-coperception-base + +ARG USER=opencda +ARG UID=1000 + +USER 0 + +RUN apt-get update && \ + apt-get install -y --no-install-recommends git=1:2.43.* && \ + rm -rf /var/lib/apt/lists/* + +COPY --from=python-opencood-dependencies /opt/venv /opt/venv +COPY --chown=${UID}:${UID} opencood/ ${OPENCOOD_WORKSPACE}/ +COPY opencood/docker/sync-cuda-artifacts.sh \ + /usr/local/bin/opencood-sync-cuda-artifacts + +RUN chmod 755 /usr/local/bin/opencood-sync-cuda-artifacts && \ + python -m pip install --no-cache-dir --no-build-isolation --no-deps \ + --editable ${OPENCOOD_WORKSPACE} + +USER ${USER} + +ENTRYPOINT ["/usr/local/bin/opencood-sync-cuda-artifacts", "/usr/local/bin/opencda-sync-build-artifacts"] -ENV OPENCDA_NATIVE_COMPONENTS="cuda" -COPY --from=cuda-builder /opt/opencda-artifacts/cuda \ - /opt/opencda-artifacts/cuda +FROM opencda-coperception-base AS opencda-coperception +ENV OPENCOOD_NATIVE_COMPONENTS="" -FROM opencda-base AS opencda -ENV OPENCDA_NATIVE_COMPONENTS="protobuf cuda" +FROM opencda-coperception-base AS opencda-cuda + +ENV OPENCOOD_NATIVE_COMPONENTS="cuda" + +COPY --from=opencood-cuda-builder /opt/opencood-artifacts/cuda \ + /opt/opencood-artifacts/cuda + + +FROM opencda-cuda AS opencda + +ENV OPENCDA_NATIVE_COMPONENTS="protobuf" COPY --from=protobuf-builder /opt/opencda-artifacts/protobuf \ /opt/opencda-artifacts/protobuf -COPY --from=cuda-builder /opt/opencda-artifacts/cuda \ - /opt/opencda-artifacts/cuda diff --git a/OpenCOOD/.devcontainer/ubuntu/Dockerfile b/OpenCOOD/.devcontainer/ubuntu/Dockerfile deleted file mode 100644 index 9c5afbe4..00000000 --- a/OpenCOOD/.devcontainer/ubuntu/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# TODO: Check why opencda mounts to devcontainer instead of just OpenCOOD -FROM docker.io/nvidia/cuda:12.8.1-cudnn-devel-ubuntu24.04@sha256:24c8e3581ea6330038b0d374920721983312627f8adbfcf390bdb4b399d280ed AS opencda -SHELL [ "/bin/bash", "-c"] -ARG USER=devcontainer -ARG UID=1000 -ARG HOME=/home/${USER} - -ENV TORCH_CUDA_ARCH_LIST="8.9" -ENV TORCH_NVCC_FLAGS="--allow-unsupported-compiler" -ENV PYTHONPATH="/workspaces/opencda/OpenCOOD" - -RUN userdel -r ubuntu && useradd -l -m -u ${UID} -s /bin/bash ${USER} -d ${HOME} - -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - python3-pip=24.0+* \ - python3-dev=3.12.* \ - libgl1=1.7.* \ - libglib2.0-0=2.80.* \ - libsm6=2:1.2.* \ - libxext6=2:1.3.* \ - libxrender1=1:0.9.* \ - && \ - apt-get autoremove -y && \ - rm -rf /var/lib/apt/lists/* - -RUN ln -s /usr/bin/python3 /usr/local/bin/python - -USER ${USER} -WORKDIR ${HOME} - -COPY requirements.txt requirements.txt - -RUN python3 -m pip install --no-cache-dir --break-system-packages --upgrade pip==26.0.1 && \ - python3 -m pip install --no-cache-dir --break-system-packages -r requirements.txt - -CMD ["/bin/bash"] diff --git a/OpenCOOD/.devcontainer/ubuntu/devcontainer.json b/OpenCOOD/.devcontainer/ubuntu/devcontainer.json deleted file mode 100644 index c5eb1c1a..00000000 --- a/OpenCOOD/.devcontainer/ubuntu/devcontainer.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "build": { - "args": { - "USER": "devcontainer" - }, - "context": "${localWorkspaceFolder}", - "dockerfile": "${localWorkspaceFolder}/.devcontainer/ubuntu/Dockerfile" - }, - "containerUser": "devcontainer", - "name": "OpenCOOD DevContainer", - "runArgs": [ - "--shm-size=8g" - ] -} diff --git a/OpenCOOD/.gitignore b/OpenCOOD/.gitignore deleted file mode 100644 index 0ab0c974..00000000 --- a/OpenCOOD/.gitignore +++ /dev/null @@ -1,140 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -.DS_Store - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ -logs/ -*.c -*.so -.idea - -opv2v_data_dumping -checkpoints -DCOS.md -spconv/ diff --git a/OpenCOOD/.readthedocs.yaml b/OpenCOOD/.readthedocs.yaml deleted file mode 100644 index 7a3b5af8..00000000 --- a/OpenCOOD/.readthedocs.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py diff --git a/OpenCOOD/LICENSE b/OpenCOOD/LICENSE deleted file mode 100644 index 758e8d95..00000000 --- a/OpenCOOD/LICENSE +++ /dev/null @@ -1,10 +0,0 @@ - -Academic Software License: © 2021 UCLA Mobility Lab (“Institution”). Academic or nonprofit researchers are permitted to use this Software (as defined below) subject to Paragraphs 1-3: - -Institution hereby grants to you free of charge, so long as you are an academic or nonprofit researcher, a nonexclusive license under Institution’s copyright ownership interest in this software and any derivative works made by you thereof (collectively, the “Software”) to use, copy, and make derivative works of the Software solely for educational or academic research purposes, in all cases subject to the terms of this Academic Software License. Except as granted herein, all rights are reserved by Institution, including the right to pursue patent protection of the Software. - -Please note you are prohibited from further transferring the Software -- including any derivatives you make thereof -- to any person or entity. Failure by you to adhere to the requirements in Paragraphs 1 and 2 will result in immediate termination of the license granted to you pursuant to this Academic Software License effective as of the date you first used the Software. - -IN NO EVENT SHALL INSTITUTION BE LIABLE TO ANY ENTITY OR PERSON FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE, EVEN IF INSTITUTION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. INSTITUTION SPECIFICALLY DISCLAIMS ANY AND ALL WARRANTIES, EXPRESS AND IMPLIED, INCLUDING, BUT NOT LIMITED TO, ANY IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE IS PROVIDED “AS IS.” INSTITUTION HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS OF THIS SOFTWARE. - -Commercial entities: please contact the UCLA Mobility Lab at jiaqima@ucla.edu for licensing opportunities. diff --git a/OpenCOOD/README.md b/OpenCOOD/README.md deleted file mode 100644 index 66e89fa7..00000000 --- a/OpenCOOD/README.md +++ /dev/null @@ -1,277 +0,0 @@ -
- -
 
- -
 
-
- -[![paper](https://img.shields.io/badge/arXiv-Paper-.svg)](https://arxiv.org/pdf/2109.07644.pdf) -[![Documentation Status](https://readthedocs.org/projects/opencood/badge/?version=latest)](https://opencood.readthedocs.io/en/latest/?badge=latest) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - -OpenCOOD is an Open COOperative Detection framework for autonomous driving. It is also the official implementation of the ICRA 2022 -paper [OPV2V.](https://arxiv.org/abs/2109.07644) - -

- - -

- -## Contributing: -We use Ruff as a linter and formatter. Before creating a pull request, check for linter warnings and then run the formatter. -Linter: -```bash -ruff check -``` -Formatter: -```bash -ruff format -``` - -## News: -- 12/28/2022: OpenCOOD now support multi-gpu training. -- 12/21/2022: V2XSet (ECCV2022) is supported by OpenCOOD now! -- 12/16/2022: Both spconv 1.2.1 and spconv 2.x are supported! -- 12/04/2022: The log replay tool for OPV2V is online now! With -this toolbox, you can 100% replay all the events in the offline dataset and add/change any sensors/groundtruth you -want to explore the tasks that the origin dataset do not support. Check [here](logreplay/README.md) to see more details. -- 09/15/2022: So far OpenCOOD has supported several top conference papers, including ECCV,ICRA,CoRL,NeurIPS,WACV! The bottom of this project page lists the detailed information. -## Features -- Provide easy data API for multiple popular multi-agent perception dataset: - - [x] [OPV2V [ICRA2022]](https://mobility-lab.seas.ucla.edu/opv2v/) - - [x] [V2XSet [ECCV2022]](https://arxiv.org/pdf/2203.10638.pdf) - - [ ] [DAIR-V2X [CVPR2022]](https://arxiv.org/abs/2204.05575) - - [ ] [V2V4Real [CVPR2023 Highlight]](https://arxiv.org/abs/2303.07601) -- Provide APIs to allow users use different sensor modalities - - [x] LiDAR APIs - - [ ] Camera APIs - - [ ] Radar APIs -- Provide multiple SOTA 3D detection backbone: - - [X] [PointPillar](https://arxiv.org/abs/1812.05784) - - [X] [Pixor](https://arxiv.org/abs/1902.06326) - - [X] [VoxelNet](https://arxiv.org/abs/1711.06396) - - [X] [SECOND](https://www.mdpi.com/1424-8220/18/10/3337) -- Support multiple sparse convolution versions - - [X] Spconv 1.2.1 - - [X] Spconv 2.x -- Support SOTA multi-agent perception models: - - [x] [Attentive Fusion [ICRA2022]](https://arxiv.org/abs/2109.07644) - - [x] [Cooper [ICDCS]](https://arxiv.org/abs/1905.05265) - - [x] [F-Cooper [SEC2019]](https://arxiv.org/abs/1909.06459) - - [x] [V2VNet [ECCV2022]](https://arxiv.org/abs/2008.07519) - - [x] [CoAlign (fusion only) [ICRA2023]](https://arxiv.org/abs/2211.07214) - - [x] [FPV-RCNN [RAL2022]](https://arxiv.org/pdf/2109.11615.pdf) - - [ ] [DiscoNet [NeurIPS2021]](https://arxiv.org/abs/2111.00643) - - [x] [V2X-ViT [ECCV2022]](https://github.com/DerrickXuNu/v2x-vit) - - [x] [CoBEVT [CoRL2022]](https://arxiv.org/abs/2207.02202) - - [ ] [AdaFusion [WACV2023]](https://arxiv.org/abs/2208.00116) - - [x] [Where2comm [NeurIPS2022]](https://arxiv.org/abs/2209.12836) - - [x] [V2VAM [TIV2023]](https://arxiv.org/abs/2212.08273) - -- **Provide a convenient log replay toolbox for OPV2V dataset.** Check [here](logreplay/README.md) to see more details. - -## Data Downloading -All the data can be downloaded from [UCLA BOX](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V). If you have a good internet, you can directly -download the complete large zip file such as `train.zip`. In case you suffer from downloading large files, we also split each data set into small chunks, which can be found -in the directory ending with `_chunks`, such as `train_chunks`. After downloading, please run the following command to each set to merge those chunks together: -```python -cat train.zip.part* > train.zip -unzip train.zip -``` - -## Installation -Please refer to [data introduction](https://opencood.readthedocs.io/en/latest/md_files/data_intro.html) -and [installation](https://opencood.readthedocs.io/en/latest/md_files/installation.html) guide to prepare -data and install OpenCOOD. To see more details of OPV2V data, please check [our website.](https://mobility-lab.seas.ucla.edu/opv2v/) - -## Quick Start -### Data sequence visualization -To quickly visualize the LiDAR stream in the OPV2V dataset, first modify the `validate_dir` -in your `opencood/hypes_yaml/visualization.yaml` to the opv2v data path on your local machine, e.g. `opv2v/validate`, -and the run the following commond: -```python -cd ~/OpenCOOD -python opencood/visualization/vis_data_sequence.py [--color_mode ${COLOR_RENDERING_MODE}] -``` -Arguments Explanation: -- `color_mode` : str type, indicating the lidar color rendering mode. You can choose from 'constant', 'intensity' or 'z-value'. - - -### Train your model -OpenCOOD uses yaml file to configure all the parameters for training. To train your own model -from scratch or a continued checkpoint, run the following commonds: -```python -python opencood/tools/train.py --hypes_yaml ${CONFIG_FILE} [--model_dir ${CHECKPOINT_FOLDER} --half] -``` -Arguments Explanation: -- `hypes_yaml`: the path of the training configuration file, e.g. `opencood/hypes_yaml/second_early_fusion.yaml`, meaning you want to train -an early fusion model which utilizes SECOND as the backbone. See [Tutorial 1: Config System](https://opencood.readthedocs.io/en/latest/md_files/config_tutorial.html) to learn more about the rules of the yaml files. -- `model_dir` (optional) : the path of the checkpoints. This is used to fine-tune the trained models. When the `model_dir` is -given, the trainer will discard the `hypes_yaml` and load the `config.yaml` in the checkpoint folder. -- `half` (optional): If set, the model will be trained with half precision. It cannot be set with multi-gpu training togetger. - -To train on **multiple gpus**, run the following command: -``` -CUDA_VISIBLE_DEVICES=0,1,2,3 python -m torch.distributed.launch --nproc_per_node=4 --use_env opencood/tools/train.py --hypes_yaml ${CONFIG_FILE} [--model_dir ${CHECKPOINT_FOLDER}] -``` - - -### Test the model -Before you run the following command, first make sure the `validation_dir` in config.yaml under your checkpoint folder -refers to the testing dataset path, e.g. `opv2v_data_dumping/test`. - -```python -python opencood/tools/inference.py --model_dir ${CHECKPOINT_FOLDER} --fusion_method ${FUSION_STRATEGY} [--show_vis] [--show_video_vis] -``` -Arguments Explanation: -- `model_dir`: the path to your saved model. -- `fusion_method`: indicate the fusion strategy, currently support 'early', 'late', and 'intermediate'. -- `show_vis`: whether to visualize the detection overlay with point cloud. -- `show_video_vis` : the detection results will visualized in a video stream. It can NOT be set with `show_vis` at the same time. -- `global_sort_detections`: whether to globally sort detections by confidence score. If set to True, it is the mainstream AP computing method, but would increase the tolerance for FP (False Positives). **OPV2V paper does not perform the global sort.** Please choose the consistent AP calculation method in your paper for fair comparison. - -The evaluation results will be dumped in the model directory. - -## Benchmark and model zoo -### Results on OPV2V LiDAR-track (AP@0.7 for no-compression/ compression) - -| | Spconv Version| Backbone | Fusion Strategy | Bandwidth (Megabit),
before/after compression| Default Towns |Culver City| Download | -|--------------------| -------|-------- | --------------- | --------------- | ------------- |-----------| -------- | -| Naive Late | 1.2.1 | PointPillar | Late | **0.024**/**0.024** | 0.781/0.781 | 0.668/0.668 | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621128604521) | -| [Cooper](https://arxiv.org/abs/1905.05265) | 1.2.1 | PointPillar | Early | 7.68/7.68 | 0.800/x | 0.696/x | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621122534978) | -| [Attentive Fusion](https://arxiv.org/abs/2109.07644) | 1.2.1 | PointPillar | Intermediate | 126.8/1.98 | 0.815/0.810 | 0.735/0.731 | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621110356814) | -| [F-Cooper](https://arxiv.org/abs/1909.06459) | 1.2.1 | PointPillar | Intermediate | 72.08/1.12 | 0.790/0.788 | 0.728/0.726 | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621123814293) | -| [V2VNet](https://arxiv.org/abs/2008.07519) | 1.2.1 | PointPillar | Intermediate | 72.08/1.12 | 0.822/0.814 | 0.734/0.729 | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621111444798) | -| [CoAlign](https://arxiv.org/abs/2211.07214) | 1.2.1 | PointPillar | Intermediate | 72.08/2.24 | **0.833**/0.806 | 0.760/ **0.750** | [url](https://drive.google.com/file/d/1mUEI_Dh4tkG6-LG3QcZ05kK7oOGJzCGK/view?usp=sharing) -| [FPV-RCNN](https://arxiv.org/abs/2109.11615) | 1.2.1 | PV-RCNN | Intermediate(2 stage) | 0.24/0.24 | 0.820/0.820 | 0.763/**0.763** | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621109586667) | -| [V2VAM](https://arxiv.org/pdf/2212.08273.pdf) | 1.2.1 | PointPillar | Intermediate | x/x | **0.860**/**0.860** | **0.813**/**0.791** | [url](https://drive.google.com/drive/folders/1gFY6Aedrbi3vLJdPZ3x9abPnfW5w_HKN?usp=sharing) | -| [CoBEVT]( https://arxiv.org/pdf/2207.02202.pdf) | 2.0 | PointPillar | Intermediate | 72.08/1.12 | **0.861**/**0.836** | **0.773**/0.730 | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/folder/280139625059) | -| Naive Late | 1.2.1 | VoxelNet | Late | **0.024**/**0.024** | 0.738/0.738 | 0.588/0.588 | [url]() | -| Cooper | 1.2.1 | VoxelNet | Early | 7.68/7.68 | 0.758/x | 0.677/x | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621124922131) | -| Attentive Fusion | 1.2.1 | VoxelNet | Intermediate | 576.71/1.12 | **0.864**/**0.852** | **0.775**/**0.746** | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621111451952) | -| Naive Late | 1.2.1 | SECOND | Late | **0.024**/**0.024** | 0.775/0.775 |0.682/0.682 | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621113752957) | -| Cooper | 1.2.1 | SECOND | Early | 7.68/7.68 | 0.813/x | 0.738/x | [url](https://drive.google.com/file/d/1Z9io1VNcU-urcRW8l0ogWCTVCB53mw4N/view?usp=sharing) | -| Attentive | 1.2.1 | SECOND | Intermediate | 63.4/0.99 | **0.826**/**0.783** | **0.760**/**0.760** | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621121166914) | -| Naive Late | 1.2.1 | PIXOR | Late | **0.024**/**0.024** | 0.578/0.578 | 0.360/0.360 | [url]() | -| Cooper | 1.2.1 | PIXOR | Early | 7.68/7.68 | 0.678/x | **0.558**/x | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621127209949) | -| Attentive | 1.2.1 | PIXOR | Intermediate | 313.75/1.22 | **0.687**/**0.612** | 0.546/**0.492** | [url]() | - -**Note**: -* We suggest using **PointPillar** as the backbone when you are creating your method and try to compare with -our benchmark, as we implement most of the SOTA methods with this backbone only. -* We assume the transimssion rate is 27Mbp/s. Considering the frequency of LiDAR is 10Hz, the -bandwidth requirement should be less than **2.7Mbp** to avoid severe delay. -* A 'x' in the benchmark table represents the bandwidth requirement is too large, which -can not be considered to employ in practice. - - -### Results of BEV semantic segmentation on OPV2V camera-track (IoU) - -| | Backbone | Fusion Strategy | Vehicles| Road Surface |Lane| Download | -|--------------------| -------- | --------------- | --------------- | ------------- |-----------| -------- | -| No Fusion | CVT | No Fusion | 37.7 | 57.8 | 43.7 | [None]() | -| Map Fusion | CVT | Late | 45.1 | 60.0 | 44.1 | [None]() | -| [Attentive Fusion](https://arxiv.org/abs/2109.07644) | CVT | Intermediate | 51.9 |60.5 | 46.2 | [None]() | -| [F-Cooper](https://arxiv.org/abs/1909.06459) | CVT | Intermediate |52.5 | 60.4 | 46.5 | [None]() | -| [V2VNet](https://arxiv.org/abs/2008.07519) | CVT | Intermediate | 53.5 | 60.2 | 47.5 | [None]() | -| [DiscoNet](https://arxiv.org/abs/2109.11615) | CVT | Intermediate | 52.9 | 60.7 | 45.8 | [None]() | -| [FuseBEVT](https://arxiv.org/pdf/2207.02202.pdf) | CVT | Intermediate | 59.0 | 62.1 | 49.2 | [url]() | -| [CoBEVT](https://arxiv.org/pdf/2207.02202.pdf) | SinBEVT | Intermediate | **60.4** | **63.0** | **53.0** | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/folder/280139287054) | - -**Note**: -To play with OPV2V camera data, please check here: https://github.com/DerrickXuNu/CoBEVT - -### Results of 3D Detection on V2XSet LiDAR-Track -| Method | Spconv Version | Backbone | Perfect AP@0.5 | Perfect AP@0.7 | Noisy AP@0.5 | Noisy AP@0.7 | Download Link | -|--------------|----------------|-------------|----------------|----------------|--------------|--------------|--------------------------------------------------------------------------| -| No Fusion | 2.0 | PointPillar | 60.6 | 40.2 | 60.6 | 40.2 | | -| Late Fusion | 2.0 | PointPillar | 72.7 | 62.0 | 54.9 | 30.7 | | -| Early Fusion | 2.0 | PointPillar | 81.9 | 71.0 | 72.0 | 38.4 | | -| [F-Cooper](https://arxiv.org/abs/1909.06459) | 2.0 | PointPillar | 84.0 | 68.0 | 71.5 | 46.9 | | -| [Attentive Fusion](https://arxiv.org/abs/2109.07644) | 2.0 | PointPillar | 80.7 | 66.4 | 70.9 | 48.7 | | -| [V2VNet](https://arxiv.org/abs/2008.07519) | 2.0 | PointPillar | 84.5 | 67.7 | 79.1 | 49.3 | | -| [DiscoNet](https://arxiv.org/abs/2109.11615) | 2.0 | PointPillar | 84.4 | 69.5 | 79.8 | 54.1 | -| [CoBEVT](https://arxiv.org/abs/2207.02202) | 2.0 | PointPillar | 84.9 | 66.0 | 81.1 | 54.3 | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-V2XVIT/file/1622754822466) || -| [Where2Comm](https://arxiv.org/abs/2209.12836) | 2.0 | PointPillar | 85.5 | 65.4 | 82.0| 53.4 | [URL](https://ucla.app.box.com/v/UCLA-MobilityLab-OPV2V/file/1621122986293) || -| [V2X-ViT](https://arxiv.org/pdf/2203.10638.pdf) | 2.0 | PointPillar | **88.2** | **71.2** | **83.6** | **61.4** | [url](https://ucla.app.box.com/v/UCLA-MobilityLab-V2XVIT/folder/280421221519) | - -Important Notes for Training in V2XSet: -1. When you train from scratch, please first set `async` and `loc_err` to false to train on perfect setting. Also, set `compression` to 0 at beginning. -2. After the model on perfect setting converged, set `compression` to 32 (please change the config yaml in your trained model directory) and continue training on the perfect setting for another 1-2 epoches. -3. Next, set `async` to true, `async_mode` to 'real', `async_overhead` to 200 or 300, `loc_err` to true, `xyz_std` to 0.2, `rpy_std` to 0.2, and then continue training your model on this noisy setting. Please note that you are free to change these noise setting during training to obtain better performance. -4. Eventually, use the model fine-tuned on noisy setting as the test model for both perfect and noisy setting. - -## Tutorials -We have a series of tutorials to help you understand OpenCOOD more. Please check the series of our [tutorials](https://opencood.readthedocs.io/en/latest/md_files/config_tutorial.html). - - -## Citation - If you are using our OpenCOOD framework or OPV2V dataset for your research, please cite the following paper: - ```bibtex -@inproceedings{xu2022opencood, - author = {Runsheng Xu, Hao Xiang, Xin Xia, Xu Han, Jinlong Li, Jiaqi Ma}, - title = {OPV2V: An Open Benchmark Dataset and Fusion Pipeline for Perception with Vehicle-to-Vehicle Communication}, - booktitle = {2022 IEEE International Conference on Robotics and Automation (ICRA)}, - year = {2022}} -``` - -## Supported Projects -OpenCOOD has supported several top conference papers in cooperative perception field. - -**V2V4Real: A large-scale real-world dataset for Vehicle-to-Vehicle Cooperative Perception**
- Runsheng Xu, Xin Xia, Jinlong Li, Hanzhao Li, Shuo Zhang, Zhengzhong Tu, Zonglin Meng, Hao Xiang, Xiaoyu Dong, Rui Song, Hongkai Yu, Bolei Zhou, Jiaqi Ma
-*CVPR 2023*
-[[Paper]]()[[Code]](https://github.com/ucla-mobility/V2V4Real) - -**Robust Collaborative 3D Object Detection in Presence of Pose Errors**
-Yifan Lu, Quanhao Li, Baoan Liu, Mehrdad Dianati, Chen Feng, Siheng Chen, Yanfeng Wang
-*ICRA 2023*
-[[Paper]](https://arxiv.org/abs/2211.07214)[[Code]](https://github.com/yifanlu0227/CoAlign) - - -**Analyzing Infrastructure LiDAR Placement with Realistic LiDAR Simulation Library**
-Xinyu Cai, Wentao Jiang, Runsheng Xu, Wenquan Zhao, Jiaqi Ma, Si Liu, Yikang Li
-*ICRA 2023*
-[[Paper]](https://arxiv.org/abs/2211.15975)[[Code]](https://github.com/PJLab-ADG/LiDARSimLib-and-Placement-Evaluation) - -**Bridging the Domain Gap for Multi-Agent Perception**
-Runsheng Xu, Jinlong Li, Xiaoyu Dong, Hongkai Yu, Jiaqi Ma∗
-*ICRA 2023*
-[[Paper]](https://arxiv.org/pdf/2210.08451.pdf)[[Code]](https://github.com/DerrickXuNu/MPDA) - -**Model Agnostic Multi-agent Perception**
-Runsheng Xu, Weizhe Chen, Hao Xiang, Xin Xia, Lantao Liu, Jiaqi Ma∗
-*ICRA 2023*
-[[Paper]](https://arxiv.org/abs/2203.13168)[[Code]](https://github.com/DerrickXuNu/model_anostic) - -**Learning for Vehicle-to-Vehicle Cooperative Perception under Lossy Communication**
-Jinlong Li, Runsheng Xu, Xinyu Liu, Jin Ma, Zicheng Chi, Jiaqi Ma, Hongkai Yu
-*TIV 2023*
-[[Paper]](https://ieeexplore.ieee.org/document/10077757) [[Code]](https://github.com/jinlong17/V2VLC) - - -**Where2comm: Communication-Efficient Collaborative Perception via Spatial Confidence Maps**
-Yue Hu, Shaoheng Fang, Zixing Lei, Yiqi Zhong, Siheng Chen
-*Neurips 2022*
-[[Paper]](https://arxiv.org/abs/2209.12836) [[Code]](https://github.774.gs/MediaBrain-SJTU/Where2comm) - -**Adaptive Feature Fusion for Cooperative Perception using LiDAR Point Clouds**
-Donghao Qiao, Farhana Zulkernine
-*WACV 2023*
-[[Paper]](https://arxiv.org/abs/2208.00116) - - -**CoBEVT: Cooperative Bird's Eye View Semantic Segmentation with Sparse Transformers**
-Runsheng Xu*, Zhengzhong Tu*, Hao Xiang, Wei Shao, Bolei Zhou, Jiaqi Ma
-*CoRL2022*
-[[Paper]](https://arxiv.org/abs/2207.02202) [[Code]](https://github.com/DerrickXuNu/CoBEVT) - -**V2X-ViT: Vehicle-to-Everything Cooperative Perception with Vision Transformer**
-Runsheng Xu*, Hao Xiang*, Zhengzhong Tu*, Xin Xia, Ming-Hsuan Yang, Jiaqi Ma
-*ECCV2022*
-[[Paper]](https://arxiv.org/abs/2203.10638) [[Code]](https://github.com/DerrickXuNu/v2x-vit) [[Talk]](https://course.zhidx.com/c/MmQ1YWUyMzM1M2I3YzVlZjE1NzM=) - -**OPV2V: An Open Benchmark Dataset and Fusion Pipeline for Perception with Vehicle-to-Vehicle Communication**
-Runsheng Xu*, Hao Xiang*, Xin Xia, Jinlong Li, Jiaqi Ma
-*ICRA2022*
-[[Paper]](https://arxiv.org/abs/2109.07644) [[Website]](https://mobility-lab.seas.ucla.edu/opv2v/) [[Code]](https://github.com/DerrickXuNu/OpenCOOD) diff --git a/OpenCOOD/docs/Makefile b/OpenCOOD/docs/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/OpenCOOD/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/OpenCOOD/docs/conf.py b/OpenCOOD/docs/conf.py deleted file mode 100644 index 5401b2c4..00000000 --- a/OpenCOOD/docs/conf.py +++ /dev/null @@ -1,66 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - -# sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath("./../")) - - -# -- Project information ----------------------------------------------------- - -project = "OpenCOOD" -copyright = "2021, UCLA Mobility Lab" -author = "Runsheng Xu, Hao Xiang, Jiaqi Ma" - -# The full version, including alpha/beta/rc tags -release = "0.1" - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.napoleon", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.coverage", - "sphinx.ext.mathjax", - "recommonmark", -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - -todo_include_todos = False - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] diff --git a/OpenCOOD/docs/index.rst b/OpenCOOD/docs/index.rst deleted file mode 100644 index 64781a14..00000000 --- a/OpenCOOD/docs/index.rst +++ /dev/null @@ -1,57 +0,0 @@ -.. OpenCDA documentation master file, created by - sphinx-quickstart on Fri Jul 2 11:48:53 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to OpenCOOD's documentation! -=================================== -OpenCOOD is an open-source cooperative detection framework for autonomous driving. It provides SOTA cooperative detection algorithms, -convenient APIs for the large-scale simulated V2V perception dataset `OPV2V `_, and a set of useful tools for log replay. - -In collaboration with `OpenCDA `_ , OpenCOOD is mainly focus on offline cooperative perception training and testing. If you are -interested in online cooperative perception and the corresponding closed-loop simulation test, OpenCDA will be the best tool. - -OpenCOOD is a work in progress. Many features on the roadmap are being continuously developed. We welcome your contribution and please visit our Github repo -for the latest release. - - `OpenCOOD source code on Github `_ - -.. toctree:: - :maxdepth: 1 - :caption: Getting Started - - md_files/data_intro.md - md_files/installation.md - -.. toctree:: - :maxdepth: 1 - :caption: Tutorials - - md_files/config_tutorial.md - md_files/data_annotation_tutorial.md - md_files/logic_flow.md - -.. toctree:: - :maxdepth: 1 - :caption: Additional Information - - md_files/contributor.md - -**Citing OpenCOOD**\ : - -If you are using our OpenCOOD framework or codes for your development, please cite the following paper:: - - @inproceedings{xu2022opencood, - author = {Runsheng Xu, Hao Xiang, Xin Xia, Xu Han, Jinlong Li, Jiaqi Ma}, - title = {OPV2V: An Open Benchmark Dataset and Fusion Pipeline for Perception with Vehicle-to-Vehicle Communication}, - booktitle = {2022 IEEE International Conference on Robotics and Automation (ICRA)}, - year = {2022}} - -Also, under this LICENSE, OpenCOOD is for non-commercial research only. Researchers can modify the source code for their own research only. - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/OpenCOOD/docs/make.bat b/OpenCOOD/docs/make.bat deleted file mode 100644 index 2119f510..00000000 --- a/OpenCOOD/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/OpenCOOD/docs/md_files/config_tutorial.md b/OpenCOOD/docs/md_files/config_tutorial.md deleted file mode 100644 index 3071201d..00000000 --- a/OpenCOOD/docs/md_files/config_tutorial.md +++ /dev/null @@ -1,139 +0,0 @@ -## Tutorial 1: Learn about Config System - ---- -We incorporate modular and inheritance design into the config system to enable users conveniently -modify the model/training/inference parameters. Specifically, we use **yaml** files to configure all the -important parameters. - -### Config File Location -All the yaml files should be saved in `opencood/hypes_yaml`, and users should use the `load_yaml()` function in [`opencood/hypes_yaml/yaml_utils.py`](https://github.com/DerrickXuNu/OpenCOOD/blob/31ba16025da27ffe4e336f011290dfbc66f9a1f1/opencood/hypes_yaml/yaml_utils.py#L14) to load the parameters into a dictionary. - -### Config Name Style -We follow the below style to name config yaml files. -```python -{backbone}_{fusion_strategy}.yaml -``` - -### A concrete example -Now let's go through the `point_pillar_intermediate_fusion.yaml` as an example. -```yaml -name: point_pillar_intermediate_fusion # this parameter together with the current timestamp will define the name of the saved folder for the model. -root_dir: "v2xset/train" # this is where the training data locate. It can be either opv2v/train or v2xset/train -validate_dir: "v2xset/validate" # during training, it defines the validation folder. during testing, it defines the testing folder path. - -yaml_parser: "load_point_pillar_params" # we need specific loading functions for different backbones. -train_params: # the common training parameters - batch_size: &batch_size 2 - epoches: 60 - eval_freq: 1 - save_freq: 1 - -wild_setting: # setting related to noise - async: true - async_mode: 'sim' - async_overhead: 100 - backbone_delay: 10 - data_size: 1.06 - loc_err: true - ryp_std: 0.2 - seed: 25 - transmission_speed: 27 - xyz_std: 0.2 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, and IntermediateFusionDataset are supported - args: - cur_ego_pose_flag: True - # when the cur_ego_pose_flag is set to True, there is no time gap - # between the time when the LiDAR data is captured by connected - # agents and when the extracted features are received by - # the ego vehicle, which is equal to implement STCM. When set to False, - # STCM has to be used. To validate STCM, V2X-ViT will set this as False. - -# preprocess-related -preprocess: - # options: BasePreprocessor, SpVoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] # the voxel resolution for PointPillar - max_points_per_voxel: 32 # maximum points allowed in each voxel - max_voxel_train: 32000 # the maximum voxel number during training - max_voxel_test: 70000 # the maximum voxel number during testing - # LiDAR point cloud cropping range - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -# data augmentation options. -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# post processing related. -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor and BevPostprocessor are supported - anchor_args: # anchor generator parameters - cav_lidar_range: *cav_lidar # the range is consistent with the lidar cropping range to generate the correct ancrhors - l: 3.9 # the default length of the anchor - w: 1.6 # the default width - h: 1.56 # the default height - r: [0, 90] # the yaw angles. 0, 90 meaning for each voxel, two anchors will be generated with 0 and 90 degree yaw angle - feature_stride: 2 # the feature map is shrank twice compared the input voxel tensor - num: &achor_num 2 # for each location in the feature map, 2 anchors will be generated - target_args: # used to generate positive and negative samples for object detection - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames have the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_opv2v # trainer will load the corresponding model python file with the same name - args: # detailed parameters of the point pillar model - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - compression: 0 # whether to compress the features before fusion to reduce the bandwidth - backbone_fix: false # whether fix the pointpillar backbone weights during training. - anchor_num: *achor_num - -loss: # loss function - core_method: point_pillar_loss # trainer will load the loss function with the same name - args: - cls_weight: 1.0 # classification weights - reg: 2.0 # regression weights - -optimizer: # optimzer setup - core_method: Adam # the name has to exist in Pytorch optimizer library - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: # learning rate schedular - core_method: multistep #step, multistep and Exponential are supported - gamma: 0.1 - step_size: [15, 30] - -``` diff --git a/OpenCOOD/docs/md_files/contributor.md b/OpenCOOD/docs/md_files/contributor.md deleted file mode 100644 index 210915c8..00000000 --- a/OpenCOOD/docs/md_files/contributor.md +++ /dev/null @@ -1,18 +0,0 @@ -## About Us -OpenCOOD is brought to you by [UCLA Mobility Lab](https://mobility-lab.seas.ucla.edu/). - -### Supervisor -Dr. Jiaqi Ma (Associate Professor @ UCLA) -- Lab Principal Investigator -- [Linkedin](https://www.linkedin.com/in/jiaqi-ma-17037838/) -- [Google Scholar](https://scholar.google.com/citations?user=S3cQz1AAAAAJ&hl=en) - - -### Core Developer -Runsheng Xu (Ph.D. Student @ UCLA): -- [Homepage](https://derrickxunu.github.io/) -- [Linkedin](https://www.linkedin.com/in/runsheng-xu/) - -Hao Xiang (Ph.D. Student @ UCLA): -- [Homepage](https://xhwind.github.io/) -- [Linkedin](https://www.linkedin.com/in/hao-xiang-42bb5a1b2/) diff --git a/OpenCOOD/docs/md_files/data_annotation_tutorial.md b/OpenCOOD/docs/md_files/data_annotation_tutorial.md deleted file mode 100644 index 1e021963..00000000 --- a/OpenCOOD/docs/md_files/data_annotation_tutorial.md +++ /dev/null @@ -1,93 +0,0 @@ -## Tutorial 2: Data Annotation Introduction - ---- -We save all groundtruth annotations per agent per timestamp in the yaml files. For instance, -`2021_08_24_21_29_28/4805/000069.yaml` refers to the data annotations with the perspective of te -agent 4805 at timestamp 69 in the scenario database `2021_08_24_21_29_28`. Here we go through an example: - -```yaml -camera0: # parameters for frontal camera - cords: # the x,y,z,roll,yaw,pitch under CARLA map coordinate - - 141.35067749023438 - - -388.642578125 - - 1.0410505533218384 - - 0.07589337974786758 - - 174.18048095703125 - - 0.20690691471099854 - extrinsic: # extrinsic matrix from camera to LiDAR - - - 0.9999999999999999 - - -5.1230071481984265e-18 - - 9.322129061605055e-20 - - -2.999993025731527 - - - -2.5011383190939924e-18 - - 1.0 - - 1.1458579204685086e-19 - - -3.934422863949294e-06 - - - 2.7713237218713775e-20 - - 3.7310309839064755e-20 - - 1.0 - - 0.8999999040861146 - - - 0.0 - - 0.0 - - 0.0 - - 1.0 - intrinsic: # camera intrinsic matrix - - - 335.639852470912 - - 0.0 - - 400.0 - - - 0.0 - - 335.639852470912 - - 300.0 - - - 0.0 - - 0.0 - - 1.0 -camera1: ... # params of right rear camera -camera2: ... # params of left rear camera -canera3: ... # params of back camera -ego_speed: 18.13 # agent's current speed, km/h -lidar_pose: # LiDAR pose under CARLA map coordinate system -- 144.33 -- -388.94 -- 1.93 -- 0.078 -- 174.18 -- 0.21 -plan_trajectory: # agent's planning trajectory -- - 140. - - -388 - - 87 -predicted_ego_pos: # agent's localization (x,y,z,roll,yaw,pitch) gained from GPS -- 143.78 -- -388.94 -- 0.036 -- 0.080 -- -185.95 -- 0.18 -true_ego_pos: # agent's true localization -- 143.83 -- -388.89 -- 0.032 -- 0.075 -- 174.18 -- 0.21 -vehicles: # the surrounding vehicles that have at least one LiDAR point hit from the agent - 4796: # the id of the vehicle (i.e. object) - angle: # roll, yaw, pitch under CARLA map coordinate system - - 0.096 - - -177.86 - - 0.197 - center: # the relative position from bounding box center to the frontal axis of this vehicle - - 0.0004 - - 0.0005 - - 0.71 - extent: # half length, width and height of the vehicle in meter - - 2.45 - - 1.06 - - 0.75 - location: # x, y ,z position of the center in the frontal axis of the vehicle under CARLA map coordinate system - - 158.55 - - -385.75 - - 0.032 - speed: 19.47 # vehicle's speed - 4880: ... -``` diff --git a/OpenCOOD/docs/md_files/data_intro.md b/OpenCOOD/docs/md_files/data_intro.md deleted file mode 100644 index ca333eea..00000000 --- a/OpenCOOD/docs/md_files/data_intro.md +++ /dev/null @@ -1,143 +0,0 @@ -# Data Preparation - ---- -OpenCOOD support both **OPV2V (ICRA2022)** and **V2XSet (ECCV2022)** dataset. - -## OPV2V -All the data can be downloaded from [google drive](https://drive.google.com/drive/folders/1dkDeHlwOVbmgXcDazZvO6TFEZ6V_7WUu). If you have a good internet, you can directly -download the complete large zip file such as `train.zip`. In case you suffer from downloading large fiels, we also split each data set into small chunks, which can be found -in the directory ending with `_chunks`, such as `train_chunks`. After downloading, please run the following command to each set to merge those chunks together: -```python -cat train.zip.part* > train.zip -unzip train.zip -``` -After downloading is finished, please make the file structured as following: - -```sh -OpenCOOD # root of your OpenCOOD -├── opv2v_data_dumping # the downloaded opv2v data -│ ├── train -│ ├── validate -│ ├── test -│ ├── test_culvercity -├── opencood # the core codebase - -``` - - -OPV2V data is structured as following: - -```sh -opv2v_data_dumping -├── train # data for training -│ ├── 2021_08_22_21_41_24 # scenario folder -│ ├── data_protocol.yaml # the simulation parameters used to collect the data in Carla -│ └── 1732 # The connected automated vehicle's id -│ └── 00000.pcd - 00700.pcd # the point clouds data from timestamp 0 to 700 -│ ├── 00000.yaml - 00700.yaml # corresponding metadata for each timestamp -│ ├── 00000_camera0.png - 00700_camera0.png # frontal camera images -│ ├── 00000_camera1.png - 00700_camera1.png # right rear camera images -│ ├── 00000_camera2.png - 00700_camera2.png # left rear camera images -│ └── 00000_camera3.png - 00700_camera3.png # back camera images -├── validate -├── test -├── test_culvercity -``` - -### 1. Data Split -OPV2V dataset can be divided into 4 different folders: `train`, `validation`, `test`, and `test_culvercity` -- `train`: contains all training data -- `validate`: used for validation during training -- `test`: test set for calrla default towns -- `test_culvercity`: test set for the digital town of Culver City, Los Angeles - -### 2. Scenario Database -OPV2V has 73 scenarios in total, where each of them contains data stream from different agents across different timestamps. -Each scenario is named by the time it was gathered, e.g., `2021_08_22_21_41_24`. - -### 3. Agent Contents -Under each scenario folder, the data of every intelligent agent~(i.e. connected automated vehicle) appearing in the current scenario is saved in different folders. Each folder is named by the agent's unique id, e.g., 1732. - -In each agent folder, data across different timestamps will be saved. Those timestamps are represented by five digits integers -as the prefix of the filenames (e.g., 00700.pcd). There are three types of files inside the agent folders: LiDAR point clouds (`.pcd` files), camera images (`.png` files), and metadata (`.yaml` files). - -#### 3.1 Lidar point cloud -The LiDAR data is saved with Open3d package and has a postfix ".pcd" in the name. - -#### 3.2 Camera images -Each CAV is equipped with 4 RGB cameras (check https://mobility-lab.seas.ucla.edu/opv2v/ to see the mounted positions of these cameras) to capture the 360 degree of view of the surrounding scene.`camera0`, `camera1`, `camera2`, and `camera3` represent the front, right rear, left rear, and back cameras respectively. - -#### 3.3 Data Annotation -All the metadata is saved in yaml files. It records the following important information at the current timestamp: -- **ego information**: Current ego pose with and without GPS noise under Carla world coordinates, ego speed in km/h, the LiDAR pose, and future planning trajectories. -- **calibration**: The intrinsic matrix and extrinsic matrix from each camera to the LiDAR sensor. -- **objects annotation**: The pose and velocity of each surrounding human driving vehicle that has at least one point hit by the agent's LiDAR sensor. See [data annotation section](data_annotation_tutorial.md) for more details. - -### 4. Data Collection Protocol -Besides agent contents, every scenario database also has a yaml file named `data_protocol.yaml`. -This yaml file records the simulation configuration to collect the current scenario. It is used to log replay -the data and enable users to add/modify sensors for new tasks without changing the original events. - -To help users collect customized data in CARLA (e.g., different sensor configurations and modalities) with similar data format and structure, we have released our OPV2V data collection code in the `feature/data_collection` branch of [OpenCDA](https://github.com/ucla-mobility/OpenCDA/tree/feature/data_collection). Users can refer to its [documentation](https://opencda-documentation.readthedocs.io/en/latest/md_files/introduction.html) for detailed instructions. - ---- -## V2XSet -The data can be found from [google url](https://drive.google.com/drive/folders/1r5sPiBEvo8Xby-nMaWUTnJIPK6WhY1B6?usp=sharing). Since the data for train/validate/test -is very large, we split each data set into small chunks, which can be found in the directory ending with `_chunks`, such as `train_chunks`. After downloading, please run the following command to each set to merge those chunks together: -``` -cat train.zip.part* > train.zip -unzip train.zip -``` - -### 1. Structure -After downloading is finished, please make the file structured as following: - -```sh -OpenCOOD # root of opencood -├── v2xset # the downloaded v2xset data -│ ├── train -│ ├── validate -│ ├── test -├── opencood # the core codebase -``` - -### 2. Label format -V2XSet's data label format is nearly the same with OPV2V, except: -- OPV2V only has vehicles while V2XSet has infrastructure sensors -- All the infrastructure sensors' folder is named with negative integer, e.g. `-1` - ---- -## Adding Noise to OPV2V and V2XSet - -Cooperative perception faces the challenge of GPS error and communication delay. Our OpenCOOD allows users -to add realistic GPS error and communication delay to the dataset. - -To add noise to both OPV2V and V2XSet, just add the following parameters to the model yaml file: -``` -wild_setting: # setting related to noise - async: true - async_mode: 'sim' - async_overhead: 100 - backbone_delay: 10 - data_size: 1.06 - loc_err: true - ryp_std: 0.2 - seed: 25 - transmission_speed: 27 - xyz_std: 0.2 -``` -`async`: whether add communication delay.
-`aysnc_mode`: sim or real mode. In sim mode, the delay is a constant while in real mode, the delay has a uniform distribution. -The major experiment in the paper used sim mode whereas the 'Effects of transmission size' study used real - mode.
-`async_overhead`: the communication delay in ms. In sim mode, it represents a constant number. In real mode, -the systematic async will be a random number from 0 to `aysnc_overhead`.
-`backbone_delay`: an estimate of backbone computation time. Only useful in real mode.
-`data_size`: transmission data size in Mb. Only used in real mode.
-`transmission_speed`: data transmitting speed during communication. By default 27 Mb/s. Only used in real mode.
-`loc_err`: whether to add localization error.
-`xyz_std`: the standard deviation of positional GPS error.
-`ryp_std`: the standard deviation of angular GPS error.
-`seed`: random seed for noise simulation. please keep it as 25 during testing . - -To see a complete example, refer to [configuration tutorial section](config_tutorial.md) diff --git a/OpenCOOD/docs/md_files/data_pipeline.png b/OpenCOOD/docs/md_files/data_pipeline.png deleted file mode 100644 index de5a296f..00000000 Binary files a/OpenCOOD/docs/md_files/data_pipeline.png and /dev/null differ diff --git a/OpenCOOD/docs/md_files/installation.md b/OpenCOOD/docs/md_files/installation.md deleted file mode 100644 index 7b6a0227..00000000 --- a/OpenCOOD/docs/md_files/installation.md +++ /dev/null @@ -1,66 +0,0 @@ -# Installation - -* [__System/Hardware Requirements__](#requirements) -* [__Installation__](#installation) - * [__1. Dependency Installation__](#1-dependency-installation) - * [__2. Install Pytorch__](#2-pytorch-installation-18) - * [__3. Install Spconv__](#3-spconv-121-requred) - - - - ---- -## System/Hardware Requirements -To get started, the following requirements should be fulfilled. -* __System requirements.__ OpenCOOD is tested under Ubuntu 18.04 -* __Adequate GPU.__ A minimum of 6GB gpu is recommended. -* __Disk Space.__ Estimate 100GB of space is recommended for data downoading. -* __Python__ Python3.7 is required. - - ---- -## Installation -### 1. Dependency Installation -First, download OpenCOOD github to your local folder if you haven't done it yet. -```sh -git clone https://github.com/DerrickXuNu/OpenCOOD.git -cd OpenCOOD -``` -Next we create a conda environment and install the requirements. - -```sh -conda env create -f environment.yml -conda activate opencood -python setup.py develop -``` - -If conda install failed, install through pip -```sh -pip install -r requirements.txt -``` - -### 2. Pytorch Installation (>=1.8, tested on 1.8-1.12.0) -Go to https://pytorch.org/ to install pytorch cuda version. - -### 3. Spconv (1.2.1 or 2.x) -OpenCOOD support both spconv 1.2.1 and 2.x to generate voxel features. - -To install spconv 1.2.1, please follow the guide in https://github.com/traveller59/spconv/tree/v1.2.1. - -To install spconv 2.x, please run the following commands (if you are using cuda 11.3): -```python -pip install spconv-cu113 -``` -#### Tips for installing spconv 1.2.1: -1. make sure your cmake version >= 3.13.2 -2. CUDNN and CUDA runtime library (use `nvcc --version` to check) needs to be installed on your machine. - - - -### 4. Dependencies for FPV-RCNN (optional) -The OpenCOOD CUDA extensions used by FPV-RCNN are built by the OpenCDA -Docker CUDA builder. They are copied into the runtime image and synchronized -into the source tree when the container starts. - -Rebuild the OpenCDA image after changing any source under -`opencood/pcdet_utils`. diff --git a/OpenCOOD/docs/md_files/lidar_benchmark.md b/OpenCOOD/docs/md_files/lidar_benchmark.md deleted file mode 100644 index 12be8ca7..00000000 --- a/OpenCOOD/docs/md_files/lidar_benchmark.md +++ /dev/null @@ -1,29 +0,0 @@ -## Benchmark: 3D LiDAR Detection - ---- -### Results on OPV2V dataset (AP@0.7 for no-compression/ compression) - -| | Backbone | Fusion Strategy | Bandwidth (Megabit),
before/after compression| Default Towns |Culver City| Download | -|--------------------| -------- | --------------- | --------------- | ------------- |-----------| -------- | -| Naive Late | PointPillar | Late | **0.024**/**0.024** | 0.781/0.781 | 0.668/0.668 | [url](https://drive.google.com/file/d/1WTKooW6k0exLqoIE5Czqy6ptycYlgKZz/view?usp=sharing) | -| [Cooper](https://arxiv.org/abs/1905.05265) | PointPillar | Early | 7.68/7.68 | 0.800/x | 0.696/x | [url](https://drive.google.com/file/d/1N1p6syxGSKD18ELgtBQoSuUzR8tX1JeE/view?usp=sharing) | -| [Attentive Fusion](https://arxiv.org/abs/2109.07644) | PointPillar | Intermediate | 126.8/1.98 | **0.815**/**0.810** | **0.735**/**0.731** | [url](https://drive.google.com/file/d/1u4w13SDzdGq6Irh2PHxT-qIlNXRT3z6Z/view?usp=sharing) | -| [F-Cooper](https://arxiv.org/abs/1909.06459) | PointPillar | Intermediate | 72.08/1.12 | 0.790/0.788 | 0.728/0.726 | [url](https://drive.google.com/file/d/1CjXu9Y2ZTzJA6Oo3hnqFhbTqBVKq3mQb/view?usp=sharing) | -| Naive Late | VoxelNet | Late | **0.024**/**0.024** | 0.738/0.738 | 0.588/0.588 | [url]() | -| Cooper | VoxelNet | Early | 7.68/7.68 | 0.758/x | 0.677/x | [url](https://drive.google.com/file/d/14WD7iLLyyCJJ3lApbYYdr5KOUM1ACnve/view?usp=sharing) | -| Attentive Fusion | VoxelNet | Intermediate | 576.71/1.12 | **0.864**/**0.852** | **0.775**/**0.746** | [url](https://drive.google.com/file/d/16q8CfcB8dS4EVhJMvvEfn0gM2ynxZB3E/view?usp=sharing) | -| Naive Late | SECOND | Late | **0.024**/**0.024** | 0.775/0.775 |0.682/0.682 | [url](https://drive.google.com/file/d/1VG_FKe1mKagPVGXH7UGHpyaM5q3cxtD8/view?usp=sharing) | -| Cooper | SECOND | Early | 7.68/7.68 | 0.813/x | 0.738/x | [url](https://drive.google.com/file/d/1Z9io1VNcU-urcRW8l0ogWCTVCB53mw4N/view?usp=sharing) | -| Attentive | SECOND | Intermediate | 63.4/0.99 | **0.826**/**0.783** | **0.760**/**0.760** | [url](https://drive.google.com/file/d/1zEB8EyZ0X-WQykHFOM0pVwI8jXunRz1Z/view?usp=sharing) | -| Naive Late | PIXOR | Late | **0.024**/**0.024** | 0.578/0.578 | 0.360/0.360 | [url]() | -| Cooper | PIXOR | Early | 7.68/7.68 | 0.678/x | **0.558**/x | [url](https://drive.google.com/file/d/1ZDLjtizZCuV6D92LloEPKRIw-LqxfE1j/view?usp=sharing) | -| Attentive | PIXOR | Intermediate | 313.75/1.22 | **0.687**/**0.612** | 0.546/**0.492** | [url]() | - - -**Note**: -* We suggest using **PointPillar** as the backbone when you are creating your method and try to compare with -our benchmark, as we implement most of the SOTA methods with this backbone only. -* We assume the transimssion rate is 27Mbp/s. Considering the frequency of LiDAR is 10Hz, the -bandwidth requirement should be less than **2.7Mbp** to avoid severe delay. -* A 'x' in the benchmark table represents the bandwidth requirement is too large, which -can not be considered to employ in practice. diff --git a/OpenCOOD/docs/md_files/logic_flow.md b/OpenCOOD/docs/md_files/logic_flow.md deleted file mode 100644 index 440d235a..00000000 --- a/OpenCOOD/docs/md_files/logic_flow.md +++ /dev/null @@ -1,85 +0,0 @@ -## Tutorial 3: OpenCOOD Pipeline - ---- -![teaser](data_pipeline.png) - -In this tutorial, we will go through the pipeline of OpenCOOD from loading data to generate structure predictions. -As the figure shown above, there are five important modules: **configuration system**, **data loading**, **preprocessor**, **postprocessor**, and **model**. - -### Configuration System - ---- -The configuration system will read the yaml file defined by users to load all parameters for data I/O, training schema, and model. The configuration will be saved in a dictionary. Please refer to [tutorial 1](config_tutorial.md) for more details. - -### Data Loading - ---- -As different fusion strategies can lead to different data processing (e.g., early fusion aggregates all raw -LiDAR point clouds whereas intermediate fusion aggregates intermediate deep features), we have three -distinct PyTorch dataset class: `EarlyFusionDataset`, `LateFusionDataset`, and `IntermediateFusionDataset`. -These three classes share the same interface and inherit from `BaseDataset`. - -#### BaseDataset -There are two major tasks for `BaseDataset`. -- First, given the path to the dataset, `BaseDataset` will construct a scenario database (dictionary format). The scenarior database is indexed by scenarior id, agent id and timestamp and returns file pathes for various sensors and saved yaml data. Here is an example of how the database structured: -```yaml - -{scenario_id : - {agent_1 : - {timestamp1 : - {yaml: path (str), - lidar: path (str), - cameras: alist of path to 4 dfferent cameras}}}} - -``` -- Second, during the runtime, given an index from PyTorch dataloader, `BaseDataset` will retrieve the corresponding path information from scenario database, load the data from disk and return the raw format data: -```yaml -{cav_id : - {ego : True or False, # whether this CAV is regarded as ego - params: a dictionary, # contains all metadata of this CAV and annotations for surrounding objects - lidar_np: a (n, 4) numpy array for the raw point cloud, - camera_rgbs: a list of rgb images from 4 cameras on the CAV. - } -} -``` - -#### EarlyFusion, LateFusion, IntermediateFusion dataset -All of these 3 classes will inherit the `BaseDataset` to first construct a scenario database. During runtime, they will first call the `__get_item__` function from `BaseDataset` to get the raw format data. Next, they will do: - -1. Project all objects' bounding box coordinates from Carla Map global reference to the ego vehicle's reference. Note that for late fusion, the CAV project to its own coordinate frame instead of the ego vehicle. -2. For early fusion and intermediate fusion, all agents will project their LiDAR point clouds to ego vehicle's coordinate frame. -3. Call the corresponding `PreProcessor` to convert the raw point clouds to voxel/bev features. -4. Call the corresponding `PostProcessor` to convert the objects annotation to the correct format for the loss computing. -5. Structure all the information in a dictionary. The processed format will be: -```yaml -{ agent_id: - { 'object_bbx_center': numpy array, # the bounding boxes of all objects with padding - 'object_bbx_mask': numpy array, # indicate the valid index for object_bbx_center - 'object_ids': a list of integer, # objects - 'anchor_box': numpy array, # pre-defined anchor box for detection - 'processed_lidar': numpy array, # voxel/bev features generated from preprocessor - 'label_dict': numpy array, # the groundtruth that can be directly used for computing loss during training - } -} -``` -Notice here we just mention the general process and data format, these 3 classes have some differences in details. -For example, early fusion and intermediate fusion only have a single `agent_id` element -- `ego`, while late fusion -has multiple `agent_id`. Please read the code for more details. - -#### Collate Function -As our data format is customized, all 3 dataset classes have their own `collate_fn` for the PyTorch dataloader. The output of the collate function will be a similar dictionary as the one mentioned above except that all values are now in torch.Tensor format. - -### Preprocessor and Postprocessor - ---- -The preprocessor is mainly used to convert the raw point clouds to voxels or 2D bev features (refer to PIXOR [paper](https://arxiv.org/abs/1902.06326)). - -The postprocessor has three purposes: -- Convert the bounding boxes of the objects to the correct format so that the models can use to compute the loss. -- Generate the predefined anchor box if needed. -- Convert the output of the models to the correct format for evaluation and visualization. - -### Models - ---- -All the models' architecture designs are saved in `opencood/models`. For early fusion and late fusion, each model file only have the backbone codes. For intermediate fusion, the model files have both backbone and fusion net codes. diff --git a/OpenCOOD/docs/requirements.txt b/OpenCOOD/docs/requirements.txt deleted file mode 100644 index 31071e3d..00000000 --- a/OpenCOOD/docs/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -matplotlib==3.4.2 -numpy -open3d -opencv-python -shapely -tensorboardX diff --git a/OpenCOOD/environment.yml b/OpenCOOD/environment.yml deleted file mode 100644 index 6e8246b5..00000000 --- a/OpenCOOD/environment.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: opencood -channels: - - defaults -dependencies: - - pip==21.1.2 - - python==3.7.11 - - pip: - - matplotlib==3.4.2 - - numpy - - open3d - - opencv-python==4.5.5.62 - - pygame - - tensorboardX - - shapely==2.0.0 - - einops - - cumm - - timm diff --git a/OpenCOOD/images/camera_demo.gif b/OpenCOOD/images/camera_demo.gif deleted file mode 100644 index 2107af46..00000000 Binary files a/OpenCOOD/images/camera_demo.gif and /dev/null differ diff --git a/OpenCOOD/images/demo1.gif b/OpenCOOD/images/demo1.gif deleted file mode 100644 index b18b949b..00000000 Binary files a/OpenCOOD/images/demo1.gif and /dev/null differ diff --git a/OpenCOOD/images/logreplay.gif b/OpenCOOD/images/logreplay.gif deleted file mode 100644 index bacb3046..00000000 Binary files a/OpenCOOD/images/logreplay.gif and /dev/null differ diff --git a/OpenCOOD/images/opencood.png b/OpenCOOD/images/opencood.png deleted file mode 100644 index 6b9e2bd5..00000000 Binary files a/OpenCOOD/images/opencood.png and /dev/null differ diff --git a/OpenCOOD/images/pytorch-logo-dark.png b/OpenCOOD/images/pytorch-logo-dark.png deleted file mode 100644 index b7a1ceb9..00000000 Binary files a/OpenCOOD/images/pytorch-logo-dark.png and /dev/null differ diff --git a/OpenCOOD/logreplay/README.md b/OpenCOOD/logreplay/README.md deleted file mode 100644 index a3fe4796..00000000 --- a/OpenCOOD/logreplay/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# CARLA Log Replay Tool for Cooperative Perception Dataset -

- -

-This repository provides a toolbox to replay simulated cooperative perception dataset in CARLA. With -this toolbox, you can 100% replay all the events in the offline dataset and add/change any sensors/groundtruth you -want to explore the tasks that the origin dataset do not support. - -## Features -- Support replaying multiple cooperative perception dataset in CARLA - - [x] [OPV2V [ICRA2022]](https://mobility-lab.seas.ucla.edu/opv2v/) - - [ ] [V2XSet [ECCV2022]]() -- Help you add new sensors to the agents to have additional sensor data, including but not limited to: - - Depth sensor - - Event camera - - Semantic LiDAR - - Radar - -- Dump new groundtruth that the origin dataset does not have, including but not limited to: - - HDMap ([CoBEVT](https://arxiv.org/abs/2207.02202) (CoRL2022) used this groundtruth ) - - Trajectory for each vehicle -- Change the number of CAVs! The original OPV2V data has fixed CAVs, but with this tool, you can -assign any vehicle to a CAV equipped with sensors. - -## How to use -### Step0: Install CARLA 0.9.12 and CARLA API to your python environment -Check the CARLA official website: https://carla.readthedocs.io/en/latest/start_quickstart/ -### Step1: Yaml file configuration -Please change the parameters in `logreplay/hypes_yaml/replay.yaml` to configure your replay. -We have provided very detailed explanation for each line in the yaml. -### Step2: add new sensors if necessary -We have by default provide Semantic Lidar and BEV semantic camera sensors in the yaml file and codebase. -If you want to add some new sensors, please add your sensor under `logreplay/sensors/` and refer to the `semantic_lidar` -or `bev_semantic_camera.py` to write your own sensor class. -### Step3: Run CARLA -Start your CARLA simulator first. Right now we only support version 0.9.12 -### Step4: run the log replay tool -Execute the following command: -```python -cd logreplay -python scenario/scenarios_manager.py -``` diff --git a/OpenCOOD/logreplay/__init__.py b/OpenCOOD/logreplay/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/logreplay/assets/__init__.py b/OpenCOOD/logreplay/assets/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/logreplay/assets/presave_lib.py b/OpenCOOD/logreplay/assets/presave_lib.py deleted file mode 100644 index 1da9a501..00000000 --- a/OpenCOOD/logreplay/assets/presave_lib.py +++ /dev/null @@ -1,298 +0,0 @@ -BLUE_PRINT_LIB = { - "vehicle.audi.a2": { - "location": [1.1874361216523965e-10, 0.00033203110797330737, 0.7818066477775574], - "extent": [1.852684736251831, 0.8943392634391785, 0.7745251059532166], - "num_wheel": 4, - }, - "vehicle.audi.tt": { - "location": [-0.00037109359982423484, 1.3271345133958334e-10, 0.6926488876342773], - "extent": [2.0906050205230713, 0.9970585703849792, 0.6926480531692505], - "num_wheel": 4, - }, - "vehicle.micro.microlino": { - "location": [-2.0954755292068583e-11, -5.8593730500433594e-05, 0.6771557331085205], - "extent": [1.1036475896835327, 0.7404598593711853, 0.6880123615264893], - "num_wheel": 4, - }, - "vehicle.bmw.grandtourer": { - "location": [-1.885927941591703e-10, -0.0005273435963317752, 0.7651049494743347], - "extent": [2.3055028915405273, 1.1208566427230835, 0.8336379528045654], - "num_wheel": 4, - }, - "vehicle.mercedes.sprinter": { - "location": [-0.010537102818489075, 0.004140627570450306, 1.2882421016693115], - "extent": [2.957595109939575, 0.9942164421081543, 1.2803276777267456], - "num_wheel": 4, - }, - "vehicle.dodge.charger_police": { - "location": [0.023632802069187164, 0.0006054601399227977, 0.7926709055900574], - "extent": [2.487122058868408, 1.0192005634307861, 0.7710590958595276], - "num_wheel": 4, - }, - "vehicle.jeep.wrangler_rubicon": { - "location": [0.00021484403987415135, 0.00103515584487468, 0.9322753548622131], - "extent": [1.9331103563308716, 0.9525982737541199, 0.9389679431915283], - "num_wheel": 4, - }, - "vehicle.mini.cooper_s": { - "location": [-2.0954755292068583e-11, -5.8593730500433594e-05, 0.6930102109909058], - "extent": [1.9029000997543335, 0.985137939453125, 0.7375151515007019], - "num_wheel": 4, - }, - "vehicle.nissan.micra": { - "location": [3.492459099696532e-10, 0.0009765622089616954, 0.7812451124191284], - "extent": [1.8166879415512085, 0.9225568771362305, 0.7506412863731384], - "num_wheel": 4, - }, - "vehicle.carlamotors.carlacola": { - "location": [0.0, 0.0, 1.2337206602096558], - "extent": [2.601919174194336, 1.3134948015213013, 1.2337223291397095], - "num_wheel": 4, - }, - "vehicle.mercedes.coupe_2020": { - "location": [-0.00259765493683517, 9.289941038659322e-10, 0.7203612923622131], - "extent": [2.3368194103240967, 1.0011461973190308, 0.7209736704826355], - "num_wheel": 4, - }, - "vehicle.dodge.charger_police_2020": { - "location": [0.109501913189888, -3.916094470923781e-08, 0.8174071907997131], - "extent": [2.6187572479248047, 1.0485419034957886, 0.819191575050354], - "num_wheel": 4, - }, - "vehicle.mini.cooper_s_2021": { - "location": [-0.031249990686774254, 1.1175869119028903e-08, 0.8910351395606995], - "extent": [2.2763495445251465, 1.0485360622406006, 0.8835831880569458], - "num_wheel": 4, - }, - "vehicle.bh.crossbike": { - "location": [0.00029444904066622257, 195.28602600097656, 0.5411913990974426], - "extent": [0.7436444163322449, 0.0, 0.5397894978523254], - "num_wheel": 2, - }, - "vehicle.nissan.patrol": { - "location": [-0.05737302824854851, 0.00015627048560418189, 0.9351245164871216], - "extent": [2.3022549152374268, 0.9657964706420898, 0.9274230599403381], - "num_wheel": 4, - }, - "vehicle.kawasaki.ninja": { - "location": [-0.01960790529847145, 195.23898315429688, 0.5854394435882568], - "extent": [1.0166761875152588, 0.0, 0.5727267861366272], - "num_wheel": 2, - }, - "vehicle.lincoln.mkz_2020": { - "location": [-0.006289059761911631, 2.249143760124639e-09, 0.7437793016433716], - "extent": [2.44619083404541, 1.115301489830017, 0.7400735020637512], - "num_wheel": 4, - }, - "vehicle.lincoln.mkz_2017": { - "location": [0.004042967688292265, -1.4458780839277097e-09, 0.718859851360321], - "extent": [2.4508416652679443, 1.0641621351242065, 0.7553732395172119], - "num_wheel": 4, - }, - "vehicle.ford.mustang": { - "location": [0.03213866055011749, -1.149368333841494e-08, 0.6504687070846558], - "extent": [2.358762502670288, 0.947413444519043, 0.650469958782196], - "num_wheel": 4, - }, - "vehicle.tesla.cybertruck": { - "location": [0.0, 0.0, 1.049096703529358], - "extent": [3.1367764472961426, 1.1947870254516602, 1.049095630645752], - "num_wheel": 4, - }, - "vehicle.ford.ambulance": { - "location": [-0.2885546088218689, 0.001933696330524981, 1.2156102657318115], - "extent": [3.18282151222229, 1.1755871772766113, 1.215687870979309], - "num_wheel": 4, - }, - "vehicle.mercedes.coupe": { - "location": [-2.7939672797572257e-10, -0.0007812496623955667, 0.6667944192886353], - "extent": [2.5133883953094482, 1.0757731199264526, 0.8253258466720581], - "num_wheel": 4, - }, - "vehicle.seat.leon": { - "location": [-0.00014648489013779908, -0.0015624993247911334, 0.7396337985992432], - "extent": [2.0964150428771973, 0.9080929160118103, 0.7369155883789062], - "num_wheel": 4, - }, - "vehicle.nissan.patrol_2021": { - "location": [0.028378896415233612, -1.0149086016042475e-08, 1.0167187452316284], - "extent": [2.782914400100708, 1.0749834775924683, 1.0225735902786255], - "num_wheel": 4, - }, - "vehicle.vespa.zx125": { - "location": [0.010460472665727139, 195.32354736328125, 0.6211523413658142], - "extent": [0.9023334980010986, 0.0, 0.6178141832351685], - "num_wheel": 2, - }, - "vehicle.yamaha.yzf": { - "location": [-0.004266120493412018, 195.2182159423828, 0.64984130859375], - "extent": [1.1047229766845703, 0.0, 0.6255727410316467], - "num_wheel": 2, - }, - "vehicle.chevrolet.impala": { - "location": [5.859437442268245e-05, 0.0017968743341043591, 0.70149165391922], - "extent": [2.6787397861480713, 1.0166014432907104, 0.7053293585777283], - "num_wheel": 4, - }, - "vehicle.volkswagen.t2": { - "location": [0.0013281241990625858, -0.0005468753515742719, 1.0144726037979126], - "extent": [2.2402184009552, 1.034657597541809, 1.0188959836959839], - "num_wheel": 4, - }, - "vehicle.citroen.c3": { - "location": [8.451751520865969e-10, 0.002363280626013875, 0.76191645860672], - "extent": [1.9938424825668335, 0.9254241585731506, 0.8085547685623169], - "num_wheel": 4, - }, - "vehicle.diamondback.century": { - "location": [1.1252891454205383e-05, 195.3052520751953, 0.525390625], - "extent": [0.8214218020439148, 0.0, 0.5119513869285583], - "num_wheel": 2, - }, - "vehicle.tesla.model3": { - "location": [0.02921873889863491, -1.0449437759518787e-08, 0.7355297803878784], - "extent": [2.3958897590637207, 1.081725001335144, 0.744159996509552], - "num_wheel": 4, - }, - "vehicle.carlamotors.firetruck": { - "location": [-0.25343742966651917, 0.005312588065862656, 1.9133495092391968], - "extent": [4.234020709991455, 1.4455441236495972, 1.9137061834335327], - "num_wheel": 4, - }, - "vehicle.audi.etron": { - "location": [0.0, 0.0, 0.8205029368400574], - "extent": [2.427854299545288, 1.0163782835006714, 0.8246796727180481], - "num_wheel": 4, - }, - "vehicle.dodge.charger_2020": { - "location": [-0.006230466067790985, 2.228188966668654e-09, 0.76557856798172], - "extent": [2.5030298233032227, 1.0485419034957886, 0.7673624753952026], - "num_wheel": 4, - }, - "vehicle.gazelle.omafiets": { - "location": [-0.01564301736652851, 195.3675537109375, 0.5801635384559631], - "extent": [0.9177202582359314, 0.0, 0.5628286004066467], - "num_wheel": 2, - }, - "vehicle.harley-davidson.low_rider": { - "location": [-0.008875441737473011, 195.3657989501953, 0.6599145531654358], - "extent": [1.1778701543807983, 0.0, 0.6382853388786316], - "num_wheel": 2, - }, - "vehicle.toyota.prius": { - "location": [0.001992186764255166, -0.00039062555879354477, 0.724169909954071], - "extent": [2.256761312484741, 1.0034072399139404, 0.7624167203903198], - "num_wheel": 4, - }, -} - -TOWN_DICTIONARY = { - "2021_08_20_21_48_35": "Town06", - "2021_08_18_19_48_05": "Town06", - "2021_08_20_21_10_24": "Town06", - "2021_08_21_09_28_12": "Town06", - "2021_08_22_07_52_02": "Town05", - "2021_08_22_09_08_29": "Town05", - "2021_08_22_21_41_24": "Town05", - "2021_08_23_12_58_19": "Town05", - "2021_08_23_15_19_19": "Town04", - "2021_08_23_16_06_26": "Town04", - "2021_08_23_17_22_47": "Town04", - "2021_08_23_21_07_10": "Town10HD", - "2021_08_23_21_47_19": "Town10HD", - "2021_08_24_07_45_41": "Town10HD", - "2021_08_24_11_37_54": "Town07", - "2021_08_24_20_09_18": "Town04", - "2021_08_24_20_49_54": "Town04", - "2021_08_24_21_29_28": "Town04", - "2021_08_16_22_26_54": "Town06", - "2021_08_18_09_02_56": "Town06", - "2021_08_18_18_33_56": "Town06", - "2021_08_18_21_38_28": "Town06", - "2021_08_18_22_16_12": "Town06", - "2021_08_18_23_23_19": "Town06", - "2021_08_19_15_07_39": "Town06", - "2021_08_20_16_20_46": "Town06", - "2021_08_20_20_39_00": "Town06", - "2021_08_20_21_00_19": "Town06", - "2021_08_21_09_09_41": "Town06", - "2021_08_21_15_41_04": "Town05", - "2021_08_21_16_08_42": "Town05", - "2021_08_21_17_00_32": "Town05", - "2021_08_21_21_35_56": "Town05", - "2021_08_21_22_21_37": "Town05", - "2021_08_22_06_43_37": "Town05", - "2021_08_22_07_24_12": "Town05", - "2021_08_22_08_39_02": "Town05", - "2021_08_22_09_43_53": "Town05", - "2021_08_22_10_10_40": "Town05", - "2021_08_22_10_46_58": "Town06", - "2021_08_22_11_29_38": "Town06", - "2021_08_22_22_30_58": "Town05", - "2021_08_23_10_47_16": "Town04", - "2021_08_23_11_06_41": "Town05", - "2021_08_23_11_22_46": "Town04", - "2021_08_23_12_13_48": "Town05", - "2021_08_23_13_10_47": "Town05", - "2021_08_23_16_42_39": "Town04", - "2021_08_23_17_07_55": "Town04", - "2021_08_23_19_27_57": "Town10HD", - "2021_08_23_20_47_11": "Town10HD", - "2021_08_23_22_31_01": "Town10HD", - "2021_08_23_23_08_17": "Town10HD", - "2021_08_24_09_25_42": "Town07", - "2021_08_24_09_58_32": "Town07", - "2021_08_24_12_19_30": "Town07", - "2021_09_09_13_20_58": "Town03", - "2021_09_09_19_27_35": "Town01", - "2021_09_10_12_07_11": "Town04", - "2021_09_09_23_21_21": "Town03", - "2021_08_21_17_30_41": "Town05", - "2021_08_22_13_37_16": "Town06", - "2021_08_22_22_01_17": "Town05", - "2021_08_23_10_51_24": "Town05", - "2021_08_23_13_17_21": "Town05", - "2021_08_23_19_42_07": "Town10HD", - "2021_09_09_22_21_11": "Town02", - "2021_09_11_00_33_16": "Town10HD", - "2021_08_18_19_11_02": "Town06", -} - - -class bcolors: - WARNING = "\033[93m" - FAIL = "\033[91m" - ENDC = "\033[0m" - - -EXCLUDE_ROAD_MAP = [ - "2021_08_18_19_11_02", - "2021_08_21_09_09_41", - "2021_08_23_16_42_39", - "2021_08_23_17_07_55", - "2021_08_24_12_19_30", - "2021_08_24_09_25_42", - "2021_08_24_09_58_32", - "2021_08_24_12_19_30", - "2021_08_21_09_28_12", - "2021_08_24_11_37_54", - "2021_08_23_17_22_47", - "2021_08_24_20_09_18", -] - -OR_Z_VALUE_MAP = [ - "2021_08_22_09_43_53", - "2021_08_22_10_10_40", - "2021_08_23_12_13_48", - "2021_08_23_13_10_47", - "2021_08_24_09_25_42", - "2021_08_24_09_58_32", - "2021_08_24_12_19_30", - "2021_09_09_23_21_21", - "2021_09_10_12_07_11", - "2021_08_22_09_08_29", - "2021_08_23_12_58_19", - "2021_08_23_17_22_47", - "2021_08_24_11_37_54", - "2021_08_24_20_09_18", -] diff --git a/OpenCOOD/logreplay/assets/utils.py b/OpenCOOD/logreplay/assets/utils.py deleted file mode 100644 index 2d59d938..00000000 --- a/OpenCOOD/logreplay/assets/utils.py +++ /dev/null @@ -1,26 +0,0 @@ -from logreplay.assets.presave_lib import TOWN_DICTIONARY, BLUE_PRINT_LIB - - -def find_town(scenario_name): - """ - Given the scenario name, find the corresponding town name, - - Parameters - ---------- - scenario_name : str - The scenario name. - - Returns - ------- - The corresponding town's name. - """ - return TOWN_DICTIONARY[scenario_name] - - -def find_blue_print(extent): - for blueprint_name, blueprint_content in BLUE_PRINT_LIB.items(): - bp_extent = blueprint_content["extent"] - if abs(extent[0] - bp_extent[0]) < 0.001 and abs(extent[1] - bp_extent[1]) < 0.001: - return blueprint_name - - return None diff --git a/OpenCOOD/logreplay/hypes_yaml/__init__.py b/OpenCOOD/logreplay/hypes_yaml/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/logreplay/hypes_yaml/replay.yaml b/OpenCOOD/logreplay/hypes_yaml/replay.yaml deleted file mode 100644 index a38d05d8..00000000 --- a/OpenCOOD/logreplay/hypes_yaml/replay.yaml +++ /dev/null @@ -1,61 +0,0 @@ -root_dir: '/data/opv2v/train' # your original opv2v dataset -output_dir: '/data/opv2v/train/additional' # the new folder to save the dumped new data -map: # corresponding to bev map - activate: true # whether activate HDMap module - visualize: true # whether to visualize the BEV Map for each CAV - - save_yml: false # whether to save the map meta data in yaml files - save_static: false # whether to dump road segmentation mask - save_lane: true # whether to dump lane segmentation mask - save_dynamic: false # whether to dump dynamic object BEV segmentaition mask - save_bev_vis: true # whether to dump the BEV visualization image that has all elements - save_bev_sem: true # whether to dump the BEV semantic mask that contains all elements - - radius: 50 # the BEV Map range, e.g., 50 means a square around the ego whose side length is 100 - raster_size: [256, 256] # the dumped BEV images resolution - lane_sample_resolution: 0.1 # the lane keypoint sampling resolution - - static: - draw_lane: true # whether to draw lane on the BEV visualization image - draw_traffic_light: true # if set to true, the lanes that are influenced by traffic light will change color according to the light state, e.g., yellow, red, green - exclude_road: false # set this to false by default - exclude_intersection_lane: true # whether to draw the lane topology in the intersection - z_filter_value: 6 # the road that is higher or lower relative to ego's position than the threshold won't be drawed - other_objs: ['terrain', 'building', 'sidewalk'] # besides, lane and road, what other objects need to render on the BEV visualization image - dynamic: - exclude_self: true # if set to true, ego won't be drawed - exclude_off_road: true # eclude the objects that are off road - visibility: false # whether save visibility mask under ego coordinate. when set to true, semantic lidar has to be implemented - visibility_corp: false # whether save visibility mask that can be seen for all cavs. when set to true, semantic lidar has to be implemented - -sensor: # new attached sensor - sensor_list: - - name: 'semantic_lidar' - args: &base_semantic_lidar - upper_fov: 10 - lower_fov: -30 - channels: 32 - rotation_frequency: 20 - points_per_second: 250000 - range: 50 - relative_pose: front - thresh: 5 # mininum number of points hit to be regarded as visible - - name: 'semantic_lidar' - args: - <<: *base_semantic_lidar - relative_pose: left - - name: 'semantic_lidar' - args: - <<: *base_semantic_lidar - relative_pose: right - - name: 'semantic_lidar' - args: - <<: *base_semantic_lidar - relative_pose: back - - name: 'bev_semantic_camera' - args: - visualize: true - fov: 45 - image_size_x: 512 - image_size_y: 512 - height: 115.88 diff --git a/OpenCOOD/logreplay/map/__init__.py b/OpenCOOD/logreplay/map/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/logreplay/map/map_drawing.py b/OpenCOOD/logreplay/map/map_drawing.py deleted file mode 100644 index acd04c36..00000000 --- a/OpenCOOD/logreplay/map/map_drawing.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Rasterization drawing functions""" - -import numpy as np -import cv2 - -# sub-pixel drawing precision constants -CV2_SUB_VALUES = {"shift": 9, "lineType": cv2.LINE_AA} -CV2_SHIFT_VALUE = 2 ** CV2_SUB_VALUES["shift"] - -AGENT_COLOR = (255, 255, 255) -ROAD_COLOR = (255, 255, 255) -ROAD_COLOR_VIS = (17, 17, 31) -Lane_COLOR = {"normal": (255, 217, 82), "red": (255, 0, 0), "yellow": (255, 255, 0), "green": (0, 255, 0)} -BUILD_COLOR = (70, 70, 70) -Terrain_COLOR = (145, 170, 100) -SideWalk_COLOR = (244, 35, 232) -# color map -OBJ_COLOR_MAP = {"building": BUILD_COLOR, "terrain": Terrain_COLOR, "sidewalk": SideWalk_COLOR} - - -def cv2_subpixel(coords: np.ndarray) -> np.ndarray: - """ - Cast coordinates to numpy.int but keep fractional part by previously multiplying by 2**CV2_SHIFT - cv2 calls will use shift to restore original values with higher precision - - Args: - coords (np.ndarray): XY coords as float - - Returns: - np.ndarray: XY coords as int for cv2 shift draw - """ - coords = coords * CV2_SHIFT_VALUE - coords = coords.astype(int) - return coords - - -def draw_agent(agent_list, image): - """ - Draw agent mask on image. - - Parameters - ---------- - agent_list : list - The agent corner list. - - image : np.ndarray - The image to be drawn. - - Returns - ------- - Drawn image. - """ - for agent_corner in agent_list: - agent_corner = agent_corner.reshape(-1, 2) - cv2.fillPoly(image, [agent_corner], AGENT_COLOR, **CV2_SUB_VALUES) - return image - - -def draw_road(lane_area_list, image, visualize=False): - """ - Draw poly for road. - - Parameters - ---------- - visualize : bool - If set to true, the road segment will be black color. - - lane_area_list : list - List of lane coordinates - - image : np.ndarray - image to be drawn - - Returns - ------- - drawed image. - """ - color = ROAD_COLOR if not visualize else ROAD_COLOR_VIS - - for lane_area in lane_area_list: - lane_area = lane_area.reshape(-1, 2) - cv2.fillPoly(image, [lane_area], color, **CV2_SUB_VALUES) - return image - - -def road_exclude(static_road): - """ - Exclude the road segment that is not connected to the ego vehicle - position. - - Parameters - ---------- - static_road : np.ndarray - The static bev map with road segment. - - Returns - ------- - The road without unrelated road. - """ - binary_bev = cv2.cvtColor(static_road, cv2.COLOR_BGR2GRAY) - _, label, _, _ = cv2.connectedComponentsWithStats(binary_bev) - - ego_label = label[static_road.shape[0] // 2, static_road.shape[1] // 2] - static_road[label != ego_label] = 0 - - return static_road - - -def draw_lane(lane_area_list, lane_type_list, image, intersection_list=None, vis=True): - """ - Draw lanes on image (polylines). - - Parameters - ---------- - intersection_list : list - lane_area_list : list - List of lane coordinates - - lane_type_list : list - List of lane types, normal, red, green or yellow. - - image : np.ndarray - image to be drawn - - vis : bool - Whether to visualize - - Returns - ------- - drawed image. - """ - if intersection_list is None: - intersection_list = [False] * len(lane_area_list) - - for lane_area, lane_type, inter_flag in zip(lane_area_list, lane_type_list, intersection_list): - if inter_flag: - continue - cv2.polylines(image, lane_area, False, Lane_COLOR[lane_type] if vis else (255, 255, 255), **CV2_SUB_VALUES) - - return image - - -def draw_crosswalks(lane_area_list, image): - """ - Draw lanes on image (polylines). - - Parameters - ---------- - lane_area_list : list - List of cross coordinates - - image : np.ndarray - image to be drawn - - vis : bool - Whether to visualize - - Returns - ------- - drawed image. - """ - for lane_area in lane_area_list: - up_line = lane_area[0] - bottom_line = lane_area[1] - - cv2.line(image, (up_line[0, 0], up_line[0, 1]), (up_line[-1, 0], up_line[-1, 1]), (255, 255, 255), 2, **CV2_SUB_VALUES) - cv2.line(image, (bottom_line[0, 0], bottom_line[0, 1]), (bottom_line[-1, 0], bottom_line[-1, 1]), (255, 255, 255), 2, **CV2_SUB_VALUES) - cv2.line(image, (up_line[0, 0], up_line[0, 1]), (bottom_line[-1, 0], bottom_line[-1, 1]), (255, 255, 255), 2, **CV2_SUB_VALUES) - cv2.line(image, (up_line[-1, 0], up_line[-1, 1]), (bottom_line[0, 0], bottom_line[0, 1]), (255, 255, 255), 2, **CV2_SUB_VALUES) - return image - - -def draw_city_objects(city_obj_info, image): - """ - Draw static objects other than lane, road, crosswalks on image. - - Parameters - ---------- - city_obj_info : dict - image : np.ndarray - - Returns - ------- - Drew image. - """ - for obj_category, obj_content in city_obj_info.items(): - for _, obj in obj_content.items(): - obj_corner = obj["corner_area"].reshape(-1, 2) - cv2.fillPoly(image, [obj_corner], OBJ_COLOR_MAP[obj_category], **CV2_SUB_VALUES) - return image diff --git a/OpenCOOD/logreplay/map/map_manager.py b/OpenCOOD/logreplay/map/map_manager.py deleted file mode 100644 index 46a9ef0a..00000000 --- a/OpenCOOD/logreplay/map/map_manager.py +++ /dev/null @@ -1,841 +0,0 @@ -"""HDMap manager""" - -import math -import os.path -import uuid - -import cv2 -import carla -import numpy as np -from matplotlib.path import Path -from shapely.geometry import Polygon - -from logreplay.assets.presave_lib import EXCLUDE_ROAD_MAP, OR_Z_VALUE_MAP -from logreplay.map.map_utils import ( - world_to_sensor, - lateral_shift, - list_loc2array, - list_wpt2array, - convert_tl_status, - exclude_off_road_agents, - retrieve_city_object_info, - obj_in_range, -) -from logreplay.map.map_drawing import cv2_subpixel, draw_agent, draw_road, draw_lane, road_exclude, draw_crosswalks, draw_city_objects -from opencood.hypes_yaml.yaml_utils import save_yaml_wo_overwriting - -CV2_SUB_VALUES = {"shift": 9, "lineType": cv2.LINE_AA} - - -class MapManager(object): - """ - This class is used to manage HD Map. We emulate the style of Lyft dataset. - - Parameters - ---------- - world : Carla.World - CARLA world. - - config : dict - All the map manager parameters. - - output_root : str - The data dump root folder. - - scene_name : str - The name of the scene. - - Attributes - ---------- - world : carla.world - Carla simulation world. - - meter_per_pixel : float - m/pixel - - raster_size : float - The rasterization map size in pixels. - - radius_meter : float - The valid radius(m) in the center of the rasterization map. - - topology : list - Map topology in list. - - lane_info : dict - A dictionary that contains all lane information. - - crosswalk_info : dict - A dictionary that contains all crosswalk information. - todo: will be implemented in the next version. - - traffic_light_info : dict - A dictionary that contains all traffic light information. - - bound_info : dict - A dictionary that saves boundary information of lanes and crosswalks. - It is used to efficiently filter out invalid lanes/crosswarlks. - - lane_sample_resolution : int - The sampling resolution for drawing lanes. - - draw_lane : bool - Whether to draw lane on the static bev map - - static_bev : np.array - The static bev map containing lanes and drivable road information. - - dynamic_bev : np.array - The dynamic bev map containing vehicle's information. - - vis_bev : np.array - The comprehensive bev map for visualization. - - """ - - def __init__(self, world, config, output_root, scene_name): - self.world = world - self.carla_map = world.get_map() - self.center = None - self.actor_id = None - self.out_root = output_root - self.scene_name = scene_name - - # whether to activate this module - self.activate = config["activate"] - if not self.activate: - return - - # save flags - self.save_yml = config["save_yml"] - self.save_static = config["save_static"] - self.save_dynamic = config["save_dynamic"] - self.save_lane = config["save_lane"] - self.save_bev_vis = config["save_bev_vis"] - - # whether to visualize the bev map while running simulation - self.visualize = config["visualize"] - # whether exclude the road that is unrelated to the ego vehicle - self.exclude_road = config["static"]["exclude_road"] - if self.scene_name in EXCLUDE_ROAD_MAP: - self.exclude_road = True - - self.radius_meter = config["radius"] - self.z_filter_value = config["static"]["z_filter_value"] - self.exclude_intersection_lane = config["static"]["exclude_intersection_lane"] - self.other_render_objs = config["static"]["other_objs"] - - assert config["raster_size"][0] == config["raster_size"][1] - self.raster_size = np.array([config["raster_size"][0], config["raster_size"][1]]) - self.pixels_per_meter = self.raster_size[0] / (self.radius_meter * 2) - self.meter_per_pixel = 1 / self.pixels_per_meter - self.lane_sample_resolution = config["lane_sample_resolution"] - - # list of all start waypoints in HD Map - topology = [x[0] for x in self.carla_map.get_topology()] - # sort by altitude - self.topology = sorted(topology, key=lambda w: w.transform.location.z) - - # basic elements in HDMap: lane, crosswalk and traffic light - self.lane_info = {} - self.crosswalk_info = {} - self.traffic_light_info = {} - # this is mainly used for efficient filtering - self.bound_info = {"lanes": {}, "crosswalks": {}} - self.traffic_stop_pos = [] - - self.retrieve_light_stop_pos() - # generate information for traffic light - self.generate_tl_info(self.world) - # generate lane, crosswalk and boundary information - self.generate_lane_cross_info() - - # generate labels - self.other_objs_info = retrieve_city_object_info(self.world, self.other_render_objs) - - # static related info - self.draw_lane = config["static"]["draw_lane"] - self.draw_traffic_light = config["static"]["draw_traffic_light"] - # dynamic related info - self.exclude_self = config["dynamic"]["exclude_self"] - self.exclude_off_road = config["dynamic"]["exclude_off_road"] - - # whether to check visibility in camera for ego only - self.visibility = config["dynamic"]["visibility"] - # visible agent id for ego - self.vis_ids = [] - - # whether to check visibility in camera for multi-agent perspective - self.visibility_corp = config["dynamic"]["visibility_corp"] - # visible agent id for all vehicles - self.vis_corp_ids = [] - - # bev maps - self.dynamic_bev = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.vis_mask = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.vis_corp_mask = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.static_bev = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.lane_bev = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.vis_bev = 255 * np.ones(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - - def run_step(self, cav_id, cav_content, veh_dict): - """ - Rasterization + Visualize the bev map if needed. - - Parameters - ---------- - cav_id : str - The cav's original id in the dataset. - cav_content : dict - The cav's information. - veh_dict : dict - All vehicles' information. Used for visibility check for coop. - """ - if not self.activate: - return - - self.actor_id = cav_content["actor_id"] - self.center = cav_content["cur_pose"] - self.agent_id = cav_id - self.current_timstamp = cav_content["cur_count"] - - # clean buffer every round starts - self.vis_ids = [] - self.vis_corp_ids = [] - - if self.visibility: - self.vis_ids = self.check_visibility_single(cav_content, self.vis_ids) - if self.visibility_corp: - self.vis_corp_ids = self.check_visibility_corp(veh_dict, self.vis_corp_ids) - - self.rasterize_static() - self.rasterize_dynamic() - - if self.exclude_off_road: - self.dynamic_bev = exclude_off_road_agents(self.static_bev, self.dynamic_bev) - - if self.visualize: - cv2.imshow("the bev map of agent %s" % self.agent_id, self.vis_bev) - cv2.waitKey(1) - self.data_dump() - - def check_visibility_corp(self, veh_dict, vis_corp_mask_list): - for _, veh_contnet in veh_dict.items(): - if "cav" in veh_contnet: - vis_corp_mask_list += self.check_visibility_single(veh_contnet, vis_corp_mask_list) - vis_corp_mask_list = list(set(vis_corp_mask_list)) - return vis_corp_mask_list - - # check visibility agent list for ego vehicle - @staticmethod - def check_visibility_single(cav_content, vis_mask_list): - """ - Retrieve visible objects in the ego camera. - - Parameters - ---------- - vis_mask_list : list - cav_content : dict - """ - - sensor_manager = cav_content["sensor_manager"] - meta_data = sensor_manager.sensor_meta - for key, item in meta_data.items(): - if "semantic_lidar" in key: - vis_mask_list += item - vis_mask_list = list(set(vis_mask_list)) - return vis_mask_list - - @staticmethod - def get_bounds(left_lane, right_lane): - """ - Get boundary information of a lane. - - Parameters - ---------- - left_lane : np.array - shape: (n, 3) - right_lane : np.array - shape: (n,3) - Returns - ------- - bound : np.array - """ - x_min = min(np.min(left_lane[:, 0]), np.min(right_lane[:, 0])) - y_min = min(np.min(left_lane[:, 1]), np.min(right_lane[:, 1])) - z_min = min(np.min(left_lane[:, 2]), np.min(right_lane[:, 2])) - x_max = max(np.max(left_lane[:, 0]), np.max(right_lane[:, 0])) - y_max = max(np.max(left_lane[:, 1]), np.max(right_lane[:, 1])) - z_max = max(np.max(left_lane[:, 2]), np.max(right_lane[:, 2])) - - bounds = np.asarray([[[x_min, y_min], [x_max, y_max], [z_min, z_max]]]) - - return bounds - - def agents_in_range(self, radius, agents_dict): - """ - Filter out all agents out of the radius. - - Parameters - ---------- - radius : float - Radius in meters - - agents_dict : dict - Dictionary containing all dynamic agents. - - Returns - ------- - The dictionary that only contains the agent in range. - """ - final_agents = {} - - # convert center to list format - center = [self.center.location.x, self.center.location.y] - - for agent_id, agent in agents_dict.items(): - location = agent["location"] - distance = math.sqrt((location[0] - center[0]) ** 2 + (location[1] - center[1]) ** 2) - if distance < radius: - final_agents.update({agent_id: agent}) - - return final_agents - - def indices_in_bounds(self, bounds: np.ndarray, half_extent: float) -> np.ndarray: - """ - Get indices of elements for which the bounding box described by bounds - intersects the one defined around center (square with side 2*half_side) - - Parameters - ---------- - bounds :np.ndarray - array of shape Nx2x2 [[x_min,y_min],[x_max, y_max]] - - half_extent : float - half the side of the bounding box centered around center - - Returns - ------- - np.ndarray: indices of elements inside radius from center - """ - x_center, y_center, z_center = self.center.location.x, self.center.location.y, self.center.location.z - - x_min_in = x_center > bounds[:, 0, 0] - half_extent - y_min_in = y_center > bounds[:, 0, 1] - half_extent - x_max_in = x_center < bounds[:, 1, 0] + half_extent - y_max_in = y_center < bounds[:, 1, 1] + half_extent - - z_min_in = abs(z_center - bounds[:, 2, 0]) < self.z_filter_value - z_max_in = abs(z_center - bounds[:, 2, 1]) < self.z_filter_value - - # this several scenes has some speciality and needs to be hardcoded - if self.scene_name in OR_Z_VALUE_MAP: - z_flag = z_min_in | z_max_in - else: - z_flag = z_min_in & z_max_in - - return np.nonzero(x_min_in & y_min_in & x_max_in & y_max_in & z_flag)[0] - - def retrieve_light_stop_pos(self): - # retrieve all stop and traffic light position - all_actors = self.world.get_actors() - for actor in all_actors: - if "traffic_light" in actor.type_id or "stop" in actor.type_id: - location = actor.get_transform().location - self.traffic_stop_pos.append((location.x, location.y)) - - def associate_lane_tl(self, mid_lane): - """ - Given the waypoints for a certain lane, find the traffic light that - influence it. - - Parameters - ---------- - mid_lane : np.ndarray - The middle line of the lane. - Returns - ------- - associate_tl_id : str - The associated traffic light id. - """ - associate_tl_id = "" - - for tl_id, tl_content in self.traffic_light_info.items(): - trigger_poly = tl_content["corners"] - # use Path to do fast computation - trigger_path = Path(trigger_poly.boundary) - # check if any point in the middle line inside the trigger area - check_array = trigger_path.contains_points(mid_lane[:, :2]) - - if check_array.any(): - associate_tl_id = tl_id - return associate_tl_id - - def data_dump(self): - """ - Dump the data to the corresponding folder. - """ - save_name = os.path.join(self.out_root, self.agent_id) - - if not os.path.exists(save_name): - os.makedirs(save_name) - - map_info = { - "bev_map": { - "raster_size_pixel": self.raster_size, - "radius_meter": self.radius_meter, - "pixel_per_meter": self.pixels_per_meter, - "topology": self.valid_lane_info, - } - } - if self.save_yml: - # save metadata - save_yaml_name = os.path.join(save_name, self.current_timstamp + "_additional.yaml") - save_yaml_wo_overwriting(map_info, save_yaml_name) - - if self.save_static: - # save rgb image - save_static_name = os.path.join(save_name, self.current_timstamp + "_bev_static.png") - cv2.imwrite(save_static_name, self.static_bev) - - if self.save_lane: - # save lane topology seg - save_lane_name = os.path.join(save_name, self.current_timstamp + "_bev_lane.png") - cv2.imwrite(save_lane_name, self.lane_bev) - - if self.save_dynamic: - # save dynamic bev - save_dynamic_name = os.path.join(save_name, self.current_timstamp + "_bev_dynamic.png") - cv2.imwrite(save_dynamic_name, self.dynamic_bev) - - if self.visibility: - # save visibility map for ego agent - save_visibility_name = os.path.join(save_name, self.current_timstamp + "_bev_visibility.png") - cv2.imwrite(save_visibility_name, self.vis_mask) - - if self.visibility_corp: - # save visibility map for ego agent - save_visibility_name = os.path.join(save_name, self.current_timstamp + "_bev_visibility_corp.png") - cv2.imwrite(save_visibility_name, self.vis_corp_mask) - - if self.save_bev_vis: - # save visualize bev - save_vis_name = os.path.join(save_name, self.current_timstamp + "_bev_vis.png") - cv2.imwrite(save_vis_name, self.vis_bev) - - def generate_lane_cross_info(self): - """ - From the topology generate all lane and crosswalk - information in a dictionary under world's coordinate frame. - """ - # list of str - lanes_id = [] - crosswalks_ids = [] - - # boundary of each lane for later filtering - lanes_bounds = np.empty((0, 3, 2), dtype=float) - crosswalks_bounds = np.empty((0, 3, 2), dtype=float) - - # for crosswalk information - crosswalks_list = self.split_cross_walks() - for i, crosswalk in enumerate(crosswalks_list): - crosswalk_id = uuid.uuid4().hex[:6].upper() - crosswalks_ids.append(crosswalk_id) - - cross_marking = np.array(crosswalk) - bound = self.get_bounds(cross_marking, cross_marking) - crosswalks_bounds = np.append(crosswalks_bounds, bound, axis=0) - - self.crosswalk_info.update({crosswalk_id: {"xyz": cross_marking}}) - - self.bound_info["crosswalks"]["ids"] = crosswalks_ids - self.bound_info["crosswalks"]["bounds"] = crosswalks_bounds - - # loop all waypoints to get lane information - for i, waypoint in enumerate(self.topology): - # unique id for each lane - lane_id = uuid.uuid4().hex[:6].upper() - lanes_id.append(lane_id) - - intersection_flag = True if waypoint.is_intersection and self.exclude_intersection_lane else False - # we need to exclude the intersections without traffic light/stops - if intersection_flag: - intersection_flag = False - for t_pos in self.traffic_stop_pos: - distance = np.sqrt((t_pos[0] - waypoint.transform.location.x) ** 2 + (t_pos[1] - waypoint.transform.location.y) ** 2) - if distance <= 30: - intersection_flag = True - break - - waypoints = [waypoint] - nxt = waypoint.next(self.lane_sample_resolution)[0] - # looping until next lane - while nxt.road_id == waypoint.road_id and nxt.lane_id == waypoint.lane_id: - waypoints.append(nxt) - nxt = nxt.next(self.lane_sample_resolution)[0] - - # waypoint is the centerline, we need to calculate left lane mark - left_marking = [lateral_shift(w.transform, -w.lane_width * 0.5) for w in waypoints] - right_marking = [lateral_shift(w.transform, w.lane_width * 0.5) for w in waypoints] - # convert the list of carla.Location to np.array - left_marking = list_loc2array(left_marking) - right_marking = list_loc2array(right_marking) - mid_lane = list_wpt2array(waypoints) - - # get boundary information - bound = self.get_bounds(left_marking, right_marking) - lanes_bounds = np.append(lanes_bounds, bound, axis=0) - - # associate with traffic light - tl_id = self.associate_lane_tl(mid_lane) - - self.lane_info.update( - { - lane_id: { - "xyz_left": left_marking, - "xyz_right": right_marking, - "xyz_mid": mid_lane, - "tl_id": tl_id, - "intersection_flag": intersection_flag, - } - } - ) - # boundary information - self.bound_info["lanes"]["ids"] = lanes_id - self.bound_info["lanes"]["bounds"] = lanes_bounds - - def split_cross_walks(self): - """ - Find each crosswalk with their key points. - """ - all_cross_walks = self.carla_map.get_crosswalks() - cross_walks_list = [] - - tmp_list = [] - for key_points in all_cross_walks: - if (key_points.x, key_points.y, key_points.z) in tmp_list: - cross_walks_list.append(tmp_list) - tmp_list = [] - else: - tmp_list.append((key_points.x, key_points.y, key_points.z)) - - return cross_walks_list - - def generate_tl_info(self, world): - """ - Generate traffic light information under world's coordinate frame. - - Parameters - ---------- - world : carla.world - Carla simulator env. - - Returns - ------- - A dictionary containing traffic lights information. - """ - tl_list = world.get_actors().filter("traffic.traffic_light*") - - for tl_actor in tl_list: - tl_id = uuid.uuid4().hex[:4].upper() - - base_transform = tl_actor.get_transform() - base_rot = base_transform.rotation.yaw - # this is where the vehicle stops - area_loc = base_transform.transform(tl_actor.trigger_volume.location) - area_transform = carla.Transform(area_loc, carla.Rotation(yaw=base_rot)) - - # bbx extent - ext = tl_actor.trigger_volume.extent - # the y is to narrow - ext.y += 0.5 - ext_corners = np.array([[-ext.x, -ext.y], [ext.x, -ext.y], [ext.x, ext.y], [-ext.x, ext.y]]) - for i in range(ext_corners.shape[0]): - corrected_loc = area_transform.transform(carla.Location(ext_corners[i][0], ext_corners[i][1])) - ext_corners[i, 0] = corrected_loc.x - ext_corners[i, 1] = corrected_loc.y - - # use shapely lib to convert to polygon - corner_poly = Polygon(ext_corners) - self.traffic_light_info.update( - {tl_id: {"actor": tl_actor, "corners": corner_poly, "base_rot": base_rot, "base_transform": base_transform}} - ) - - def generate_lane_area(self, xyz_left, xyz_right): - """ - Generate the lane area poly under rasterization map's center - coordinate frame. - - Parameters - ---------- - xyz_left : np.ndarray - Left lanemarking of a lane, shape: (n, 3). - xyz_right : np.ndarray - Right lanemarking of a lane, shape: (n, 3). - - Returns - ------- - lane_area : np.ndarray - Combine left and right lane together to form a polygon. - """ - lane_area = np.zeros((2, xyz_left.shape[0], 2)) - # convert coordinates to center's coordinate frame - xyz_left = xyz_left.T - xyz_left = np.r_[xyz_left, [np.ones(xyz_left.shape[1])]] - xyz_right = xyz_right.T - xyz_right = np.r_[xyz_right, [np.ones(xyz_right.shape[1])]] - - # ego's coordinate frame - xyz_left = world_to_sensor(xyz_left, self.center).T - xyz_right = world_to_sensor(xyz_right, self.center).T - - # to image coordinate frame - lane_area[0] = xyz_left[:, :2] - lane_area[1] = xyz_right[::-1, :2] - # switch x and y - lane_area = lane_area[..., ::-1] - # y revert - lane_area[:, :, 1] = -lane_area[:, :, 1] - - lane_area[:, :, 0] = lane_area[:, :, 0] * self.pixels_per_meter + self.raster_size[0] // 2 - lane_area[:, :, 1] = lane_area[:, :, 1] * self.pixels_per_meter + self.raster_size[1] // 2 - - # to make more precise polygon - lane_area = cv2_subpixel(lane_area) - - return lane_area - - def generate_cross_area(self, xyz): - """ - Generate the cross lane under rasterization map's center - coordinate frame. - - Parameters - ---------- - xyz : np.ndarray - Crosswalk lane marking, shape: (n, 3). - Returns - ------- - lane_area : np.ndarray - Combine up and down line together. - """ - if xyz.shape[0] == 2: - print("dman") - lane_area = np.zeros((2, xyz.shape[0] // 2, 2)) - # convert coordinates to center's coordinate frame - xyz = xyz.T - xyz = np.r_[xyz, [np.ones(xyz.shape[1])]] - - # ego's coordinate frame - xyz = world_to_sensor(xyz, self.center).T - - # to image coordinate frame - lane_area[0] = xyz[: xyz.shape[0] // 2, :2] - lane_area[1] = xyz[xyz.shape[0] // 2 :, :2] - # switch x and y - lane_area = lane_area[..., ::-1] - # y revert - lane_area[:, :, 1] = -lane_area[:, :, 1] - - lane_area[:, :, 0] = lane_area[:, :, 0] * self.pixels_per_meter + self.raster_size[0] // 2 - lane_area[:, :, 1] = lane_area[:, :, 1] * self.pixels_per_meter + self.raster_size[1] // 2 - # to make more precise polygon - lane_area = cv2_subpixel(lane_area) - - return lane_area - - def generate_agent_area(self, corners): - """ - Convert the agent's bbx corners from world coordinates to - rasterization coordinates. - - Parameters - ---------- - corners : list - The four corners of the agent's bbx under world coordinate. - - Returns - ------- - agent four corners in image. - """ - # (4, 3) numpy array - corners = np.array(corners) - # for homogeneous transformation - corners = corners.T - corners = np.r_[corners, [np.ones(corners.shape[1])]] - # convert to ego's coordinate frame - corners = world_to_sensor(corners, self.center).T - corners = corners[:, :2] - - # switch x and y - corners = corners[..., ::-1] - # y revert - corners[:, 1] = -corners[:, 1] - - corners[:, 0] = corners[:, 0] * self.pixels_per_meter + self.raster_size[0] // 2 - corners[:, 1] = corners[:, 1] * self.pixels_per_meter + self.raster_size[1] // 2 - - # to make more precise polygon - corner_area = cv2_subpixel(corners[:, :2]) - - return corner_area - - def load_agents_world(self): - """ - Load all the dynamic agents info from server directly - into a dictionary. - - Returns - ------- - The dictionary contains all agents info in the carla world. - """ - - agent_list = self.world.get_actors().filter("vehicle.*") - dynamic_agent_info = {} - - for agent in agent_list: - agent_id = agent.id - - agent_transform = agent.get_transform() - agent_loc = [ - agent_transform.location.x, - agent_transform.location.y, - agent_transform.location.z, - ] - - agent_yaw = agent_transform.rotation.yaw - - # calculate 4 corners - bb = agent.bounding_box.extent - corners = [ - carla.Location(x=-bb.x, y=-bb.y), - carla.Location(x=-bb.x, y=bb.y), - carla.Location(x=bb.x, y=bb.y), - carla.Location(x=bb.x, y=-bb.y), - ] - # corners are originally in ego coordinate frame, convert to - # world coordinate - agent_transform.transform(corners) - corners_reformat = [[x.x, x.y, x.z] for x in corners] - - dynamic_agent_info[agent_id] = {"location": agent_loc, "yaw": agent_yaw, "corners": corners_reformat} - return dynamic_agent_info - - def rasterize_dynamic(self): - """ - Rasterize the dynamic agents. - - Returns - ------- - Rasterization image. - """ - self.dynamic_bev = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.vis_mask = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.vis_corp_mask = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - # filter using half a radius from the center - raster_radius = float(np.linalg.norm(self.raster_size * np.array([self.meter_per_pixel, self.meter_per_pixel]))) / 2 - # retrieve all agents - dynamic_agents = self.load_agents_world() - # filter out agents out of range - final_agents = self.agents_in_range(raster_radius, dynamic_agents) - - corner_list = [] - vis_corner_list = [] - vis_corp_corner_list = [] - for agent_id, agent in final_agents.items(): - # in case we don't want to draw the cav itself - if agent_id == self.actor_id and self.exclude_self: - continue - agent_corner = self.generate_agent_area(agent["corners"]) - corner_list.append(agent_corner) - - # get visibility for single vehicle - if agent_id in self.vis_ids: - vis_corner_list.append(agent_corner) - # get visibility for multi-agents - if agent_id in self.vis_corp_ids: - vis_corp_corner_list.append(agent_corner) - - self.dynamic_bev = draw_agent(corner_list, self.dynamic_bev) - self.vis_bev = draw_agent(corner_list, self.vis_bev) - self.vis_mask = draw_agent(vis_corner_list, self.vis_mask) - self.vis_corp_mask = draw_agent(vis_corp_corner_list, self.vis_corp_mask) - - def rasterize_static(self): - """ - Generate the static bev map. - """ - self.static_bev = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.lane_bev = 255 * np.zeros(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - self.vis_bev = 255 * np.ones(shape=(self.raster_size[1], self.raster_size[0], 3), dtype=np.uint8) - - # filter using half a radius from the center - raster_radius = float(np.linalg.norm(self.raster_size * np.array([self.meter_per_pixel, self.meter_per_pixel]))) / 2 - lane_indices = self.indices_in_bounds(self.bound_info["lanes"]["bounds"], raster_radius) - cross_indices = self.indices_in_bounds(self.bound_info["crosswalks"]["bounds"], raster_radius) - - # --------- Retrieve road topology related first ---------- # - lanes_area_list = [] - cross_area_list = [] - lane_type_list = [] - intersection_list = [] - - for idx, cross_idx in enumerate(cross_indices): - cross_idx = self.bound_info["crosswalks"]["ids"][cross_idx] - cross_info = self.crosswalk_info[cross_idx] - - cross_area = self.generate_cross_area(cross_info["xyz"]) - cross_area_list.append(cross_area) - - for idx, lane_idx in enumerate(lane_indices): - lane_idx = self.bound_info["lanes"]["ids"][lane_idx] - lane_info = self.lane_info[lane_idx] - - # save the topology info for this cav - self.valid_lane_info = lane_info - - xyz_left, xyz_right = lane_info["xyz_left"], lane_info["xyz_right"] - - # generate lane area - lane_area = self.generate_lane_area(xyz_left, xyz_right) - lanes_area_list.append(lane_area) - - # intersection flag - intersection_list.append(lane_info["intersection_flag"]) - - # check the associated traffic light - associated_tl_id = lane_info["tl_id"] - if associated_tl_id and self.draw_traffic_light: - tl_actor = self.traffic_light_info[associated_tl_id]["actor"] - status = convert_tl_status(tl_actor.get_state()) - lane_type_list.append(status) - else: - lane_type_list.append("normal") - - self.static_bev = draw_road(lanes_area_list, self.static_bev) - if self.exclude_road: - self.static_bev = road_exclude(self.static_bev) - - if self.draw_lane: - self.lane_bev = draw_lane(lanes_area_list, lane_type_list, self.lane_bev, intersection_list, vis=False) - self.lane_bev = draw_crosswalks(cross_area_list, self.lane_bev) - self.lane_bev[self.static_bev == 0] = 0 - - # --------- Retrieve city objects such as buildings ---------- # - # first filter out objects out of range - final_city_objs = obj_in_range(self.center, raster_radius, self.other_objs_info) - for _, obj_content in final_city_objs.items(): - for obj_id, obj in obj_content.items(): - corner_area = self.generate_agent_area(obj["corners"]) - obj["corner_area"] = corner_area - - # we try to draw everything for visualization, but only dumping the - # elements we need. - self.vis_bev = draw_city_objects(final_city_objs, self.vis_bev) - self.vis_bev = draw_road(lanes_area_list, self.vis_bev, visualize=True) - self.vis_bev = draw_lane(lanes_area_list, lane_type_list, self.vis_bev, intersection_list) - self.vis_bev = draw_crosswalks(cross_area_list, self.vis_bev) - self.vis_bev = cv2.cvtColor(self.vis_bev, cv2.COLOR_RGB2BGR) - - def destroy(self): - cv2.destroyAllWindows() diff --git a/OpenCOOD/logreplay/map/map_utils.py b/OpenCOOD/logreplay/map/map_utils.py deleted file mode 100644 index dcbcabef..00000000 --- a/OpenCOOD/logreplay/map/map_utils.py +++ /dev/null @@ -1,241 +0,0 @@ -"""HDMap utilities""" - -import carla -import numpy as np -import uuid -import math - -LABEL_TO_CARLA = {"building": carla.CityObjectLabel.Buildings, "terrain": carla.CityObjectLabel.Terrain, "sidewalk": carla.CityObjectLabel.Sidewalks} - - -def lateral_shift(transform, shift): - transform.rotation.yaw += 90 - return transform.location + shift * transform.get_forward_vector() - - -def list_loc2array(list_location): - """ - Convert list of carla location to np.array - Parameters - ---------- - list_location : list - List of carla locations. - - Returns - ------- - loc_array : np.array - Numpy array of shape (N, 3) - """ - loc_array = np.zeros((len(list_location), 3)) - for i, carla_location in enumerate(list_location): - loc_array[i, 0] = carla_location.x - loc_array[i, 1] = carla_location.y - loc_array[i, 2] = carla_location.z - - return loc_array - - -def list_wpt2array(list_wpt): - """ - Convert list of carla transform to np.array - Parameters - ---------- - list_wpt : list - List of carla waypoint. - - Returns - ------- - loc_array : np.array - Numpy array of shape (N, 3) - """ - loc_array = np.zeros((len(list_wpt), 3)) - for i, carla_wpt in enumerate(list_wpt): - loc_array[i, 0] = carla_wpt.transform.location.x - loc_array[i, 1] = carla_wpt.transform.location.y - loc_array[i, 2] = carla_wpt.transform.location.z - - return loc_array - - -def convert_tl_status(status): - """ - Convert carla.TrafficLightState to str. - Parameters - ---------- - status : carla.TrafficLightState - - Returns - ------- - status_str : str - """ - if status == carla.TrafficLightState.Red: - return "red" - elif status == carla.TrafficLightState.Green: - return "green" - elif status == carla.TrafficLightState.Yellow: - return "yellow" - else: - return "normal" - - -def x_to_world_transformation(transform): - """ - Get the transformation matrix from x(it can be vehicle or sensor) - coordinates to world coordinate. - - Parameters - ---------- - transform : carla.Transform - The transform that contains location and rotation - - Returns - ------- - matrix : np.ndarray - The transformation matrx. - - """ - rotation = transform.rotation - location = transform.location - - # used for rotation matrix - c_y = np.cos(np.radians(rotation.yaw)) - s_y = np.sin(np.radians(rotation.yaw)) - c_r = np.cos(np.radians(rotation.roll)) - s_r = np.sin(np.radians(rotation.roll)) - c_p = np.cos(np.radians(rotation.pitch)) - s_p = np.sin(np.radians(rotation.pitch)) - - matrix = np.identity(4) - # translation matrix - matrix[0, 3] = location.x - matrix[1, 3] = location.y - matrix[2, 3] = location.z - - # rotation matrix - matrix[0, 0] = c_p * c_y - matrix[0, 1] = c_y * s_p * s_r - s_y * c_r - matrix[0, 2] = -c_y * s_p * c_r - s_y * s_r - matrix[1, 0] = s_y * c_p - matrix[1, 1] = s_y * s_p * s_r + c_y * c_r - matrix[1, 2] = -s_y * s_p * c_r + c_y * s_r - matrix[2, 0] = s_p - matrix[2, 1] = -c_p * s_r - matrix[2, 2] = c_p * c_r - - return matrix - - -def world_to_sensor(cords, sensor_transform): - """ - Transform coordinates from world reference to sensor reference. - - Parameters - ---------- - cords : np.ndarray - Coordinates under world reference, shape: (4, n). - - sensor_transform : carla.Transform - Sensor position in the world. - - Returns - ------- - sensor_cords : np.ndarray - Coordinates in the sensor reference. - - """ - sensor_world_matrix = x_to_world_transformation(sensor_transform) - world_sensor_matrix = np.linalg.inv(sensor_world_matrix) - sensor_cords = np.dot(world_sensor_matrix, cords) - - return sensor_cords - - -def exclude_off_road_agents(static_bev, dynamic_bev): - dynamic_bev[static_bev == 0] = 0 - return dynamic_bev - - -def retrieve_city_object_info(world, label_list): - """ - A general function to retrieve object bbx in carla world except vehicle, - lane, crosswalk and road. - - Parameters - ---------- - world : carla.World - Carla world object - label_list : list of str - The label that users want to retrieve. - - Returns - ------- - A dictionary with information of the retrieved objects. - """ - city_object_info = {} - for label_name in label_list: - object_ins = world.get_level_bbs(LABEL_TO_CARLA[label_name]) - - obj_info = {} - - for obj in object_ins: - obj_id = uuid.uuid4().hex[:6].upper() - - obj_transform = carla.Transform(obj.location, obj.rotation) - obj_loc = [ - obj.location.x, - obj.location.y, - obj.location.z, - ] - obj_yaw = obj.rotation.yaw - - # calculate 4 corners - bb = obj.extent - corners = [ - carla.Location(x=-bb.x, y=-bb.y), - carla.Location(x=-bb.x, y=bb.y), - carla.Location(x=bb.x, y=bb.y), - carla.Location(x=bb.x, y=-bb.y), - ] - - obj_transform.transform(corners) - corners_reformat = [[x.x, x.y, x.z] for x in corners] - - obj_info[obj_id] = {"location": obj_loc, "yaw": obj_yaw, "corners": corners_reformat} - - city_object_info.update({label_name: obj_info}) - - return city_object_info - - -def obj_in_range(center, radius, obj_info_dict): - """ - Retrieve the object in range. - - Parameters - ---------- - center : carla.Transform - The ego position - - radius : float - Valid radius. - - obj_info_dict : dict - - Returns - ------- - A dictionary that contains objects in range. - """ - final_objs = {} - - for obj_category, obj_contents in obj_info_dict.items(): - cur_objs = {} - for obj_id, obj_info in obj_contents.items(): - corners = obj_info["corners"] - for corner in corners: - distance = math.sqrt((corner[0] - center.location.x) ** 2 + (corner[1] - center.location.y) ** 2) - if distance < radius: - cur_objs.update({obj_id: obj_info}) - break - final_objs.update({obj_category: cur_objs}) - - return final_objs diff --git a/OpenCOOD/logreplay/scenario/__init__.py b/OpenCOOD/logreplay/scenario/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/logreplay/scenario/scenarios_manager.py b/OpenCOOD/logreplay/scenario/scenarios_manager.py deleted file mode 100644 index 5a2aa022..00000000 --- a/OpenCOOD/logreplay/scenario/scenarios_manager.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -from collections import OrderedDict - -from opencood.hypes_yaml.yaml_utils import load_yaml -from logreplay.scenario.scene_manager import SceneManager - - -class ScenariosManager: - """ - Format all scenes in a structured way. - - Parameters - ---------- - scenario_params: dict - Overall parameters for the replayed scenes. - - Attributes - ---------- - - """ - - def __init__(self, scenario_params): - # this defines carla world sync mode, weather, town name, and seed. - self.scene_params = scenario_params - - # e.g. /opv2v/data/train - root_dir = self.scene_params["root_dir"] - - # first load all paths of different scenarios - scenario_folders = sorted([os.path.join(root_dir, x) for x in os.listdir(root_dir) if os.path.isdir(os.path.join(root_dir, x))]) - self.scenario_database = OrderedDict() - - # loop over all scenarios - for i, scenario_folder in enumerate(scenario_folders): - scene_name = os.path.split(scenario_folder)[-1] - self.scenario_database.update({scene_name: OrderedDict()}) - - # load the collection yaml file - protocol_yml = [x for x in os.listdir(scenario_folder) if x.endswith(".yaml")] - collection_params = load_yaml(os.path.join(scenario_folder, protocol_yml[0])) - - # create the corresponding scene manager - cur_sg = SceneManager(scenario_folder, scene_name, collection_params, scenario_params) - self.scenario_database[scene_name].update({"scene_manager": cur_sg}) - - def tick(self): - """ - Tick for every scene manager to do the log replay. - """ - for scene_name, scene_content in self.scenario_database.items(): - print("log replay %s" % scene_name) - scene_manager = scene_content["scene_manager"] - run_flag = True - - scene_manager.start_simulator() - - while run_flag: - run_flag = scene_manager.tick() - - scene_manager.close() - - -if __name__ == "__main__": - from opencood.hypes_yaml.yaml_utils import load_yaml - - scene_params = load_yaml("../hypes_yaml/replay.yaml") - scenarion_manager = ScenariosManager(scenario_params=scene_params) - scenarion_manager.tick() - print("test passed") diff --git a/OpenCOOD/logreplay/scenario/scene_manager.py b/OpenCOOD/logreplay/scenario/scene_manager.py deleted file mode 100644 index f49356f3..00000000 --- a/OpenCOOD/logreplay/scenario/scene_manager.py +++ /dev/null @@ -1,432 +0,0 @@ -import os -import random -import sys -from collections import OrderedDict - -import carla -import numpy as np - -from logreplay.assets.utils import find_town, find_blue_print -from logreplay.assets.presave_lib import bcolors -from logreplay.map.map_manager import MapManager -from logreplay.sensors.sensor_manager import SensorManager -from opencood.hypes_yaml.yaml_utils import load_yaml - - -class SceneManager: - """ - Manager for each scene for spawning, moving and destroying. - - Parameters - ---------- - folder : str - The folder to the current scene. - - scene_name : str - The scene's name. - - collection_params : dict - The collecting protocol information. - - scenario_params : dict - The replay params. - """ - - def __init__(self, folder, scene_name, collection_params, scenario_params): - self.scene_name = scene_name - self.town_name = find_town(scene_name) - self.collection_params = collection_params - self.scenario_params = scenario_params - self.cav_id_list = [] - - # dumping related - self.output_root = os.path.join(scenario_params["output_dir"], scene_name) - - if "seed" in collection_params["world"]: - np.random.seed(collection_params["world"]["seed"]) - random.seed(collection_params["world"]["seed"]) - - # at least 1 cav should show up - cav_list = sorted([x for x in os.listdir(folder) if os.path.isdir(os.path.join(folder, x))]) - assert len(cav_list) > 0 - - self.database = OrderedDict() - # we want to save timestamp as the parent keys for cavs - cav_sample = os.path.join(folder, cav_list[0]) - - yaml_files = sorted([os.path.join(cav_sample, x) for x in os.listdir(cav_sample) if x.endswith(".yaml") and "additional" not in x]) - self.timestamps = self.extract_timestamps(yaml_files) - - # loop over all timestamps - for timestamp in self.timestamps: - self.database[timestamp] = OrderedDict() - # loop over all cavs - for j, cav_id in enumerate(cav_list): - if cav_id not in self.cav_id_list: - self.cav_id_list.append(str(cav_id)) - - self.database[timestamp][cav_id] = OrderedDict() - cav_path = os.path.join(folder, cav_id) - - yaml_file = os.path.join(cav_path, timestamp + ".yaml") - self.database[timestamp][cav_id]["yaml"] = yaml_file - - # this is used to dynamically save all information of the objects - self.veh_dict = OrderedDict() - # used to count timestamp - self.cur_count = 0 - # used for HDMap - self.map_manager = None - - def start_simulator(self): - """ - Connect to the carla simulator for log replay. - """ - simulation_config = self.collection_params["world"] - - # simulation sync mode time step - fixed_delta_seconds = simulation_config["fixed_delta_seconds"] - weather_config = simulation_config["weather"] if "weather" in simulation_config else None - - # setup the carla client - self.client = carla.Client("localhost", simulation_config["client_port"]) - self.client.set_timeout(10.0) - - # load the map - if self.town_name != "Culver_City": - try: - self.world = self.client.load_world(self.town_name) - except RuntimeError: - print( - f"{bcolors.FAIL} %s is not found in your CARLA repo! " - f"Please download all town maps to your CARLA " - f"repo!{bcolors.ENDC}" % self.town_name - ) - else: - self.world = self.client.get_world() - - if not self.world: - sys.exit("World loading failed") - - # setup the new setting - self.origin_settings = self.world.get_settings() - new_settings = self.world.get_settings() - - new_settings.synchronous_mode = True # noqa: DC05 - new_settings.fixed_delta_seconds = fixed_delta_seconds - - self.world.apply_settings(new_settings) - # set weather if needed - if weather_config is not None: - weather = self.set_weather(weather_config) - self.world.set_weather(weather) - # get map - self.carla_map = self.world.get_map() - # spectator - self.spectator = self.world.get_spectator() - # hd map manager per scene - self.map_manager = MapManager(self.world, self.scenario_params["map"], self.output_root, self.scene_name) - - def tick(self): - """ - Spawn the vehicle to the correct position - """ - if self.cur_count >= len(self.timestamps): - return False - - cur_timestamp = self.timestamps[self.cur_count] - cur_database = self.database[cur_timestamp] - - for i, (cav_id, cav_yml) in enumerate(cur_database.items()): - cav_content = load_yaml(cav_yml["yaml"]) - if cav_id not in self.veh_dict: - self.spawn_cav(cav_id, cav_content, cur_timestamp) - else: - self.move_vehicle(cav_id, cur_timestamp, self.structure_transform_cav((cav_content["true_ego_pos"]))) - - self.veh_dict[cav_id]["cav"] = True - # spawn the sensor on each cav - if "sensor_manager" not in self.veh_dict[cav_id]: - self.veh_dict[cav_id]["sensor_manager"] = SensorManager( - cav_id, self.veh_dict[cav_id], self.world, self.scenario_params["sensor"], self.output_root - ) - - # set the spectator to the first cav - if i == 0: - transform = self.structure_transform_cav(cav_content["true_ego_pos"]) - self.spectator.set_transform(carla.Transform(transform.location + carla.Location(z=70), carla.Rotation(pitch=-90))) - - for bg_veh_id, bg_veh_content in cav_content["vehicles"].items(): - if str(bg_veh_id) not in self.veh_dict: - self.spawn_bg_vehicles(bg_veh_id, bg_veh_content, cur_timestamp) - else: - self.move_vehicle( - str(bg_veh_id), cur_timestamp, self.structure_transform_bg_veh(bg_veh_content["location"], bg_veh_content["angle"]) - ) - # remove the vehicles that are not in any cav's scope - self.destroy_vehicle(cur_timestamp) - - self.cur_count += 1 - self.world.tick() - - # we dump data after tick() so the agent can retrieve the newest info - self.sensor_dumping(cur_timestamp) - self.map_dumping() - - return True - - def map_dumping(self): - """ - Dump bev map related. - - Parameters - ---------- - """ - for veh_id, veh_content in self.veh_dict.items(): - if "cav" in veh_content: - self.map_manager.run_step(veh_id, veh_content, self.veh_dict) - - def sensor_dumping(self, cur_timestamp): - for veh_id, veh_content in self.veh_dict.items(): - if "sensor_manager" in veh_content: - veh_content["sensor_manager"].run_step(cur_timestamp) - - def spawn_cav(self, cav_id, cav_content, cur_timestamp): - """ - Spawn the cav based on current content. - - Parameters - ---------- - cav_id : str - The saved cav_id. - - cav_content : dict - The information in the cav's folder. - - cur_timestamp : str - This is used to judge whether this vehicle has been already - called in this timestamp. - """ - - # cav always use lincoln - model = "vehicle.lincoln.mkz_2017" - - # retrive the blueprint library - blueprint_library = self.world.get_blueprint_library() - cav_bp = blueprint_library.find(model) - # cav is always green - color = "0, 0, 255" - cav_bp.set_attribute("color", color) - - cur_pose = cav_content["true_ego_pos"] - # convert to carla needed format - cur_pose = self.structure_transform_cav(cur_pose) - - # spawn the vehicle - vehicle = self.world.try_spawn_actor(cav_bp, cur_pose) - - while not vehicle: - cur_pose.location.z += 0.01 - vehicle = self.world.try_spawn_actor(cav_bp, cur_pose) - - self.veh_dict.update( - { - str(cav_id): { - "cur_pose": cur_pose, - "model": model, - "color": color, - "actor_id": vehicle.id, - "actor": vehicle, - "cur_count": cur_timestamp, - } - } - ) - - def spawn_bg_vehicles(self, bg_veh_id, bg_veh_content, cur_timestamp): - """ - Spawn the background vehicle. - - Parameters - ---------- - bg_veh_id : str - The id of the bg vehicle. - bg_veh_content : dict - The contents of the bg vehicle - cur_timestamp : str - This is used to judge whether this vehicle has been already - called in this timestamp. - """ - # retrieve the blueprint library - blueprint_library = self.world.get_blueprint_library() - - cur_pose = self.structure_transform_bg_veh(bg_veh_content["location"], bg_veh_content["angle"]) - if str(bg_veh_id) in self.cav_id_list: - model = "vehicle.lincoln.mkz_2017" - veh_bp = blueprint_library.find(model) - color = "0, 0, 255" - else: - model = find_blue_print(bg_veh_content["extent"]) - if not model: - print("model net found for %s" % bg_veh_id) - veh_bp = blueprint_library.find(model) - - color = random.choice(veh_bp.get_attribute("color").recommended_values) - - veh_bp.set_attribute("color", color) - - # spawn the vehicle - vehicle = self.world.try_spawn_actor(veh_bp, cur_pose) - - while not vehicle: - cur_pose.location.z += 0.01 - vehicle = self.world.try_spawn_actor(veh_bp, cur_pose) - - self.veh_dict.update( - { - str(bg_veh_id): { - "cur_pose": cur_pose, - "model": model, - "color": color, - "actor_id": vehicle.id, - "actor": vehicle, - "cur_count": cur_timestamp, - } - } - ) - - def move_vehicle(self, veh_id, cur_timestamp, transform): - """ - Move the - - Parameters - ---------- - veh_id : str - Vehicle's id - - cur_timestamp : str - Current timestamp - - transform : carla.Transform - Current pose/ - """ - # this represent this vehicle is already moved in this round - if self.veh_dict[veh_id]["cur_count"] == cur_timestamp: - return - - self.veh_dict[veh_id]["actor"].set_transform(transform) - self.veh_dict[veh_id]["cur_count"] = cur_timestamp - self.veh_dict[veh_id]["cur_pose"] = transform - - def close(self): - self.world.apply_settings(self.origin_settings) - actor_list = self.world.get_actors() - for actor in actor_list: - actor.destroy() - self.map_manager.destroy() - self.sensor_destory() - - def sensor_destory(self): - for veh_id, veh_content in self.veh_dict.items(): - if "sensor_manager" in veh_content: - veh_content["sensor_manager"].destroy() - - def destroy_vehicle(self, cur_timestamp): - """ - This is used to destroy the vehicles that are out of any cav's scope. - """ - destroy_list = [] - for veh_id, veh_content in self.veh_dict.items(): - if veh_content["cur_count"] != cur_timestamp: - veh_content["actor"].destroy() - destroy_list.append(veh_id) - - for veh_id in destroy_list: - self.veh_dict.pop(veh_id) - - def structure_transform_cav(self, pose): - """ - Convert the pose saved in list to transform format. - - Parameters - ---------- - pose : list - x, y, z, roll, yaw, pitch - - Returns - ------- - carla.Transform - """ - cur_pose = carla.Transform(carla.Location(x=pose[0], y=pose[1], z=pose[2]), carla.Rotation(roll=pose[3], yaw=pose[4], pitch=pose[5])) - - return cur_pose - - @staticmethod - def structure_transform_bg_veh(location, rotation): - """ - Convert the location and rotation in list to carla transform format. - - Parameters - ---------- - location : list - rotation : list - Returns - ------- - carla.Transform - """ - cur_pose = carla.Transform( - carla.Location(x=location[0], y=location[1], z=location[2]), carla.Rotation(roll=rotation[0], yaw=rotation[1], pitch=rotation[2]) - ) - - return cur_pose - - @staticmethod - def extract_timestamps(yaml_files): - """ - Given the list of the yaml files, extract the mocked timestamps. - - Parameters - ---------- - yaml_files : list - The full path of all yaml files of ego vehicle - - Returns - ------- - timestamps : list - The list containing timestamps only. - """ - timestamps = [] - - for file in yaml_files: - res = file.split("/")[-1] - - timestamp = res.replace(".yaml", "") - timestamps.append(timestamp) - - return timestamps - - @staticmethod - def set_weather(weather_settings): - """ - Set CARLA weather params. - - Parameters - ---------- - weather_settings : dict - The dictionary that contains all parameters of weather. - - Returns - ------- - The CARLA weather setting. - """ - weather = carla.WeatherParameters( - sun_altitude_angle=weather_settings["sun_altitude_angle"], - cloudiness=weather_settings["cloudiness"], - precipitation=weather_settings["precipitation"], - precipitation_deposits=weather_settings["precipitation_deposits"], - wind_intensity=weather_settings["wind_intensity"], - fog_density=weather_settings["fog_density"], - fog_distance=weather_settings["fog_distance"], - fog_falloff=weather_settings["fog_falloff"], - wetness=weather_settings["wetness"], - ) - return weather diff --git a/OpenCOOD/logreplay/sensors/__init__.py b/OpenCOOD/logreplay/sensors/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/logreplay/sensors/base_sensor.py b/OpenCOOD/logreplay/sensors/base_sensor.py deleted file mode 100644 index 95be66f2..00000000 --- a/OpenCOOD/logreplay/sensors/base_sensor.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -Base Class for sensors -""" - -import cv2 - - -class BaseSensor: - def __init__(self, agent_id, vehicle, world, config, global_position): - return - - def visualize_data(self): - return - - def data_dump(self, output_folder, cur_timestamp): - return - - def tick(self): - return None - - def destroy(self): - self.sensor.destroy() - cv2.destroyAllWindows() diff --git a/OpenCOOD/logreplay/sensors/bev_semantic_camera.py b/OpenCOOD/logreplay/sensors/bev_semantic_camera.py deleted file mode 100644 index d1bdaaed..00000000 --- a/OpenCOOD/logreplay/sensors/bev_semantic_camera.py +++ /dev/null @@ -1,197 +0,0 @@ -""" -CARLA Semantic Camera Sensor -""" - -import os.path -import weakref - -import carla -import cv2 -import numpy as np - -from opencood.hypes_yaml.yaml_utils import save_yaml_wo_overwriting -from logreplay.sensors.utils import get_camera_intrinsic -from logreplay.sensors.base_sensor import BaseSensor - - -class BEVSemanticCamera(BaseSensor): - """ - BEV camera for semantic segmentation. - - Parameters - ---------- - vehicle : carla.Vehicle - The carla.Vehicle, this is for cav. - - world : carla.World - The carla world object, this is for rsu. - - config : dict - Semantic camera configuration. - - global_position : list - Global position of the infrastructure, [x, y, z] - - Attributes - ---------- - image : np.ndarray - Current received rgb image. - sensor : carla.sensor - The carla sensor that mounts at the vehicle. - - """ - - def __init__(self, agent_id, vehicle, world, config, global_position): - super().__init__(agent_id, vehicle, world, config, global_position) - if vehicle is not None: - world = vehicle.get_world() - - self.agent_id = agent_id - self.name = "bev_semantic_camera" - - blueprint = world.get_blueprint_library().find("sensor.camera.semantic_segmentation") - blueprint.set_attribute("fov", str(config["fov"])) - blueprint.set_attribute("image_size_x", str(config["image_size_x"])) - blueprint.set_attribute("image_size_y", str(config["image_size_y"])) - self.height = config["height"] - self.visualize = config["visualize"] - - spawn_point = self.spawn_point_estimation(global_position) - - if vehicle is not None: - self.sensor = world.spawn_actor(blueprint, spawn_point, attach_to=vehicle) - else: - self.sensor = world.spawn_actor(blueprint, spawn_point) - - self.image = None - self.timstamp = None # noqa: DC05 - self.frame = 0 - weak_self = weakref.ref(self) - self.sensor.listen(lambda event: BEVSemanticCamera._on_rgb_image_event(weak_self, event)) - - # camera attributes - self.image_width = int(self.sensor.attributes["image_size_x"]) - self.image_height = int(self.sensor.attributes["image_size_y"]) - - def spawn_point_estimation(self, global_position): - pitch = -90 - carla_location = carla.Location(x=0, y=0, z=self.height) - - if global_position is not None: - carla_location = carla.Location(x=global_position[0], y=global_position[1], z=self.height) - - carla_rotation = carla.Rotation(roll=0, yaw=0, pitch=pitch) - spawn_point = carla.Transform(carla_location, carla_rotation) - - return spawn_point - - @staticmethod - def labels_to_array(bgr_image): - """ - Convert an image containing CARLA semantic segmentation labels to a - 2D array containing the label of each pixel. - - Parameters - ---------- - bgr_image : np.ndarray - BGR image - """ - return bgr_image[:, :, 2] - - @staticmethod - def labels_to_cityscapes_palette(label): - """ - Convert an image containing CARLA semantic segmentation labels to - Cityscapes palette. - - Parameters - ---------- - label : np.ndarray - label image, (h, w) - - Returns - ------- - Converted BGR image. - """ - classes = { - 0: [0, 0, 0], # None - 1: [70, 70, 70], # Buildings - 2: [190, 153, 153], # Fences - 3: [72, 0, 90], # Other - 4: [220, 20, 60], # Pedestrians - 5: [153, 153, 153], # Poles - 6: [157, 234, 50], # RoadLines - 7: [128, 64, 128], # Roads - 8: [244, 35, 232], # Sidewalks - 9: [107, 142, 35], # Vegetation - 10: [0, 0, 255], # Vehicles - 11: [102, 102, 156], # Walls - 12: [220, 220, 0], # TrafficSigns - 13: [70, 130, 180], # Sky - 14: [81, 0, 81], # Ground - 15: [150, 100, 100], # Bridge - 16: [230, 150, 140], # RailTrack - 17: [180, 165, 180], # All types of guard rails/crash barriers. - 18: [250, 170, 30], # Traffic Light - 19: [110, 190, 160], # Static - 20: [170, 120, 50], # Dynamic - 21: [45, 60, 150], # Water - 22: [145, 170, 100], # Terrain - } - result = np.zeros((label.shape[0], label.shape[1], 3), dtype=np.uint8) - for key, value in classes.items(): - result[np.where(label == key)] = value - return result - - @staticmethod - def _on_rgb_image_event(weak_self, event): - """CAMERA method""" - self = weak_self() - if not self: - return - image = np.array(event.raw_data) - image = image.reshape((self.image_height, self.image_width, 4)) - # we need to remove the alpha channel - image = image[:, :, :3] - # self.image is the dumped data - self.image = np.array(self.labels_to_array(image), dtype=int) - self.vis_image = cv2.cvtColor(self.labels_to_cityscapes_palette(self.image), cv2.COLOR_BGR2RGB) - - self.frame = event.frame - self.timestamp = event.timestamp - - def visualize_data(self): - if self.visualize: - while not hasattr(self, "vis_image") or self.vis_image is None: - continue - cv2.imshow("bev seg camera agent %s" % self.agent_id, self.vis_image) - cv2.waitKey(1) - - def data_dump(self, output_root, cur_timestamp): - while not hasattr(self, "vis_image") or self.vis_image is None: - continue - # dump visualization - output_vis_name = os.path.join(output_root, cur_timestamp + "_bev_sem_vis.png") - cv2.imwrite(output_vis_name, self.vis_image) - - # dump the label - output_label_name = os.path.join(output_root, cur_timestamp + "_bev_sem_label.png") - cv2.imwrite(output_label_name, self.image) - - # dump the yaml - save_yaml_name = os.path.join(output_root, cur_timestamp + "_additional.yaml") - # intrinsic - camera_intrinsic = get_camera_intrinsic(self.sensor) - # pose under world coordinate system - camera_transformation = self.sensor.get_transform() - cords = [ - camera_transformation.location.x, - camera_transformation.location.y, - camera_transformation.location.z, - camera_transformation.rotation.roll, - camera_transformation.rotation.yaw, - camera_transformation.rotation.pitch, - ] - - bev_sem_cam_info = {"bev_sem_cam": {"intrinsic": camera_intrinsic, "cords": cords}} - save_yaml_wo_overwriting(bev_sem_cam_info, save_yaml_name) diff --git a/OpenCOOD/logreplay/sensors/semantic_lidar.py b/OpenCOOD/logreplay/sensors/semantic_lidar.py deleted file mode 100644 index 0d6bc4a9..00000000 --- a/OpenCOOD/logreplay/sensors/semantic_lidar.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -This is mainly used to filter out objects that is not in the sight -of cameras. -""" - -import weakref - -import carla -import numpy as np -from logreplay.sensors.base_sensor import BaseSensor - - -class SemanticLidar(BaseSensor): - def __init__(self, agent_id, vehicle, world, config, global_position): - super().__init__(agent_id, vehicle, world, config, global_position) - - if vehicle is not None: - world = vehicle.get_world() - - self.agent_id = agent_id - - blueprint = world.get_blueprint_library().find("sensor.lidar.ray_cast_semantic") - # set attribute based on the configuration - blueprint.set_attribute("upper_fov", str(config["upper_fov"])) - blueprint.set_attribute("lower_fov", str(config["lower_fov"])) - blueprint.set_attribute("channels", str(config["channels"])) - blueprint.set_attribute("range", str(config["range"])) - blueprint.set_attribute("points_per_second", str(config["points_per_second"])) - blueprint.set_attribute("rotation_frequency", str(config["rotation_frequency"])) - - relative_position = config["relative_pose"] - spawn_point = self.spawn_point_estimation(relative_position, global_position) - self.name = "semantic_lidar" + str(relative_position) - self.thresh = config["thresh"] - - if vehicle is not None: - self.sensor = world.spawn_actor(blueprint, spawn_point, attach_to=vehicle) - else: - self.sensor = world.spawn_actor(blueprint, spawn_point) - - # lidar data - self.points = None - self.obj_idx = None - self.obj_tag = None - - self.timestamp = None - self.frame = 0 - - weak_self = weakref.ref(self) - self.sensor.listen(lambda event: SemanticLidar._on_data_event(weak_self, event)) - - @staticmethod - def _on_data_event(weak_self, event): - """Semantic Lidar method""" - self = weak_self() - if not self: - return - - # shape:(n, 6) - data = np.frombuffer( - event.raw_data, - dtype=np.dtype( - [("x", np.float32), ("y", np.float32), ("z", np.float32), ("CosAngle", np.float32), ("ObjIdx", np.uint32), ("ObjTag", np.uint32)] - ), - ) - - # (x, y, z, intensity) - self.points = np.array([data["x"], data["y"], data["z"]]).T - self.obj_tag = np.array(data["ObjTag"]) - self.obj_idx = np.array(data["ObjIdx"]) - - self.data = data - self.frame = event.frame - self.timestamp = event.timestamp - - @staticmethod - def spawn_point_estimation(relative_position, global_position): - pitch = 0 - carla_location = carla.Location(x=0, y=0, z=0) - - if global_position is not None: - carla_location = carla.Location(x=global_position[0], y=global_position[1], z=global_position[2]) - pitch = -35 - - if relative_position == "front": - carla_location = carla.Location(x=carla_location.x + 2.5, y=carla_location.y, z=carla_location.z + 1.0) - yaw = 0 - - elif relative_position == "right": - carla_location = carla.Location(x=carla_location.x + 0.0, y=carla_location.y + 0.3, z=carla_location.z + 1.8) - yaw = 100 - - elif relative_position == "left": - carla_location = carla.Location(x=carla_location.x + 0.0, y=carla_location.y - 0.3, z=carla_location.z + 1.8) - yaw = -100 - else: - carla_location = carla.Location(x=carla_location.x - 2.0, y=carla_location.y, z=carla_location.z + 1.5) - yaw = 180 - - carla_rotation = carla.Rotation(roll=0, yaw=yaw, pitch=pitch) - spawn_point = carla.Transform(carla_location, carla_rotation) - - return spawn_point - - def tick(self): - while self.obj_idx is None or self.obj_tag is None or self.obj_idx.shape[0] != self.obj_tag.shape[0]: - continue - - # label 10 is the vehicle - vehicle_idx = self.obj_idx[self.obj_tag == 10] - # each individual instance id - vehicle_unique_id = list(np.unique(vehicle_idx)) - vehicle_id_filter = [] - - for veh_id in vehicle_unique_id: - if vehicle_idx[vehicle_idx == veh_id].shape[0] > self.thresh: - vehicle_id_filter.append(veh_id) - - # these are the ids that are visible - return vehicle_id_filter diff --git a/OpenCOOD/logreplay/sensors/sensor_manager.py b/OpenCOOD/logreplay/sensors/sensor_manager.py deleted file mode 100644 index 5eb39973..00000000 --- a/OpenCOOD/logreplay/sensors/sensor_manager.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Sensor Manager for each cav -""" - -import importlib -import os -from collections import OrderedDict - - -class SensorManager: - """ - The manager controls all sensor data streaming and dumping for each cav. - todo: rsu not considered yet. - Parameters - ---------- - agent_id : str - The cav's original id. - - vehicle_content : dict - The content of the cav. - - world : carla.World - Carla simulation server object. - - config_yaml : dict - Configurations for the sensor manager. - - output_root : str - Output directory for data dumping. - - Attributes - ---------- - sensor_list : list - A list of sensors to dump/visualize data. - """ - - def __init__(self, agent_id, vehicle_content, world, config_yaml, output_root): - self.agent_id = agent_id - self.output_root = output_root - self.vehicle = vehicle_content["actor"] - self.world = world - self.sensor_list = [] - # this is used to gather the meta information return from sensors - self.sensor_meta = OrderedDict() - - for sensor_content in config_yaml["sensor_list"]: - sensor = None - sensor_name = sensor_content["name"] - - # find the relative path - sensor_filename = "logreplay.sensors." + sensor_name - sensor_lib = importlib.import_module(sensor_filename) - target_sensor_name = sensor_name.replace("_", "") - - # the sensor corresponding class has to have the same - # name pattern as the file - for name, cls in sensor_lib.__dict__.items(): - if name.lower() == target_sensor_name.lower(): - sensor = cls - - assert sensor is not None, "The sensor class name has to be thesame as the file name. e.g. bev_semantic_camera -> BevSemanticCamera" - # todo: rsu is not considered yet - sensor_instance = sensor(self.agent_id, self.vehicle, self.world, sensor_content["args"], None) - self.sensor_list.append(sensor_instance) - - def run_step(self, cur_timestamp): - for sensor_instance in self.sensor_list: - sensor_name = sensor_instance.name - sensor_instance.visualize_data() - - meta_info = sensor_instance.tick() - self.sensor_meta.update({sensor_name: meta_info}) - - # for data dumping - output_folder = os.path.join(self.output_root, self.agent_id) - if not os.path.exists(output_folder): - os.makedirs(output_folder) - sensor_instance.data_dump(output_folder, cur_timestamp) - - def destroy(self): - for sensor_instance in self.sensor_list: - sensor_instance.destroy() diff --git a/OpenCOOD/logreplay/sensors/utils.py b/OpenCOOD/logreplay/sensors/utils.py deleted file mode 100644 index a729d178..00000000 --- a/OpenCOOD/logreplay/sensors/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -import numpy as np - - -def get_camera_intrinsic(sensor): - """ - Retrieve the camera intrinsic matrix. - - Parameters - ---------- - sensor : carla.sensor - Carla rgb camera object. - - Returns - ------- - matrix_x : np.ndarray - The 2d intrinsic matrix. - - """ - VIEW_WIDTH = int(sensor.attributes["image_size_x"]) - VIEW_HEIGHT = int(sensor.attributes["image_size_y"]) - VIEW_FOV = int(float(sensor.attributes["fov"])) - - matrix_k = np.identity(3) - matrix_k[0, 2] = VIEW_WIDTH / 2.0 - matrix_k[1, 2] = VIEW_HEIGHT / 2.0 - matrix_k[0, 0] = matrix_k[1, 1] = VIEW_WIDTH / (2.0 * np.tan(VIEW_FOV * np.pi / 360.0)) - - return matrix_k diff --git a/OpenCOOD/opencood/__init__.py b/OpenCOOD/opencood/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/data_utils/__init__.py b/OpenCOOD/opencood/data_utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/data_utils/augmentor/__init__.py b/OpenCOOD/opencood/data_utils/augmentor/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/data_utils/augmentor/augment_utils.py b/OpenCOOD/opencood/data_utils/augmentor/augment_utils.py deleted file mode 100644 index 9ed27d08..00000000 --- a/OpenCOOD/opencood/data_utils/augmentor/augment_utils.py +++ /dev/null @@ -1,42 +0,0 @@ -import numpy as np -from opencood.utils import common_utils - - -def global_rotation(gt_boxes, points, rot_range): - """ - Args: - gt_boxes: (N, 7 + C), [x, y, z, dx, dy, dz, heading, [vx], [vy]] - points: (M, 3 + C), - rot_range: [min, max] - Returns: - """ - noise_rotation = np.random.uniform(rot_range[0], rot_range[1]) - points = common_utils.rotate_points_along_z(points[np.newaxis, :, :], np.array([noise_rotation]))[0] - - gt_boxes[:, 0:3] = common_utils.rotate_points_along_z(gt_boxes[np.newaxis, :, 0:3], np.array([noise_rotation]))[0] - gt_boxes[:, 6] += noise_rotation - - if gt_boxes.shape[1] > 7: - gt_boxes[:, 7:9] = common_utils.rotate_points_along_z( - np.hstack((gt_boxes[:, 7:9], np.zeros((gt_boxes.shape[0], 1))))[np.newaxis, :, :], - np.array([noise_rotation]), - )[0][:, 0:2] - - return gt_boxes, points - - -def global_scaling(gt_boxes, points, scale_range): - """ - Args: - gt_boxes: (N, 7), [x, y, z, dx, dy, dz, heading] - points: (M, 3 + C), - scale_range: [min, max] - Returns: - """ - if scale_range[1] - scale_range[0] < 1e-3: - return gt_boxes, points - noise_scale = np.random.uniform(scale_range[0], scale_range[1]) - points[:, :3] *= noise_scale - gt_boxes[:, :6] *= noise_scale - - return gt_boxes, points diff --git a/OpenCOOD/opencood/data_utils/augmentor/data_augmentor.py b/OpenCOOD/opencood/data_utils/augmentor/data_augmentor.py deleted file mode 100644 index 99adb36a..00000000 --- a/OpenCOOD/opencood/data_utils/augmentor/data_augmentor.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Class for data augmentation -""" - -from functools import partial -from opencood.data_utils.augmentor import augment_utils - - -class DataAugmentor(object): - """ - Data Augmentor. - - Parameters - ---------- - augment_config : list - A list of augmentation configuration. - - Attributes - ---------- - data_augmentor_queue : list - The list of data augmented functions. - """ - - def __init__(self, augment_config, train=True): - self.data_augmentor_queue = [] - self.train = train - - for cur_cfg in augment_config: - cur_augmentor = getattr(self, cur_cfg["NAME"])(config=cur_cfg) - self.data_augmentor_queue.append(cur_augmentor) - - def random_world_flip(self, data_dict=None, config=None): - if data_dict is None: - return partial(self.random_world_flip, config=config) - - gt_boxes, gt_mask, points = ( - data_dict["object_bbx_center"], - data_dict["object_bbx_mask"], - data_dict["lidar_np"], - ) - gt_boxes_valid = gt_boxes[gt_mask == 1] - - for cur_axis in config["ALONG_AXIS_LIST"]: - assert cur_axis in ["x", "y"] - gt_boxes_valid, points = getattr(augment_utils, "random_flip_along_%s" % cur_axis)( - gt_boxes_valid, - points, - ) - - gt_boxes[: gt_boxes_valid.shape[0], :] = gt_boxes_valid - - data_dict["object_bbx_center"] = gt_boxes - data_dict["object_bbx_mask"] = gt_mask - data_dict["lidar_np"] = points - - return data_dict - - def random_world_rotation(self, data_dict=None, config=None): - if data_dict is None: - return partial(self.random_world_rotation, config=config) - - rot_range = config["WORLD_ROT_ANGLE"] - if not isinstance(rot_range, list): - rot_range = [-rot_range, rot_range] - - gt_boxes, gt_mask, points = ( - data_dict["object_bbx_center"], - data_dict["object_bbx_mask"], - data_dict["lidar_np"], - ) - gt_boxes_valid = gt_boxes[gt_mask == 1] - gt_boxes_valid, points = augment_utils.global_rotation(gt_boxes_valid, points, rot_range=rot_range) - gt_boxes[: gt_boxes_valid.shape[0], :] = gt_boxes_valid - - data_dict["object_bbx_center"] = gt_boxes - data_dict["object_bbx_mask"] = gt_mask - data_dict["lidar_np"] = points - - return data_dict - - def random_world_scaling(self, data_dict=None, config=None): - if data_dict is None: - return partial(self.random_world_scaling, config=config) - - gt_boxes, gt_mask, points = ( - data_dict["object_bbx_center"], - data_dict["object_bbx_mask"], - data_dict["lidar_np"], - ) - gt_boxes_valid = gt_boxes[gt_mask == 1] - - gt_boxes_valid, points = augment_utils.global_scaling(gt_boxes_valid, points, config["WORLD_SCALE_RANGE"]) - gt_boxes[: gt_boxes_valid.shape[0], :] = gt_boxes_valid - - data_dict["object_bbx_center"] = gt_boxes - data_dict["object_bbx_mask"] = gt_mask - data_dict["lidar_np"] = points - - return data_dict - - def forward(self, data_dict): - """ - Args: - data_dict: - points: (N, 3 + C_in) - gt_boxes: optional, (N, 7) [x, y, z, dx, dy, dz, heading] - gt_names: optional, (N), string - ... - - Returns: - """ - if self.train: - for cur_augmentor in self.data_augmentor_queue: - data_dict = cur_augmentor(data_dict=data_dict) - - return data_dict diff --git a/OpenCOOD/opencood/data_utils/datasets/__init__.py b/OpenCOOD/opencood/data_utils/datasets/__init__.py deleted file mode 100644 index d9300026..00000000 --- a/OpenCOOD/opencood/data_utils/datasets/__init__.py +++ /dev/null @@ -1,50 +0,0 @@ -from importlib import import_module - - -_DATASET_REGISTRY = { - "LateFusionDataset": ( - "opencood.data_utils.datasets.late_fusion_dataset", - "LateFusionDataset", - ), - "EarlyFusionDataset": ( - "opencood.data_utils.datasets.early_fusion_dataset", - "EarlyFusionDataset", - ), - "IntermediateFusionDataset": ( - "opencood.data_utils.datasets.intermediate_fusion_dataset", - "IntermediateFusionDataset", - ), - "IntermediateFusionDatasetV2": ( - "opencood.data_utils.datasets.intermediate_fusion_dataset_v2", - "IntermediateFusionDatasetV2", - ), -} - -__all__ = tuple(_DATASET_REGISTRY) - -# the final range for evaluation -GT_RANGE = [-140.8, -41.6, -10.6, 140.8, 41.6, 10.6] -# The communication range for cavs -COM_RANGE = 70 - - -def _load_dataset_class(dataset_name): - module_name, class_name = _DATASET_REGISTRY[dataset_name] - dataset_class = getattr(import_module(module_name), class_name) - globals()[dataset_name] = dataset_class - return dataset_class - - -def __getattr__(name): # Noqa: DC02 - if name not in _DATASET_REGISTRY: - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") - return _load_dataset_class(name) - - -def build_dataset(dataset_cfg, visualize=False, train=True, payload_handler=None): - dataset_name = dataset_cfg["fusion"]["core_method"] - error_message = f"{dataset_name} is not found. Please add the dataset to opencood.data_utils.datasets" - assert dataset_name in _DATASET_REGISTRY, error_message - - dataset_class = _load_dataset_class(dataset_name) - return dataset_class(params=dataset_cfg, visualize=visualize, train=train, payload_handler=payload_handler) diff --git a/OpenCOOD/opencood/data_utils/datasets/basedataset.py b/OpenCOOD/opencood/data_utils/datasets/basedataset.py deleted file mode 100644 index 084d6b4e..00000000 --- a/OpenCOOD/opencood/data_utils/datasets/basedataset.py +++ /dev/null @@ -1,619 +0,0 @@ -""" -Basedataset class for all kinds of fusion. -""" - -from __future__ import annotations - -import os -import math -import logging -from collections import OrderedDict -from typing import TYPE_CHECKING - -import torch -import numpy as np -from torch.utils.data import Dataset - -import opencood.utils.pcd_utils as pcd_utils -from opencood.data_utils.augmentor.data_augmentor import DataAugmentor -from opencood.hypes_yaml.yaml_utils import load_yaml -from opencood.utils.pcd_utils import downsample_lidar_minimum -from opencood.utils.transformation_utils import x1_to_x2 - -if TYPE_CHECKING: - from opencood.models.communication_adapters.base import ModelCommunicationAdapter - -logger = logging.getLogger("cavise.opencda.OpenCOOD.opencood.data_utils.datasets.basedataset") - - -class BaseDataset(Dataset): - """ - Base dataset for all kinds of fusion. Mainly used to initialize the - database and associate the __get_item__ index with the correct timestamp - and scenario. - - Parameters - __________ - params : dict - The dictionary contains all parameters for training/testing. - - visualize : false - If set to true, the raw point cloud will be saved in the memory - for visualization. - - Attributes - ---------- - scenario_database : OrderedDict - A structured dictionary contains all file information. - - len_record : list - The list to record each scenario's data length. This is used to - retrieve the correct index during training. - - pre_processor : opencood.pre_processor - Used to preprocess the raw data. - - post_processor : opencood.post_processor - Used to generate training labels and convert the model outputs to - bbx formats. - - data_augmentor : opencood.data_augmentor - Used to augment data. - - communication_adapter : ModelCommunicationAdapter | None - Model-owned wire adapter attached by the CAPI model manager. - - """ - - def __init__(self, params, visualize, train=True): - self.params = params - self.visualize = visualize - self.train = train - self.communication_adapter: ModelCommunicationAdapter | None = None - - self.pre_processor = None - self.post_processor = None - self.data_augmentor = DataAugmentor(params["data_augment"], train) - - # if the training/testing include noisy setting - if "wild_setting" in params: - self.seed = params["wild_setting"]["seed"] - # whether to add time delay - self.async_flag = params["wild_setting"]["async"] - self.async_mode = "sim" if "async_mode" not in params["wild_setting"] else params["wild_setting"]["async_mode"] - self.async_overhead = params["wild_setting"]["async_overhead"] - - # localization error - self.loc_err_flag = params["wild_setting"]["loc_err"] - self.xyz_noise_std = params["wild_setting"]["xyz_std"] - self.ryp_noise_std = params["wild_setting"]["ryp_std"] - - # transmission data size - self.data_size = params["wild_setting"]["data_size"] if "data_size" in params["wild_setting"] else 0 - self.transmission_speed = params["wild_setting"]["transmission_speed"] if "transmission_speed" in params["wild_setting"] else 27 - self.backbone_delay = params["wild_setting"]["backbone_delay"] if "backbone_delay" in params["wild_setting"] else 0 - - else: - self.async_flag = False - self.async_overhead = 0 # ms - self.async_mode = "sim" - self.loc_err_flag = False - self.xyz_noise_std = 0 - self.ryp_noise_std = 0 - self.data_size = 0 # Mb (Megabits) - self.transmission_speed = 27 # Mbps - self.backbone_delay = 0 # ms - - if "train_params" not in params or "max_cav" not in params["train_params"]: - self.max_cav = 7 - else: - self.max_cav = params["train_params"]["max_cav"] - - self.scenario_database = OrderedDict() - self.len_record = [] - - self.update_database() - - def update_database(self, memory_data=None): - """ - Update the scenario database by re-scanning the root directory. - - This is an optimized method to refresh the database and length - records without re-initializing the entire dataset object. It is - particularly useful for real-time simulations where new data - frames are added to the directory dynamically. - - Parameters - ---------- - None - - Returns - ------- - None - """ - if memory_data is not None: - self.scenario_database = memory_data - self.len_record = [1] - return - - if self.train: - root_dir = self.params["root_dir"] - else: - root_dir = self.params["validate_dir"] - - if not os.path.exists(root_dir): - logger.warning(f"Root directory {root_dir} does not exist.") - return - - # first load all paths of different scenarios - scenario_folders = sorted([os.path.join(root_dir, x) for x in os.listdir(root_dir) if os.path.isdir(os.path.join(root_dir, x))]) - # Structure: {scenario_id : {cav_1 : {timestamp1 : {yaml: path, - # lidar: path, cameras:list of path}}}} - self.scenario_database = OrderedDict() - self.len_record = [] - - # loop over all scenarios - for i, scenario_folder in enumerate(scenario_folders): - self.scenario_database.update({i: OrderedDict()}) - - # at least 1 cav should show up - cav_list = sorted([x for x in os.listdir(scenario_folder) if os.path.isdir(os.path.join(scenario_folder, x))]) - if len(cav_list) == 0: - logger.warning(f"No CAVs found in {scenario_folder}. Skipping.") - continue - - # roadside unit data's id is always negative, so here we want to - # make sure they will be in the end of the list as they shouldn't - # be ego vehicle. - if "rsu" in cav_list[0]: - cav_list = cav_list[1:] + [cav_list[0]] - - # loop over all CAV data - for j, cav_id in enumerate(cav_list): - if j > self.max_cav - 1: - logger.warning(f"Too many CAVs and RSUs: {len(cav_list)}") - logger.warning(f"Maximum is {self.max_cav}") - break - self.scenario_database[i][cav_id] = OrderedDict() - - # save all yaml files to the dictionary - cav_path = os.path.join(scenario_folder, cav_id) - - # use the frame number as key, the full path as the values - yaml_files = sorted([os.path.join(cav_path, x) for x in os.listdir(cav_path) if x.endswith(".yaml") and "additional" not in x]) - timestamps = self.extract_timestamps(yaml_files) - - for timestamp in timestamps: - self.scenario_database[i][cav_id][timestamp] = OrderedDict() - - yaml_file = os.path.join(cav_path, timestamp + ".yaml") - lidar_file = os.path.join(cav_path, timestamp + ".pcd") - camera_files = self.load_camera_files(cav_path, timestamp) - - self.scenario_database[i][cav_id][timestamp]["yaml"] = yaml_file - self.scenario_database[i][cav_id][timestamp]["lidar"] = lidar_file - self.scenario_database[i][cav_id][timestamp]["camera0"] = camera_files - # Assume all cavs will have the same timestamps length. Thus - # we only need to calculate for the first vehicle in the - # scene. - if j == 0: - # we regard the agent with the minimum id as the ego - self.scenario_database[i][cav_id]["ego"] = True - if not self.len_record: - self.len_record.append(len(timestamps)) - else: - prev_last = self.len_record[-1] - self.len_record.append(prev_last + len(timestamps)) - else: - self.scenario_database[i][cav_id]["ego"] = False - - if not self.len_record: - self.len_record = [0] - - def __len__(self): - return self.len_record[-1] if self.len_record else 0 - - def __getitem__(self, idx): - """ - Abstract method, needs to be define by the children class. - """ - pass - - def retrieve_base_data(self, idx, cur_ego_pose_flag=True): - """ - Given the index, return the corresponding data. - - Parameters - ---------- - idx : int - Index given by dataloader. - - cur_ego_pose_flag : bool - Indicate whether to use current timestamp ego pose to calculate - transformation matrix. If set to false, meaning when other cavs - project their LiDAR point cloud to ego, they are projecting to - past ego pose. - - Returns - ------- - data : dict - The dictionary contains loaded yaml params and lidar data for - each cav. - """ - # we loop the accumulated length list to see get the scenario index - scenario_index = 0 - for i, ele in enumerate(self.len_record): - if idx < ele: - scenario_index = i - break - scenario_database = self.scenario_database[scenario_index] - - # check the timestamp index - timestamp_index = idx if scenario_index == 0 else idx - self.len_record[scenario_index - 1] - # retrieve the corresponding timestamp key - timestamp_key = self.return_timestamp_key(scenario_database, timestamp_index) - # calculate distance to ego for each cav - ego_cav_content = self.calc_dist_to_ego(scenario_database, timestamp_key) - - data = OrderedDict() - # load files for all CAVs - for cav_id, cav_content in scenario_database.items(): - data[cav_id] = OrderedDict() - data[cav_id]["ego"] = cav_content["ego"] - - # calculate delay for this vehicle - timestamp_delay = self.time_delay_calculation(cav_content["ego"]) - - if timestamp_index - timestamp_delay <= 0: - timestamp_delay = timestamp_index - timestamp_index_delay = max(0, timestamp_index - timestamp_delay) - timestamp_key_delay = self.return_timestamp_key(scenario_database, timestamp_index_delay) - # add time delay to vehicle parameters - data[cav_id]["time_delay"] = timestamp_delay - # load the corresponding data into the dictionary - data[cav_id]["params"] = self.reform_param(cav_content, ego_cav_content, timestamp_key, timestamp_key_delay, cur_ego_pose_flag) - data[cav_id]["lidar_np"] = ( - cav_content[timestamp_key_delay]["lidar_np"] - if "lidar_np" in cav_content[timestamp_key_delay] - else pcd_utils.pcd_to_np(cav_content[timestamp_key_delay]["lidar"]) - ) - if "spoofing_mask" in cav_content[timestamp_key_delay]: - data[cav_id]["spoofing_mask"] = cav_content[timestamp_key_delay]["spoofing_mask"] - return data - - @staticmethod - def extract_timestamps(yaml_files): - """ - Given the list of the yaml files, extract the mocked timestamps. - - Parameters - ---------- - yaml_files : list - The full path of all yaml files of ego vehicle - - Returns - ------- - timestamps : list - The list containing timestamps only. - """ - timestamps = [] - - for file in yaml_files: - res = file.split("/")[-1] - - timestamp = res.replace(".yaml", "") - timestamps.append(timestamp) - - return timestamps - - @staticmethod - def return_timestamp_key(scenario_database, timestamp_index): - """ - Given the timestamp index, return the correct timestamp key, e.g. - 2 --> '000078'. - - Parameters - ---------- - scenario_database : OrderedDict - The dictionary contains all contents in the current scenario. - - timestamp_index : int - The index for timestamp. - - Returns - ------- - timestamp_key : str - The timestamp key saved in the cav dictionary. - """ - # get all timestamp keys - timestamp_keys = list(scenario_database.items())[0][1] - # retrieve the correct index - timestamp_key = list(timestamp_keys.items())[timestamp_index][0] - - return timestamp_key - - def calc_dist_to_ego(self, scenario_database, timestamp_key): - """ - Calculate the distance to ego for each cav. - """ - ego_lidar_pose = None - ego_cav_content = None - # Find ego pose first - for cav_id, cav_content in scenario_database.items(): - if cav_content["ego"]: - ego_cav_content = cav_content - ego_lidar_pose = ( - cav_content[timestamp_key]["params"] if "params" in cav_content[timestamp_key] else load_yaml(cav_content[timestamp_key]["yaml"]) - )["lidar_pose"] - break - - assert ego_lidar_pose is not None - - # calculate the distance - for cav_id, cav_content in scenario_database.items(): - cur_lidar_pose = ( - cav_content[timestamp_key]["params"] if "params" in cav_content[timestamp_key] else load_yaml(cav_content[timestamp_key]["yaml"]) - )["lidar_pose"] - - dx = cur_lidar_pose[0] - ego_lidar_pose[0] - dy = cur_lidar_pose[1] - ego_lidar_pose[1] - distance = math.hypot(dx, dy) - - cav_content["distance_to_ego"] = distance - scenario_database.update({cav_id: cav_content}) - - return ego_cav_content - - def time_delay_calculation(self, ego_flag): - """ - Calculate the time delay for a certain vehicle. - - Parameters - ---------- - ego_flag : boolean - Whether the current cav is ego. - - Return - ------ - time_delay : int - The time delay quantization. - """ - # there is not time delay for ego vehicle - if ego_flag: - return 0 - # time delay real mode - if self.async_mode == "real": - # in the real mode, time delay = systematic async time + data - # transmission time + backbone computation time - overhead_noise = np.random.uniform(0, self.async_overhead) - tc = self.data_size / self.transmission_speed * 1000 - time_delay = int(overhead_noise + tc + self.backbone_delay) - elif self.async_mode == "sim": - # in the simulation mode, the time delay is constant - time_delay = np.abs(self.async_overhead) - - # the data is 10 hz for both opv2v and v2x-set - # todo: it may not be true for other dataset like DAIR-V2X and V2X-Sim - time_delay = time_delay // 100 - return time_delay if self.async_flag else 0 - - def add_loc_noise(self, pose, xyz_std, ryp_std): - """ - Add localization noise to the pose. - - Parameters - ---------- - pose : list - x,y,z,roll,yaw,pitch - - xyz_std : float - std of the gaussian noise on xyz - - ryp_std : float - std of the gaussian noise - """ - np.random.seed(self.seed) - xyz_noise = np.random.normal(0, xyz_std, 3) - ryp_std = np.random.normal(0, ryp_std, 3) - noise_pose = [pose[0] + xyz_noise[0], pose[1] + xyz_noise[1], pose[2] + xyz_noise[2], pose[3], pose[4] + ryp_std[1], pose[5]] - return noise_pose - - def reform_param(self, cav_content, ego_content, timestamp_cur, timestamp_delay, cur_ego_pose_flag): - """ - Reform the data params with current timestamp object groundtruth and - delay timestamp LiDAR pose for other CAVs. - - Parameters - ---------- - cav_content : dict - Dictionary that contains all file paths in the current cav/rsu. - - ego_content : dict - Ego vehicle content. - - timestamp_cur : str - The current timestamp. - - timestamp_delay : str - The delayed timestamp. - - cur_ego_pose_flag : bool - Whether use current ego pose to calculate transformation matrix. - - Return - ------ - The merged parameters. - """ - cur_params = ( - cav_content[timestamp_cur]["params"] if "params" in cav_content[timestamp_cur] else load_yaml(cav_content[timestamp_cur]["yaml"]) - ).copy() - delay_params = ( - cav_content[timestamp_delay]["params"] if "params" in cav_content[timestamp_delay] else load_yaml(cav_content[timestamp_delay]["yaml"]) - ).copy() - - cur_ego_params = ( - ego_content[timestamp_cur]["params"] if "params" in ego_content[timestamp_cur] else load_yaml(ego_content[timestamp_cur]["yaml"]) - ) - delay_ego_params = ( - ego_content[timestamp_delay]["params"] if "params" in ego_content[timestamp_delay] else load_yaml(ego_content[timestamp_delay]["yaml"]) - ) - - # we need to calculate the transformation matrix from cav to ego - # at the delayed timestamp - delay_cav_lidar_pose = delay_params["lidar_pose"] - delay_ego_lidar_pose = delay_ego_params["lidar_pose"] - - cur_ego_lidar_pose = cur_ego_params["lidar_pose"] - cur_cav_lidar_pose = cur_params["lidar_pose"] - - if not cav_content["ego"] and self.loc_err_flag: - delay_cav_lidar_pose = self.add_loc_noise(delay_cav_lidar_pose, self.xyz_noise_std, self.ryp_noise_std) - cur_cav_lidar_pose = self.add_loc_noise(cur_cav_lidar_pose, self.xyz_noise_std, self.ryp_noise_std) - - if cur_ego_pose_flag: - transformation_matrix = x1_to_x2(delay_cav_lidar_pose, cur_ego_lidar_pose) - spatial_correction_matrix = np.eye(4) - else: - transformation_matrix = x1_to_x2(delay_cav_lidar_pose, delay_ego_lidar_pose) - spatial_correction_matrix = x1_to_x2(delay_ego_lidar_pose, cur_ego_lidar_pose) - # This is only used for late fusion, as it did the transformation - # in the postprocess, so we want the gt object transformation use - # the correct one - gt_transformation_matrix = x1_to_x2(cur_cav_lidar_pose, cur_ego_lidar_pose) - - # we always use current timestamp's gt bbx to gain a fair evaluation - delay_params["vehicles"] = cur_params["vehicles"] - delay_params["transformation_matrix"] = transformation_matrix - delay_params["gt_transformation_matrix"] = gt_transformation_matrix - delay_params["spatial_correction_matrix"] = spatial_correction_matrix - - return delay_params - - @staticmethod - def load_camera_files(cav_path, timestamp): - """ - Retrieve the paths to all camera files. - - Parameters - ---------- - cav_path : str - The full file path of current cav. - - timestamp : str - Current timestamp - - Returns - ------- - camera_files : list - The list containing all camera png file paths. - """ - camera0_file = os.path.join(cav_path, timestamp + "_camera0.png") - camera1_file = os.path.join(cav_path, timestamp + "_camera1.png") - camera2_file = os.path.join(cav_path, timestamp + "_camera2.png") - camera3_file = os.path.join(cav_path, timestamp + "_camera3.png") - return [camera0_file, camera1_file, camera2_file, camera3_file] - - def project_points_to_bev_map(self, points, ratio=0.1): - """ - Project points to BEV occupancy map with default ratio=0.1. - - Parameters - ---------- - points : np.ndarray - (N, 3) / (N, 4) - - ratio : float - Discretization parameters. Default is 0.1. - - Returns - ------- - bev_map : np.ndarray - BEV occupancy map including projected points - with shape (img_row, img_col). - - """ - return self.pre_processor.project_points_to_bev_map(points, ratio) - - def augment(self, lidar_np, object_bbx_center, object_bbx_mask): - """ - Given the raw point cloud, augment by flipping and rotation. - - Parameters - ---------- - lidar_np : np.ndarray - (n, 4) shape - - object_bbx_center : np.ndarray - (n, 7) shape to represent bbx's x, y, z, h, w, l, yaw - - object_bbx_mask : np.ndarray - Indicate which elements in object_bbx_center are padded. - """ - tmp_dict = {"lidar_np": lidar_np, "object_bbx_center": object_bbx_center, "object_bbx_mask": object_bbx_mask} - tmp_dict = self.data_augmentor.forward(tmp_dict) - - lidar_np = tmp_dict["lidar_np"] - object_bbx_center = tmp_dict["object_bbx_center"] - object_bbx_mask = tmp_dict["object_bbx_mask"] - - return lidar_np, object_bbx_center, object_bbx_mask - - def collate_batch_train(self, batch): - """ - Customized collate function for pytorch dataloader during training - for early and late fusion dataset. - - Parameters - ---------- - batch : dict - - Returns - ------- - batch : dict - Reformatted batch. - """ - # during training, we only care about ego. - output_dict = {"ego": {}} - - object_bbx_center = [] - object_bbx_mask = [] - processed_lidar_list = [] - label_dict_list = [] - - if self.visualize: - origin_lidar = [] - - for i in range(len(batch)): - ego_dict = batch[i]["ego"] - object_bbx_center.append(ego_dict["object_bbx_center"]) - object_bbx_mask.append(ego_dict["object_bbx_mask"]) - processed_lidar_list.append(ego_dict["processed_lidar"]) - label_dict_list.append(ego_dict["label_dict"]) - - if self.visualize: - origin_lidar.append(ego_dict["origin_lidar"]) - - # convert to numpy, (B, max_num, 7) - object_bbx_center = torch.from_numpy(np.array(object_bbx_center)) - object_bbx_mask = torch.from_numpy(np.array(object_bbx_mask)) - - processed_lidar_torch_dict = self.pre_processor.collate_batch(processed_lidar_list) - label_torch_dict = self.post_processor.collate_batch(label_dict_list) - output_dict["ego"].update( - { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "processed_lidar": processed_lidar_torch_dict, - "label_dict": label_torch_dict, - } - ) - if self.visualize: - origin_lidar = np.array(downsample_lidar_minimum(pcd_np_list=origin_lidar)) - origin_lidar = torch.from_numpy(origin_lidar) - output_dict["ego"].update({"origin_lidar": origin_lidar}) - - return output_dict - - def visualize_result(self, pred_box_tensor, gt_tensor, pcd, show_vis, save_path, dataset=None): - # visualize the model output - self.post_processor.visualize(pred_box_tensor, gt_tensor, pcd, show_vis, save_path, dataset=dataset) diff --git a/OpenCOOD/opencood/data_utils/datasets/early_fusion_dataset.py b/OpenCOOD/opencood/data_utils/datasets/early_fusion_dataset.py deleted file mode 100644 index fbc408ca..00000000 --- a/OpenCOOD/opencood/data_utils/datasets/early_fusion_dataset.py +++ /dev/null @@ -1,451 +0,0 @@ -""" -Dataset class for early fusion -""" - -import math -import logging -from collections import OrderedDict -from collections.abc import Callable -from typing import Any - -import numpy as np -import numpy.typing as npt -import torch - -import opencood.data_utils.datasets -from opencood.utils import box_utils -from opencood.data_utils.post_processor import build_postprocessor -from opencood.data_utils.datasets import basedataset -from opencood.models.communication_adapters import PoseFrameMetadata -from opencood.data_utils.pre_processor import build_preprocessor -from opencood.utils.pcd_utils import downsample_lidar_minimum -from opencood.utils.transformation_utils import x1_to_x2 - -logger = logging.getLogger("cavise.opencda.OpenCOOD.opencood.data_utils.datasets.early_fusion_dataset") - - -class EarlyFusionDataset(basedataset.BaseDataset): - """ - This dataset is used for early fusion, where each CAV transmit the raw - point cloud to the ego vehicle. - """ - - def __init__(self, params, visualize, train=True, payload_handler=None): - super(EarlyFusionDataset, self).__init__(params, visualize, train) - self.pre_processor = build_preprocessor(params["preprocess"], train) - self.post_processor = build_postprocessor(params["postprocess"], train) - - self.payload_handler = payload_handler - self.module_name = "OpenCOOD.EarlyFusionDataset" - - def __find_ego_vehicle(self, base_data_dict): - ego_id = -1 - ego_lidar_pose = [] - - # first find the ego vehicle's lidar pose - for cav_id, cav_content in base_data_dict.items(): - if cav_content["ego"]: - ego_id = cav_id - ego_lidar_pose = cav_content["params"]["lidar_pose"] - break - - assert ego_id != -1 - assert len(ego_lidar_pose) > 0 - - return ego_id, ego_lidar_pose - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[[npt.NDArray[Any], PoseFrameMetadata], object], - ) -> None: - """ - Publish sender-local observations through the communication adapter. - - Parameters - ---------- - idx : int - Dataset frame to publish. - agent_payload_builder : Callable - Adapter callback converting one local observation into its wire - representation. - """ - base_data_dict = self.retrieve_base_data(idx) - - if self.payload_handler is not None: - for cav_id, selected_cav_base in base_data_dict.items(): - lidar_points, _ = self.__prepare_local_lidar(selected_cav_base) - payload = agent_payload_builder( - lidar_points, - PoseFrameMetadata( - lidar_pose=selected_cav_base["params"]["lidar_pose"], - capture_frame=idx - int(selected_cav_base["time_delay"]), - ), - ) - self.payload_handler.set_opencda_payload(cav_id, self.module_name, payload) - - def __process_with_messages(self, ego_id, ego_lidar_pose, base_data_dict): - projected_lidar_stack = [] - - ego_cav_base = base_data_dict.get(ego_id) - ego_cav_processed = self.get_item_single_car(ego_cav_base, ego_lidar_pose) - - projected_lidar_stack.append(ego_cav_processed["projected_lidar"]) - - if ego_id in self.payload_handler.current_artery_payload: - for cav_id, _ in base_data_dict.items(): - if cav_id == ego_id: - continue - raw_payload = self.payload_handler.get_artery_payload(ego_id, cav_id, self.module_name) - if raw_payload is None: - continue - if self.communication_adapter is None: - raise RuntimeError("Early-fusion payload decoding requires a communication adapter") - decoded_payload = self.communication_adapter.decode_received_payload( - raw_payload, - ego_lidar_pose, - ) - projected_lidar_stack.append(decoded_payload["projected_lidar"]) - - return {"projected_lidar_stack": projected_lidar_stack} - - def __process_without_messages(self, ego_lidar_pose, base_data_dict): - projected_lidar_stack = [] - - # loop over all CAVs to process information - for cav_id, selected_cav_base in base_data_dict.items(): - # check if the cav is within the communication range with ego - dx = selected_cav_base["params"]["lidar_pose"][0] - ego_lidar_pose[0] - dy = selected_cav_base["params"]["lidar_pose"][1] - ego_lidar_pose[1] - distance = math.hypot(dx, dy) - - if distance > opencood.data_utils.datasets.COM_RANGE: - continue - - selected_cav_processed = self.get_item_single_car(selected_cav_base, ego_lidar_pose) - projected_lidar_stack.append(selected_cav_processed["projected_lidar"]) - - return {"projected_lidar_stack": projected_lidar_stack} - - def build_local_supervision(self, base_data_dict: dict[str, Any], ego_lidar_pose: list[float]) -> dict[str, Any]: - """ - Build ego-frame ground truth from the local scene snapshot. - - Parameters - ---------- - base_data_dict : dict[str, Any] - Complete local dataset snapshot for the current scene. - ego_lidar_pose : list[float] - Current ego LiDAR pose in world coordinates. - - Returns - ------- - dict[str, Any] - Ground-truth boxes, mask, and object identifiers. - """ - object_bbx_center, object_bbx_mask, object_ids = self.post_processor.generate_object_center(list(base_data_dict.values()), ego_lidar_pose) - return { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "object_ids": object_ids, - } - - def build_visualization_context( - self, - ego_id: str, - ego_lidar_pose: list[float], - base_data_dict: dict[str, Any], - ) -> dict[str, Any]: - """ - Build point-cloud visualization data from the local scene. - - Parameters - ---------- - ego_id : str - Identifier of the receiving ego agent. - ego_lidar_pose : list[float] - Current ego LiDAR pose in world coordinates. - base_data_dict : dict[str, Any] - Local dataset snapshot, independent of delivered messages. - - Returns - ------- - dict[str, Any] - Per-agent point clouds, identities, roles, and spoofing masks. - """ - if not self.visualize: - return {} - - projected_lidar_stack = [] - projected_lidar_roles = [] - projected_lidar_agent_ids = [] - projected_lidar_spoofing_masks = [] - lidar_range = self.params["preprocess"]["cav_lidar_range"] - - for cav_id, cav_base in base_data_dict.items(): - cav_processed = self.get_item_single_car(cav_base, ego_lidar_pose) - projected_lidar = cav_processed["projected_lidar"] - range_mask = ( - (projected_lidar[:, 0] > lidar_range[0]) - & (projected_lidar[:, 0] < lidar_range[3]) - & (projected_lidar[:, 1] > lidar_range[1]) - & (projected_lidar[:, 1] < lidar_range[4]) - & (projected_lidar[:, 2] > lidar_range[2]) - & (projected_lidar[:, 2] < lidar_range[5]) - ) - projected_lidar_stack.append(projected_lidar[range_mask]) - projected_lidar_roles.append("ego" if cav_id == ego_id else "other") - projected_lidar_agent_ids.append(cav_id) - projected_lidar_spoofing_masks.append(cav_processed["projected_lidar_spoofing_mask"][range_mask]) - - return { - "origin_lidar": np.vstack(projected_lidar_stack), - "origin_lidar_by_agent": projected_lidar_stack, - "origin_lidar_roles": projected_lidar_roles, - "origin_lidar_agent_ids": projected_lidar_agent_ids, - "origin_lidar_spoofing_masks": projected_lidar_spoofing_masks, - } - - def assemble_inference_sample( - self, - inference_input: dict[str, Any], - local_supervision: dict[str, Any], - visualization_context: dict[str, Any], - ) -> OrderedDict[str, dict[str, Any]]: - """ - Combine delivered LiDAR with local supervision and visualization. - - Parameters - ---------- - inference_input : dict[str, Any] - Ego LiDAR plus point clouds from messages that were delivered. - local_supervision : dict[str, Any] - Ground truth produced solely from the local dataset snapshot. - visualization_context : dict[str, Any] - Visualization data produced solely from the local snapshot. - - Returns - ------- - collections.OrderedDict - Early-fusion sample ready for collation. - """ - projected_lidar_stack = np.vstack(inference_input["projected_lidar_stack"]) - object_bbx_center = local_supervision["object_bbx_center"] - object_bbx_mask = local_supervision["object_bbx_mask"] - - projected_lidar_stack, object_bbx_center, object_bbx_mask = self.augment( - projected_lidar_stack, - object_bbx_center, - object_bbx_mask, - ) - - lidar_range = self.params["preprocess"]["cav_lidar_range"] - lidar_mask = ( - (projected_lidar_stack[:, 0] > lidar_range[0]) - & (projected_lidar_stack[:, 0] < lidar_range[3]) - & (projected_lidar_stack[:, 1] > lidar_range[1]) - & (projected_lidar_stack[:, 1] < lidar_range[4]) - & (projected_lidar_stack[:, 2] > lidar_range[2]) - & (projected_lidar_stack[:, 2] < lidar_range[5]) - ) - projected_lidar_stack = projected_lidar_stack[lidar_mask] - - valid_indices = np.flatnonzero(object_bbx_mask == 1) - valid_object_ids = [local_supervision["object_ids"][index] for index in valid_indices] - valid_boxes, range_mask = box_utils.mask_boxes_outside_range_numpy( - object_bbx_center[valid_indices], - lidar_range, - self.params["postprocess"]["order"], - return_mask=True, - ) - object_bbx_center.fill(0) - object_bbx_mask.fill(0) - object_bbx_center[: valid_boxes.shape[0]] = valid_boxes - object_bbx_mask[: valid_boxes.shape[0]] = 1 - - anchor_box = self.post_processor.generate_anchor_box() - label_dict = self.post_processor.generate_label( - gt_box_center=object_bbx_center, - anchors=anchor_box, - mask=object_bbx_mask, - ) - ego_sample = { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "object_ids": [object_id for object_id, keep in zip(valid_object_ids, range_mask) if keep], - "anchor_box": anchor_box, - "processed_lidar": self.pre_processor.preprocess(projected_lidar_stack), - "label_dict": label_dict, - **visualization_context, - } - return OrderedDict({"ego": ego_sample}) - - def __getitem__(self, idx): - base_data_dict = self.retrieve_base_data(idx) - ego_id, ego_lidar_pose = self.__find_ego_vehicle(base_data_dict) - - if self.payload_handler is not None: - inference_input = self.__process_with_messages(ego_id, ego_lidar_pose, base_data_dict) - else: - inference_input = self.__process_without_messages(ego_lidar_pose, base_data_dict) - - local_supervision = self.build_local_supervision(base_data_dict, ego_lidar_pose) - visualization_context = self.build_visualization_context(ego_id, ego_lidar_pose, base_data_dict) - return self.assemble_inference_sample(inference_input, local_supervision, visualization_context) - - def get_item_single_car(self, selected_cav_base, ego_pose): - """ - Project one agent's LiDAR into ego coordinates. - - Parameters - ---------- - selected_cav_base : dict - The dictionary contains a single CAV's raw information. - ego_pose : list - The ego vehicle lidar pose under world coordinate. - - Returns - ------- - selected_cav_processed : dict - The dictionary contains the cav's processed information. - """ - # calculate the transformation matrix - transformation_matrix = x1_to_x2(selected_cav_base["params"]["lidar_pose"], ego_pose) - - lidar_np, spoofing_mask = self.__prepare_local_lidar(selected_cav_base) - # project the lidar to ego space - lidar_np[:, :3] = box_utils.project_points_by_matrix_torch(lidar_np[:, :3], transformation_matrix) - - return { - "projected_lidar": lidar_np, - "projected_lidar_spoofing_mask": spoofing_mask, - } - - @staticmethod - def __prepare_local_lidar( - selected_cav_base: dict[str, Any], - ) -> tuple[np.ndarray, np.ndarray]: - """ - Shuffle and remove ego returns without changing coordinate frames. - - Parameters - ---------- - selected_cav_base : dict[str, Any] - Raw dataset entry for one sending agent. - - Returns - ------- - tuple[numpy.ndarray, numpy.ndarray] - Sender-local LiDAR points and their aligned spoofing mask. - """ - lidar_np = selected_cav_base["lidar_np"] - spoofing_mask = np.asarray( - selected_cav_base.get( - "spoofing_mask", - np.zeros((lidar_np.shape[0],), dtype=np.bool_), - ), - dtype=np.bool_, - ) - - shuffle_idx = np.random.permutation(lidar_np.shape[0]) - lidar_np = np.array(lidar_np[shuffle_idx], copy=True) - spoofing_mask = np.array(spoofing_mask[shuffle_idx], copy=True) - - ego_mask = (lidar_np[:, 0] >= -1.95) & (lidar_np[:, 0] <= 2.95) & (lidar_np[:, 1] >= -1.1) & (lidar_np[:, 1] <= 1.1) - keep_mask = np.logical_not(ego_mask) - return lidar_np[keep_mask], spoofing_mask[keep_mask] - - def collate_batch_test(self, batch): - """ - Customized collate function for pytorch dataloader during testing - for late fusion dataset. - - Parameters - ---------- - batch : dict - - Returns - ------- - batch : dict - Reformatted batch. - """ - # currently, we only support batch size of 1 during testing - assert len(batch) <= 1, "Batch size 1 is required during testing!" - batch = batch[0] - - output_dict = {} - - for cav_id, cav_content in batch.items(): - output_dict.update({cav_id: {}}) - # shape: (1, max_num, 7) - object_bbx_center = torch.from_numpy(np.array([cav_content["object_bbx_center"]])) - object_bbx_mask = torch.from_numpy(np.array([cav_content["object_bbx_mask"]])) - object_ids = cav_content["object_ids"] - - # the anchor box is the same for all bounding boxes usually, thus - # we don't need the batch dimension. - if cav_content["anchor_box"] is not None: - output_dict[cav_id].update({"anchor_box": torch.from_numpy(np.array(cav_content["anchor_box"]))}) - if self.visualize: - origin_lidar = [cav_content["origin_lidar"]] - - # processed lidar dictionary - processed_lidar_torch_dict = self.pre_processor.collate_batch([cav_content["processed_lidar"]]) - # label dictionary - label_torch_dict = self.post_processor.collate_batch([cav_content["label_dict"]]) - - # save the transformation matrix (4, 4) to ego vehicle - transformation_matrix_torch = torch.from_numpy(np.identity(4)).float() - - output_dict[cav_id].update( - { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "processed_lidar": processed_lidar_torch_dict, - "label_dict": label_torch_dict, - "object_ids": object_ids, - "transformation_matrix": transformation_matrix_torch, - } - ) - - if self.visualize: - origin_lidar = np.array(downsample_lidar_minimum(pcd_np_list=origin_lidar)) - origin_lidar = torch.from_numpy(origin_lidar) - output_dict[cav_id].update({"origin_lidar": origin_lidar}) - if "origin_lidar_by_agent" in cav_content: - output_dict[cav_id].update( - { - "origin_lidar_by_agent": [torch.from_numpy(np.array(points)) for points in cav_content["origin_lidar_by_agent"]], - "origin_lidar_roles": list(cav_content["origin_lidar_roles"]), - "origin_lidar_agent_ids": list(cav_content["origin_lidar_agent_ids"]), - "origin_lidar_spoofing_masks": [ - torch.from_numpy(np.asarray(mask, dtype=np.bool_)) for mask in cav_content["origin_lidar_spoofing_masks"] - ], - } - ) - - return output_dict - - def post_process(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D/3D bounding box. - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box_tensor : torch.Tensor - The tensor of prediction bounding box after NMS. - gt_box_tensor : torch.Tensor - The tensor of gt bounding box. - """ - pred_box_tensor, pred_score = self.post_processor.post_process(data_dict, output_dict) - gt_box_tensor = self.post_processor.generate_gt_bbx(data_dict) - - return pred_box_tensor, pred_score, gt_box_tensor diff --git a/OpenCOOD/opencood/data_utils/datasets/early_fusion_vis_dataset.py b/OpenCOOD/opencood/data_utils/datasets/early_fusion_vis_dataset.py deleted file mode 100644 index 65f73673..00000000 --- a/OpenCOOD/opencood/data_utils/datasets/early_fusion_vis_dataset.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -This is a dataset for early fusion visualization only. -""" - -from collections import OrderedDict - -import numpy as np -import torch - -from opencood.utils import box_utils -from opencood.data_utils.post_processor import build_postprocessor -from opencood.data_utils.datasets import basedataset -from opencood.data_utils.pre_processor import build_preprocessor -from opencood.utils.pcd_utils import mask_points_by_range, mask_ego_points, shuffle_points, downsample_lidar_minimum -from opencood.utils.transformation_utils import x1_to_x2 - - -# TODO: Вообше не понятно зачем он нужен, вроде нигде не используется -class EarlyFusionVisDataset(basedataset.BaseDataset): - def __init__(self, params, visualize, train=True): - super(EarlyFusionVisDataset, self).__init__(params, visualize, train) - self.pre_processor = build_preprocessor(params["preprocess"], train) - self.post_processor = build_postprocessor(params["postprocess"], train) - - def __getitem__(self, idx): - base_data_dict = self.retrieve_base_data(idx) - - processed_data_dict = OrderedDict() - processed_data_dict["ego"] = {} - - ego_id = -1 - ego_lidar_pose = [] - - # first find the ego vehicle's lidar pose - for cav_id, cav_content in base_data_dict.items(): - if cav_content["ego"]: - ego_id = cav_id - ego_lidar_pose = cav_content["params"]["lidar_pose"] - break - - assert ego_id != -1 - assert len(ego_lidar_pose) > 0 - - projected_lidar_stack = [] - object_stack = [] - object_id_stack = [] - - # loop over all CAVs to process information - for cav_id, selected_cav_base in base_data_dict.items(): - selected_cav_processed = self.get_item_single_car(selected_cav_base, ego_lidar_pose) - # all these lidar and object coordinates are projected to ego - # already. - projected_lidar_stack.append(selected_cav_processed["projected_lidar"]) - object_stack.append(selected_cav_processed["object_bbx_center"]) - object_id_stack += selected_cav_processed["object_ids"] - - # exclude all repetitive objects - unique_indices = [object_id_stack.index(x) for x in set(object_id_stack)] - object_stack = np.vstack(object_stack) - object_stack = object_stack[unique_indices] - - # make sure bounding boxes across all frames have the same number - object_bbx_center = np.zeros((self.params["postprocess"]["max_num"], 7)) - mask = np.zeros(self.params["postprocess"]["max_num"]) - object_bbx_center[: object_stack.shape[0], :] = object_stack - mask[: object_stack.shape[0]] = 1 - - # convert list to numpy array, (N, 4) - projected_lidar_stack = np.vstack(projected_lidar_stack) - - # data augmentation - projected_lidar_stack, object_bbx_center, mask = self.augment(projected_lidar_stack, object_bbx_center, mask) - - # we do lidar filtering in the stacked lidar - projected_lidar_stack = mask_points_by_range(projected_lidar_stack, self.params["preprocess"]["cav_lidar_range"]) - # augmentation may remove some of the bbx out of range - object_bbx_center_valid = object_bbx_center[mask == 1] - object_bbx_center_valid = box_utils.mask_boxes_outside_range_numpy( - object_bbx_center_valid, self.params["preprocess"]["cav_lidar_range"], self.params["postprocess"]["order"] - ) - mask[object_bbx_center_valid.shape[0] :] = 0 - object_bbx_center[: object_bbx_center_valid.shape[0]] = object_bbx_center_valid - object_bbx_center[object_bbx_center_valid.shape[0] :] = 0 - - processed_data_dict["ego"].update( - { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": mask, - "object_ids": [object_id_stack[i] for i in unique_indices], - "origin_lidar": projected_lidar_stack, - } - ) - - return processed_data_dict - - def get_item_single_car(self, selected_cav_base, ego_pose): - """ - Project the lidar and bbx to ego space first, and then do clipping. - - Parameters - ---------- - selected_cav_base : dict - The dictionary contains a single CAV's raw information. - ego_pose : list - The ego vehicle lidar pose under world coordinate. - - Returns - ------- - selected_cav_processed : dict - The dictionary contains the cav's processed information. - """ - selected_cav_processed = {} - - # calculate the transformation matrix - transformation_matrix = x1_to_x2(selected_cav_base["params"]["lidar_pose"], ego_pose) - - # retrieve objects under ego coordinates - object_bbx_center, object_bbx_mask, object_ids = self.post_processor.generate_object_center([selected_cav_base], ego_pose) - - # filter lidar - lidar_np = selected_cav_base["lidar_np"] - lidar_np = shuffle_points(lidar_np) - # remove points that hit itself - lidar_np = mask_ego_points(lidar_np) - # project the lidar to ego space - lidar_np[:, :3] = box_utils.project_points_by_matrix_torch(lidar_np[:, :3], transformation_matrix) - - selected_cav_processed.update( - {"object_bbx_center": object_bbx_center[object_bbx_mask == 1], "object_ids": object_ids, "projected_lidar": lidar_np} - ) - - return selected_cav_processed - - def collate_batch_train(self, batch): - """ - Customized collate function for pytorch dataloader during training - for late fusion dataset. - - Parameters - ---------- - batch : dict - - Returns - ------- - batch : dict - Reformatted batch. - """ - # during training, we only care about ego. - output_dict = {"ego": {}} - - object_bbx_center = [] - object_bbx_mask = [] - origin_lidar = [] - - for i in range(len(batch)): - ego_dict = batch[i]["ego"] - object_bbx_center.append(ego_dict["object_bbx_center"]) - object_bbx_mask.append(ego_dict["object_bbx_mask"]) - origin_lidar.append(ego_dict["origin_lidar"]) - - # convert to numpy, (B, max_num, 7) - object_bbx_center = torch.from_numpy(np.array(object_bbx_center)) - object_bbx_mask = torch.from_numpy(np.array(object_bbx_mask)) - output_dict["ego"].update({"object_bbx_center": object_bbx_center, "object_bbx_mask": object_bbx_mask}) - - origin_lidar = np.array(downsample_lidar_minimum(pcd_np_list=origin_lidar)) - origin_lidar = torch.from_numpy(origin_lidar) - output_dict["ego"].update({"origin_lidar": origin_lidar}) - - return output_dict diff --git a/OpenCOOD/opencood/data_utils/datasets/intermediate_fusion_dataset.py b/OpenCOOD/opencood/data_utils/datasets/intermediate_fusion_dataset.py deleted file mode 100644 index 392e1072..00000000 --- a/OpenCOOD/opencood/data_utils/datasets/intermediate_fusion_dataset.py +++ /dev/null @@ -1,763 +0,0 @@ -""" -Dataset class for intermediate fusion -""" - -import math -import logging -from collections import OrderedDict -from collections.abc import Callable -from typing import Any - -import numpy as np -import torch - -import opencood.data_utils.datasets -import opencood.data_utils.post_processor as post_processor -from opencood.utils import box_utils -from opencood.data_utils.datasets import basedataset -from opencood.models.communication_adapters import ( - InferenceInput, - IntermediateMetadata, - PoseFrameMetadata, - V2XViTMetadata, - build_inference_input, - merge_inference_inputs, -) -from opencood.data_utils.pre_processor import build_preprocessor -from opencood.utils.pcd_utils import pcd_to_np, mask_points_by_range, mask_ego_points, shuffle_points, downsample_lidar_minimum -from opencood.utils.transformation_utils import x1_to_x2 - -logger = logging.getLogger("cavise.opencda.OpenCOOD.opencood.data_utils.datasets.intermediate_fusion_dataset") - -PAIRWISE_METADATA_MODELS = frozenset( - { - "point_pillar_coalign", - "point_pillar_v2vnet", - "point_pillar_where2comm", - } -) -V2X_VIT_METADATA_MODELS = frozenset({"point_pillar_transformer"}) - - -class IntermediateFusionDataset(basedataset.BaseDataset): - """ - This class is for intermediate fusion where each vehicle transmit the - deep features to ego. - """ - - def __init__(self, params, visualize, train=True, payload_handler=None): - super(IntermediateFusionDataset, self).__init__(params, visualize, train) - - # if project first, cav's lidar will first be projected to - # the ego's coordinate frame. otherwise, the feature will be - # projected instead. - self.proj_first = True - if "proj_first" in params["fusion"]["args"] and not params["fusion"]["args"]["proj_first"]: - self.proj_first = False - - # whether there is a time delay between the time that cav project - # lidar to ego and the ego receive the delivered feature - if "cur_ego_pose_flag" in params["fusion"]["args"]: - self.cur_ego_pose_flag = params["fusion"]["args"]["cur_ego_pose_flag"] - else: - self.cur_ego_pose_flag = True - - self.pre_processor = build_preprocessor(params["preprocess"], train) - self.post_processor = post_processor.build_postprocessor(params["postprocess"], train) - - self.payload_handler = payload_handler - self.module_name = "OpenCOOD.IntermediateFusionDataset" - self.model_name = params["model"]["core_method"] - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[ - [InferenceInput, IntermediateMetadata | None], - object, - ], - ) -> None: - """ - Publish individual agent inputs through the selected model adapter. - - Parameters - ---------- - idx : int - Dataset frame to publish. - agent_payload_builder : Callable - Model adapter callback that encodes the sender-local input at the - configured model's communication boundary. - """ - base_data_dict = self.retrieve_base_data(idx, cur_ego_pose_flag=self.cur_ego_pose_flag) - _, ego_lidar_pose = self.__find_ego_vehicle(base_data_dict) - - if self.payload_handler is not None: - for cav_id, selected_cav_base in base_data_dict.items(): - selected_cav_processed = self.get_item_single_car(selected_cav_base, ego_lidar_pose) - payload = agent_payload_builder( - selected_cav_processed["inference_input"], - self.build_model_metadata(cav_id, selected_cav_base, idx), - ) - self.payload_handler.set_opencda_payload(cav_id, self.module_name, payload) - - def build_model_metadata( - self, - cav_id: str, - selected_cav_base: dict[str, Any], - receive_frame: int, - ) -> IntermediateMetadata | None: - """ - Build only the metadata consumed by the configured model. - - Parameters - ---------- - cav_id : str - Identifier of the sending CAV or RSU. - selected_cav_base : dict[str, Any] - Local dataset entry for the sending agent. - receive_frame : int - Dataset frame in which the model input is received. - - Returns - ------- - IntermediateMetadata | None - Model-specific metadata, or ``None`` for models that consume only - the preprocessed inference input. - """ - capture_frame = receive_frame - int(selected_cav_base["time_delay"]) - if self.model_name in PAIRWISE_METADATA_MODELS: - return PoseFrameMetadata( - lidar_pose=selected_cav_base["params"]["lidar_pose"], - capture_frame=capture_frame, - ) - if self.model_name in V2X_VIT_METADATA_MODELS: - return V2XViTMetadata( - velocity=float(selected_cav_base["params"]["ego_speed"] / 30), - agent_type=1 if "rsu" in cav_id else 0, - capture_frame=capture_frame, - spatial_correction_matrix=selected_cav_base["params"]["spatial_correction_matrix"], - ) - return None - - def __find_ego_vehicle(self, base_data_dict): - ego_id = -1 - ego_lidar_pose = [] - - # first find the ego vehicle's lidar pose - for cav_id, cav_content in base_data_dict.items(): - if cav_content["ego"]: - ego_id = cav_id - ego_lidar_pose = cav_content["params"]["lidar_pose"] - break - - assert cav_id == list(base_data_dict.keys())[0], "The first element in the OrderedDict must be ego" - assert ego_id != -1 - assert len(ego_lidar_pose) > 0 - - return ego_id, ego_lidar_pose - - def __retrieve_visualization_base_data(self, idx): - scenario_index = 0 - for i, ele in enumerate(self.len_record): - if idx < ele: - scenario_index = i - break - scenario_database = self.scenario_database[scenario_index] - - timestamp_index = idx if scenario_index == 0 else idx - self.len_record[scenario_index - 1] - timestamp_key = self.return_timestamp_key(scenario_database, timestamp_index) - ego_cav_content = self.calc_dist_to_ego(scenario_database, timestamp_key) - - data = OrderedDict() - for cav_id, cav_content in scenario_database.items(): - cav_snapshot = cav_content[timestamp_key] - data[cav_id] = OrderedDict() - data[cav_id]["ego"] = cav_content["ego"] - data[cav_id]["time_delay"] = 0 - data[cav_id]["params"] = self.reform_param( - cav_content, - ego_cav_content, - timestamp_key, - timestamp_key, - cur_ego_pose_flag=True, - ) - data[cav_id]["lidar_np"] = cav_snapshot["lidar_np"] if "lidar_np" in cav_snapshot else pcd_to_np(cav_snapshot["lidar"]) - if "spoofing_mask" in cav_snapshot: - data[cav_id]["spoofing_mask"] = cav_snapshot["spoofing_mask"] - return data - - def __pad_spatial_matrix(self, matrix_list): - matrix_list = np.stack(matrix_list) - padding_eye = np.tile(np.eye(4)[None], (self.max_cav - len(matrix_list), 1, 1)) - return np.concatenate([matrix_list, padding_eye], axis=0) - - def __pad_to_max(self, lst, pad_value): - return lst + (self.max_cav - len(lst)) * [pad_value] - - def __build_pairwise_transformation(self, metadata: list[PoseFrameMetadata]) -> np.ndarray: - """ - Build pairwise transformations for the received agents. - - Parameters - ---------- - metadata : list[PoseFrameMetadata] - Pose metadata ordered exactly like the received model inputs. - - Returns - ------- - numpy.ndarray - Pairwise transformation matrix padded to ``max_cav``. - """ - pairwise_t_matrix = np.zeros((self.max_cav, self.max_cav, 4, 4)) - if self.proj_first: - pairwise_t_matrix[:, :] = np.identity(4) - return pairwise_t_matrix - - for source_index, source_metadata in enumerate(metadata): - for target_index, target_metadata in enumerate(metadata): - pairwise_t_matrix[source_index, target_index] = x1_to_x2( - source_metadata.lidar_pose, - target_metadata.lidar_pose, - ) - return pairwise_t_matrix - - def __assemble_model_metadata( - self, - metadata: list[IntermediateMetadata | None], - receive_frame: int, - ) -> dict[str, Any]: - """ - Derive model tensors from received metadata. - - Parameters - ---------- - metadata : list[IntermediateMetadata | None] - Metadata ordered exactly like the available inference inputs. - receive_frame : int - Current receiver dataset frame. - - Returns - ------- - dict[str, Any] - Model fields derived from metadata. - - Raises - ------ - TypeError - If the received metadata does not match the configured model. - """ - if self.model_name in PAIRWISE_METADATA_MODELS: - if not all(isinstance(agent_metadata, PoseFrameMetadata) for agent_metadata in metadata): - raise TypeError(f"{self.model_name} requires PoseFrameMetadata for every agent") - pose_metadata = [agent_metadata for agent_metadata in metadata if isinstance(agent_metadata, PoseFrameMetadata)] - return {"pairwise_t_matrix": self.__build_pairwise_transformation(pose_metadata)} - - if self.model_name in V2X_VIT_METADATA_MODELS: - if not all(isinstance(agent_metadata, V2XViTMetadata) for agent_metadata in metadata): - raise TypeError(f"{self.model_name} requires V2XViTMetadata for every agent") - v2x_metadata = [agent_metadata for agent_metadata in metadata if isinstance(agent_metadata, V2XViTMetadata)] - return { - "velocity": self.__pad_to_max([agent_metadata.velocity for agent_metadata in v2x_metadata], 0.0), - "time_delay": self.__pad_to_max( - [float(receive_frame - agent_metadata.capture_frame) for agent_metadata in v2x_metadata], - 0.0, - ), - "infra": self.__pad_to_max([agent_metadata.agent_type for agent_metadata in v2x_metadata], 0.0), - "spatial_correction_matrix": self.__pad_spatial_matrix([agent_metadata.spatial_correction_matrix for agent_metadata in v2x_metadata]), - } - - if any(agent_metadata is not None for agent_metadata in metadata): - raise TypeError(f"{self.model_name} does not consume wire metadata") - return {} - - @staticmethod - def __build_model_data(): - return { - "inference_inputs": [], - "metadata": [], - } - - def __append_processed_model_data(self, data, cav_id, cav_base, cav_processed, receive_frame): - data["inference_inputs"].append(cav_processed["inference_input"]) - data["metadata"].append(self.build_model_metadata(cav_id, cav_base, receive_frame)) - - def __agent_in_communication_range(self, cav_base, ego_lidar_pose): - dx = cav_base["params"]["lidar_pose"][0] - ego_lidar_pose[0] - dy = cav_base["params"]["lidar_pose"][1] - ego_lidar_pose[1] - return math.hypot(dx, dy) <= opencood.data_utils.datasets.COM_RANGE - - def __project_lidar_for_visualization(self, cav_base): - transformation_matrix = cav_base["params"]["transformation_matrix"] - lidar_np = np.array(cav_base["lidar_np"], copy=True) - lidar_np = mask_ego_points(lidar_np) - if self.proj_first: - lidar_np[:, :3] = box_utils.project_points_by_matrix_torch(lidar_np[:, :3], transformation_matrix) - return mask_points_by_range(lidar_np, self.params["preprocess"]["cav_lidar_range"]) - - def build_visualization_context(self, ego_id: str, base_data_dict: dict[str, Any]) -> dict[str, Any]: - """ - Build visualization data from the local scene snapshot. - - Parameters - ---------- - ego_id : str - Identifier of the receiving ego agent. - base_data_dict : dict[str, Any] - Local dataset snapshot, independent of delivered messages. - - Returns - ------- - dict[str, Any] - Per-agent projected point clouds and their local identities. - """ - if not self.visualize: - return { - "projected_lidar_stack": [], - "projected_lidar_roles": [], - "projected_lidar_agent_ids": [], - } - - projected_lidar_stack = [] - projected_lidar_roles = [] - projected_lidar_agent_ids = [] - - for cav_id in base_data_dict: - projected_lidar_stack.append(self.__project_lidar_for_visualization(base_data_dict[cav_id])) - projected_lidar_roles.append("ego" if cav_id == ego_id else "other") - projected_lidar_agent_ids.append(cav_id) - - return { - "projected_lidar_stack": projected_lidar_stack, - "projected_lidar_roles": projected_lidar_roles, - "projected_lidar_agent_ids": projected_lidar_agent_ids, - } - - def __process_with_messages(self, ego_id, ego_lidar_pose, base_data_dict, receive_frame): - return self.__process_encoded_messages( - ego_id, - ego_lidar_pose, - base_data_dict, - receive_frame, - ) - - def __process_encoded_messages( - self, - ego_id: str, - ego_lidar_pose: list[float], - base_data_dict: dict[str, Any], - receive_frame: int, - ) -> dict[str, Any]: - """ - Assemble learned local and delivered features for model-side fusion. - - Parameters - ---------- - ego_id : str - Identifier of the receiving agent. - ego_lidar_pose : list[float] - Current receiver LiDAR pose. - base_data_dict : dict[str, Any] - Local scene observations. - receive_frame : int - Current dataset frame. - - Returns - ------- - dict[str, Any] - Successfully available learned features and their metadata. - """ - if self.communication_adapter is None: - raise RuntimeError("Intermediate feature decoding requires a communication adapter") - - ego_cav_base = base_data_dict[ego_id] - ego_cav_processed = self.get_item_single_car(ego_cav_base, ego_lidar_pose) - features = [ - self.communication_adapter.encode_local_intermediate_input( - self, - ego_cav_processed["inference_input"], - ) - ] - metadata = [self.build_model_metadata(ego_id, ego_cav_base, receive_frame)] - - if ego_id in self.payload_handler.current_artery_payload: - for cav_id in base_data_dict: - if cav_id == ego_id: - continue - raw_payload = self.payload_handler.get_artery_payload( - ego_id, - cav_id, - self.module_name, - ) - if raw_payload is None: - continue - decoded = self.communication_adapter.decode_received_payload( - raw_payload, - ego_lidar_pose, - ) - metadata.append(decoded.pop("metadata")) - features.append(decoded) - - return { - "intermediate_features": features, - "metadata": metadata, - } - - def __process_without_messages(self, ego_id, ego_lidar_pose, base_data_dict, receive_frame): - data = self.__build_model_data() - for cav_id, selected_cav_base in base_data_dict.items(): - if not self.__agent_in_communication_range(selected_cav_base, ego_lidar_pose): - continue - - selected_cav_processed = self.get_item_single_car(selected_cav_base, ego_lidar_pose) - self.__append_processed_model_data(data, cav_id, selected_cav_base, selected_cav_processed, receive_frame) - - return data - - def build_local_supervision(self, base_data_dict: dict[str, Any], ego_lidar_pose: list[float]) -> dict[str, Any]: - """ - Build ground truth and training targets from the local scene. - - Parameters - ---------- - base_data_dict : dict[str, Any] - Complete local dataset snapshot for the current scene. - ego_lidar_pose : list[float] - Current ego LiDAR pose in world coordinates. - - Returns - ------- - dict[str, Any] - Ground-truth boxes, object identifiers, anchors, and labels. - """ - object_bbx_center, object_bbx_mask, object_ids = self.post_processor.generate_object_center(list(base_data_dict.values()), ego_lidar_pose) - anchor_box = self.post_processor.generate_anchor_box() - label_dict = self.post_processor.generate_label( - gt_box_center=object_bbx_center, - anchors=anchor_box, - mask=object_bbx_mask, - ) - return { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "object_ids": object_ids, - "anchor_box": anchor_box, - "label_dict": label_dict, - } - - def assemble_inference_sample( - self, - inference_input: dict[str, Any], - local_supervision: dict[str, Any], - visualization_context: dict[str, Any], - receive_frame: int, - ) -> OrderedDict[str, dict[str, Any]]: - """ - Assemble model input with local supervision and visualization. - - Parameters - ---------- - inference_input : dict[str, Any] - Ego features plus features from messages that were delivered. - local_supervision : dict[str, Any] - Ground truth produced solely from the local dataset snapshot. - visualization_context : dict[str, Any] - Visualization data produced solely from the local snapshot. - receive_frame : int - Current receiver dataset frame. - - Returns - ------- - collections.OrderedDict - Dataset sample consumed by the collate and inference pipeline. - - Raises - ------ - ValueError - If model inputs and metadata entries are not aligned. - """ - metadata: list[IntermediateMetadata | None] = inference_input["metadata"] - if "intermediate_features" in inference_input: - intermediate_features = inference_input["intermediate_features"] - if len(metadata) != len(intermediate_features): - raise ValueError("Every intermediate feature must have a matching metadata entry") - feature_fields = set(intermediate_features[0]) - if any(set(features) != feature_fields for features in intermediate_features[1:]): - raise TypeError("All agents must provide the same intermediate feature fields") - merged_feature_dict = { - field: ( - np.concatenate([features[field] for features in intermediate_features], axis=0) - if isinstance(intermediate_features[0][field], np.ndarray) - else np.asarray([features[field] for features in intermediate_features]) - ) - for field in feature_fields - } - cav_num = len(intermediate_features) - model_input = {"intermediate_features": merged_feature_dict} - else: - typed_inputs: list[InferenceInput] = inference_input["inference_inputs"] - if len(metadata) != len(typed_inputs): - raise ValueError("Every inference input must have a matching metadata entry") - model_input = {"processed_lidar": merge_inference_inputs(typed_inputs)} - cav_num = len(typed_inputs) - - ego_sample = { - **local_supervision, - **model_input, - "cav_num": cav_num, - **self.__assemble_model_metadata(metadata, receive_frame), - } - if self.visualize: - ego_sample.update( - { - "origin_lidar": np.vstack(visualization_context["projected_lidar_stack"]), - "origin_lidar_by_agent": visualization_context["projected_lidar_stack"], - "origin_lidar_roles": visualization_context["projected_lidar_roles"], - "origin_lidar_agent_ids": visualization_context["projected_lidar_agent_ids"], - } - ) - - return OrderedDict({"ego": ego_sample}) - - def __getitem__(self, idx): - base_data_dict = self.retrieve_base_data(idx, cur_ego_pose_flag=self.cur_ego_pose_flag) - ego_id, ego_lidar_pose = self.__find_ego_vehicle(base_data_dict) - visualization_base_data_dict = self.__retrieve_visualization_base_data(idx) if self.visualize else base_data_dict - - if self.payload_handler is not None: - inference_input = self.__process_with_messages(ego_id, ego_lidar_pose, base_data_dict, idx) - else: - inference_input = self.__process_without_messages(ego_id, ego_lidar_pose, base_data_dict, idx) - - local_supervision = self.build_local_supervision(base_data_dict, ego_lidar_pose) - visualization_context = self.build_visualization_context(ego_id, visualization_base_data_dict) - return self.assemble_inference_sample( - inference_input, - local_supervision, - visualization_context, - idx, - ) - - def get_item_single_car(self, selected_cav_base, ego_pose): - """ - Project and preprocess one agent's LiDAR input. - - Parameters - ---------- - selected_cav_base : dict - The dictionary contains a single CAV's raw information. - ego_pose : list - The ego vehicle lidar pose under world coordinate. - - Returns - ------- - selected_cav_processed : dict - The dictionary contains the cav's processed information. - """ - selected_cav_processed = {} - - # calculate the transformation matrix - transformation_matrix = selected_cav_base["params"]["transformation_matrix"] - - # filter lidar - lidar_np = selected_cav_base["lidar_np"] - lidar_np = shuffle_points(lidar_np) - # remove points that hit itself - lidar_np = mask_ego_points(lidar_np) - # project the lidar to ego space - if self.proj_first: - lidar_np[:, :3] = box_utils.project_points_by_matrix_torch(lidar_np[:, :3], transformation_matrix) - lidar_np = mask_points_by_range(lidar_np, self.params["preprocess"]["cav_lidar_range"]) - inference_input = build_inference_input(self.pre_processor.preprocess(lidar_np)) - - selected_cav_processed.update( - { - "projected_lidar": lidar_np, - "inference_input": inference_input, - } - ) - - return selected_cav_processed - - @staticmethod - def merge_features_to_dict(processed_feature_list): - """ - Merge the preprocessed features from different cavs to the same - dictionary. - - Parameters - ---------- - processed_feature_list : list - A list of dictionary containing all processed features from - different cavs. - - Returns - ------- - merged_feature_dict: dict - key: feature names, value: list of features. - """ - - merged_feature_dict = OrderedDict() - - for i in range(len(processed_feature_list)): - for feature_name, feature in processed_feature_list[i].items(): - if feature_name not in merged_feature_dict: - merged_feature_dict[feature_name] = [] - if isinstance(feature, list): - merged_feature_dict[feature_name] += feature - else: - merged_feature_dict[feature_name].append(feature) - - return merged_feature_dict - - def collate_batch_train(self, batch): - # Intermediate fusion is different the other two - output_dict = {"ego": {}} - - object_bbx_center = [] - object_bbx_mask = [] - object_ids = [] - processed_lidar_list = [] - intermediate_feature_list = [] - # used to record different scenario - record_len = [] - label_dict_list = [] - - # used for PriorEncoding for models - velocity = [] - time_delay = [] - infra = [] - - # pairwise transformation matrix - pairwise_t_matrix_list = [] - - # used for correcting the spatial transformation between delayed timestamp - # and current timestamp - spatial_correction_matrix_list = [] - - if self.visualize: - origin_lidar = [] - - for i in range(len(batch)): - ego_dict = batch[i]["ego"] - object_bbx_center.append(ego_dict["object_bbx_center"]) - object_bbx_mask.append(ego_dict["object_bbx_mask"]) - object_ids.append(ego_dict["object_ids"]) - - if "intermediate_features" in ego_dict: - intermediate_feature_list.append(ego_dict["intermediate_features"]) - else: - processed_lidar_list.append(ego_dict["processed_lidar"]) - record_len.append(ego_dict["cav_num"]) - label_dict_list.append(ego_dict["label_dict"]) - - if self.model_name in PAIRWISE_METADATA_MODELS: - pairwise_t_matrix_list.append(ego_dict["pairwise_t_matrix"]) - elif self.model_name in V2X_VIT_METADATA_MODELS: - velocity.append(ego_dict["velocity"]) - time_delay.append(ego_dict["time_delay"]) - infra.append(ego_dict["infra"]) - spatial_correction_matrix_list.append(ego_dict["spatial_correction_matrix"]) - - if self.visualize: - origin_lidar.append(ego_dict["origin_lidar"]) - # convert to numpy, (B, max_num, 7) - object_bbx_center = torch.from_numpy(np.array(object_bbx_center)) - object_bbx_mask = torch.from_numpy(np.array(object_bbx_mask)) - - if intermediate_feature_list: - if processed_lidar_list: - raise TypeError("A batch cannot mix preprocessor inputs and intermediate features") - intermediate_features_torch = { - field: torch.from_numpy(np.concatenate([features[field] for features in intermediate_feature_list], axis=0)) - for field in intermediate_feature_list[0] - } - else: - merged_feature_dict = self.merge_features_to_dict(processed_lidar_list) - processed_lidar_torch_dict = self.pre_processor.collate_batch(merged_feature_dict) - # [2, 3, 4, ..., M], M <= max_cav - record_len = torch.from_numpy(np.array(record_len, dtype=int)) - label_torch_dict = self.post_processor.collate_batch(label_dict_list) - - # object id is only used during inference, where batch size is 1. - # so here we only get the first element. - output_dict["ego"].update( - { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "record_len": record_len, - "label_dict": label_torch_dict, - "object_ids": object_ids[0], - } - ) - - if intermediate_feature_list: - output_dict["ego"]["intermediate_features"] = intermediate_features_torch - else: - output_dict["ego"]["processed_lidar"] = processed_lidar_torch_dict - - if self.model_name in PAIRWISE_METADATA_MODELS: - output_dict["ego"]["pairwise_t_matrix"] = torch.from_numpy(np.array(pairwise_t_matrix_list)) - elif self.model_name in V2X_VIT_METADATA_MODELS: - velocity_tensor = torch.from_numpy(np.array(velocity)) - time_delay_tensor = torch.from_numpy(np.array(time_delay)) - infra_tensor = torch.from_numpy(np.array(infra)) - output_dict["ego"].update( - { - "prior_encoding": torch.stack([velocity_tensor, time_delay_tensor, infra_tensor], dim=-1).float(), - "spatial_correction_matrix": torch.from_numpy(np.array(spatial_correction_matrix_list)), - } - ) - - if self.visualize: - origin_lidar = np.array(downsample_lidar_minimum(pcd_np_list=origin_lidar)) - origin_lidar = torch.from_numpy(origin_lidar) - output_dict["ego"].update({"origin_lidar": origin_lidar}) - - return output_dict - - def collate_batch_test(self, batch): - assert len(batch) <= 1, "Batch size 1 is required during testing!" - output_dict = self.collate_batch_train(batch) - - # check if anchor box in the batch - if batch[0]["ego"]["anchor_box"] is not None: - output_dict["ego"].update({"anchor_box": torch.from_numpy(np.array(batch[0]["ego"]["anchor_box"]))}) - - if self.visualize and "origin_lidar_by_agent" in batch[0]["ego"]: - output_dict["ego"].update( - { - "origin_lidar_by_agent": [torch.from_numpy(np.array(points)) for points in batch[0]["ego"]["origin_lidar_by_agent"]], - "origin_lidar_roles": list(batch[0]["ego"]["origin_lidar_roles"]), - "origin_lidar_agent_ids": list(batch[0]["ego"]["origin_lidar_agent_ids"]), - } - ) - - # save the transformation matrix (4, 4) to ego vehicle - transformation_matrix_torch = torch.from_numpy(np.identity(4)).float() - output_dict["ego"].update({"transformation_matrix": transformation_matrix_torch}) - - return output_dict - - def post_process(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D/3D bounding box. - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box_tensor : torch.Tensor - The tensor of prediction bounding box after NMS. - gt_box_tensor : torch.Tensor - The tensor of gt bounding box. - """ - pred_box_tensor, pred_score = self.post_processor.post_process(data_dict, output_dict) - gt_box_tensor = self.post_processor.generate_gt_bbx(data_dict) - - return pred_box_tensor, pred_score, gt_box_tensor diff --git a/OpenCOOD/opencood/data_utils/datasets/intermediate_fusion_dataset_v2.py b/OpenCOOD/opencood/data_utils/datasets/intermediate_fusion_dataset_v2.py deleted file mode 100644 index c21f2e72..00000000 --- a/OpenCOOD/opencood/data_utils/datasets/intermediate_fusion_dataset_v2.py +++ /dev/null @@ -1,595 +0,0 @@ -""" -Dataset class for 2-stage backbone intermediate fusion -""" - -import math -from collections import OrderedDict -from collections.abc import Callable -from typing import Any - -import numpy as np -import torch - -import opencood.data_utils.datasets -import opencood.data_utils.post_processor as post_processor -from opencood.utils import box_utils -from opencood.data_utils.datasets import basedataset -from opencood.models.communication_adapters import PoseFrameMetadata -from opencood.data_utils.pre_processor import build_preprocessor -from opencood.utils.pcd_utils import pcd_to_np, mask_points_by_range, mask_ego_points, shuffle_points -from opencood.utils.transformation_utils import x1_to_x2 -from opencood.pcdet_utils.roiaware_pool3d.roiaware_pool3d_utils import points_in_boxes_cpu - - -# TODO: The fpvrcnn_intermediate_fusion model has an issue with weights on this dataset -# TODO: Verify the model behavior with this dataset -# The weights appear to be incorrectly arranged -# size mismatch for spconv_block.conv_out.0.weight: -# copying a param with shape torch.Size([3, 1, 1, 64, 64]) from checkpoint, -# while the current model expects torch.Size([64, 3, 1, 1, 64]) -# The model will need to be retrained and the code updated -class IntermediateFusionDatasetV2(basedataset.BaseDataset): - """ - This class is for intermediate fusion where each vehicle transmit the - deep features to ego. - """ - - def __init__(self, params, visualize, train=True, payload_handler=None): - super(IntermediateFusionDatasetV2, self).__init__(params, visualize, train) - self.pre_processor = build_preprocessor(params["preprocess"], train) - self.post_processor = post_processor.build_postprocessor(params["postprocess"], train) - - # whether there is a time delay between the time that cav project - # lidar to ego and the ego receive the delivered feature - if "cur_ego_pose_flag" in params["fusion"]["args"]: - self.cur_ego_pose_flag = params["fusion"]["args"]["cur_ego_pose_flag"] - else: - self.cur_ego_pose_flag = True - - self.payload_handler = payload_handler - self.module_name = "OpenCOOD.IntermediateFusionDatasetV2" - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[[dict[str, Any], PoseFrameMetadata], object] | None = None, - ) -> None: - """ - Build model-specific payloads from local agent observations. - - Parameters - ---------- - idx : int - Dataset frame to publish. - agent_payload_builder : Callable[[dict[str, Any], PoseFrameMetadata], object] | None - Model adapter callback that converts one local agent observation - and its metadata into a typed wire payload. - - Raises - ------ - NotImplementedError - If the configured model has no communication adapter for this - dataset contract. - """ - base_data_dict = self.retrieve_base_data(idx, cur_ego_pose_flag=self.cur_ego_pose_flag) - - if self.payload_handler is not None: - if agent_payload_builder is None: - raise NotImplementedError("IntermediateFusionDatasetV2 requires a model-specific communication adapter") - for cav_id, selected_cav_base in base_data_dict.items(): - sender_pose = selected_cav_base["params"]["lidar_pose"] - selected_cav_processed = self.build_agent_model_input(selected_cav_base, sender_pose) - metadata = PoseFrameMetadata( - lidar_pose=tuple(float(value) for value in sender_pose), - capture_frame=int(idx - selected_cav_base["time_delay"]), - ) - payload = agent_payload_builder(selected_cav_processed, metadata) - self.payload_handler.set_opencda_payload(cav_id, self.module_name, payload) - - def __find_ego_vehicle(self, base_data_dict): - ego_id = -1 - ego_lidar_pose = [] - - # first find the ego vehicle's lidar pose - for cav_id, cav_content in base_data_dict.items(): - if cav_content["ego"]: - ego_id = cav_id - ego_lidar_pose = cav_content["params"]["lidar_pose"] - break - - assert cav_id == list(base_data_dict.keys())[0], "The first element in the OrderedDict must be ego" - assert ego_id != -1 - assert len(ego_lidar_pose) > 0 - - return ego_id, ego_lidar_pose - - def __retrieve_visualization_base_data(self, idx): - scenario_index = 0 - for i, ele in enumerate(self.len_record): - if idx < ele: - scenario_index = i - break - scenario_database = self.scenario_database[scenario_index] - - timestamp_index = idx if scenario_index == 0 else idx - self.len_record[scenario_index - 1] - timestamp_key = self.return_timestamp_key(scenario_database, timestamp_index) - ego_cav_content = self.calc_dist_to_ego(scenario_database, timestamp_key) - - data = OrderedDict() - for cav_id, cav_content in scenario_database.items(): - cav_snapshot = cav_content[timestamp_key] - data[cav_id] = OrderedDict() - data[cav_id]["ego"] = cav_content["ego"] - data[cav_id]["time_delay"] = 0 - data[cav_id]["params"] = self.reform_param( - cav_content, - ego_cav_content, - timestamp_key, - timestamp_key, - cur_ego_pose_flag=True, - ) - data[cav_id]["lidar_np"] = cav_snapshot["lidar_np"] if "lidar_np" in cav_snapshot else pcd_to_np(cav_snapshot["lidar"]) - if "spoofing_mask" in cav_snapshot: - data[cav_id]["spoofing_mask"] = cav_snapshot["spoofing_mask"] - return data - - def __project_lidar_for_visualization(self, cav_base, ego_lidar_pose): - transformation_matrix = x1_to_x2(cav_base["params"]["lidar_pose"], ego_lidar_pose) - lidar_np = np.array(cav_base["lidar_np"], copy=True) - lidar_np = mask_ego_points(lidar_np) - lidar_np[:, :3] = box_utils.project_points_by_matrix_torch(lidar_np[:, :3], transformation_matrix) - return mask_points_by_range(lidar_np, self.params["preprocess"]["cav_lidar_range"]) - - def build_visualization_context(self, ego_id, ego_lidar_pose, base_data_dict): - """ - Build local visualization data independently of wire delivery. - - Parameters - ---------- - ego_id : str - Identifier of the receiving agent. - ego_lidar_pose : list[float] - Receiver pose used to project visualization point clouds. - base_data_dict : dict[str, Any] - Complete local scene snapshot. - - Returns - ------- - dict[str, Any] - Per-agent point clouds, roles, and identifiers used only by - visualization code. - """ - if not self.visualize: - return { - "projected_lidar_stack": [], - "projected_lidar_roles": [], - "projected_lidar_agent_ids": [], - } - - projected_lidar_stack = [] - projected_lidar_roles = [] - projected_lidar_agent_ids = [] - - for cav_id in base_data_dict: - projected_lidar_stack.append(self.__project_lidar_for_visualization(base_data_dict[cav_id], ego_lidar_pose)) - projected_lidar_roles.append("ego" if cav_id == ego_id else "other") - projected_lidar_agent_ids.append(cav_id) - - return { - "projected_lidar_stack": projected_lidar_stack, - "projected_lidar_roles": projected_lidar_roles, - "projected_lidar_agent_ids": projected_lidar_agent_ids, - } - - def __process_with_messages(self, ego_id, ego_lidar_pose, base_data_dict, visualization_ego_lidar_pose, visualization_base_data_dict): - processed_features = [] - model_lidar_stack = [] - remote_agent_outputs = [] - - ego_cav_base = base_data_dict.get(ego_id) - ego_cav_processed = self.build_agent_model_input(ego_cav_base, ego_lidar_pose) - - processed_features.append(ego_cav_processed["processed_features"]) - model_lidar_stack.append(ego_cav_processed["projected_lidar"]) - - if self.communication_adapter is None: - raise RuntimeError("IntermediateFusionDatasetV2 requires an attached communication adapter") - - if ego_id in self.payload_handler.current_artery_payload: - for cav_id, _ in base_data_dict.items(): - if cav_id == ego_id: - continue - raw_payload = self.payload_handler.get_artery_payload(ego_id, cav_id, self.module_name) - if raw_payload is None: - continue - remote_agent_outputs.append(self.communication_adapter.decode_received_payload(raw_payload, ego_lidar_pose)) - - return { - "processed_features": processed_features, - "model_lidar_stack": model_lidar_stack, - "remote_agent_outputs": remote_agent_outputs, - **self.build_visualization_context(ego_id, visualization_ego_lidar_pose, visualization_base_data_dict), - } - - def build_local_supervision(self, base_data_dict: dict[str, Any], ego_lidar_pose: list[float]) -> dict[str, Any]: - """ - Build two-stage ground truth from the complete local scene. - - Parameters - ---------- - base_data_dict : dict[str, Any] - Complete local dataset snapshot, independent of packet delivery. - ego_lidar_pose : list[float] - Current receiver LiDAR pose. - - Returns - ------- - dict[str, Any] - Ground-truth boxes, masks, identifiers, and anchors, plus - stage-two targets when building a training sample. - """ - object_bbx_center, object_bbx_mask, object_ids = self.post_processor.generate_object_center( - list(base_data_dict.values()), - ego_lidar_pose, - ) - anchor_box = self.post_processor.generate_anchor_box() - supervision: dict[str, Any] = { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "object_ids": object_ids, - "anchor_box": anchor_box, - } - if self.payload_handler is None: - supervision["stage2_label"] = self.post_processor.generate_label( - gt_box_center=object_bbx_center, - anchors=anchor_box, - mask=object_bbx_mask, - ) - return supervision - - def __process_without_messages(self, ego_id, ego_lidar_pose, base_data_dict, visualization_ego_lidar_pose, visualization_base_data_dict): - processed_features = [] - object_stack = [] - model_lidar_stack = [] - agent_to_ego = [] - - for cav_id, selected_cav_base in base_data_dict.items(): - dx = selected_cav_base["params"]["lidar_pose"][0] - ego_lidar_pose[0] - dy = selected_cav_base["params"]["lidar_pose"][1] - ego_lidar_pose[1] - distance = math.hypot(dx, dy) - - if distance > opencood.data_utils.datasets.COM_RANGE: - continue - - agent_pose = selected_cav_base["params"]["lidar_pose"] - selected_cav_processed = self.get_item_single_car(selected_cav_base, agent_pose) - - if len(selected_cav_processed["projected_lidar"]) > 10: - object_stack.append(selected_cav_processed["object_bbx_center"]) - processed_features.append(selected_cav_processed["processed_features"]) - model_lidar_stack.append(selected_cav_processed["projected_lidar"]) - agent_to_ego.append(x1_to_x2(agent_pose, ego_lidar_pose)) - - return { - "processed_features": processed_features, - "object_stack": object_stack, - "model_lidar_stack": model_lidar_stack, - "agent_to_ego": agent_to_ego, - **self.build_visualization_context(ego_id, visualization_ego_lidar_pose, visualization_base_data_dict), - } - - def __getitem__(self, idx): - # put here to avoid initialization error - base_data_dict = self.retrieve_base_data(idx, cur_ego_pose_flag=self.cur_ego_pose_flag) - - processed_data_dict = OrderedDict() - processed_data_dict["ego"] = {} - - ego_id, ego_lidar_pose = self.__find_ego_vehicle(base_data_dict) - visualization_base_data_dict = self.__retrieve_visualization_base_data(idx) if self.visualize else base_data_dict - _, visualization_ego_lidar_pose = self.__find_ego_vehicle(visualization_base_data_dict) - - if self.payload_handler is not None: - data = self.__process_with_messages( - ego_id, - ego_lidar_pose, - base_data_dict, - visualization_ego_lidar_pose, - visualization_base_data_dict, - ) - else: - data = self.__process_without_messages( - ego_id, - ego_lidar_pose, - base_data_dict, - visualization_ego_lidar_pose, - visualization_base_data_dict, - ) - - # merge preprocessed features from different cavs into the same dict - cav_num = len(data["processed_features"]) - merged_feature_dict = self.merge_features_to_dict(data["processed_features"]) - - local_supervision = self.build_local_supervision(base_data_dict, ego_lidar_pose) - anchor_box = local_supervision["anchor_box"] - - processed_data_dict["ego"].update( - { - "object_bbx_center": local_supervision["object_bbx_center"], - "object_bbx_mask": local_supervision["object_bbx_mask"], - "object_ids": local_supervision["object_ids"], - "anchor_box": anchor_box, - "processed_lidar": merged_feature_dict, - "cav_num": cav_num, - } - ) - - if "stage2_label" in local_supervision: - label_dict_no_coop = [] - for boxes, points in zip(data["object_stack"], data["model_lidar_stack"]): - point_indices = points_in_boxes_cpu(points[:, :3], boxes[:, [0, 1, 2, 5, 4, 3, 6]]) - cur_mask = point_indices.sum(axis=1) > 0 - if cur_mask.sum() == 0: - label_dict_no_coop.append( - { - "pos_equal_one": np.zeros((*anchor_box.shape[:2], self.post_processor.anchor_num)), - "neg_equal_one": np.ones((*anchor_box.shape[:2], self.post_processor.anchor_num)), - "targets": np.zeros((*anchor_box.shape[:2], self.post_processor.anchor_num * 7)), - } - ) - continue - bbx_center = np.zeros((self.params["postprocess"]["max_num"], 7)) - bbx_mask = np.zeros(self.params["postprocess"]["max_num"]) - bbx_center[: boxes[cur_mask].shape[0], :] = boxes[cur_mask] - bbx_mask[: boxes[cur_mask].shape[0]] = 1 - label_dict_no_coop.append( - self.post_processor.generate_label( - gt_box_center=bbx_center, - anchors=anchor_box, - mask=bbx_mask, - ) - ) - processed_data_dict["ego"]["label_dict"] = { - "stage1": label_dict_no_coop, - "stage2": local_supervision["stage2_label"], - } - - processed_data_dict["ego"].update( - { - "origin_lidar": data["model_lidar_stack"], - "origin_lidar_by_agent": data["projected_lidar_stack"], - "origin_lidar_roles": data["projected_lidar_roles"], - "origin_lidar_agent_ids": data["projected_lidar_agent_ids"], - } - ) - if "remote_agent_outputs" in data: - processed_data_dict["ego"]["remote_agent_outputs"] = data["remote_agent_outputs"] - if "agent_to_ego" in data: - processed_data_dict["ego"]["agent_to_ego"] = data["agent_to_ego"] - return processed_data_dict - - def build_agent_model_input(self, selected_cav_base, target_pose): - """ - Build one agent's local two-stage input without supervision data. - - Parameters - ---------- - selected_cav_base : dict - Raw and metadata fields for one agent. - target_pose : list - Pose of the coordinate frame used for this model input. - - Returns - ------- - dict - Projected raw points and their voxelized representation. - """ - transformation_matrix = x1_to_x2(selected_cav_base["params"]["lidar_pose"], target_pose) - - lidar_np = selected_cav_base["lidar_np"] - lidar_np = shuffle_points(lidar_np) - lidar_np = mask_ego_points(lidar_np) - lidar_np[:, :3] = box_utils.project_points_by_matrix_torch(lidar_np[:, :3], transformation_matrix) - lidar_np = mask_points_by_range(lidar_np, self.params["preprocess"]["cav_lidar_range"]) - return { - "projected_lidar": lidar_np, - "processed_features": self.pre_processor.preprocess(lidar_np), - } - - def get_item_single_car(self, selected_cav_base, ego_pose): - """ - Build one local training agent with its stage-one ground truth. - - Parameters - ---------- - selected_cav_base : dict - Raw and metadata fields for one agent. - ego_pose : list - Pose of the coordinate frame used for model input and targets. - - Returns - ------- - dict - Agent model input plus local stage-one ground-truth boxes. - """ - selected_cav_processed = self.build_agent_model_input(selected_cav_base, ego_pose) - object_bbx_center, object_bbx_mask, _ = self.post_processor.generate_object_center([selected_cav_base], ego_pose) - selected_cav_processed["object_bbx_center"] = object_bbx_center[object_bbx_mask == 1] - return selected_cav_processed - - @staticmethod - def merge_features_to_dict(processed_feature_list): - """ - Merge the preprocessed features from different cavs to the same - dictionary. - - Parameters - ---------- - processed_feature_list : list - A list of dictionary containing all processed features from - different cavs. - - Returns - ------- - merged_feature_dict: dict - key: feature names, value: list of features. - """ - - merged_feature_dict = OrderedDict() - - for i in range(len(processed_feature_list)): - for feature_name, feature in processed_feature_list[i].items(): - if feature_name not in merged_feature_dict: - merged_feature_dict[feature_name] = [] - if isinstance(feature, list): - merged_feature_dict[feature_name] += feature - else: - merged_feature_dict[feature_name].append(feature) - - return merged_feature_dict - - def collate_batch_train(self, batch): - # Intermediate fusion is different the other two - output_dict = {"ego": {}} - - object_bbx_center = [] - object_bbx_mask = [] - object_ids = [] - processed_lidar_list = [] - # used to record different scenario - record_len = [] - label_dict_list = [] - label_dict_no_coop_list = [] - has_labels = "label_dict" in batch[0]["ego"] - - origin_lidar = [] - agent_to_ego = [] - - # added by yys, fpvrcnn needs anchors for - # first stage proposal generation - if batch[0]["ego"]["anchor_box"] is not None: - output_dict["ego"].update({"anchor_box": torch.from_numpy(np.array(batch[0]["ego"]["anchor_box"]))}) - - for i in range(len(batch)): - ego_dict = batch[i]["ego"] - object_bbx_center.append(ego_dict["object_bbx_center"]) - object_bbx_mask.append(ego_dict["object_bbx_mask"]) - object_ids.append(ego_dict["object_ids"]) - - processed_lidar_list.append(ego_dict["processed_lidar"]) - record_len.append(ego_dict["cav_num"]) - if has_labels: - label_dict_no_coop_list.append(ego_dict["label_dict"]["stage1"]) - label_dict_list.append(ego_dict["label_dict"]["stage2"]) - - origin_lidar.append(ego_dict["origin_lidar"]) - if "agent_to_ego" in ego_dict: - agent_to_ego.extend(ego_dict["agent_to_ego"]) - - # convert to numpy, (B, max_num, 7) - object_bbx_center = torch.from_numpy(np.array(object_bbx_center)) - object_bbx_mask = torch.from_numpy(np.array(object_bbx_mask)) - - # example: {'voxel_features':[np.array([1,2,3]]), - # np.array([3,5,6]), ...]} - merged_feature_dict = self.merge_features_to_dict(processed_lidar_list) - processed_lidar_torch_dict = self.pre_processor.collate_batch(merged_feature_dict) - # [2, 3, 4, ..., M], M <= 5 - record_len = torch.from_numpy(np.array(record_len, dtype=int)) - # object id is only used during inference, where batch size is 1. - # so here we only get the first element. - output_dict["ego"].update( - { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "processed_lidar": processed_lidar_torch_dict, - "record_len": record_len, - "object_ids": object_ids[0], - } - ) - if has_labels: - label_torch_dict = self.post_processor.collate_batch(label_dict_list) - label_dict_no_coop = [label_dict for label_list in label_dict_no_coop_list for label_dict in label_list] - output_dict["ego"]["label_dict"] = { - "stage1": self.post_processor.collate_batch(label_dict_no_coop), - "stage2": label_torch_dict, - } - - coords = [] - idx = 0 - for b in range(len(batch)): - for points in origin_lidar[b]: - assert len(points) != 0 - coor_pad = np.pad(points, ((0, 0), (1, 0)), mode="constant", constant_values=idx) - coords.append(coor_pad) - idx += 1 - origin_lidar = np.concatenate(coords, axis=0) - - origin_lidar = torch.from_numpy(origin_lidar) - output_dict["ego"].update({"origin_lidar": origin_lidar}) - - if agent_to_ego: - output_dict["ego"]["agent_to_ego"] = torch.from_numpy(np.asarray(agent_to_ego)).float() - - if "remote_agent_outputs" in batch[0]["ego"]: - if len(batch) != 1: - raise ValueError("Distributed two-stage inference requires batch size one") - output_dict["ego"]["remote_agent_outputs"] = [ - {field: torch.from_numpy(np.asarray(value)) for field, value in agent_output.items()} - for agent_output in batch[0]["ego"]["remote_agent_outputs"] - ] - - return output_dict - - def collate_batch_test(self, batch): - assert len(batch) <= 1, "Batch size 1 is required during testing!" - output_dict = self.collate_batch_train(batch) - - # check if anchor box in the batch - if batch[0]["ego"]["anchor_box"] is not None: - output_dict["ego"].update({"anchor_box": torch.from_numpy(np.array(batch[0]["ego"]["anchor_box"]))}) - - if "origin_lidar_by_agent" in batch[0]["ego"]: - output_dict["ego"].update( - { - "origin_lidar_by_agent": [torch.from_numpy(np.array(points)) for points in batch[0]["ego"]["origin_lidar_by_agent"]], - "origin_lidar_roles": list(batch[0]["ego"]["origin_lidar_roles"]), - "origin_lidar_agent_ids": list(batch[0]["ego"]["origin_lidar_agent_ids"]), - } - ) - - # save the transformation matrix (4, 4) to ego vehicle - transformation_matrix_torch = torch.from_numpy(np.identity(4)).float() - output_dict["ego"].update({"transformation_matrix": transformation_matrix_torch}) - - return output_dict - - def post_process(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D/3D bounding box. - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box_tensor : torch.Tensor - The tensor of prediction bounding box after NMS. - gt_box_tensor : torch.Tensor - The tensor of gt bounding box. - """ - pred_box_tensor, pred_score = self.post_processor.post_process(data_dict, output_dict) - gt_box_tensor = self.post_processor.generate_gt_bbx(data_dict) - - return pred_box_tensor, pred_score, gt_box_tensor - - def visualize_result(self, pred_box_tensor, gt_tensor, pcd, show_vis, save_path, dataset=None): - # we need to convert the pcd from [n, 5] -> [n, 4] - pcd = pcd[:, 1:] - # visualize the model output - self.post_processor.visualize(pred_box_tensor, gt_tensor, pcd, show_vis, save_path, dataset=dataset) diff --git a/OpenCOOD/opencood/data_utils/datasets/late_fusion_dataset.py b/OpenCOOD/opencood/data_utils/datasets/late_fusion_dataset.py deleted file mode 100644 index 9a4b0464..00000000 --- a/OpenCOOD/opencood/data_utils/datasets/late_fusion_dataset.py +++ /dev/null @@ -1,566 +0,0 @@ -""" -Dataset class for late fusion -""" - -import math -import random -import logging -from collections import OrderedDict -from collections.abc import Callable -from typing import Any - -import numpy as np -import numpy.typing as npt -import torch - -import opencood.data_utils.datasets -from opencood.data_utils.post_processor import build_postprocessor -from opencood.data_utils.datasets import basedataset -from opencood.models.communication_adapters import ( - InferenceInput, - PoseFrameMetadata, - build_inference_input, - inference_input_to_dict, -) -from opencood.data_utils.pre_processor import build_preprocessor -from opencood.utils import box_utils -from opencood.utils.pcd_utils import pcd_to_np, mask_points_by_range, mask_ego_points, shuffle_points, downsample_lidar_minimum -from opencood.utils.transformation_utils import x1_to_x2 - -logger = logging.getLogger("cavise.opencda.OpenCOOD.opencood.data_utils.datasets.late_fusion_dataset") - - -class LateFusionDataset(basedataset.BaseDataset): - """ - This class is for late fusion where each vehicle transmits its - detection outputs to ego. - """ - - def __init__(self, params, visualize, train=True, payload_handler=None): - super(LateFusionDataset, self).__init__(params, visualize, train) - self.pre_processor = build_preprocessor(params["preprocess"], train) - self.post_processor = build_postprocessor(params["postprocess"], train) - - self.payload_handler = payload_handler - self.module_name = "OpenCOOD.LateFusionDataset" - - def __getitem__(self, idx): - base_data_dict = self.retrieve_base_data(idx) - if self.train: - reformat_data_dict = self.get_item_train(base_data_dict) - else: - reformat_data_dict = self.get_item_test(idx, base_data_dict) - - return reformat_data_dict - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[[InferenceInput, PoseFrameMetadata], object], - ) -> None: - """ - Publish local detector inputs through the late-fusion adapter. - - Parameters - ---------- - idx : int - Dataset frame to publish. - agent_payload_builder : Callable - Adapter callback that runs the sender-side detector and builds its - wire representation. - """ - base_data_dict = self.retrieve_base_data(idx) - - if self.payload_handler is not None: - for cav_id, selected_cav_base in base_data_dict.items(): - selected_cav_processed = self.__build_model_input(selected_cav_base) - payload = agent_payload_builder( - selected_cav_processed["inference_input"], - PoseFrameMetadata( - lidar_pose=selected_cav_base["params"]["lidar_pose"], - capture_frame=idx - int(selected_cav_base["time_delay"]), - ), - ) - self.payload_handler.set_opencda_payload(cav_id, self.module_name, payload) - - def __find_ego_vehicle(self, base_data_dict): - ego_id = -1 - ego_lidar_pose = [] - - # first find the ego vehicle's lidar pose - for cav_id, cav_content in base_data_dict.items(): - if cav_content["ego"]: - ego_id = cav_id - ego_lidar_pose = cav_content["params"]["lidar_pose"] - break - - assert cav_id == list(base_data_dict.keys())[0], "The first element in the OrderedDict must be ego" - assert ego_id != -1 - - return ego_id, ego_lidar_pose - - def __retrieve_visualization_base_data(self, idx): - scenario_index = 0 - for i, ele in enumerate(self.len_record): - if idx < ele: - scenario_index = i - break - scenario_database = self.scenario_database[scenario_index] - - timestamp_index = idx if scenario_index == 0 else idx - self.len_record[scenario_index - 1] - timestamp_key = self.return_timestamp_key(scenario_database, timestamp_index) - ego_cav_content = self.calc_dist_to_ego(scenario_database, timestamp_key) - - data = OrderedDict() - for cav_id, cav_content in scenario_database.items(): - cav_snapshot = cav_content[timestamp_key] - data[cav_id] = OrderedDict() - data[cav_id]["ego"] = cav_content["ego"] - data[cav_id]["time_delay"] = 0 - data[cav_id]["params"] = self.reform_param( - cav_content, - ego_cav_content, - timestamp_key, - timestamp_key, - cur_ego_pose_flag=True, - ) - data[cav_id]["lidar_np"] = cav_snapshot["lidar_np"] if "lidar_np" in cav_snapshot else pcd_to_np(cav_snapshot["lidar"]) - if "spoofing_mask" in cav_snapshot: - data[cav_id]["spoofing_mask"] = cav_snapshot["spoofing_mask"] - return data - - def __project_lidar_for_visualization(self, cav_base, ego_lidar_pose): - transformation_matrix = x1_to_x2(cav_base["params"]["lidar_pose"], ego_lidar_pose) - lidar_np = np.array(cav_base["lidar_np"], copy=True) - lidar_np = mask_ego_points(lidar_np) - lidar_np[:, :3] = box_utils.project_points_by_matrix_torch(lidar_np[:, :3], transformation_matrix) - return mask_points_by_range(lidar_np, self.params["preprocess"]["cav_lidar_range"]) - - def build_visualization_context( - self, - ego_id: str, - ego_lidar_pose: list[float], - base_data_dict: dict[str, Any], - ) -> dict[str, Any]: - """ - Build visualization data from the local scene snapshot. - - Parameters - ---------- - ego_id : str - Identifier of the receiving ego agent. - ego_lidar_pose : list[float] - Current ego LiDAR pose in world coordinates. - base_data_dict : dict[str, Any] - Local dataset snapshot, independent of delivered messages. - - Returns - ------- - dict[str, Any] - Per-agent projected point clouds and their local identities. - """ - if not self.visualize: - return {} - - origin_lidar_by_agent = [] - origin_lidar_roles = [] - origin_lidar_agent_ids = [] - - for cav_id in base_data_dict: - origin_lidar_by_agent.append(self.__project_lidar_for_visualization(base_data_dict[cav_id], ego_lidar_pose)) - origin_lidar_roles.append("ego" if cav_id == ego_id else "other") - origin_lidar_agent_ids.append(cav_id) - - return { - "origin_lidar_by_agent": origin_lidar_by_agent, - "origin_lidar_roles": origin_lidar_roles, - "origin_lidar_agent_ids": origin_lidar_agent_ids, - } - - def __process_with_messages(self, ego_id, ego_lidar_pose, base_data_dict): - processed_data_dict = OrderedDict() - - ego_cav_base = base_data_dict.get(ego_id) - ego_cav_processed = self.__build_model_input(ego_cav_base) - - transformation_matrix_info = x1_to_x2(ego_lidar_pose, ego_lidar_pose) - processed_data_dict["ego"] = { - "inference_input": ego_cav_processed["inference_input"], - "transformation_matrix": transformation_matrix_info, - } - - if ego_id in self.payload_handler.current_artery_payload: - for cav_id, _ in base_data_dict.items(): - if cav_id == ego_id: - continue - raw_payload = self.payload_handler.get_artery_payload(ego_id, cav_id, self.module_name) - if raw_payload is None: - continue - if self.communication_adapter is None: - raise RuntimeError("Late-fusion payload decoding requires a communication adapter") - processed_data_dict[cav_id] = self.communication_adapter.decode_received_payload( - raw_payload, - ego_lidar_pose, - ) - - return processed_data_dict - - def __process_without_messages(self, ego_id, ego_lidar_pose, base_data_dict): - processed_data_dict = OrderedDict() - - for cav_id, selected_cav_base in base_data_dict.items(): - dx = selected_cav_base["params"]["lidar_pose"][0] - ego_lidar_pose[0] - dy = selected_cav_base["params"]["lidar_pose"][1] - ego_lidar_pose[1] - distance = math.hypot(dx, dy) - - if distance > opencood.data_utils.datasets.COM_RANGE: - continue - - # find the transformation matrix from current cav to ego. - cav_lidar_pose = selected_cav_base["params"]["lidar_pose"] - transformation_matrix = x1_to_x2(cav_lidar_pose, ego_lidar_pose) - - selected_cav_processed = self.__build_model_input(selected_cav_base) - update_cav = "ego" if cav_id == ego_id else cav_id - processed_data_dict[update_cav] = { - "inference_input": selected_cav_processed["inference_input"], - "transformation_matrix": transformation_matrix, - } - - return processed_data_dict - - def build_local_supervision(self, base_data_dict: dict[str, Any], ego_lidar_pose: list[float]) -> dict[str, Any]: - """ - Build ego-frame ground truth and labels from the local scene. - - Parameters - ---------- - base_data_dict : dict[str, Any] - Complete local dataset snapshot for the current scene. - ego_lidar_pose : list[float] - Current ego LiDAR pose in world coordinates. - - Returns - ------- - dict[str, Any] - Ego-frame ground truth, anchors, and target labels. - """ - object_bbx_center, object_bbx_mask, object_ids = self.post_processor.generate_object_center(list(base_data_dict.values()), ego_lidar_pose) - anchor_box = self.post_processor.generate_anchor_box() - label_dict = self.post_processor.generate_label( - gt_box_center=object_bbx_center, - anchors=anchor_box, - mask=object_bbx_mask, - ) - return { - "object_bbx_center": object_bbx_center, - "object_bbx_mask": object_bbx_mask, - "object_ids": object_ids, - "anchor_box": anchor_box, - "label_dict": label_dict, - } - - def assemble_inference_sample( - self, - inference_input: OrderedDict[str, dict[str, Any]], - local_supervision: dict[str, Any], - visualization_context: dict[str, Any], - ) -> OrderedDict[str, dict[str, Any]]: - """ - Combine delivered model inputs with local-only dataset data. - - Parameters - ---------- - inference_input : collections.OrderedDict - Ego model input plus model inputs from delivered messages. - local_supervision : dict[str, Any] - Ground truth and labels produced from the local scene. - visualization_context : dict[str, Any] - Visualization data produced from the local scene. - - Returns - ------- - collections.OrderedDict - Late-fusion sample ready for collation. - """ - anchor_box = local_supervision["anchor_box"] - for cav_content in inference_input.values(): - if "inference_input" in cav_content: - cav_content["anchor_box"] = anchor_box - - inference_input["ego"].update(local_supervision) - inference_input["ego"].update(visualization_context) - return inference_input - - def __prepare_lidar(self, selected_cav_base: dict[str, Any]) -> npt.NDArray[Any]: - """ - Filter one agent's local LiDAR point cloud. - - Parameters - ---------- - selected_cav_base : dict[str, Any] - Raw dataset entry for one agent. - - Returns - ------- - numpy.ndarray - Shuffled and range-filtered LiDAR points. - """ - lidar_np = shuffle_points(selected_cav_base["lidar_np"]) - lidar_np = mask_points_by_range(lidar_np, self.params["preprocess"]["cav_lidar_range"]) - return mask_ego_points(lidar_np) - - def __build_model_input(self, selected_cav_base: dict[str, Any]) -> dict[str, Any]: - """ - Build model input without constructing local supervision. - - Parameters - ---------- - selected_cav_base : dict[str, Any] - Raw dataset entry for one agent. - - Returns - ------- - dict[str, Any] - Preprocessed LiDAR features used by the detector. - """ - lidar_np = self.__prepare_lidar(selected_cav_base) - return {"inference_input": build_inference_input(self.pre_processor.preprocess(lidar_np))} - - def get_item_single_car(self, selected_cav_base): - """ - Process a single CAV's information for the train/test pipeline. - - Parameters - ---------- - selected_cav_base : dict - The dictionary contains a single CAV's raw information. - - Returns - ------- - selected_cav_processed : dict - The dictionary contains the cav's processed information. - """ - selected_cav_processed = {} - - lidar_np = self.__prepare_lidar(selected_cav_base) - - # generate the bounding box(n, 7) under the cav's space - object_bbx_center, object_bbx_mask, object_ids = self.post_processor.generate_object_center( - [selected_cav_base], selected_cav_base["params"]["lidar_pose"] - ) - # data augmentation - lidar_np, object_bbx_center, object_bbx_mask = self.augment(lidar_np, object_bbx_center, object_bbx_mask) - - if self.visualize: - selected_cav_processed.update({"origin_lidar": lidar_np}) - - # pre-process the lidar to voxel/bev/downsampled lidar - lidar_dict = self.pre_processor.preprocess(lidar_np) - selected_cav_processed.update({"processed_lidar": lidar_dict}) - - # generate the anchor boxes - anchor_box = self.post_processor.generate_anchor_box() - selected_cav_processed.update({"anchor_box": anchor_box}) - - selected_cav_processed.update({"object_bbx_center": object_bbx_center, "object_bbx_mask": object_bbx_mask, "object_ids": object_ids}) - - # generate targets label - label_dict = self.post_processor.generate_label(gt_box_center=object_bbx_center, anchors=anchor_box, mask=object_bbx_mask) - selected_cav_processed.update({"label_dict": label_dict}) - - return selected_cav_processed - - def get_item_train(self, base_data_dict): - processed_data_dict = OrderedDict() - - # during training, we return a random cav's data - if not self.visualize: - _, selected_cav_base = random.choice(list(base_data_dict.items())) - else: - _, selected_cav_base = list(base_data_dict.items())[0] - - selected_cav_processed = self.get_item_single_car(selected_cav_base) - processed_data_dict.update({"ego": selected_cav_processed}) - - return processed_data_dict - - def get_item_test(self, idx, base_data_dict): - ego_id = -1 - ego_lidar_pose = [] - - ego_id, ego_lidar_pose = self.__find_ego_vehicle(base_data_dict) - visualization_base_data_dict = self.__retrieve_visualization_base_data(idx) if self.visualize else base_data_dict - _, visualization_ego_lidar_pose = self.__find_ego_vehicle(visualization_base_data_dict) - - if self.payload_handler is not None: - inference_input = self.__process_with_messages(ego_id, ego_lidar_pose, base_data_dict) - else: - inference_input = self.__process_without_messages(ego_id, ego_lidar_pose, base_data_dict) - - local_supervision = self.build_local_supervision(base_data_dict, ego_lidar_pose) - visualization_context = self.build_visualization_context( - ego_id, - visualization_ego_lidar_pose, - visualization_base_data_dict, - ) - return self.assemble_inference_sample(inference_input, local_supervision, visualization_context) - - def __collate_processed_lidar(self, cav_content): - if "inference_input" in cav_content: - return self.pre_processor.collate_batch([inference_input_to_dict(cav_content["inference_input"])]) - return self.pre_processor.collate_batch([cav_content["processed_lidar"]]) - - def collate_batch_test(self, batch): - """ - Customized collate function for pytorch dataloader during testing - for late fusion dataset. - - Parameters - ---------- - batch : dict - - Returns - ------- - batch : dict - Reformatted batch. - """ - # currently, we only support batch size of 1 during testing - assert len(batch) <= 1, "Batch size 1 is required during testing!" - batch = batch[0] - - output_dict = {} - - # for late fusion, we also need to stack the lidar for better - # visualization - if self.visualize: - projected_lidar_list = [] - origin_lidar_by_agent = batch.get("ego", {}).get("origin_lidar_by_agent") - origin_lidar_roles = batch.get("ego", {}).get("origin_lidar_roles", []) - origin_lidar_agent_ids = batch.get("ego", {}).get("origin_lidar_agent_ids", []) - - for cav_id, cav_content in batch.items(): - output_dict.update({cav_id: {}}) - - if "pred_box_tensor" in cav_content: - output_dict[cav_id].update( - { - "pred_box_tensor": torch.from_numpy(np.asarray(cav_content["pred_box_tensor"])), - "pred_score": torch.from_numpy(np.asarray(cav_content["pred_score"])), - } - ) - continue - - # the anchor box is the same for all bounding boxes usually, thus - # we don't need the batch dimension. - if cav_content["anchor_box"] is not None: - output_dict[cav_id].update({"anchor_box": torch.from_numpy(np.array(cav_content["anchor_box"]))}) - if self.visualize: - if "origin_lidar" in cav_content: - transformation_matrix = cav_content["transformation_matrix"] - local_lidar = np.array(cav_content["origin_lidar"], copy=True) - projected_lidar = np.array(cav_content["origin_lidar"], copy=True) - projected_lidar[:, :3] = box_utils.project_points_by_matrix_torch(projected_lidar[:, :3], transformation_matrix) - projected_lidar_list.append(projected_lidar) - - # processed lidar dictionary - processed_lidar_torch_dict = self.__collate_processed_lidar(cav_content) - - # save the transformation matrix (4, 4) to ego vehicle - transformation_matrix_torch = torch.from_numpy(np.array(cav_content["transformation_matrix"])).float() - - output_dict[cav_id].update( - { - "processed_lidar": processed_lidar_torch_dict, - "batch_size": 1, - "transformation_matrix": transformation_matrix_torch, - } - ) - - if "object_bbx_center" in cav_content: - output_dict[cav_id].update( - { - "object_bbx_center": torch.from_numpy(np.array([cav_content["object_bbx_center"]])), - "object_bbx_mask": torch.from_numpy(np.array([cav_content["object_bbx_mask"]])), - "label_dict": self.post_processor.collate_batch([cav_content["label_dict"]]), - "object_ids": cav_content["object_ids"], - } - ) - - if self.visualize: - if "origin_lidar" in cav_content: - origin_lidar = np.array(downsample_lidar_minimum(pcd_np_list=[local_lidar])) - origin_lidar = torch.from_numpy(origin_lidar) - output_dict[cav_id].update({"origin_lidar": origin_lidar, "origin_lidar_local": origin_lidar.clone(), "agent_id": cav_id}) - - if self.visualize: - if origin_lidar_by_agent is not None: - output_dict["ego"].update( - { - "origin_lidar": torch.from_numpy(np.vstack(origin_lidar_by_agent)), - "origin_lidar_by_agent": [torch.from_numpy(np.array(points)) for points in origin_lidar_by_agent], - "origin_lidar_roles": list(origin_lidar_roles), - "origin_lidar_agent_ids": list(origin_lidar_agent_ids), - } - ) - elif projected_lidar_list: - projected_lidar_stack = torch.from_numpy(np.vstack(projected_lidar_list)) - output_dict["ego"].update({"origin_lidar": projected_lidar_stack}) - - return output_dict - - def post_process(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D/3D bounding box. - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box_tensor : torch.Tensor - The tensor of prediction bounding box after NMS. - gt_box_tensor : torch.Tensor - The tensor of gt bounding box. - """ - pred_box_list = [] - pred_score_list = [] - for cav_id, cav_content in data_dict.items(): - if "pred_box_tensor" in cav_content: - boxes = cav_content["pred_box_tensor"] - scores = cav_content["pred_score"] - else: - boxes, scores = self.post_processor.decode_agent_predictions( - cav_content, - output_dict[cav_id], - ) - if boxes.shape[0] > 0: - transformation_matrix = cav_content["transformation_matrix"] - if boxes.shape[-1] == 3: - boxes = box_utils.project_box3d(boxes, transformation_matrix) - else: - box3d = torch.nn.functional.pad(boxes, (0, 1)) - boxes = box_utils.project_points_by_matrix_torch( - box3d.reshape(-1, 3), - transformation_matrix, - )[:, :2].reshape(-1, 4, 2) - - if boxes.shape[0] > 0: - pred_box_list.append(boxes) - pred_score_list.append(scores) - - if pred_box_list: - pred_box_tensor, pred_score = self.post_processor.post_process_detections( - torch.cat(pred_box_list, dim=0), - torch.cat(pred_score_list, dim=0), - ) - else: - pred_box_tensor, pred_score = None, None - gt_box_tensor = self.post_processor.generate_gt_bbx({"ego": data_dict["ego"]}) - - return pred_box_tensor, pred_score, gt_box_tensor diff --git a/OpenCOOD/opencood/data_utils/post_processor/__init__.py b/OpenCOOD/opencood/data_utils/post_processor/__init__.py deleted file mode 100644 index da18532c..00000000 --- a/OpenCOOD/opencood/data_utils/post_processor/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -from opencood.data_utils.post_processor.voxel_postprocessor import VoxelPostprocessor -from opencood.data_utils.post_processor.bev_postprocessor import BevPostprocessor -from opencood.data_utils.post_processor.ciassd_postprocessor import CiassdPostprocessor -from opencood.data_utils.post_processor.fpvrcnn_postprocessor import FpvrcnnPostprocessor - -__all__ = { - "VoxelPostprocessor": VoxelPostprocessor, - "BevPostprocessor": BevPostprocessor, - "CiassdPostprocessor": CiassdPostprocessor, - "FpvrcnnPostprocessor": FpvrcnnPostprocessor, -} - - -def build_postprocessor(anchor_cfg, train): - process_method_name = anchor_cfg["core_method"] - assert process_method_name in ["VoxelPostprocessor", "BevPostprocessor", "CiassdPostprocessor", "FpvrcnnPostprocessor"] - anchor_generator = __all__[process_method_name](anchor_params=anchor_cfg, train=train) - - return anchor_generator diff --git a/OpenCOOD/opencood/data_utils/post_processor/base_postprocessor.py b/OpenCOOD/opencood/data_utils/post_processor/base_postprocessor.py deleted file mode 100644 index c89aa6d9..00000000 --- a/OpenCOOD/opencood/data_utils/post_processor/base_postprocessor.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -Template for AnchorGenerator -""" - -import numpy as np -import torch - -from opencood.utils import box_utils - - -class BasePostprocessor(object): - """ - Template for Anchor generator. - - Parameters - ---------- - anchor_params : dict - The dictionary containing all anchor-related parameters. - train : bool - Indicate train or test mode. - - Attributes - ---------- - bbx_dict : dictionary - Contain all objects information across the cav, key: id, value: bbx - coordinates (1, 7) - """ - - def __init__(self, anchor_params, train=True): - self.params = anchor_params - self.bbx_dict = {} # noqa: DC05 - self.train = train - - def generate_anchor_box(self): - # needs to be overloaded - return None - - def generate_label(self, *argv): - return None - - def generate_gt_bbx(self, data_dict): - """ - The base postprocessor will generate 3d groundtruth bounding box. - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - Returns - ------- - gt_box3d_tensor : torch.Tensor - The groundtruth bounding box tensor, shape (N, 8, 3). - """ - gt_box3d_list = [] - # used to avoid repetitive bounding box - object_id_list = [] - - for cav_id, cav_content in data_dict.items(): - # used to project gt bounding box to ego space - transformation_matrix = cav_content["transformation_matrix"] - - object_bbx_center = cav_content["object_bbx_center"] - object_bbx_mask = cav_content["object_bbx_mask"] - object_ids = cav_content["object_ids"] - object_bbx_center = object_bbx_center[object_bbx_mask == 1] - - # convert center to corner - object_bbx_corner = box_utils.boxes_to_corners_3d(object_bbx_center, self.params["order"]) - projected_object_bbx_corner = box_utils.project_box3d(object_bbx_corner.float(), transformation_matrix) - gt_box3d_list.append(projected_object_bbx_corner) - - # append the corresponding ids - object_id_list += object_ids - - # gt bbx 3d - gt_box3d_list = torch.vstack(gt_box3d_list) - # some of the bbx may be repetitive, use the id list to filter - gt_box3d_selected_indices = [object_id_list.index(x) for x in set(object_id_list)] - gt_box3d_tensor = gt_box3d_list[gt_box3d_selected_indices] - - # filter the gt_box to make sure all bbx are in the range - mask = box_utils.get_mask_for_boxes_within_range_torch(gt_box3d_tensor) - gt_box3d_tensor = gt_box3d_tensor[mask, :, :] - - return gt_box3d_tensor - - def generate_object_center(self, cav_contents, reference_lidar_pose): - """ - Retrieve all objects in a format of (n, 7), where 7 represents - x, y, z, l, w, h, yaw or x, y, z, h, w, l, yaw. - - Parameters - ---------- - cav_contents : list - List of dictionary, save all cavs' information. - - reference_lidar_pose : list - The final target lidar pose with length 6. - - Returns - ------- - object_np : np.ndarray - Shape is (max_num, 7). - mask : np.ndarray - Shape is (max_num,). - object_ids : list - Length is number of bbx in current sample. - """ - - tmp_object_dict = {} - for cav_content in cav_contents: - tmp_object_dict.update(cav_content["params"]["vehicles"]) - - output_dict = {} - filter_range = self.params["anchor_args"]["cav_lidar_range"] - - box_utils.project_world_objects(tmp_object_dict, output_dict, reference_lidar_pose, filter_range, self.params["order"]) - - object_np = np.zeros((self.params["max_num"], 7)) - mask = np.zeros(self.params["max_num"]) - object_ids = [] - - for i, (object_id, object_bbx) in enumerate(output_dict.items()): - object_np[i] = object_bbx[0, :] - mask[i] = 1 - object_ids.append(object_id) - - return object_np, mask, object_ids diff --git a/OpenCOOD/opencood/data_utils/post_processor/bev_postprocessor.py b/OpenCOOD/opencood/data_utils/post_processor/bev_postprocessor.py deleted file mode 100644 index 057fae72..00000000 --- a/OpenCOOD/opencood/data_utils/post_processor/bev_postprocessor.py +++ /dev/null @@ -1,438 +0,0 @@ -""" -Anchor-free 2d Generator -""" - -import numpy as np -import torch -import torch.nn.functional as F - -from opencood.utils.transformation_utils import dist_to_continuous -from opencood.data_utils.post_processor.base_postprocessor import BasePostprocessor -from opencood.utils import box_utils -from opencood.visualization import vis_utils - - -class BevPostprocessor(BasePostprocessor): - def __init__(self, anchor_params, train): - super(BevPostprocessor, self).__init__(anchor_params, train) - # self.geometry_param = anchor_params["geometry"] - self.geometry_param = anchor_params["geometry_param"] - - # TODO - # Hard coded for now. Need to calculate for our own training dataset - self.target_mean = np.array([0.008, 0.001, 0.202, 0.2, 0.43, 1.368]) - self.target_std_dev = np.array([0.866, 0.5, 0.954, 0.668, 0.09, 0.111]) - - def generate_anchor_box(self): - return None - - def generate_label(self, **kwargs): - """ - Generate targets for training. - - Parameters - ---------- - kwargs : list - gt_box_center:(max_num, 7) - - Returns - ------- - label_dict : dict - Dictionary that contains all target related info. - """ - - assert self.params["order"] == "lwh", "Currently BEV only support lwh bbx order." - # (max_num, 7) - gt_box_center = kwargs["gt_box_center"] - - # (max_num) - masks = kwargs["mask"] - - # (n, 7) - gt_box_center_valid = gt_box_center[masks == 1] - # (n, 4, 3) - bev_corners = box_utils.boxes_to_corners2d(gt_box_center_valid, self.params["order"]) - - _ = gt_box_center_valid.shape[0] # n - # (n, 4, 2) - bev_corners = bev_corners[:, :, :2] - yaw = gt_box_center_valid[:, -1] - x, y = gt_box_center_valid[:, 0], gt_box_center_valid[:, 1] - dx, dy = gt_box_center_valid[:, 3], gt_box_center_valid[:, 4] - # (n, 6) - reg_targets = np.column_stack([np.cos(yaw), np.sin(yaw), x, y, dx, dy]) - - # target label map including classification and regression targets - label_map = np.zeros(self.geometry_param["label_shape"]) - self.update_label_map(label_map, bev_corners, reg_targets) - label_map = self.normalize_targets(label_map) - label_dict = { - # (7, label_shape[0], label_shape[1]) - "label_map": np.transpose(label_map, (2, 0, 1)).astype(np.float32), - "bev_corners": bev_corners, - } - return label_dict - - def update_label_map(self, label_map, bev_corners, reg_targets): - """ - Update label_map based on bbx and regression targets. - - Parameters - ---------- - label_map : numpy.array - Targets array for classification and regression tasks with - the shape of label_shape. - - bev_corners : numpy.array - The bbx corners in lidar frame with shape (n, 4, 2) - - reg_targets : numpy.array - Array containing the regression targets information. It need to be - further processed. - - """ - res = self.geometry_param["res"] - downsample_rate = self.geometry_param["downsample_rate"] - - bev_origin = np.array([self.geometry_param["L1"], self.geometry_param["W1"]]).reshape(1, -1) - - # discretized bbx corner representations -- (n, 4, 2) - bev_corners_dist = (bev_corners - bev_origin) / res / downsample_rate - # generate the coordinates of m - x = np.arange(self.geometry_param["label_shape"][0]) - y = np.arange(self.geometry_param["label_shape"][1]) - xx, yy = np.meshgrid(x, y) - - # (label_shape[0]*label_shape[1], 2) - points = np.concatenate([xx.reshape(-1, 1), yy.reshape(-1, 1)], axis=-1) - bev_origin_dist = bev_origin / res / downsample_rate - - # loop over each bbx, find the points within the bbx. - for i in range(bev_corners.shape[0]): - reg_target = reg_targets[i, :] - - # find discredited points in bbx - points_in_box = box_utils.get_points_in_rotated_box(points, bev_corners_dist[i, ...]) - # convert points to continuous space - points_continuous = dist_to_continuous(points_in_box, bev_origin_dist, res, downsample_rate) - actual_reg_target = np.repeat(reg_target.reshape(1, -1), points_continuous.shape[0], axis=0) - # build learning targets - actual_reg_target[:, 2:4] = actual_reg_target[:, 2:4] - points_continuous - actual_reg_target[:, 4:] = np.log(actual_reg_target[:, 4:]) - - # update label map - label_map[points_in_box[:, 0], points_in_box[:, 1], 0] = 1.0 - label_map[points_in_box[:, 0], points_in_box[:, 1], 1:] = actual_reg_target - - def normalize_targets(self, label_map): - """ - Normalize label_map - - Parameters - ---------- - label_map : numpy.array - Targets array for classification and regression tasks with the - shape of label_shape. - - Returns - ------- - label_map: numpy.array - Nromalized label_map. - - """ - label_map[..., 1:] = (label_map[..., 1:] - self.target_mean) / self.target_std_dev - return label_map - - def denormalize_reg_map(self, reg_map): - """ - Denormalize the regression map - - Parameters - ---------- - reg_map : np.ndarray / torch.Tensor - Regression output mapwith the shape of (label_shape[0], - label_shape[1], 6). - - Returns - ------- - reg_map : np.ndarray / torch.Tensor - Denormalized regression map. - - """ - if isinstance(reg_map, np.ndarray): - target_mean = self.target_mean - target_std_dev = self.target_std_dev - - else: - target_mean = torch.from_numpy(self.target_mean).to(reg_map.device) - target_std_dev = torch.from_numpy(self.target_std_dev).to(reg_map.device) - reg_map = reg_map * target_std_dev + target_mean - return reg_map - - @staticmethod - def collate_batch(label_batch_list): - """ - Customized collate function for target label generation. - - Parameters - ---------- - label_batch_list : list - The list of dictionary that contains all labels for several - frames. - - Returns - ------- - processed_batch : dict - Reformatted labels in torch tensor. - """ - label_map_list = [x["label_map"][np.newaxis, ...] for x in label_batch_list] - processed_batch = { - # (batch_size, 7, label_shape[0], label_shape[1]) - "label_map": torch.from_numpy(np.concatenate(label_map_list, axis=0)), - "bev_corners": [torch.from_numpy(x["bev_corners"]) for x in label_batch_list], - } - return processed_batch - - def decode_agent_predictions(self, cav_content, output): - """ - Decode one agent's PIXOR output without projection or NMS. - - Parameters - ---------- - cav_content : dict - Local model input. It is accepted for a common postprocessor API. - output : dict - Raw output produced by the local detector. - - Returns - ------- - tuple[torch.Tensor, torch.Tensor] - Sender-frame 2D box corners and confidence scores. - """ - del cav_content - prob = torch.sigmoid(output["cls"].squeeze(0).squeeze(0)) - reg_map = output["reg"].squeeze(0).permute(1, 2, 0) - reg_map = self.denormalize_reg_map(reg_map) - mask = torch.gt(prob, self.params["target_args"]["score_threshold"]) - if mask.sum() == 0: - return reg_map.new_empty((0, 4, 2)), prob.new_empty((0,)) - return self.reg_map_to_bbx_corners(reg_map, mask), prob[mask] - - def post_process_detections(self, boxes, scores): - """ - Apply receiver-side NMS and range filtering to 2D detections. - - Parameters - ---------- - boxes : torch.Tensor - Ego-frame boxes with shape ``(N, 4, 2)``. - scores : torch.Tensor - Confidence scores with shape ``(N,)``. - - Returns - ------- - tuple[torch.Tensor | None, torch.Tensor | None] - Final boxes and scores, or two ``None`` values when empty. - """ - if boxes.shape[0] == 0: - return None, None - keep_index = box_utils.nms_rotated(boxes, scores, self.params["nms_thresh"]) - boxes = boxes[keep_index] - scores = scores[keep_index] - - range_mask = box_utils.get_mask_for_boxes_within_range_torch(boxes) - boxes = boxes[range_mask] - scores = scores[range_mask] - assert scores.shape[0] == boxes.shape[0] - return boxes, scores - - def post_process(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D bounding box. - Step1: convert each cav's output to bounding box format - Step2: project the bounding boxes to ego space. - Step:3 NMS - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box2d_tensor : torch.Tensor - The prediction bounding box tensor after NMS. - - gt_box2d_tensor : torch.Tensor - The groundtruth bounding box tensor. - """ - - # the final bounding box list - pred_box2d_list = [] - pred_score_list = [] - - for cav_id, cav_content in data_dict.items(): - assert cav_id in output_dict - # the transformation matrix to ego space - transformation_matrix = cav_content["transformation_matrix"] - - corners2d, scores = self.decode_agent_predictions( - cav_content, - output_dict[cav_id], - ) - if corners2d.shape[0] > 0: - # assume the z-diviation in transformation_matrix is small, - # thus we can pad zeros to simulate the 3d transformation. - # (number of high confidence bbx, 4, 3) - box3d = F.pad(corners2d, (0, 1)) - # (number of high confidence bbx, 4, 2) - projected_boxes2d = box_utils.project_points_by_matrix_torch(box3d.view(-1, 3), transformation_matrix)[:, :2] - - projected_boxes2d = projected_boxes2d.view(-1, 4, 2) - pred_box2d_list.append(projected_boxes2d) - pred_score_list.append(scores) - - if len(pred_box2d_list): - pred_box2ds = torch.cat(pred_box2d_list, dim=0) - pred_scores = torch.cat(pred_score_list, dim=0) - else: - return None, None - - return self.post_process_detections(pred_box2ds, pred_scores) - - def reg_map_to_bbx_corners(self, reg_map, mask): - """ - Construct bbx from the regression output of the model. - - Parameters - ---------- - reg_map : torch.Tensor - Regression output of neural networks. - - mask : torch.Tensor - Masks used to filter bbx. - - Returns - ------- - corners : torch.Tensor - Bbx output with shape (N, 4, 2). - - """ - - assert len(reg_map.shape) == 3, "only support shape of label_shape i.e. (*, *, 6)" - device = reg_map.device - - cos_t, sin_t, x, y, log_dx, log_dy = [tt.squeeze(-1) for tt in torch.chunk(reg_map, 6, dim=-1)] - yaw = torch.atan2(sin_t, cos_t) - dx, dy = log_dx.exp(), log_dy.exp() - - grid_size = self.geometry_param["res"] * self.geometry_param["downsample_rate"] - grid_x = torch.arange(self.geometry_param["L1"], self.geometry_param["L2"], grid_size, dtype=torch.float32, device=device) - grid_y = torch.arange(self.geometry_param["W1"], self.geometry_param["W2"], grid_size, dtype=torch.float32, device=device) - - xx, yy = torch.meshgrid([grid_x, grid_y]) - center_x = xx + x - center_y = yy + y - - bbx2d = torch.stack([center_x, center_y, dx, dy, yaw], dim=-1) - bbx2d = bbx2d[mask, :] - corners = box_utils.boxes2d_to_corners2d(bbx2d) - - return corners - - def post_process_debug(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D bounding box for debug purpose. - Step1: convert each cav's output to bounding box format - Step2: project the bounding boxes to ego space. - Step:3 NMS - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box2d_tensor : torch.Tensor - The prediction bounding box tensor after NMS. - gt_box2d_tensor : torch.Tensor - The groundtruth bounding box tensor. - """ - # the final bounding box list - pred_box2d_list = [] - pred_score_list = [] - - # the transformation matrix to ego space - transformation_matrix = data_dict["transformation_matrix"] - - # classification probability -- (label_shape[0], label_shape[1]) - prob = output_dict["cls"].squeeze(0).squeeze(0) - prob = torch.sigmoid(prob) - - # regression map -- (label_shape[0], label_shape[1], 6) - reg_map = output_dict["reg"].squeeze(0).permute(1, 2, 0) - reg_map = self.denormalize_reg_map(reg_map) - - threshold = 0.5 - mask = torch.gt(prob, threshold) - - if mask.sum() > 0: - # (number of high confidence bbx, 4, 2) - corners2d = self.reg_map_to_bbx_corners(reg_map, mask) - # assume the z-diviation in transformation_matrix is small, - # thus we can pad zeros to simulate the 3d transformation. - # (number of high confidence bbx, 4, 3) - box3d = F.pad(corners2d, (0, 1)) - - # (number of high confidence bbx, 4, 2) - projected_boxes2d = box_utils.project_points_by_matrix_torch(box3d.view(-1, 3), transformation_matrix)[:, :2] - projected_boxes2d = projected_boxes2d.view(-1, 4, 2) - scores = prob[mask] - pred_box2d_list.append(projected_boxes2d) - pred_score_list.append(scores) - - pred_box2ds = torch.cat(pred_box2d_list, dim=0) - pred_scores = torch.cat(pred_score_list, dim=0) - - keep_index = box_utils.nms_rotated(pred_box2ds, pred_scores, self.params["nms_thresh"]) - pred_box2ds = pred_box2ds[keep_index] - - # filter out the prediction out of the range. - mask = box_utils.get_mask_for_boxes_within_range_torch(pred_box2ds) - pred_box2ds = pred_box2ds[mask, :, :] - return pred_box2ds - - @staticmethod - def visualize(pred_box_tensor, gt_tensor, pcd, show_vis, save_path, dataset=None): - """ - Visualize the BEV 2D prediction, ground truth with point cloud together. - - Parameters - ---------- - pred_box_tensor : torch.Tensor - (N, 8, 3) prediction. - - gt_tensor : torch.Tensor - (N, 8, 3) groundtruth bbx - - pcd : torch.Tensor - PointCloud, (N, 4). - - show_vis : bool - Whether to show visualization. - - save_path : str - Save the visualization results to given path. - - dataset : BaseDataset - opencood dataset object. - """ - assert dataset is not None, "dataset argument can't be None" - vis_utils.visualize_single_sample_output_bev(pred_box_tensor, gt_tensor, pcd, dataset, show_vis, save_path) diff --git a/OpenCOOD/opencood/data_utils/post_processor/ciassd_postprocessor.py b/OpenCOOD/opencood/data_utils/post_processor/ciassd_postprocessor.py deleted file mode 100644 index 59487c31..00000000 --- a/OpenCOOD/opencood/data_utils/post_processor/ciassd_postprocessor.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -3D Anchor Generator for Voxel -""" - -import numpy as np -import torch - -from opencood.data_utils.post_processor.voxel_postprocessor import VoxelPostprocessor -from opencood.utils import box_utils - - -class CiassdPostprocessor(VoxelPostprocessor): - def __init__(self, anchor_params, train): - super(CiassdPostprocessor, self).__init__(anchor_params, train) - self.train = train - self.anchor_num = self.params["anchor_args"]["num"] - - def decode_agent_predictions(self, cav_content, output): - """ - Decode one agent's CIA-SSD output without projection or NMS. - - Parameters - ---------- - cav_content : dict - Local model input containing the configured anchors. - output : dict - Raw output produced by CIA-SSD. - - Returns - ------- - tuple[torch.Tensor, torch.Tensor] - Sender-frame box corners and confidence scores. - """ - preds_dict = output["preds_dict_stage1"] - prob = torch.sigmoid(preds_dict["cls_preds"].permute(0, 2, 3, 1).contiguous()) - reg = preds_dict["box_preds"].permute(0, 2, 3, 1).contiguous() - iou = preds_dict["iou_preds"].permute(0, 2, 3, 1).contiguous().reshape(1, -1) - direction = preds_dict["dir_cls_preds"].permute(0, 2, 3, 1).contiguous().reshape(1, -1, 2) - - batch_box3d = self.delta_to_boxes3d(reg, cav_content["anchor_box"], False) - mask = torch.gt(prob, self.params["target_args"]["score_threshold"]).view(1, -1) - mask_reg = mask.unsqueeze(2).repeat(1, 1, 7) - assert batch_box3d.shape[0] == 1 - boxes3d = torch.masked_select(batch_box3d[0], mask_reg[0]).view(-1, 7) - scores = torch.masked_select(prob.reshape(-1), mask[0]) - if boxes3d.shape[0] == 0: - return boxes3d.new_empty((0, 8, 3)), scores - - direction_labels = torch.max(direction, dim=-1)[1][mask] - adjusted_iou = (iou + 1) * 0.5 - scores = scores * torch.pow(adjusted_iou.masked_select(mask), 4) - opposite_direction = (boxes3d[..., -1] > 0) ^ (direction_labels.byte() == 1) - boxes3d[..., -1] += torch.where( - opposite_direction, - torch.tensor(np.pi).type_as(boxes3d), - torch.tensor(0.0).type_as(boxes3d), - ) - return box_utils.boxes_to_corners_3d( - boxes3d, - order=self.params["order"], - ), scores - - def post_process(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D/3D bounding box. - Step1: convert each cav's output to bounding box format - Step2: project the bounding boxes to ego space. - Step:3 NMS - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box3d_tensor : torch.Tensor - The prediction bounding box tensor after NMS. - gt_box3d_tensor : torch.Tensor - The groundtruth bounding box tensor. - """ - # the final bounding box list - global batch_num_box_count - pred_box3d_original_list = [] - pred_box3d_list = [] - pred_box2d_list = [] - - for cav_id, cav_content in data_dict.items(): - assert cav_id in output_dict - # the transformation matrix to ego space - if "transformation_matrix" in cav_content: - transformation_matrix = cav_content["transformation_matrix"] - else: - transformation_matrix = torch.from_numpy(np.identity(4)).float().to(cav_content["anchor_box"].device) - - # (H, W, anchor_num, 7) - anchor_box = cav_content["anchor_box"] - - # prediction result - preds_dict = output_dict[cav_id]["preds_dict_stage1"] - - # preds - prob = preds_dict["cls_preds"] - prob = torch.sigmoid(prob.permute(0, 2, 3, 1).contiguous()) - reg = preds_dict["box_preds"].permute(0, 2, 3, 1).contiguous() - iou = preds_dict["iou_preds"].permute(0, 2, 3, 1).contiguous().reshape(1, -1) - dir = preds_dict["dir_cls_preds"].permute(0, 2, 3, 1).contiguous().reshape(1, -1, 2) - - # convert regression map back to bounding box - # (N, W*L*anchor_num, 7) - batch_box3d = self.delta_to_boxes3d(reg, anchor_box, False) - mask = torch.gt(prob, self.params["target_args"]["score_threshold"]) - batch_num_box_count = [int(m.sum()) for m in mask] - mask = mask.view(1, -1) - mask_reg = mask.unsqueeze(2).repeat(1, 1, 7) - - # during validation/testing, the batch size should be 1 - if not self.train: - assert batch_box3d.shape[0] == 1 - - boxes3d = torch.masked_select(batch_box3d.view(-1, 7), mask_reg[0]).view(-1, 7) - scores = torch.masked_select(prob.view(-1), mask[0]) - - dir_labels = torch.max(dir, dim=-1)[1] - dir_labels = dir_labels[mask] - # top_labels = torch.zeros([scores.shape[0]], dtype=torch.long).cuda() - if scores.shape[0] != 0: - iou = (iou + 1) * 0.5 - scores = scores * torch.pow(iou.masked_select(mask), 4) - # correct_direction - top_labels = (boxes3d[..., -1] > 0) ^ (dir_labels.byte() == 1) - boxes3d[..., -1] += torch.where(top_labels, torch.tensor(np.pi).type_as(boxes3d), torch.tensor(0.0).type_as(boxes3d)) - pred_box3d_original_list.append(boxes3d.detach()) - - # convert output to bounding box - if len(boxes3d) != 0: - # (N, 8, 3) - boxes3d_corner = box_utils.boxes_to_corners_3d(boxes3d, order=self.params["order"]) - # (N, 8, 3) - projected_boxes3d = box_utils.project_box3d(boxes3d_corner, transformation_matrix) - # convert 3d bbx to 2d, (N,4) - projected_boxes2d = box_utils.corner_to_standup_box_torch(projected_boxes3d) - # (N, 5) - boxes2d_score = torch.cat((projected_boxes2d, scores.unsqueeze(1)), dim=1) - - pred_box2d_list.append(boxes2d_score) - pred_box3d_list.append(projected_boxes3d) - - if len(pred_box2d_list) == 0 or len(pred_box3d_list) == 0: - return None, None - # shape: (N, 5) - pred_box2d_list = torch.vstack(pred_box2d_list) - # scores - scores = pred_box2d_list[:, -1] - # predicted 3d bbx - pred_box3d_tensor = torch.vstack(pred_box3d_list) - pred_box3d_original = torch.vstack(pred_box3d_original_list) - - if not self.train: - # remove large bbx - keep_index_1 = box_utils.remove_large_pred_bbx(pred_box3d_tensor) - keep_index_2 = box_utils.remove_bbx_abnormal_z(pred_box3d_tensor) - keep_index = torch.logical_and(keep_index_1, keep_index_2) - - pred_box3d_tensor = pred_box3d_tensor[keep_index] - scores = scores[keep_index] - - # nms - keep_index = box_utils.nms_rotated(pred_box3d_tensor, scores, self.params["nms_thresh"]) - - pred_box3d_tensor = pred_box3d_tensor[keep_index] - - # select cooresponding score - scores = scores[keep_index] - - # filter out the prediction out of the range. - mask = box_utils.get_mask_for_boxes_within_range_torch(pred_box3d_tensor) - pred_box3d_tensor = pred_box3d_tensor[mask, :, :] - scores = scores[mask] - - assert scores.shape[0] == pred_box3d_tensor.shape[0] - return pred_box3d_tensor, scores - else: - cur_idx = 0 - batch_pred_boxes3d = [] - batch_scores = [] - for n in batch_num_box_count: - cur_boxes = pred_box3d_tensor[cur_idx : cur_idx + n] - cur_scores = scores[cur_idx : cur_idx + n] - # nms - keep_index = box_utils.nms_rotated(cur_boxes, cur_scores, self.params["nms_thresh"]) - cur_boxes = pred_box3d_original[cur_idx : cur_idx + n] # [:, [0, 1, 2, 5, 4, 3, 6]] # hwl -> lwh - batch_pred_boxes3d.append(cur_boxes[keep_index]) - batch_scores.append(cur_scores[keep_index]) - cur_idx += n - - return batch_pred_boxes3d, batch_scores diff --git a/OpenCOOD/opencood/data_utils/post_processor/fpvrcnn_postprocessor.py b/OpenCOOD/opencood/data_utils/post_processor/fpvrcnn_postprocessor.py deleted file mode 100644 index c95d259d..00000000 --- a/OpenCOOD/opencood/data_utils/post_processor/fpvrcnn_postprocessor.py +++ /dev/null @@ -1,196 +0,0 @@ -""" -3D Anchor Generator for Voxel -""" - -import numpy as np -import torch - -from opencood.data_utils.post_processor.voxel_postprocessor import VoxelPostprocessor -from opencood.utils import box_utils -from opencood.utils import common_utils - - -class FpvrcnnPostprocessor(VoxelPostprocessor): - def __init__(self, anchor_params, train): - super(FpvrcnnPostprocessor, self).__init__(anchor_params, train) - - def post_process(self, data_dict, output_dict, stage1=False): - if stage1: - return self.post_process_stage1(data_dict, output_dict) - else: - return self.post_process_stage2(data_dict) - - def post_process_stage1(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D/3D bounding box. - Step1: convert each cav's output to bounding box format - Step2: project the bounding boxes to ego space. - Step:3 NMS - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box3d_tensor : torch.Tensor - The prediction bounding box tensor after NMS. - gt_box3d_tensor : torch.Tensor - The groundtruth bounding box tensor. - """ - # the final bounding box list - pred_box3d_original_list = [] - pred_box3d_list = [] - pred_box2d_list = [] - - for cav_id, cav_content in data_dict.items(): - assert cav_id in output_dict - # the transformation matrix to ego space - if "transformation_matrix" in cav_content: - transformation_matrix = cav_content["transformation_matrix"] - else: - transformation_matrix = torch.from_numpy(np.identity(4)).float().to(cav_content["anchor_box"].device) - - # (H, W, anchor_num, 7) - anchor_box = cav_content["anchor_box"] - - # prediction result - preds_dict = output_dict[cav_id]["preds_dict_stage1"] - - # preds - prob = preds_dict["cls_preds"] - prob = torch.sigmoid(prob.permute(0, 2, 3, 1).contiguous()) - reg = preds_dict["box_preds"].permute(0, 2, 3, 1).contiguous() - iou = torch.sigmoid(preds_dict["iou_preds"].permute(0, 2, 3, 1).contiguous()).reshape(1, -1) - dir = preds_dict["dir_cls_preds"].permute(0, 2, 3, 1).contiguous().reshape(1, -1, 2) - - # convert regression map back to bounding box - # (N, W*L*anchor_num, 7) - batch_box3d = self.delta_to_boxes3d(reg, anchor_box, False) - mask = torch.gt(prob, self.params["target_args"]["score_threshold"]) - batch_num_box_count = [int(m.sum()) for m in mask] - mask = mask.view(1, -1) - mask_reg = mask.unsqueeze(2).repeat(1, 1, 7) - - # during validation/testing, the batch size should be 1 - if not self.train: - assert batch_box3d.shape[0] == 1 - - boxes3d = torch.masked_select(batch_box3d.view(-1, 7), mask_reg[0]).view(-1, 7) # hwl - scores = torch.masked_select(prob.view(-1), mask[0]) - - dir_labels = torch.max(dir, dim=-1)[1] - dir_labels = dir_labels[mask] - # top_labels = torch.zeros([scores.shape[0]], dtype=torch.long).cuda() - if scores.shape[0] != 0: - iou = torch.clamp(iou, min=0.0, max=1.0) - iou = (iou + 1) * 0.5 - scores = scores * torch.pow(iou.masked_select(mask), 4) - # correct_direction - top_labels = (boxes3d[..., -1] > 0) ^ (dir_labels.byte() == 1) - boxes3d[..., -1] += torch.where(top_labels, torch.tensor(np.pi).type_as(boxes3d), torch.tensor(0.0).type_as(boxes3d)) - - # filter invalid boxes - keep_idx = torch.logical_and((boxes3d[:, 3:6] > 1).all(dim=1), (boxes3d[:, 3:6] < 10).all(dim=1)) - idx_start = 0 - count = [] - for i, n in enumerate(batch_num_box_count): - count.append(int(keep_idx[idx_start : idx_start + n].sum())) - batch_num_box_count = count - boxes3d = boxes3d[keep_idx] - scores = scores[keep_idx] - - # if the number of boxes is too huge, this would consume a lot of memory in the second stage - # therefore, randomly select some boxes if the box number is too big at the beginning of the training - if len(boxes3d) > 300: - keep_idx = torch.multinomial(scores, 300) - idx_start = 0 - count = [] - for i, n in enumerate(batch_num_box_count): - count.append(int(torch.logical_and(keep_idx >= idx_start, keep_idx < idx_start + n).sum())) - batch_num_box_count = count - boxes3d = boxes3d[keep_idx] - scores = scores[keep_idx] - - pred_box3d_original_list.append(boxes3d.detach()) - - # convert output to bounding box - if len(boxes3d) != 0: - # (N, 8, 3) - boxes3d_corner = box_utils.boxes_to_corners_3d(boxes3d, order=self.params["order"]) - # (N, 8, 3) - projected_boxes3d = box_utils.project_box3d(boxes3d_corner, transformation_matrix) - # convert 3d bbx to 2d, (N,4) - projected_boxes2d = box_utils.corner_to_standup_box_torch(projected_boxes3d) - # (N, 5) - boxes2d_score = torch.cat((projected_boxes2d, scores.unsqueeze(1)), dim=1) - - pred_box2d_list.append(boxes2d_score) - pred_box3d_list.append(projected_boxes3d) - - if len(pred_box2d_list) == 0 or len(pred_box3d_list) == 0: - return None, None - # shape: (N, 5) - pred_box2d_list = torch.vstack(pred_box2d_list) - # scores - scores = pred_box2d_list[:, -1] - # predicted 3d bbx - pred_box3d_tensor = torch.vstack(pred_box3d_list) - pred_box3d_original = torch.vstack(pred_box3d_original_list) - - cur_idx = 0 - batch_pred_boxes3d = [] - batch_scores = [] - for n in batch_num_box_count: - cur_boxes = pred_box3d_tensor[cur_idx : cur_idx + n] - cur_scores = scores[cur_idx : cur_idx + n] - # nms - keep_index = box_utils.nms_rotated(cur_boxes, cur_scores, self.params["nms_thresh"]) - cur_boxes = pred_box3d_original[cur_idx : cur_idx + n][:, [0, 1, 2, 5, 4, 3, 6]] # hwl -> lwh - batch_pred_boxes3d.append(cur_boxes[keep_index]) - batch_scores.append(cur_scores[keep_index]) - cur_idx += n - - return batch_pred_boxes3d, batch_scores - - def post_process_stage2(self, data_dict): - from opencood.pcdet_utils.iou3d_nms.iou3d_nms_utils import nms_gpu - - output_dict = data_dict["ego"]["fpvrcnn_out"] - if output_dict is None: - return None, None - label_dict = data_dict["ego"]["rcnn_label_dict"] - rcnn_cls = output_dict["rcnn_cls"].sigmoid().view(-1) - rcnn_iou = output_dict["rcnn_iou"].view(-1) - rcnn_score = rcnn_cls * rcnn_iou**4 - rcnn_reg = output_dict["rcnn_reg"].view(-1, 7) - rois_anchor = label_dict["rois_anchor"] - rois = label_dict["rois"] - roi_center = rois[:, 0:3] - roi_ry = rois[:, 6] % (2 * np.pi) - - boxes_local = box_utils.box_decode(rcnn_reg, rois_anchor) - # boxes_local = rcnn_reg + rois_anchor - detections = common_utils.rotate_points_along_z(points=boxes_local.view(-1, 1, boxes_local.shape[-1]), angle=roi_ry.view(-1)).view( - -1, boxes_local.shape[-1] - ) - detections[:, :3] = detections[:, :3] + roi_center - detections[:, 6] = detections[:, 6] + roi_ry - mask = rcnn_score >= 0.01 - detections = detections[mask] - scores = rcnn_score[mask] - # gt_boxes = label_dict['gt_of_rois_src'][mask] - mask = nms_gpu(detections, scores, thresh=0.01)[0] - boxes3d = detections[mask][:, [0, 1, 2, 5, 4, 3, 6]] # lwh -> hwl - projected_boxes3d = None - if len(boxes3d) != 0: - # (N, 8, 3) - boxes3d_corner = box_utils.boxes_to_corners_3d(boxes3d, order=self.params["order"]) - # (N, 8, 3) - projected_boxes3d = box_utils.project_box3d(boxes3d_corner, data_dict["ego"]["transformation_matrix"]) - - return projected_boxes3d, scores[mask] diff --git a/OpenCOOD/opencood/data_utils/post_processor/voxel_postprocessor.py b/OpenCOOD/opencood/data_utils/post_processor/voxel_postprocessor.py deleted file mode 100644 index beadd119..00000000 --- a/OpenCOOD/opencood/data_utils/post_processor/voxel_postprocessor.py +++ /dev/null @@ -1,391 +0,0 @@ -""" -3D Anchor Generator for Voxel -""" - -import math -import sys - -import numpy as np -import torch - -from opencood.data_utils.post_processor.base_postprocessor import BasePostprocessor -from opencood.utils import box_utils -from opencood.utils.box_overlaps import bbox_overlaps -from opencood.visualization import vis_utils - - -class VoxelPostprocessor(BasePostprocessor): - def __init__(self, anchor_params, train): - super(VoxelPostprocessor, self).__init__(anchor_params, train) - self.anchor_num = self.params["anchor_args"]["num"] - - def generate_anchor_box(self): - W = self.params["anchor_args"]["W"] - H = self.params["anchor_args"]["H"] - - length = self.params["anchor_args"]["l"] - w = self.params["anchor_args"]["w"] - h = self.params["anchor_args"]["h"] - r = self.params["anchor_args"]["r"] - - assert self.anchor_num == len(r) - r = [math.radians(ele) for ele in r] - - vh = self.params["anchor_args"]["vh"] - vw = self.params["anchor_args"]["vw"] - - xrange = [self.params["anchor_args"]["cav_lidar_range"][0], self.params["anchor_args"]["cav_lidar_range"][3]] - yrange = [self.params["anchor_args"]["cav_lidar_range"][1], self.params["anchor_args"]["cav_lidar_range"][4]] - - if "feature_stride" in self.params["anchor_args"]: - feature_stride = self.params["anchor_args"]["feature_stride"] - else: - feature_stride = 2 - - x = np.linspace(xrange[0] + vw, xrange[1] - vw, W // feature_stride) - y = np.linspace(yrange[0] + vh, yrange[1] - vh, H // feature_stride) - - cx, cy = np.meshgrid(x, y) - cx = np.tile(cx[..., np.newaxis], self.anchor_num) - cy = np.tile(cy[..., np.newaxis], self.anchor_num) - cz = np.ones_like(cx) * -1.0 - - w = np.ones_like(cx) * w - length = np.ones_like(cx) * length - h = np.ones_like(cx) * h - - r_ = np.ones_like(cx) - for i in range(self.anchor_num): - r_[..., i] = r[i] - - if self.params["order"] == "hwl": - anchors = np.stack([cx, cy, cz, h, w, length, r_], axis=-1) - elif self.params["order"] == "lhw": - anchors = np.stack([cx, cy, cz, length, h, w, r_], axis=-1) - else: - sys.exit("Unknown bbx order.") - - return anchors - - def generate_label(self, **kwargs): - """ - Generate targets for training. - - Parameters - ---------- - argv : list - gt_box_center:(max_num, 7), anchor:(H, W, anchor_num, 7) - - Returns - ------- - label_dict : dict - Dictionary that contains all target related info. - """ - assert self.params["order"] == "hwl", "Currently Voxel only supporthwl bbx order." - # (max_num, 7) - gt_box_center = kwargs["gt_box_center"] - # (H, W, anchor_num, 7) - anchors = kwargs["anchors"] - # (max_num) - masks = kwargs["mask"] - - # (H, W) - feature_map_shape = anchors.shape[:2] - - # (H*W*anchor_num, 7) - anchors = anchors.reshape(-1, 7) - # normalization factor, (H * W * anchor_num) - anchors_d = np.sqrt(anchors[:, 4] ** 2 + anchors[:, 5] ** 2) - - # (H, W, 2) - pos_equal_one = np.zeros((*feature_map_shape, self.anchor_num)) - neg_equal_one = np.zeros((*feature_map_shape, self.anchor_num)) - # (H, W, self.anchor_num * 7) - targets = np.zeros((*feature_map_shape, self.anchor_num * 7)) - - # (n, 7) - gt_box_center_valid = gt_box_center[masks == 1] - # (n, 8, 3) - gt_box_corner_valid = box_utils.boxes_to_corners_3d(gt_box_center_valid, self.params["order"]) - # (H*W*anchor_num, 8, 3) - anchors_corner = box_utils.boxes_to_corners_3d(anchors, order=self.params["order"]) - # (H*W*anchor_num, 4) - anchors_standup_2d = box_utils.corner2d_to_standup_box(anchors_corner) - # (n, 4) - gt_standup_2d = box_utils.corner2d_to_standup_box(gt_box_corner_valid) - - # (H*W*anchor_n) - iou = bbox_overlaps( - np.ascontiguousarray(anchors_standup_2d).astype(np.float32), - np.ascontiguousarray(gt_standup_2d).astype(np.float32), - ) - - # the anchor boxes has the largest iou across - # shape: (n) - id_highest = np.argmax(iou.T, axis=1) - # [0, 1, 2, ..., n-1] - id_highest_gt = np.arange(iou.T.shape[0]) - # make sure all highest iou is larger than 0 - mask = iou.T[id_highest_gt, id_highest] > 0 - id_highest, id_highest_gt = id_highest[mask], id_highest_gt[mask] - - # find anchors iou > params['pos_iou'] - id_pos, id_pos_gt = np.where(iou > self.params["target_args"]["pos_threshold"]) - # find anchors iou < params['neg_iou'] - id_neg = np.where(np.sum(iou < self.params["target_args"]["neg_threshold"], axis=1) == iou.shape[1])[0] - id_pos = np.concatenate([id_pos, id_highest]) - id_pos_gt = np.concatenate([id_pos_gt, id_highest_gt]) - id_pos, index = np.unique(id_pos, return_index=True) - id_pos_gt = id_pos_gt[index] - id_neg.sort() - - # cal the target and set the equal one - index_x, index_y, index_z = np.unravel_index(id_pos, (*feature_map_shape, self.anchor_num)) - pos_equal_one[index_x, index_y, index_z] = 1 - - # calculate the targets - targets[index_x, index_y, np.array(index_z) * 7] = (gt_box_center[id_pos_gt, 0] - anchors[id_pos, 0]) / anchors_d[id_pos] - targets[index_x, index_y, np.array(index_z) * 7 + 1] = (gt_box_center[id_pos_gt, 1] - anchors[id_pos, 1]) / anchors_d[id_pos] - targets[index_x, index_y, np.array(index_z) * 7 + 2] = (gt_box_center[id_pos_gt, 2] - anchors[id_pos, 2]) / anchors[id_pos, 3] - targets[index_x, index_y, np.array(index_z) * 7 + 3] = np.log(gt_box_center[id_pos_gt, 3] / anchors[id_pos, 3]) - targets[index_x, index_y, np.array(index_z) * 7 + 4] = np.log(gt_box_center[id_pos_gt, 4] / anchors[id_pos, 4]) - targets[index_x, index_y, np.array(index_z) * 7 + 5] = np.log(gt_box_center[id_pos_gt, 5] / anchors[id_pos, 5]) - targets[index_x, index_y, np.array(index_z) * 7 + 6] = gt_box_center[id_pos_gt, 6] - anchors[id_pos, 6] - - index_x, index_y, index_z = np.unravel_index(id_neg, (*feature_map_shape, self.anchor_num)) - neg_equal_one[index_x, index_y, index_z] = 1 - - # to avoid a box be pos/neg in the same time - index_x, index_y, index_z = np.unravel_index(id_highest, (*feature_map_shape, self.anchor_num)) - neg_equal_one[index_x, index_y, index_z] = 0 - - label_dict = {"pos_equal_one": pos_equal_one, "neg_equal_one": neg_equal_one, "targets": targets} - - return label_dict - - @staticmethod - def collate_batch(label_batch_list): - """ - Customized collate function for target label generation. - - Parameters - ---------- - label_batch_list : list - The list of dictionary that contains all labels for several - frames. - - Returns - ------- - target_batch : dict - Reformatted labels in torch tensor. - """ - pos_equal_one = [] - neg_equal_one = [] - targets = [] - - for i in range(len(label_batch_list)): - pos_equal_one.append(label_batch_list[i]["pos_equal_one"]) - neg_equal_one.append(label_batch_list[i]["neg_equal_one"]) - targets.append(label_batch_list[i]["targets"]) - - pos_equal_one = torch.from_numpy(np.array(pos_equal_one)) - neg_equal_one = torch.from_numpy(np.array(neg_equal_one)) - targets = torch.from_numpy(np.array(targets)) - - return {"targets": targets, "pos_equal_one": pos_equal_one, "neg_equal_one": neg_equal_one} - - def decode_agent_predictions(self, cav_content, output): - """ - Decode one agent's detector output without projection or NMS. - - Parameters - ---------- - cav_content : dict - Local model input containing the configured anchors. - output : dict - Raw output produced by the local detector. - - Returns - ------- - tuple[torch.Tensor, torch.Tensor] - Sender-frame box corners and confidence scores. - """ - anchor_box = cav_content["anchor_box"] - prob = torch.sigmoid(output["psm"].permute(0, 2, 3, 1)).reshape(1, -1) - batch_box3d = self.delta_to_boxes3d(output["rm"], anchor_box) - mask = torch.gt(prob, self.params["target_args"]["score_threshold"]).view(1, -1) - mask_reg = mask.unsqueeze(2).repeat(1, 1, 7) - - assert batch_box3d.shape[0] == 1 - boxes3d = torch.masked_select(batch_box3d[0], mask_reg[0]).view(-1, 7) - scores = torch.masked_select(prob[0], mask[0]) - if boxes3d.shape[0] == 0: - return boxes3d.new_empty((0, 8, 3)), scores - return box_utils.boxes_to_corners_3d(boxes3d, order=self.params["order"]), scores - - def post_process_detections(self, boxes, scores): - """ - Apply receiver-side filtering and NMS to decoded 3D detections. - - Parameters - ---------- - boxes : torch.Tensor - Ego-frame boxes with shape ``(N, 8, 3)``. - scores : torch.Tensor - Confidence scores with shape ``(N,)``. - - Returns - ------- - tuple[torch.Tensor | None, torch.Tensor | None] - Final boxes and scores, or two ``None`` values when empty. - """ - if boxes.shape[0] == 0: - return None, None - - keep_index = torch.logical_and( - box_utils.remove_large_pred_bbx(boxes), - box_utils.remove_bbx_abnormal_z(boxes), - ) - boxes = boxes[keep_index] - scores = scores[keep_index] - - keep_index = box_utils.nms_rotated(boxes, scores, self.params["nms_thresh"]) - boxes = boxes[keep_index] - scores = scores[keep_index] - - range_mask = box_utils.get_mask_for_boxes_within_range_torch(boxes) - boxes = boxes[range_mask] - scores = scores[range_mask] - assert scores.shape[0] == boxes.shape[0] - return boxes, scores - - def post_process(self, data_dict, output_dict): - """ - Process the outputs of the model to 2D/3D bounding box. - Step1: convert each cav's output to bounding box format - Step2: project the bounding boxes to ego space. - Step:3 NMS - - Parameters - ---------- - data_dict : dict - The dictionary containing the origin input data of model. - - output_dict :dict - The dictionary containing the output of the model. - - Returns - ------- - pred_box3d_tensor : torch.Tensor - The prediction bounding box tensor after NMS. - gt_box3d_tensor : torch.Tensor - The groundtruth bounding box tensor. - """ - # the final bounding box list - pred_box3d_list = [] - pred_box2d_list = [] - - for cav_id, cav_content in data_dict.items(): - assert cav_id in output_dict - # the transformation matrix to ego space - transformation_matrix = cav_content["transformation_matrix"] - - boxes3d_corner, scores = self.decode_agent_predictions( - cav_content, - output_dict[cav_id], - ) - if len(boxes3d_corner) != 0: - # (N, 8, 3) - projected_boxes3d = box_utils.project_box3d(boxes3d_corner, transformation_matrix) - # convert 3d bbx to 2d, (N,4) - projected_boxes2d = box_utils.corner_to_standup_box_torch(projected_boxes3d) - # (N, 5) - boxes2d_score = torch.cat((projected_boxes2d, scores.unsqueeze(1)), dim=1) - - pred_box2d_list.append(boxes2d_score) - pred_box3d_list.append(projected_boxes3d) - - if len(pred_box2d_list) == 0 or len(pred_box3d_list) == 0: - return None, None - pred_box2d_tensor = torch.vstack(pred_box2d_list) - return self.post_process_detections( - torch.vstack(pred_box3d_list), - pred_box2d_tensor[:, -1], - ) - - @staticmethod - def delta_to_boxes3d(deltas, anchors, channel_swap=True): - """ - Convert the output delta to 3d bbx. - - Parameters - ---------- - deltas : torch.Tensor - (N, W, L, 14) - anchors : torch.Tensor - (W, L, 2, 7) -> xyzhwlr - channel_swap : bool - Whether to swap the channel of deltas. It is only false when using - FPV-RCNN - - Returns - ------- - box3d : torch.Tensor - (N, W*L*2, 7) - """ - # batch size - N = deltas.shape[0] - if channel_swap: - deltas = deltas.permute(0, 2, 3, 1).contiguous().view(N, -1, 7) - else: - deltas = deltas.contiguous().view(N, -1, 7) - - boxes3d = torch.zeros_like(deltas) - if deltas.is_cuda: - anchors = anchors.cuda() - boxes3d = boxes3d.cuda() - - # (W*L*2, 7) - anchors_reshaped = anchors.view(-1, 7).float() - # the diagonal of the anchor 2d box, (W*L*2) - anchors_d = torch.sqrt(anchors_reshaped[:, 4] ** 2 + anchors_reshaped[:, 5] ** 2) - anchors_d = anchors_d.repeat(N, 2, 1).transpose(1, 2) - anchors_reshaped = anchors_reshaped.repeat(N, 1, 1) - - # Inv-normalize to get xyz - boxes3d[..., [0, 1]] = torch.mul(deltas[..., [0, 1]], anchors_d) + anchors_reshaped[..., [0, 1]] - boxes3d[..., [2]] = torch.mul(deltas[..., [2]], anchors_reshaped[..., [3]]) + anchors_reshaped[..., [2]] - # hwl - boxes3d[..., [3, 4, 5]] = torch.exp(deltas[..., [3, 4, 5]]) * anchors_reshaped[..., [3, 4, 5]] - # yaw angle - boxes3d[..., 6] = deltas[..., 6] + anchors_reshaped[..., 6] - - return boxes3d - - @staticmethod - def visualize(pred_box_tensor, gt_tensor, pcd, show_vis, save_path, dataset=None): - """ - Visualize the prediction, ground truth with point cloud together. - - Parameters - ---------- - pred_box_tensor : torch.Tensor - (N, 8, 3) prediction. - - gt_tensor : torch.Tensor - (N, 8, 3) groundtruth bbx - - pcd : torch.Tensor - PointCloud, (N, 4). - - show_vis : bool - Whether to show visualization. - - save_path : str - Save the visualization results to given path. - - dataset : BaseDataset - opencood dataset object. - - """ - vis_utils.visualize_single_sample_output_gt(pred_box_tensor, gt_tensor, pcd, show_vis, save_path) diff --git a/OpenCOOD/opencood/data_utils/pre_processor/__init__.py b/OpenCOOD/opencood/data_utils/pre_processor/__init__.py deleted file mode 100644 index cb7304fb..00000000 --- a/OpenCOOD/opencood/data_utils/pre_processor/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -from opencood.data_utils.pre_processor.base_preprocessor import BasePreprocessor -from opencood.data_utils.pre_processor.voxel_preprocessor import VoxelPreprocessor -from opencood.data_utils.pre_processor.bev_preprocessor import BevPreprocessor -from opencood.data_utils.pre_processor.sp_voxel_preprocessor import SpVoxelPreprocessor - -__all__ = { - "BasePreprocessor": BasePreprocessor, - "VoxelPreprocessor": VoxelPreprocessor, - "BevPreprocessor": BevPreprocessor, - "SpVoxelPreprocessor": SpVoxelPreprocessor, -} - - -def build_preprocessor(preprocess_cfg, train): - process_method_name = preprocess_cfg["core_method"] - error_message = f"{process_method_name} is not found. Please add your processor file's name in opencood/data_utils/processor/init.py" - assert process_method_name in ["BasePreprocessor", "VoxelPreprocessor", "BevPreprocessor", "SpVoxelPreprocessor"], error_message - - processor = __all__[process_method_name](preprocess_params=preprocess_cfg, train=train) - - return processor diff --git a/OpenCOOD/opencood/data_utils/pre_processor/base_preprocessor.py b/OpenCOOD/opencood/data_utils/pre_processor/base_preprocessor.py deleted file mode 100644 index bb170bf7..00000000 --- a/OpenCOOD/opencood/data_utils/pre_processor/base_preprocessor.py +++ /dev/null @@ -1,104 +0,0 @@ -import numpy as np -import torch - -from opencood.utils import pcd_utils - - -class BasePreprocessor(object): - """ - Basic Lidar pre-processor. - - Parameters - ---------- - preprocess_params : dict - The dictionary containing all parameters of the preprocessing. - - train : bool - Train or test mode. - """ - - def __init__(self, preprocess_params, train): - self.params = preprocess_params - self.train = train - - def preprocess(self, pcd_np): - """ - Preprocess the lidar points by simple sampling. - - Parameters - ---------- - pcd_np : np.ndarray - The raw lidar. - - Returns - ------- - data_dict : the output dictionary. - """ - data_dict = {} - sample_num = self.params["args"]["sample_num"] - - pcd_np = pcd_utils.downsample_lidar(pcd_np, sample_num) - data_dict["downsample_lidar"] = pcd_np - - return data_dict - - @staticmethod - def collate_batch(batch): - """Collate sampled point-cloud inputs. - - Parameters - ---------- - batch : list[dict] | dict[str, list] - Point-cloud inputs represented either as a list of samples or a - dict of agent input lists. - - Returns - ------- - dict[str, torch.Tensor] - Batched point clouds under the ``downsample_lidar`` model key. - - Raises - ------ - TypeError - If ``batch`` is neither a list nor a dictionary. - """ - if isinstance(batch, list): - point_clouds = [sample["downsample_lidar"] for sample in batch] - elif isinstance(batch, dict): - point_clouds = batch["downsample_lidar"] - else: - raise TypeError(f"Unsupported point-cloud batch type: {type(batch).__name__}") - - return {"downsample_lidar": torch.from_numpy(np.stack(point_clouds))} - - def project_points_to_bev_map(self, points, ratio=0.1): - """ - Project points to BEV occupancy map with default ratio=0.1. - - Parameters - ---------- - points : np.ndarray - (N, 3) / (N, 4) - - ratio : float - Discretization parameters. Default is 0.1. - - Returns - ------- - bev_map : np.ndarray - BEV occupancy map including projected points with shape - (img_row, img_col). - - """ - L1, W1, H1, L2, W2, _ = self.params["cav_lidar_range"] - img_row = int((L2 - L1) / ratio) - img_col = int((W2 - W1) / ratio) - bev_map = np.zeros((img_row, img_col)) - bev_origin = np.array([L1, W1, H1]).reshape(1, -1) - # (N, 3) - indices = ((points[:, :3] - bev_origin) / ratio).astype(int) - mask = np.logical_and(indices[:, 0] > 0, indices[:, 0] < img_row) - mask = np.logical_and(mask, np.logical_and(indices[:, 1] > 0, indices[:, 1] < img_col)) - indices = indices[mask, :] - bev_map[indices[:, 0], indices[:, 1]] = 1 - return bev_map diff --git a/OpenCOOD/opencood/data_utils/pre_processor/bev_preprocessor.py b/OpenCOOD/opencood/data_utils/pre_processor/bev_preprocessor.py deleted file mode 100644 index b742686e..00000000 --- a/OpenCOOD/opencood/data_utils/pre_processor/bev_preprocessor.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Convert lidar to bev -""" - -import numpy as np -import torch -from opencood.data_utils.pre_processor.base_preprocessor import BasePreprocessor - - -class BevPreprocessor(BasePreprocessor): - def __init__(self, preprocess_params, train): - super(BevPreprocessor, self).__init__(preprocess_params, train) - self.lidar_range = self.params["cav_lidar_range"] - self.geometry_param = preprocess_params["geometry_param"] - - def preprocess(self, pcd_raw): - """ - Preprocess the lidar points to BEV representations. - - Parameters - ---------- - pcd_raw : np.ndarray - The raw lidar. - - Returns - ------- - data_dict : the structured output dictionary. - """ - bev = np.zeros(self.geometry_param["input_shape"], dtype=np.float32) - intensity_map_count = np.zeros((bev.shape[0], bev.shape[1]), dtype=np.int64) - bev_origin = np.array([self.geometry_param["L1"], self.geometry_param["W1"], self.geometry_param["H1"]]).reshape(1, -1) - - indices = ((pcd_raw[:, :3] - bev_origin) / self.geometry_param["res"]).astype(int) - - for i in range(indices.shape[0]): - bev[indices[i, 0], indices[i, 1], indices[i, 2]] = 1 - bev[indices[i, 0], indices[i, 1], -1] += pcd_raw[i, 3] - intensity_map_count[indices[i, 0], indices[i, 1]] += 1 - divide_mask = intensity_map_count != 0 - bev[divide_mask, -1] = np.divide(bev[divide_mask, -1], intensity_map_count[divide_mask]) - - data_dict = {"bev_input": np.transpose(bev, (2, 0, 1))} - return data_dict - - @staticmethod - def collate_batch_list(batch): - """ - Customized pytorch data loader collate function. - - Parameters - ---------- - batch : list - List of dictionary. Each dictionary represent a single frame. - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - bev_input_list = [x["bev_input"][np.newaxis, ...] for x in batch] - processed_batch = {"bev_input": torch.from_numpy(np.concatenate(bev_input_list, axis=0))} - return processed_batch - - @staticmethod - def collate_batch_dict(batch): - """ - Customized pytorch data loader collate function. - - Parameters - ---------- - batch : dict - Dict of list. Each element represents a CAV. - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - bev_input_list = [x[np.newaxis, ...] for x in batch["bev_input"]] - processed_batch = {"bev_input": torch.from_numpy(np.concatenate(bev_input_list, axis=0))} - return processed_batch - - def collate_batch(self, batch): - """ - Customized pytorch data loader collate function. - - Parameters - ---------- - batch : list / dict - Batched data. - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - if isinstance(batch, list): - return self.collate_batch_list(batch) - elif isinstance(batch, dict): - return self.collate_batch_dict(batch) - else: - raise NotImplementedError diff --git a/OpenCOOD/opencood/data_utils/pre_processor/sp_voxel_preprocessor.py b/OpenCOOD/opencood/data_utils/pre_processor/sp_voxel_preprocessor.py deleted file mode 100644 index 10e01c7a..00000000 --- a/OpenCOOD/opencood/data_utils/pre_processor/sp_voxel_preprocessor.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Transform points to voxels using sparse conv library -""" - -import sys - -import numpy as np -import torch -from cumm import tensorview as tv -from opencood.data_utils.pre_processor.base_preprocessor import BasePreprocessor - - -class SpVoxelPreprocessor(BasePreprocessor): - def __init__(self, preprocess_params, train): - super(SpVoxelPreprocessor, self).__init__(preprocess_params, train) - self.spconv = 1 - try: - # spconv v1.x - from spconv.utils import VoxelGeneratorV2 as VoxelGenerator - except ImportError: - # spconv v2.x - from spconv.utils import Point2VoxelCPU3d as VoxelGenerator - - self.spconv = 2 - self.lidar_range = self.params["cav_lidar_range"] - self.voxel_size = self.params["args"]["voxel_size"] - self.max_points_per_voxel = self.params["args"]["max_points_per_voxel"] - - if train: - self.max_voxels = self.params["args"]["max_voxel_train"] - else: - self.max_voxels = self.params["args"]["max_voxel_test"] - - grid_size = (np.array(self.lidar_range[3:6]) - np.array(self.lidar_range[0:3])) / np.array(self.voxel_size) - self.grid_size = np.round(grid_size).astype(np.int64) - - # use sparse conv library to generate voxel - if self.spconv == 1: - self.voxel_generator = VoxelGenerator( - voxel_size=self.voxel_size, point_cloud_range=self.lidar_range, max_num_points=self.max_points_per_voxel, max_voxels=self.max_voxels - ) - else: - self.voxel_generator = VoxelGenerator( - vsize_xyz=self.voxel_size, - coors_range_xyz=self.lidar_range, - max_num_points_per_voxel=self.max_points_per_voxel, - num_point_features=4, - max_num_voxels=self.max_voxels, - ) - - def preprocess(self, pcd_np): - data_dict = {} - if self.spconv == 1: - voxel_output = self.voxel_generator.generate(pcd_np) - else: - pcd_tv = tv.from_numpy(pcd_np) - voxel_output = self.voxel_generator.point_to_voxel(pcd_tv) - if isinstance(voxel_output, dict): - voxels, coordinates, num_points = voxel_output["voxels"], voxel_output["coordinates"], voxel_output["num_points_per_voxel"] - else: - voxels, coordinates, num_points = voxel_output - - if self.spconv == 2: - voxels = voxels.numpy() - coordinates = coordinates.numpy() - num_points = num_points.numpy() - - data_dict["voxel_features"] = voxels - data_dict["voxel_coords"] = coordinates - data_dict["voxel_num_points"] = num_points - - return data_dict - - def collate_batch(self, batch): - """ - Customized pytorch data loader collate function. - - Parameters - ---------- - batch : list or dict - List or dictionary. - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - - if isinstance(batch, list): - return self.collate_batch_list(batch) - elif isinstance(batch, dict): - return self.collate_batch_dict(batch) - else: - sys.exit("Batch has too be a list or a dictionarn") - - @staticmethod - def collate_batch_list(batch): - """ - Customized pytorch data loader collate function. - - Parameters - ---------- - batch : list - List of dictionary. Each dictionary represent a single frame. - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - voxel_features = [] - voxel_num_points = [] - voxel_coords = [] - - for i in range(len(batch)): - voxel_features.append(batch[i]["voxel_features"]) - voxel_num_points.append(batch[i]["voxel_num_points"]) - coords = batch[i]["voxel_coords"] - voxel_coords.append(np.pad(coords, ((0, 0), (1, 0)), mode="constant", constant_values=i)) - - voxel_num_points = torch.from_numpy(np.concatenate(voxel_num_points)) - voxel_features = torch.from_numpy(np.concatenate(voxel_features)) - voxel_coords = torch.from_numpy(np.concatenate(voxel_coords)) - - return {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points} - - @staticmethod - def collate_batch_dict(batch: dict): - """ - Collate batch if the batch is a dictionary, - eg: {'voxel_features': [feature1, feature2...., feature n]} - - Parameters - ---------- - batch : dict - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - voxel_features = torch.from_numpy(np.concatenate(batch["voxel_features"])) - voxel_num_points = torch.from_numpy(np.concatenate(batch["voxel_num_points"])) - coords = batch["voxel_coords"] - voxel_coords = [] - - for i in range(len(coords)): - voxel_coords.append(np.pad(coords[i], ((0, 0), (1, 0)), mode="constant", constant_values=i)) - voxel_coords = torch.from_numpy(np.concatenate(voxel_coords)) - - return {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points} diff --git a/OpenCOOD/opencood/data_utils/pre_processor/voxel_preprocessor.py b/OpenCOOD/opencood/data_utils/pre_processor/voxel_preprocessor.py deleted file mode 100644 index 4d487844..00000000 --- a/OpenCOOD/opencood/data_utils/pre_processor/voxel_preprocessor.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Convert lidar to voxel. This class was manually designed, and we recommend -to use sp_voxel_preprocessor. -""" - -import sys - -import numpy as np -import torch - -from opencood.data_utils.pre_processor.base_preprocessor import BasePreprocessor - - -class VoxelPreprocessor(BasePreprocessor): - def __init__(self, preprocess_params, train): - super(VoxelPreprocessor, self).__init__(preprocess_params, train) - # TODO: add intermediate lidar range later - self.lidar_range = self.params["cav_lidar_range"] - - self.vw = self.params["args"]["vw"] - self.vh = self.params["args"]["vh"] - self.vd = self.params["args"]["vd"] - self.T = self.params["args"]["T"] - - def preprocess(self, pcd_np): - """ - Preprocess the lidar points by voxelization. - - Parameters - ---------- - pcd_np : np.ndarray - The raw lidar. - - Returns - ------- - data_dict : the structured output dictionary. - """ - data_dict = {} - - # calculate the voxel coordinates - voxel_coords = ( - pcd_np[:, :3] - np.floor(np.array([self.lidar_range[0], self.lidar_range[1], self.lidar_range[2]])) / (self.vw, self.vh, self.vd) - ).astype(np.int32) - - # convert to (D, H, W) as the paper - voxel_coords = voxel_coords[:, [2, 1, 0]] - voxel_coords, inv_ind, voxel_counts = np.unique(voxel_coords, axis=0, return_inverse=True, return_counts=True) - - voxel_features = [] - - for i in range(len(voxel_coords)): - voxel = np.zeros((self.T, 7), dtype=np.float32) - pts = pcd_np[inv_ind == i] - if voxel_counts[i] > self.T: - pts = pts[: self.T, :] - voxel_counts[i] = self.T - - # augment the points - voxel[: pts.shape[0], :] = np.concatenate((pts, pts[:, :3] - np.mean(pts[:, :3], 0)), axis=1) - voxel_features.append(voxel) - - data_dict["voxel_features"] = np.array(voxel_features) - data_dict["voxel_coords"] = voxel_coords - - return data_dict - - def collate_batch(self, batch): - """ - Customized pytorch data loader collate function. - - Parameters - ---------- - batch : list or dict - List or dictionary. - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - - if isinstance(batch, list): - return self.collate_batch_list(batch) - elif isinstance(batch, dict): - return self.collate_batch_dict(batch) - else: - sys.exit("Batch has too be a list or a dictionarn") - - @staticmethod - def collate_batch_list(batch): - """ - Customized pytorch data loader collate function. - - Parameters - ---------- - batch : list - List of dictionary. Each dictionary represent a single frame. - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - voxel_features = [] - voxel_coords = [] - - for i in range(len(batch)): - voxel_features.append(batch[i]["voxel_features"]) - coords = batch[i]["voxel_coords"] - voxel_coords.append(np.pad(coords, ((0, 0), (1, 0)), mode="constant", constant_values=i)) - - voxel_features = torch.from_numpy(np.concatenate(voxel_features)) - voxel_coords = torch.from_numpy(np.concatenate(voxel_coords)) - - return {"voxel_features": voxel_features, "voxel_coords": voxel_coords} - - @staticmethod - def collate_batch_dict(batch: dict): - """ - Collate batch if the batch is a dictionary, - eg: {'voxel_features': [feature1, feature2...., feature n]} - - Parameters - ---------- - batch : dict - - Returns - ------- - processed_batch : dict - Updated lidar batch. - """ - voxel_features = torch.from_numpy(np.concatenate(batch["voxel_features"])) - coords = batch["voxel_coords"] - voxel_coords = [] - - for i in range(len(coords)): - voxel_coords.append(np.pad(coords[i], ((0, 0), (1, 0)), mode="constant", constant_values=i)) - voxel_coords = torch.from_numpy(np.concatenate(voxel_coords)) - - return {"voxel_features": voxel_features, "voxel_coords": voxel_coords} diff --git a/OpenCOOD/opencood/hypes_yaml/__init__.py b/OpenCOOD/opencood/hypes_yaml/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/hypes_yaml/ciassd_early_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/ciassd_early_fusion.yaml deleted file mode 100644 index d69b5eca..00000000 --- a/OpenCOOD/opencood/hypes_yaml/ciassd_early_fusion.yaml +++ /dev/null @@ -1,120 +0,0 @@ -name: ciassd_early_fusion - -yaml_parser: "load_voxel_params" -root_dir: "/media/hdd/yuan/koko/data/OPV2V/train" # "/media/hdd/yuan/data/OPV2V/train" -validate_dir: "/media/hdd/yuan/koko/data/OPV2V/validate" - -train_params: - batch_size: &batch_size 6 - epoches: 100 - eval_freq: 2 - save_freq: 2 - -fusion: - core_method: 'EarlyFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.1, 0.1, 0.1] - max_points_per_voxel: &T 5 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'CiassdPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - predict_iou: true - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - feature_stride: 8 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.3 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.2 - -# model related -model: - core_method: ciassd # corresponding to VoxelNet - args: - N: *batch_size - T: *T - lidar_range: *cav_lidar - voxel_size: *voxel_size - anchor_num: *achor_num - mean_vfe: - num_point_features: 4 - spconv: - num_features_in: 64 - num_features_out: 64 - map2bev: - feature_num: 128 - ssfa: - feature_num: 128 - head: - num_input: 128 - num_pred: 14 - num_cls: 2 - num_iou: 2 - use_dir: True - num_dir: 4 - -loss: - core_method: ciassd_loss - args: - pos_cls_weight: 50.0 - encode_rad_error_by_sin: true - cls: - type: 'SigmoidFocalLoss' - alpha: 0.25 - gamma: 2.0 - weight: 2.0 - reg: - type: 'WeightedSmoothL1Loss' - sigma: 3.0 - codewise: true - weight: 2.0 - iou: - type: 'WeightedSmoothL1Loss' - sigma: 3.0 - codewise: true - weight: 1.0 - dir: - type: 'WeightedSoftmaxClassificationLoss' - weight: 0.2 - - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/ciassd_intermediate_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/ciassd_intermediate_fusion.yaml deleted file mode 100644 index d03f6c47..00000000 --- a/OpenCOOD/opencood/hypes_yaml/ciassd_intermediate_fusion.yaml +++ /dev/null @@ -1,89 +0,0 @@ -name: ciassd - -yaml_parser: "load_voxel_params" -root_dir: "/media/hdd/yuan/data/OPV2V/train" -validate_dir: "/media/hdd/yuan/data/OPV2V/validate" - -train_params: - batch_size: &batch_size 1 - epoches: 30 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 0.4] - max_points_per_voxel: &T 32 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -41.6, -3, 140.8, 41.6, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.25 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: voxel_net_intermediate # corresponding to VoxelNet - args: - N: *batch_size - T: *T - lidar_range: *cav_lidar - voxel_size: *voxel_size - anchor_num: *achor_num - compression: 4 - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [ 64 ] - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/ciassd_late_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/ciassd_late_fusion.yaml deleted file mode 100644 index d3d2ef2a..00000000 --- a/OpenCOOD/opencood/hypes_yaml/ciassd_late_fusion.yaml +++ /dev/null @@ -1,120 +0,0 @@ -name: ciassd_late_fusion - -yaml_parser: "load_voxel_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -train_params: - batch_size: &batch_size 6 - epoches: 100 - eval_freq: 2 - save_freq: 2 - -fusion: - core_method: 'LateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.1, 0.1, 0.1] - max_points_per_voxel: &T 5 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -8, 140.8, 40, 8] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'CiassdPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - predict_iou: true - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - feature_stride: 8 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.3 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.2 - -# model related -model: - core_method: ciassd # corresponding to VoxelNet - args: - N: *batch_size - T: *T - lidar_range: *cav_lidar - voxel_size: *voxel_size - anchor_num: *achor_num - mean_vfe: - num_point_features: 4 - spconv: - num_features_in: 64 - num_features_out: 64 - map2bev: - feature_num: 128 - ssfa: - feature_num: 128 - head: - num_input: 128 - num_pred: 14 - num_cls: 2 - num_iou: 2 - use_dir: True - num_dir: 4 - -loss: - core_method: ciassd_loss - args: - pos_cls_weight: 50.0 - encode_rad_error_by_sin: true - cls: - type: 'SigmoidFocalLoss' - alpha: 0.25 - gamma: 2.0 - weight: 2.0 - reg: - type: 'WeightedSmoothL1Loss' - sigma: 3.0 - codewise: true - weight: 2.0 - iou: - type: 'WeightedSmoothL1Loss' - sigma: 3.0 - codewise: true - weight: 1.0 - dir: - type: 'WeightedSoftmaxClassificationLoss' - weight: 0.2 - - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/fpvrcnn_intermediate_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/fpvrcnn_intermediate_fusion.yaml deleted file mode 100644 index 261df692..00000000 --- a/OpenCOOD/opencood/hypes_yaml/fpvrcnn_intermediate_fusion.yaml +++ /dev/null @@ -1,177 +0,0 @@ -name: fpvrcnn_intermediate_fusion - -yaml_parser: "load_voxel_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -train_params: - batch_size: &batch_size 4 - epoches: 20 - eval_freq: 1 - save_freq: 1 - max_cav: &max_cav 10 - -fusion: - core_method: 'IntermediateFusionDatasetV2' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset IntermediateFusionDatasetV2 supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.1, 0.1, 0.4] - max_points_per_voxel: &T 5 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -41.6, -10.6, 140.8, 41.6, 10.6] - keep_original_lidar: true - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: &postprocess_params - core_method: 'FpvrcnnPostprocessor' # VoxelPostprocessor, BevPostprocessor, FpvrcnnPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 8 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.5 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: fpvrcnn # corresponding to FPVRCNN - args: - activate_stage2: False # True after training for first 20 epochs - N: *batch_size - T: *T - lidar_range: *cav_lidar - voxel_size: *voxel_size - anchor_num: *achor_num - mean_vfe: - num_point_features: 4 - spconv: - num_features_in: 4 - num_features_out: 64 - map2bev: - feature_num: 128 - ssfa: - feature_num: 128 - head: - num_input: 128 - num_pred: 14 - num_cls: 2 - num_iou: 2 - use_dir: True - num_dir: 4 - post_processer: *postprocess_params - vsa: - enlarge_selection_boxes: true - point_source: 'raw_points' - num_keypoints: 4096 - num_out_features: &cpm_features 32 - features_source: ['bev', 'x_conv1', 'x_conv2', 'x_conv3', 'x_conv4', 'raw_points'] - sa_layer: - raw_points: - mlps: [[16, 16], [16, 16]] - pool_radius: [0.4, 0.8] - n_sample: [16, 16] - x_conv1: - downsample_factor: 1 - mlps: [[16, 16], [16, 16]] - pool_radius: [0.4, 0.8] - n_sample: [16, 16] - x_conv2: - downsample_factor: 2 - mlps: [[32, 32], [32, 32]] - pool_radius: [0.8, 1.2] - n_sample: [16, 32] - x_conv3: - downsample_factor: 4 - mlps: [[64, 64], [64, 64]] - pool_radius: [1.2, 2.4] - n_sample: [16, 32] - x_conv4: - downsample_factor: 8 - mlps: [[64, 64], [64, 64]] - pool_radius: [2.4, 4.8] - n_sample: [16, 32] - matcher: 'none' # may need to configure parameters for loc correction - roi_head: - num_cls: 1 - in_channels: *cpm_features - n_fc_neurons: 256 - dp_ratio: 0.3 # dropout ratio - roi_grid_pool: - grid_size: 6 - mlps: [[64, 64], [64, 64]] - pool_radius: [0.8, 1.6] - n_sample: [16, 16] - pool_method: 'max_pool' - -loss: - core_method: fpvrcnn_loss - args: - stage1: - pos_cls_weight: 2.0 - encode_rad_error_by_sin: true - cls: - type: 'SigmoidFocalLoss' - alpha: 0.25 - gamma: 2.0 - weight: 2.0 - reg: - type: 'WeightedSmoothL1Loss' - sigma: 3.0 - codewise: true - weight: 2.0 - iou: &iou - type: 'WeightedSmoothL1Loss' - sigma: 3.0 - codewise: true - weight: 1.0 - dir: - type: 'WeightedSoftmaxClassificationLoss' - weight: 0.2 - stage2: - cls: - type: 'WeightedSigmoidBinaryCELoss' - weight: 1.0 - iou: *iou - reg: - type: 'WeightedSmoothL1Loss' - sigma: 3.0 - reduction: 'mean' - code_weights: [20.0] - weight: 1.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [10, 20] diff --git a/OpenCOOD/opencood/hypes_yaml/pixor_early_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/pixor_early_fusion.yaml deleted file mode 100644 index f3ac4586..00000000 --- a/OpenCOOD/opencood/hypes_yaml/pixor_early_fusion.yaml +++ /dev/null @@ -1,64 +0,0 @@ -name: pixor_early_fusion - -yaml_parser: "load_bev_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 -fusion: - core_method: 'EarlyFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'BevPreprocessor' - args: - res: &res 0.2 # discretization resolusion - downsample_rate: &downsample_rate 4 # pixor downsample ratio - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-160, -40, -3, 160, 40, 1] # must be divisible by 16 - -data_augment: [] - -# anchor box related -postprocess: - core_method: 'BevPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - nms_thresh: 0.15 - anchor_args: - cav_lidar_range: *cav_lidar - res: *res - downsample_rate: *downsample_rate # pixor downsample ratio - target_args: - score_threshold: 0.5 - - - order: 'lwh' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - -# model related -model: - core_method: pixor # corresponding to VoxelNet - args: - use_bn: True - decode: False - -loss: - core_method: pixor_loss - args: - alpha: 1.0 - beta: 1.0 - -optimizer: - core_method: Adam - lr: 0.001 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: exponential #step, multistep and exponential and cosineannealwarm support - gamma: 0.99 diff --git a/OpenCOOD/opencood/hypes_yaml/pixor_intermediate_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/pixor_intermediate_fusion.yaml deleted file mode 100644 index 3061efb8..00000000 --- a/OpenCOOD/opencood/hypes_yaml/pixor_intermediate_fusion.yaml +++ /dev/null @@ -1,69 +0,0 @@ -name: pixor_intermediate_fusion - -yaml_parser: "load_bev_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" -train_params: - batch_size: &batch_size 2 - epoches: 10 - eval_freq: 1 - save_freq: 1 -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'BevPreprocessor' - args: - res: &res 0.5 # discretization resolusion - downsample_rate: &downsample_rate 4 # pixor downsample ratio - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-160, -40, -8, 160, 40, 8] # must be divisible by 16 - -data_augment: [] - -# anchor box related -postprocess: - core_method: 'BevPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - nms_thresh: 0.15 - anchor_args: - cav_lidar_range: *cav_lidar - res: *res - downsample_rate: *downsample_rate # pixor downsample ratio - target_args: - score_threshold: 0.7 - - - order: 'lwh' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - -# model related -model: - core_method: pixor_intermediate - args: - use_bn: True - decode: False - -loss: - core_method: pixor_loss - args: - alpha: 1.0 - beta: 1.0 - -optimizer: - core_method: Adam - lr: 0.00001 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: cosineannealwarm #step, multistep and exponential and cosineannealwarm support - epoches: 1 # Specifying the number of epochs that matches train_params - T_0: 5 # The initial period for CosineAnnealingWarmRestarts - T_mult: 1 # Period Multiplier - warmup_lr: 0.00001 # Initial learning rate for warming up - warmup_epoches: 5 # Number of epochs to warm up - lr_min: 0.00001 # The minimum value of the learning rate diff --git a/OpenCOOD/opencood/hypes_yaml/pixor_late_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/pixor_late_fusion.yaml deleted file mode 100644 index 59064426..00000000 --- a/OpenCOOD/opencood/hypes_yaml/pixor_late_fusion.yaml +++ /dev/null @@ -1,58 +0,0 @@ -name: pixor_post_fusion - -yaml_parser: "load_bev_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 -fusion: - core_method: 'LateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'BevPreprocessor' - args: - res: &res 0.2 # discretization resolusion - downsample_rate: &downsample_rate 4 # pixor downsample ratio - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-80, -40, -3, 80, 40, 1] # must be divisible by 16 - -# anchor box related -postprocess: - core_method: 'BevPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - nms_thresh: 0.15 - anchor_args: - cav_lidar_range: *cav_lidar - res: *res - downsample_rate: *downsample_rate # pixor downsample ratio - target_args: - score_threshold: 0.3 - - - order: 'lwh' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - -# model related -model: - core_method: pixor # corresponding to VoxelNet - args: - use_bn: True - decode: False - -loss: - core_method: pixor_loss - args: - alpha: 1.0 - beta: 1.0 - -optimizer: - core_method: Adam - lr: 0.001 - args: - eps: 1e-10 - weight_decay: 1e-4 diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_coalign.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_coalign.yaml deleted file mode 100644 index cdc496ba..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_coalign.yaml +++ /dev/null @@ -1,105 +0,0 @@ -# This yaml only contains CoAlign's multiscale intermediate feature fusion. -# If you want build the agent-object pose graph to correct pose error, -# please refer to https://github.com/yifanlu0227/CoAlign - -name: point_pillar_coalign -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 2 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_coalign - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - compression: 0 # compression rate - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - res_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - compression: 0 - - anchor_num: *achor_num - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [10, 15] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_cobevt.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_cobevt.yaml deleted file mode 100644 index 5e7fdc9c..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_cobevt.yaml +++ /dev/null @@ -1,132 +0,0 @@ -name: corpbevtlidar -root_dir: 'v2xset/train' -validate_dir: 'v2xset/validate' - -wild_setting: - async: false - async_overhead: 100 - seed: 20 - loc_err: false - xyz_std: 0.2 - ryp_std: 0.2 - data_size: 1.06 # Mb!! - transmission_speed: 27 # Mbps!! - backbone_delay: 10 # ms - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 4 - epoches: &epoches 90 - eval_freq: 2 - save_freq: 1 - max_cav: &max_cav 5 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -38.4, -3, 140.8, 38.4, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 4 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_cobevt - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - max_cav: *max_cav - compression: 0 # compression rate - backbone_fix: false - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - shrink_header: - kernal_size: [3] - stride: [2] - padding: [1] - dim: [256] - input_dim: 384 # 128 * 3 - - fax_fusion: - input_dim: 256 - mlp_dim: 256 - agent_size: *max_cav - window_size: 4 - dim_head: 32 - drop_out: 0.1 - depth: 3 - mask: true - - - # add decoder later - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.001 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: cosineannealwarm #step, multistep, Exponential and cosineannealwarm support - epoches: *epoches - warmup_lr: 2e-4 - warmup_epoches: 10 - lr_min: 2e-5 diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_early_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_early_fusion.yaml deleted file mode 100644 index 1c082dae..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_early_fusion.yaml +++ /dev/null @@ -1,96 +0,0 @@ -name: point_pillar_early_fusion -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'EarlyFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - - anchor_num: *achor_num - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [10, 15] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_fcooper.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_fcooper.yaml deleted file mode 100644 index 18ce02b3..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_fcooper.yaml +++ /dev/null @@ -1,106 +0,0 @@ -name: point_pillar_fcooper -root_dir: 'opv2v_data_dumping/train' -validate_dir: 'opv2v_data_dumping/validate' - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 - max_cav: &max_cav 5 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 2 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_fcooper - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - max_cav: *max_cav - compression: 0 # compression rate - backbone_fix: false - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - shrink_header: - kernal_size: [ 1 ] - stride: [ 1 ] - padding: [ 0 ] - dim: [ 256 ] - input_dim: 384 # 128 * 3 - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.001 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [10, 15] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_intermediate_V2VAM.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_intermediate_V2VAM.yaml deleted file mode 100644 index ee5f2426..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_intermediate_V2VAM.yaml +++ /dev/null @@ -1,107 +0,0 @@ -name: point_pillar_intermediate_V2VAM_nocompressionV2 -sub_name: -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 4 - epoches: 60 - eval_freq: 1 - save_freq: 1 - max_cav: &max_cav 5 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 4 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_intermediate_V2VAM - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - max_cav: *max_cav - compression: 0 #32 # compression rate - backbone_fix: false - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - shrink_header: - kernal_size: [3] - stride: [2] - padding: [1] - dim: [256] - input_dim: 384 # 128 * 3 - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.0000001 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15,40,60] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_intermediate_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_intermediate_fusion.yaml deleted file mode 100644 index b2084d37..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_intermediate_fusion.yaml +++ /dev/null @@ -1,99 +0,0 @@ -name: point_pillar_intermediate_fusion -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 2 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_intermediate - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - compression: 0 - - anchor_num: *achor_num - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [10, 15] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_late_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_late_fusion.yaml deleted file mode 100644 index 26cb8ad5..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_late_fusion.yaml +++ /dev/null @@ -1,96 +0,0 @@ -name: point_pillar_late_fusion -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'LateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 16000 - max_voxel_test: 40000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-70.4, -40, -3, 70.4, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - - anchor_num: *achor_num - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [10, 15] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_v2vnet.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_v2vnet.yaml deleted file mode 100644 index dc623570..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_v2vnet.yaml +++ /dev/null @@ -1,123 +0,0 @@ -name: point_pillar_v2vnet -root_dir: 'opv2v_data_dumping/train' -validate_dir: 'opv2v_data_dumping/validate' - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 4 - epoches: 60 - eval_freq: 1 - save_freq: 1 - max_cav: &max_cav 5 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: - proj_first: true - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 4 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_v2vnet - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - max_cav: *max_cav - compression: 0 # compression rate - backbone_fix: false - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - shrink_header: - kernal_size: [ 3 ] - stride: [ 2 ] - padding: [ 1 ] - dim: [ 256 ] - input_dim: 384 # 128 * 3 - - v2vfusion: - voxel_size: *voxel_size - downsample_rate: 4 - num_iteration: 2 - in_channels: 256 - gru_flag: true - agg_operator: "avg" # max or avg - conv_gru: - H: 50 - W: 176 - num_layers: 1 - kernel_size: [[3,3]] - - - # add decoder later - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.001 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [10, 50, 80] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_v2xvit.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_v2xvit.yaml deleted file mode 100644 index 07765327..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_v2xvit.yaml +++ /dev/null @@ -1,161 +0,0 @@ -name: point_pillar_v2xvit -root_dir: 'v2xset/train' -validate_dir: 'v2xset/validate' - -wild_setting: - async: false - async_mode: 'sim' - async_overhead: 100 - seed: 25 - loc_err: false - xyz_std: 0.2 - ryp_std: 0.2 - data_size: 1.06 # Mb!! - transmission_speed: 27 # Mbps!! - backbone_delay: 10 # ms - -yaml_parser: "load_point_pillar_params" -train_params: - batch_size: &batch_size 2 - epoches: 60 - eval_freq: 1 - save_freq: 1 - max_cav: &max_cav 5 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: - cur_ego_pose_flag: False - # when the cur_ego_pose_flag is set to True, there is no time gap - # between the time when the LiDAR data is captured by connected - # agents and when the extracted features are received by - # the ego vehicle, which is equal to implement STCM. When set to False, - # STCM has to be used. - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 4] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -38.4, -3, 140.8, 38.4, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 4 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.27 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: point_pillar_transformer - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - max_cav: *max_cav - compression: 0 # compression rate - backbone_fix: false - - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [64] - point_pillar_scatter: - num_features: 64 - - base_bev_backbone: - layer_nums: [3, 5, 8] - layer_strides: [2, 2, 2] - num_filters: [64, 128, 256] - upsample_strides: [1, 2, 4] - num_upsample_filter: [128, 128, 128] - shrink_header: - kernal_size: [3] - stride: [2] - padding: [1] - dim: [256] - input_dim: 384 # 128 * 3 - - transformer: - encoder: &encoder - # number of fusion blocks per encoder layer - num_blocks: 1 - # number of encoder layers - depth: 3 - use_roi_mask: true - use_RTE: &use_RTE true - RTE_ratio: &RTE_ratio 2 # 2 means the dt has 100ms interval while 1 means 50 ms interval - # agent-wise attention - cav_att_config: &cav_att_config - dim: 256 - use_hetero: true - use_RTE: *use_RTE - RTE_ratio: *RTE_ratio - heads: 8 - dim_head: 32 - dropout: 0.3 - # spatial-wise attention - pwindow_att_config: &pwindow_att_config - dim: 256 - heads: [16, 8, 4] - dim_head: [16, 32, 64] - dropout: 0.3 - window_size: [4, 8, 16] - relative_pos_embedding: true - fusion_method: 'split_attn' - # feedforward condition - feed_forward: &feed_forward - mlp_dim: 256 - dropout: 0.3 - sttf: &sttf - voxel_size: *voxel_size - downsample_rate: 4 - - # add decoder later - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.001 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 50] diff --git a/OpenCOOD/opencood/hypes_yaml/point_pillar_where2comm.yaml b/OpenCOOD/opencood/hypes_yaml/point_pillar_where2comm.yaml deleted file mode 100644 index 93966ad4..00000000 --- a/OpenCOOD/opencood/hypes_yaml/point_pillar_where2comm.yaml +++ /dev/null @@ -1,133 +0,0 @@ -name: point_pillar_where2comm -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - - - -wild_setting: - seed: 20 - async: False - async_mode: 'sim' - async_overhead: 100 - loc_err: False - xyz_std: 0.2 - ryp_std: 0.2 - data_size: 1.06 # Mb - transmission_speed: 27 # Mbps - backbone_delay: 10 # ms - -yaml_parser: 'load_point_pillar_params' -train_params: - batch_size: &batch_size 4 - epoches: &epoches 100 - eval_freq: 1 - save_freq: 1 - max_cav: &max_cav 8 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [ ] - -# Preprocess-related -preprocess: - # Options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [ 0.4, 0.4, 16 ] - max_points_per_voxel: 32 - max_voxel_train: 32000 - max_voxel_test: 70000 - # LiDAR range for each individual CAV - cav_lidar_range: &cav_lidar [ -140.8, -38.4, -8 , 140.8, 38.4, 8 ] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# Anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [ 0, 90 ] - num: &achor_num 2 - feature_stride: 4 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.2 - order: 'hwl' # hwl or lwh - max_num: 100 # Maximum number of objects in a single frame. Use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# Model related -model: - core_method: point_pillar_where2comm - args: - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - head_dim: 256 - max_cav: *max_cav - compression: 0 # Compression rate - backbone_fix: False - pillar_vfe: - use_norm: True - with_distance: False - use_absolute_xyz: True - num_filters: [ 64 ] - point_pillar_scatter: - num_features: 64 - base_bev_backbone: - layer_nums: &layer_nums [ 3, 5, 8 ] - layer_strides: [ 2, 2, 2 ] - num_filters: &num_filters [ 64, 128, 256 ] - upsample_strides: [ 1, 2, 4 ] - num_upsample_filter: [ 128, 128, 128 ] - shrink_header: - kernal_size: [ 3 ] - stride: [ 2 ] - padding: [ 1 ] - dim: [ 256 ] - input_dim: 384 # 128 * 3 - where2comm_fusion: - fully: False - voxel_size: *voxel_size - downsample_rate: 4 - in_channels: 256 - multi_scale: True - layer_nums: *layer_nums - num_filters: *num_filters - communication: - round: 1 - threshold: 0.01 - gaussian_smooth: - k_size: 5 - c_sigma: 1.0 - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.005 - args: - eps: 1e-10 - weight_decay: 1e-2 - -lr_scheduler: - core_method: cosineannealwarm # step, multistep, exponential and cosineannealwarm support - epoches: *epoches - warmup_lr: 0.005 - warmup_epoches: 10 - lr_min: 5e-6 diff --git a/OpenCOOD/opencood/hypes_yaml/second_early_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/second_early_fusion.yaml deleted file mode 100644 index abf01980..00000000 --- a/OpenCOOD/opencood/hypes_yaml/second_early_fusion.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: second_early_fusion -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -yaml_parser: "load_second_params" -train_params: - batch_size: &batch_size 2 - epoches: 30 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'EarlyFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.1, 0.1, 0.1] - max_points_per_voxel: 5 - max_voxel_train: 32000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 8 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: second - args: - batch_size: *batch_size - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - mean_vfe: - feature_points: 4 - - backbone_3d: [] - - height_compression: - feature_num: 256 - - base_bev_backbone: - layer_nums: [5, 5] - layer_strides: [1, 2] - num_filters: [128, 256] - upsample_strides: [1, 2] - num_upsample_filter: [256, 256] - - anchor_num: *achor_num - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/second_intermediate_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/second_intermediate_fusion.yaml deleted file mode 100644 index 9e29b595..00000000 --- a/OpenCOOD/opencood/hypes_yaml/second_intermediate_fusion.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: second_intermediate_fusion -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -yaml_parser: "load_second_params" -train_params: - batch_size: &batch_size 2 - epoches: 60 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.1, 0.1, 0.1] - max_points_per_voxel: 5 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 8 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: second_intermediate - args: - batch_size: *batch_size - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - mean_vfe: - feature_points: 4 - - backbone_3d: [] - - height_compression: - feature_num: 256 - - base_bev_backbone: - layer_nums: [5, 5] - layer_strides: [1, 2] - num_filters: [128, 256] - upsample_strides: [1, 2] - num_upsample_filter: [256, 256] - - anchor_num: *achor_num - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/second_late_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/second_late_fusion.yaml deleted file mode 100644 index 8e72919a..00000000 --- a/OpenCOOD/opencood/hypes_yaml/second_late_fusion.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: second_late_fusion_low_res -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -yaml_parser: "load_second_params" -train_params: - batch_size: &batch_size 2 - epoches: 40 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'LateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.1, 0.1, 0.1] - max_points_per_voxel: 5 - max_voxel_train: 16000 - max_voxel_test: 40000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-70.4, -40, -3, 70.4, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - feature_stride: 8 - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.20 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: second - args: - batch_size: *batch_size - voxel_size: *voxel_size - lidar_range: *cav_lidar - anchor_number: *achor_num - mean_vfe: - feature_points: 4 - - backbone_3d: [] - - height_compression: - feature_num: 256 - - base_bev_backbone: - layer_nums: [5, 5] - layer_strides: [1, 2] - num_filters: [128, 256] - upsample_strides: [1, 2] - num_upsample_filter: [256, 256] - - anchor_num: *achor_num - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/visualization.yaml b/OpenCOOD/opencood/hypes_yaml/visualization.yaml deleted file mode 100644 index 15853ebb..00000000 --- a/OpenCOOD/opencood/hypes_yaml/visualization.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# this yaml is only for visualization -name: visualization - -yaml_parser: "load_voxel_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/test" - -train_params: - batch_size: &batch_size 4 - epoches: 100 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'EarlyFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 0.4] - max_points_per_voxel: &T 32 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.96 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 diff --git a/OpenCOOD/opencood/hypes_yaml/voxelnet_early_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/voxelnet_early_fusion.yaml deleted file mode 100644 index ca958525..00000000 --- a/OpenCOOD/opencood/hypes_yaml/voxelnet_early_fusion.yaml +++ /dev/null @@ -1,88 +0,0 @@ -name: voxelnet_early_fusion - -yaml_parser: "load_voxel_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -train_params: - batch_size: &batch_size 4 - epoches: 100 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'EarlyFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 0.4] - max_points_per_voxel: &T 32 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -40, -3, 140.8, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.96 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.20 - -# model related -model: - core_method: voxel_net # corresponding to VoxelNet - args: - N: *batch_size - T: *T - lidar_range: *cav_lidar - voxel_size: *voxel_size - anchor_num: *achor_num - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [ 64 ] - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/voxelnet_intermediate_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/voxelnet_intermediate_fusion.yaml deleted file mode 100644 index 4630f521..00000000 --- a/OpenCOOD/opencood/hypes_yaml/voxelnet_intermediate_fusion.yaml +++ /dev/null @@ -1,89 +0,0 @@ -name: voxelnet_intermediate_fusion - -yaml_parser: "load_voxel_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -train_params: - batch_size: &batch_size 4 - epoches: 100 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'IntermediateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 0.4] - max_points_per_voxel: &T 32 - max_voxel_train: 36000 - max_voxel_test: 70000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-140.8, -41.6, -8, 140.8, 41.6, 8] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.25 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.15 - -# model related -model: - core_method: voxel_net_intermediate # corresponding to VoxelNet - args: - N: *batch_size - T: *T - lidar_range: *cav_lidar - voxel_size: *voxel_size - anchor_num: *achor_num - compression: 0 - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [ 64 ] - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.005 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/voxelnet_late_fusion.yaml b/OpenCOOD/opencood/hypes_yaml/voxelnet_late_fusion.yaml deleted file mode 100644 index 02a88947..00000000 --- a/OpenCOOD/opencood/hypes_yaml/voxelnet_late_fusion.yaml +++ /dev/null @@ -1,88 +0,0 @@ -name: voxelnet_late_fusion - -yaml_parser: "load_voxel_params" -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/validate" - -train_params: - batch_size: &batch_size 4 - epoches: 30 - eval_freq: 1 - save_freq: 1 - -fusion: - core_method: 'LateFusionDataset' # LateFusionDataset, EarlyFusionDataset, IntermediateFusionDataset supported - args: [] - -# preprocess-related -preprocess: - # options: BasePreprocessor, VoxelPreprocessor, BevPreprocessor - core_method: 'SpVoxelPreprocessor' - args: - voxel_size: &voxel_size [0.4, 0.4, 0.4] - max_points_per_voxel: &T 32 - max_voxel_train: 16000 - max_voxel_test: 40000 - # lidar range for each individual cav. - cav_lidar_range: &cav_lidar [-70.4, -40, -3, 70.4, 40, 1] - -data_augment: - - NAME: random_world_flip - ALONG_AXIS_LIST: [ 'x' ] - - - NAME: random_world_rotation - WORLD_ROT_ANGLE: [ -0.78539816, 0.78539816 ] - - - NAME: random_world_scaling - WORLD_SCALE_RANGE: [ 0.95, 1.05 ] - -# anchor box related -postprocess: - core_method: 'VoxelPostprocessor' # VoxelPostprocessor, BevPostprocessor supported - anchor_args: - cav_lidar_range: *cav_lidar - l: 3.9 - w: 1.6 - h: 1.56 - r: [0, 90] - num: &achor_num 2 - target_args: - pos_threshold: 0.6 - neg_threshold: 0.45 - score_threshold: 0.25 - order: 'hwl' # hwl or lwh - max_num: 100 # maximum number of objects in a single frame. use this number to make sure different frames has the same dimension in the same batch - nms_thresh: 0.05 - -# model related -model: - core_method: voxel_net # corresponding to VoxelNet - args: - N: *batch_size - T: *T - lidar_range: *cav_lidar - voxel_size: *voxel_size - anchor_num: *achor_num - pillar_vfe: - use_norm: true - with_distance: false - use_absolute_xyz: true - num_filters: [ 64 ] - -loss: - core_method: point_pillar_loss - args: - cls_weight: 1.0 - reg: 2.0 - -optimizer: - core_method: Adam - lr: 0.002 - args: - eps: 1e-10 - weight_decay: 1e-4 - -lr_scheduler: - core_method: multistep #step, multistep and exponential and cosineannealwarm support - gamma: 0.1 - step_size: [15, 30] diff --git a/OpenCOOD/opencood/hypes_yaml/yaml_utils.py b/OpenCOOD/opencood/hypes_yaml/yaml_utils.py deleted file mode 100644 index 4ac4612e..00000000 --- a/OpenCOOD/opencood/hypes_yaml/yaml_utils.py +++ /dev/null @@ -1,252 +0,0 @@ -import re -import yaml -import os -import math - -import numpy as np - - -def load_yaml(file, opt=None): - """ - Load yaml file and return a dictionary. - - Parameters - ---------- - file : string - yaml file path. - - opt : argparser - Argparser. - Returns - ------- - param : dict - A dictionary that contains defined parameters. - """ - if opt and opt.model_dir: - file = os.path.join(opt.model_dir, "config.yaml") - - stream = open(file, "r") - loader = yaml.Loader - loader.add_implicit_resolver( - "tag:yaml.org,2002:float", - re.compile( - """^(?: - [-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+]?[0-9]+)? - |[-+]?(?:[0-9][0-9_]*)(?:[eE][-+]?[0-9]+) - |\\.[0-9_]+(?:[eE][-+][0-9]+)? - |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]* - |[-+]?\\.(?:inf|Inf|INF) - |\\.(?:nan|NaN|NAN))$""", - re.X, - ), - list("-+0123456789."), - ) - param = yaml.load(stream, Loader=loader) - if "yaml_parser" in param: - # TODO: Replace eval with secure function - param = eval(param["yaml_parser"])(param) - - return param - - -def load_voxel_params(param): # noqa: DC02 - """ - Based on the lidar range and resolution of voxel, calcuate the anchor box - and target resolution. - - Parameters - ---------- - param : dict - Original loaded parameter dictionary. - - Returns - ------- - param : dict - Modified parameter dictionary with new attribute `anchor_args[W][H][L]` - """ - anchor_args = param["postprocess"]["anchor_args"] - cav_lidar_range = anchor_args["cav_lidar_range"] - voxel_size = param["preprocess"]["args"]["voxel_size"] - - vw = voxel_size[0] - vh = voxel_size[1] - vd = voxel_size[2] - - anchor_args["vw"] = vw - anchor_args["vh"] = vh - anchor_args["vd"] = vd - - anchor_args["W"] = int((cav_lidar_range[3] - cav_lidar_range[0]) / vw) - anchor_args["H"] = int((cav_lidar_range[4] - cav_lidar_range[1]) / vh) - anchor_args["D"] = int((cav_lidar_range[5] - cav_lidar_range[2]) / vd) - - param["postprocess"].update({"anchor_args": anchor_args}) - - # sometimes we just want to visualize the data without implementing model - if "model" in param: - param["model"]["args"]["W"] = anchor_args["W"] - param["model"]["args"]["H"] = anchor_args["H"] - param["model"]["args"]["D"] = anchor_args["D"] - - return param - - -def load_point_pillar_params(param): # noqa: DC02 - """ - Based on the lidar range and resolution of voxel, calcuate the anchor box - and target resolution. - - Parameters - ---------- - param : dict - Original loaded parameter dictionary. - - Returns - ------- - param : dict - Modified parameter dictionary with new attribute. - """ - cav_lidar_range = param["preprocess"]["cav_lidar_range"] - voxel_size = param["preprocess"]["args"]["voxel_size"] - - grid_size = (np.array(cav_lidar_range[3:6]) - np.array(cav_lidar_range[0:3])) / np.array(voxel_size) - grid_size = np.round(grid_size).astype(np.int64) - param["model"]["args"]["point_pillar_scatter"]["grid_size"] = grid_size - - anchor_args = param["postprocess"]["anchor_args"] - - vw = voxel_size[0] - vh = voxel_size[1] - vd = voxel_size[2] - - anchor_args["vw"] = vw - anchor_args["vh"] = vh - anchor_args["vd"] = vd - - anchor_args["W"] = math.ceil((cav_lidar_range[3] - cav_lidar_range[0]) / vw) - anchor_args["H"] = math.ceil((cav_lidar_range[4] - cav_lidar_range[1]) / vh) - anchor_args["D"] = math.ceil((cav_lidar_range[5] - cav_lidar_range[2]) / vd) - - param["postprocess"].update({"anchor_args": anchor_args}) - - return param - - -def load_second_params(param): # noqa: DC02 - """ - Based on the lidar range and resolution of voxel, calcuate the anchor box - and target resolution. - - Parameters - ---------- - param : dict - Original loaded parameter dictionary. - - Returns - ------- - param : dict - Modified parameter dictionary with new attribute. - """ - cav_lidar_range = param["preprocess"]["cav_lidar_range"] - voxel_size = param["preprocess"]["args"]["voxel_size"] - - grid_size = (np.array(cav_lidar_range[3:6]) - np.array(cav_lidar_range[0:3])) / np.array(voxel_size) - grid_size = np.round(grid_size).astype(np.int64) - param["model"]["args"]["grid_size"] = grid_size - - anchor_args = param["postprocess"]["anchor_args"] - - vw = voxel_size[0] - vh = voxel_size[1] - vd = voxel_size[2] - - anchor_args["vw"] = vw - anchor_args["vh"] = vh - anchor_args["vd"] = vd - - anchor_args["W"] = int((cav_lidar_range[3] - cav_lidar_range[0]) / vw) - anchor_args["H"] = int((cav_lidar_range[4] - cav_lidar_range[1]) / vh) - anchor_args["D"] = int((cav_lidar_range[5] - cav_lidar_range[2]) / vd) - - param["postprocess"].update({"anchor_args": anchor_args}) - - return param - - -def load_bev_params(param): # noqa: DC02 - """ - Load bev related geometry parameters s.t. boundary, resolutions, input - shape, target shape etc. - - Parameters - ---------- - param : dict - Original loaded parameter dictionary. - - Returns - ------- - param : dict - Modified parameter dictionary with new attribute `geometry_param`. - - """ - res = param["preprocess"]["args"]["res"] - L1, W1, H1, L2, W2, H2 = param["preprocess"]["cav_lidar_range"] - downsample_rate = param["preprocess"]["args"]["downsample_rate"] - - def f(low, high, r): - return int((high - low) / r) - - input_shape = (int((f(L1, L2, res))), int((f(W1, W2, res))), int((f(H1, H2, res)) + 1)) - label_shape = (int(input_shape[0] / downsample_rate), int(input_shape[1] / downsample_rate), 7) - geometry_param = { - "L1": L1, - "L2": L2, - "W1": W1, - "W2": W2, - "H1": H1, - "H2": H2, - "downsample_rate": downsample_rate, - "input_shape": input_shape, - "label_shape": label_shape, - "res": res, - } - param["preprocess"]["geometry_param"] = geometry_param - param["postprocess"]["geometry_param"] = geometry_param - param["model"]["args"]["geometry_param"] = geometry_param - return param - - -def save_yaml(data, save_name): - """ - Save the dictionary into a yaml file. - - Parameters - ---------- - data : dict - The dictionary contains all data. - - save_name : string - Full path of the output yaml file. - """ - - with open(save_name, "w") as outfile: - yaml.dump(data, outfile, default_flow_style=False) - - -def save_yaml_wo_overwriting(data, save_name): - """ - Save the yaml file without overwriting the existing one. - - Parameters - ---------- - data : dict - The dictionary contains all data. - - save_name : string - Full path of the output yaml file. - """ - if os.path.exists(save_name): - prev_data = load_yaml(save_name) - data = {**data, **prev_data} - - save_yaml(data, save_name) diff --git a/OpenCOOD/opencood/loss/__init__.py b/OpenCOOD/opencood/loss/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/loss/ciassd_loss.py b/OpenCOOD/opencood/loss/ciassd_loss.py deleted file mode 100644 index 4a0b273e..00000000 --- a/OpenCOOD/opencood/loss/ciassd_loss.py +++ /dev/null @@ -1,223 +0,0 @@ -import torch -import torch.nn as nn - -from opencood.data_utils.post_processor.voxel_postprocessor import VoxelPostprocessor -from opencood.pcdet_utils.iou3d_nms.iou3d_nms_utils import aligned_boxes_iou3d_gpu - - -class CiassdLoss(nn.Module): - def __init__(self, args): - super(CiassdLoss, self).__init__() - self.pos_cls_weight = args["pos_cls_weight"] - self.encode_rad_error_by_sin = args["encode_rad_error_by_sin"] - self.cls = args["cls"] - self.reg = args["reg"] - self.iou = args["iou"] - self.dir = args["dir"] - self.loss_dict = {} - ## - self.num_cls = 2 - self.box_codesize = 7 - - def forward(self, output_dict, label_dict): - """ - Parameters - ---------- - output_dict : dict - target_dict : dict - """ - preds_dict = output_dict["preds_dict_stage1"] - target_dict = label_dict["stage1"] - if "record_len" in output_dict: - batch_size = int(output_dict["record_len"].sum()) - else: - batch_size = output_dict["batch_size"] - - # ######## - # pred = torch.sigmoid(preds_dict['cls_preds'][0]).sum(dim=0).cpu().detach().numpy() - # tagt_pos = target_dict['pos_equal_one'][0].sum(dim=-1).cpu().detach().numpy() - # tagt_neg = target_dict['neg_equal_one'][0].sum(dim=-1).cpu().detach().numpy() - # import matplotlib.pyplot as plt - # - # fig = plt.figure(figsize=(18, 8)) - # ax1 = fig.add_subplot(3, 1, 1) - # ax1.imshow(pred, cmap='cool') - # ax2 = fig.add_subplot(3, 1, 2) - # ax2.imshow(tagt_pos, cmap='cool') - # ax3 = fig.add_subplot(3, 1, 3) - # ax3.imshow(tagt_neg, cmap='cool') - # plt.show() - # plt.close() - # ######### - - cls_labls = target_dict["pos_equal_one"].view(batch_size, -1, self.num_cls - 1) - positives = cls_labls > 0 - negatives = target_dict["neg_equal_one"].view(batch_size, -1, self.num_cls - 1) > 0 - cared = torch.logical_or(positives, negatives) - cls_labls = cls_labls * cared.type_as(cls_labls) - # num_normalizer = cared.sum(1, keepdim=True) - pos_normalizer = positives.sum(1, keepdim=True).float() - - # cls loss - cls_preds = preds_dict["cls_preds"].permute(0, 2, 3, 1).contiguous().view(batch_size, -1, self.num_cls - 1) - cls_weights = positives * self.pos_cls_weight + negatives * 1.0 - cls_weights /= torch.clamp(pos_normalizer, min=1.0) - cls_loss = sigmoid_focal_loss(cls_preds, cls_labls, weights=cls_weights, **self.cls) - cls_loss_reduced = cls_loss.sum() * self.cls["weight"] / batch_size - - # reg loss - reg_weights = positives / torch.clamp(pos_normalizer, min=1.0) - reg_preds = preds_dict["box_preds"].permute(0, 2, 3, 1).contiguous().view(batch_size, -1, self.box_codesize) - reg_targets = target_dict["targets"].view(batch_size, -1, self.box_codesize) - if self.encode_rad_error_by_sin: - reg_preds, reg_targets = add_sin_difference(reg_preds, reg_targets) - reg_loss = weighted_smooth_l1_loss(reg_preds, reg_targets, weights=reg_weights, sigma=self.reg["sigma"]) - reg_loss_reduced = reg_loss.sum() * self.reg["weight"] / batch_size - - # dir loss - dir_targets = get_direction_target(reg_targets, output_dict["anchor_box"]) - dir_logits = preds_dict["dir_cls_preds"].permute(0, 2, 3, 1).contiguous().view(batch_size, -1, 2) - dir_weights = (cls_labls > 0).type_as(dir_logits).view(batch_size, -1) - dir_weights /= torch.clamp(dir_weights.sum(-1, keepdim=True), min=1.0) # [8, 70400], averaged in sample. - dir_loss = softmax_cross_entropy_with_logits(dir_logits.view(-1, self.num_cls), dir_targets.view(-1, self.num_cls)) - dir_loss = dir_loss.view(dir_weights.shape) * dir_weights - dir_loss_reduced = dir_loss.sum() * self.dir["weight"] / batch_size - - # iou loss - iou_preds = preds_dict["iou_preds"].permute(0, 2, 3, 1).contiguous() - pos_pred_mask = reg_weights.squeeze(dim=-1) > 0 # (4, 70400) - iou_pos_preds = iou_preds.view(batch_size, -1)[pos_pred_mask] - boxes3d_pred = VoxelPostprocessor.delta_to_boxes3d( - preds_dict["box_preds"].permute(0, 2, 3, 1).contiguous().detach(), output_dict["anchor_box"], False - )[pos_pred_mask] - boxes3d_tgt = VoxelPostprocessor.delta_to_boxes3d(target_dict["targets"], output_dict["anchor_box"], False)[pos_pred_mask] - iou_weights = reg_weights[pos_pred_mask].view(-1) - iou_pos_targets = ( - aligned_boxes_iou3d_gpu(boxes3d_pred.float()[:, [0, 1, 2, 5, 4, 3, 6]], boxes3d_tgt.float()[:, [0, 1, 2, 5, 4, 3, 6]]).detach().squeeze() - ) - iou_pos_targets = 2 * iou_pos_targets.view(-1) - 1 - iou_loss = weighted_smooth_l1_loss(iou_pos_preds, iou_pos_targets, weights=iou_weights, sigma=self.iou["sigma"]) - - iou_loss_reduced = iou_loss.sum() * self.iou["weight"] / batch_size - - loss = cls_loss_reduced + reg_loss_reduced + dir_loss_reduced + iou_loss_reduced - - self.loss_dict.update( - { - "total_loss": loss, - "cls_loss": cls_loss_reduced, - "reg_loss": reg_loss_reduced, - "dir_loss": dir_loss_reduced, - "iou_loss": iou_loss_reduced, - } - ) - - return loss - - def logging(self, epoch, batch_id, batch_len, writer, pbar=None): - """ - Print out the loss function for current iteration. - - Parameters - ---------- - epoch : int - Current epoch for training. - batch_id : int - The current batch. - batch_len : int - Total batch length in one iteration of training, - writer : SummaryWriter - Used to visualize on tensorboard - """ - total_loss = self.loss_dict["total_loss"] - reg_loss = self.loss_dict["reg_loss"] - cls_loss = self.loss_dict["cls_loss"] - dir_loss = self.loss_dict["dir_loss"] - iou_loss = self.loss_dict["iou_loss"] - if (batch_id + 1) % 10 == 0: - print( - "[epoch %d][%d/%d], || Loss: %.4f || Cls: %.4f" - " || Loc: %.4f || Dir: %.4f || Iou: %.4f" - % (epoch, batch_id + 1, batch_len, total_loss.item(), cls_loss.item(), reg_loss.item(), dir_loss.item(), iou_loss.item()) - ) - - writer.add_scalar("Regression_loss", reg_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Confidence_loss", cls_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Direction_loss", dir_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Iou_loss", iou_loss.item(), epoch * batch_len + batch_id) - - -def add_sin_difference(boxes1, boxes2): - rad_pred_encoding = torch.sin(boxes1[..., -1:]) * torch.cos(boxes2[..., -1:]) # ry -> sin(pred_ry)*cos(gt_ry) - rad_gt_encoding = torch.cos(boxes1[..., -1:]) * torch.sin(boxes2[..., -1:]) # ry -> cos(pred_ry)*sin(gt_ry) - res_boxes1 = torch.cat([boxes1[..., :-1], rad_pred_encoding], dim=-1) - res_boxes2 = torch.cat([boxes2[..., :-1], rad_gt_encoding], dim=-1) - return res_boxes1, res_boxes2 - - -def get_direction_target(reg_targets, anchors, one_hot=True, dir_offset=0.0): - """ - Generate targets for bounding box direction classification. - - Parameters - ---------- - anchors: torch.Tensor - shape as (H*W*2, 7) or (H, W, 2, 7) - reg_targets: torch.Tensor - shape as (B, H*W*2, 7) - - Returns - ------- - dir_cls_targets : torch.Tensor - [batch_size, w*h*num_anchor_per_pos, 2] - """ - batch_size = reg_targets.shape[0] - anchors = anchors.view(1, -1, anchors.shape[-1]).repeat(batch_size, 1, 1) - rot_gt = reg_targets[..., -1] + anchors[..., -1] # [4, 70400] - dir_cls_targets = ((rot_gt - dir_offset) > 0).long() # [4, 70400] - if one_hot: - dir_cls_targets = one_hot_f(dir_cls_targets, 2, dtype=anchors.dtype) - return dir_cls_targets - - -def one_hot_f(tensor, depth, dim=-1, on_value=1.0, dtype=torch.float32): - tensor_onehot = torch.zeros(*list(tensor.shape), depth, dtype=dtype, device=tensor.device) # [4, 70400, 2] - tensor_onehot.scatter_(dim, tensor.unsqueeze(dim).long(), on_value) # [4, 70400, 2] - return tensor_onehot - - -def sigmoid_focal_loss(preds, targets, weights=None, **kwargs): - assert "gamma" in kwargs and "alpha" in kwargs - # sigmoid cross entropy with logits - # more details: https://www.tensorflow.org/api_docs/python/tf/nn/sigmoid_cross_entropy_with_logits - per_entry_cross_ent = torch.clamp(preds, min=0) - preds * targets.type_as(preds) - per_entry_cross_ent += torch.log1p(torch.exp(-torch.abs(preds))) - # focal loss - prediction_probabilities = torch.sigmoid(preds) - p_t = (targets * prediction_probabilities) + ((1 - targets) * (1 - prediction_probabilities)) - modulating_factor = torch.pow(1.0 - p_t, kwargs["gamma"]) - alpha_weight_factor = targets * kwargs["alpha"] + (1 - targets) * (1 - kwargs["alpha"]) - - loss = modulating_factor * alpha_weight_factor * per_entry_cross_ent - if weights is not None: - loss *= weights - return loss - - -def softmax_cross_entropy_with_logits(logits, labels): - param = list(range(len(logits.shape))) - transpose_param = [0] + [param[-1]] + param[1:-1] - logits = logits.permute(*transpose_param) - loss_ftor = torch.nn.CrossEntropyLoss(reduction="none") - loss = loss_ftor(logits, labels.max(dim=-1)[1]) - return loss - - -def weighted_smooth_l1_loss(preds, targets, sigma=3.0, weights=None): - diff = preds - targets - abs_diff = torch.abs(diff) - abs_diff_lt_1 = torch.le(abs_diff, 1 / (sigma**2)).type_as(abs_diff) - loss = abs_diff_lt_1 * 0.5 * torch.pow(abs_diff * sigma, 2) + (abs_diff - 0.5 / (sigma**2)) * (1.0 - abs_diff_lt_1) - if weights is not None: - loss *= weights - return loss diff --git a/OpenCOOD/opencood/loss/fpvrcnn_loss.py b/OpenCOOD/opencood/loss/fpvrcnn_loss.py deleted file mode 100644 index 5fc73928..00000000 --- a/OpenCOOD/opencood/loss/fpvrcnn_loss.py +++ /dev/null @@ -1,175 +0,0 @@ -import torch -from torch import nn -import numpy as np -from opencood.loss.ciassd_loss import CiassdLoss, weighted_smooth_l1_loss - - -class FpvrcnnLoss(nn.Module): - def __init__(self, args): - super(FpvrcnnLoss, self).__init__() - self.ciassd_loss = CiassdLoss(args["stage1"]) - self.cls = args["stage2"]["cls"] - self.reg = args["stage2"]["reg"] - self.iou = args["stage2"]["iou"] - self.loss_dict = {} - - def forward(self, output_dict, label_dict): - """ - Parameters - ---------- - output_dict : dict - target_dict : dict - """ - ciassd_loss = self.ciassd_loss(output_dict, label_dict) - - # only update ciassd if no bbox is detected in the first stage - if "fpvrcnn_out" not in output_dict: - self.loss_dict = { - "loss": ciassd_loss, - } - return ciassd_loss - - # rcnn out - rcnn_cls = output_dict["fpvrcnn_out"]["rcnn_cls"].view(1, -1, 1) - rcnn_iou = output_dict["fpvrcnn_out"]["rcnn_iou"].view(1, -1, 1) - rcnn_reg = output_dict["fpvrcnn_out"]["rcnn_reg"].view(1, -1, 7) - - tgt_cls = output_dict["rcnn_label_dict"]["cls_tgt"].view(1, -1, 1) - tgt_iou = output_dict["rcnn_label_dict"]["iou_tgt"].view(1, -1, 1) - tgt_reg = output_dict["rcnn_label_dict"]["reg_tgt"].view(1, -1, 7) - - _ = tgt_cls.sum() # pos_norm - # cls loss - loss_cls = weighted_sigmoid_binary_cross_entropy(rcnn_cls, tgt_cls) - - # iou loss - # TODO: also count the negative samples - loss_iou = weighted_smooth_l1_loss(rcnn_iou, tgt_iou, weights=tgt_cls).mean() - - # regression loss - # Target resampling : Generate a weights mask to force the regressor concentrate on low iou predictions - # sample 50% with iou>0.7 and 50% < 0.7 - weights = torch.ones(tgt_iou.shape, device=tgt_iou.device) - weights[tgt_cls == 0] = 0 - neg = torch.logical_and(tgt_iou < 0.7, tgt_cls != 0) - pos = torch.logical_and(tgt_iou >= 0.7, tgt_cls != 0) - num_neg = int(neg.sum(dim=1)) - num_pos = int(pos.sum(dim=1)) - num_pos_smps = max(num_neg, 2) - pos_indices = torch.where(pos)[1] - not_selsected = torch.randperm(num_pos)[: num_pos - num_pos_smps] - # not_selsected_indices = pos_indices[not_selsected] - weights[:, pos_indices[not_selsected]] = 0 - loss_reg = weighted_smooth_l1_loss(rcnn_reg, tgt_reg, weights=weights / max(weights.sum(), 1)).sum() - - loss_cls_reduced = loss_cls * self.cls["weight"] - loss_iou_reduced = loss_iou * self.iou["weight"] - loss_reg_reduced = loss_reg * self.reg["weight"] - - # if torch.isnan(loss_reg_reduced): - # print('debug') - - rcnn_loss = loss_cls_reduced + loss_iou_reduced + loss_reg_reduced - loss = rcnn_loss + ciassd_loss - - self.loss_dict.update( - { - "loss": loss, - "rcnn_loss": rcnn_loss, - "cls_loss": loss_cls_reduced, - "iou_loss": loss_iou_reduced, - "reg_loss": loss_reg_reduced, - } - ) - - return loss - - def logging(self, epoch, batch_id, batch_len, writer, pbar=None): - """ - Print out the loss function for current iteration. - - Parameters - ---------- - epoch : int - Current epoch for training. - batch_id : int - The current batch. - batch_len : int - Total batch length in one iteration of training, - writer : SummaryWriter - Used to visualize on tensorboard - """ - ciassd_loss_dict = self.ciassd_loss.loss_dict - ciassd_total_loss = ciassd_loss_dict["total_loss"] - reg_loss = ciassd_loss_dict["reg_loss"] - cls_loss = ciassd_loss_dict["cls_loss"] - dir_loss = ciassd_loss_dict["dir_loss"] - iou_loss = ciassd_loss_dict["iou_loss"] - - if (batch_id + 1) % 10 == 0: - str_to_print = "[epoch %d][%d/%d], || Loss: %.4f || Ciassd: %.4f || Cls1: %.4f || Loc1: %.4f || Dir1: %.4f || Iou1: %.4f" % ( - epoch, - batch_id + 1, - batch_len, - self.loss_dict["loss"], - ciassd_total_loss.item(), - cls_loss.item(), - reg_loss.item(), - dir_loss.item(), - iou_loss.item(), - ) - if "rcnn_loss" in self.loss_dict: - str_to_print += " || Rcnn: %.4f || Cls2: %.4f || Loc2: %.4f || Iou2: %.4f" % ( - self.loss_dict["rcnn_loss"], - self.loss_dict["cls_loss"].item(), - self.loss_dict["reg_loss"].item(), - self.loss_dict["iou_loss"].item(), - ) - print(str_to_print) - - writer.add_scalar("Ciassd_regression_loss", reg_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Ciassd_Confidence_loss", cls_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Ciassd_Direction_loss", dir_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Ciassd_Iou_loss", iou_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Ciassd_loss", ciassd_total_loss.item(), epoch * batch_len + batch_id) - if "rcnn_loss" in self.loss_dict: - writer.add_scalar("Rcnn_regression_loss", self.loss_dict["reg_loss"].item(), epoch * batch_len + batch_id) - writer.add_scalar("Rcnn_Confidence_loss", self.loss_dict["cls_loss"].item(), epoch * batch_len + batch_id) - writer.add_scalar("Rcnn_Iou_loss", self.loss_dict["iou_loss"].item(), epoch * batch_len + batch_id) - writer.add_scalar("Rcnn_loss", self.loss_dict["rcnn_loss"].item(), epoch * batch_len + batch_id) - writer.add_scalar("Total_loss", self.loss_dict["loss"].item(), epoch * batch_len + batch_id) - - -def weighted_sigmoid_binary_cross_entropy(preds, tgts, weights=None, class_indices=None): - if weights is not None: - weights = weights.unsqueeze(-1) - if class_indices is not None: - weights *= indices_to_dense_vector(class_indices, preds.shape[2]).view(1, 1, -1).type_as(preds) - per_entry_cross_ent = nn.functional.binary_cross_entropy_with_logits(preds, tgts, weights) - return per_entry_cross_ent - - -def indices_to_dense_vector(indices, size, indices_value=1.0, default_value=0, dtype=np.float32): - """Creates dense vector with indices set to specific value and rest to zeros. - - This function exists because it is unclear if it is safe to use - tf.sparse_to_dense(indices, [size], 1, validate_indices=False) - with indices which are not ordered. - This function accepts a dynamic size (e.g. tf.shape(tensor)[0]) - - Args: - indices: 1d Tensor with integer indices which are to be set to - indices_values. - size: scalar with size (integer) of output Tensor. - indices_value: values of elements specified by indices in the output vector - default_value: values of other elements in the output vector. - dtype: data type. - - Returns: - dense 1D Tensor of shape [size] with indices set to indices_values and the - rest set to default_value. - """ - dense = torch.zeros(size).fill_(default_value) - dense[indices] = indices_value - - return dense diff --git a/OpenCOOD/opencood/loss/pixor_loss.py b/OpenCOOD/opencood/loss/pixor_loss.py deleted file mode 100644 index da8ff4d2..00000000 --- a/OpenCOOD/opencood/loss/pixor_loss.py +++ /dev/null @@ -1,116 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class PixorLoss(nn.Module): - def __init__(self, args): - super(PixorLoss, self).__init__() - self.alpha = args["alpha"] - self.beta = args["beta"] - self.loss_dict = {} - - def dtype(self): - return torch.float16 - - def forward(self, output_dict, target_dict): - """ - Compute loss for pixor network - Parameters - ---------- - output_dict : dict - The dictionary that contains the output. - - target_dict : dict - The dictionary that contains the target. - - Returns - ------- - total_loss : torch.Tensor - Total loss. - - """ - targets = target_dict["label_map"] - cls_preds, loc_preds = output_dict["cls"], output_dict["reg"] - - cls_targets, loc_targets = targets.split([1, 6], dim=1) - pos_count = cls_targets.sum() - neg_count = (cls_targets == 0).sum() - - w1 = (neg_count / (pos_count + neg_count)).to(dtype=self.dtype()) - w2 = (pos_count / (pos_count + neg_count)).to(dtype=self.dtype()) - - weights = torch.ones_like(cls_preds.reshape(-1), dtype=self.dtype(), device=cls_preds.device) - weights[cls_targets.reshape(-1) == 1] = w1 - weights[cls_targets.reshape(-1) == 0] = w2 - - # cls_loss = F.binary_cross_entropy_with_logits(input=cls_preds.reshape(-1), target=cls_targets.reshape(-1), weight=weights, - # reduction='mean') - - print(f"cls_preds shape: {cls_preds.shape}") - print(f"cls_targets shape: {cls_targets.shape}") - # print(f"pos_neg_weights shape: {pos_neg_weights.shape}") - - if cls_targets.shape[2:] != cls_preds.shape[2:]: - cls_targets = F.interpolate(cls_targets, size=cls_preds.shape[2:], mode="bilinear", align_corners=False) - # pos_neg_weights = F.interpolate(pos_neg_weights, size=cls_preds.shape[2:], mode='bilinear', align_corners=False) - print(f"Resized cls_targets shape: {cls_targets.shape}") - # print(f"Resized pos_neg_weights shape: {pos_neg_weights.shape}") - - cls_loss = F.binary_cross_entropy_with_logits(input=cls_preds, target=cls_targets, reduction="mean") - pos_pixels = cls_targets.sum() - - loc_loss = F.smooth_l1_loss(cls_targets * loc_preds, cls_targets * loc_targets, reduction="sum") - loc_loss = loc_loss / pos_pixels if pos_pixels > 0 else loc_loss - - total_loss = self.alpha * cls_loss + self.beta * loc_loss - - self.loss_dict.update({"total_loss": total_loss, "reg_loss": loc_loss, "cls_loss": cls_loss}) - - return total_loss - - def logging(self, epoch, batch_id, batch_len, writer, pbar=None): - """ - Print out the loss function for current iteration. - - Parameters - ---------- - epoch : int - Current epoch for training. - batch_id : int - The current batch. - batch_len : int - Total batch length in one iteration of training, - writer : SummaryWriter - Used to visualize on tensorboard - """ - total_loss = self.loss_dict["total_loss"] - reg_loss = self.loss_dict["reg_loss"] - cls_loss = self.loss_dict["cls_loss"] - - if pbar is None: - print( - "[epoch %d][%d/%d], || Loss: %.4f || Conf Loss: %.4f" - " || Loc Loss: %.4f" % (epoch, batch_id + 1, batch_len, total_loss.item(), cls_loss.item(), reg_loss.item()) - ) - else: - pbar.set_description( - "[epoch %d][%d/%d], || Loss: %.4f || Conf Loss: %.4f" - " || Loc Loss: %.4f" % (epoch, batch_id + 1, batch_len, total_loss.item(), cls_loss.item(), reg_loss.item()) - ) - - writer.add_scalar("Regression_loss", reg_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Confidence_loss", cls_loss.item(), epoch * batch_len + batch_id) - - -def test(): - torch.manual_seed(0) - loss = PixorLoss(None) - pred = torch.sigmoid(torch.randn(1, 7, 2, 3)) - label = torch.zeros(1, 7, 2, 3) - loss = loss(pred, label) - print(loss) - - -if __name__ == "__main__": - test() diff --git a/OpenCOOD/opencood/loss/point_pillar_loss.py b/OpenCOOD/opencood/loss/point_pillar_loss.py deleted file mode 100644 index 6b976123..00000000 --- a/OpenCOOD/opencood/loss/point_pillar_loss.py +++ /dev/null @@ -1,222 +0,0 @@ -import torch -import torch.nn as nn -import numpy as np - - -class WeightedSmoothL1Loss(nn.Module): - """ - Code-wise Weighted Smooth L1 Loss modified based on fvcore.nn.smooth_l1_loss - https://github.com/facebookresearch/fvcore/blob/master/fvcore/nn/smooth_l1_loss.py - | 0.5 * x ** 2 / beta if abs(x) < beta - smoothl1(x) = | - | abs(x) - 0.5 * beta otherwise, - where x = input - target. - """ - - def __init__(self, beta: float = 1.0 / 9.0, code_weights: list = None): - """ - Args: - beta: Scalar float. - L1 to L2 change point. - For beta values < 1e-5, L1 loss is computed. - code_weights: (#codes) float list if not None. - Code-wise weights. - """ - super(WeightedSmoothL1Loss, self).__init__() - self.beta = beta - if code_weights is not None: - self.code_weights = np.array(code_weights, dtype=np.float32) - self.code_weights = torch.from_numpy(self.code_weights).cuda() - - @staticmethod - def smooth_l1_loss(diff, beta): - if beta < 1e-5: - loss = torch.abs(diff) - else: - n = torch.abs(diff) - loss = torch.where(n < beta, 0.5 * n**2 / beta, n - 0.5 * beta) - - return loss - - def forward(self, input: torch.Tensor, target: torch.Tensor, weights: torch.Tensor = None): - """ - Args: - input: (B, #anchors, #codes) float tensor. - Ecoded predicted locations of objects. - target: (B, #anchors, #codes) float tensor. - Regression targets. - weights: (B, #anchors) float tensor if not None. - - Returns: - loss: (B, #anchors) float tensor. - Weighted smooth l1 loss without reduction. - """ - target = torch.where(torch.isnan(target), input, target) # ignore nan targets - - diff = input - target - loss = self.smooth_l1_loss(diff, self.beta) - - # anchor-wise weighting - if weights is not None: - assert weights.shape[0] == loss.shape[0] and weights.shape[1] == loss.shape[1] - loss = loss * weights.unsqueeze(-1) - - return loss - - -class PointPillarLoss(nn.Module): - def __init__(self, args): - super(PointPillarLoss, self).__init__() - self.reg_loss_func = WeightedSmoothL1Loss() - self.alpha = 0.25 - self.gamma = 2.0 - - self.cls_weight = args["cls_weight"] - self.reg_coe = args["reg"] - self.loss_dict = {} - - def forward(self, output_dict, target_dict): - """ - Parameters - ---------- - output_dict : dict - target_dict : dict - """ - rm = output_dict["rm"] - psm = output_dict["psm"] - targets = target_dict["targets"] - - cls_preds = psm.permute(0, 2, 3, 1).contiguous() - - box_cls_labels = target_dict["pos_equal_one"] - box_cls_labels = box_cls_labels.view(psm.shape[0], -1).contiguous() - - positives = box_cls_labels > 0 - negatives = box_cls_labels == 0 - negative_cls_weights = negatives * 1.0 - cls_weights = (negative_cls_weights + 1.0 * positives).float() - reg_weights = positives.float() - - pos_normalizer = positives.sum(1, keepdim=True).float() - reg_weights /= torch.clamp(pos_normalizer, min=1.0) - cls_weights /= torch.clamp(pos_normalizer, min=1.0) - cls_targets = box_cls_labels - cls_targets = cls_targets.unsqueeze(dim=-1) - - cls_targets = cls_targets.squeeze(dim=-1) - one_hot_targets = torch.zeros(*list(cls_targets.shape), 2, dtype=cls_preds.dtype, device=cls_targets.device) - one_hot_targets.scatter_(-1, cls_targets.unsqueeze(dim=-1).long(), 1.0) - cls_preds = cls_preds.view(psm.shape[0], -1, 1) - one_hot_targets = one_hot_targets[..., 1:] - - cls_loss_src = self.cls_loss_func(cls_preds, one_hot_targets, weights=cls_weights) # [N, M] - cls_loss = cls_loss_src.sum() / psm.shape[0] - conf_loss = cls_loss * self.cls_weight - - # regression - rm = rm.permute(0, 2, 3, 1).contiguous() - rm = rm.view(rm.size(0), -1, 7) - targets = targets.view(targets.size(0), -1, 7) - box_preds_sin, reg_targets_sin = self.add_sin_difference(rm, targets) - loc_loss_src = self.reg_loss_func(box_preds_sin, reg_targets_sin, weights=reg_weights) - reg_loss = loc_loss_src.sum() / rm.shape[0] - reg_loss *= self.reg_coe - - total_loss = reg_loss + conf_loss - - self.loss_dict.update({"total_loss": total_loss, "reg_loss": reg_loss, "conf_loss": conf_loss}) - - return total_loss - - def cls_loss_func(self, input: torch.Tensor, target: torch.Tensor, weights: torch.Tensor): - """ - Args: - input: (B, #anchors, #classes) float tensor. - Predicted logits for each class - target: (B, #anchors, #classes) float tensor. - One-hot encoded classification targets - weights: (B, #anchors) float tensor. - Anchor-wise weights. - - Returns: - weighted_loss: (B, #anchors, #classes) float tensor after weighting. - """ - pred_sigmoid = torch.sigmoid(input) - alpha_weight = target * self.alpha + (1 - target) * (1 - self.alpha) - pt = target * (1.0 - pred_sigmoid) + (1.0 - target) * pred_sigmoid - focal_weight = alpha_weight * torch.pow(pt, self.gamma) - - bce_loss = self.sigmoid_cross_entropy_with_logits(input, target) - - loss = focal_weight * bce_loss - - if weights.shape.__len__() == 2 or (weights.shape.__len__() == 1 and target.shape.__len__() == 2): - weights = weights.unsqueeze(-1) - - assert weights.shape.__len__() == loss.shape.__len__() - - return loss * weights - - @staticmethod - def sigmoid_cross_entropy_with_logits(input: torch.Tensor, target: torch.Tensor): - """PyTorch Implementation for tf.nn.sigmoid_cross_entropy_with_logits: - max(x, 0) - x * z + log(1 + exp(-abs(x))) in - https://www.tensorflow.org/api_docs/python/tf/nn/sigmoid_cross_entropy_with_logits - - Args: - input: (B, #anchors, #classes) float tensor. - Predicted logits for each class - target: (B, #anchors, #classes) float tensor. - One-hot encoded classification targets - - Returns: - loss: (B, #anchors, #classes) float tensor. - Sigmoid cross entropy loss without reduction - """ - loss = torch.clamp(input, min=0) - input * target + torch.log1p(torch.exp(-torch.abs(input))) - return loss - - @staticmethod - def add_sin_difference(boxes1, boxes2, dim=6): - assert dim != -1 - rad_pred_encoding = torch.sin(boxes1[..., dim : dim + 1]) * torch.cos(boxes2[..., dim : dim + 1]) - rad_tg_encoding = torch.cos(boxes1[..., dim : dim + 1]) * torch.sin(boxes2[..., dim : dim + 1]) - - boxes1 = torch.cat([boxes1[..., :dim], rad_pred_encoding, boxes1[..., dim + 1 :]], dim=-1) - boxes2 = torch.cat([boxes2[..., :dim], rad_tg_encoding, boxes2[..., dim + 1 :]], dim=-1) - return boxes1, boxes2 - - def logging(self, epoch, batch_id, batch_len, writer, pbar=None): - """ - Print out the loss function for current iteration. - - Parameters - ---------- - epoch : int - Current epoch for training. - batch_id : int - The current batch. - batch_len : int - Total batch length in one iteration of training, - writer : SummaryWriter - Used to visualize on tensorboard - pbar : tqdm - Progress bar - """ - total_loss = self.loss_dict["total_loss"] - reg_loss = self.loss_dict["reg_loss"] - conf_loss = self.loss_dict["conf_loss"] - - if pbar is None: - print( - "[epoch %d][%d/%d], || Loss: %.4f || Conf Loss: %.4f" - " || Loc Loss: %.4f" % (epoch, batch_id + 1, batch_len, total_loss.item(), conf_loss.item(), reg_loss.item()) - ) - else: - pbar.set_description( - "[epoch %d][%d/%d], || Loss: %.4f || Conf Loss: %.4f" - " || Loc Loss: %.4f" % (epoch, batch_id + 1, batch_len, total_loss.item(), conf_loss.item(), reg_loss.item()) - ) - - writer.add_scalar("Regression_loss", reg_loss.item(), epoch * batch_len + batch_id) - writer.add_scalar("Confidence_loss", conf_loss.item(), epoch * batch_len + batch_id) diff --git a/OpenCOOD/opencood/models/__init__.py b/OpenCOOD/opencood/models/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/models/ciassd.py b/OpenCOOD/opencood/models/ciassd.py deleted file mode 100644 index b9f0bdbf..00000000 --- a/OpenCOOD/opencood/models/ciassd.py +++ /dev/null @@ -1,44 +0,0 @@ -from torch import nn -import numpy as np - -from opencood.models.sub_modules.mean_vfe import MeanVFE -from opencood.models.sub_modules.sparse_backbone_3d import VoxelBackBone8x -from opencood.models.sub_modules.height_compression import HeightCompression -from opencood.models.sub_modules.cia_ssd_utils import SSFA, Head - - -class CIASSD(nn.Module): - def __init__(self, args): - super(CIASSD, self).__init__() - lidar_range = np.array(args["lidar_range"]) - grid_size = np.round((lidar_range[3:6] - lidar_range[:3]) / np.array(args["voxel_size"])).astype(np.int64) - self.vfe = MeanVFE(args["mean_vfe"], args["mean_vfe"]["num_point_features"]) - self.spconv_block = VoxelBackBone8x(args["spconv"], input_channels=args["spconv"]["num_features_in"], grid_size=grid_size) - self.map_to_bev = HeightCompression(args["map2bev"]) - self.ssfa = SSFA(args["ssfa"]) - self.head = Head(**args["head"]) - - def forward(self, batch_dict): - processed_lidar = batch_dict["processed_lidar"] - batch_size = batch_dict.get("batch_size") - if batch_size is None: - batch_size = batch_dict["object_bbx_center"].shape[0] - model_batch = { - **processed_lidar, - "batch_size": batch_size, - } - model_batch = self.vfe(model_batch) - model_batch = self.spconv_block(model_batch) - model_batch = self.map_to_bev(model_batch) - out = self.ssfa(model_batch["spatial_features"]) - out = self.head(out) - return { - "preds_dict_stage1": out, - "batch_size": batch_size, - "anchor_box": batch_dict.get("anchor_box"), - } - - -if __name__ == "__main__": - model = SSFA(None) - print(model) diff --git a/OpenCOOD/opencood/models/communication_adapters/__init__.py b/OpenCOOD/opencood/models/communication_adapters/__init__.py deleted file mode 100644 index 2db04053..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/__init__.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Model-owned communication adapter selection.""" - -from typing import cast - -import torch -from torch import nn - -from opencood.models.communication_adapters.base import ModelCommunicationAdapter -from opencood.models.communication_adapters.early import EarlyFusionCommunicationAdapter -from opencood.models.communication_adapters.inference_input import ( - BevInferenceInput, - DetectionInferenceInput, - FpvrcnnAgentInferenceInput, - InferenceInput, - MultiScaleFeatureInput, - PointCloudInferenceInput, - SpatialFeatureInput, - VoxelInferenceInput, - Where2CommFeatureInput, - build_inference_input, - inference_input_to_dict, - merge_inference_inputs, -) -from opencood.models.communication_adapters.intermediate import ( - IntermediateFeatureCommunicationAdapter, - MultiScaleFeatureCommunicationAdapter, - SpatialFeatureCommunicationAdapter, -) -from opencood.models.communication_adapters.inference_metadata import ( - IntermediateMetadata, - PoseFrameMetadata, - V2XViTMetadata, -) -from opencood.models.communication_adapters.late import LateFusionCommunicationAdapter -from opencood.models.communication_adapters.where2comm import Where2CommCommunicationAdapter -from opencood.models.communication_adapters.wire_payload import ( - EarlyFusionWirePayload, - FpvrcnnWirePayload, - IntermediateFeatureWirePayload, - LateFusionWirePayload, - Where2CommWirePayload, -) - - -def build_communication_adapter( - model: nn.Module, - device: torch.device, - fusion_method: str | None = None, -) -> ModelCommunicationAdapter: - """ - Build the communication adapter declared by a perception model. - - Fusion-wide contracts are selected from the configured fusion method. - Intermediate models declare the adapter matching their learned-feature - communication boundary. - - Parameters - ---------- - model : torch.nn.Module - Cooperative perception model instance. - device : torch.device - Device used for sender-side model execution. - fusion_method : str | None - Explicit fusion method selected by the model configuration. - - Returns - ------- - ModelCommunicationAdapter - Adapter responsible for the model's network communication boundary. - - Raises - ------ - TypeError - If the model declares an invalid adapter class. - """ - if fusion_method == "EarlyFusionDataset": - return EarlyFusionCommunicationAdapter(model, device) - if fusion_method == "LateFusionDataset": - return LateFusionCommunicationAdapter(model, device) - - adapter_class: object = getattr(type(model), "communication_adapter_class", None) - if adapter_class is None: - raise TypeError(f"{type(model).__name__} must declare communication_adapter_class") - if not isinstance(adapter_class, type) or not issubclass( - adapter_class, - ModelCommunicationAdapter, - ): - raise TypeError("model.communication_adapter_class must derive from ModelCommunicationAdapter") - return cast(type[ModelCommunicationAdapter], adapter_class)(model, device) - - -__all__ = ( - "BevInferenceInput", - "DetectionInferenceInput", - "EarlyFusionWirePayload", - "EarlyFusionCommunicationAdapter", - "FpvrcnnAgentInferenceInput", - "FpvrcnnWirePayload", - "InferenceInput", - "IntermediateFeatureCommunicationAdapter", - "IntermediateFeatureWirePayload", - "IntermediateMetadata", - "LateFusionWirePayload", - "LateFusionCommunicationAdapter", - "ModelCommunicationAdapter", - "MultiScaleFeatureCommunicationAdapter", - "MultiScaleFeatureInput", - "PointCloudInferenceInput", - "PoseFrameMetadata", - "SpatialFeatureCommunicationAdapter", - "SpatialFeatureInput", - "V2XViTMetadata", - "VoxelInferenceInput", - "Where2CommCommunicationAdapter", - "Where2CommFeatureInput", - "Where2CommWirePayload", - "build_communication_adapter", - "build_inference_input", - "inference_input_to_dict", - "merge_inference_inputs", -) diff --git a/OpenCOOD/opencood/models/communication_adapters/base.py b/OpenCOOD/opencood/models/communication_adapters/base.py deleted file mode 100644 index 137de7fa..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/base.py +++ /dev/null @@ -1,107 +0,0 @@ -"""Common interface for model-specific network communication adapters.""" - -from abc import ABC, abstractmethod -from typing import Any - -import torch -from torch import nn - - -class ModelCommunicationAdapter(ABC): - """Bridge a perception model to its sender/receiver wire contract.""" - - def __init__(self, model: nn.Module, device: torch.device) -> None: - self.model = model - self.device = device - - @abstractmethod - def prepare_transmission_payloads(self, dataset: Any, idx: int) -> None: - """ - Build and publish every sender payload for one dataset frame. - - Parameters - ---------- - dataset : Any - OpenCOOD dataset providing the local scene observations. - idx : int - Dataset frame to encode and publish. - """ - - def move_to_device(self, value: Any) -> Any: - """ - Recursively move a model input to the adapter device. - - Parameters - ---------- - value : Any - Nested tensors, mappings, or sequences forming a model input. - - Returns - ------- - Any - Input with every tensor moved to the configured device. - """ - if isinstance(value, torch.Tensor): - return value.to(self.device) - if isinstance(value, dict): - return {key: self.move_to_device(item) for key, item in value.items()} - if isinstance(value, list): - return [self.move_to_device(item) for item in value] - if isinstance(value, tuple): - return tuple(self.move_to_device(item) for item in value) - return value - - def decode_received_payload( - self, - payload: object, - receiver_pose: list[float], - ) -> dict[str, Any]: - """ - Decode one delivered model payload for receiver-side inference. - - Parameters - ---------- - payload : object - Deserialized model payload delivered over the V2X communication - channel. - receiver_pose : list[float] - Receiver pose used for model-specific coordinate conversion. - - Returns - ------- - dict[str, Any] - Model-specific remote inference input. - - Raises - ------ - NotImplementedError - If the adapter does not support receiver-side payload decoding. - """ - raise NotImplementedError(f"{type(self).__name__} does not implement receiver-side payload decoding") - - def encode_local_intermediate_input( - self, - dataset: Any, - inference_input: Any, - ) -> dict[str, Any]: - """ - Encode the receiver's local input at an intermediate model boundary. - - Parameters - ---------- - dataset : Any - Dataset providing model preprocessing facilities. - inference_input : Any - Receiver-local preprocessor output. - - Returns - ------- - dict[str, Any] - Model-specific learned feature input. - - Raises - ------ - NotImplementedError - If the adapter does not implement an intermediate feature split. - """ - raise NotImplementedError(f"{type(self).__name__} does not implement intermediate feature encoding") diff --git a/OpenCOOD/opencood/models/communication_adapters/early.py b/OpenCOOD/opencood/models/communication_adapters/early.py deleted file mode 100644 index 53c28606..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/early.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Early-fusion V2X communication adapter.""" - -from collections.abc import Callable -from typing import Any, Protocol, runtime_checkable - -import numpy as np -import numpy.typing as npt - -from opencood.models.communication_adapters.base import ModelCommunicationAdapter -from opencood.models.communication_adapters.inference_metadata import PoseFrameMetadata -from opencood.models.communication_adapters.wire_payload import EarlyFusionWirePayload -from opencood.utils import box_utils -from opencood.utils.transformation_utils import x1_to_x2 - - -@runtime_checkable -class EarlyFusionCommunicationDataset(Protocol): - """Dataset capabilities required by the early-fusion adapter.""" - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[[npt.NDArray[Any], PoseFrameMetadata], object], - ) -> None: - raise NotImplementedError - - -class EarlyFusionCommunicationAdapter(ModelCommunicationAdapter): - """Exchange local LiDAR points and project them only at the receiver.""" - - def prepare_transmission_payloads(self, dataset: Any, idx: int) -> None: - """ - Build and publish an early-fusion payload for every local agent. - - Parameters - ---------- - dataset : Any - Dataset exposing local early-fusion agent observations. - idx : int - Dataset frame to publish. - - Raises - ------ - TypeError - If the selected dataset cannot build early-fusion observations. - """ - if not isinstance(dataset, EarlyFusionCommunicationDataset): - raise TypeError("Early fusion requires a dataset capable of building local LiDAR observations") - dataset.extract_data(idx, agent_payload_builder=self._build_wire_payload) - - @staticmethod - def _build_wire_payload( - lidar_points: npt.NDArray[Any], - metadata: PoseFrameMetadata, - ) -> EarlyFusionWirePayload: - """ - Build a payload without applying a receiver-specific transformation. - - Parameters - ---------- - lidar_points : numpy.typing.NDArray[Any] - LiDAR points expressed in the sender coordinate frame. - metadata : PoseFrameMetadata - Sender pose and capture frame. - - Returns - ------- - EarlyFusionWirePayload - Local LiDAR points and the metadata needed by a receiver. - """ - return EarlyFusionWirePayload( - lidar_points=lidar_points, - metadata=metadata, - ) - - def decode_received_payload( - self, - payload: object, - receiver_pose: list[float], - ) -> dict[str, Any]: - """ - Project one sender-local point cloud into the receiver frame. - - Parameters - ---------- - payload : object - Deserialized early-fusion payload delivered over V2X. - receiver_pose : list[float] - Receiver LiDAR pose for the current inference frame. - - Returns - ------- - dict[str, Any] - Point cloud expressed in the receiver coordinate frame. - - Raises - ------ - TypeError - If a payload belonging to another contract is received. - """ - if not isinstance(payload, EarlyFusionWirePayload): - raise TypeError(f"Expected EarlyFusionWirePayload, got {type(payload).__name__}") - - transformation_matrix = x1_to_x2(payload.metadata.lidar_pose, receiver_pose) - projected_lidar = np.array(payload.lidar_points, copy=True) - if len(projected_lidar) > 0: - projected_lidar[:, :3] = box_utils.project_points_by_matrix_torch( - projected_lidar[:, :3], - transformation_matrix, - ) - return {"projected_lidar": projected_lidar} diff --git a/OpenCOOD/opencood/models/communication_adapters/fpvrcnn.py b/OpenCOOD/opencood/models/communication_adapters/fpvrcnn.py deleted file mode 100644 index d45578ae..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/fpvrcnn.py +++ /dev/null @@ -1,202 +0,0 @@ -"""Communication adapter for distributed FPV-RCNN inference.""" - -from collections.abc import Callable, Mapping -from typing import Any, Protocol, runtime_checkable - -import numpy as np -import numpy.typing as npt -import torch - -from opencood.models.communication_adapters.base import ModelCommunicationAdapter -from opencood.models.communication_adapters.inference_input import ( - FpvrcnnAgentInferenceInput, -) -from opencood.models.communication_adapters.inference_metadata import PoseFrameMetadata -from opencood.models.communication_adapters.wire_payload import FpvrcnnWirePayload -from opencood.utils import box_utils -from opencood.utils.transformation_utils import x1_to_x2 - - -@runtime_checkable -class FpvrcnnCommunicationDataset(Protocol): - """Dataset capabilities required by the FPV-RCNN adapter.""" - - pre_processor: Any - post_processor: Any - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[[dict[str, Any], PoseFrameMetadata], object] | None, - ) -> None: - raise NotImplementedError - - -class FpvrcnnCommunicationAdapter(ModelCommunicationAdapter): - """Run FPV-RCNN stage one and VSA before serializing an agent payload.""" - - def prepare_transmission_payloads(self, dataset: Any, idx: int) -> None: - """ - Encode and publish FPV-RCNN outputs for every local agent. - - Parameters - ---------- - dataset : Any - Dataset exposing the FPV-RCNN agent-sample capabilities. - idx : int - Dataset frame to encode and publish. - - Raises - ------ - TypeError - If the selected dataset cannot prepare FPV-RCNN agent samples. - """ - if not isinstance(dataset, FpvrcnnCommunicationDataset): - raise TypeError("FPV-RCNN communication requires a dataset capable of building local agent observations") - dataset.extract_data( - idx, - agent_payload_builder=lambda sample, metadata: self._build_wire_payload( - dataset, - sample, - metadata, - ), - ) - - def _build_wire_payload( - self, - dataset: FpvrcnnCommunicationDataset, - sample: dict[str, Any], - metadata: PoseFrameMetadata, - ) -> FpvrcnnWirePayload: - """ - Run sender-side FPV-RCNN and build its typed wire payload. - - Parameters - ---------- - dataset : FpvrcnnCommunicationDataset - Dataset providing the FPV-RCNN preprocessor and anchors. - sample : dict[str, Any] - Local sender model input without supervision fields. - metadata : PoseFrameMetadata - Sender pose and feature capture frame. - - Returns - ------- - FpvrcnnWirePayload - Typed payload containing proposals and VSA keypoint features. - - Raises - ------ - TypeError - If the model output is not the expected tensor mapping. - """ - encode_agent = getattr(self.model, "encode_agent", None) - if encode_agent is None: - raise TypeError("FpvrcnnCommunicationAdapter requires a model with encode_agent()") - - processed_lidar = dataset.pre_processor.collate_batch([sample["processed_features"]]) - origin_lidar = np.pad(sample["projected_lidar"], ((0, 0), (1, 0)), mode="constant") - batch = self.move_to_device( - { - "processed_lidar": processed_lidar, - "origin_lidar": torch.from_numpy(origin_lidar), - "anchor_box": torch.from_numpy(np.asarray(dataset.post_processor.generate_anchor_box())), - "record_len": torch.ones(1, dtype=torch.long), - } - ) - self.model.eval() - with torch.no_grad(): - output = encode_agent(batch) - if not isinstance(output, Mapping): - raise TypeError("FPV-RCNN encode_agent() must return a tensor mapping") - - return FpvrcnnWirePayload( - inference_input=FpvrcnnAgentInferenceInput( - proposals=self._tensor_to_numpy(output, "proposals"), - proposal_scores=self._tensor_to_numpy(output, "proposal_scores"), - point_coords=self._tensor_to_numpy(output, "point_coords"), - point_features=self._tensor_to_numpy(output, "point_features"), - ), - metadata=metadata, - ) - - def decode_received_payload( - self, - payload: object, - receiver_pose: list[float], - ) -> dict[str, Any]: - """ - Decode and project one FPV-RCNN payload into the receiver frame. - - Parameters - ---------- - payload : object - Deserialized FPV-RCNN payload delivered over V2X. - receiver_pose : list[float] - Receiver LiDAR pose for the current inference frame. - - Returns - ------- - dict[str, Any] - Remote proposals and VSA keypoint data in the receiver frame. - - Raises - ------ - TypeError - If a payload belonging to another model contract is received. - """ - if not isinstance(payload, FpvrcnnWirePayload): - raise TypeError(f"Expected FpvrcnnWirePayload, got {type(payload).__name__}") - - transformation_matrix = x1_to_x2(payload.metadata.lidar_pose, receiver_pose) - point_coords = np.array(payload.inference_input.point_coords, copy=True) - if len(point_coords) > 0: - point_coords[:, :3] = box_utils.project_points_by_matrix_torch( - point_coords[:, :3], - transformation_matrix, - ) - - proposals = np.array(payload.inference_input.proposals, copy=True) - if len(proposals) > 0: - proposals[:, :3] = box_utils.project_points_by_matrix_torch( - proposals[:, :3], - transformation_matrix, - ) - proposals[:, 6] += np.arctan2( - transformation_matrix[1, 0], - transformation_matrix[0, 0], - ) - - return { - "proposals": proposals, - "proposal_scores": np.array(payload.inference_input.proposal_scores, copy=True), - "point_coords": point_coords, - "point_features": np.array(payload.inference_input.point_features, copy=True), - } - - @staticmethod - def _tensor_to_numpy(output: Mapping[str, Any], field: str) -> npt.NDArray[Any]: - """ - Detach one required model output as a NumPy array. - - Parameters - ---------- - output : collections.abc.Mapping[str, Any] - Sender-side model output. - field : str - Required tensor field to convert. - - Returns - ------- - numpy.typing.NDArray[Any] - Detached CPU representation of the tensor. - - Raises - ------ - TypeError - If the required field is not a tensor. - """ - value = output.get(field) - if not isinstance(value, torch.Tensor): - raise TypeError(f'FPV-RCNN output field "{field}" must be a tensor') - return value.detach().cpu().numpy() diff --git a/OpenCOOD/opencood/models/communication_adapters/inference_input.py b/OpenCOOD/opencood/models/communication_adapters/inference_input.py deleted file mode 100644 index 22e3962b..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/inference_input.py +++ /dev/null @@ -1,253 +0,0 @@ -"""Typed model inputs exchanged through communication adapters.""" - -from dataclasses import dataclass -from typing import Any, Mapping, TypeAlias - -import numpy.typing as npt - - -VOXEL_INPUT_FIELDS = frozenset({"voxel_features", "voxel_coords"}) -SPARSE_VOXEL_INPUT_FIELDS = VOXEL_INPUT_FIELDS | {"voxel_num_points"} -BEV_INPUT_FIELDS = frozenset({"bev_input"}) -POINT_CLOUD_INPUT_FIELDS = frozenset({"downsample_lidar"}) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class VoxelInferenceInput: - """ - Voxelized LiDAR input. - - Attributes - ---------- - voxel_features : numpy.typing.NDArray[Any] - Features stored for every non-empty voxel. - voxel_coords : numpy.typing.NDArray[Any] - Coordinates of the non-empty voxels. - voxel_num_points : numpy.typing.NDArray[Any] | None - Number of points in every voxel when supplied by the preprocessor. - """ - - voxel_features: npt.NDArray[Any] - voxel_coords: npt.NDArray[Any] - voxel_num_points: npt.NDArray[Any] | None = None - - -@dataclass(frozen=True, slots=True, kw_only=True) -class BevInferenceInput: - """ - Bird's-eye-view model input. - - Attributes - ---------- - bev_input : numpy.typing.NDArray[Any] - Dense BEV feature map produced by the preprocessor. - """ - - bev_input: npt.NDArray[Any] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class PointCloudInferenceInput: - """ - Point-cloud model input. - - Attributes - ---------- - points : numpy.typing.NDArray[Any] - Sampled LiDAR points produced by the preprocessor. - """ - - points: npt.NDArray[Any] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class FpvrcnnAgentInferenceInput: - """ - Agent-side FPV-RCNN output consumed by cooperative stage two. - - Attributes - ---------- - proposals : numpy.typing.NDArray[Any] - Stage-one bounding-box proposals in the sender coordinate frame. - proposal_scores : numpy.typing.NDArray[Any] - Confidence scores corresponding to ``proposals``. - point_coords : numpy.typing.NDArray[Any] - Coordinates of VSA keypoints in the sender coordinate frame. - point_features : numpy.typing.NDArray[Any] - VSA features corresponding to ``point_coords``. - """ - - proposals: npt.NDArray[Any] - proposal_scores: npt.NDArray[Any] - point_coords: npt.NDArray[Any] - point_features: npt.NDArray[Any] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class DetectionInferenceInput: - """ - Detection candidates produced by one late-fusion sender. - - Attributes - ---------- - boxes : numpy.typing.NDArray[Any] - Bounding-box corners expressed in the sender coordinate frame. - scores : numpy.typing.NDArray[Any] - Confidence scores corresponding to ``boxes``. - """ - - boxes: npt.NDArray[Any] - scores: npt.NDArray[Any] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class Where2CommFeatureInput: - """ - Learned feature data selected by a Where2Comm sender. - - Attributes - ---------- - spatial_features : numpy.typing.NDArray[Any] - Sender-side feature representation after the communication mask and - optional learned compression encoder. - communication_rate : float - Fraction of spatial locations selected for transmission. - """ - - spatial_features: npt.NDArray[Any] - communication_rate: float - - -@dataclass(frozen=True, slots=True, kw_only=True) -class SpatialFeatureInput: - """ - Single learned spatial feature map produced by an agent encoder. - - Attributes - ---------- - spatial_features : numpy.typing.NDArray[Any] - Feature map including its single-agent batch dimension. - """ - - spatial_features: npt.NDArray[Any] - - -@dataclass(frozen=True, slots=True, kw_only=True) -class MultiScaleFeatureInput: - """ - Learned feature maps produced at multiple encoder scales. - - Attributes - ---------- - feature_maps : tuple[numpy.typing.NDArray[Any], ...] - Ordered feature maps including their single-agent batch dimensions. - """ - - feature_maps: tuple[npt.NDArray[Any], ...] - - -InferenceInput: TypeAlias = VoxelInferenceInput | BevInferenceInput | PointCloudInferenceInput - - -def build_inference_input(preprocessor_output: Mapping[str, Any]) -> InferenceInput: - """ - Convert a preprocessor result into a typed inference input. - - The concrete type is selected from the fields actually returned by the - preprocessor. Dataset and transport code therefore remain independent of - the configured preprocessor class. - - Parameters - ---------- - preprocessor_output : collections.abc.Mapping[str, Any] - Result returned by ``preprocessor.preprocess()``. - - Returns - ------- - InferenceInput - Typed representation of the preprocessor result. - - Raises - ------ - ValueError - If the returned field set does not match a supported input type. - """ - fields = frozenset(preprocessor_output) - - if fields == VOXEL_INPUT_FIELDS or fields == SPARSE_VOXEL_INPUT_FIELDS: - return VoxelInferenceInput( - voxel_features=preprocessor_output["voxel_features"], - voxel_coords=preprocessor_output["voxel_coords"], - voxel_num_points=preprocessor_output.get("voxel_num_points"), - ) - if fields == BEV_INPUT_FIELDS: - return BevInferenceInput(bev_input=preprocessor_output["bev_input"]) - if fields == POINT_CLOUD_INPUT_FIELDS: - return PointCloudInferenceInput(points=preprocessor_output["downsample_lidar"]) - - raise ValueError(f"Unsupported preprocessor output fields: {sorted(fields)}") - - -def inference_input_to_dict(inference_input: InferenceInput) -> dict[str, npt.NDArray[Any]]: - """ - Convert a typed inference input to the preprocessor collate format. - - Parameters - ---------- - inference_input : InferenceInput - Typed model input. - - Returns - ------- - dict[str, numpy.typing.NDArray[Any]] - Mapping accepted by the corresponding preprocessor's collate path. - """ - if isinstance(inference_input, VoxelInferenceInput): - output = { - "voxel_features": inference_input.voxel_features, - "voxel_coords": inference_input.voxel_coords, - } - if inference_input.voxel_num_points is not None: - output["voxel_num_points"] = inference_input.voxel_num_points - return output - if isinstance(inference_input, BevInferenceInput): - return {"bev_input": inference_input.bev_input} - if isinstance(inference_input, PointCloudInferenceInput): - return {"downsample_lidar": inference_input.points} - raise TypeError(f"Unsupported inference input type: {type(inference_input).__name__}") - - -def merge_inference_inputs(inference_inputs: list[InferenceInput]) -> dict[str, list[npt.NDArray[Any]]]: - """ - Merge homogeneous agent inputs for preprocessor collation. - - Parameters - ---------- - inference_inputs : list[InferenceInput] - Inputs for the agents participating in the current inference. - - Returns - ------- - dict[str, list[numpy.typing.NDArray[Any]]] - Dict-of-lists accepted by ``preprocessor.collate_batch()``. - - Raises - ------ - ValueError - If the input list is empty. - TypeError - If agents provide different input representations. - """ - if not inference_inputs: - raise ValueError("At least one inference input is required") - - input_type = type(inference_inputs[0]) - if any(type(inference_input) is not input_type for inference_input in inference_inputs[1:]): - raise TypeError("All agents must use the same inference input representation") - - input_dicts = [inference_input_to_dict(inference_input) for inference_input in inference_inputs] - input_fields = set(input_dicts[0]) - if any(set(input_dict) != input_fields for input_dict in input_dicts[1:]): - raise TypeError("All agents must provide the same inference input fields") - - return {field: [input_dict[field] for input_dict in input_dicts] for field in input_dicts[0]} diff --git a/OpenCOOD/opencood/models/communication_adapters/inference_metadata.py b/OpenCOOD/opencood/models/communication_adapters/inference_metadata.py deleted file mode 100644 index b351b37e..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/inference_metadata.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Typed metadata exchanged through communication adapters.""" - -from dataclasses import dataclass -from typing import Any, TypeAlias - -import numpy.typing as npt - - -@dataclass(frozen=True, slots=True, kw_only=True) -class PoseFrameMetadata: - """ - Pose and capture frame used to derive pairwise transformations. - - Attributes - ---------- - lidar_pose : list[float] | tuple[float, ...] - Agent LiDAR pose at feature capture time. - capture_frame : int - Dataset frame in which the model input was captured. - """ - - lidar_pose: list[float] | tuple[float, ...] - capture_frame: int - - -@dataclass(frozen=True, slots=True, kw_only=True) -class V2XViTMetadata: - """ - Metadata consumed by the V2X-ViT fusion model. - - Attributes - ---------- - velocity : float - Sender velocity normalized by 30 km/h. - agent_type : int - ``1`` for an infrastructure agent and ``0`` for a vehicle. - capture_frame : int - Dataset frame in which the model input was captured. - spatial_correction_matrix : numpy.typing.NDArray[Any] - Transformation correcting the captured feature to the receive frame. - """ - - velocity: float - agent_type: int - capture_frame: int - spatial_correction_matrix: npt.NDArray[Any] - - -IntermediateMetadata: TypeAlias = PoseFrameMetadata | V2XViTMetadata diff --git a/OpenCOOD/opencood/models/communication_adapters/intermediate.py b/OpenCOOD/opencood/models/communication_adapters/intermediate.py deleted file mode 100644 index e2a6adbb..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/intermediate.py +++ /dev/null @@ -1,261 +0,0 @@ -"""Reusable adapters for learned intermediate feature maps.""" - -from collections.abc import Callable, Mapping -from typing import Any, Protocol, runtime_checkable - -import numpy as np -import torch - -from opencood.models.communication_adapters.base import ModelCommunicationAdapter -from opencood.models.communication_adapters.inference_input import ( - InferenceInput, - MultiScaleFeatureInput, - SpatialFeatureInput, - inference_input_to_dict, -) -from opencood.models.communication_adapters.inference_metadata import IntermediateMetadata -from opencood.models.communication_adapters.wire_payload import IntermediateFeatureWirePayload - - -@runtime_checkable -class IntermediateCommunicationDataset(Protocol): - """Dataset capabilities required by learned-feature adapters.""" - - pre_processor: Any - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[ - [InferenceInput, IntermediateMetadata | None], - object, - ], - ) -> None: - raise NotImplementedError - - -class IntermediateFeatureCommunicationAdapter(ModelCommunicationAdapter): - """Base adapter for models exposing an ``encode_agent`` split.""" - - def prepare_transmission_payloads(self, dataset: Any, idx: int) -> None: - """ - Encode and publish learned features for every local agent. - - Parameters - ---------- - dataset : Any - Intermediate dataset exposing individual agent inputs. - idx : int - Dataset frame to publish. - """ - communication_dataset = self._require_dataset(dataset) - communication_dataset.extract_data( - idx, - agent_payload_builder=lambda inference_input, metadata: self._build_wire_payload( - communication_dataset, - inference_input, - metadata, - ), - ) - - @staticmethod - def _require_dataset(dataset: Any) -> IntermediateCommunicationDataset: - if not isinstance(dataset, IntermediateCommunicationDataset): - raise TypeError("Intermediate feature encoding requires an intermediate communication dataset") - return dataset - - def _encode( - self, - dataset: IntermediateCommunicationDataset, - inference_input: InferenceInput, - ) -> Mapping[str, Any]: - encode_agent = getattr(self.model, "encode_agent", None) - if encode_agent is None: - raise TypeError(f"{type(self).__name__} requires a model with encode_agent()") - model_input = self.move_to_device( - { - "processed_lidar": dataset.pre_processor.collate_batch([inference_input_to_dict(inference_input)]), - "batch_size": 1, - } - ) - self.model.eval() - with torch.no_grad(): - output = encode_agent(model_input) - if not isinstance(output, Mapping): - raise TypeError("encode_agent() must return a tensor mapping") - return output - - def _build_wire_payload( - self, - dataset: IntermediateCommunicationDataset, - inference_input: InferenceInput, - metadata: IntermediateMetadata | None, - ) -> IntermediateFeatureWirePayload: - encoded = self._encode(dataset, inference_input) - return IntermediateFeatureWirePayload( - inference_input=self._serialize_features(encoded), - metadata=metadata, - ) - - def encode_local_intermediate_input( - self, - dataset: Any, - inference_input: Any, - ) -> dict[str, Any]: - """ - Encode the receiver's own observation at the model boundary. - - Parameters - ---------- - dataset : Any - Intermediate dataset providing preprocessing. - inference_input : Any - Receiver-local typed preprocessor output. - - Returns - ------- - dict[str, Any] - Learned feature maps in the receiver assembly format. - """ - communication_dataset = self._require_dataset(dataset) - encoded = self._encode(communication_dataset, inference_input) - return self._deserialize_features(self._serialize_features(encoded)) - - def decode_received_payload( - self, - payload: object, - receiver_pose: list[float], - ) -> dict[str, Any]: - """ - Decode one successfully delivered intermediate feature payload. - - Parameters - ---------- - payload : object - Deserialized model payload delivered over V2X. - receiver_pose : list[float] - Receiver pose reserved for model-side alignment. - - Returns - ------- - dict[str, Any] - Learned features and their sender metadata. - """ - del receiver_pose - if not isinstance(payload, IntermediateFeatureWirePayload): - raise TypeError(f"Expected IntermediateFeatureWirePayload, got {type(payload).__name__}") - decoded = self._deserialize_features(payload.inference_input) - decoded["metadata"] = payload.metadata - return decoded - - def _serialize_features( - self, - encoded: Mapping[str, Any], - ) -> SpatialFeatureInput | MultiScaleFeatureInput: - raise NotImplementedError - - def _deserialize_features( - self, - inference_input: SpatialFeatureInput | MultiScaleFeatureInput, - ) -> dict[str, Any]: - raise NotImplementedError - - -class SpatialFeatureCommunicationAdapter(IntermediateFeatureCommunicationAdapter): - """Exchange one dense learned feature map per agent.""" - - def _serialize_features( - self, - encoded: Mapping[str, Any], - ) -> SpatialFeatureInput: - """ - Move one encoded spatial feature map to the wire representation. - - Parameters - ---------- - encoded : collections.abc.Mapping[str, Any] - Model output containing ``spatial_features``. - - Returns - ------- - SpatialFeatureInput - CPU NumPy representation of the learned feature map. - """ - spatial_features = encoded.get("spatial_features") - if not isinstance(spatial_features, torch.Tensor): - raise TypeError('encode_agent() field "spatial_features" must be a tensor') - return SpatialFeatureInput( - spatial_features=spatial_features.detach().cpu().numpy(), - ) - - def _deserialize_features( - self, - inference_input: SpatialFeatureInput | MultiScaleFeatureInput, - ) -> dict[str, Any]: - """ - Restore one spatial feature map for receiver-side collation. - - Parameters - ---------- - inference_input : SpatialFeatureInput | MultiScaleFeatureInput - Typed feature representation received over V2X. - - Returns - ------- - dict[str, Any] - Receiver assembly mapping containing ``spatial_features``. - """ - if not isinstance(inference_input, SpatialFeatureInput): - raise TypeError(f"Expected SpatialFeatureInput, got {type(inference_input).__name__}") - return { - "spatial_features": np.array(inference_input.spatial_features, copy=True), - } - - -class MultiScaleFeatureCommunicationAdapter(IntermediateFeatureCommunicationAdapter): - """Exchange an ordered tuple of learned feature scales per agent.""" - - def _serialize_features( - self, - encoded: Mapping[str, Any], - ) -> MultiScaleFeatureInput: - """ - Move ordered model feature scales to the wire representation. - - Parameters - ---------- - encoded : collections.abc.Mapping[str, Any] - Model output containing ``feature_maps``. - - Returns - ------- - MultiScaleFeatureInput - CPU NumPy representations of all learned feature scales. - """ - feature_maps = encoded.get("feature_maps") - if not isinstance(feature_maps, (list, tuple)) or not all(isinstance(feature_map, torch.Tensor) for feature_map in feature_maps): - raise TypeError('encode_agent() field "feature_maps" must be a tensor sequence') - return MultiScaleFeatureInput( - feature_maps=tuple(feature_map.detach().cpu().numpy() for feature_map in feature_maps), - ) - - def _deserialize_features( - self, - inference_input: SpatialFeatureInput | MultiScaleFeatureInput, - ) -> dict[str, Any]: - """ - Restore ordered feature scales for receiver-side collation. - - Parameters - ---------- - inference_input : SpatialFeatureInput | MultiScaleFeatureInput - Typed feature representation received over V2X. - - Returns - ------- - dict[str, Any] - Receiver assembly mapping keyed by scale index. - """ - if not isinstance(inference_input, MultiScaleFeatureInput): - raise TypeError(f"Expected MultiScaleFeatureInput, got {type(inference_input).__name__}") - return {f"feature_{index}": np.array(feature_map, copy=True) for index, feature_map in enumerate(inference_input.feature_maps)} diff --git a/OpenCOOD/opencood/models/communication_adapters/late.py b/OpenCOOD/opencood/models/communication_adapters/late.py deleted file mode 100644 index e065b762..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/late.py +++ /dev/null @@ -1,170 +0,0 @@ -"""Late-fusion V2X communication adapter.""" - -from collections.abc import Callable, Mapping -from typing import Any, Protocol, runtime_checkable - -import numpy as np -import torch - -from opencood.models.communication_adapters.base import ModelCommunicationAdapter -from opencood.models.communication_adapters.inference_input import ( - DetectionInferenceInput, - InferenceInput, - inference_input_to_dict, -) -from opencood.models.communication_adapters.inference_metadata import PoseFrameMetadata -from opencood.models.communication_adapters.wire_payload import LateFusionWirePayload -from opencood.utils import box_utils -from opencood.utils.transformation_utils import x1_to_x2 - - -@runtime_checkable -class LateFusionCommunicationDataset(Protocol): - """Dataset capabilities required by the late-fusion adapter.""" - - pre_processor: Any - post_processor: Any - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[[InferenceInput, PoseFrameMetadata], object], - ) -> None: - raise NotImplementedError - - -class LateFusionCommunicationAdapter(ModelCommunicationAdapter): - """Run a complete detector locally and exchange detection candidates.""" - - def prepare_transmission_payloads(self, dataset: Any, idx: int) -> None: - """ - Run sender-side late-fusion inference for every local agent. - - Parameters - ---------- - dataset : Any - Dataset exposing local detector inputs and post-processing. - idx : int - Dataset frame to publish. - - Raises - ------ - TypeError - If the selected dataset lacks late-fusion capabilities. - """ - if not isinstance(dataset, LateFusionCommunicationDataset): - raise TypeError("Late fusion requires a dataset capable of building local detector inputs") - dataset.extract_data( - idx, - agent_payload_builder=lambda inference_input, metadata: self._build_wire_payload( - dataset, - inference_input, - metadata, - ), - ) - - def _build_wire_payload( - self, - dataset: LateFusionCommunicationDataset, - inference_input: InferenceInput, - metadata: PoseFrameMetadata, - ) -> LateFusionWirePayload: - """ - Run the local detector and serialize its decoded candidates. - - Parameters - ---------- - dataset : LateFusionCommunicationDataset - Dataset providing pre- and post-processing implementations. - inference_input : InferenceInput - Preprocessed input belonging only to the sender. - metadata : PoseFrameMetadata - Sender pose and capture frame. - - Returns - ------- - LateFusionWirePayload - Decoded sender-frame boxes and their confidence scores. - - Raises - ------ - TypeError - If the model output or postprocessor result is invalid. - """ - processed_lidar = dataset.pre_processor.collate_batch([inference_input_to_dict(inference_input)]) - anchor_box = dataset.post_processor.generate_anchor_box() - model_input = self.move_to_device( - { - "processed_lidar": processed_lidar, - "batch_size": 1, - "anchor_box": (None if anchor_box is None else torch.from_numpy(np.asarray(anchor_box))), - } - ) - - self.model.eval() - with torch.no_grad(): - output = self.model(model_input) - if not isinstance(output, Mapping): - raise TypeError("Late-fusion model must return a tensor mapping") - - decode_predictions = getattr(dataset.post_processor, "decode_agent_predictions", None) - if decode_predictions is None: - raise TypeError("Late-fusion postprocessor must implement decode_agent_predictions()") - boxes, scores = decode_predictions(model_input, output) - if not isinstance(boxes, torch.Tensor) or not isinstance(scores, torch.Tensor): - raise TypeError("decode_agent_predictions() must return box and score tensors") - - return LateFusionWirePayload( - detections=DetectionInferenceInput( - boxes=boxes.detach().cpu().numpy(), - scores=scores.detach().cpu().numpy(), - ), - metadata=metadata, - ) - - def decode_received_payload( - self, - payload: object, - receiver_pose: list[float], - ) -> dict[str, Any]: - """ - Project one sender's detections into the receiver frame. - - Parameters - ---------- - payload : object - Deserialized late-fusion payload delivered over V2X. - receiver_pose : list[float] - Receiver LiDAR pose for the current inference frame. - - Returns - ------- - dict[str, Any] - Detection candidates expressed in the receiver frame. - - Raises - ------ - TypeError - If the payload or box representation is unsupported. - """ - if not isinstance(payload, LateFusionWirePayload): - raise TypeError(f"Expected LateFusionWirePayload, got {type(payload).__name__}") - - boxes = np.array(payload.detections.boxes, copy=True) - transformation_matrix = x1_to_x2(payload.metadata.lidar_pose, receiver_pose) - if boxes.ndim != 3 or boxes.shape[-2:] not in {(4, 2), (8, 3)}: - raise TypeError(f"Unsupported late-fusion box shape: {boxes.shape}") - if boxes.shape[0] > 0: - if boxes.shape[-1] == 3: - boxes = box_utils.project_box3d(boxes, transformation_matrix) - else: - box3d = np.pad(boxes, ((0, 0), (0, 0), (0, 1))) - boxes = box_utils.project_points_by_matrix_torch( - box3d.reshape(-1, 3), - transformation_matrix, - )[:, :2].reshape(-1, 4, 2) - - return { - "pred_box_tensor": boxes, - "pred_score": np.array(payload.detections.scores, copy=True), - } diff --git a/OpenCOOD/opencood/models/communication_adapters/where2comm.py b/OpenCOOD/opencood/models/communication_adapters/where2comm.py deleted file mode 100644 index 23c849ad..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/where2comm.py +++ /dev/null @@ -1,226 +0,0 @@ -"""Where2Comm intermediate-feature communication adapter.""" - -from collections.abc import Callable, Mapping -from typing import Any, Protocol, runtime_checkable - -import numpy as np -import torch - -from opencood.models.communication_adapters.base import ModelCommunicationAdapter -from opencood.models.communication_adapters.inference_input import ( - InferenceInput, - Where2CommFeatureInput, - inference_input_to_dict, -) -from opencood.models.communication_adapters.inference_metadata import PoseFrameMetadata -from opencood.models.communication_adapters.wire_payload import Where2CommWirePayload - - -@runtime_checkable -class Where2CommCommunicationDataset(Protocol): - """Dataset capabilities required by the Where2Comm adapter.""" - - pre_processor: Any - - def extract_data( - self, - idx: int, - agent_payload_builder: Callable[ - [InferenceInput, PoseFrameMetadata], - object, - ], - ) -> None: - raise NotImplementedError - - -class Where2CommCommunicationAdapter(ModelCommunicationAdapter): - """Execute the private Where2Comm encoder before V2X transmission.""" - - def prepare_transmission_payloads(self, dataset: Any, idx: int) -> None: - """ - Encode and publish learned features for every local agent. - - Parameters - ---------- - dataset : Any - Dataset exposing individual intermediate-fusion inputs. - idx : int - Dataset frame to publish. - - Raises - ------ - TypeError - If the selected dataset lacks the required capabilities. - """ - if not isinstance(dataset, Where2CommCommunicationDataset): - raise TypeError("Where2Comm requires an intermediate-fusion communication dataset") - dataset.extract_data( - idx, - agent_payload_builder=lambda inference_input, metadata: self._build_wire_payload( - dataset, - inference_input, - metadata, - ), - ) - - def _encode( - self, - dataset: Where2CommCommunicationDataset, - inference_input: InferenceInput, - *, - apply_communication_mask: bool, - ) -> tuple[np.ndarray, float]: - """ - Run the sender-private model stages for one agent. - - Parameters - ---------- - dataset : Where2CommCommunicationDataset - Dataset providing the configured preprocessor. - inference_input : InferenceInput - One agent's preprocessed LiDAR input. - apply_communication_mask : bool - Whether to apply the over-the-air feature selection mask. - - Returns - ------- - tuple[numpy.ndarray, float] - Learned feature map and selected-location ratio. - - Raises - ------ - TypeError - If the model does not expose the Where2Comm encoder contract. - """ - encode_agent = getattr(self.model, "encode_agent", None) - if encode_agent is None: - raise TypeError("Where2CommCommunicationAdapter requires a model with encode_agent()") - - model_input = self.move_to_device( - { - "processed_lidar": dataset.pre_processor.collate_batch([inference_input_to_dict(inference_input)]), - } - ) - self.model.eval() - with torch.no_grad(): - output = encode_agent( - model_input, - apply_communication_mask=apply_communication_mask, - ) - if not isinstance(output, Mapping): - raise TypeError("Where2Comm encode_agent() must return a tensor mapping") - spatial_features = output.get("spatial_features") - communication_rate = output.get("communication_rate") - if not isinstance(spatial_features, torch.Tensor): - raise TypeError('Where2Comm output field "spatial_features" must be a tensor') - if not isinstance(communication_rate, torch.Tensor): - raise TypeError('Where2Comm output field "communication_rate" must be a tensor') - return ( - spatial_features.detach().cpu().numpy(), - float(communication_rate.detach().cpu().item()), - ) - - def _build_wire_payload( - self, - dataset: Where2CommCommunicationDataset, - inference_input: InferenceInput, - metadata: PoseFrameMetadata, - ) -> Where2CommWirePayload: - """ - Build one sender's learned-feature payload. - - Parameters - ---------- - dataset : Where2CommCommunicationDataset - Dataset providing the configured preprocessor. - inference_input : InferenceInput - Sender-local preprocessor output. - metadata : PoseFrameMetadata - Sender pose and capture frame. - - Returns - ------- - Where2CommWirePayload - Masked learned features and sender metadata. - """ - spatial_features, communication_rate = self._encode( - dataset, - inference_input, - apply_communication_mask=True, - ) - return Where2CommWirePayload( - inference_input=Where2CommFeatureInput( - spatial_features=spatial_features, - communication_rate=communication_rate, - ), - metadata=metadata, - ) - - def encode_local_intermediate_input( - self, - dataset: Any, - inference_input: Any, - ) -> dict[str, Any]: - """ - Encode the receiver's own feature without an over-the-air mask. - - Parameters - ---------- - dataset : Any - Intermediate-fusion dataset providing the preprocessor. - inference_input : Any - Receiver-local typed preprocessor output. - - Returns - ------- - dict[str, Any] - Receiver-local learned feature input. - """ - if not isinstance(dataset, Where2CommCommunicationDataset): - raise TypeError("Where2Comm requires an intermediate-fusion communication dataset") - spatial_features, communication_rate = self._encode( - dataset, - inference_input, - apply_communication_mask=False, - ) - return { - "spatial_features": spatial_features, - "communication_rate": communication_rate, - } - - def decode_received_payload( - self, - payload: object, - receiver_pose: list[float], - ) -> dict[str, Any]: - """ - Decode one successfully delivered Where2Comm feature payload. - - Parameters - ---------- - payload : object - Deserialized Where2Comm payload delivered over V2X. - receiver_pose : list[float] - Receiver pose reserved for later receiver-side feature alignment. - - Returns - ------- - dict[str, Any] - Learned feature map and communication rate. - - Raises - ------ - TypeError - If a payload belonging to another contract is received. - """ - del receiver_pose - if not isinstance(payload, Where2CommWirePayload): - raise TypeError(f"Expected Where2CommWirePayload, got {type(payload).__name__}") - return { - "spatial_features": np.array( - payload.inference_input.spatial_features, - copy=True, - ), - "communication_rate": payload.inference_input.communication_rate, - "metadata": payload.metadata, - } diff --git a/OpenCOOD/opencood/models/communication_adapters/wire_payload.py b/OpenCOOD/opencood/models/communication_adapters/wire_payload.py deleted file mode 100644 index 56ee71e9..00000000 --- a/OpenCOOD/opencood/models/communication_adapters/wire_payload.py +++ /dev/null @@ -1,104 +0,0 @@ -"""Typed cooperative perception payloads exchanged over V2X.""" - -from dataclasses import dataclass -from typing import Any - -import numpy.typing as npt - -from opencood.models.communication_adapters.inference_input import ( - DetectionInferenceInput, - FpvrcnnAgentInferenceInput, - MultiScaleFeatureInput, - SpatialFeatureInput, - Where2CommFeatureInput, -) -from opencood.models.communication_adapters.inference_metadata import ( - IntermediateMetadata, - PoseFrameMetadata, -) - - -@dataclass(frozen=True, slots=True, kw_only=True) -class EarlyFusionWirePayload: - """ - Sender-local LiDAR data exchanged for early fusion. - - Attributes - ---------- - lidar_points : numpy.typing.NDArray[Any] - LiDAR points expressed in the sender coordinate frame. - metadata : PoseFrameMetadata - Sender pose and capture frame used for receiver-side projection. - """ - - lidar_points: npt.NDArray[Any] - metadata: PoseFrameMetadata - - -@dataclass(frozen=True, slots=True, kw_only=True) -class LateFusionWirePayload: - """ - Sender-side detections exchanged for late fusion. - - Attributes - ---------- - detections : DetectionInferenceInput - Bounding-box candidates and confidence scores produced by the sender. - metadata : PoseFrameMetadata - Pose and capture frame required for late-fusion post-processing. - """ - - detections: DetectionInferenceInput - metadata: PoseFrameMetadata - - -@dataclass(frozen=True, slots=True, kw_only=True) -class FpvrcnnWirePayload: - """ - Agent-side FPV-RCNN output exchanged over V2X. - - Attributes - ---------- - inference_input : FpvrcnnAgentInferenceInput - Stage-one proposals and VSA keypoint features computed by the sender. - metadata : PoseFrameMetadata - Sender pose and capture frame used to transform the output at the - receiver. - """ - - inference_input: FpvrcnnAgentInferenceInput - metadata: PoseFrameMetadata - - -@dataclass(frozen=True, slots=True, kw_only=True) -class Where2CommWirePayload: - """ - Sender-side learned features exchanged by Where2Comm. - - Attributes - ---------- - inference_input : Where2CommFeatureInput - Masked feature map and its effective communication rate. - metadata : PoseFrameMetadata - Sender pose and capture frame used by the fusion pipeline. - """ - - inference_input: Where2CommFeatureInput - metadata: PoseFrameMetadata - - -@dataclass(frozen=True, slots=True, kw_only=True) -class IntermediateFeatureWirePayload: - """ - Learned features exchanged by an intermediate-fusion model. - - Attributes - ---------- - inference_input : SpatialFeatureInput | MultiScaleFeatureInput - Model-specific learned features produced by the sender. - metadata : IntermediateMetadata | None - Metadata required by the receiver-side fusion implementation. - """ - - inference_input: SpatialFeatureInput | MultiScaleFeatureInput - metadata: IntermediateMetadata | None diff --git a/OpenCOOD/opencood/models/fpvrcnn.py b/OpenCOOD/opencood/models/fpvrcnn.py deleted file mode 100644 index f3e71b3c..00000000 --- a/OpenCOOD/opencood/models/fpvrcnn.py +++ /dev/null @@ -1,230 +0,0 @@ -import numpy as np -import torch -from torch import nn - -from opencood.models.communication_adapters.fpvrcnn import FpvrcnnCommunicationAdapter -from opencood.models.sub_modules.mean_vfe import MeanVFE -from opencood.models.sub_modules.sparse_backbone_3d import VoxelBackBone8x -from opencood.models.sub_modules.height_compression import HeightCompression -from opencood.models.sub_modules.cia_ssd_utils import SSFA, Head -from opencood.models.sub_modules.vsa import VoxelSetAbstraction -from opencood.models.sub_modules.roi_head import RoIHead -from opencood.models.sub_modules.matcher import Matcher -from opencood.data_utils.post_processor.fpvrcnn_postprocessor import FpvrcnnPostprocessor - - -class FPVRCNN(nn.Module): - # The model declares its own wire boundary; neither the manager nor the - # dataset needs to infer it from the selected fusion dataset. - communication_adapter_class = FpvrcnnCommunicationAdapter - - def __init__(self, args): - super(FPVRCNN, self).__init__() - lidar_range = np.array(args["lidar_range"]) - grid_size = np.round((lidar_range[3:6] - lidar_range[:3]) / np.array(args["voxel_size"])).astype(np.int64) - self.vfe = MeanVFE(args["mean_vfe"], args["mean_vfe"]["num_point_features"]) - self.spconv_block = VoxelBackBone8x(args["spconv"], input_channels=args["spconv"]["num_features_in"], grid_size=grid_size) - self.map_to_bev = HeightCompression(args["map2bev"]) - self.ssfa = SSFA(args["ssfa"]) - self.head = Head(**args["head"]) - self.post_processor = FpvrcnnPostprocessor(args["post_processer"], train=True) - self.vsa = VoxelSetAbstraction(args["vsa"], args["voxel_size"], args["lidar_range"], num_bev_features=128, num_rawpoint_features=3) - self.matcher = Matcher(args["matcher"], args["lidar_range"]) - self.roi_head = RoIHead(args["roi_head"]) - self.train_stage2 = args["activate_stage2"] - - def _forward_stage_one(self, batch_dict): - """ - Run the per-agent backbone and proposal head. - - Parameters - ---------- - batch_dict : dict - Collated voxel input for one or more independent agents. - - Returns - ------- - tuple[dict, list[torch.Tensor] | None, list[torch.Tensor] | None] - Updated model state, proposal boxes, and proposal scores. - """ - voxel_features = batch_dict["processed_lidar"]["voxel_features"] - voxel_coords = batch_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = batch_dict["processed_lidar"]["voxel_num_points"] - - # save memory - batch_dict.pop("processed_lidar") - batch_dict.update( - { - "voxel_features": voxel_features, - "voxel_coords": voxel_coords, - "voxel_num_points": voxel_num_points, - "batch_size": int(batch_dict["record_len"].sum()), - } - ) - - batch_dict = self.vfe(batch_dict) - batch_dict = self.spconv_block(batch_dict) - batch_dict = self.map_to_bev(batch_dict) - - out = self.ssfa(batch_dict["spatial_features"]) - batch_dict["preds_dict_stage1"] = self.head(out) - - data_dict, output_dict = {}, {} - data_dict["ego"], output_dict["ego"] = batch_dict, batch_dict - - pred_box3d_list, scores_list = self.post_processor.post_process(data_dict, output_dict, stage1=True) - batch_dict["det_boxes"] = pred_box3d_list - batch_dict["det_scores"] = scores_list - - return batch_dict, pred_box3d_list, scores_list - - def encode_agent(self, batch_dict): - """ - Build the FPV-RCNN representation transmitted by one agent. - - Parameters - ---------- - batch_dict : dict - Single-agent voxel input, raw LiDAR points, and anchors. - - Returns - ------- - dict[str, torch.Tensor] - Stage-one proposals and VSA keypoint coordinates/features in the - sender coordinate frame. - - Raises - ------ - RuntimeError - If cooperative stage two is disabled in the model configuration. - """ - if not self.train_stage2: - raise RuntimeError("Distributed FPV-RCNN inference requires activate_stage2=true") - batch_dict, proposals, proposal_scores = self._forward_stage_one(batch_dict) - if proposals is None or proposal_scores is None: - device = batch_dict["voxel_features"].device - return { - "proposals": torch.empty((0, 7), device=device), - "proposal_scores": torch.empty((0,), device=device), - "point_coords": torch.empty((0, 4), device=device), - "point_features": torch.empty((0, self.vsa.num_point_features), device=device), - } - - batch_dict = self.vsa(batch_dict) - return { - "proposals": proposals[0], - "proposal_scores": proposal_scores[0], - "point_coords": batch_dict["point_coords"][0], - "point_features": batch_dict["point_features"][0], - } - - def _forward_distributed(self, batch_dict): - """ - Fuse local FPV-RCNN output with delivered agent outputs. - - Parameters - ---------- - batch_dict : dict - Ego input containing ``remote_agent_outputs`` decoded from wire - payloads. - - Returns - ------- - dict - Cooperative stage-two model output. - """ - remote_outputs = batch_dict.pop("remote_agent_outputs") - local_output = self.encode_agent(batch_dict) - agent_outputs = [local_output, *remote_outputs] - - batch_dict["det_boxes"] = [output["proposals"] for output in agent_outputs] - batch_dict["det_scores"] = [output["proposal_scores"] for output in agent_outputs] - batch_dict["point_coords"] = [output["point_coords"] for output in agent_outputs] - batch_dict["point_features"] = [output["point_features"] for output in agent_outputs] - batch_dict["record_len"] = torch.tensor( - [len(agent_outputs)], - dtype=torch.long, - device=batch_dict["voxel_features"].device, - ) - - if not any(len(boxes) > 0 for boxes in batch_dict["det_boxes"]): - batch_dict["fpvrcnn_out"] = None - return batch_dict - - batch_dict = self.matcher(batch_dict) - return self.roi_head(batch_dict) - - @staticmethod - def _transform_agent_outputs(batch_dict): - """ - Transform local proposals and keypoints into each scene's ego frame. - - Parameters - ---------- - batch_dict : dict - Model state containing per-agent detections, keypoints, and one - ``agent_to_ego`` matrix per flattened agent. - - Returns - ------- - dict - Model state with proposal centers, headings, and keypoint - coordinates expressed in their corresponding ego frames. - """ - transformations = batch_dict.pop("agent_to_ego") - if len(transformations) != len(batch_dict["det_boxes"]) or len(transformations) != len(batch_dict["point_coords"]): - raise ValueError("Every FPV-RCNN agent output requires one agent-to-ego transformation") - transformed_boxes = [] - transformed_points = [] - - for boxes, points, transformation in zip( - batch_dict["det_boxes"], - batch_dict["point_coords"], - transformations, - ): - boxes = boxes.clone() - if len(boxes) > 0: - box_transformation = transformation.to(dtype=boxes.dtype) - homogeneous_centers = torch.cat( - [boxes[:, :3], torch.ones((len(boxes), 1), dtype=boxes.dtype, device=boxes.device)], - dim=1, - ) - boxes[:, :3] = (box_transformation @ homogeneous_centers.T).T[:, :3] - boxes[:, 6] += torch.atan2(box_transformation[1, 0], box_transformation[0, 0]) - transformed_boxes.append(boxes) - - points = points.clone() - if len(points) > 0: - point_transformation = transformation.to(dtype=points.dtype) - homogeneous_points = torch.cat( - [points[:, :3], torch.ones((len(points), 1), dtype=points.dtype, device=points.device)], - dim=1, - ) - points[:, :3] = (point_transformation @ homogeneous_points.T).T[:, :3] - transformed_points.append(points) - - batch_dict["det_boxes"] = transformed_boxes - batch_dict["point_coords"] = transformed_points - return batch_dict - - def forward(self, batch_dict): - if "remote_agent_outputs" in batch_dict: - return self._forward_distributed(batch_dict) - - batch_dict, pred_box3d_list, _ = self._forward_stage_one(batch_dict) - - if pred_box3d_list is not None and self.train_stage2: - batch_dict = self.vsa(batch_dict) - if "agent_to_ego" in batch_dict: - batch_dict = self._transform_agent_outputs(batch_dict) - batch_dict = self.matcher(batch_dict) - batch_dict = self.roi_head(batch_dict) - if pred_box3d_list is None: - batch_dict["fpvrcnn_out"] = None - - return batch_dict - - -if __name__ == "__main__": - model = SSFA(None) - print(model) diff --git a/OpenCOOD/opencood/models/fuse_modules/V2VAM.py b/OpenCOOD/opencood/models/fuse_modules/V2VAM.py deleted file mode 100644 index 98ea7b29..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/V2VAM.py +++ /dev/null @@ -1,106 +0,0 @@ -import torch -import torch.nn as nn - - -from torch.nn import Softmax - - -class V2V_AttFusion(nn.Module): - def __init__(self, feature_dim): - super(V2V_AttFusion, self).__init__() - - self.cov_att = nn.Sequential( - nn.Conv2d(in_channels=feature_dim, out_channels=feature_dim, kernel_size=3, padding=1), - nn.BatchNorm2d(feature_dim, eps=1e-5, momentum=0.01, affine=True), - nn.ReLU(), - ) - - self.CCNet = CrissCrossAttention(feature_dim) - - def forward(self, x, record_len): - split_x = self.regroup(x, record_len) # x =[5, 64, 100, 352], record_len=[3,2] - - out = [] - att = [] - for xx in split_x: # split_x[0] [num_car, C, W, H] - """ CCNet: Criss-Cross Attention Module: attention for ego vehicle feature + cav feature """ - - ego_q, _, _ = xx[0:1], xx[0:1], xx[0:1] # 2 -> ego_k, 3 -> ego_v - for i in range(len(xx[:, 0, 0, 0])): - att_vehicle = self.CCNet(ego_q, xx[i : i + 1], xx[i : i + 1]) - att.append(att_vehicle) - - pooling_max = torch.max(torch.cat(att, dim=0), dim=0, keepdim=True)[0] - pooling_ave = torch.mean(torch.cat(att, dim=0), dim=0, keepdim=True)[0] - - fuse_fea = pooling_max + pooling_ave - - fuse_att = fuse_fea - fuse_att = self.cov_att(fuse_att) - - out.append(fuse_att) # [[1, 64, 100, 352], [1, 64, 100, 352]] - # torch.cuda.empty_cache() - - return torch.cat(out, dim=0) # [2, 64, 100, 352] - - def regroup(self, x, record_len): - cum_sum_len = torch.cumsum(record_len, dim=0) - split_x = torch.tensor_split(x, cum_sum_len[:-1].cpu()) - - return split_x - - -def INF(B, H, W): - return -torch.diag(torch.tensor(float("inf")).cuda().repeat(H), 0).unsqueeze(0).repeat(B * W, 1, 1) - - -class CrissCrossAttention(nn.Module): - """Criss-Cross Attention Module - - reference: https://github.com/speedinghzl/CCNet - - """ - - def __init__(self, in_dim): - super(CrissCrossAttention, self).__init__() - - self.query_conv = nn.Sequential( - nn.Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1), nn.BatchNorm2d(in_dim, eps=1e-5, momentum=0.01, affine=True), nn.ReLU() - ) - self.key_conv = nn.Sequential( - nn.Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1), nn.BatchNorm2d(in_dim, eps=1e-5, momentum=0.01, affine=True), nn.ReLU() - ) - self.value_conv = nn.Sequential( - nn.Conv2d(in_channels=in_dim, out_channels=in_dim, kernel_size=1), nn.BatchNorm2d(in_dim, eps=1e-5, momentum=0.01, affine=True), nn.ReLU() - ) - - self.softmax = Softmax(dim=3) - self.INF = INF - self.gamma = nn.Parameter(torch.zeros(1)) - - def forward(self, query, key, value): - m_batchsize, _, height, width = query.size() - - proj_query = self.query_conv(query) - proj_query_H = proj_query.permute(0, 3, 1, 2).contiguous().view(m_batchsize * width, -1, height).permute(0, 2, 1) - proj_query_W = proj_query.permute(0, 2, 1, 3).contiguous().view(m_batchsize * height, -1, width).permute(0, 2, 1) - - proj_key = self.key_conv(key) - proj_key_H = proj_key.permute(0, 3, 1, 2).contiguous().view(m_batchsize * width, -1, height) - proj_key_W = proj_key.permute(0, 2, 1, 3).contiguous().view(m_batchsize * height, -1, width) - - proj_value = self.value_conv(value) - proj_value_H = proj_value.permute(0, 3, 1, 2).contiguous().view(m_batchsize * width, -1, height) - proj_value_W = proj_value.permute(0, 2, 1, 3).contiguous().view(m_batchsize * height, -1, width) - energy_H = ( - (torch.bmm(proj_query_H, proj_key_H) + self.INF(m_batchsize, height, width)).view(m_batchsize, width, height, height).permute(0, 2, 1, 3) - ) - energy_W = torch.bmm(proj_query_W, proj_key_W).view(m_batchsize, height, width, width) - concate = self.softmax(torch.cat([energy_H, energy_W], 3)) - - att_H = concate[:, :, :, 0:height].permute(0, 2, 1, 3).contiguous().view(m_batchsize * width, height, height) - att_W = concate[:, :, :, height : height + width].contiguous().view(m_batchsize * height, width, width) - out_H = torch.bmm(proj_value_H, att_H.permute(0, 2, 1)).view(m_batchsize, width, -1, height).permute(0, 2, 3, 1) - out_W = torch.bmm(proj_value_W, att_W.permute(0, 2, 1)).view(m_batchsize, height, -1, width).permute(0, 2, 1, 3) - - return self.gamma * (out_H + out_W) + value diff --git a/OpenCOOD/opencood/models/fuse_modules/__init__.py b/OpenCOOD/opencood/models/fuse_modules/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/models/fuse_modules/coalign_fuse.py b/OpenCOOD/opencood/models/fuse_modules/coalign_fuse.py deleted file mode 100644 index 9ef0e65b..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/coalign_fuse.py +++ /dev/null @@ -1,76 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F -from opencood.models.fuse_modules.self_attn import ScaledDotProductAttention - - -def regroup(x, record_len): - cum_sum_len = torch.cumsum(record_len, dim=0) - split_x = torch.tensor_split(x, cum_sum_len[:-1].cpu()) - return split_x - - -def normalize_pairwise_tfm(pairwise_t_matrix, H, W, discrete_ratio, downsample_rate=1): - """ - normalize the pairwise transformation matrix to affine matrix need by torch.nn.functional.affine_grid() - - Parameters - ---------- - pairwise_t_matrix: torch.tensor - [B, L, L, 4, 4], B batchsize, L max_cav - H: num. - Feature map height - W: num. - Feature map width - discrete_ratio * downsample_rate: num. - One pixel on the feature map corresponds to the actual physical distance - - Returns - ------- - normalized_affine_matrix: torch.tensor - [B, L, L, 2, 3], B batchsize, L max_cav - """ - pairwise_t_matrix = pairwise_t_matrix[:, :, :, [0, 1], :][:, :, :, :, [0, 1, 3]] # [B, L, L, 2, 3] - pairwise_t_matrix[..., 0, 1] = pairwise_t_matrix[..., 0, 1] * H / W - pairwise_t_matrix[..., 1, 0] = pairwise_t_matrix[..., 1, 0] * W / H - pairwise_t_matrix[..., 0, 2] = pairwise_t_matrix[..., 0, 2] / (downsample_rate * discrete_ratio * W) * 2 - pairwise_t_matrix[..., 1, 2] = pairwise_t_matrix[..., 1, 2] / (downsample_rate * discrete_ratio * H) * 2 - - normalized_affine_matrix = pairwise_t_matrix - - return normalized_affine_matrix - - -def warp_affine_simple(src, M, dsize, mode="bilinear", padding_mode="zeros", align_corners=False): - """ """ - B, C, H, W = src.size() - grid = F.affine_grid(M, [B, C, dsize[0], dsize[1]], align_corners=align_corners).to(src) - return F.grid_sample(src, grid, align_corners=align_corners) - - -class Att_w_Warp(nn.Module): - def __init__(self, feature_dims): - super(Att_w_Warp, self).__init__() - self.att = ScaledDotProductAttention(feature_dims) - - def forward(self, xx, record_len, normalized_affine_matrix): - _, C, H, W = xx.shape - B, L = normalized_affine_matrix.shape[:2] - split_x = regroup(xx, record_len) - batch_node_features = split_x - out = [] - # iterate each batch - for b in range(B): - N = record_len[b] - t_matrix = normalized_affine_matrix[b][:N, :N, :, :] - # update each node i - i = 0 # ego - x = warp_affine_simple(batch_node_features[b], t_matrix[i, :, :, :], (H, W)) - cav_num = x.shape[0] - x = x.view(cav_num, C, -1).permute(2, 0, 1) # (H*W, cav_num, C), perform self attention on each pixel. - h = self.att(x, x, x) - h = h.permute(1, 2, 0).view(cav_num, C, H, W)[0, ...] # C, W, H before - out.append(h) - - out = torch.stack(out) - return out diff --git a/OpenCOOD/opencood/models/fuse_modules/f_cooper_fuse.py b/OpenCOOD/opencood/models/fuse_modules/f_cooper_fuse.py deleted file mode 100644 index d8bc68b7..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/f_cooper_fuse.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Implementation of F-cooper maxout fusing. -""" - -import torch -import torch.nn as nn - - -class SpatialFusion(nn.Module): - def __init__(self): - super(SpatialFusion, self).__init__() - - def regroup(self, x, record_len): - cum_sum_len = torch.cumsum(record_len, dim=0) - split_x = torch.tensor_split(x, cum_sum_len[:-1].cpu()) - return split_x - - def forward(self, x, record_len): - # x: B, C, H, W, split x:[(B1, C, W, H), (B2, C, W, H)] - split_x = self.regroup(x, record_len) - out = [] - - for xx in split_x: - xx = torch.max(xx, dim=0, keepdim=True)[0] - out.append(xx) - return torch.cat(out, dim=0) diff --git a/OpenCOOD/opencood/models/fuse_modules/fuse_utils.py b/OpenCOOD/opencood/models/fuse_modules/fuse_utils.py deleted file mode 100644 index 8a00f196..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/fuse_utils.py +++ /dev/null @@ -1,54 +0,0 @@ -import torch -import numpy as np - -from einops import rearrange -from opencood.utils.common_utils import torch_tensor_to_numpy - - -def regroup(dense_feature, record_len, max_len): - """ - Regroup the data based on the record_len. - - Parameters - ---------- - dense_feature : torch.Tensor - N, C, H, W - record_len : list - [sample1_len, sample2_len, ...] - max_len : int - Maximum cav number - - Returns - ------- - regroup_feature : torch.Tensor - B, L, C, H, W - """ - cum_sum_len = list(np.cumsum(torch_tensor_to_numpy(record_len))) - split_features = torch.tensor_split(dense_feature, cum_sum_len[:-1]) - regroup_features = [] - mask = [] - - for split_feature in split_features: - # M, C, H, W - feature_shape = split_feature.shape - - # the maximum M is 5 as most 5 cavs - padding_len = max_len - feature_shape[0] - mask.append([1] * feature_shape[0] + [0] * padding_len) - - padding_tensor = torch.zeros(padding_len, feature_shape[1], feature_shape[2], feature_shape[3]) - padding_tensor = padding_tensor.to(split_feature.device) - - split_feature = torch.cat([split_feature, padding_tensor], dim=0) - - # 1, 5C, H, W - split_feature = split_feature.view(-1, feature_shape[2], feature_shape[3]).unsqueeze(0) - regroup_features.append(split_feature) - - # B, 5C, H, W - regroup_features = torch.cat(regroup_features, dim=0) - # B, L, C, H, W - regroup_features = rearrange(regroup_features, "b (l c) h w -> b l c h w", l=max_len) - mask = torch.from_numpy(np.array(mask)).to(regroup_features.device) - - return regroup_features, mask diff --git a/OpenCOOD/opencood/models/fuse_modules/hmsa.py b/OpenCOOD/opencood/models/fuse_modules/hmsa.py deleted file mode 100644 index 4922a73a..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/hmsa.py +++ /dev/null @@ -1,135 +0,0 @@ -import torch -from torch import nn - -from einops import rearrange - - -class HGTCavAttention(nn.Module): - def __init__(self, dim, heads, num_types=2, num_relations=4, dim_head=64, dropout=0.1): - super().__init__() - inner_dim = heads * dim_head - - self.heads = heads - self.scale = dim_head**-0.5 - self.num_types = num_types - - self.attend = nn.Softmax(dim=-1) - self.drop_out = nn.Dropout(dropout) - self.k_linears = nn.ModuleList() - self.q_linears = nn.ModuleList() - self.v_linears = nn.ModuleList() - self.a_linears = nn.ModuleList() - self.norms = nn.ModuleList() # noqa: DC05 - for t in range(num_types): - self.k_linears.append(nn.Linear(dim, inner_dim)) - self.q_linears.append(nn.Linear(dim, inner_dim)) - self.v_linears.append(nn.Linear(dim, inner_dim)) - self.a_linears.append(nn.Linear(inner_dim, dim)) - - self.relation_att = nn.Parameter(torch.Tensor(num_relations, heads, dim_head, dim_head)) - self.relation_msg = nn.Parameter(torch.Tensor(num_relations, heads, dim_head, dim_head)) - - torch.nn.init.xavier_uniform(self.relation_att) - torch.nn.init.xavier_uniform(self.relation_msg) - - def to_qkv(self, x, types): - # x: (B,H,W,L,C) - # types: (B,L) - q_batch = [] - k_batch = [] - v_batch = [] - - for b in range(x.shape[0]): - q_list = [] - k_list = [] - v_list = [] - - for i in range(x.shape[-2]): - # (H,W,1,C) - q_list.append(self.q_linears[types[b, i]](x[b, :, :, i, :].unsqueeze(2))) - k_list.append(self.k_linears[types[b, i]](x[b, :, :, i, :].unsqueeze(2))) - v_list.append(self.v_linears[types[b, i]](x[b, :, :, i, :].unsqueeze(2))) - # (1,H,W,L,C) - q_batch.append(torch.cat(q_list, dim=2).unsqueeze(0)) - k_batch.append(torch.cat(k_list, dim=2).unsqueeze(0)) - v_batch.append(torch.cat(v_list, dim=2).unsqueeze(0)) - # (B,H,W,L,C) - q = torch.cat(q_batch, dim=0) - k = torch.cat(k_batch, dim=0) - v = torch.cat(v_batch, dim=0) - return q, k, v - - def get_relation_type_index(self, type1, type2): - return type1 * self.num_types + type2 - - def get_hetero_edge_weights(self, x, types): - w_att_batch = [] - w_msg_batch = [] - - for b in range(x.shape[0]): - w_att_list = [] - w_msg_list = [] - - for i in range(x.shape[-2]): - w_att_i_list = [] - w_msg_i_list = [] - - for j in range(x.shape[-2]): - e_type = self.get_relation_type_index(types[b, i], types[b, j]) - w_att_i_list.append(self.relation_att[e_type].unsqueeze(0)) - w_msg_i_list.append(self.relation_msg[e_type].unsqueeze(0)) - w_att_list.append(torch.cat(w_att_i_list, dim=0).unsqueeze(0)) - w_msg_list.append(torch.cat(w_msg_i_list, dim=0).unsqueeze(0)) - - w_att_batch.append(torch.cat(w_att_list, dim=0).unsqueeze(0)) - w_msg_batch.append(torch.cat(w_msg_list, dim=0).unsqueeze(0)) - - # (B,M,L,L,C_head,C_head) - w_att = torch.cat(w_att_batch, dim=0).permute(0, 3, 1, 2, 4, 5) - w_msg = torch.cat(w_msg_batch, dim=0).permute(0, 3, 1, 2, 4, 5) - return w_att, w_msg - - def to_out(self, x, types): - out_batch = [] - for b in range(x.shape[0]): - out_list = [] - for i in range(x.shape[-2]): - out_list.append(self.a_linears[types[b, i]](x[b, :, :, i, :].unsqueeze(2))) - out_batch.append(torch.cat(out_list, dim=2).unsqueeze(0)) - out = torch.cat(out_batch, dim=0) - return out - - def forward(self, x, mask, prior_encoding): - # x: (B, L, H, W, C) -> (B, H, W, L, C) - # mask: (B, H, W, L, 1) - # prior_encoding: (B,L,H,W,3) - x = x.permute(0, 2, 3, 1, 4) - # mask: (B, 1, H, W, L, 1) - mask = mask.unsqueeze(1) - # (B,L) - _, dts, types = [itm.squeeze(-1) for itm in prior_encoding[:, :, 0, 0, :].split([1, 1, 1], dim=-1)] - types = types.to(torch.int) - dts = dts.to(torch.int) - qkv = self.to_qkv(x, types) - # (B,M,L,L,C_head,C_head) - w_att, w_msg = self.get_hetero_edge_weights(x, types) - - # q: (B, M, H, W, L, C) - q, k, v = map(lambda t: rearrange(t, "b h w l (m c) -> b m h w l c", m=self.heads), (qkv)) - # attention, (B, M, H, W, L, L) - att_map = torch.einsum("b m h w i p, b m i j p q, bm h w j q -> b m h w i j", [q, w_att, k]) * self.scale - # add mask - att_map = att_map.masked_fill(mask == 0, -float("inf")) - # softmax - att_map = self.attend(att_map) - - # out:(B, M, H, W, L, C_head) - v_msg = torch.einsum("b m i j p c, b m h w j p -> b m h w i j c", w_msg, v) - out = torch.einsum("b m h w i j, b m h w i j c -> b m h w i c", att_map, v_msg) - - out = rearrange(out, "b m h w l c -> b h w l (m c)", m=self.heads) - out = self.to_out(out, types) - out = self.drop_out(out) - # (B L H W C) - out = out.permute(0, 3, 1, 2, 4) - return out diff --git a/OpenCOOD/opencood/models/fuse_modules/mswin.py b/OpenCOOD/opencood/models/fuse_modules/mswin.py deleted file mode 100644 index a2a41c1b..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/mswin.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Multi-scale window transformer -""" - -import torch -import torch.nn as nn -import numpy as np - -from einops import rearrange -from opencood.models.sub_modules.split_attn import SplitAttn - - -def get_relative_distances(window_size): - indices = torch.tensor(np.array([[x, y] for x in range(window_size) for y in range(window_size)])) - distances = indices[None, :, :] - indices[:, None, :] - return distances - - -class BaseWindowAttention(nn.Module): - def __init__(self, dim, heads, dim_head, drop_out, window_size, relative_pos_embedding): - super().__init__() - inner_dim = dim_head * heads - - self.heads = heads - self.scale = dim_head**-0.5 - self.window_size = window_size - self.relative_pos_embedding = relative_pos_embedding - - self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False) - - if self.relative_pos_embedding: - self.relative_indices = get_relative_distances(window_size) + window_size - 1 - self.pos_embedding = nn.Parameter(torch.randn(2 * window_size - 1, 2 * window_size - 1)) - else: - self.pos_embedding = nn.Parameter(torch.randn(window_size**2, window_size**2)) - - self.to_out = nn.Sequential(nn.Linear(inner_dim, dim), nn.Dropout(drop_out)) - - def forward(self, x): - _, _, h, w, _, m = *x.shape, self.heads # 1 -> b, 2 -> length, 5 -> c - - qkv = self.to_qkv(x).chunk(3, dim=-1) - new_h = h // self.window_size - new_w = w // self.window_size - - # q : (b, l, m, new_h*new_w, window_size^2, c_head) - q, k, v = map( - lambda t: rearrange( - t, "b l (new_h w_h) (new_w w_w) (m c) -> b l m (new_h new_w) (w_h w_w) c", m=m, w_h=self.window_size, w_w=self.window_size - ), - qkv, - ) - # b l m h window_size window_size - dots = ( - torch.einsum( - "b l m h i c, b l m h j c -> b l m h i j", - q, - k, - ) - * self.scale - ) - # consider prior knowledge of the local window - if self.relative_pos_embedding: - dots += self.pos_embedding[self.relative_indices[:, :, 0], self.relative_indices[:, :, 1]] - else: - dots += self.pos_embedding - - attn = dots.softmax(dim=-1) - - out = torch.einsum("b l m h i j, b l m h j c -> b l m h i c", attn, v) - # b l h w c - out = rearrange( - out, - "b l m (new_h new_w) (w_h w_w) c -> b l (new_h w_h) (new_w w_w) (m c)", - m=self.heads, - w_h=self.window_size, - w_w=self.window_size, - new_w=new_w, - new_h=new_h, - ) - out = self.to_out(out) - - return out - - -class PyramidWindowAttention(nn.Module): - def __init__(self, dim, heads, dim_heads, drop_out, window_size, relative_pos_embedding, fuse_method="naive"): - super().__init__() - - assert isinstance(window_size, list) - assert isinstance(heads, list) - assert isinstance(dim_heads, list) - assert len(dim_heads) == len(heads) - - self.pwmsa = nn.ModuleList([]) - - for head, dim_head, ws in zip(heads, dim_heads, window_size): - self.pwmsa.append(BaseWindowAttention(dim, head, dim_head, drop_out, ws, relative_pos_embedding)) - self.fuse_mehod = fuse_method - if fuse_method == "split_attn": - self.split_attn = SplitAttn(256) - - def forward(self, x): - output = None - # naive fusion will just sum up all window attention output and do a - # mean - if self.fuse_mehod == "naive": - for wmsa in self.pwmsa: - output = wmsa(x) if output is None else output + wmsa(x) - return output / len(self.pwmsa) - - elif self.fuse_mehod == "split_attn": - window_list = [] - for wmsa in self.pwmsa: - window_list.append(wmsa(x)) - return self.split_attn(window_list) diff --git a/OpenCOOD/opencood/models/fuse_modules/self_attn.py b/OpenCOOD/opencood/models/fuse_modules/self_attn.py deleted file mode 100644 index 2133ed93..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/self_attn.py +++ /dev/null @@ -1,66 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class ScaledDotProductAttention(nn.Module): - """ - Scaled Dot-Product Attention proposed in "Attention Is All You Need" - Compute the dot products of the query with all keys, divide each by sqrt(dim), - and apply a softmax function to obtain the weights on the values - Args: dim, mask - dim (int): dimention of attention - mask (torch.Tensor): tensor containing indices to be masked - Inputs: query, key, value, mask - - **query** (batch, q_len, d_model): tensor containing projection - vector for decoder. - - **key** (batch, k_len, d_model): tensor containing projection - vector for encoder. - - **value** (batch, v_len, d_model): tensor containing features of the - encoded input sequence. - - **mask** (-): tensor containing indices to be masked - Returns: context, attn - - **context**: tensor containing the context vector from - attention mechanism. - - **attn**: tensor containing the attention (alignment) from the - encoder outputs. - """ - - def __init__(self, dim): - super(ScaledDotProductAttention, self).__init__() - self.sqrt_dim = np.sqrt(dim) - - def forward(self, query, key, value): - # Single-element attention is an identity operation. Skip backend kernel - # dispatch for this degenerate case (for example, Triton on CUDA). - if query.size(1) == 1 and key.size(1) == 1 and value.size(1) == 1: - return value - - score = torch.bmm(query, key.transpose(1, 2)) / self.sqrt_dim - attn = F.softmax(score, -1) - context = torch.bmm(attn, value) - return context - - -class AttFusion(nn.Module): - def __init__(self, feature_dim): - super(AttFusion, self).__init__() - self.att = ScaledDotProductAttention(feature_dim) - - def forward(self, x, record_len): - split_x = self.regroup(x, record_len) - C, W, H = split_x[0].shape[1:] - out = [] - for xx in split_x: - cav_num = xx.shape[0] - xx = xx.view(cav_num, C, -1).permute(2, 0, 1) - h = self.att(xx, xx, xx) - h = h.permute(1, 2, 0).view(cav_num, C, W, H)[0, ...] - out.append(h) - return torch.stack(out) - - def regroup(self, x, record_len): - cum_sum_len = torch.cumsum(record_len, dim=0) - split_x = torch.tensor_split(x, cum_sum_len[:-1].cpu()) - return split_x diff --git a/OpenCOOD/opencood/models/fuse_modules/swap_fusion_modules.py b/OpenCOOD/opencood/models/fuse_modules/swap_fusion_modules.py deleted file mode 100644 index d14e7f6a..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/swap_fusion_modules.py +++ /dev/null @@ -1,234 +0,0 @@ -""" -This class is about swap fusion applications (also known as Fused Axial Attention) -""" - -import torch -from einops import rearrange -from torch import nn, einsum -from einops.layers.torch import Rearrange, Reduce - -from opencood.models.sub_modules.base_transformer import FeedForward, PreNormResidual - - -# swap attention -> max_vit -class Attention(nn.Module): - """ - Unit Attention class. Todo: mask is not added yet. - - Parameters - ---------- - dim: int - Input feature dimension. - dim_head: int - The head dimension. - dropout: float - Dropout rate - agent_size: int - The agent can be different views, timestamps or vehicles. - """ - - def __init__(self, dim, dim_head=32, dropout=0.0, agent_size=6, window_size=7): - super().__init__() - assert (dim % dim_head) == 0, "dimension should be divisible by dimension per head" - - self.heads = dim // dim_head - self.scale = dim_head**-0.5 - self.window_size = [agent_size, window_size, window_size] - - self.to_qkv = nn.Linear(dim, dim * 3, bias=False) - self.attend = nn.Sequential(nn.Softmax(dim=-1)) - - self.to_out = nn.Sequential(nn.Linear(dim, dim, bias=False), nn.Dropout(dropout)) - - self.relative_position_bias_table = nn.Embedding( - (2 * self.window_size[0] - 1) * (2 * self.window_size[1] - 1) * (2 * self.window_size[2] - 1), self.heads - ) # 2*Wd-1 * 2*Wh-1 * 2*Ww-1, nH - - # get pair-wise relative position index for - # each token inside the window - coords_d = torch.arange(self.window_size[0]) - coords_h = torch.arange(self.window_size[1]) - coords_w = torch.arange(self.window_size[2]) - # 3, Wd, Wh, Ww - coords = torch.stack(torch.meshgrid(coords_d, coords_h, coords_w)) - coords_flatten = torch.flatten(coords, 1) # 3, Wd*Wh*Ww - - # 3, Wd*Wh*Ww, Wd*Wh*Ww - relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] - # Wd*Wh*Ww, Wd*Wh*Ww, 3 - relative_coords = relative_coords.permute(1, 2, 0).contiguous() - # shift to start from 0 - relative_coords[:, :, 0] += self.window_size[0] - 1 - relative_coords[:, :, 1] += self.window_size[1] - 1 - relative_coords[:, :, 2] += self.window_size[2] - 1 - - relative_coords[:, :, 0] *= (2 * self.window_size[1] - 1) * (2 * self.window_size[2] - 1) - relative_coords[:, :, 1] *= 2 * self.window_size[2] - 1 - relative_position_index = relative_coords.sum(-1) # Wd*Wh*Ww, Wd*Wh*Ww - self.register_buffer("relative_position_index", relative_position_index) - - def forward(self, x, mask=None): - # x shape: b, l, h, w, w_h, w_w, c - batch, agent_size, height, width, window_height, window_width, _, _, h = *x.shape, x.device, self.heads # eighth variable is device - - # flatten - x = rearrange(x, "b l x y w1 w2 d -> (b x y) (l w1 w2) d") - # project for queries, keys, values - q, k, v = self.to_qkv(x).chunk(3, dim=-1) - # split heads - q, k, v = map(lambda t: rearrange(t, "b n (h d) -> b h n d", h=h), (q, k, v)) - # scale - q = q * self.scale - - # sim - sim = einsum("b h i d, b h j d -> b h i j", q, k) - - # add positional bias - bias = self.relative_position_bias_table(self.relative_position_index) - sim = sim + rearrange(bias, "i j h -> h i j") - - # mask shape if exist: b x y w1 w2 e l - if mask is not None: - # b x y w1 w2 e l -> (b x y) 1 (l w1 w2) - mask = rearrange(mask, "b x y w1 w2 e l -> (b x y) e (l w1 w2)") - # (b x y) 1 1 (l w1 w2) = b h 1 n - mask = mask.unsqueeze(1) - sim = sim.masked_fill(mask == 0, -float("inf")) - - # attention - attn = self.attend(sim) - # aggregate - out = einsum("b h i j, b h j d -> b h i d", attn, v) - # merge heads - out = rearrange(out, "b h (l w1 w2) d -> b l w1 w2 (h d)", l=agent_size, w1=window_height, w2=window_width) - - # combine heads out - out = self.to_out(out) - return rearrange(out, "(b x y) l w1 w2 d -> b l x y w1 w2 d", b=batch, x=height, y=width) - - -class SwapFusionBlockMask(nn.Module): - """ - Swap Fusion Block contains window attention and grid attention with - mask enabled for multi-vehicle cooperation. - """ - - def __init__(self, input_dim, mlp_dim, dim_head, window_size, agent_size, drop_out): - super(SwapFusionBlockMask, self).__init__() - - self.window_size = window_size - - self.window_attention = PreNormResidual(input_dim, Attention(input_dim, dim_head, drop_out, agent_size, window_size)) - self.window_ffd = PreNormResidual(input_dim, FeedForward(input_dim, mlp_dim, drop_out)) - self.grid_attention = PreNormResidual(input_dim, Attention(input_dim, dim_head, drop_out, agent_size, window_size)) - self.grid_ffd = PreNormResidual(input_dim, FeedForward(input_dim, mlp_dim, drop_out)) - - def forward(self, x, mask): - # x: b l c h w - # mask: b h w 1 l - # window attention -> grid attention - mask_swap = mask - - # mask b h w 1 l -> b x y w1 w2 1 L - mask_swap = rearrange(mask_swap, "b (x w1) (y w2) e l -> b x y w1 w2 e l", w1=self.window_size, w2=self.window_size) - x = rearrange(x, "b m d (x w1) (y w2) -> b m x y w1 w2 d", w1=self.window_size, w2=self.window_size) - x = self.window_attention(x, mask=mask_swap) - x = self.window_ffd(x) - x = rearrange(x, "b m x y w1 w2 d -> b m d (x w1) (y w2)") - - # grid attention - mask_swap = mask - mask_swap = rearrange(mask_swap, "b (w1 x) (w2 y) e l -> b x y w1 w2 e l", w1=self.window_size, w2=self.window_size) - x = rearrange(x, "b m d (w1 x) (w2 y) -> b m x y w1 w2 d", w1=self.window_size, w2=self.window_size) - x = self.grid_attention(x, mask=mask_swap) - x = self.grid_ffd(x) - x = rearrange(x, "b m x y w1 w2 d -> b m d (w1 x) (w2 y)") - - return x - - -class SwapFusionBlock(nn.Module): - """ - Swap Fusion Block contains window attention and grid attention. - """ - - def __init__(self, input_dim, mlp_dim, dim_head, window_size, agent_size, drop_out): - super(SwapFusionBlock, self).__init__() - # b = batch * max_cav - self.block = nn.Sequential( - Rearrange("b m d (x w1) (y w2) -> b m x y w1 w2 d", w1=window_size, w2=window_size), - PreNormResidual(input_dim, Attention(input_dim, dim_head, drop_out, agent_size, window_size)), - PreNormResidual(input_dim, FeedForward(input_dim, mlp_dim, drop_out)), - Rearrange("b m x y w1 w2 d -> b m d (x w1) (y w2)"), - Rearrange("b m d (w1 x) (w2 y) -> b m x y w1 w2 d", w1=window_size, w2=window_size), - PreNormResidual(input_dim, Attention(input_dim, dim_head, drop_out, agent_size, window_size)), - PreNormResidual(input_dim, FeedForward(input_dim, mlp_dim, drop_out)), - Rearrange("b m x y w1 w2 d -> b m d (w1 x) (w2 y)"), - ) - - def forward(self, x, mask=None): - # todo: add mask operation later for mulit-agents - x = self.block(x) - return x - - -class SwapFusionEncoder(nn.Module): - """ - Data rearrange -> swap block -> mlp_head - """ - - def __init__(self, args): - super(SwapFusionEncoder, self).__init__() - - self.layers = nn.ModuleList([]) - self.depth = args["depth"] - - # block related - input_dim = args["input_dim"] - mlp_dim = args["mlp_dim"] - agent_size = args["agent_size"] - window_size = args["window_size"] - drop_out = args["drop_out"] - dim_head = args["dim_head"] - - self.mask = False - if "mask" in args: - self.mask = args["mask"] - - for i in range(self.depth): - if self.mask: - block = SwapFusionBlockMask(input_dim, mlp_dim, dim_head, window_size, agent_size, drop_out) - - else: - block = SwapFusionBlock(input_dim, mlp_dim, dim_head, window_size, agent_size, drop_out) - self.layers.append(block) - - # mlp head - self.mlp_head = nn.Sequential( - Reduce("b m d h w -> b d h w", "mean"), - Rearrange("b d h w -> b h w d"), - nn.LayerNorm(input_dim), - nn.Linear(input_dim, input_dim), - Rearrange("b h w d -> b d h w"), - ) - - def forward(self, x, mask=None): - for stage in self.layers: - x = stage(x, mask=mask) - return self.mlp_head(x) - - -if __name__ == "__main__": - import os - - os.environ["CUDA_VISIBLE_DEVICES"] = "1" - args = {"input_dim": 512, "mlp_dim": 512, "agent_size": 4, "window_size": 8, "dim_head": 4, "drop_out": 0.1, "depth": 2, "mask": True} - block = SwapFusionEncoder(args) - block.cuda() - test_data = torch.rand(1, 4, 512, 32, 32) - test_data = test_data.cuda() - mask = torch.ones(1, 32, 32, 1, 4) - mask = mask.cuda() - - output = block(test_data, mask) - print(output) diff --git a/OpenCOOD/opencood/models/fuse_modules/v2v_fuse.py b/OpenCOOD/opencood/models/fuse_modules/v2v_fuse.py deleted file mode 100644 index 756ca830..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/v2v_fuse.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Implementation of V2VNet Fusion -""" - -import torch -import torch.nn as nn - -from opencood.models.sub_modules.torch_transformation_utils import ( - get_discretized_transformation_matrix, - get_transformation_matrix, - warp_affine, - get_rotated_roi, -) -from opencood.models.sub_modules.convgru import ConvGRU - - -class V2VNetFusion(nn.Module): - def __init__(self, args): - super(V2VNetFusion, self).__init__() - - in_channels = args["in_channels"] - H, W = args["conv_gru"]["H"], args["conv_gru"]["W"] - kernel_size = args["conv_gru"]["kernel_size"] - num_gru_layers = args["conv_gru"]["num_layers"] - - self.discrete_ratio = args["voxel_size"][0] - self.downsample_rate = args["downsample_rate"] - self.num_iteration = args["num_iteration"] - self.gru_flag = args["gru_flag"] - self.agg_operator = args["agg_operator"] - - self.msg_cnn = nn.Conv2d(in_channels * 2, in_channels, kernel_size=3, stride=1, padding=1) - self.conv_gru = ConvGRU( - input_size=(H, W), - input_dim=in_channels * 2, - hidden_dim=[in_channels], - kernel_size=kernel_size, - num_layers=num_gru_layers, - batch_first=True, - bias=True, - return_all_layers=False, - ) - self.mlp = nn.Linear(in_channels, in_channels) - - def regroup(self, x, record_len): - cum_sum_len = torch.cumsum(record_len, dim=0) - split_x = torch.tensor_split(x, cum_sum_len[:-1].cpu()) - return split_x - - def forward(self, x, record_len, pairwise_t_matrix): - """ - Fusion forwarding. - - Parameters - ---------- - x : torch.Tensor - input data, (B, C, H, W) - - record_len : list - shape: (B) - - pairwise_t_matrix : torch.Tensor - The transformation matrix from each cav to ego, - shape: (B, L, L, 4, 4) - - Returns - ------- - Fused feature. - """ - _, C, H, W = x.shape - B, L = pairwise_t_matrix.shape[:2] - - # split x:[(L1, C, H, W), (L2, C, H, W)] - split_x = self.regroup(x, record_len) - # (B,L,L,2,3) - pairwise_t_matrix = get_discretized_transformation_matrix( - pairwise_t_matrix.reshape(-1, L, 4, 4), self.discrete_ratio, self.downsample_rate - ).reshape(B, L, L, 2, 3) - # (B*L,L,1,H,W) - roi_mask = get_rotated_roi((B * L, L, 1, H, W), pairwise_t_matrix.reshape(B * L * L, 2, 3)) - roi_mask = roi_mask.reshape(B, L, L, 1, H, W) - batch_node_features = split_x - - # iteratively update the features for num_iteration times - for _ in range(self.num_iteration): - batch_updated_node_features = [] - # iterate each batch - for b in range(B): - # number of valid agent - N = record_len[b] - # (N,N,4,4) - # t_matrix[i, j]-> from i to j - t_matrix = pairwise_t_matrix[b][:N, :N, :, :] - updated_node_features = [] - # update each node i - for i in range(N): - # (N,1,H,W) - mask = roi_mask[b, :N, i, ...] - - current_t_matrix = t_matrix[:, i, :, :] - current_t_matrix = get_transformation_matrix(current_t_matrix, (H, W)) - - # (N,C,H,W) - neighbor_feature = warp_affine(batch_node_features[b], current_t_matrix, (H, W)) - # (N,C,H,W) - ego_agent_feature = batch_node_features[b][i].unsqueeze(0).repeat(N, 1, 1, 1) - # (N,2C,H,W) - neighbor_feature = torch.cat([neighbor_feature, ego_agent_feature], dim=1) - # (N,C,H,W) - message = self.msg_cnn(neighbor_feature) * mask - - # (C,H,W) - if self.agg_operator == "avg": - agg_feature = torch.mean(message, dim=0) - elif self.agg_operator == "max": - agg_feature = torch.max(message, dim=0)[0] - else: - raise ValueError("agg_operator has wrong value") - # (2C, H, W) - cat_feature = torch.cat([batch_node_features[b][i, ...], agg_feature], dim=0) - # (C,H,W) - if self.gru_flag: - gru_out = self.conv_gru(cat_feature.unsqueeze(0).unsqueeze(0))[0][0].squeeze(0).squeeze(0) - else: - gru_out = batch_node_features[b][i, ...] + agg_feature - updated_node_features.append(gru_out.unsqueeze(0)) - # (N,C,H,W) - batch_updated_node_features.append(torch.cat(updated_node_features, dim=0)) - batch_node_features = batch_updated_node_features - # (B,C,H,W) - out = torch.cat([itm[0, ...].unsqueeze(0) for itm in batch_node_features], dim=0) - # (B,C,H,W) - out = self.mlp(out.permute(0, 2, 3, 1)).permute(0, 3, 1, 2) - - return out diff --git a/OpenCOOD/opencood/models/fuse_modules/v2xvit_basic.py b/OpenCOOD/opencood/models/fuse_modules/v2xvit_basic.py deleted file mode 100644 index 6874ee2c..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/v2xvit_basic.py +++ /dev/null @@ -1,190 +0,0 @@ -import math - -import torch -from torch import nn - -from opencood.models.fuse_modules.hmsa import HGTCavAttention -from opencood.models.fuse_modules.mswin import PyramidWindowAttention -from opencood.models.sub_modules.base_transformer import CavAttention, FeedForward, PreNorm -from opencood.models.sub_modules.torch_transformation_utils import ( - get_discretized_transformation_matrix, - get_roi_and_cav_mask, - get_transformation_matrix, - warp_affine, -) - - -class STTF(nn.Module): - def __init__(self, args): - super(STTF, self).__init__() - self.discrete_ratio = args["voxel_size"][0] - self.downsample_rate = args["downsample_rate"] - - def forward(self, x, mask, spatial_correction_matrix): - x = x.permute(0, 1, 4, 2, 3) - dist_correction_matrix = get_discretized_transformation_matrix(spatial_correction_matrix, self.discrete_ratio, self.downsample_rate) - # Only compensate non-ego vehicles - B, L, C, H, W = x.shape - - T = get_transformation_matrix(dist_correction_matrix[:, 1:, :, :].reshape(-1, 2, 3), (H, W)) - cav_features = warp_affine(x[:, 1:, :, :, :].reshape(-1, C, H, W), T, (H, W)) - cav_features = cav_features.reshape(B, -1, C, H, W) - x = torch.cat([x[:, 0, :, :, :].unsqueeze(1), cav_features], dim=1) - x = x.permute(0, 1, 3, 4, 2) - return x - - -class RelTemporalEncoding(nn.Module): - """ - Implement the Temporal Encoding (Sinusoid) function. - """ - - def __init__(self, n_hid, RTE_ratio, max_len=100, dropout=0.2): - super(RelTemporalEncoding, self).__init__() - position = torch.arange(0.0, max_len).unsqueeze(1) - div_term = torch.exp(torch.arange(0, n_hid, 2) * -(math.log(10000.0) / n_hid)) - emb = nn.Embedding(max_len, n_hid) - emb.weight.data[:, 0::2] = torch.sin(position * div_term) / math.sqrt(n_hid) - emb.weight.data[:, 1::2] = torch.cos(position * div_term) / math.sqrt(n_hid) - emb.requires_grad = False - self.RTE_ratio = RTE_ratio - self.emb = emb - self.lin = nn.Linear(n_hid, n_hid) - - def forward(self, x, t): - # When t has unit of 50ms, rte_ratio=1. - # So we can train on 100ms but test on 50ms - return x + self.lin(self.emb(t * self.RTE_ratio)).unsqueeze(0).unsqueeze(1) - - -class RTE(nn.Module): - def __init__(self, dim, RTE_ratio=2): - super(RTE, self).__init__() - self.RTE_ratio = RTE_ratio - - self.emb = RelTemporalEncoding(dim, RTE_ratio=self.RTE_ratio) - - def forward(self, x, dts): - # x: (B,L,H,W,C) - # dts: (B,L) - rte_batch = [] - for b in range(x.shape[0]): - rte_list = [] - for i in range(x.shape[1]): - rte_list.append(self.emb(x[b, i, :, :, :], dts[b, i]).unsqueeze(0)) - rte_batch.append(torch.cat(rte_list, dim=0).unsqueeze(0)) - return torch.cat(rte_batch, dim=0) - - -class V2XFusionBlock(nn.Module): - def __init__(self, num_blocks, cav_att_config, pwindow_config): - super().__init__() - # first multi-agent attention and then multi-window attention - self.layers = nn.ModuleList([]) - self.num_blocks = num_blocks - - for _ in range(num_blocks): - att = ( - HGTCavAttention( - cav_att_config["dim"], heads=cav_att_config["heads"], dim_head=cav_att_config["dim_head"], dropout=cav_att_config["dropout"] - ) - if cav_att_config["use_hetero"] - else CavAttention( - cav_att_config["dim"], heads=cav_att_config["heads"], dim_head=cav_att_config["dim_head"], dropout=cav_att_config["dropout"] - ) - ) - self.layers.append( - nn.ModuleList( - [ - PreNorm(cav_att_config["dim"], att), - PreNorm( - cav_att_config["dim"], - PyramidWindowAttention( - pwindow_config["dim"], - heads=pwindow_config["heads"], - dim_heads=pwindow_config["dim_head"], - drop_out=pwindow_config["dropout"], - window_size=pwindow_config["window_size"], - relative_pos_embedding=pwindow_config["relative_pos_embedding"], - fuse_method=pwindow_config["fusion_method"], - ), - ), - ] - ) - ) - - def forward(self, x, mask, prior_encoding): - for cav_attn, pwindow_attn in self.layers: - x = cav_attn(x, mask=mask, prior_encoding=prior_encoding) + x - x = pwindow_attn(x) + x - return x - - -class V2XTEncoder(nn.Module): - def __init__(self, args): - super().__init__() - - cav_att_config = args["cav_att_config"] - pwindow_att_config = args["pwindow_att_config"] - feed_config = args["feed_forward"] - - num_blocks = args["num_blocks"] - depth = args["depth"] - mlp_dim = feed_config["mlp_dim"] - dropout = feed_config["dropout"] - - self.downsample_rate = args["sttf"]["downsample_rate"] - self.discrete_ratio = args["sttf"]["voxel_size"][0] - self.use_roi_mask = args["use_roi_mask"] - self.use_RTE = cav_att_config["use_RTE"] - self.RTE_ratio = cav_att_config["RTE_ratio"] - self.sttf = STTF(args["sttf"]) - # adjust the channel numbers from 256+3 -> 256 - self.prior_feed = nn.Linear(cav_att_config["dim"] + 3, cav_att_config["dim"]) # noqa: DC05 - self.layers = nn.ModuleList([]) - if self.use_RTE: - self.rte = RTE(cav_att_config["dim"], self.RTE_ratio) - for _ in range(depth): - self.layers.append( - nn.ModuleList( - [ - V2XFusionBlock(num_blocks, cav_att_config, pwindow_att_config), - PreNorm(cav_att_config["dim"], FeedForward(cav_att_config["dim"], mlp_dim, dropout=dropout)), - ] - ) - ) - - def forward(self, x, mask, spatial_correction_matrix): - # transform the features to the current timestamp - # velocity, time_delay, infra - # (B,L,H,W,3) - prior_encoding = x[..., -3:] - # (B,L,H,W,C) - x = x[..., :-3] - if self.use_RTE: - # dt: (B,L) - dt = prior_encoding[:, :, 0, 0, 1].to(torch.int) - x = self.rte(x, dt) - x = self.sttf(x, mask, spatial_correction_matrix) - com_mask = ( - mask.unsqueeze(1).unsqueeze(2).unsqueeze(3) - if not self.use_roi_mask - else get_roi_and_cav_mask(x.shape, mask, spatial_correction_matrix, self.discrete_ratio, self.downsample_rate) - ) - for attn, ff in self.layers: - x = attn(x, mask=com_mask, prior_encoding=prior_encoding) - x = ff(x) + x - return x - - -class V2XTransformer(nn.Module): - def __init__(self, args): - super(V2XTransformer, self).__init__() - - encoder_args = args["encoder"] - self.encoder = V2XTEncoder(encoder_args) - - def forward(self, x, mask, spatial_correction_matrix): - output = self.encoder(x, mask, spatial_correction_matrix) - output = output[:, 0] - return output diff --git a/OpenCOOD/opencood/models/fuse_modules/where2comm_fuse.py b/OpenCOOD/opencood/models/fuse_modules/where2comm_fuse.py deleted file mode 100644 index 6d4b3665..00000000 --- a/OpenCOOD/opencood/models/fuse_modules/where2comm_fuse.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -Implementation of Where2comm fusion. -""" - -import numpy as np -import random -import torch -import torch.nn as nn -import torch.nn.functional as F - -from opencood.models.fuse_modules.self_attn import ScaledDotProductAttention - - -class Communication(nn.Module): - def __init__(self, args): - super(Communication, self).__init__() - # Threshold of objectiveness - self.threshold = args["threshold"] - if "gaussian_smooth" in args: - # Gaussian Smooth - self.smooth = True - kernel_size = args["gaussian_smooth"]["k_size"] - c_sigma = args["gaussian_smooth"]["c_sigma"] - self.gaussian_filter = nn.Conv2d(1, 1, kernel_size=kernel_size, stride=1, padding=(kernel_size - 1) // 2) - self.init_gaussian_filter(kernel_size, c_sigma) - self.gaussian_filter.requires_grad = False - else: - self.smooth = False - - def init_gaussian_filter(self, k_size=5, sigma=1.0): - center = k_size // 2 - x, y = np.mgrid[0 - center : k_size - center, 0 - center : k_size - center] - gaussian_kernel = 1 / (2 * np.pi * sigma) * np.exp(-(np.square(x) + np.square(y)) / (2 * np.square(sigma))) - - self.gaussian_filter.weight.data = torch.Tensor(gaussian_kernel).to(self.gaussian_filter.weight.device).unsqueeze(0).unsqueeze(0) - self.gaussian_filter.bias.data.zero_() - - def build_mask(self, confidence_map, *, force_first: bool): - """ - Build the spatial transmission mask for one group of agents. - - Parameters - ---------- - confidence_map : torch.Tensor - Per-agent classification logits. - force_first : bool - Whether the first feature belongs to the local receiver and must - remain unmasked. - - Returns - ------- - tuple[torch.Tensor, torch.Tensor] - Binary communication mask and selected-location ratio. - """ - _, _, height, width = confidence_map.shape - original_map, _ = confidence_map.sigmoid().max(dim=1, keepdim=True) - communication_map = self.gaussian_filter(original_map) if self.smooth else original_map - agent_count = communication_map.shape[0] - - if self.training: - selected_count = int(height * width * random.uniform(0, 1)) - flattened_map = communication_map.reshape(agent_count, height * width) - _, indices = torch.topk(flattened_map, k=selected_count, sorted=False) - communication_mask = torch.zeros_like(flattened_map) - selected_values = torch.ones( - agent_count, - selected_count, - dtype=communication_map.dtype, - device=communication_map.device, - ) - communication_mask = torch.scatter( - communication_mask, - -1, - indices, - selected_values, - ).reshape(agent_count, 1, height, width) - elif self.threshold: - communication_mask = torch.where( - communication_map > self.threshold, - torch.ones_like(communication_map), - torch.zeros_like(communication_map), - ) - else: - communication_mask = torch.ones_like(communication_map) - - communication_rate = communication_mask.sum() / (agent_count * height * width) - if force_first: - communication_mask[0] = 1 - return communication_mask, communication_rate - - def forward(self, batch_confidence_maps, B): - """ - Args: - batch_confidence_maps: [(L1, H, W), (L2, H, W), ...] - """ - - communication_masks = [] - communication_rates = [] - for b in range(B): - communication_mask, communication_rate = self.build_mask( - batch_confidence_maps[b], - force_first=True, - ) - communication_masks.append(communication_mask) - communication_rates.append(communication_rate) - communication_rates = sum(communication_rates) / B - communication_masks = torch.cat(communication_masks, dim=0) - return communication_masks, communication_rates - - -class AttentionFusion(nn.Module): - def __init__(self, feature_dim): - super(AttentionFusion, self).__init__() - self.att = ScaledDotProductAttention(feature_dim) - - def forward(self, x): - cav_num, C, H, W = x.shape - x = x.view(cav_num, C, -1).permute(2, 0, 1) # (H*W, cav_num, C), perform self attention on each pixel - x = self.att(x, x, x) - x = x.permute(1, 2, 0).view(cav_num, C, H, W)[0] # C, W, H before - return x - - -class Where2comm(nn.Module): - def __init__(self, args): - super(Where2comm, self).__init__() - self.discrete_ratio = args["voxel_size"][0] - self.downsample_rate = args["downsample_rate"] - - self.fully = args["fully"] - if self.fully: - print("constructing a fully connected communication graph") - else: - print("constructing a partially connected communication graph") - - self.multi_scale = args["multi_scale"] - if self.multi_scale: - layer_nums = args["layer_nums"] - num_filters = args["num_filters"] - self.num_levels = len(layer_nums) - self.fuse_modules = nn.ModuleList() - for idx in range(self.num_levels): - fuse_network = AttentionFusion(num_filters[idx]) - self.fuse_modules.append(fuse_network) - else: - self.fuse_modules = AttentionFusion(args["in_channels"]) - - self.naive_communication = Communication(args["communication"]) - - def regroup(self, x, record_len): - cum_sum_len = torch.cumsum(record_len, dim=0) - split_x = torch.tensor_split(x, cum_sum_len[:-1].cpu()) - return split_x - - def forward(self, x, psm_single, record_len, pairwise_t_matrix, backbone=None): - """ - Fusion forwarding. - - Parameters: - x: Input data, (sum(n_cav), C, H, W). - record_len: List, (B). - pairwise_t_matrix: The transformation matrix from each cav to ego, (B, L, L, 4, 4). - - Returns: - Fused feature. - """ - - _, C, H, W = x.shape - B = pairwise_t_matrix.shape[0] - - if self.multi_scale: - ups = [] - - for i in range(self.num_levels): - x = backbone.blocks[i](x) - - # 1. Communication (mask the features) - if i == 0: - if self.fully: - communication_rates = torch.tensor(1).to(x.device) - else: - # Prune - batch_confidence_maps = self.regroup(psm_single, record_len) - communication_masks, communication_rates = self.naive_communication(batch_confidence_maps, B) - if x.shape[-1] != communication_masks.shape[-1]: - communication_masks = F.interpolate( - communication_masks, size=(x.shape[-2], x.shape[-1]), mode="bilinear", align_corners=False - ) - x = x * communication_masks - - # 2. Split the features - # split_x: [(L1, C, H, W), (L2, C, H, W), ...] - # For example [[2, 256, 48, 176], [1, 256, 48, 176], ...] - batch_node_features = self.regroup(x, record_len) - - # 3. Fusion - x_fuse = [] - for b in range(B): - neighbor_feature = batch_node_features[b] - x_fuse.append(self.fuse_modules[i](neighbor_feature)) - x_fuse = torch.stack(x_fuse) - - # 4. Deconv - if len(backbone.deblocks) > 0: - ups.append(backbone.deblocks[i](x_fuse)) - else: - ups.append(x_fuse) - - if len(ups) > 1: - x_fuse = torch.cat(ups, dim=1) - elif len(ups) == 1: - x_fuse = ups[0] - - if len(backbone.deblocks) > self.num_levels: - x_fuse = backbone.deblocks[-1](x_fuse) - else: - # 1. Communication (mask the features) - if self.fully: - communication_rates = torch.tensor(1).to(x.device) - else: - # Prune - batch_confidence_maps = self.regroup(psm_single, record_len) - communication_masks, communication_rates = self.naive_communication(batch_confidence_maps, B) - x = x * communication_masks - - # 2. Split the features - # split_x: [(L1, C, H, W), (L2, C, H, W), ...] - # For example [[2, 256, 48, 176], [1, 256, 48, 176], ...] - batch_node_features = self.regroup(x, record_len) - - # 3. Fusion - x_fuse = [] - for b in range(B): - neighbor_feature = batch_node_features[b] - x_fuse.append(self.fuse_modules(neighbor_feature)) - x_fuse = torch.stack(x_fuse) - return x_fuse, communication_rates diff --git a/OpenCOOD/opencood/models/pixor.py b/OpenCOOD/opencood/models/pixor.py deleted file mode 100644 index 4f54753b..00000000 --- a/OpenCOOD/opencood/models/pixor.py +++ /dev/null @@ -1,266 +0,0 @@ -import math - -import torch.nn as nn - - -def conv3x3(in_planes, out_planes, stride=1, bias=False): - """3x3 convolution with padding""" - return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=bias) - - -class BasicBlock(nn.Module): - expansion = 1 - - def __init__(self, in_planes, planes, stride=1, downsample=None): - super(BasicBlock, self).__init__() - self.conv1 = conv3x3(in_planes, planes, stride, bias=True) - self.bn1 = nn.BatchNorm2d(planes) - self.relu = nn.ReLU(inplace=True) - self.conv2 = conv3x3(planes, planes, bias=True) - self.bn2 = nn.BatchNorm2d(planes) - self.downsample = downsample - self.stride = stride - - def forward(self, x): - residual = x - - out = self.conv1(x) - # out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - # out = self.bn2(out) - - if self.downsample is not None: - residual = self.downsample(x) - - out += residual - out = self.relu(out) - - return out - - -class Bottleneck(nn.Module): - expansion = 4 - - def __init__(self, in_planes, planes, stride=1, downsample=None, use_bn=True): - super(Bottleneck, self).__init__() - bias = not use_bn - self.use_bn = use_bn - self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=bias) - self.bn1 = nn.BatchNorm2d(planes) - self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=bias) - self.bn2 = nn.BatchNorm2d(planes) - self.conv3 = nn.Conv2d(planes, self.expansion * planes, kernel_size=1, bias=bias) - self.bn3 = nn.BatchNorm2d(self.expansion * planes) - self.downsample = downsample - self.relu = nn.ReLU(inplace=True) - - def forward(self, x): - """ - Forward pass of residual block. - Parameters - ---------- - x : torch.Tensor - Shape (N, C, W, L). - - Returns - ------- - out : torch.Tensor - Shape (N, self.expansion*planes, W/stride, L/stride). - """ - residual = x - # (N, planes, W, L) - out = self.conv1(x) - if self.use_bn: - out = self.bn1(out) - out = self.relu(out) - # (N, planes, W/stride, L/stride) - out = self.conv2(out) - if self.use_bn: - out = self.bn2(out) - out = self.relu(out) - # (N, self.expansion*planes, W/stride, L/stride) - out = self.conv3(out) - if self.use_bn: - out = self.bn3(out) - - if self.downsample is not None: - # (N, self.expansion*planes, W/2, L/2) - residual = self.downsample(x) - out = self.relu(residual + out) - return out - - -class BackBone(nn.Module): - def __init__(self, block, num_block, geom, use_bn=True): - super(BackBone, self).__init__() - - self.use_bn = use_bn - - # Block 1 - self.conv1 = conv3x3(geom["input_shape"][-1], 32) - self.conv2 = conv3x3(32, 32) - self.bn1 = nn.BatchNorm2d(32) - self.bn2 = nn.BatchNorm2d(32) - self.relu = nn.ReLU(inplace=True) - - # Block 2-5 - self.in_planes = 32 - self.block2 = self._make_layer(block, 24, num_blocks=num_block[0]) - self.block3 = self._make_layer(block, 48, num_blocks=num_block[1]) - self.block4 = self._make_layer(block, 64, num_blocks=num_block[2]) - self.block5 = self._make_layer(block, 96, num_blocks=num_block[3]) - - # Lateral layers - self.latlayer1 = nn.Conv2d(384, 196, kernel_size=1, stride=1, padding=0) - self.latlayer2 = nn.Conv2d(256, 128, kernel_size=1, stride=1, padding=0) - self.latlayer3 = nn.Conv2d(192, 96, kernel_size=1, stride=1, padding=0) - - # Top-down layers - self.deconv1 = nn.ConvTranspose2d(196, 128, kernel_size=3, stride=2, padding=1, output_padding=1) - p = 0 if geom["label_shape"][1] == 175 else 1 - self.deconv2 = nn.ConvTranspose2d(128, 96, kernel_size=3, stride=2, padding=1, output_padding=(1, p)) - - def encode(self, x): - x = self.conv1(x) - if self.use_bn: - x = self.bn1(x) - x = self.relu(x) - - x = self.conv2(x) - if self.use_bn: - x = self.bn2(x) - c1 = self.relu(x) - - # bottom up layers - c2 = self.block2(c1) - c3 = self.block3(c2) - c4 = self.block4(c3) - c5 = self.block5(c4) - - return c3, c4, c5 - - def decode(self, c3, c4, c5): - l5 = self.latlayer1(c5) - l4 = self.latlayer2(c4) - p5 = l4 + self.deconv1(l5) - l3 = self.latlayer3(c3) - p4 = l3 + self.deconv2(p5) - - return p4 - - def forward(self, x): - c3, c4, c5 = self.encode(x) - p4 = self.decode(c3, c4, c5) - - return p4 - - def _make_layer(self, block, planes, num_blocks): - if self.use_bn: - # downsample the H*W by 1/2 - downsample = nn.Sequential( - nn.Conv2d(self.in_planes, planes * block.expansion, kernel_size=1, stride=2, bias=False), nn.BatchNorm2d(planes * block.expansion) - ) - else: - downsample = nn.Conv2d(self.in_planes, planes * block.expansion, kernel_size=1, stride=2, bias=True) - - layers = [block(self.in_planes, planes, stride=2, downsample=downsample)] - - self.in_planes = planes * block.expansion - for i in range(1, num_blocks): - layers.append(block(self.in_planes, planes, stride=1)) - self.in_planes = planes * block.expansion - return nn.Sequential(*layers) - - -class Header(nn.Module): - def __init__(self, use_bn=True): - super(Header, self).__init__() - - self.use_bn = use_bn - bias = not use_bn - self.conv1 = conv3x3(96, 96, bias=bias) - self.bn1 = nn.BatchNorm2d(96) - self.conv2 = conv3x3(96, 96, bias=bias) - self.bn2 = nn.BatchNorm2d(96) - self.conv3 = conv3x3(96, 96, bias=bias) - self.bn3 = nn.BatchNorm2d(96) - self.conv4 = conv3x3(96, 96, bias=bias) - self.bn4 = nn.BatchNorm2d(96) - - self.clshead = conv3x3(96, 1, bias=True) - self.reghead = conv3x3(96, 6, bias=True) - - def forward(self, x): - x = self.conv1(x) - if self.use_bn: - x = self.bn1(x) - x = self.conv2(x) - if self.use_bn: - x = self.bn2(x) - x = self.conv3(x) - if self.use_bn: - x = self.bn3(x) - x = self.conv4(x) - if self.use_bn: - x = self.bn4(x) - - cls = self.clshead(x) - reg = self.reghead(x) - - return cls, reg - - -class PIXOR(nn.Module): - """ - The Pixor backbone. The input of PIXOR nn module is a tensor of - [batch_size, height, weight, channel], The output of PIXOR nn module - is also a tensor of [batch_size, height/4, weight/4, channel]. Note that - we convert the dimensions to [C, H, W] for PyTorch's nn.Conv2d functions - - Parameters - ---------- - args : dict - The arguments of the model. - - Attributes - ---------- - backbone : opencood.object - The backbone used to extract features. - header : opencood.object - Header used to predict the classification and coordinates. - """ - - def __init__(self, args): - super(PIXOR, self).__init__() - geom = args["geometry_param"] - use_bn = args["use_bn"] - self.backbone = BackBone(Bottleneck, [3, 6, 6, 3], geom, use_bn) - self.header = Header(use_bn) - - for m in self.modules(): - if isinstance(m, nn.Conv2d): - n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels - m.weight.data.normal_(0, math.sqrt(2.0 / n)) - elif isinstance(m, nn.BatchNorm2d): - m.weight.data.fill_(1) - m.bias.data.zero_() - - prior = 0.01 - self.header.clshead.weight.data.fill_(-math.log((1.0 - prior) / prior)) - self.header.clshead.bias.data.fill_(0) - self.header.reghead.weight.data.fill_(0) - self.header.reghead.bias.data.fill_(0) - - def forward(self, data_dict): - bev_input = data_dict["processed_lidar"]["bev_input"] - - features = self.backbone(bev_input) - # cls -- (N, 1, W/4, L/4) - # reg -- (N, 6, W/4, L/4) - cls, reg = self.header(features) - - output_dict = {"cls": cls, "reg": reg} - - return output_dict diff --git a/OpenCOOD/opencood/models/pixor_intermediate.py b/OpenCOOD/opencood/models/pixor_intermediate.py deleted file mode 100644 index ff880c59..00000000 --- a/OpenCOOD/opencood/models/pixor_intermediate.py +++ /dev/null @@ -1,139 +0,0 @@ -import math - -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import MultiScaleFeatureCommunicationAdapter -from opencood.models.fuse_modules.self_attn import AttFusion -from opencood.models.pixor import Bottleneck, BackBone, Header - - -class BackBoneIntermediate(BackBone): - def __init__(self, block, num_block, geom, use_bn=True): - super(BackBoneIntermediate, self).__init__(block, num_block, geom, use_bn) - - self.fusion_net3 = AttFusion(192) - self.fusion_net4 = AttFusion(256) - self.fusion_net5 = AttFusion(384) - - def forward(self, x, record_len): - return self.fuse_agents(self.encode(x), record_len) - - def fuse_agents(self, feature_maps, record_len): - """ - Fuse encoded PIXOR scales and run the feature pyramid decoder. - - Parameters - ---------- - feature_maps : tuple[torch.Tensor, torch.Tensor, torch.Tensor] - Per-agent ``c3``, ``c4``, and ``c5`` feature maps. - record_len : torch.Tensor - Number of available agents in each sample. - - Returns - ------- - torch.Tensor - Decoded fused PIXOR feature map. - """ - c3, c4, c5 = feature_maps - c5 = self.fusion_net5(c5, record_len) - c4 = self.fusion_net4(c4, record_len) - c3 = self.fusion_net3(c3, record_len) - return self.decode(c3, c4, c5) - - -class PIXORIntermediate(nn.Module): - """ - The Pixor backbone. The input of PIXOR nn module is a tensor of - [batch_size, height, weight, channel], The output of PIXOR nn module - is also a tensor of [batch_size, height/4, weight/4, channel]. Note that - we convert the dimensions to [C, H, W] for PyTorch's nn.Conv2d functions - - Parameters - ---------- - args : dict - The arguments of the model. - - Attributes - ---------- - backbone : opencood.object - The backbone used to extract features. - header : opencood.object - Header used to predict the classification and coordinates. - """ - - communication_adapter_class = MultiScaleFeatureCommunicationAdapter - - def __init__(self, args): - super(PIXORIntermediate, self).__init__() - geom = args["geometry_param"] - use_bn = args["use_bn"] - self.backbone = BackBoneIntermediate(Bottleneck, [3, 6, 6, 3], geom, use_bn) - self.header = Header(use_bn) - - for m in self.modules(): - if isinstance(m, nn.Conv2d): - n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels - m.weight.data.normal_(0, math.sqrt(2.0 / n)) - elif isinstance(m, nn.BatchNorm2d): - m.weight.data.fill_(1) - m.bias.data.zero_() - - prior = 0.01 - self.header.clshead.weight.data.fill_(-math.log((1.0 - prior) / prior)) - self.header.clshead.bias.data.fill_(0) - self.header.reghead.weight.data.fill_(0) - self.header.reghead.bias.data.fill_(0) - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - bev_input = data_dict["processed_lidar"]["bev_input"] - record_len = data_dict["record_len"] - - features = self.backbone(bev_input, record_len) - # cls -- (N, 1, W/4, L/4) - # reg -- (N, 6, W/4, L/4) - cls, reg = self.header(features) - - output_dict = {"cls": cls, "reg": reg} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into PIXOR's three private backbone scales. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed BEV input. - - Returns - ------- - dict - Ordered ``c3``, ``c4``, and ``c5`` feature maps. - """ - return { - "feature_maps": self.backbone.encode(data_dict["processed_lidar"]["bev_input"]), - } - - def fuse_agents(self, data_dict): - """ - Fuse received PIXOR features and run its detection header. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and ``record_len``. - - Returns - ------- - dict - PIXOR classification and regression maps. - """ - intermediate_features = data_dict["intermediate_features"] - feature_maps = tuple(intermediate_features[f"feature_{index}"] for index in range(3)) - features = self.backbone.fuse_agents(feature_maps, data_dict["record_len"]) - cls, reg = self.header(features) - return {"cls": cls, "reg": reg} diff --git a/OpenCOOD/opencood/models/point_pillar.py b/OpenCOOD/opencood/models/point_pillar.py deleted file mode 100644 index 3b6d12e6..00000000 --- a/OpenCOOD/opencood/models/point_pillar.py +++ /dev/null @@ -1,39 +0,0 @@ -import torch.nn as nn - - -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone - - -class PointPillar(nn.Module): - def __init__(self, args): - super(PointPillar, self).__init__() - - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = BaseBEVBackbone(args["base_bev_backbone"], 64) - - self.cls_head = nn.Conv2d(128 * 3, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(128 * 3, 7 * args["anchor_num"], kernel_size=1) - - def forward(self, data_dict): - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points} - - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - - psm = self.cls_head(spatial_features_2d) - rm = self.reg_head(spatial_features_2d) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict diff --git a/OpenCOOD/opencood/models/point_pillar_coalign.py b/OpenCOOD/opencood/models/point_pillar_coalign.py deleted file mode 100644 index d0193851..00000000 --- a/OpenCOOD/opencood/models/point_pillar_coalign.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -This file only contains CoAlign's multiscale intermediate feature fusion. -If you want build the agent-object pose graph to correct pose error, -please refer to https://github.com/yifanlu0227/CoAlign -""" - -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import SpatialFeatureCommunicationAdapter -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.res_bev_backbone import ResBEVBackbone -from opencood.models.sub_modules.naive_compress import NaiveCompressor -from opencood.models.sub_modules.downsample_conv import DownsampleConv -from opencood.models.fuse_modules.coalign_fuse import Att_w_Warp, normalize_pairwise_tfm - - -class PointPillarCoAlign(nn.Module): - communication_adapter_class = SpatialFeatureCommunicationAdapter - - def __init__(self, args): - super(PointPillarCoAlign, self).__init__() - - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = ResBEVBackbone(args["res_bev_backbone"], 64) - - self.voxel_size = args["voxel_size"] - self.first_layer_stride = args["res_bev_backbone"]["layer_strides"][0] - - # multiscale fusion network modules - self.fusion_net = nn.ModuleList() - for i in range(len(args["res_bev_backbone"]["layer_nums"])): - # If proj_first = True, no actual warping is performed - self.fusion_net.append(Att_w_Warp(args["res_bev_backbone"]["num_filters"][i])) - self.out_channel = sum(args["res_bev_backbone"]["num_upsample_filter"]) - - self.compression = False - if "compression" in args: - self.compression = True - self.naive_compressor = NaiveCompressor(args["res_bev_backbone"]["num_filters"][0], args["compression"]) - - # used to downsample the feature map for efficient computation - self.shrink_flag = False - if "shrink_header" in args: - self.shrink_flag = True - self.shrink_conv = DownsampleConv(args["shrink_header"]) - self.out_channel = args["shrink_header"]["dim"][-1] - - self.cls_head = nn.Conv2d(self.out_channel, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(self.out_channel, 7 * args["anchor_num"], kernel_size=1) - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - - # get affine matrix for feature warping - _, _, H0, W0 = batch_dict["spatial_features"].shape # original feature map shape H0, W0. - normalized_affine_matrix = normalize_pairwise_tfm(data_dict["pairwise_t_matrix"], H0, W0, self.voxel_size[0]) - - spatial_features = batch_dict["spatial_features"] - - # multiscale fusion - # The first scale feature 'feature_list[0]' for transmission. Default 100*352*64*32/1000000 = 72.0896 - feature_list = [] - feature_list.append(self.backbone.get_layer_i_feature(spatial_features, layer_i=0)) - if self.compression: - feature_list[0] = self.naive_compressor(feature_list[0]) - for i in range(1, self.backbone.num_levels): - feature_list.append(self.backbone.get_layer_i_feature(feature_list[i - 1], layer_i=i)) - - fused_feature_list = [] - for i, fuse_module in enumerate(self.fusion_net): - fused_feature_list.append(fuse_module(feature_list[i], record_len, normalized_affine_matrix)) - fused_feature = self.backbone.decode_multiscale_feature(fused_feature_list) - - # downsample feature to reduce memory - if self.shrink_flag: - fused_feature = self.shrink_conv(fused_feature) - - psm = self.cls_head(fused_feature) - rm = self.reg_head(fused_feature) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent up to CoAlign's first fusion scale. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - First-scale learned feature transmitted by CoAlign. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - spatial_features = self.backbone.get_layer_i_feature( - batch_dict["spatial_features"], - layer_i=0, - ) - if self.compression: - spatial_features = self.naive_compressor.encode(spatial_features) - return {"spatial_features": spatial_features} - - def fuse_agents(self, data_dict): - """ - Fuse delivered CoAlign features and run the detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and pairwise poses. - - Returns - ------- - dict - Classification and regression maps. - """ - first_scale = data_dict["intermediate_features"]["spatial_features"] - if self.compression: - first_scale = self.naive_compressor.decode(first_scale) - _, _, height, width = first_scale.shape - normalized_affine_matrix = normalize_pairwise_tfm( - data_dict["pairwise_t_matrix"], - height, - width, - self.voxel_size[0], - downsample_rate=self.first_layer_stride, - ) - - feature_list = [first_scale] - for level in range(1, self.backbone.num_levels): - feature_list.append( - self.backbone.get_layer_i_feature( - feature_list[level - 1], - layer_i=level, - ) - ) - fused_feature_list = [ - fuse_module( - feature_list[level], - data_dict["record_len"], - normalized_affine_matrix, - ) - for level, fuse_module in enumerate(self.fusion_net) - ] - fused_feature = self.backbone.decode_multiscale_feature(fused_feature_list) - if self.shrink_flag: - fused_feature = self.shrink_conv(fused_feature) - return { - "psm": self.cls_head(fused_feature), - "rm": self.reg_head(fused_feature), - } diff --git a/OpenCOOD/opencood/models/point_pillar_cobevt.py b/OpenCOOD/opencood/models/point_pillar_cobevt.py deleted file mode 100644 index 7e10c49b..00000000 --- a/OpenCOOD/opencood/models/point_pillar_cobevt.py +++ /dev/null @@ -1,169 +0,0 @@ -import torch.nn as nn -from einops import repeat - -from opencood.models.communication_adapters.intermediate import SpatialFeatureCommunicationAdapter -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone -from opencood.models.sub_modules.downsample_conv import DownsampleConv -from opencood.models.sub_modules.naive_compress import NaiveCompressor -from opencood.models.fuse_modules.swap_fusion_modules import SwapFusionEncoder -from opencood.models.fuse_modules.fuse_utils import regroup - - -class PointPillarCoBEVT(nn.Module): - communication_adapter_class = SpatialFeatureCommunicationAdapter - - def __init__(self, args): - super(PointPillarCoBEVT, self).__init__() - - self.max_cav = args["max_cav"] - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = BaseBEVBackbone(args["base_bev_backbone"], 64) - # used to downsample the feature map for efficient computation - self.shrink_flag = False - if "shrink_header" in args: - self.shrink_flag = True - self.shrink_conv = DownsampleConv(args["shrink_header"]) - self.compression = False - - if args["compression"] > 0: - self.compression = True - self.naive_compressor = NaiveCompressor(256, args["compression"]) - - self.fusion_net = SwapFusionEncoder(args["fax_fusion"]) - - self.cls_head = nn.Conv2d(128 * 2, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(128 * 2, 7 * args["anchor_number"], kernel_size=1) - - if args["backbone_fix"]: - self.backbone_fix() - - def backbone_fix(self): - """ - Fix the parameters of backbone during finetune on timedelay。 - """ - for p in self.pillar_vfe.parameters(): - p.requires_grad = False - - for p in self.scatter.parameters(): - p.requires_grad = False - - for p in self.backbone.parameters(): - p.requires_grad = False - - if self.compression: - for p in self.naive_compressor.parameters(): - p.requires_grad = False - if self.shrink_flag: - for p in self.shrink_conv.parameters(): - p.requires_grad = False - - for p in self.cls_head.parameters(): - p.requires_grad = False - for p in self.reg_head.parameters(): - p.requires_grad = False - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - # n, 4 -> n, c - batch_dict = self.pillar_vfe(batch_dict) - # n, c -> N, C, H, W - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - # downsample feature to reduce memory - if self.shrink_flag: - spatial_features_2d = self.shrink_conv(spatial_features_2d) - # compressor - if self.compression: - spatial_features_2d = self.naive_compressor(spatial_features_2d) - - # N, C, H, W -> B, L, C, H, W - regroup_feature, mask = regroup(spatial_features_2d, record_len, self.max_cav) - com_mask = mask.unsqueeze(1).unsqueeze(2).unsqueeze(3) - com_mask = repeat(com_mask, "b h w c l -> b (h new_h) (w new_w) c l", new_h=regroup_feature.shape[3], new_w=regroup_feature.shape[4]) - - fused_feature = self.fusion_net(regroup_feature, com_mask) - - psm = self.cls_head(fused_feature) - rm = self.reg_head(fused_feature) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into the feature map exchanged by CoBEVT. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - Learned spatial feature map before Swap Fusion. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - spatial_features = batch_dict["spatial_features_2d"] - if self.shrink_flag: - spatial_features = self.shrink_conv(spatial_features) - if self.compression: - spatial_features = self.naive_compressor.encode(spatial_features) - return {"spatial_features": spatial_features} - - def fuse_agents(self, data_dict): - """ - Fuse delivered CoBEVT features and run the detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and ``record_len``. - - Returns - ------- - dict - Classification and regression maps. - """ - spatial_features = data_dict["intermediate_features"]["spatial_features"] - if self.compression: - spatial_features = self.naive_compressor.decode(spatial_features) - regroup_feature, mask = regroup( - spatial_features, - data_dict["record_len"], - self.max_cav, - ) - communication_mask = repeat( - mask.unsqueeze(1).unsqueeze(2).unsqueeze(3), - "b h w c l -> b (h new_h) (w new_w) c l", - new_h=regroup_feature.shape[3], - new_w=regroup_feature.shape[4], - ) - fused_feature = self.fusion_net(regroup_feature, communication_mask) - return { - "psm": self.cls_head(fused_feature), - "rm": self.reg_head(fused_feature), - } diff --git a/OpenCOOD/opencood/models/point_pillar_fcooper.py b/OpenCOOD/opencood/models/point_pillar_fcooper.py deleted file mode 100644 index ff433c3a..00000000 --- a/OpenCOOD/opencood/models/point_pillar_fcooper.py +++ /dev/null @@ -1,158 +0,0 @@ -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import SpatialFeatureCommunicationAdapter -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone -from opencood.models.sub_modules.downsample_conv import DownsampleConv -from opencood.models.sub_modules.naive_compress import NaiveCompressor -from opencood.models.fuse_modules.f_cooper_fuse import SpatialFusion - - -class PointPillarFCooper(nn.Module): - """ - F-Cooper implementation with point pillar backbone. - """ - - communication_adapter_class = SpatialFeatureCommunicationAdapter - - def __init__(self, args): - super(PointPillarFCooper, self).__init__() - - self.max_cav = args["max_cav"] - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = BaseBEVBackbone(args["base_bev_backbone"], 64) - # used to downsample the feature map for efficient computation - self.shrink_flag = False - if "shrink_header" in args: - self.shrink_flag = True - self.shrink_conv = DownsampleConv(args["shrink_header"]) - self.compression = False - - if args["compression"] > 0: - self.compression = True - self.naive_compressor = NaiveCompressor(256, args["compression"]) - - self.fusion_net = SpatialFusion() - - self.cls_head = nn.Conv2d(128 * 2, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(128 * 2, 7 * args["anchor_number"], kernel_size=1) - - if args["backbone_fix"]: - self.backbone_fix() - - def backbone_fix(self): - """ - Fix the parameters of backbone during finetune on timedelay。 - """ - for p in self.pillar_vfe.parameters(): - p.requires_grad = False - - for p in self.scatter.parameters(): - p.requires_grad = False - - for p in self.backbone.parameters(): - p.requires_grad = False - - if self.compression: - for p in self.naive_compressor.parameters(): - p.requires_grad = False - if self.shrink_flag: - for p in self.shrink_conv.parameters(): - p.requires_grad = False - - for p in self.cls_head.parameters(): - p.requires_grad = False - for p in self.reg_head.parameters(): - p.requires_grad = False - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - # n, 4 -> n, c - batch_dict = self.pillar_vfe(batch_dict) - # n, c -> N, C, H, W - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - # downsample feature to reduce memory - if self.shrink_flag: - spatial_features_2d = self.shrink_conv(spatial_features_2d) - # compressor - if self.compression: - spatial_features_2d = self.naive_compressor(spatial_features_2d) - - fused_feature = self.fusion_net(spatial_features_2d, record_len) - - psm = self.cls_head(fused_feature) - rm = self.reg_head(fused_feature) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into the BEV feature exchanged by F-Cooper. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - Learned spatial feature map before F-Cooper fusion. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - spatial_features = batch_dict["spatial_features_2d"] - if self.shrink_flag: - spatial_features = self.shrink_conv(spatial_features) - if self.compression: - spatial_features = self.naive_compressor.encode(spatial_features) - return {"spatial_features": spatial_features} - - def fuse_agents(self, data_dict): - """ - Fuse delivered F-Cooper features and run the detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and ``record_len``. - - Returns - ------- - dict - Classification and regression maps. - """ - spatial_features = data_dict["intermediate_features"]["spatial_features"] - if self.compression: - spatial_features = self.naive_compressor.decode(spatial_features) - fused_feature = self.fusion_net( - spatial_features, - data_dict["record_len"], - ) - return { - "psm": self.cls_head(fused_feature), - "rm": self.reg_head(fused_feature), - } diff --git a/OpenCOOD/opencood/models/point_pillar_intermediate.py b/OpenCOOD/opencood/models/point_pillar_intermediate.py deleted file mode 100644 index b0e625d2..00000000 --- a/OpenCOOD/opencood/models/point_pillar_intermediate.py +++ /dev/null @@ -1,93 +0,0 @@ -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import MultiScaleFeatureCommunicationAdapter -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.att_bev_backbone import AttBEVBackbone - - -class PointPillarIntermediate(nn.Module): - communication_adapter_class = MultiScaleFeatureCommunicationAdapter - - def __init__(self, args): - super(PointPillarIntermediate, self).__init__() - - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = AttBEVBackbone(args["base_bev_backbone"], 64) - - self.cls_head = nn.Conv2d(128 * 3, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(128 * 3, 7 * args["anchor_num"], kernel_size=1) - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - - psm = self.cls_head(spatial_features_2d) - rm = self.reg_head(spatial_features_2d) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into private multi-scale PointPillar features. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - Ordered feature maps at the model's attention boundaries. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - return { - "feature_maps": self.backbone.encode_agent(batch_dict["spatial_features"]), - } - - def fuse_agents(self, data_dict): - """ - Fuse received feature scales and run PointPillar detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and ``record_len``. - - Returns - ------- - dict - Classification and regression maps. - """ - intermediate_features = data_dict["intermediate_features"] - feature_maps = tuple(intermediate_features[f"feature_{index}"] for index in range(len(self.backbone.fuse_modules))) - fused_features = self.backbone.fuse_agents(feature_maps, data_dict["record_len"]) - return { - "psm": self.cls_head(fused_features), - "rm": self.reg_head(fused_features), - } diff --git a/OpenCOOD/opencood/models/point_pillar_intermediate_V2VAM.py b/OpenCOOD/opencood/models/point_pillar_intermediate_V2VAM.py deleted file mode 100644 index 16974c33..00000000 --- a/OpenCOOD/opencood/models/point_pillar_intermediate_V2VAM.py +++ /dev/null @@ -1,162 +0,0 @@ -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import SpatialFeatureCommunicationAdapter -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone -from opencood.models.sub_modules.downsample_conv import DownsampleConv -from opencood.models.sub_modules.naive_compress import NaiveCompressor -from opencood.models.fuse_modules.V2VAM import V2V_AttFusion - - -class PointPillarintermediateV2VAM(nn.Module): - """ - V2VAM implementation with a PointPillar backbone. - """ - - communication_adapter_class = SpatialFeatureCommunicationAdapter - - def __init__(self, args): - super(PointPillarintermediateV2VAM, self).__init__() - - self.max_cav = args["max_cav"] - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = BaseBEVBackbone(args["base_bev_backbone"], 64) - # used to downsample the feature map for efficient computation - self.shrink_flag = False - if "shrink_header" in args: - self.shrink_flag = True - self.shrink_conv = DownsampleConv(args["shrink_header"]) - self.compression = False - - if args["compression"] > 0: - self.compression = True - print("self.compression: ", self.compression, args["compression"]) - self.naive_compressor = NaiveCompressor(256, args["compression"]) - - self.fusion_net = V2V_AttFusion(256) - - self.cls_head = nn.Conv2d(128 * 2, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(128 * 2, 7 * args["anchor_number"], kernel_size=1) - - if args["backbone_fix"]: - self.backbone_fix() - - def backbone_fix(self): - """ - Fix the parameters of backbone during finetune on timedelay。 - """ - for p in self.pillar_vfe.parameters(): - p.requires_grad = False - - for p in self.scatter.parameters(): - p.requires_grad = False - - for p in self.backbone.parameters(): - p.requires_grad = False - - if self.compression: - for p in self.naive_compressor.parameters(): - p.requires_grad = False - if self.shrink_flag: - for p in self.shrink_conv.parameters(): - p.requires_grad = False - - for p in self.cls_head.parameters(): - p.requires_grad = False - for p in self.reg_head.parameters(): - p.requires_grad = False - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - # n, 4 -> n, c - batch_dict = self.pillar_vfe(batch_dict) - # n, c -> N, C, H, W - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - - # downsample feature to reduce memory - if self.shrink_flag: - spatial_features_2d = self.shrink_conv(spatial_features_2d) - # compressor - if self.compression: - spatial_features_2d = self.naive_compressor(spatial_features_2d) - - fused_feature = self.fusion_net(spatial_features_2d, record_len) - - # pdb.set_trace() - - psm = self.cls_head(fused_feature) - rm = self.reg_head(fused_feature) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into the BEV feature exchanged by V2VAM. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - Learned spatial feature map before V2VAM fusion. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - spatial_features = batch_dict["spatial_features_2d"] - if self.shrink_flag: - spatial_features = self.shrink_conv(spatial_features) - if self.compression: - spatial_features = self.naive_compressor.encode(spatial_features) - return {"spatial_features": spatial_features} - - def fuse_agents(self, data_dict): - """ - Fuse delivered V2VAM features and run the detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and ``record_len``. - - Returns - ------- - dict - Classification and regression maps. - """ - spatial_features = data_dict["intermediate_features"]["spatial_features"] - if self.compression: - spatial_features = self.naive_compressor.decode(spatial_features) - fused_feature = self.fusion_net( - spatial_features, - data_dict["record_len"], - ) - return { - "psm": self.cls_head(fused_feature), - "rm": self.reg_head(fused_feature), - } diff --git a/OpenCOOD/opencood/models/point_pillar_transformer.py b/OpenCOOD/opencood/models/point_pillar_transformer.py deleted file mode 100644 index a3649181..00000000 --- a/OpenCOOD/opencood/models/point_pillar_transformer.py +++ /dev/null @@ -1,185 +0,0 @@ -import torch -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import SpatialFeatureCommunicationAdapter -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone -from opencood.models.fuse_modules.fuse_utils import regroup -from opencood.models.sub_modules.downsample_conv import DownsampleConv -from opencood.models.sub_modules.naive_compress import NaiveCompressor -from opencood.models.fuse_modules.v2xvit_basic import V2XTransformer - - -class PointPillarTransformer(nn.Module): - communication_adapter_class = SpatialFeatureCommunicationAdapter - - def __init__(self, args): - super(PointPillarTransformer, self).__init__() - - self.max_cav = args["max_cav"] - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = BaseBEVBackbone(args["base_bev_backbone"], 64) - # used to downsample the feature map for efficient computation - self.shrink_flag = False - if "shrink_header" in args: - self.shrink_flag = True - self.shrink_conv = DownsampleConv(args["shrink_header"]) - self.compression = False - - if args["compression"] > 0: - self.compression = True - self.naive_compressor = NaiveCompressor(256, args["compression"]) - - self.fusion_net = V2XTransformer(args["transformer"]) - - self.cls_head = nn.Conv2d(128 * 2, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(128 * 2, 7 * args["anchor_number"], kernel_size=1) - - if args["backbone_fix"]: - self.backbone_fix() - - def backbone_fix(self): - """ - Fix the parameters of backbone during finetune on timedelay。 - """ - for p in self.pillar_vfe.parameters(): - p.requires_grad = False - - for p in self.scatter.parameters(): - p.requires_grad = False - - for p in self.backbone.parameters(): - p.requires_grad = False - - if self.compression: - for p in self.naive_compressor.parameters(): - p.requires_grad = False - if self.shrink_flag: - for p in self.shrink_conv.parameters(): - p.requires_grad = False - - for p in self.cls_head.parameters(): - p.requires_grad = False - for p in self.reg_head.parameters(): - p.requires_grad = False - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - spatial_correction_matrix = data_dict["spatial_correction_matrix"] - - # B, max_cav, 3(dt dv infra), 1, 1 - prior_encoding = data_dict["prior_encoding"].unsqueeze(-1).unsqueeze(-1) - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - # n, 4 -> n, c - batch_dict = self.pillar_vfe(batch_dict) - # n, c -> N, C, H, W - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - # downsample feature to reduce memory - if self.shrink_flag: - spatial_features_2d = self.shrink_conv(spatial_features_2d) - # compressor - if self.compression: - spatial_features_2d = self.naive_compressor(spatial_features_2d) - # N, C, H, W -> B, L, C, H, W - regroup_feature, mask = regroup(spatial_features_2d, record_len, self.max_cav) - # prior encoding added - prior_encoding = prior_encoding.repeat(1, 1, 1, regroup_feature.shape[3], regroup_feature.shape[4]) - regroup_feature = torch.cat([regroup_feature, prior_encoding], dim=2) - - # b l c h w -> b l h w c - regroup_feature = regroup_feature.permute(0, 1, 3, 4, 2) - # transformer fusion - fused_feature = self.fusion_net(regroup_feature, mask, spatial_correction_matrix) - # b h w c -> b c h w - fused_feature = fused_feature.permute(0, 3, 1, 2) - - psm = self.cls_head(fused_feature) - rm = self.reg_head(fused_feature) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into the feature map exchanged by V2X-ViT. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - Learned spatial feature map before transformer fusion. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - spatial_features = batch_dict["spatial_features_2d"] - if self.shrink_flag: - spatial_features = self.shrink_conv(spatial_features) - if self.compression: - spatial_features = self.naive_compressor.encode(spatial_features) - return {"spatial_features": spatial_features} - - def fuse_agents(self, data_dict): - """ - Fuse delivered V2X-ViT features and run the detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and V2X-ViT metadata. - - Returns - ------- - dict - Classification and regression maps. - """ - spatial_features = data_dict["intermediate_features"]["spatial_features"] - if self.compression: - spatial_features = self.naive_compressor.decode(spatial_features) - regroup_feature, mask = regroup( - spatial_features, - data_dict["record_len"], - self.max_cav, - ) - prior_encoding = data_dict["prior_encoding"].unsqueeze(-1).unsqueeze(-1) - prior_encoding = prior_encoding.repeat( - 1, - 1, - 1, - regroup_feature.shape[3], - regroup_feature.shape[4], - ) - regroup_feature = torch.cat([regroup_feature, prior_encoding], dim=2) - fused_feature = self.fusion_net( - regroup_feature.permute(0, 1, 3, 4, 2), - mask, - data_dict["spatial_correction_matrix"], - ).permute(0, 3, 1, 2) - return { - "psm": self.cls_head(fused_feature), - "rm": self.reg_head(fused_feature), - } diff --git a/OpenCOOD/opencood/models/point_pillar_v2vnet.py b/OpenCOOD/opencood/models/point_pillar_v2vnet.py deleted file mode 100644 index 715bf8eb..00000000 --- a/OpenCOOD/opencood/models/point_pillar_v2vnet.py +++ /dev/null @@ -1,156 +0,0 @@ -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import SpatialFeatureCommunicationAdapter -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone -from opencood.models.sub_modules.downsample_conv import DownsampleConv -from opencood.models.sub_modules.naive_compress import NaiveCompressor -from opencood.models.fuse_modules.v2v_fuse import V2VNetFusion - - -class PointPillarV2VNet(nn.Module): - communication_adapter_class = SpatialFeatureCommunicationAdapter - - def __init__(self, args): - super(PointPillarV2VNet, self).__init__() - - self.max_cav = args["max_cav"] - # PIllar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = BaseBEVBackbone(args["base_bev_backbone"], 64) - - # used to downsample the feature map for efficient computation - self.shrink_flag = False - if "shrink_header" in args: - self.shrink_flag = True - self.shrink_conv = DownsampleConv(args["shrink_header"]) - self.compression = False - - if args["compression"] > 0: - self.compression = True - self.naive_compressor = NaiveCompressor(256, args["compression"]) - - self.fusion_net = V2VNetFusion(args["v2vfusion"]) - - self.cls_head = nn.Conv2d(128 * 2, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(128 * 2, 7 * args["anchor_number"], kernel_size=1) - if args["backbone_fix"]: - self.backbone_fix() - - def backbone_fix(self): - """ - Fix the parameters of backbone during finetune on timedelay。 - """ - for p in self.pillar_vfe.parameters(): - p.requires_grad = False - - for p in self.scatter.parameters(): - p.requires_grad = False - - for p in self.backbone.parameters(): - p.requires_grad = False - - if self.compression: - for p in self.naive_compressor.parameters(): - p.requires_grad = False - if self.shrink_flag: - for p in self.shrink_conv.parameters(): - p.requires_grad = False - - for p in self.cls_head.parameters(): - p.requires_grad = False - for p in self.reg_head.parameters(): - p.requires_grad = False - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - pairwise_t_matrix = data_dict["pairwise_t_matrix"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - # n, 4 -> n, c - batch_dict = self.pillar_vfe(batch_dict) - # n, c -> N, C, H, W - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - # downsample feature to reduce memory - if self.shrink_flag: - spatial_features_2d = self.shrink_conv(spatial_features_2d) - # compressor - if self.compression: - spatial_features_2d = self.naive_compressor(spatial_features_2d) - fused_feature = self.fusion_net(spatial_features_2d, record_len, pairwise_t_matrix) - - psm = self.cls_head(fused_feature) - rm = self.reg_head(fused_feature) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into the BEV feature exchanged by V2VNet. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - Learned spatial feature map before V2VNet fusion. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - spatial_features = batch_dict["spatial_features_2d"] - if self.shrink_flag: - spatial_features = self.shrink_conv(spatial_features) - if self.compression: - spatial_features = self.naive_compressor.encode(spatial_features) - return {"spatial_features": spatial_features} - - def fuse_agents(self, data_dict): - """ - Fuse delivered V2VNet features and run the detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing features, poses, and ``record_len``. - - Returns - ------- - dict - Classification and regression maps. - """ - spatial_features = data_dict["intermediate_features"]["spatial_features"] - if self.compression: - spatial_features = self.naive_compressor.decode(spatial_features) - fused_feature = self.fusion_net( - spatial_features, - data_dict["record_len"], - data_dict["pairwise_t_matrix"], - ) - return { - "psm": self.cls_head(fused_feature), - "rm": self.reg_head(fused_feature), - } diff --git a/OpenCOOD/opencood/models/point_pillar_where2comm.py b/OpenCOOD/opencood/models/point_pillar_where2comm.py deleted file mode 100644 index 2b0f6de5..00000000 --- a/OpenCOOD/opencood/models/point_pillar_where2comm.py +++ /dev/null @@ -1,227 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F - -from opencood.models.communication_adapters.where2comm import Where2CommCommunicationAdapter -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone -from opencood.models.fuse_modules.where2comm_fuse import Where2comm -from opencood.models.sub_modules.downsample_conv import DownsampleConv -from opencood.models.sub_modules.naive_compress import NaiveCompressor -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.sub_modules.point_pillar_scatter import PointPillarScatter - - -class PointPillarWhere2comm(nn.Module): - communication_adapter_class = Where2CommCommunicationAdapter - - def __init__(self, args): - super(PointPillarWhere2comm, self).__init__() - self.max_cav = args["max_cav"] - # Pillar VFE - self.pillar_vfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.scatter = PointPillarScatter(args["point_pillar_scatter"]) - self.backbone = BaseBEVBackbone(args["base_bev_backbone"], 64) - - # Used to down-sample the feature map for efficient computation - if "shrink_header" in args: - self.shrink_flag = True - self.shrink_conv = DownsampleConv(args["shrink_header"]) - else: - self.shrink_flag = False - - if args["compression"]: - self.compression = True - self.naive_compressor = NaiveCompressor(256, args["compression"]) - else: - self.compression = False - - self.fusion_net = Where2comm(args["where2comm_fusion"]) - self.multi_scale = args["where2comm_fusion"]["multi_scale"] - - self.cls_head = nn.Conv2d(args["head_dim"], args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(args["head_dim"], 7 * args["anchor_number"], kernel_size=1) - - if args["backbone_fix"]: - self.backbone_fix() - - def backbone_fix(self): - """ - Fix the parameters of backbone during finetune on timedelay. - """ - - for p in self.pillar_vfe.parameters(): - p.requires_grad = False - - for p in self.scatter.parameters(): - p.requires_grad = False - - for p in self.backbone.parameters(): - p.requires_grad = False - - if self.compression: - for p in self.naive_compressor.parameters(): - p.requires_grad = False - if self.shrink_flag: - for p in self.shrink_conv.parameters(): - p.requires_grad = False - - for p in self.cls_head.parameters(): - p.requires_grad = False - for p in self.reg_head.parameters(): - p.requires_grad = False - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - pairwise_t_matrix = data_dict["pairwise_t_matrix"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points, "record_len": record_len} - # n, 4 -> n, c - batch_dict = self.pillar_vfe(batch_dict) - # n, c -> N, C, H, W - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - # N, C, H', W': [N, 256, 48, 176] - spatial_features_2d = batch_dict["spatial_features_2d"] - # Down-sample feature to reduce memory - if self.shrink_flag: - spatial_features_2d = self.shrink_conv(spatial_features_2d) - - psm_single = self.cls_head(spatial_features_2d) - - # Compressor - if self.compression: - # The ego feature is also compressed - spatial_features_2d = self.naive_compressor(spatial_features_2d) - - if self.multi_scale: - # Bypass communication cost, communicate at high resolution, neither shrink nor compress - fused_feature, communication_rates = self.fusion_net( - batch_dict["spatial_features"], psm_single, record_len, pairwise_t_matrix, self.backbone - ) - if self.shrink_flag: - fused_feature = self.shrink_conv(fused_feature) - else: - fused_feature, communication_rates = self.fusion_net(spatial_features_2d, psm_single, record_len, pairwise_t_matrix) - - psm = self.cls_head(fused_feature) - rm = self.reg_head(fused_feature) - - output_dict = {"psm": psm, "rm": rm, "com": communication_rates} - return output_dict - - def encode_agent(self, data_dict, *, apply_communication_mask): - """ - Encode one agent up to the Where2Comm transmission boundary. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - apply_communication_mask : bool - Whether to select only features intended for transmission. - - Returns - ------- - dict - Learned feature map and selected-location ratio. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - "record_len": torch.ones(1, dtype=torch.long, device=processed_lidar["voxel_features"].device), - } - batch_dict = self.pillar_vfe(batch_dict) - batch_dict = self.scatter(batch_dict) - batch_dict = self.backbone(batch_dict) - - confidence_features = batch_dict["spatial_features_2d"] - if self.shrink_flag: - confidence_features = self.shrink_conv(confidence_features) - confidence_map = self.cls_head(confidence_features) - - if self.multi_scale: - spatial_features = self.backbone.blocks[0](batch_dict["spatial_features"]) - else: - spatial_features = confidence_features - if self.compression: - spatial_features = self.naive_compressor.encode(spatial_features) - - communication_rate = torch.ones((), device=spatial_features.device) - if not self.fusion_net.fully: - communication_mask, communication_rate = self.fusion_net.naive_communication.build_mask( - confidence_map, - force_first=False, - ) - if apply_communication_mask: - if spatial_features.shape[-2:] != communication_mask.shape[-2:]: - communication_mask = F.interpolate( - communication_mask, - size=spatial_features.shape[-2:], - mode="bilinear", - align_corners=False, - ) - spatial_features = spatial_features * communication_mask - - return { - "spatial_features": spatial_features, - "communication_rate": communication_rate, - } - - def fuse_agents(self, data_dict): - """ - Fuse successfully received learned features and run detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing encoded features and ``record_len``. - - Returns - ------- - dict - Fused classification, regression, and communication outputs. - """ - intermediate_features = data_dict["intermediate_features"] - spatial_features = intermediate_features["spatial_features"] - record_len = data_dict["record_len"] - - if self.multi_scale: - batch_node_features = self.fusion_net.regroup(spatial_features, record_len) - fused_feature = torch.stack([self.fusion_net.fuse_modules[0](features) for features in batch_node_features]) - upsampled_features = [self.backbone.deblocks[0](fused_feature) if len(self.backbone.deblocks) > 0 else fused_feature] - - for level in range(1, self.fusion_net.num_levels): - spatial_features = self.backbone.blocks[level](spatial_features) - batch_node_features = self.fusion_net.regroup(spatial_features, record_len) - fused_feature = torch.stack([self.fusion_net.fuse_modules[level](features) for features in batch_node_features]) - if len(self.backbone.deblocks) > 0: - upsampled_features.append(self.backbone.deblocks[level](fused_feature)) - else: - upsampled_features.append(fused_feature) - - fused_feature = torch.cat(upsampled_features, dim=1) - if len(self.backbone.deblocks) > self.fusion_net.num_levels: - fused_feature = self.backbone.deblocks[-1](fused_feature) - if self.shrink_flag: - fused_feature = self.shrink_conv(fused_feature) - else: - if self.compression: - spatial_features = self.naive_compressor.decode(spatial_features) - batch_node_features = self.fusion_net.regroup(spatial_features, record_len) - fused_feature = torch.stack([self.fusion_net.fuse_modules(features) for features in batch_node_features]) - - communication_rates = intermediate_features["communication_rate"] - return { - "psm": self.cls_head(fused_feature), - "rm": self.reg_head(fused_feature), - "com": communication_rates.mean(), - } diff --git a/OpenCOOD/opencood/models/second.py b/OpenCOOD/opencood/models/second.py deleted file mode 100644 index 4f28467b..00000000 --- a/OpenCOOD/opencood/models/second.py +++ /dev/null @@ -1,51 +0,0 @@ -import torch.nn as nn - -from opencood.models.sub_modules.mean_vfe import MeanVFE -from opencood.models.sub_modules.sparse_backbone_3d import VoxelBackBone8x -from opencood.models.sub_modules.height_compression import HeightCompression -from opencood.models.sub_modules.base_bev_backbone import BaseBEVBackbone - - -class Second(nn.Module): - def __init__(self, args): - super(Second, self).__init__() - - self.batch_size = args["batch_size"] - # mean_vfe - self.mean_vfe = MeanVFE(args["mean_vfe"], 4) - # sparse 3d backbone - self.backbone_3d = VoxelBackBone8x(args["backbone_3d"], 4, args["grid_size"]) - # height compression - self.height_compression = HeightCompression(args["height_compression"]) - # base ben backbone - self.backbone_2d = BaseBEVBackbone(args["base_bev_backbone"], 256) - - # head - self.cls_head = nn.Conv2d(256 * 2, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(256 * 2, 7 * args["anchor_num"], kernel_size=1) - - def forward(self, data_dict): - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - - batch_dict = { - "voxel_features": voxel_features, - "voxel_coords": voxel_coords, - "voxel_num_points": voxel_num_points, - "batch_size": data_dict.get("batch_size", self.batch_size), - } - - batch_dict = self.mean_vfe(batch_dict) - batch_dict = self.backbone_3d(batch_dict) - batch_dict = self.height_compression(batch_dict) - batch_dict = self.backbone_2d(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - - psm = self.cls_head(spatial_features_2d) - rm = self.reg_head(spatial_features_2d) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict diff --git a/OpenCOOD/opencood/models/second_intermediate.py b/OpenCOOD/opencood/models/second_intermediate.py deleted file mode 100644 index aad6f284..00000000 --- a/OpenCOOD/opencood/models/second_intermediate.py +++ /dev/null @@ -1,113 +0,0 @@ -import torch -import torch.nn as nn - -from opencood.models.communication_adapters.intermediate import MultiScaleFeatureCommunicationAdapter -from opencood.models.sub_modules.mean_vfe import MeanVFE -from opencood.models.sub_modules.sparse_backbone_3d import VoxelBackBone8x -from opencood.models.sub_modules.height_compression import HeightCompression -from opencood.models.sub_modules.att_bev_backbone import AttBEVBackbone - - -class SecondIntermediate(nn.Module): - communication_adapter_class = MultiScaleFeatureCommunicationAdapter - - def __init__(self, args): - super(SecondIntermediate, self).__init__() - - self.batch_size = args["batch_size"] - # mean_vfe - self.mean_vfe = MeanVFE(args["mean_vfe"], 4) - # sparse 3d backbone - self.backbone_3d = VoxelBackBone8x(args["backbone_3d"], 4, args["grid_size"]) - # height compression - self.height_compression = HeightCompression(args["height_compression"]) - # base ben backbone - self.backbone_2d = AttBEVBackbone(args["base_bev_backbone"], 256) - - # head - self.cls_head = nn.Conv2d(256 * 2, args["anchor_number"], kernel_size=1) - self.reg_head = nn.Conv2d(256 * 2, 7 * args["anchor_num"], kernel_size=1) - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - batch_dict = { - "voxel_features": voxel_features, - "voxel_coords": voxel_coords, - "voxel_num_points": voxel_num_points, - "batch_size": torch.sum(record_len).cpu().numpy(), - "record_len": record_len, - } - - batch_dict = self.mean_vfe(batch_dict) - batch_dict = self.backbone_3d(batch_dict) - batch_dict = self.height_compression(batch_dict) - batch_dict = self.backbone_2d(batch_dict) - - spatial_features_2d = batch_dict["spatial_features_2d"] - - psm = self.cls_head(spatial_features_2d) - rm = self.reg_head(spatial_features_2d) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent into private multi-scale SECOND features. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed LiDAR input. - - Returns - ------- - dict - Ordered feature maps at the 2D attention boundaries. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - "batch_size": data_dict.get("batch_size", 1), - } - batch_dict = self.mean_vfe(batch_dict) - batch_dict = self.backbone_3d(batch_dict) - batch_dict = self.height_compression(batch_dict) - return { - "feature_maps": self.backbone_2d.encode_agent(batch_dict["spatial_features"]), - } - - def fuse_agents(self, data_dict): - """ - Fuse received SECOND feature scales and run detection heads. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and ``record_len``. - - Returns - ------- - dict - Classification and regression maps. - """ - intermediate_features = data_dict["intermediate_features"] - feature_maps = tuple(intermediate_features[f"feature_{index}"] for index in range(len(self.backbone_2d.fuse_modules))) - fused_features = self.backbone_2d.fuse_agents( - feature_maps, - data_dict["record_len"], - ) - return { - "psm": self.cls_head(fused_features), - "rm": self.reg_head(fused_features), - } diff --git a/OpenCOOD/opencood/models/sub_modules/att_bev_backbone.py b/OpenCOOD/opencood/models/sub_modules/att_bev_backbone.py deleted file mode 100644 index b6ad7010..00000000 --- a/OpenCOOD/opencood/models/sub_modules/att_bev_backbone.py +++ /dev/null @@ -1,196 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn - -from opencood.models.fuse_modules.self_attn import AttFusion -from opencood.models.sub_modules.auto_encoder import AutoEncoder - - -class AttBEVBackbone(nn.Module): - def __init__(self, model_cfg, input_channels): - super().__init__() - self.model_cfg = model_cfg - self.compress = False - - if "compression" in model_cfg and model_cfg["compression"] > 0: - self.compress = True - self.compress_layer = model_cfg["compression"] - - if "layer_nums" in self.model_cfg: - assert len(self.model_cfg["layer_nums"]) == len(self.model_cfg["layer_strides"]) == len(self.model_cfg["num_filters"]) - - layer_nums = self.model_cfg["layer_nums"] - layer_strides = self.model_cfg["layer_strides"] - num_filters = self.model_cfg["num_filters"] - else: - layer_nums = layer_strides = num_filters = [] - - if "upsample_strides" in self.model_cfg: - assert len(self.model_cfg["upsample_strides"]) == len(self.model_cfg["num_upsample_filter"]) - - num_upsample_filters = self.model_cfg["num_upsample_filter"] - upsample_strides = self.model_cfg["upsample_strides"] - - else: - upsample_strides = num_upsample_filters = [] - - num_levels = len(layer_nums) - c_in_list = [input_channels, *num_filters[:-1]] - - self.blocks = nn.ModuleList() - self.fuse_modules = nn.ModuleList() - self.deblocks = nn.ModuleList() - - if self.compress: - self.compression_modules = nn.ModuleList() - - for idx in range(num_levels): - cur_layers = [ - nn.ZeroPad2d(1), - nn.Conv2d(c_in_list[idx], num_filters[idx], kernel_size=3, stride=layer_strides[idx], padding=0, bias=False), - nn.BatchNorm2d(num_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ] - - fuse_network = AttFusion(num_filters[idx]) - self.fuse_modules.append(fuse_network) - if self.compress and self.compress_layer - idx > 0: - self.compression_modules.append(AutoEncoder(num_filters[idx], self.compress_layer - idx)) - - for k in range(layer_nums[idx]): - cur_layers.extend( - [ - nn.Conv2d(num_filters[idx], num_filters[idx], kernel_size=3, padding=1, bias=False), - nn.BatchNorm2d(num_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ] - ) - - self.blocks.append(nn.Sequential(*cur_layers)) - if len(upsample_strides) > 0: - stride = upsample_strides[idx] - if stride >= 1: - self.deblocks.append( - nn.Sequential( - nn.ConvTranspose2d( - num_filters[idx], num_upsample_filters[idx], upsample_strides[idx], stride=upsample_strides[idx], bias=False - ), - nn.BatchNorm2d(num_upsample_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - else: - stride = np.round(1 / stride).astype(int) - self.deblocks.append( - nn.Sequential( - nn.Conv2d(num_filters[idx], num_upsample_filters[idx], stride, stride=stride, bias=False), - nn.BatchNorm2d(num_upsample_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - - c_in = sum(num_upsample_filters) - if len(upsample_strides) > num_levels: - self.deblocks.append( - nn.Sequential( - nn.ConvTranspose2d(c_in, c_in, upsample_strides[-1], stride=upsample_strides[-1], bias=False), - nn.BatchNorm2d(c_in, eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - - self.num_bev_features = c_in - - def forward(self, data_dict): - spatial_features = data_dict["spatial_features"] - record_len = data_dict["record_len"] - - upsampled_features = [] - features = spatial_features - for level, block in enumerate(self.blocks): - features = block(features) - if self.compress and level < len(self.compression_modules): - features = self.compression_modules[level](features) - fused_features = self.fuse_modules[level](features, record_len) - if len(self.deblocks) > 0: - upsampled_features.append(self.deblocks[level](fused_features)) - else: - upsampled_features.append(fused_features) - - if len(upsampled_features) > 1: - features = torch.cat(upsampled_features, dim=1) - elif len(upsampled_features) == 1: - features = upsampled_features[0] - else: - raise ValueError("At least one backbone level is required") - - if len(self.deblocks) > len(self.blocks): - features = self.deblocks[-1](features) - data_dict["spatial_features_2d"] = features - return data_dict - - def encode_agent(self, spatial_features): - """ - Compute every private backbone scale before feature fusion. - - Parameters - ---------- - spatial_features : torch.Tensor - Unfused BEV features for one or more agents. - - Returns - ------- - tuple[torch.Tensor, ...] - Ordered private feature maps consumed by attention fusion. - """ - feature_maps = [] - features = spatial_features - for level, block in enumerate(self.blocks): - features = block(features) - if self.compress and level < len(self.compression_modules): - encoded_features = self.compression_modules[level].encode(features) - feature_maps.append(encoded_features) - features = self.compression_modules[level].decode(encoded_features) - else: - feature_maps.append(features) - return tuple(feature_maps) - - def fuse_agents(self, feature_maps, record_len): - """ - Fuse encoded feature scales and decode the final BEV representation. - - Parameters - ---------- - feature_maps : tuple[torch.Tensor, ...] - Concatenated per-agent feature maps at every scale. - record_len : torch.Tensor - Number of successfully available agents in each sample. - - Returns - ------- - torch.Tensor - Decoded fused BEV feature map. - """ - if len(feature_maps) != len(self.fuse_modules): - raise ValueError("Unexpected number of intermediate feature scales") - - ups = [] - for level, features in enumerate(feature_maps): - if self.compress and level < len(self.compression_modules): - features = self.compression_modules[level].decode(features) - fused_features = self.fuse_modules[level](features, record_len) - if len(self.deblocks) > 0: - ups.append(self.deblocks[level](fused_features)) - else: - ups.append(fused_features) - - if len(ups) > 1: - features = torch.cat(ups, dim=1) - elif len(ups) == 1: - features = ups[0] - else: - raise ValueError("At least one intermediate feature scale is required") - - if len(self.deblocks) > len(self.blocks): - features = self.deblocks[-1](features) - return features diff --git a/OpenCOOD/opencood/models/sub_modules/auto_encoder.py b/OpenCOOD/opencood/models/sub_modules/auto_encoder.py deleted file mode 100644 index c5719d04..00000000 --- a/OpenCOOD/opencood/models/sub_modules/auto_encoder.py +++ /dev/null @@ -1,91 +0,0 @@ -import torch.nn as nn - - -class AutoEncoder(nn.Module): - def __init__(self, feature_num, layer_num): - super().__init__() - self.feature_num = feature_num - self.feature_stride = 2 - - self.encoder = nn.ModuleList() - self.decoder = nn.ModuleList() - - for i in range(layer_num): - cur_layers = [ - nn.ZeroPad2d(1), - nn.Conv2d(feature_num, feature_num, kernel_size=3, stride=2, padding=0, bias=False), - nn.BatchNorm2d(feature_num, eps=1e-3, momentum=0.01), - nn.ReLU(), - ] - - cur_layers.extend( - [ - nn.Conv2d(feature_num, feature_num // self.feature_stride, kernel_size=3, padding=1, bias=False), - nn.BatchNorm2d(feature_num // self.feature_stride, eps=1e-3, momentum=0.01), - nn.ReLU(), - ] - ) - - self.encoder.append(nn.Sequential(*cur_layers)) - feature_num = feature_num // self.feature_stride - - feature_num = self.feature_num - for i in range(layer_num): - cur_layers = [ - nn.Sequential( - nn.ConvTranspose2d(feature_num // 2, feature_num, kernel_size=2, stride=2, bias=False), - nn.BatchNorm2d(feature_num, eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ] - - cur_layers.extend( - [ - nn.Sequential( - nn.Conv2d(feature_num, feature_num, kernel_size=3, stride=1, bias=False, padding=1), - nn.BatchNorm2d(feature_num, eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ] - ) - self.decoder.append(nn.Sequential(*cur_layers)) - feature_num //= 2 - - def forward(self, x): - return self.decode(self.encode(x)) - - def encode(self, x): - """ - Produce the compact spatial representation for transmission. - - Parameters - ---------- - x : torch.Tensor - Full-resolution feature map. - - Returns - ------- - torch.Tensor - Encoded feature map with reduced channels and resolution. - """ - for i in range(len(self.encoder)): - x = self.encoder[i](x) - return x - - def decode(self, x): - """ - Restore a compact spatial representation for feature fusion. - - Parameters - ---------- - x : torch.Tensor - Encoded feature map received over V2X. - - Returns - ------- - torch.Tensor - Decoded feature map matching the fusion network input. - """ - for i in range(len(self.decoder) - 1, -1, -1): - x = self.decoder[i](x) - return x diff --git a/OpenCOOD/opencood/models/sub_modules/base_bev_backbone.py b/OpenCOOD/opencood/models/sub_modules/base_bev_backbone.py deleted file mode 100644 index 4b0f48b8..00000000 --- a/OpenCOOD/opencood/models/sub_modules/base_bev_backbone.py +++ /dev/null @@ -1,113 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn - - -class BaseBEVBackbone(nn.Module): - def __init__(self, model_cfg, input_channels): - super().__init__() - self.model_cfg = model_cfg - - if "layer_nums" in self.model_cfg: - assert len(self.model_cfg["layer_nums"]) == len(self.model_cfg["layer_strides"]) == len(self.model_cfg["num_filters"]) - - layer_nums = self.model_cfg["layer_nums"] - layer_strides = self.model_cfg["layer_strides"] - num_filters = self.model_cfg["num_filters"] - else: - layer_nums = layer_strides = num_filters = [] - - if "upsample_strides" in self.model_cfg: - assert len(self.model_cfg["upsample_strides"]) == len(self.model_cfg["num_upsample_filter"]) - - num_upsample_filters = self.model_cfg["num_upsample_filter"] - upsample_strides = self.model_cfg["upsample_strides"] - - else: - upsample_strides = num_upsample_filters = [] - - num_levels = len(layer_nums) - c_in_list = [input_channels, *num_filters[:-1]] - - self.blocks = nn.ModuleList() - self.deblocks = nn.ModuleList() - - for idx in range(num_levels): - cur_layers = [ - nn.ZeroPad2d(1), - nn.Conv2d(c_in_list[idx], num_filters[idx], kernel_size=3, stride=layer_strides[idx], padding=0, bias=False), - nn.BatchNorm2d(num_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ] - for k in range(layer_nums[idx]): - cur_layers.extend( - [ - nn.Conv2d(num_filters[idx], num_filters[idx], kernel_size=3, padding=1, bias=False), - nn.BatchNorm2d(num_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ] - ) - - self.blocks.append(nn.Sequential(*cur_layers)) - if len(upsample_strides) > 0: - stride = upsample_strides[idx] - if stride >= 1: - self.deblocks.append( - nn.Sequential( - nn.ConvTranspose2d( - num_filters[idx], num_upsample_filters[idx], upsample_strides[idx], stride=upsample_strides[idx], bias=False - ), - nn.BatchNorm2d(num_upsample_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - else: - stride = np.round(1 / stride).astype(int) - self.deblocks.append( - nn.Sequential( - nn.Conv2d(num_filters[idx], num_upsample_filters[idx], stride, stride=stride, bias=False), - nn.BatchNorm2d(num_upsample_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - - c_in = sum(num_upsample_filters) - if len(upsample_strides) > num_levels: - self.deblocks.append( - nn.Sequential( - nn.ConvTranspose2d(c_in, c_in, upsample_strides[-1], stride=upsample_strides[-1], bias=False), - nn.BatchNorm2d(c_in, eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - - self.num_bev_features = c_in - - def forward(self, data_dict): - spatial_features = data_dict["spatial_features"] - - ups = [] - ret_dict = {} - x = spatial_features - - for i in range(len(self.blocks)): - x = self.blocks[i](x) - - stride = int(spatial_features.shape[2] / x.shape[2]) - ret_dict["spatial_features_%dx" % stride] = x - - if len(self.deblocks) > 0: - ups.append(self.deblocks[i](x)) - else: - ups.append(x) - - if len(ups) > 1: - x = torch.cat(ups, dim=1) - elif len(ups) == 1: - x = ups[0] - - if len(self.deblocks) > len(self.blocks): - x = self.deblocks[-1](x) - - data_dict["spatial_features_2d"] = x - return data_dict diff --git a/OpenCOOD/opencood/models/sub_modules/base_transformer.py b/OpenCOOD/opencood/models/sub_modules/base_transformer.py deleted file mode 100644 index cf36927d..00000000 --- a/OpenCOOD/opencood/models/sub_modules/base_transformer.py +++ /dev/null @@ -1,78 +0,0 @@ -import torch -from torch import nn - -from einops import rearrange - - -class PreNormResidual(nn.Module): - def __init__(self, dim, fn): - super().__init__() - self.norm = nn.LayerNorm(dim) - self.fn = fn - - def forward(self, x, **kwargs): - return self.fn(self.norm(x), **kwargs) + x - - -class PreNorm(nn.Module): - def __init__(self, dim, fn): - super().__init__() - self.norm = nn.LayerNorm(dim) - self.fn = fn - - def forward(self, x, **kwargs): - return self.fn(self.norm(x), **kwargs) - - -class FeedForward(nn.Module): - def __init__(self, dim, hidden_dim, dropout=0.0): - super().__init__() - self.net = nn.Sequential(nn.Linear(dim, hidden_dim), nn.GELU(), nn.Dropout(dropout), nn.Linear(hidden_dim, dim), nn.Dropout(dropout)) - - def forward(self, x): - return self.net(x) - - -class CavAttention(nn.Module): - """ - Vanilla CAV attention. - """ - - def __init__(self, dim, heads, dim_head=64, dropout=0.1): - super().__init__() - inner_dim = heads * dim_head - - self.heads = heads - self.scale = dim_head**-0.5 - - self.attend = nn.Softmax(dim=-1) - self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False) - - self.to_out = nn.Sequential(nn.Linear(inner_dim, dim), nn.Dropout(dropout)) - - def forward(self, x, mask, prior_encoding): - # x: (B, L, H, W, C) -> (B, H, W, L, C) - # mask: (B, L) - x = x.permute(0, 2, 3, 1, 4) - # mask: (B, 1, H, W, L, 1) - mask = mask.unsqueeze(1) - - # qkv: [(B, H, W, L, C_inner) *3] - qkv = self.to_qkv(x).chunk(3, dim=-1) - # q: (B, M, H, W, L, C) - q, k, v = map(lambda t: rearrange(t, "b h w l (m c) -> b m h w l c", m=self.heads), qkv) - - # attention, (B, M, H, W, L, L) - att_map = torch.einsum("b m h w i c, b m h w j c -> b m h w i j", q, k) * self.scale - # add mask - att_map = att_map.masked_fill(mask == 0, -float("inf")) - # softmax - att_map = self.attend(att_map) - - # out:(B, M, H, W, L, C_head) - out = torch.einsum("b m h w i j, b m h w j c -> b m h w i c", att_map, v) - out = rearrange(out, "b m h w l c -> b h w l (m c)", m=self.heads) - out = self.to_out(out) - # (B L H W C) - out = out.permute(0, 3, 1, 2, 4) - return out diff --git a/OpenCOOD/opencood/models/sub_modules/cia_ssd_utils.py b/OpenCOOD/opencood/models/sub_modules/cia_ssd_utils.py deleted file mode 100644 index 66e56a79..00000000 --- a/OpenCOOD/opencood/models/sub_modules/cia_ssd_utils.py +++ /dev/null @@ -1,108 +0,0 @@ -import torch -from torch import nn - - -class SSFA(nn.Module): - def __init__(self, args): - super(SSFA, self).__init__() - # 128 - self._num_input_features = args["feature_num"] # noqa: DC05 - - seq = [nn.ZeroPad2d(1)] + get_conv_layers( - "Conv2d", 128, 128, n_layers=3, kernel_size=[3, 3, 3], stride=[1, 1, 1], padding=[0, 1, 1], sequential=False - ) - self.bottom_up_block_0 = nn.Sequential(*seq) - self.bottom_up_block_1 = get_conv_layers("Conv2d", 128, 256, n_layers=3, kernel_size=[3, 3, 3], stride=[2, 1, 1], padding=[1, 1, 1]) - - self.trans_0 = get_conv_layers("Conv2d", 128, 128, n_layers=1, kernel_size=[1], stride=[1], padding=[0]) - self.trans_1 = get_conv_layers("Conv2d", 256, 256, n_layers=1, kernel_size=[1], stride=[1], padding=[0]) - - self.deconv_block_0 = get_conv_layers("ConvTranspose2d", 256, 128, n_layers=1, kernel_size=[3], stride=[2], padding=[1], output_padding=[1]) - self.deconv_block_1 = get_conv_layers("ConvTranspose2d", 256, 128, n_layers=1, kernel_size=[3], stride=[2], padding=[1], output_padding=[1]) - - self.conv_0 = get_conv_layers("Conv2d", 128, 128, n_layers=1, kernel_size=[3], stride=[1], padding=[1]) - self.conv_1 = get_conv_layers("Conv2d", 128, 128, n_layers=1, kernel_size=[3], stride=[1], padding=[1]) - - self.w_0 = get_conv_layers("Conv2d", 128, 1, n_layers=1, kernel_size=[1], stride=[1], padding=[0], relu_last=False) - self.w_1 = get_conv_layers("Conv2d", 128, 1, n_layers=1, kernel_size=[1], stride=[1], padding=[0], relu_last=False) - - # default init_weights for conv(msra) and norm in ConvModule - def init_weights(self): - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.xavier_normal_(m.weight, gain=1) - if hasattr(m, "bias") and m.bias is not None: - nn.init.constant_(m.bias, 0) - - def forward(self, x): - x_0 = self.bottom_up_block_0(x) - x_1 = self.bottom_up_block_1(x_0) - x_trans_0 = self.trans_0(x_0) - x_trans_1 = self.trans_1(x_1) - x_middle_0 = self.deconv_block_0(x_trans_1) + x_trans_0 - x_middle_1 = self.deconv_block_1(x_trans_1) - x_output_0 = self.conv_0(x_middle_0) - x_output_1 = self.conv_1(x_middle_1) - - x_weight_0 = self.w_0(x_output_0) - x_weight_1 = self.w_1(x_output_1) - x_weight = torch.softmax(torch.cat([x_weight_0, x_weight_1], dim=1), dim=1) - x_output = x_output_0 * x_weight[:, 0:1, :, :] + x_output_1 * x_weight[:, 1:, :, :] - - return x_output.contiguous() - - -def get_conv_layers(conv_name, in_channels, out_channels, n_layers, kernel_size, stride, padding, relu_last=True, sequential=True, **kwargs): - """ - Build convolutional layers. kernel_size, stride and padding should be a list with the lengths that match n_layers - """ - seq = [] - for i in range(n_layers): - seq.extend( - [ - getattr(nn, conv_name)( - in_channels, - out_channels, - kernel_size[i], - stride=stride[i], - padding=padding[i], - bias=False, - **{k: v[i] for k, v in kwargs.items()}, - ), - nn.BatchNorm2d(out_channels, eps=1e-3, momentum=0.01), - ] - ) - if i < n_layers - 1 or relu_last: - seq.append(nn.ReLU()) - in_channels = out_channels - if sequential: - return nn.Sequential(*seq) - else: - return seq - - -class Head(nn.Module): - def __init__(self, num_input, num_pred, num_cls, num_iou=2, use_dir=False, num_dir=1): - super(Head, self).__init__() - self.use_dir = use_dir - - self.conv_box = nn.Conv2d(num_input, num_pred, 1) # 128 -> 14 - self.conv_cls = nn.Conv2d(num_input, num_cls, 1) # 128 -> 2 - self.conv_iou = nn.Conv2d(num_input, num_iou, 1, bias=False) - - if self.use_dir: - self.conv_dir = nn.Conv2d(num_input, num_dir, 1) # 128 -> 4 - - def forward(self, x): - box_preds = self.conv_box(x) - cls_preds = self.conv_cls(x) - ret_dict = {"box_preds": box_preds, "cls_preds": cls_preds} - if self.use_dir: - dir_preds = self.conv_dir(x) # dir_preds.shape=[8, w, h, 4] - ret_dict["dir_cls_preds"] = dir_preds - else: - ret_dict["dir_cls_preds"] = torch.zeros((len(box_preds), 1, 2)) - - ret_dict["iou_preds"] = self.conv_iou(x) - - return ret_dict diff --git a/OpenCOOD/opencood/models/sub_modules/convgru.py b/OpenCOOD/opencood/models/sub_modules/convgru.py deleted file mode 100644 index 46b014bd..00000000 --- a/OpenCOOD/opencood/models/sub_modules/convgru.py +++ /dev/null @@ -1,191 +0,0 @@ -import torch -from torch import nn -from torch.autograd import Variable - - -class ConvGRUCell(nn.Module): - def __init__(self, input_size, input_dim, hidden_dim, kernel_size, bias): - """ - Initialize the ConvLSTM cell - :param input_size: (int, int) - Height and width of input tensor as (height, width). - :param input_dim: int - Number of channels of input tensor. - :param hidden_dim: int - Number of channels of hidden state. - :param kernel_size: (int, int) - Size of the convolutional kernel. - :param bias: bool - Whether or not to add the bias. - :param dtype: torch.cuda.FloatTensor or torch.FloatTensor - Whether or not to use cuda. - """ - super(ConvGRUCell, self).__init__() - self.height, self.width = input_size - self.padding = kernel_size[0] // 2, kernel_size[1] // 2 - self.hidden_dim = hidden_dim - self.bias = bias - - self.conv_gates = nn.Conv2d( - in_channels=input_dim + hidden_dim, - out_channels=2 * self.hidden_dim, - # for update_gate,reset_gate respectively - kernel_size=kernel_size, - padding=self.padding, - bias=self.bias, - ) - - self.conv_can = nn.Conv2d( - in_channels=input_dim + hidden_dim, - out_channels=self.hidden_dim, - # for candidate neural memory - kernel_size=kernel_size, - padding=self.padding, - bias=self.bias, - ) - - def init_hidden(self, batch_size): - return Variable(torch.zeros(batch_size, self.hidden_dim, self.height, self.width)) - - def forward(self, input_tensor, h_cur): - """ - :param self: - :param input_tensor: (b, c, h, w) - input is actually the target_model - :param h_cur: (b, c_hidden, h, w) - current hidden and cell states respectively - :return: h_next, - next hidden state - """ - combined = torch.cat([input_tensor, h_cur], dim=1) - combined_conv = self.conv_gates(combined) - - gamma, beta = torch.split(combined_conv, self.hidden_dim, dim=1) - reset_gate = torch.sigmoid(gamma) - update_gate = torch.sigmoid(beta) - - combined = torch.cat([input_tensor, reset_gate * h_cur], dim=1) - cc_cnm = self.conv_can(combined) - cnm = torch.tanh(cc_cnm) - - h_next = (1 - update_gate) * h_cur + update_gate * cnm - return h_next - - -class ConvGRU(nn.Module): - def __init__(self, input_size, input_dim, hidden_dim, kernel_size, num_layers, batch_first=False, bias=True, return_all_layers=False): - """ - :param input_size: (int, int) - Height and width of input tensor as (height, width). - :param input_dim: int e.g. 256 - Number of channels of input tensor. - :param hidden_dim: int e.g. 1024 - Number of channels of hidden state. - :param kernel_size: (int, int) - Size of the convolutional kernel. - :param num_layers: int - Number of ConvLSTM layers - :param dtype: torch.cuda.FloatTensor or torch.FloatTensor - Whether or not to use cuda. - :param alexnet_path: str - pretrained alexnet parameters - :param batch_first: bool - if the first position of array is batch or not - :param bias: bool - Whether or not to add the bias. - :param return_all_layers: bool - if return hidden and cell states for all layers - """ - super(ConvGRU, self).__init__() - - # Make sure that both `kernel_size` and - # `hidden_dim` are lists having len == num_layers - kernel_size = self._extend_for_multilayer(kernel_size, num_layers) - hidden_dim = self._extend_for_multilayer(hidden_dim, num_layers) - if not len(kernel_size) == len(hidden_dim) == num_layers: - raise ValueError("Inconsistent list length.") - - self.height, self.width = input_size - self.input_dim = input_dim - self.hidden_dim = hidden_dim - self.kernel_size = kernel_size - self.num_layers = num_layers - self.batch_first = batch_first - self.bias = bias - self.return_all_layers = return_all_layers - - cell_list = [] - for i in range(0, self.num_layers): - cur_input_dim = input_dim if i == 0 else hidden_dim[i - 1] - cell_list.append( - ConvGRUCell( - input_size=(self.height, self.width), - input_dim=cur_input_dim, - hidden_dim=self.hidden_dim[i], - kernel_size=self.kernel_size[i], - bias=self.bias, - ) - ) - - # convert python list to pytorch module - self.cell_list = nn.ModuleList(cell_list) - - def forward(self, input_tensor, hidden_state=None): - """ - :param input_tensor: (b, t, c, h, w) or (t,b,c,h,w) - depends on if batch first or not extracted features from alexnet - :param hidden_state: - :return: layer_output_list, last_state_list - """ - if not self.batch_first: - # (t, b, c, h, w) -> (b, t, c, h, w) - input_tensor = input_tensor.permute(1, 0, 2, 3, 4) - - # Implement stateful ConvLSTM - if hidden_state is not None: - raise NotImplementedError() - else: - hidden_state = self._init_hidden(batch_size=input_tensor.size(0), device=input_tensor.device, dtype=input_tensor.dtype) - - layer_output_list = [] - last_state_list = [] - - seq_len = input_tensor.size(1) - cur_layer_input = input_tensor - - for layer_idx in range(self.num_layers): - h = hidden_state[layer_idx] - output_inner = [] - for t in range(seq_len): - # input current hidden and cell state - # then compute the next hidden - # and cell state through ConvLSTMCell forward function - h = self.cell_list[layer_idx]( - input_tensor=cur_layer_input[:, t, :, :, :], # (b,t,c,h,w) - h_cur=h, - ) - output_inner.append(h) - - layer_output = torch.stack(output_inner, dim=1) - cur_layer_input = layer_output - - layer_output_list.append(layer_output) - last_state_list.append([h]) - - if not self.return_all_layers: - layer_output_list = layer_output_list[-1:] - last_state_list = last_state_list[-1:] - - return layer_output_list, last_state_list - - def _init_hidden(self, batch_size, device=None, dtype=None): - init_states = [] - for i in range(self.num_layers): - init_states.append(self.cell_list[i].init_hidden(batch_size).to(device).to(dtype)) - return init_states - - @staticmethod - def _extend_for_multilayer(param, num_layers): - if not isinstance(param, list): - param = [param] * num_layers - return param diff --git a/OpenCOOD/opencood/models/sub_modules/downsample_conv.py b/OpenCOOD/opencood/models/sub_modules/downsample_conv.py deleted file mode 100644 index d4b45761..00000000 --- a/OpenCOOD/opencood/models/sub_modules/downsample_conv.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Class used to downsample features by 3*3 conv -""" - -import torch.nn as nn - - -class DoubleConv(nn.Module): - """ - Double convoltuion - Args: - in_channels: input channel num - out_channels: output channel num - """ - - def __init__(self, in_channels, out_channels, kernel_size, stride, padding): - super().__init__() - self.double_conv = nn.Sequential( - nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding), - nn.ReLU(inplace=True), - nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1), - nn.ReLU(inplace=True), - ) - - def forward(self, x): - return self.double_conv(x) - - -class DownsampleConv(nn.Module): - def __init__(self, config): - super(DownsampleConv, self).__init__() - self.layers = nn.ModuleList([]) - input_dim = config["input_dim"] - - for ksize, dim, stride, padding in zip(config["kernal_size"], config["dim"], config["stride"], config["padding"]): - self.layers.append(DoubleConv(input_dim, dim, kernel_size=ksize, stride=stride, padding=padding)) - input_dim = dim - - def forward(self, x): - for i in range(len(self.layers)): - x = self.layers[i](x) - return x diff --git a/OpenCOOD/opencood/models/sub_modules/height_compression.py b/OpenCOOD/opencood/models/sub_modules/height_compression.py deleted file mode 100644 index e917a310..00000000 --- a/OpenCOOD/opencood/models/sub_modules/height_compression.py +++ /dev/null @@ -1,26 +0,0 @@ -import torch.nn as nn - - -class HeightCompression(nn.Module): - def __init__(self, model_cfg, **kwargs): - super().__init__() - self.model_cfg = model_cfg - self.num_bev_features = self.model_cfg["feature_num"] - - def forward(self, batch_dict): - """ - Args: - batch_dict: - encoded_spconv_tensor: sparse tensor - Returns: - batch_dict: - spatial_features: - - """ - encoded_spconv_tensor = batch_dict["encoded_spconv_tensor"] - spatial_features = encoded_spconv_tensor.dense() - N, C, D, H, W = spatial_features.shape - spatial_features = spatial_features.view(N, C * D, H, W) - batch_dict["spatial_features"] = spatial_features - batch_dict["spatial_features_stride"] = batch_dict["encoded_spconv_tensor_stride"] - return batch_dict diff --git a/OpenCOOD/opencood/models/sub_modules/matcher.py b/OpenCOOD/opencood/models/sub_modules/matcher.py deleted file mode 100644 index e50c2353..00000000 --- a/OpenCOOD/opencood/models/sub_modules/matcher.py +++ /dev/null @@ -1,124 +0,0 @@ -import torch -from torch import nn - -from opencood.pcdet_utils.iou3d_nms.iou3d_nms_utils import boxes_iou3d_gpu - -pi = 3.141592653 - - -def limit_period(val, offset=0.5, period=2 * pi): - return val - torch.floor(val / period + offset) * period - - -class Matcher(nn.Module): - """Correct localization error and use Algorithm 1: - BBox matching with scores to fuse the proposal BBoxes""" - - def __init__(self, cfg, pc_range): - super(Matcher, self).__init__() - self.pc_range = pc_range - - @torch.no_grad() - def forward(self, data_dict): - clusters, scores = self.clustering(data_dict) - data_dict["boxes_fused"], data_dict["scores_fused"] = self.cluster_fusion(clusters, scores) - self.merge_keypoints(data_dict) - return data_dict - - def clustering(self, data_dict): - """ - Assign predicted boxes to clusters according to their ious with each other - """ - clusters_batch = [] - scores_batch = [] - record_len = [int(length) for length in data_dict["record_len"]] - for i, length in enumerate(record_len): - cur_boxes_list = data_dict["det_boxes"][sum(record_len[:i]) : sum(record_len[:i]) + length] - cur_scores_list = data_dict["det_scores"][sum(record_len[:i]) : sum(record_len[:i]) + length] - cur_boxes_list = [b for b in cur_boxes_list if len(b) > 0] - cur_scores_list = [s for s in cur_scores_list if len(s) > 0] - if len(cur_scores_list) == 0: - clusters_batch.append([torch.Tensor([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.57]).to(torch.device("cuda:0")).view(1, 7)]) - scores_batch.append([torch.Tensor([0.01]).to(torch.device("cuda:0")).view(-1)]) - continue - - pred_boxes_cat = torch.cat(cur_boxes_list, dim=0) - pred_boxes_cat[:, -1] = limit_period(pred_boxes_cat[:, -1]) - pred_scores_cat = torch.cat(cur_scores_list, dim=0) - - ious = boxes_iou3d_gpu(pred_boxes_cat, pred_boxes_cat) - cluster_indices = torch.zeros(len(ious)).int() # gt assignments of preds - cur_cluster_id = 1 - while torch.any(cluster_indices == 0): - cur_idx = torch.where(cluster_indices == 0)[0][0] # find the idx of the first pred which is not assigned yet - cluster_indices[torch.where(ious[cur_idx] > 0.1)[0]] = cur_cluster_id - cur_cluster_id += 1 - clusters = [] - scores = [] - for j in range(1, cur_cluster_id): - clusters.append(pred_boxes_cat[cluster_indices == j]) - scores.append(pred_scores_cat[cluster_indices == j]) - clusters_batch.append(clusters) - scores_batch.append(scores) - - return clusters_batch, scores_batch - - def cluster_fusion(self, clusters, scores): - """ - Merge boxes in each cluster with scores as weights for merging - """ - boxes_fused = [] - scores_fused = [] - for cl, sl in zip(clusters, scores): - for c, s in zip(cl, sl): - # reverse direction for non-dominant direction of boxes - dirs = c[:, -1] - max_score_idx = torch.argmax(s) - dirs_diff = torch.abs(dirs - dirs[max_score_idx].item()) - lt_pi = (dirs_diff > pi).int() - dirs_diff = dirs_diff * (1 - lt_pi) + (2 * pi - dirs_diff) * lt_pi - score_lt_half_pi = s[dirs_diff > pi / 2].sum() # larger than - score_set_half_pi = s[dirs_diff <= pi / 2].sum() # small equal than - # select larger scored direction as final direction - if score_lt_half_pi <= score_set_half_pi: - dirs[dirs_diff > pi / 2] += pi - else: - dirs[dirs_diff <= pi / 2] += pi - dirs = limit_period(dirs) - s_normalized = s / s.sum() - sint = torch.sin(dirs) * s_normalized - cost = torch.cos(dirs) * s_normalized - theta = torch.atan2(sint.sum(), cost.sum()).view( - 1, - ) - center_dim = c[:, :-1] * s_normalized[:, None] - boxes_fused.append(torch.cat([center_dim.sum(dim=0), theta])) - s_sorted = torch.sort(s, descending=True).values - s_fused = 0 - for i, ss in enumerate(s_sorted): - s_fused += ss ** (i + 1) - s_fused = torch.tensor([min(s_fused, 1.0)], device=s.device) - scores_fused.append(s_fused) - - assert len(boxes_fused) > 0 - boxes_fused = torch.stack(boxes_fused, dim=0) - len_records = [len(c) for c in clusters] - boxes_fused = [boxes_fused[sum(len_records[:i]) : sum(len_records[:i]) + length] for i, length in enumerate(len_records)] - scores_fused = torch.stack(scores_fused, dim=0) - scores_fused = [scores_fused[sum(len_records[:i]) : sum(len_records[:i]) + length] for i, length in enumerate(len_records)] - - return boxes_fused, scores_fused - - def merge_keypoints(self, data_dict): - # merge keypoints - kpts_feat_out = [] - kpts_coor_out = [] - keypoints_features = data_dict["point_features"] - keypoints_coords = data_dict["point_coords"] - idx = 0 - for length in data_dict["record_len"]: - kpts_coor_out.append(torch.cat(keypoints_coords[idx : length + idx], dim=0)) - kpts_feat_out.append(torch.cat(keypoints_features[idx : length + idx], dim=0)) - idx += length - data_dict["point_features"] = kpts_feat_out - data_dict["point_coords"] = kpts_coor_out diff --git a/OpenCOOD/opencood/models/sub_modules/mean_vfe.py b/OpenCOOD/opencood/models/sub_modules/mean_vfe.py deleted file mode 100644 index 1fe4e8f5..00000000 --- a/OpenCOOD/opencood/models/sub_modules/mean_vfe.py +++ /dev/null @@ -1,28 +0,0 @@ -import torch -import torch.nn as nn - - -class MeanVFE(nn.Module): - def __init__(self, model_cfg, num_point_features, **kwargs): - super().__init__() - self.model_cfg = model_cfg - self.num_point_features = num_point_features - - def forward(self, batch_dict, **kwargs): - """ - Args: - batch_dict: - voxels: (num_voxels, max_points_per_voxel, C) - voxel_num_points: optional (num_voxels) - **kwargs: - - Returns: - vfe_features: (num_voxels, C) - """ - voxel_features, voxel_num_points = batch_dict["voxel_features"], batch_dict["voxel_num_points"] - points_mean = voxel_features[:, :, :].sum(dim=1, keepdim=False) - normalizer = torch.clamp_min(voxel_num_points.view(-1, 1), min=1.0).type_as(voxel_features) - points_mean = points_mean / normalizer - batch_dict["voxel_features"] = points_mean.contiguous() - - return batch_dict diff --git a/OpenCOOD/opencood/models/sub_modules/naive_compress.py b/OpenCOOD/opencood/models/sub_modules/naive_compress.py deleted file mode 100644 index 8c2cfbd9..00000000 --- a/OpenCOOD/opencood/models/sub_modules/naive_compress.py +++ /dev/null @@ -1,58 +0,0 @@ -import torch.nn as nn - - -class NaiveCompressor(nn.Module): - """ - A very naive compression that only compress on the channel. - """ - - def __init__(self, input_dim, compress_raito): - super().__init__() - self.encoder = nn.Sequential( - nn.Conv2d(input_dim, input_dim // compress_raito, kernel_size=3, stride=1, padding=1), - nn.BatchNorm2d(input_dim // compress_raito, eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - self.decoder = nn.Sequential( - nn.Conv2d(input_dim // compress_raito, input_dim, kernel_size=3, stride=1, padding=1), - nn.BatchNorm2d(input_dim, eps=1e-3, momentum=0.01), - nn.ReLU(), - nn.Conv2d(input_dim, input_dim, kernel_size=3, stride=1, padding=1), - nn.BatchNorm2d(input_dim, eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - - def forward(self, x): - return self.decode(self.encode(x)) - - def encode(self, x): - """ - Produce the channel-compressed representation for transmission. - - Parameters - ---------- - x : torch.Tensor - Full-channel spatial feature map. - - Returns - ------- - torch.Tensor - Channel-compressed feature map. - """ - return self.encoder(x) - - def decode(self, x): - """ - Restore a transmitted channel-compressed representation. - - Parameters - ---------- - x : torch.Tensor - Channel-compressed feature map. - - Returns - ------- - torch.Tensor - Feature map restored to the fusion network's channel count. - """ - return self.decoder(x) diff --git a/OpenCOOD/opencood/models/sub_modules/pillar_vfe.py b/OpenCOOD/opencood/models/sub_modules/pillar_vfe.py deleted file mode 100644 index ca3c6a6b..00000000 --- a/OpenCOOD/opencood/models/sub_modules/pillar_vfe.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Pillar VFE, credits to OpenPCDet. -""" - -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class PFNLayer(nn.Module): - def __init__(self, in_channels, out_channels, use_norm=True, last_layer=False): - super().__init__() - - self.last_vfe = last_layer - self.use_norm = use_norm - if not self.last_vfe: - out_channels = out_channels // 2 - - if self.use_norm: - self.linear = nn.Linear(in_channels, out_channels, bias=False) - self.norm = nn.BatchNorm1d(out_channels, eps=1e-3, momentum=0.01) - else: - self.linear = nn.Linear(in_channels, out_channels, bias=True) - - self.part = 50000 - - def forward(self, inputs): - if inputs.shape[0] > self.part: - # nn.Linear performs randomly when batch size is too large - num_parts = inputs.shape[0] // self.part - part_linear_out = [self.linear(inputs[num_part * self.part : (num_part + 1) * self.part]) for num_part in range(num_parts + 1)] - x = torch.cat(part_linear_out, dim=0) - else: - x = self.linear(inputs) - torch.backends.cudnn.enabled = False # noqa: DC05 - x = self.norm(x.permute(0, 2, 1)).permute(0, 2, 1) if self.use_norm else x - torch.backends.cudnn.enabled = True # noqa: DC05 - x = F.relu(x) - x_max = torch.max(x, dim=1, keepdim=True)[0] - - if self.last_vfe: - return x_max - else: - x_repeat = x_max.repeat(1, inputs.shape[1], 1) - x_concatenated = torch.cat([x, x_repeat], dim=2) - return x_concatenated - - -class PillarVFE(nn.Module): - def __init__(self, model_cfg, num_point_features, voxel_size, point_cloud_range): - super().__init__() - self.model_cfg = model_cfg - - self.use_norm = self.model_cfg["use_norm"] - self.with_distance = self.model_cfg["with_distance"] - - self.use_absolute_xyz = self.model_cfg["use_absolute_xyz"] - num_point_features += 6 if self.use_absolute_xyz else 3 - if self.with_distance: - num_point_features += 1 - - self.num_filters = self.model_cfg["num_filters"] - assert len(self.num_filters) > 0 - num_filters = [num_point_features] + list(self.num_filters) - - pfn_layers = [] - for i in range(len(num_filters) - 1): - in_filters = num_filters[i] - out_filters = num_filters[i + 1] - pfn_layers.append(PFNLayer(in_filters, out_filters, self.use_norm, last_layer=(i >= len(num_filters) - 2))) - self.pfn_layers = nn.ModuleList(pfn_layers) - - self.voxel_x = voxel_size[0] - self.voxel_y = voxel_size[1] - self.voxel_z = voxel_size[2] - self.x_offset = self.voxel_x / 2 + point_cloud_range[0] - self.y_offset = self.voxel_y / 2 + point_cloud_range[1] - self.z_offset = self.voxel_z / 2 + point_cloud_range[2] - - @staticmethod - def get_paddings_indicator(actual_num, max_num, axis=0): - actual_num = torch.unsqueeze(actual_num, axis + 1) - max_num_shape = [1] * len(actual_num.shape) - max_num_shape[axis + 1] = -1 - max_num = torch.arange(max_num, dtype=torch.int, device=actual_num.device).view(max_num_shape) - paddings_indicator = actual_num.int() > max_num - return paddings_indicator - - def forward(self, batch_dict): - voxel_features, voxel_num_points, coords = batch_dict["voxel_features"], batch_dict["voxel_num_points"], batch_dict["voxel_coords"] - points_mean = voxel_features[:, :, :3].sum(dim=1, keepdim=True) / voxel_num_points.type_as(voxel_features).view(-1, 1, 1) - f_cluster = voxel_features[:, :, :3] - points_mean - - f_center = torch.zeros_like(voxel_features[:, :, :3]) - f_center[:, :, 0] = voxel_features[:, :, 0] - (coords[:, 3].to(voxel_features.dtype).unsqueeze(1) * self.voxel_x + self.x_offset) - f_center[:, :, 1] = voxel_features[:, :, 1] - (coords[:, 2].to(voxel_features.dtype).unsqueeze(1) * self.voxel_y + self.y_offset) - f_center[:, :, 2] = voxel_features[:, :, 2] - (coords[:, 1].to(voxel_features.dtype).unsqueeze(1) * self.voxel_z + self.z_offset) - - if self.use_absolute_xyz: - features = [voxel_features, f_cluster, f_center] - else: - features = [voxel_features[..., 3:], f_cluster, f_center] - - if self.with_distance: - points_dist = torch.norm(voxel_features[:, :, :3], 2, 2, keepdim=True) - features.append(points_dist) - features = torch.cat(features, dim=-1) - - voxel_count = features.shape[1] - mask = self.get_paddings_indicator(voxel_num_points, voxel_count, axis=0) - mask = torch.unsqueeze(mask, -1).type_as(voxel_features) - features *= mask - for pfn in self.pfn_layers: - features = pfn(features) - features = features.squeeze() - batch_dict["pillar_features"] = features - return batch_dict diff --git a/OpenCOOD/opencood/models/sub_modules/point_pillar_scatter.py b/OpenCOOD/opencood/models/sub_modules/point_pillar_scatter.py deleted file mode 100644 index 12022ce3..00000000 --- a/OpenCOOD/opencood/models/sub_modules/point_pillar_scatter.py +++ /dev/null @@ -1,39 +0,0 @@ -import torch -import torch.nn as nn - - -class PointPillarScatter(nn.Module): - def __init__(self, model_cfg): - super().__init__() - - self.model_cfg = model_cfg - self.num_bev_features = self.model_cfg["num_features"] - self.nx, self.ny, self.nz = model_cfg["grid_size"] - assert self.nz == 1 - - def forward(self, batch_dict): - pillar_features, coords = batch_dict["pillar_features"], batch_dict["voxel_coords"] - batch_spatial_features = [] - batch_size = coords[:, 0].max().int().item() + 1 - - for batch_idx in range(batch_size): - spatial_feature = torch.zeros( - self.num_bev_features, self.nz * self.nx * self.ny, dtype=pillar_features.dtype, device=pillar_features.device - ) - - batch_mask = coords[:, 0] == batch_idx - this_coords = coords[batch_mask, :] - - indices = this_coords[:, 1] + this_coords[:, 2] * self.nx + this_coords[:, 3] - indices = indices.type(torch.long) - - pillars = pillar_features[batch_mask, :] - pillars = pillars.t() - spatial_feature[:, indices] = pillars - batch_spatial_features.append(spatial_feature) - - batch_spatial_features = torch.stack(batch_spatial_features, 0) - batch_spatial_features = batch_spatial_features.view(batch_size, self.num_bev_features * self.nz, self.ny, self.nx) - batch_dict["spatial_features"] = batch_spatial_features - - return batch_dict diff --git a/OpenCOOD/opencood/models/sub_modules/res_bev_backbone.py b/OpenCOOD/opencood/models/sub_modules/res_bev_backbone.py deleted file mode 100644 index 168d0803..00000000 --- a/OpenCOOD/opencood/models/sub_modules/res_bev_backbone.py +++ /dev/null @@ -1,123 +0,0 @@ -import numpy as np -import torch -import torch.nn as nn -from opencood.models.sub_modules.resblock import ResNetLayers - - -class ResBEVBackbone(nn.Module): - def __init__(self, model_cfg, input_channels=64): - super().__init__() - self.model_cfg = model_cfg - - if "layer_nums" in self.model_cfg: - assert len(self.model_cfg["layer_nums"]) == len(self.model_cfg["layer_strides"]) == len(self.model_cfg["num_filters"]) - - layer_nums = self.model_cfg["layer_nums"] - layer_strides = self.model_cfg["layer_strides"] - num_filters = self.model_cfg["num_filters"] - else: - layer_nums = layer_strides = num_filters = [] - - if "upsample_strides" in self.model_cfg: - assert len(self.model_cfg["upsample_strides"]) == len(self.model_cfg["num_upsample_filter"]) - - num_upsample_filters = self.model_cfg["num_upsample_filter"] - upsample_strides = self.model_cfg["upsample_strides"] - - else: - upsample_strides = num_upsample_filters = [] - - self.resnet = ResNetLayers(layer_nums, layer_strides, num_filters, inplanes=input_channels) - - num_levels = len(layer_nums) - self.num_levels = len(layer_nums) - self.deblocks = nn.ModuleList() - - for idx in range(num_levels): - if len(upsample_strides) > 0: - stride = upsample_strides[idx] - if stride >= 1: - self.deblocks.append( - nn.Sequential( - nn.ConvTranspose2d( - num_filters[idx], num_upsample_filters[idx], upsample_strides[idx], stride=upsample_strides[idx], bias=False - ), - nn.BatchNorm2d(num_upsample_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - else: - stride = np.round(1 / stride).astype(int) - self.deblocks.append( - nn.Sequential( - nn.Conv2d(num_filters[idx], num_upsample_filters[idx], stride, stride=stride, bias=False), - nn.BatchNorm2d(num_upsample_filters[idx], eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - - c_in = sum(num_upsample_filters) - if len(upsample_strides) > num_levels: - self.deblocks.append( - nn.Sequential( - nn.ConvTranspose2d(c_in, c_in, upsample_strides[-1], stride=upsample_strides[-1], bias=False), - nn.BatchNorm2d(c_in, eps=1e-3, momentum=0.01), - nn.ReLU(), - ) - ) - - self.num_bev_features = c_in - - def forward(self, data_dict): - """ - This can be used in single agent detection (late fusion) - - For intermediate fusion, use get_multiscale_feature and - decode_multiscale_feature. - """ - spatial_features = data_dict["spatial_features"] - - x = self.resnet(spatial_features) # tuple of features - ups = [] - - for i in range(self.num_levels): - if len(self.deblocks) > 0: - ups.append(self.deblocks[i](x[i])) - else: - ups.append(x[i]) - - if len(ups) > 1: - x = torch.cat(ups, dim=1) - elif len(ups) == 1: - x = ups[0] - - if len(self.deblocks) > self.num_levels: - x = self.deblocks[-1](x) - - data_dict["spatial_features_2d"] = x - return data_dict - - def decode_multiscale_feature(self, x): - """ - after multiscale interemediate fusion - """ - ups = [] - for i in range(self.num_levels): - if len(self.deblocks) > 0: - ups.append(self.deblocks[i](x[i])) - else: - ups.append(x[i]) - if len(ups) > 1: - x = torch.cat(ups, dim=1) - elif len(ups) == 1: - x = ups[0] - - if len(self.deblocks) > self.num_levels: - x = self.deblocks[-1](x) - return x - - def get_layer_i_feature(self, spatial_features, layer_i): - """ - before multiscale intermediate fusion - """ - return eval(f"self.resnet.layer{layer_i}")(spatial_features) # tuple of features diff --git a/OpenCOOD/opencood/models/sub_modules/resblock.py b/OpenCOOD/opencood/models/sub_modules/resblock.py deleted file mode 100644 index 637d5dea..00000000 --- a/OpenCOOD/opencood/models/sub_modules/resblock.py +++ /dev/null @@ -1,146 +0,0 @@ -from torch import Tensor -import torch.nn as nn -from typing import Callable, List, Optional - - -def conv3x3(in_planes: int, out_planes: int, stride: int = 1, groups: int = 1, dilation: int = 1) -> nn.Conv2d: - """3x3 convolution with padding""" - return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=dilation, groups=groups, bias=False, dilation=dilation) - - -def conv1x1(in_planes: int, out_planes: int, stride: int = 1) -> nn.Conv2d: - """1x1 convolution""" - return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False) - - -class BasicBlock(nn.Module): - expansion: int = 1 - - def __init__( - self, - inplanes: int, - planes: int, - stride: int = 1, - downsample: Optional[nn.Module] = None, - groups: int = 1, - base_width: int = 64, - dilation: int = 1, - norm_layer: Optional[Callable[..., nn.Module]] = None, - ) -> None: - super(BasicBlock, self).__init__() - if norm_layer is None: - norm_layer = nn.BatchNorm2d - if groups != 1 or base_width != 64: - raise ValueError("BasicBlock only supports groups=1 and base_width=64") - if dilation > 1: - raise NotImplementedError("Dilation > 1 not supported in BasicBlock") - # Both self.conv1 and self.downsample layers downsample the input when stride != 1 - self.conv1 = conv3x3(inplanes, planes, stride) - self.bn1 = norm_layer(planes) - self.relu = nn.ReLU(inplace=True) - self.conv2 = conv3x3(planes, planes) - self.bn2 = norm_layer(planes) - self.downsample = downsample - self.stride = stride - - def forward(self, x: Tensor) -> Tensor: - identity = x - - out = self.conv1(x) - out = self.bn1(out) - out = self.relu(out) - - out = self.conv2(out) - out = self.bn2(out) - - if self.downsample is not None: - identity = self.downsample(x) - - out += identity - out = self.relu(out) - - return out - - -class ResNetLayers(nn.Module): - def __init__( - self, - layers: List[int], # number of block in one layer - layer_strides: List[int], # stride after one layer - num_filters: List[int], # feature dim - zero_init_residual: bool = False, - groups: int = 1, - width_per_group: int = 64, - replace_stride_with_dilation: Optional[List[bool]] = None, - norm_layer: Optional[Callable[..., nn.Module]] = None, - inplanes=64, - ) -> None: - super(ResNetLayers, self).__init__() - - if norm_layer is None: - norm_layer = nn.BatchNorm2d - self._norm_layer = norm_layer - - self.inplanes = inplanes - self.dilation = 1 - if replace_stride_with_dilation is None: - # each element in the tuple indicates if we should replace - # the 2x2 stride with a dilated convolution instead - replace_stride_with_dilation = [False, False, False] - if len(replace_stride_with_dilation) != 3: - raise ValueError("replace_stride_with_dilation should be None or a 3-element tuple, got {}".format(replace_stride_with_dilation)) - self.groups = groups - self.base_width = width_per_group - self.layernum = len(num_filters) - - for i in range(self.layernum): - self.__setattr__(f"layer{i}", self._make_layer(num_filters[i], layers[i], stride=layer_strides[i])) - - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu") - elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)): - nn.init.constant_(m.weight, 1) - nn.init.constant_(m.bias, 0) - - # Zero-initialize the last BN in each residual branch, - # so that the residual branch starts with zeros, and each residual block behaves like an identity. - # This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677 - if zero_init_residual: - for m in self.modules(): - if isinstance(m, BasicBlock): - nn.init.constant_(m.bn2.weight, 0) - - def _make_layer(self, planes: int, blocks: int, stride: int = 1, dilate: bool = False) -> nn.Sequential: - block = BasicBlock - norm_layer = self._norm_layer - downsample = None - previous_dilation = self.dilation - if dilate: - self.dilation *= stride - stride = 1 - if stride != 1 or self.inplanes != planes * block.expansion: - downsample = nn.Sequential( - conv1x1(self.inplanes, planes * block.expansion, stride), - norm_layer(planes * block.expansion), - ) - - layers = [] - # if stride != 1, the first block will downsample the feature map - # plane is the feature dim - # if Bottleneck, then the output dim is planes * block.expansion(4) - layers.append(block(self.inplanes, planes, stride, downsample, self.groups, self.base_width, previous_dilation, norm_layer)) - self.inplanes = planes * block.expansion - for _ in range(1, blocks): - layers.append(block(self.inplanes, planes, groups=self.groups, base_width=self.base_width, dilation=self.dilation, norm_layer=norm_layer)) - - return nn.Sequential(*layers) - - def forward(self, x: Tensor): - interm_features = [] - - for i in range(self.layernum): - x = eval(f"self.layer{i}")(x) - interm_features.append(x) - - return interm_features diff --git a/OpenCOOD/opencood/models/sub_modules/roi_head.py b/OpenCOOD/opencood/models/sub_modules/roi_head.py deleted file mode 100644 index 28e4f0e0..00000000 --- a/OpenCOOD/opencood/models/sub_modules/roi_head.py +++ /dev/null @@ -1,263 +0,0 @@ -import copy - -import torch.nn as nn -import torch -import numpy as np -from opencood.pcdet_utils.pointnet2.pointnet2_stack import pointnet2_modules as pointnet2_stack_modules -from opencood.utils import common_utils -from opencood.pcdet_utils.iou3d_nms.iou3d_nms_utils import boxes_iou3d_gpu -from opencood.utils import box_utils - - -class RoIHead(nn.Module): - def __init__(self, model_cfg): - super().__init__() - self.model_cfg = model_cfg - input_channels = model_cfg["in_channels"] - self.code_size = 7 - - mlps = copy.copy(self.model_cfg["roi_grid_pool"]["mlps"]) - for k in range(len(mlps)): - mlps[k] = [input_channels] + mlps[k] - - self.roi_grid_pool_layer = pointnet2_stack_modules.StackSAModuleMSG( - radii=self.model_cfg["roi_grid_pool"]["pool_radius"], - nsamples=self.model_cfg["roi_grid_pool"]["n_sample"], - mlps=mlps, - use_xyz=True, - pool_method=self.model_cfg["roi_grid_pool"]["pool_method"], - ) - - grid_size = self.model_cfg["roi_grid_pool"]["grid_size"] - self.grid_size = grid_size - c_out = sum([x[-1] for x in mlps]) - pre_channel = grid_size * grid_size * grid_size * c_out - fc_layers = [self.model_cfg["n_fc_neurons"]] * 2 - self.shared_fc_layers, pre_channel = self._make_fc_layers(pre_channel, fc_layers) - - self.cls_layers, pre_channel = self._make_fc_layers(pre_channel, fc_layers, output_channels=self.model_cfg["num_cls"]) - self.iou_layers, _ = self._make_fc_layers(pre_channel, fc_layers, output_channels=self.model_cfg["num_cls"]) - self.reg_layers, _ = self._make_fc_layers(pre_channel, fc_layers, output_channels=self.model_cfg["num_cls"] * 7) - - self._init_weights(weight_init="xavier") - - def _init_weights(self, weight_init="xavier"): - if weight_init == "kaiming": - init_func = nn.init.kaiming_normal_ - elif weight_init == "xavier": - init_func = nn.init.xavier_normal_ - elif weight_init == "normal": - init_func = nn.init.normal_ - else: - raise NotImplementedError - - for m in self.modules(): - if isinstance(m, nn.Conv2d) or isinstance(m, nn.Conv1d): - if weight_init == "normal": - init_func(m.weight, mean=0, std=0.001) - else: - init_func(m.weight) - if m.bias is not None: - nn.init.constant_(m.bias, 0) - nn.init.normal_(self.reg_layers[-1].weight, mean=0, std=0.001) - - def _make_fc_layers(self, input_channels, fc_list, output_channels=None): - fc_layers = [] - pre_channel = input_channels - for k in range(len(fc_list)): - fc_layers.extend( - [ - nn.Conv1d(pre_channel, fc_list[k], kernel_size=1, bias=False), - # nn.BatchNorm1d(fc_list[k]), - nn.ReLU(), - ] - ) - pre_channel = fc_list[k] - if self.model_cfg["dp_ratio"] > 0: - fc_layers.append(nn.Dropout(self.model_cfg["dp_ratio"])) - if output_channels is not None: - fc_layers.append(nn.Conv1d(pre_channel, output_channels, kernel_size=1, bias=True)) - fc_layers = nn.Sequential(*fc_layers) - return fc_layers, pre_channel - - def get_global_grid_points_of_roi(self, rois): - rois = rois.view(-1, rois.shape[-1]) - batch_size_rcnn = rois.shape[0] - - # (B, 6x6x6, 3) - local_roi_grid_points = self.get_dense_grid_points(rois, batch_size_rcnn, self.grid_size) - global_roi_grid_points = common_utils.rotate_points_along_z(local_roi_grid_points.clone(), rois[:, 6]).squeeze(dim=1) - global_center = rois[:, 0:3].clone() - global_roi_grid_points += global_center.unsqueeze(dim=1) - return global_roi_grid_points, local_roi_grid_points - - @staticmethod - def get_dense_grid_points(rois, batch_size_rcnn, grid_size): - """ - Get the local coordinates of each grid point of a roi in the coordinate - system of the roi(origin lies in the center of this roi. - """ - faked_features = rois.new_ones((grid_size, grid_size, grid_size)) - dense_idx = torch.stack(torch.where(faked_features), dim=1) # (N, 3) [x_idx, y_idx, z_idx] - dense_idx = dense_idx.repeat(batch_size_rcnn, 1, 1).float() # (B, 6x6x6, 3) - - local_roi_size = rois.view(batch_size_rcnn, -1)[:, 3:6] - roi_grid_points = (dense_idx + 0.5) / grid_size * local_roi_size.unsqueeze(dim=1) - (local_roi_size.unsqueeze(dim=1) / 2) # (B, 6x6x6, 3) - return roi_grid_points - - def assign_targets(self, batch_dict): - batch_dict["rcnn_label_dict"] = { - "rois": [], - "gt_of_rois": [], - "gt_of_rois_src": [], - "cls_tgt": [], - "reg_tgt": [], - "iou_tgt": [], - "rois_anchor": [], - "record_len": [], - } - # pred_boxes = [boxes[:, [0, 1, 2, 5, 4, 3, 6]] for boxes in batch_dict['boxes_fused']] - pred_boxes = batch_dict["boxes_fused"] - gt_boxes = [b[m][:, [0, 1, 2, 5, 4, 3, 6]].float() for b, m in zip(batch_dict["object_bbx_center"], batch_dict["object_bbx_mask"].bool())] - for rois, gts in zip(pred_boxes, gt_boxes): - gts[:, -1] *= 1 - ious = boxes_iou3d_gpu(rois, gts) - max_ious, gt_inds = ious.max(dim=1) - gt_of_rois = gts[gt_inds] - rcnn_labels = (max_ious > 0.3).float() - mask = torch.logical_not(rcnn_labels.bool()) - - # set negative samples back to rois, no correction in stage2 for them - gt_of_rois[mask] = rois[mask] - gt_of_rois_src = gt_of_rois.clone().detach() - - # canoical transformation - roi_center = rois[:, 0:3] - # TODO: roi_ry > 0 in pcdet - roi_ry = rois[:, 6] % (2 * np.pi) - gt_of_rois[:, 0:3] = gt_of_rois[:, 0:3] - roi_center - gt_of_rois[:, 6] = gt_of_rois[:, 6] - roi_ry - - # transfer LiDAR coords to local coords - gt_of_rois = common_utils.rotate_points_along_z(points=gt_of_rois.view(-1, 1, gt_of_rois.shape[-1]), angle=-roi_ry.view(-1)).view( - -1, gt_of_rois.shape[-1] - ) - - # flip orientation if rois have opposite orientation - heading_label = ( - gt_of_rois[:, 6] + (torch.div(torch.abs(gt_of_rois[:, 6].min()), (2 * np.pi), rounding_mode="trunc") + 1) * 2 * np.pi - ) % (2 * np.pi) # 0 ~ 2pi - opposite_flag = (heading_label > np.pi * 0.5) & (heading_label < np.pi * 1.5) - - # (0 ~ pi/2, 3pi/2 ~ 2pi) - heading_label[opposite_flag] = (heading_label[opposite_flag] + np.pi) % (2 * np.pi) - flag = heading_label > np.pi - heading_label[flag] = heading_label[flag] - np.pi * 2 # (-pi/2, pi/2) - heading_label = torch.clamp(heading_label, min=-np.pi / 2, max=np.pi / 2) - gt_of_rois[:, 6] = heading_label - - # generate regression target - rois_anchor = rois.clone().detach().view(-1, self.code_size) - rois_anchor[:, 0:3] = 0 - rois_anchor[:, 6] = 0 - - reg_targets = box_utils.box_encode(gt_of_rois.view(-1, self.code_size), rois_anchor) - - batch_dict["rcnn_label_dict"]["rois"].append(rois) - batch_dict["rcnn_label_dict"]["gt_of_rois"].append(gt_of_rois) - batch_dict["rcnn_label_dict"]["gt_of_rois_src"].append(gt_of_rois_src) - batch_dict["rcnn_label_dict"]["cls_tgt"].append(rcnn_labels) - batch_dict["rcnn_label_dict"]["reg_tgt"].append(reg_targets) - batch_dict["rcnn_label_dict"]["iou_tgt"].append(max_ious) - batch_dict["rcnn_label_dict"]["rois_anchor"].append(rois_anchor) - batch_dict["rcnn_label_dict"]["record_len"].append(rois.shape[0]) - - # cat list to tensor - for k, v in batch_dict["rcnn_label_dict"].items(): - if k == "record_len": - continue - batch_dict["rcnn_label_dict"][k] = torch.cat(v, dim=0) - - return batch_dict - - def prepare_rois(self, batch_dict): - """ - Prepare fused proposals for inference without ground truth. - - Parameters - ---------- - batch_dict : dict - Model state containing one ``boxes_fused`` tensor per scene. - - Returns - ------- - dict - Model state with the proposal fields required by RoI pooling and - stage-two box decoding. - """ - rois_by_scene = batch_dict["boxes_fused"] - rois = torch.cat(rois_by_scene, dim=0) - rois_anchor = rois.clone().detach().view(-1, self.code_size) - rois_anchor[:, 0:3] = 0 - rois_anchor[:, 6] = 0 - batch_dict["rcnn_label_dict"] = { - "rois": rois, - "rois_anchor": rois_anchor, - "record_len": [rois.shape[0] for rois in rois_by_scene], - } - return batch_dict - - def roi_grid_pool(self, batch_dict): - batch_size = len(batch_dict["record_len"]) - rois = batch_dict["rcnn_label_dict"]["rois"] - point_coords = batch_dict["point_coords"] - point_features = batch_dict["point_features"] - label_record_len = batch_dict["rcnn_label_dict"]["record_len"] - - point_features = torch.cat(point_features, dim=0) - # (BxN, 6x6x6, 3) - global_roi_grid_points, _ = self.get_global_grid_points_of_roi(rois) - # (B, Nx6x6x6, 3) - global_roi_grid_points = global_roi_grid_points.view(batch_size, -1, 3) - - xyz = torch.cat(point_coords, dim=0) - xyz_batch_cnt = xyz.new_zeros(batch_size).int() - for bs_idx in range(batch_size): - xyz_batch_cnt[bs_idx] = len(point_coords[bs_idx]) - new_xyz = global_roi_grid_points.view(-1, 3) - new_xyz_batch_cnt = xyz.new_zeros(batch_size).int() - for bs_idx in range(batch_size): - new_xyz_batch_cnt[bs_idx] = label_record_len[bs_idx] * self.grid_size**3 - - _, pooled_features = self.roi_grid_pool_layer( - xyz=xyz[:, :3].contiguous(), - xyz_batch_cnt=xyz_batch_cnt, - new_xyz=new_xyz[:, :3].contiguous(), - new_xyz_batch_cnt=new_xyz_batch_cnt, - features=point_features.contiguous(), # weighted point features - ) # (M1 + M2 ..., C) - # (BxN, 6x6x6, C) - pooled_features = pooled_features.view(-1, self.grid_size**3, pooled_features.shape[-1]) - - return pooled_features - - def forward(self, batch_dict): - batch_dict = self.assign_targets(batch_dict) if self.training else self.prepare_rois(batch_dict) - # RoI aware pooling - pooled_features = self.roi_grid_pool(batch_dict) # (BxN, 6x6x6, C) - - batch_size_rcnn = pooled_features.shape[0] - pooled_features = ( - pooled_features.permute(0, 2, 1).contiguous().view(batch_size_rcnn, -1, self.grid_size, self.grid_size, self.grid_size) - ) # (BxN, C, 6, 6, 6) - shared_features = self.shared_fc_layers(pooled_features.view(batch_size_rcnn, -1, 1)) - rcnn_cls = self.cls_layers(shared_features).transpose(1, 2).contiguous().squeeze(dim=1) # (B, 1 or 2) - rcnn_iou = self.iou_layers(shared_features).transpose(1, 2).contiguous().squeeze(dim=1) # (B, 1) - rcnn_reg = self.reg_layers(shared_features).transpose(1, 2).contiguous().squeeze(dim=1) # (B, C) - - batch_dict["fpvrcnn_out"] = { - "rcnn_cls": rcnn_cls, - "rcnn_iou": rcnn_iou, - "rcnn_reg": rcnn_reg, - } - return batch_dict diff --git a/OpenCOOD/opencood/models/sub_modules/sparse_backbone_3d.py b/OpenCOOD/opencood/models/sub_modules/sparse_backbone_3d.py deleted file mode 100644 index ffa1f392..00000000 --- a/OpenCOOD/opencood/models/sub_modules/sparse_backbone_3d.py +++ /dev/null @@ -1,134 +0,0 @@ -from functools import partial - -import torch.nn as nn - -try: # spconv1 - from spconv import SparseSequential, SubMConv3d, SparseConv3d, SparseInverseConv3d, SparseConvTensor -except ImportError: # spconv2 - from spconv.pytorch import SparseSequential, SubMConv3d, SparseConv3d, SparseInverseConv3d, SparseConvTensor - - -def post_act_block(in_channels, out_channels, kernel_size, indice_key=None, stride=1, padding=0, conv_type="subm", norm_fn=None): - if conv_type == "subm": - conv = SubMConv3d(in_channels, out_channels, kernel_size, bias=False, indice_key=indice_key) - elif conv_type == "spconv": - conv = SparseConv3d(in_channels, out_channels, kernel_size, stride=stride, padding=padding, bias=False, indice_key=indice_key) - elif conv_type == "inverseconv": - conv = SparseInverseConv3d(in_channels, out_channels, kernel_size, indice_key=indice_key, bias=False) - else: - raise NotImplementedError - - m = SparseSequential( - conv, - norm_fn(out_channels), - nn.ReLU(), - ) - - return m - - -class VoxelBackBone8x(nn.Module): - def __init__(self, model_cfg, input_channels, grid_size, **kwargs): - super().__init__() - self.model_cfg = model_cfg - norm_fn = partial(nn.BatchNorm1d, eps=1e-3, momentum=0.01) - - self.sparse_shape = grid_size[::-1] + [1, 0, 0] - - self.conv_input = SparseSequential( - SubMConv3d(input_channels, 16, 3, padding=1, bias=False, indice_key="subm1"), - norm_fn(16), - nn.ReLU(), - ) - block = post_act_block - - self.conv1 = SparseSequential( - block(16, 16, 3, norm_fn=norm_fn, padding=1, indice_key="subm1"), - ) - - self.conv2 = SparseSequential( - # [1600, 1408, 41] <- [800, 704, 21] - block(16, 32, 3, norm_fn=norm_fn, stride=2, padding=1, indice_key="spconv2", conv_type="spconv"), - block(32, 32, 3, norm_fn=norm_fn, padding=1, indice_key="subm2"), - block(32, 32, 3, norm_fn=norm_fn, padding=1, indice_key="subm2"), - ) - - self.conv3 = SparseSequential( - # [800, 704, 21] <- [400, 352, 11] - block(32, 64, 3, norm_fn=norm_fn, stride=2, padding=1, indice_key="spconv3", conv_type="spconv"), - block(64, 64, 3, norm_fn=norm_fn, padding=1, indice_key="subm3"), - block(64, 64, 3, norm_fn=norm_fn, padding=1, indice_key="subm3"), - ) - - self.conv4 = SparseSequential( - # [400, 352, 11] <- [200, 176, 5] - block(64, 64, 3, norm_fn=norm_fn, stride=2, padding=(0, 1, 1), indice_key="spconv4", conv_type="spconv"), - block(64, 64, 3, norm_fn=norm_fn, padding=1, indice_key="subm4"), - block(64, 64, 3, norm_fn=norm_fn, padding=1, indice_key="subm4"), - ) - - last_pad = 0 - if "num_features_out" in self.model_cfg: - self.num_point_features = self.model_cfg["num_features_out"] - else: - self.num_point_features = 128 - self.conv_out = SparseSequential( - # [200, 150, 5] -> [200, 150, 2] - SparseConv3d(64, self.num_point_features, (3, 1, 1), stride=(2, 1, 1), padding=last_pad, bias=False, indice_key="spconv_down2"), - norm_fn(self.num_point_features), - nn.ReLU(), - ) - - self.backbone_channels = {"x_conv1": 16, "x_conv2": 32, "x_conv3": 64, "x_conv4": 64} # noqa: DC05 - - def forward(self, batch_dict): - """ - Args: - batch_dict: - batch_size: int - vfe_features: (num_voxels, C) - voxel_coords: (num_voxels, 4), [batch_idx, z_idx, y_idx, x_idx] - Returns: - batch_dict: - encoded_spconv_tensor: sparse tensor - """ - voxel_features, voxel_coords = batch_dict["voxel_features"], batch_dict["voxel_coords"] - batch_size = batch_dict["batch_size"] - input_sp_tensor = SparseConvTensor( - features=voxel_features, indices=voxel_coords.int(), spatial_shape=self.sparse_shape, batch_size=batch_size - ) - - x = self.conv_input(input_sp_tensor) - - x_conv1 = self.conv1(x) - x_conv2 = self.conv2(x_conv1) - x_conv3 = self.conv3(x_conv2) - x_conv4 = self.conv4(x_conv3) - - # for detection head - # [200, 176, 5] -> [200, 176, 2] - out = self.conv_out(x_conv4) - - batch_dict.update({"encoded_spconv_tensor": out, "encoded_spconv_tensor_stride": 8}) - batch_dict.update( - { - "multi_scale_3d_features": { - "x_conv1": x_conv1, - "x_conv2": x_conv2, - "x_conv3": x_conv3, - "x_conv4": x_conv4, - } - } - ) - batch_dict.update( - { - "multi_scale_3d_strides": { - "x_conv1": 1, - "x_conv2": 2, - "x_conv3": 4, - "x_conv4": 8, - } - } - ) - - return batch_dict diff --git a/OpenCOOD/opencood/models/sub_modules/split_attn.py b/OpenCOOD/opencood/models/sub_modules/split_attn.py deleted file mode 100644 index 8341fdf2..00000000 --- a/OpenCOOD/opencood/models/sub_modules/split_attn.py +++ /dev/null @@ -1,63 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F - - -class RadixSoftmax(nn.Module): - def __init__(self, radix, cardinality): - super(RadixSoftmax, self).__init__() - self.radix = radix - self.cardinality = cardinality - - def forward(self, x): - # x: (B, L, 1, 1, 3C) - batch = x.size(0) - cav_num = x.size(1) - - if self.radix > 1: - # x: (B, L, 1, 3, C) - x = x.view(batch, cav_num, self.cardinality, self.radix, -1) - x = F.softmax(x, dim=3) - # B, 3LC - x = x.reshape(batch, -1) - else: - x = torch.sigmoid(x) - return x - - -class SplitAttn(nn.Module): - def __init__(self, input_dim): - super(SplitAttn, self).__init__() - self.input_dim = input_dim - - self.fc1 = nn.Linear(input_dim, input_dim, bias=False) - self.bn1 = nn.LayerNorm(input_dim) - self.act1 = nn.ReLU() - self.fc2 = nn.Linear(input_dim, input_dim * 3, bias=False) - - self.rsoftmax = RadixSoftmax(3, 1) - - def forward(self, window_list): - # window list: [(B, L, H, W, C) * 3] - assert len(window_list) == 3, "only 3 windows are supported" - - sw, mw, bw = window_list[0], window_list[1], window_list[2] - B, L = sw.shape[0], sw.shape[1] - - # global average pooling, B, L, H, W, C - x_gap = sw + mw + bw - # B, L, 1, 1, C - x_gap = x_gap.mean((2, 3), keepdim=True) - x_gap = self.act1(self.bn1(self.fc1(x_gap))) - # B, L, 1, 1, 3C - x_attn = self.fc2(x_gap) - # B L 1 1 3C - x_attn = self.rsoftmax(x_attn).view(B, L, 1, 1, -1) - - out = ( - sw * x_attn[:, :, :, :, 0 : self.input_dim] - + mw * x_attn[:, :, :, :, self.input_dim : 2 * self.input_dim] - + bw * x_attn[:, :, :, :, self.input_dim * 2 :] - ) - - return out diff --git a/OpenCOOD/opencood/models/sub_modules/torch_transformation_utils.py b/OpenCOOD/opencood/models/sub_modules/torch_transformation_utils.py deleted file mode 100644 index f7804ba8..00000000 --- a/OpenCOOD/opencood/models/sub_modules/torch_transformation_utils.py +++ /dev/null @@ -1,396 +0,0 @@ -""" -torch_transformation_utils.py -""" - -import os - -import torch -import torch.nn.functional as F -import numpy as np -import matplotlib.pyplot as plt - - -def get_roi_and_cav_mask(shape, cav_mask, spatial_correction_matrix, discrete_ratio, downsample_rate): - """ - Get mask for the combination of cav_mask and rorated ROI mask. - Parameters - ---------- - shape : tuple - Shape of (B, L, H, W, C). - cav_mask : torch.Tensor - Shape of (B, L). - spatial_correction_matrix : torch.Tensor - Shape of (B, L, 4, 4) - discrete_ratio : float - Discrete ratio. - downsample_rate : float - Downsample rate. - - Returns - ------- - com_mask : torch.Tensor - Combined mask with shape (B, H, W, L, 1). - - """ - B, L, H, W, C = shape - C = 1 - # (B,L,4,4) - dist_correction_matrix = get_discretized_transformation_matrix(spatial_correction_matrix, discrete_ratio, downsample_rate) - # (B*L,2,3) - T = get_transformation_matrix(dist_correction_matrix.reshape(-1, 2, 3), (H, W)) - # (B,L,1,H,W) - roi_mask = get_rotated_roi((B, L, C, H, W), T) - # (B,L,1,H,W) - com_mask = combine_roi_and_cav_mask(roi_mask, cav_mask) - # (B,H,W,1,L) - com_mask = com_mask.permute(0, 3, 4, 2, 1) - return com_mask - - -def combine_roi_and_cav_mask(roi_mask, cav_mask): - """ - Combine ROI mask and CAV mask - - Parameters - ---------- - roi_mask : torch.Tensor - Mask for ROI region after considering the spatial transformation/correction. - cav_mask : torch.Tensor - Mask for CAV to remove padded 0. - - Returns - ------- - com_mask : torch.Tensor - Combined mask. - """ - # (B, L, 1, 1, 1) - cav_mask = cav_mask.unsqueeze(2).unsqueeze(3).unsqueeze(4) - # (B, L, C, H, W) - cav_mask = cav_mask.expand(roi_mask.shape) - # (B, L, C, H, W) - com_mask = roi_mask * cav_mask - return com_mask - - -def get_rotated_roi(shape, correction_matrix): - """ - Get rorated ROI mask. - - Parameters - ---------- - shape : tuple - Shape of (B,L,C,H,W). - correction_matrix : torch.Tensor - Correction matrix with shape (N,2,3). - - Returns - ------- - roi_mask : torch.Tensor - Roated ROI mask with shape (N,2,3). - - """ - B, L, C, H, W = shape - # To reduce the computation, we only need to calculate the - # mask for the first channel. - # (B,L,1,H,W) - x = torch.ones((B, L, 1, H, W)).to(correction_matrix.dtype).to(correction_matrix.device) - # (B*L,1,H,W) - roi_mask = warp_affine(x.reshape(-1, 1, H, W), correction_matrix, dsize=(H, W), mode="nearest") - # (B,L,C,H,W) - roi_mask = torch.repeat_interleave(roi_mask, C, dim=1).reshape(B, L, C, H, W) - return roi_mask - - -def get_discretized_transformation_matrix(matrix, discrete_ratio, downsample_rate): - """ - Get disretized transformation matrix. - Parameters - ---------- - matrix : torch.Tensor - Shape -- (B, L, 4, 4) where B is the batch size, L is the max cav - number. - discrete_ratio : float - Discrete ratio. - downsample_rate : float/int - downsample_rate - - Returns - ------- - matrix : torch.Tensor - Output transformation matrix in 2D with shape (B, L, 2, 3), - including 2D transformation and 2D rotation. - - """ - matrix = matrix[:, :, [0, 1], :][:, :, :, [0, 1, 3]] - # normalize the x,y transformation - matrix[:, :, :, -1] = matrix[:, :, :, -1] / (discrete_ratio * downsample_rate) - - return matrix.type(dtype=torch.float) - - -def _torch_inverse_cast(input): - r""" - Helper function to make torch.inverse work with other than fp32/64. - The function torch.inverse is only implemented for fp32/64 which makes - impossible to be used by fp16 or others. What this function does, - is cast input data type to fp32, apply torch.inverse, - and cast back to the input dtype. - Args: - input : torch.Tensor - Tensor to be inversed. - - Returns: - out : torch.Tensor - Inversed Tensor. - - """ - dtype = input.dtype - if dtype not in (torch.float32, torch.float64): - dtype = torch.float32 - out = torch.inverse(input.to(dtype)).to(input.dtype) - return out - - -def normal_transform_pixel(height, width, device, dtype, eps=1e-14): - r""" - Compute the normalization matrix from image size in pixels to [-1, 1]. - Args: - height : int - Image height. - width : int - Image width. - device : torch.device - Output tensor devices. - dtype : torch.dtype - Output tensor data type. - eps : float - Epsilon to prevent divide-by-zero errors. - - Returns: - tr_mat : torch.Tensor - Normalized transform with shape :math:`(1, 3, 3)`. - """ - tr_mat = torch.tensor([[1.0, 0.0, -1.0], [0.0, 1.0, -1.0], [0.0, 0.0, 1.0]], device=device, dtype=dtype) # 3x3 - - # prevent divide by zero bugs - width_denom = eps if width == 1 else width - 1.0 - height_denom = eps if height == 1 else height - 1.0 - - tr_mat[0, 0] = tr_mat[0, 0] * 2.0 / width_denom - tr_mat[1, 1] = tr_mat[1, 1] * 2.0 / height_denom - - return tr_mat.unsqueeze(0) # 1x3x3 - - -def eye_like(n, B, device, dtype): - r""" - Return a 2-D tensor with ones on the diagonal and - zeros elsewhere with the same batch size as the input. - Args: - n : int - The number of rows :math:`(n)`. - B : int - Btach size. - device : torch.device - Devices of the output tensor. - dtype : torch.dtype - Data type of the output tensor. - - Returns: - The identity matrix with the shape :math:`(B, n, n)`. - """ - - identity = torch.eye(n, device=device, dtype=dtype) - return identity[None].repeat(B, 1, 1) - - -def normalize_homography(dst_pix_trans_src_pix, dsize_src, dsize_dst=None): - r""" - Normalize a given homography in pixels to [-1, 1]. - Args: - dst_pix_trans_src_pix : torch.Tensor - Homography/ies from source to destination to be normalized with - shape :math:`(B, 3, 3)`. - dsize_src : Tuple[int, int] - Size of the source image (height, width). - dsize_dst : Tuple[int, int] - Size of the destination image (height, width). - - Returns: - dst_norm_trans_src_norm : torch.Tensor - The normalized homography of shape :math:`(B, 3, 3)`. - """ - if dsize_dst is None: - dsize_dst = dsize_src - # source and destination sizes - src_h, src_w = dsize_src - dst_h, dst_w = dsize_dst - device = dst_pix_trans_src_pix.device - dtype = dst_pix_trans_src_pix.dtype - # compute the transformation pixel/norm for src/dst - src_norm_trans_src_pix = normal_transform_pixel(src_h, src_w, device, dtype).to(dst_pix_trans_src_pix) - - src_pix_trans_src_norm = _torch_inverse_cast(src_norm_trans_src_pix) - dst_norm_trans_dst_pix = normal_transform_pixel(dst_h, dst_w, device, dtype).to(dst_pix_trans_src_pix) - # compute chain transformations - dst_norm_trans_src_norm: torch.Tensor = dst_norm_trans_dst_pix @ (dst_pix_trans_src_pix @ src_pix_trans_src_norm) - return dst_norm_trans_src_norm - - -def get_rotation_matrix2d(M, dsize): - r""" - Return rotation matrix for torch.affine_grid based on transformation matrix. - Args: - M : torch.Tensor - Transformation matrix with shape :math:`(B, 2, 3)`. - dsize : Tuple[int, int] - Size of the source image (height, width). - - Returns: - R : torch.Tensor - Rotation matrix with shape :math:`(B, 2, 3)`. - """ - H, W = dsize - B = M.shape[0] - center = torch.Tensor([W / 2, H / 2]).to(M.dtype).to(M.device).unsqueeze(0) - shift_m = eye_like(3, B, M.device, M.dtype) - shift_m[:, :2, 2] = center - - shift_m_inv = eye_like(3, B, M.device, M.dtype) - shift_m_inv[:, :2, 2] = -center - - rotat_m = eye_like(3, B, M.device, M.dtype) - rotat_m[:, :2, :2] = M[:, :2, :2] - affine_m = shift_m @ rotat_m @ shift_m_inv - return affine_m[:, :2, :] # Bx2x3 - - -def get_transformation_matrix(M, dsize): - r""" - Return transformation matrix for torch.affine_grid. - Args: - M : torch.Tensor - Transformation matrix with shape :math:`(N, 2, 3)`. - dsize : Tuple[int, int] - Size of the source image (height, width). - - Returns: - T : torch.Tensor - Transformation matrix with shape :math:`(N, 2, 3)`. - """ - T = get_rotation_matrix2d(M, dsize) - T[..., 2] += M[..., 2] - return T - - -def convert_affinematrix_to_homography(A): - r""" - Convert to homography coordinates - Args: - A : torch.Tensor - The affine matrix with shape :math:`(B,2,3)`. - - Returns: - H : torch.Tensor - The homography matrix with shape of :math:`(B,3,3)`. - """ - H: torch.Tensor = torch.nn.functional.pad(A, [0, 0, 0, 1], "constant", value=0.0) - H[..., -1, -1] += 1.0 - return H - - -def warp_affine(src, M, dsize, mode="bilinear", padding_mode="zeros", align_corners=True): - r""" - Transform the src based on transformation matrix M. - Args: - src : torch.Tensor - Input feature map with shape :math:`(B,C,H,W)`. - M : torch.Tensor - Transformation matrix with shape :math:`(B,2,3)`. - dsize : tuple - Tuple of output image H_out and W_out. - mode : str - Interpolation methods for F.grid_sample. - padding_mode : str - Padding methods for F.grid_sample. - align_corners : boolean - Parameter of F.affine_grid. - - Returns: - Transformed features with shape :math:`(B,C,H,W)`. - """ - - B, C, H, W = src.size() - - # we generate a 3x3 transformation matrix from 2x3 affine - M_3x3 = convert_affinematrix_to_homography(M) - dst_norm_trans_src_norm = normalize_homography(M_3x3, (H, W), dsize) - - # src_norm_trans_dst_norm = torch.inverse(dst_norm_trans_src_norm) - src_norm_trans_dst_norm = _torch_inverse_cast(dst_norm_trans_src_norm) - grid = F.affine_grid(src_norm_trans_dst_norm[:, :2, :], [B, C, dsize[0], dsize[1]], align_corners=align_corners) - - return F.grid_sample(src.half() if grid.dtype == torch.half else src, grid, align_corners=align_corners, mode=mode, padding_mode=padding_mode) - - -class Test: - """ - Test the transformation in this file. - The methods in this class are not supposed to be used outside of this file. - """ - - def __init__(self): - pass - - @staticmethod - def load_img(): - torch.manual_seed(0) - x = torch.randn(1, 5, 16, 400, 200) * 100 - # x = torch.ones(1, 5, 16, 400, 200) - return x - - @staticmethod - def load_raw_transformation_matrix(N): - a = 90 / 180 * np.pi - matrix = torch.Tensor([[np.cos(a), -np.sin(a), 10], [np.sin(a), np.cos(a), 10]]) - matrix = torch.repeat_interleave(matrix.unsqueeze(0).unsqueeze(0), N, dim=1) - return matrix - - @staticmethod - def load_raw_transformation_matrix2(N, alpha): - a = alpha / 180 * np.pi - matrix = torch.Tensor([[np.cos(a), -np.sin(a), 0, 0], [np.sin(a), np.cos(a), 0, 0]]) - matrix = torch.repeat_interleave(matrix.unsqueeze(0).unsqueeze(0), N, dim=1) - return matrix - - @staticmethod - def test(): - img = Test.load_img() - B, L, C, H, W = img.shape - raw_T = Test.load_raw_transformation_matrix(5) - T = get_transformation_matrix(raw_T.reshape(-1, 2, 3), (H, W)) - img_rot = warp_affine(img.reshape(-1, C, H, W), T, (H, W)) - print(img_rot[0, 0, :, :]) - plt.matshow(img_rot[0, 0, :, :]) - plt.show() - - @staticmethod - def test_combine_roi_and_cav_mask(): - B = 2 - L = 5 - C = 16 - H = 300 - W = 400 - # 2, 5 - cav_mask = torch.Tensor([[1, 1, 1, 0, 0], [1, 0, 0, 0, 0]]) - _ = torch.zeros(B, L, C, H, W) # x - correction_matrix = Test.load_raw_transformation_matrix2(5, 10) - correction_matrix = torch.cat([correction_matrix, correction_matrix], dim=0) - mask = get_roi_and_cav_mask((B, L, H, W, C), cav_mask, correction_matrix, 0.4, 4) - plt.matshow(mask[0, :, :, 0, 0]) - plt.show() - - -if __name__ == "__main__": - os.environ["KMP_DUPLICATE_LIB_OK"] = "True" - Test.test_combine_roi_and_cav_mask() diff --git a/OpenCOOD/opencood/models/sub_modules/vsa.py b/OpenCOOD/opencood/models/sub_modules/vsa.py deleted file mode 100644 index 0c120aec..00000000 --- a/OpenCOOD/opencood/models/sub_modules/vsa.py +++ /dev/null @@ -1,264 +0,0 @@ -import copy - -import torch -import torch.nn as nn - -from opencood.pcdet_utils.pointnet2.pointnet2_stack import pointnet2_modules as pointnet2_stack_modules -from opencood.pcdet_utils.pointnet2.pointnet2_stack import pointnet2_utils as pointnet2_stack_utils -from opencood.pcdet_utils.roiaware_pool3d.roiaware_pool3d_utils import points_in_boxes_gpu -from opencood.utils import common_utils - - -def bilinear_interpolate_torch(im, x, y): - """ - Args: - im: (H, W, C) [y, x] - x: (N) - y: (N) - - Returns: - - """ - x0 = torch.floor(x).long() - x1 = x0 + 1 - - y0 = torch.floor(y).long() - y1 = y0 + 1 - - x0 = torch.clamp(x0, 0, im.shape[1] - 1) - x1 = torch.clamp(x1, 0, im.shape[1] - 1) - y0 = torch.clamp(y0, 0, im.shape[0] - 1) - y1 = torch.clamp(y1, 0, im.shape[0] - 1) - - Ia = im[y0, x0] - Ib = im[y1, x0] - Ic = im[y0, x1] - Id = im[y1, x1] - - wa = (x1.type_as(x) - x) * (y1.type_as(y) - y) - wb = (x1.type_as(x) - x) * (y - y0.type_as(y)) - wc = (x - x0.type_as(x)) * (y1.type_as(y) - y) - wd = (x - x0.type_as(x)) * (y - y0.type_as(y)) - ans = torch.t((torch.t(Ia) * wa)) + torch.t(torch.t(Ib) * wb) + torch.t(torch.t(Ic) * wc) + torch.t(torch.t(Id) * wd) - return ans - - -class VoxelSetAbstraction(nn.Module): - def __init__(self, model_cfg, voxel_size, point_cloud_range, num_bev_features=None, num_rawpoint_features=None, **kwargs): - super().__init__() - self.model_cfg = model_cfg - self.voxel_size = voxel_size - self.point_cloud_range = point_cloud_range - - SA_cfg = self.model_cfg["sa_layer"] - - self.SA_layers = nn.ModuleList() - self.SA_layer_names = [] - self.downsample_times_map = {} - c_in = 0 - for src_name in self.model_cfg["features_source"]: - if src_name in ["bev", "raw_points"]: - continue - self.downsample_times_map[src_name] = SA_cfg[src_name]["downsample_factor"] - mlps = copy.copy(SA_cfg[src_name]["mlps"]) - for k in range(len(mlps)): - mlps[k] = [mlps[k][0]] + mlps[k] - cur_layer = pointnet2_stack_modules.StackSAModuleMSG( - radii=SA_cfg[src_name]["pool_radius"], - nsamples=SA_cfg[src_name]["n_sample"], - mlps=mlps, - use_xyz=True, - pool_method="max_pool", - ) - self.SA_layers.append(cur_layer) - self.SA_layer_names.append(src_name) - - c_in += sum([x[-1] for x in mlps]) - - if "bev" in self.model_cfg["features_source"]: - c_bev = num_bev_features - c_in += c_bev - - if "raw_points" in self.model_cfg["features_source"]: - mlps = copy.copy(SA_cfg["raw_points"]["mlps"]) - for k in range(len(mlps)): - mlps[k] = [num_rawpoint_features - 3] + mlps[k] - - self.SA_rawpoints = pointnet2_stack_modules.StackSAModuleMSG( - radii=SA_cfg["raw_points"]["pool_radius"], nsamples=SA_cfg["raw_points"]["n_sample"], mlps=mlps, use_xyz=True, pool_method="max_pool" - ) - c_in += sum([x[-1] for x in mlps]) - - self.vsa_point_feature_fusion = nn.Sequential( - nn.Linear(c_in, self.model_cfg["num_out_features"], bias=False), - nn.BatchNorm1d(self.model_cfg["num_out_features"]), - nn.ReLU(), - ) - self.num_point_features = self.model_cfg["num_out_features"] - self.num_point_features_before_fusion = c_in # noqa: DC05 - - def interpolate_from_bev_features(self, keypoints, bev_features, batch_size, bev_stride): - x_idxs = (keypoints[:, :, 0] - self.point_cloud_range[0]) / self.voxel_size[0] - y_idxs = (keypoints[:, :, 1] - self.point_cloud_range[1]) / self.voxel_size[1] - x_idxs = x_idxs / bev_stride - y_idxs = y_idxs / bev_stride - - point_bev_features_list = [] - for k in range(batch_size): - cur_x_idxs = x_idxs[k] - cur_y_idxs = y_idxs[k] - cur_bev_features = bev_features[k].permute(1, 2, 0) # (H, W, C) - point_bev_features = bilinear_interpolate_torch(cur_bev_features, cur_x_idxs, cur_y_idxs) - point_bev_features_list.append(point_bev_features.unsqueeze(dim=0)) - - point_bev_features = torch.cat(point_bev_features_list, dim=0) # (B, N, C0) - return point_bev_features - - def get_sampled_points(self, batch_dict): - batch_size = batch_dict["batch_size"] - if self.model_cfg["point_source"] == "raw_points": - src_points = batch_dict["origin_lidar"][:, 1:] - batch_indices = batch_dict["origin_lidar"][:, 0].long() - elif self.model_cfg["point_source"] == "voxel_centers": - src_points = common_utils.get_voxel_centers( - batch_dict["voxel_coords"][:, 1:4], downsample_times=1, voxel_size=self.voxel_size, point_cloud_range=self.point_cloud_range - ) - batch_indices = batch_dict["voxel_coords"][:, 0].long() - else: - raise NotImplementedError - - keypoints_batch = torch.randn((batch_size, self.model_cfg["num_keypoints"], 4), device=src_points.device) - keypoints_batch[..., 0] = keypoints_batch[..., 0] * 140 - keypoints_batch[..., 1] = keypoints_batch[..., 0] * 40 - # points with height flag 10 are padding/invalid, for later filtering - keypoints_batch[..., 2] = 10.0 - for bs_idx in range(batch_size): - bs_mask = batch_indices == bs_idx - sampled_points = src_points[bs_mask].unsqueeze(dim=0) # (1, N, 3) - # sample points with FPS - # some cropped pcd may have very few points, select various number - # of points to ensure similar sample density - # 50000 is approximately the number of points in one full pcd - num_kpts = int(self.model_cfg["num_keypoints"] * sampled_points.shape[1] / 50000) + 1 - num_kpts = min(num_kpts, self.model_cfg["num_keypoints"]) - cur_pt_idxs = pointnet2_stack_utils.furthest_point_sample(sampled_points[:, :, 0:3].contiguous(), num_kpts).long() - - if sampled_points.shape[1] < num_kpts: - empty_num = num_kpts - sampled_points.shape[1] - cur_pt_idxs[0, -empty_num:] = cur_pt_idxs[0, :empty_num] - - keypoints = sampled_points[0][cur_pt_idxs[0]].unsqueeze(dim=0) - - keypoints_batch[bs_idx, : len(keypoints[0]), :] = keypoints - - # keypoints = torch.cat(keypoints_list, dim=0) # (B, M, 3) - return keypoints_batch - - def forward(self, batch_dict): - """ - Args: - batch_dict: - batch_size: - keypoints: (B, num_keypoints, 3) - multi_scale_3d_features: { - 'x_conv4': ... - } - points: optional (N, 1 + 3 + C) [bs_idx, x, y, z, ...] - spatial_features: optional - spatial_features_stride: optional - - Returns: - point_features: (N, C) - point_coords: (N, 4) - - """ - keypoints = self.get_sampled_points(batch_dict) # BxNx4 - kpt_mask1 = torch.logical_and(keypoints[..., 2] > -2.8, keypoints[..., 2] < 1.0) - kpt_mask2 = None - # Only select the points that are in the predicted bounding boxes - if "det_boxes" in batch_dict: - dets_list = batch_dict["det_boxes"] - max_len = max([len(dets) for dets in dets_list]) - boxes = torch.zeros((len(dets_list), max_len, 7), dtype=dets_list[0].dtype, device=dets_list[0].device) - for i, dets in enumerate(dets_list): - if len(dets) == 0: - continue - cur_dets = dets.clone() - if self.model_cfg["enlarge_selection_boxes"]: - cur_dets[:, 3:6] += 0.5 - boxes[i, : len(dets)] = cur_dets - # mask out some keypoints to spare the GPU storage - kpt_mask2 = points_in_boxes_gpu(keypoints[..., :3], boxes) >= 0 - - kpt_mask = torch.logical_and(kpt_mask1, kpt_mask2) if kpt_mask2 is not None else kpt_mask1 - # Ensure there are more than 2 points are selected to satisfy the - # condition of batch norm in the FC layers of feature fusion module - if (kpt_mask).sum() < 2: - kpt_mask[0, torch.randint(0, 1024, (2,))] = True - - point_features_list = [] - if "bev" in self.model_cfg["features_source"]: - point_bev_features = self.interpolate_from_bev_features( - keypoints[..., :3], batch_dict["spatial_features"], batch_dict["batch_size"], bev_stride=batch_dict["spatial_features_stride"] - ) - point_features_list.append(point_bev_features[kpt_mask]) - - batch_size, _, _ = keypoints.shape - - new_xyz = keypoints[kpt_mask] - new_xyz_batch_cnt = torch.tensor([(mask).sum() for mask in kpt_mask], device=new_xyz.device).int() - - if "raw_points" in self.model_cfg["features_source"]: - raw_points = batch_dict["origin_lidar"] - xyz = raw_points[:, 1:4] - xyz_batch_cnt = xyz.new_zeros(batch_size).int() - indices = raw_points[:, 0].long() - for bs_idx in range(batch_size): - xyz_batch_cnt[bs_idx] = (indices == bs_idx).sum() - point_features = None - - _, pooled_features = self.SA_rawpoints( - xyz=xyz.contiguous(), - xyz_batch_cnt=xyz_batch_cnt, - new_xyz=new_xyz[:, :3].contiguous(), - new_xyz_batch_cnt=new_xyz_batch_cnt, - features=point_features, - ) - point_features_list.append(pooled_features) - - for k, src_name in enumerate(self.SA_layer_names): - cur_coords = batch_dict["multi_scale_3d_features"][src_name].indices - xyz = common_utils.get_voxel_centers( - cur_coords[:, 1:4], - downsample_times=self.downsample_times_map[src_name], - voxel_size=self.voxel_size, - point_cloud_range=self.point_cloud_range, - ) - xyz_batch_cnt = xyz.new_zeros(batch_size).int() - for bs_idx in range(batch_size): - xyz_batch_cnt[bs_idx] = (cur_coords[:, 0] == bs_idx).sum() - - _, pooled_features = self.SA_layers[k]( - xyz=xyz.contiguous(), - xyz_batch_cnt=xyz_batch_cnt, - new_xyz=new_xyz[:, :3].contiguous(), - new_xyz_batch_cnt=new_xyz_batch_cnt, - features=batch_dict["multi_scale_3d_features"][src_name].features.contiguous(), - ) - - point_features_list.append(pooled_features) - - point_features = torch.cat(point_features_list, dim=1) - - batch_dict["point_features_before_fusion"] = point_features.view(-1, point_features.shape[-1]) - point_features = self.vsa_point_feature_fusion(point_features.view(-1, point_features.shape[-1])) - - cur_idx = 0 - batch_dict["point_features"] = [] - batch_dict["point_coords"] = [] - for num in new_xyz_batch_cnt: - batch_dict["point_features"].append(point_features[cur_idx : cur_idx + num]) - batch_dict["point_coords"].append(new_xyz[cur_idx : cur_idx + num]) - cur_idx += num - - return batch_dict diff --git a/OpenCOOD/opencood/models/voxel_net.py b/OpenCOOD/opencood/models/voxel_net.py deleted file mode 100644 index 0d7799f3..00000000 --- a/OpenCOOD/opencood/models/voxel_net.py +++ /dev/null @@ -1,231 +0,0 @@ -import torch.nn as nn -import torch.nn.functional as F -import torch - -from opencood.models.sub_modules.pillar_vfe import PillarVFE - - -# conv2d + bn + relu -class Conv2d(nn.Module): - def __init__(self, in_channels, out_channels, k, s, p, activation=True, batch_norm=True): - super(Conv2d, self).__init__() - self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=k, stride=s, padding=p) - if batch_norm: - self.bn = nn.BatchNorm2d(out_channels) - else: - self.bn = None - self.activation = activation - - def forward(self, x): - x = self.conv(x) - if self.bn is not None: - x = self.bn(x) - if self.activation: - return F.relu(x, inplace=True) - else: - return x - - -# conv3d + bn + relu -class Conv3d(nn.Module): - def __init__(self, in_channels, out_channels, k, s, p, batch_norm=True): - super(Conv3d, self).__init__() - self.conv = nn.Conv3d(in_channels, out_channels, kernel_size=k, stride=s, padding=p) - if batch_norm: - self.bn = nn.BatchNorm3d(out_channels) - else: - self.bn = None - - def forward(self, x): - x = self.conv(x) - if self.bn is not None: - x = self.bn(x) - - return F.relu(x, inplace=True) - - -# Fully Connected Network -class FCN(nn.Module): - def __init__(self, cin, cout): - super(FCN, self).__init__() - self.cout = cout - self.linear = nn.Linear(cin, cout) - self.bn = nn.BatchNorm1d(cout) - - def forward(self, x): - # KK is the stacked k across batch - kk, t, _ = x.shape - x = self.linear(x.view(kk * t, -1)) - x = F.relu(self.bn(x)) - return x.view(kk, t, -1) - - -# Voxel Feature Encoding layer -class VFE(nn.Module): - def __init__(self, cin, cout, T): - super(VFE, self).__init__() - assert cout % 2 == 0 - self.units = cout // 2 - self.fcn = FCN(cin, self.units) - self.T = T - - def forward(self, x, mask): - # point-wise feature - pwf = self.fcn(x) - # locally aggregated feature - laf = torch.max(pwf, 1)[0].unsqueeze(1).repeat(1, self.T, 1) - # point-wise concat feature - pwcf = torch.cat((pwf, laf), dim=2) - # apply mask - mask = mask.unsqueeze(2).repeat(1, 1, self.units * 2) - pwcf = pwcf * mask.float() - - return pwcf - - -# Stacked Voxel Feature Encoding -class SVFE(nn.Module): - def __init__(self, T): - super(SVFE, self).__init__() - self.vfe_1 = VFE(7, 32, T) - self.vfe_2 = VFE(32, 128, T) - self.fcn = FCN(128, 128) - - def forward(self, x): - mask = torch.ne(torch.max(x, 2)[0], 0) - x = self.vfe_1(x, mask) - x = self.vfe_2(x, mask) - x = self.fcn(x) - # element-wise max pooling - x = torch.max(x, 1)[0] - return x - - -# Convolutional Middle Layer -class CML(nn.Module): - def __init__(self): - super(CML, self).__init__() - self.conv3d_1 = Conv3d(64, 64, 3, s=(2, 1, 1), p=(1, 1, 1)) - self.conv3d_2 = Conv3d(64, 64, 3, s=(1, 1, 1), p=(0, 1, 1)) - self.conv3d_3 = Conv3d(64, 64, 3, s=(2, 1, 1), p=(1, 1, 1)) - - def forward(self, x): - x = self.conv3d_1(x) - x = self.conv3d_2(x) - x = self.conv3d_3(x) - return x - - -# Region Proposal Network -class RPN(nn.Module): - def __init__(self, anchor_num=2): - super(RPN, self).__init__() - self.anchor_num = anchor_num - - self.block_1 = [Conv2d(128, 128, 3, 2, 1)] - self.block_1 += [Conv2d(128, 128, 3, 1, 1) for _ in range(3)] - self.block_1 = nn.Sequential(*self.block_1) - - self.block_2 = [Conv2d(128, 128, 3, 2, 1)] - self.block_2 += [Conv2d(128, 128, 3, 1, 1) for _ in range(5)] - self.block_2 = nn.Sequential(*self.block_2) - - self.block_3 = [Conv2d(128, 256, 3, 2, 1)] - self.block_3 += [nn.Conv2d(256, 256, 3, 1, 1) for _ in range(5)] - self.block_3 = nn.Sequential(*self.block_3) - - self.deconv_1 = nn.Sequential(nn.ConvTranspose2d(256, 256, 4, 4, 0), nn.BatchNorm2d(256)) - self.deconv_2 = nn.Sequential(nn.ConvTranspose2d(128, 256, 2, 2, 0), nn.BatchNorm2d(256)) - self.deconv_3 = nn.Sequential(nn.ConvTranspose2d(128, 256, 1, 1, 0), nn.BatchNorm2d(256)) - - self.score_head = Conv2d(768, self.anchor_num, 1, 1, 0, activation=False, batch_norm=False) - self.reg_head = Conv2d(768, 7 * self.anchor_num, 1, 1, 0, activation=False, batch_norm=False) - - def forward(self, x): - x = self.block_1(x) - x_skip_1 = x - x = self.block_2(x) - x_skip_2 = x - x = self.block_3(x) - x_0 = self.deconv_1(x) - x_1 = self.deconv_2(x_skip_2) - x_2 = self.deconv_3(x_skip_1) - x = torch.cat((x_0, x_1, x_2), 1) - return self.score_head(x), self.reg_head(x) - - -class VoxelNet(nn.Module): - def __init__(self, args): - super(VoxelNet, self).__init__() - self.svfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - - # self.svfe = SVFE(args['T']) - self.cml = CML() - self.rpn = RPN(args["anchor_num"]) - - self.N = args["N"] - self.D = args["D"] - self.H = args["H"] - self.W = args["W"] - self.T = args["T"] - self.anchor_num = args["anchor_num"] - - def voxel_indexing(self, sparse_features, coords, batch_size): - """ - Scatter sparse voxel features into a dense model batch. - - Parameters - ---------- - sparse_features : torch.Tensor - Encoded features for non-empty voxels. - coords : torch.Tensor - Batched voxel coordinates. - batch_size : int - Number of point clouds represented by the coordinates. - - Returns - ------- - torch.Tensor - Dense voxel feature tensor. - """ - dim = sparse_features.shape[-1] - dense_feature = torch.zeros( - dim, - batch_size, - self.D, - self.H, - self.W, - device=sparse_features.device, - dtype=sparse_features.dtype, - ) - coords = coords.long() - - dense_feature[:, coords[:, 0], coords[:, 1], coords[:, 2], coords[:, 3]] = sparse_features.transpose(0, 1) - - return dense_feature.transpose(0, 1) - - def forward(self, data_dict): - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - batch_size = data_dict.get("batch_size", self.N) - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points} - - # feature learning network - vwfs = self.svfe(batch_dict)["pillar_features"] - - vwfs = self.voxel_indexing(vwfs, voxel_coords, batch_size) - - # convolutional middle network - vwfs = self.cml(vwfs) - - # region proposal network - - # merge the depth and feature dim into one, output probability score - # map and regression map - psm, rm = self.rpn(vwfs.view(batch_size, -1, self.H, self.W)) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict diff --git a/OpenCOOD/opencood/models/voxel_net_intermediate.py b/OpenCOOD/opencood/models/voxel_net_intermediate.py deleted file mode 100644 index 97474b50..00000000 --- a/OpenCOOD/opencood/models/voxel_net_intermediate.py +++ /dev/null @@ -1,246 +0,0 @@ -""" -VoxelNet for intermediate fusion -""" - -import torch.nn as nn -import torch.nn.functional as F -import torch -import numpy as np - -from opencood.models.communication_adapters.intermediate import SpatialFeatureCommunicationAdapter -from opencood.models.voxel_net import RPN, CML -from opencood.models.sub_modules.pillar_vfe import PillarVFE -from opencood.models.fuse_modules.self_attn import AttFusion -from opencood.models.sub_modules.auto_encoder import AutoEncoder - - -# conv2d + bn + relu -class Conv2d(nn.Module): - def __init__(self, in_channels, out_channels, k, s, p, activation=True, batch_norm=True, bias=True): - super(Conv2d, self).__init__() - self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=k, stride=s, padding=p, bias=bias) - if batch_norm: - self.bn = nn.BatchNorm2d(out_channels) - else: - self.bn = None - self.activation = activation - - def forward(self, x): - x = self.conv(x) - if self.bn is not None: - x = self.bn(x) - if self.activation: - return F.relu(x, inplace=True) - else: - return x - - -class NaiveFusion(nn.Module): - def __init__(self): - super(NaiveFusion, self).__init__() - self.conv1 = Conv2d(128 * 5, 256, 3, 1, 1, batch_norm=False, bias=False) - self.conv2 = Conv2d(256, 128, 3, 1, 1) - - def forward(self, x): - x = self.conv1(x) - x = self.conv2(x) - - return x - - -class VoxelNetIntermediate(nn.Module): - communication_adapter_class = SpatialFeatureCommunicationAdapter - - def __init__(self, args): - super(VoxelNetIntermediate, self).__init__() - self.svfe = PillarVFE(args["pillar_vfe"], num_point_features=4, voxel_size=args["voxel_size"], point_cloud_range=args["lidar_range"]) - self.cml = CML() - self.fusion_net = AttFusion(128) - self.rpn = RPN(args["anchor_num"]) - - self.N = args["N"] - self.D = args["D"] - self.H = args["H"] - self.W = args["W"] - self.T = args["T"] - self.anchor_num = args["anchor_num"] - - self.compression = False - if "compression" in args and args["compression"] > 0: - self.compression = True - self.compression_layer = AutoEncoder(128, args["compression"]) - - def voxel_indexing(self, sparse_features, coords, batch_size): - """ - Scatter sparse VoxelNet features into a dense 3D tensor. - - Parameters - ---------- - sparse_features : torch.Tensor - Learned feature vector for every non-empty voxel. - coords : torch.Tensor - Batch, depth, height, and width index for every sparse feature. - batch_size : int - Number of agents represented by the sparse input. - - Returns - ------- - torch.Tensor - Dense tensor shaped ``(batch_size, channels, D, H, W)``. - """ - dim = sparse_features.shape[-1] - - dense_feature = torch.zeros( - dim, - batch_size, - self.D, - self.H, - self.W, - device=sparse_features.device, - dtype=sparse_features.dtype, - ) - coords = coords.long() - - dense_feature[:, coords[:, 0], coords[:, 1], coords[:, 2], coords[:, 3]] = sparse_features.transpose(0, 1) - - return dense_feature.transpose(0, 1) - - def regroup(self, dense_feature, record_len): - """ - Regroup the data based on the record_len. - - Parameters - ---------- - dense_feature : torch.Tensor - N, C, H, W - record_len : list - [sample1_len, sample2_len, ...] - - Returns - ------- - regroup_feature : torch.Tensor - B, 5C, H, W - """ - cum_sum_len = list(np.cumsum(record_len)) - split_features = torch.tensor_split(dense_feature, cum_sum_len[:-1]) - regroup_features = [] - - for split_feature in split_features: - # M, C, H, W - feature_shape = split_feature.shape - - # the maximum M is 5 as most 5 cavs - padding_len = 5 - feature_shape[0] - padding_tensor = torch.zeros(padding_len, feature_shape[1], feature_shape[2], feature_shape[3]) - padding_tensor = padding_tensor.to(split_feature.device) - - split_feature = torch.cat([split_feature, padding_tensor], dim=0) - - # 1, 5C, H, W - split_feature = split_feature.view(-1, feature_shape[2], feature_shape[3]).unsqueeze(0) - regroup_features.append(split_feature) - - # B, 5C, H, W - regroup_features = torch.cat(regroup_features, dim=0) - - return regroup_features - - def forward(self, data_dict): - if "intermediate_features" in data_dict: - return self.fuse_agents(data_dict) - - voxel_features = data_dict["processed_lidar"]["voxel_features"] - voxel_coords = data_dict["processed_lidar"]["voxel_coords"] - voxel_num_points = data_dict["processed_lidar"]["voxel_num_points"] - record_len = data_dict["record_len"] - - batch_dict = {"voxel_features": voxel_features, "voxel_coords": voxel_coords, "voxel_num_points": voxel_num_points} - - record_len_tmp = record_len.cpu() if voxel_coords.is_cuda else record_len - - record_len_tmp = list(record_len_tmp.numpy()) - - self.N = sum(record_len_tmp) - - # feature learning network - vwfs = self.svfe(batch_dict)["pillar_features"] - - vwfs = self.voxel_indexing(vwfs, voxel_coords, self.N) - - # convolutional middle network - vwfs = self.cml(vwfs) - # convert from 3d to 2d N C H W - vmfs = vwfs.view(self.N, -1, self.H, self.W) - - # compression layer - if self.compression: - vmfs = self.compression_layer(vmfs) - - # information naive fusion - vmfs_fusion = self.fusion_net(vmfs, record_len) - - # region proposal network - # merge the depth and feature dim into one, output probability score - # map and regression map - psm, rm = self.rpn(vmfs_fusion) - - output_dict = {"psm": psm, "rm": rm} - - return output_dict - - def encode_agent(self, data_dict): - """ - Encode one agent through VoxelNet's convolutional middle layers. - - Parameters - ---------- - data_dict : dict - Sender-local preprocessed voxel input. - - Returns - ------- - dict - Learned spatial feature map before attention fusion. - """ - processed_lidar = data_dict["processed_lidar"] - batch_dict = { - "voxel_features": processed_lidar["voxel_features"], - "voxel_coords": processed_lidar["voxel_coords"], - "voxel_num_points": processed_lidar["voxel_num_points"], - } - voxel_features = self.svfe(batch_dict)["pillar_features"] - dense_features = self.voxel_indexing( - voxel_features, - processed_lidar["voxel_coords"], - data_dict.get("batch_size", 1), - ) - spatial_features = self.cml(dense_features).view( - data_dict.get("batch_size", 1), - -1, - self.H, - self.W, - ) - if self.compression: - spatial_features = self.compression_layer.encode(spatial_features) - return {"spatial_features": spatial_features} - - def fuse_agents(self, data_dict): - """ - Fuse received VoxelNet features and run its proposal network. - - Parameters - ---------- - data_dict : dict - Receiver input containing learned features and ``record_len``. - - Returns - ------- - dict - Classification and regression maps. - """ - spatial_features = data_dict["intermediate_features"]["spatial_features"] - if self.compression: - spatial_features = self.compression_layer.decode(spatial_features) - fused_features = self.fusion_net(spatial_features, data_dict["record_len"]) - psm, rm = self.rpn(fused_features) - return {"psm": psm, "rm": rm} diff --git a/OpenCOOD/opencood/pcdet_utils/CMakeLists.txt b/OpenCOOD/opencood/pcdet_utils/CMakeLists.txt deleted file mode 100644 index 1ce46480..00000000 --- a/OpenCOOD/opencood/pcdet_utils/CMakeLists.txt +++ /dev/null @@ -1,83 +0,0 @@ -separate_arguments(OPENCDA_TORCH_CXX_FLAGS NATIVE_COMMAND "${TORCH_CXX_FLAGS}") - -function(opencda_add_cuda_extension target package_directory) - Python3_add_library(${target} MODULE WITH_SOABI ${ARGN}) - - target_include_directories(${target} PRIVATE ${TORCH_INCLUDE_DIRS}) - target_link_libraries(${target} PRIVATE - ${TORCH_LIBRARIES} - "${OPENCDA_TORCH_PYTHON_LIBRARY}" - CUDA::cudart - ) - target_compile_definitions(${target} PRIVATE - TORCH_API_INCLUDE_EXTENSION_H - TORCH_EXTENSION_NAME=${target} - ) - target_compile_options(${target} PRIVATE - ${OPENCDA_TORCH_CXX_FLAGS} - $<$:-D__CUDA_NO_HALF_OPERATORS__> - $<$:-D__CUDA_NO_HALF_CONVERSIONS__> - $<$:-D__CUDA_NO_BFLOAT16_CONVERSIONS__> - $<$:-D__CUDA_NO_HALF2_OPERATORS__> - $<$:--expt-relaxed-constexpr> - ) - - set_target_properties(${target} PROPERTIES - CUDA_ARCHITECTURES OFF - INSTALL_RPATH "${OPENCDA_TORCH_RUNTIME_LIBRARY_DIR}" - ) - - install( - TARGETS ${target} - LIBRARY DESTINATION "OpenCOOD/opencood/pcdet_utils/${package_directory}" - ) -endfunction() - -opencda_add_cuda_extension( - iou3d_nms_cuda - iou3d_nms - iou3d_nms/src/iou3d_cpu.cpp - iou3d_nms/src/iou3d_nms_api.cpp - iou3d_nms/src/iou3d_nms.cpp - iou3d_nms/src/iou3d_nms_kernel.cu -) - -opencda_add_cuda_extension( - roiaware_pool3d_cuda - roiaware_pool3d - roiaware_pool3d/src/roiaware_pool3d.cpp - roiaware_pool3d/src/roiaware_pool3d_kernel.cu -) - -opencda_add_cuda_extension( - pointnet2_stack_cuda - pointnet2/pointnet2_stack - pointnet2/pointnet2_stack/src/pointnet2_api.cpp - pointnet2/pointnet2_stack/src/ball_query.cpp - pointnet2/pointnet2_stack/src/ball_query_gpu.cu - pointnet2/pointnet2_stack/src/group_points.cpp - pointnet2/pointnet2_stack/src/group_points_gpu.cu - pointnet2/pointnet2_stack/src/sampling.cpp - pointnet2/pointnet2_stack/src/sampling_gpu.cu - pointnet2/pointnet2_stack/src/interpolate.cpp - pointnet2/pointnet2_stack/src/interpolate_gpu.cu -) - -opencda_add_cuda_extension( - pointnet2_batch_cuda - pointnet2/pointnet2_batch - pointnet2/pointnet2_batch/src/pointnet2_api.cpp - pointnet2/pointnet2_batch/src/ball_query.cpp - pointnet2/pointnet2_batch/src/ball_query_gpu.cu - pointnet2/pointnet2_batch/src/group_points.cpp - pointnet2/pointnet2_batch/src/group_points_gpu.cu - pointnet2/pointnet2_batch/src/interpolate.cpp - pointnet2/pointnet2_batch/src/interpolate_gpu.cu - pointnet2/pointnet2_batch/src/sampling.cpp - pointnet2/pointnet2_batch/src/sampling_gpu.cu -) - -install( - SCRIPT - "${CMAKE_CURRENT_LIST_DIR}/cmake/WriteCudaArtifactManifest.cmake" -) diff --git a/OpenCOOD/opencood/pcdet_utils/__init__.py b/OpenCOOD/opencood/pcdet_utils/__init__.py deleted file mode 100644 index bcbe18dd..00000000 --- a/OpenCOOD/opencood/pcdet_utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""This is used to save the utility functions for OpenPCDet""" diff --git a/OpenCOOD/opencood/pcdet_utils/cmake/WriteCudaArtifactManifest.cmake b/OpenCOOD/opencood/pcdet_utils/cmake/WriteCudaArtifactManifest.cmake deleted file mode 100644 index 4b6a2f7a..00000000 --- a/OpenCOOD/opencood/pcdet_utils/cmake/WriteCudaArtifactManifest.cmake +++ /dev/null @@ -1,17 +0,0 @@ -file( - GLOB_RECURSE installed_cuda_artifacts - RELATIVE "${CMAKE_INSTALL_PREFIX}" - "${CMAKE_INSTALL_PREFIX}/OpenCOOD/opencood/pcdet_utils/*_cuda*.so" -) -list(SORT installed_cuda_artifacts) - -string(REPLACE ";" "\n" cuda_manifest "${installed_cuda_artifacts}") -if(NOT cuda_manifest STREQUAL "") - string(APPEND cuda_manifest "\n") -endif() - -file( - WRITE - "${CMAKE_INSTALL_PREFIX}/cuda-artifacts.manifest" - "${cuda_manifest}" -) diff --git a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/iou3d_nms_utils.py b/OpenCOOD/opencood/pcdet_utils/iou3d_nms/iou3d_nms_utils.py deleted file mode 100644 index 1bac606d..00000000 --- a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/iou3d_nms_utils.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -3D IoU Calculation and Rotated NMS -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -""" - -import torch - -from opencood.pcdet_utils.iou3d_nms import iou3d_nms_cuda - - -def aligned_boxes_iou3d_gpu(boxes_a, boxes_b, return_union=False): - """ - Args: - boxes_a: (N, 7) [x, y, z, dx, dy, dz, heading] - boxes_b: (N, 7) [x, y, z, dx, dy, dz, heading] - - Returns: - ans_iou: (N, 1) - """ - assert boxes_a.shape[1] == boxes_b.shape[1] == 7 - assert boxes_a.shape[0] == boxes_b.shape[0] - # height overlap - boxes_a_height_max = (boxes_a[:, 2] + boxes_a[:, 5] / 2).view(-1, 1) - boxes_a_height_min = (boxes_a[:, 2] - boxes_a[:, 5] / 2).view(-1, 1) - boxes_b_height_max = (boxes_b[:, 2] + boxes_b[:, 5] / 2).view(-1, 1) - boxes_b_height_min = (boxes_b[:, 2] - boxes_b[:, 5] / 2).view(-1, 1) - - # bev overlap - overlaps_bev = torch.cuda.FloatTensor(torch.Size((boxes_a.shape[0], boxes_b.shape[0]))).zero_() # (N, M) - iou3d_nms_cuda.boxes_overlap_bev_gpu(boxes_a.contiguous(), boxes_b.contiguous(), overlaps_bev) - overlaps_bev = torch.diagonal(overlaps_bev).reshape(-1, 1) - - max_of_min = torch.max(boxes_a_height_min, boxes_b_height_min) - min_of_max = torch.min(boxes_a_height_max, boxes_b_height_max) - overlaps_h = torch.clamp(min_of_max - max_of_min, min=0) - - # 3d iou - overlaps_3d = overlaps_bev * overlaps_h - - vol_a = (boxes_a[:, 3] * boxes_a[:, 4] * boxes_a[:, 5]).view(-1, 1) - vol_b = (boxes_b[:, 3] * boxes_b[:, 4] * boxes_b[:, 5]).view(-1, 1) - union = torch.clamp(vol_a + vol_b - overlaps_3d, min=1e-6) - iou3d = overlaps_3d / union - if return_union: - return iou3d, union - return iou3d - - -def boxes_iou3d_gpu(boxes_a, boxes_b, return_union=False): - """ - Args: - boxes_a: (N, 7) [x, y, z, dx, dy, dz, heading] - boxes_b: (N, 7) [x, y, z, dx, dy, dz, heading] - - Returns: - ans_iou: (N, M) - """ - assert boxes_a.shape[1] == boxes_b.shape[1] == 7 - - # height overlap - boxes_a_height_max = (boxes_a[:, 2] + boxes_a[:, 5] / 2).view(-1, 1) - boxes_a_height_min = (boxes_a[:, 2] - boxes_a[:, 5] / 2).view(-1, 1) - boxes_b_height_max = (boxes_b[:, 2] + boxes_b[:, 5] / 2).view(1, -1) - boxes_b_height_min = (boxes_b[:, 2] - boxes_b[:, 5] / 2).view(1, -1) - - # bev overlap - overlaps_bev = torch.cuda.FloatTensor(torch.Size((boxes_a.shape[0], boxes_b.shape[0]))).zero_() # (N, M) - iou3d_nms_cuda.boxes_overlap_bev_gpu(boxes_a.contiguous(), boxes_b.contiguous(), overlaps_bev) - - max_of_min = torch.max(boxes_a_height_min, boxes_b_height_min) - min_of_max = torch.min(boxes_a_height_max, boxes_b_height_max) - overlaps_h = torch.clamp(min_of_max - max_of_min, min=0) - - # 3d iou - overlaps_3d = overlaps_bev * overlaps_h - - vol_a = (boxes_a[:, 3] * boxes_a[:, 4] * boxes_a[:, 5]).view(-1, 1) - vol_b = (boxes_b[:, 3] * boxes_b[:, 4] * boxes_b[:, 5]).view(1, -1) - union = torch.clamp(vol_a + vol_b - overlaps_3d, min=1e-6) - iou3d = overlaps_3d / union - if return_union: - return iou3d, union - return iou3d - - -def nms_gpu(boxes, scores, thresh, pre_maxsize=None, **kwargs): - """ - Operate on rotated bev boxes[x,y,dx,dy,heading] - :param boxes: (N, 7) [x, y, z, dx, dy, dz, heading] - :param scores: (N) - :param thresh: - :return: - """ - assert boxes.shape[1] == 7 - order = scores.sort(0, descending=True)[1] - if pre_maxsize is not None: - order = order[:pre_maxsize] - - boxes = boxes[order].contiguous() - keep = torch.LongTensor(boxes.size(0)) - num_out = iou3d_nms_cuda.nms_gpu(boxes, keep, thresh) - return order[keep[:num_out].cuda()].contiguous(), None - - -def nms_normal_gpu(boxes, scores, thresh, **kwargs): - """ - Ignore heading and operate on bev boxes[x,y,dx,dy] - :param boxes: (N, 7) [x, y, z, dx, dy, dz, heading] - :param scores: (N) - :param thresh: - :return: - """ - assert boxes.shape[1] == 7 - order = scores.sort(0, descending=True)[1] - - boxes = boxes[order].contiguous() - - keep = torch.LongTensor(boxes.size(0)) - num_out = iou3d_nms_cuda.nms_normal_gpu(boxes, keep, thresh) - return order[keep[:num_out].cuda()].contiguous(), None diff --git a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_cpu.cpp b/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_cpu.cpp deleted file mode 100644 index 22ab25e0..00000000 --- a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_cpu.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* -3D Rotated IoU Calculation (CPU) -Written by Shaoshuai Shi -All Rights Reserved 2020. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include "iou3d_cpu.h" - -#define CHECK_CUDA(x) do { \ - if (!x.is_cuda()) { \ - fprintf(stderr, "%s must be CUDA tensor at %s:%d\n", #x, __FILE__, __LINE__); \ - exit(-1); \ - } \ -} while (0) -#define CHECK_CONTIGUOUS(x) do { \ - if (!x.is_contiguous()) { \ - fprintf(stderr, "%s must be contiguous tensor at %s:%d\n", #x, __FILE__, __LINE__); \ - exit(-1); \ - } \ -} while (0) -#define CHECK_INPUT(x) CHECK_CUDA(x);CHECK_CONTIGUOUS(x) - -inline float min(float a, float b){ - return a > b ? b : a; -} - -inline float max(float a, float b){ - return a > b ? a : b; -} - -const float EPS = 1e-8; -struct Point { - float x, y; - __device__ Point() {} - __device__ Point(double _x, double _y){ - x = _x, y = _y; - } - - __device__ void set(float _x, float _y){ - x = _x; y = _y; - } - - __device__ Point operator +(const Point &b)const{ - return Point(x + b.x, y + b.y); - } - - __device__ Point operator -(const Point &b)const{ - return Point(x - b.x, y - b.y); - } -}; - -inline float cross(const Point &a, const Point &b){ - return a.x * b.y - a.y * b.x; -} - -inline float cross(const Point &p1, const Point &p2, const Point &p0){ - return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); -} - -inline int check_rect_cross(const Point &p1, const Point &p2, const Point &q1, const Point &q2){ - int ret = min(p1.x,p2.x) <= max(q1.x,q2.x) && - min(q1.x,q2.x) <= max(p1.x,p2.x) && - min(p1.y,p2.y) <= max(q1.y,q2.y) && - min(q1.y,q2.y) <= max(p1.y,p2.y); - return ret; -} - -inline int check_in_box2d(const float *box, const Point &p){ - //params: (7) [x, y, z, dx, dy, dz, heading] - const float MARGIN = 1e-2; - - float center_x = box[0], center_y = box[1]; - float angle_cos = cos(-box[6]), angle_sin = sin(-box[6]); // rotate the point in the opposite direction of box - float rot_x = (p.x - center_x) * angle_cos + (p.y - center_y) * (-angle_sin); - float rot_y = (p.x - center_x) * angle_sin + (p.y - center_y) * angle_cos; - - return (fabs(rot_x) < box[3] / 2 + MARGIN && fabs(rot_y) < box[4] / 2 + MARGIN); -} - -inline int intersection(const Point &p1, const Point &p0, const Point &q1, const Point &q0, Point &ans){ - // fast exclusion - if (check_rect_cross(p0, p1, q0, q1) == 0) return 0; - - // check cross standing - float s1 = cross(q0, p1, p0); - float s2 = cross(p1, q1, p0); - float s3 = cross(p0, q1, q0); - float s4 = cross(q1, p1, q0); - - if (!(s1 * s2 > 0 && s3 * s4 > 0)) return 0; - - // calculate intersection of two lines - float s5 = cross(q1, p1, p0); - if(fabs(s5 - s1) > EPS){ - ans.x = (s5 * q0.x - s1 * q1.x) / (s5 - s1); - ans.y = (s5 * q0.y - s1 * q1.y) / (s5 - s1); - - } - else{ - float a0 = p0.y - p1.y, b0 = p1.x - p0.x, c0 = p0.x * p1.y - p1.x * p0.y; - float a1 = q0.y - q1.y, b1 = q1.x - q0.x, c1 = q0.x * q1.y - q1.x * q0.y; - float D = a0 * b1 - a1 * b0; - - ans.x = (b0 * c1 - b1 * c0) / D; - ans.y = (a1 * c0 - a0 * c1) / D; - } - - return 1; -} - -inline void rotate_around_center(const Point ¢er, const float angle_cos, const float angle_sin, Point &p){ - float new_x = (p.x - center.x) * angle_cos + (p.y - center.y) * (-angle_sin) + center.x; - float new_y = (p.x - center.x) * angle_sin + (p.y - center.y) * angle_cos + center.y; - p.set(new_x, new_y); -} - -inline int point_cmp(const Point &a, const Point &b, const Point ¢er){ - return atan2(a.y - center.y, a.x - center.x) > atan2(b.y - center.y, b.x - center.x); -} - -inline float box_overlap(const float *box_a, const float *box_b){ - // params: box_a (7) [x, y, z, dx, dy, dz, heading] - // params: box_b (7) [x, y, z, dx, dy, dz, heading] - -// float a_x1 = box_a[0], a_y1 = box_a[1], a_x2 = box_a[2], a_y2 = box_a[3], a_angle = box_a[4]; -// float b_x1 = box_b[0], b_y1 = box_b[1], b_x2 = box_b[2], b_y2 = box_b[3], b_angle = box_b[4]; - float a_angle = box_a[6], b_angle = box_b[6]; - float a_dx_half = box_a[3] / 2, b_dx_half = box_b[3] / 2, a_dy_half = box_a[4] / 2, b_dy_half = box_b[4] / 2; - float a_x1 = box_a[0] - a_dx_half, a_y1 = box_a[1] - a_dy_half; - float a_x2 = box_a[0] + a_dx_half, a_y2 = box_a[1] + a_dy_half; - float b_x1 = box_b[0] - b_dx_half, b_y1 = box_b[1] - b_dy_half; - float b_x2 = box_b[0] + b_dx_half, b_y2 = box_b[1] + b_dy_half; - - Point center_a(box_a[0], box_a[1]); - Point center_b(box_b[0], box_b[1]); - - Point box_a_corners[5]; - box_a_corners[0].set(a_x1, a_y1); - box_a_corners[1].set(a_x2, a_y1); - box_a_corners[2].set(a_x2, a_y2); - box_a_corners[3].set(a_x1, a_y2); - - Point box_b_corners[5]; - box_b_corners[0].set(b_x1, b_y1); - box_b_corners[1].set(b_x2, b_y1); - box_b_corners[2].set(b_x2, b_y2); - box_b_corners[3].set(b_x1, b_y2); - - // get oriented corners - float a_angle_cos = cos(a_angle), a_angle_sin = sin(a_angle); - float b_angle_cos = cos(b_angle), b_angle_sin = sin(b_angle); - - for (int k = 0; k < 4; k++){ - rotate_around_center(center_a, a_angle_cos, a_angle_sin, box_a_corners[k]); - rotate_around_center(center_b, b_angle_cos, b_angle_sin, box_b_corners[k]); - } - - box_a_corners[4] = box_a_corners[0]; - box_b_corners[4] = box_b_corners[0]; - - // get intersection of lines - Point cross_points[16]; - Point poly_center; - int cnt = 0, flag = 0; - - poly_center.set(0, 0); - for (int i = 0; i < 4; i++){ - for (int j = 0; j < 4; j++){ - flag = intersection(box_a_corners[i + 1], box_a_corners[i], box_b_corners[j + 1], box_b_corners[j], cross_points[cnt]); - if (flag){ - poly_center = poly_center + cross_points[cnt]; - cnt++; - } - } - } - - // check corners - for (int k = 0; k < 4; k++){ - if (check_in_box2d(box_a, box_b_corners[k])){ - poly_center = poly_center + box_b_corners[k]; - cross_points[cnt] = box_b_corners[k]; - cnt++; - } - if (check_in_box2d(box_b, box_a_corners[k])){ - poly_center = poly_center + box_a_corners[k]; - cross_points[cnt] = box_a_corners[k]; - cnt++; - } - } - - poly_center.x /= cnt; - poly_center.y /= cnt; - - // sort the points of polygon - Point temp; - for (int j = 0; j < cnt - 1; j++){ - for (int i = 0; i < cnt - j - 1; i++){ - if (point_cmp(cross_points[i], cross_points[i + 1], poly_center)){ - temp = cross_points[i]; - cross_points[i] = cross_points[i + 1]; - cross_points[i + 1] = temp; - } - } - } - - // get the overlap areas - float area = 0; - for (int k = 0; k < cnt - 1; k++){ - area += cross(cross_points[k] - cross_points[0], cross_points[k + 1] - cross_points[0]); - } - - return fabs(area) / 2.0; -} - -inline float iou_bev(const float *box_a, const float *box_b){ - // params: box_a (7) [x, y, z, dx, dy, dz, heading] - // params: box_b (7) [x, y, z, dx, dy, dz, heading] - float sa = box_a[3] * box_a[4]; - float sb = box_b[3] * box_b[4]; - float s_overlap = box_overlap(box_a, box_b); - return s_overlap / fmaxf(sa + sb - s_overlap, EPS); -} - - -int boxes_iou_bev_cpu(at::Tensor boxes_a_tensor, at::Tensor boxes_b_tensor, at::Tensor ans_iou_tensor){ - // params boxes_a_tensor: (N, 7) [x, y, z, dx, dy, dz, heading] - // params boxes_b_tensor: (M, 7) [x, y, z, dx, dy, dz, heading] - // params ans_iou_tensor: (N, M) - - CHECK_CONTIGUOUS(boxes_a_tensor); - CHECK_CONTIGUOUS(boxes_b_tensor); - - int num_boxes_a = boxes_a_tensor.size(0); - int num_boxes_b = boxes_b_tensor.size(0); - const float *boxes_a = boxes_a_tensor.data_ptr(); - const float *boxes_b = boxes_b_tensor.data_ptr(); - float *ans_iou = ans_iou_tensor.data_ptr(); - - for (int i = 0; i < num_boxes_a; i++){ - for (int j = 0; j < num_boxes_b; j++){ - ans_iou[i * num_boxes_b + j] = iou_bev(boxes_a + i * 7, boxes_b + j * 7); - } - } - return 1; -} diff --git a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_cpu.h b/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_cpu.h deleted file mode 100644 index 8835ee70..00000000 --- a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_cpu.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef IOU3D_CPU_H -#define IOU3D_CPU_H - -#include -#include -#include -#include - -int boxes_iou_bev_cpu(at::Tensor boxes_a_tensor, at::Tensor boxes_b_tensor, at::Tensor ans_iou_tensor); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms.cpp b/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms.cpp deleted file mode 100644 index 75dfe0c0..00000000 --- a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms.cpp +++ /dev/null @@ -1,186 +0,0 @@ -/* -3D IoU Calculation and Rotated NMS(modified from 2D NMS written by others) -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - -#include -#include -#include -#include -#include -#include "iou3d_nms.h" - -#define CHECK_CUDA(x) do { \ - if (!x.is_cuda()) { \ - fprintf(stderr, "%s must be CUDA tensor at %s:%d\n", #x, __FILE__, __LINE__); \ - exit(-1); \ - } \ -} while (0) -#define CHECK_CONTIGUOUS(x) do { \ - if (!x.is_contiguous()) { \ - fprintf(stderr, "%s must be contiguous tensor at %s:%d\n", #x, __FILE__, __LINE__); \ - exit(-1); \ - } \ -} while (0) -#define CHECK_INPUT(x) CHECK_CUDA(x);CHECK_CONTIGUOUS(x) - -#define DIVUP(m,n) ((m) / (n) + ((m) % (n) > 0)) - -#define CHECK_ERROR(ans) { gpuAssert((ans), __FILE__, __LINE__); } -inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort=true) -{ - if (code != cudaSuccess) - { - fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line); - if (abort) exit(code); - } -} - -const int THREADS_PER_BLOCK_NMS = sizeof(unsigned long long) * 8; - - -void boxesoverlapLauncher(const int num_a, const float *boxes_a, const int num_b, const float *boxes_b, float *ans_overlap); -void boxesioubevLauncher(const int num_a, const float *boxes_a, const int num_b, const float *boxes_b, float *ans_iou); -void nmsLauncher(const float *boxes, unsigned long long * mask, int boxes_num, float nms_overlap_thresh); -void nmsNormalLauncher(const float *boxes, unsigned long long * mask, int boxes_num, float nms_overlap_thresh); - - -int boxes_overlap_bev_gpu(at::Tensor boxes_a, at::Tensor boxes_b, at::Tensor ans_overlap){ - // params boxes_a: (N, 7) [x, y, z, dx, dy, dz, heading] - // params boxes_b: (M, 7) [x, y, z, dx, dy, dz, heading] - // params ans_overlap: (N, M) - - CHECK_INPUT(boxes_a); - CHECK_INPUT(boxes_b); - CHECK_INPUT(ans_overlap); - - int num_a = boxes_a.size(0); - int num_b = boxes_b.size(0); - - const float * boxes_a_data = boxes_a.data_ptr(); - const float * boxes_b_data = boxes_b.data_ptr(); - float * ans_overlap_data = ans_overlap.data_ptr(); - - boxesoverlapLauncher(num_a, boxes_a_data, num_b, boxes_b_data, ans_overlap_data); - - return 1; -} - -int boxes_iou_bev_gpu(at::Tensor boxes_a, at::Tensor boxes_b, at::Tensor ans_iou){ - // params boxes_a: (N, 7) [x, y, z, dx, dy, dz, heading] - // params boxes_b: (M, 7) [x, y, z, dx, dy, dz, heading] - // params ans_overlap: (N, M) - CHECK_INPUT(boxes_a); - CHECK_INPUT(boxes_b); - CHECK_INPUT(ans_iou); - - int num_a = boxes_a.size(0); - int num_b = boxes_b.size(0); - - const float * boxes_a_data = boxes_a.data_ptr(); - const float * boxes_b_data = boxes_b.data_ptr(); - float * ans_iou_data = ans_iou.data_ptr(); - - boxesioubevLauncher(num_a, boxes_a_data, num_b, boxes_b_data, ans_iou_data); - - return 1; -} - -int nms_gpu(at::Tensor boxes, at::Tensor keep, float nms_overlap_thresh){ - // params boxes: (N, 7) [x, y, z, dx, dy, dz, heading] - // params keep: (N) - CHECK_INPUT(boxes); - CHECK_CONTIGUOUS(keep); - - int boxes_num = boxes.size(0); - const float * boxes_data = boxes.data_ptr(); - long * keep_data = keep.data_ptr(); - - const int col_blocks = DIVUP(boxes_num, THREADS_PER_BLOCK_NMS); - - unsigned long long *mask_data = NULL; - CHECK_ERROR(cudaMalloc((void**)&mask_data, boxes_num * col_blocks * sizeof(unsigned long long))); - nmsLauncher(boxes_data, mask_data, boxes_num, nms_overlap_thresh); - - // unsigned long long mask_cpu[boxes_num * col_blocks]; - // unsigned long long *mask_cpu = new unsigned long long [boxes_num * col_blocks]; - std::vector mask_cpu(boxes_num * col_blocks); - -// printf("boxes_num=%d, col_blocks=%d\n", boxes_num, col_blocks); - CHECK_ERROR(cudaMemcpy(&mask_cpu[0], mask_data, boxes_num * col_blocks * sizeof(unsigned long long), - cudaMemcpyDeviceToHost)); - - cudaFree(mask_data); - - unsigned long long remv_cpu[col_blocks]; - memset(remv_cpu, 0, col_blocks * sizeof(unsigned long long)); - - int num_to_keep = 0; - - for (int i = 0; i < boxes_num; i++){ - int nblock = i / THREADS_PER_BLOCK_NMS; - int inblock = i % THREADS_PER_BLOCK_NMS; - - if (!(remv_cpu[nblock] & (1ULL << inblock))){ - keep_data[num_to_keep++] = i; - unsigned long long *p = &mask_cpu[0] + i * col_blocks; - for (int j = nblock; j < col_blocks; j++){ - remv_cpu[j] |= p[j]; - } - } - } - if ( cudaSuccess != cudaGetLastError() ) printf( "Error!\n" ); - - return num_to_keep; -} - - -int nms_normal_gpu(at::Tensor boxes, at::Tensor keep, float nms_overlap_thresh){ - // params boxes: (N, 7) [x, y, z, dx, dy, dz, heading] - // params keep: (N) - - CHECK_INPUT(boxes); - CHECK_CONTIGUOUS(keep); - - int boxes_num = boxes.size(0); - const float * boxes_data = boxes.data_ptr(); - long * keep_data = keep.data_ptr(); - - const int col_blocks = DIVUP(boxes_num, THREADS_PER_BLOCK_NMS); - - unsigned long long *mask_data = NULL; - CHECK_ERROR(cudaMalloc((void**)&mask_data, boxes_num * col_blocks * sizeof(unsigned long long))); - nmsNormalLauncher(boxes_data, mask_data, boxes_num, nms_overlap_thresh); - - // unsigned long long mask_cpu[boxes_num * col_blocks]; - // unsigned long long *mask_cpu = new unsigned long long [boxes_num * col_blocks]; - std::vector mask_cpu(boxes_num * col_blocks); - -// printf("boxes_num=%d, col_blocks=%d\n", boxes_num, col_blocks); - CHECK_ERROR(cudaMemcpy(&mask_cpu[0], mask_data, boxes_num * col_blocks * sizeof(unsigned long long), - cudaMemcpyDeviceToHost)); - - cudaFree(mask_data); - - unsigned long long remv_cpu[col_blocks]; - memset(remv_cpu, 0, col_blocks * sizeof(unsigned long long)); - - int num_to_keep = 0; - - for (int i = 0; i < boxes_num; i++){ - int nblock = i / THREADS_PER_BLOCK_NMS; - int inblock = i % THREADS_PER_BLOCK_NMS; - - if (!(remv_cpu[nblock] & (1ULL << inblock))){ - keep_data[num_to_keep++] = i; - unsigned long long *p = &mask_cpu[0] + i * col_blocks; - for (int j = nblock; j < col_blocks; j++){ - remv_cpu[j] |= p[j]; - } - } - } - if ( cudaSuccess != cudaGetLastError() ) printf( "Error!\n" ); - - return num_to_keep; -} diff --git a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms.h b/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms.h deleted file mode 100644 index aa7ae0ed..00000000 --- a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef IOU3D_NMS_H -#define IOU3D_NMS_H - -#include -#include -#include -#include - -int boxes_overlap_bev_gpu(at::Tensor boxes_a, at::Tensor boxes_b, at::Tensor ans_overlap); -int boxes_iou_bev_gpu(at::Tensor boxes_a, at::Tensor boxes_b, at::Tensor ans_iou); -int nms_gpu(at::Tensor boxes, at::Tensor keep, float nms_overlap_thresh); -int nms_normal_gpu(at::Tensor boxes, at::Tensor keep, float nms_overlap_thresh); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms_api.cpp b/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms_api.cpp deleted file mode 100644 index 5a2d3a37..00000000 --- a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms_api.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include -#include -#include -#include - -#include "iou3d_cpu.h" -#include "iou3d_nms.h" - - -PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { - m.def("boxes_overlap_bev_gpu", &boxes_overlap_bev_gpu, "oriented boxes overlap"); - m.def("boxes_iou_bev_gpu", &boxes_iou_bev_gpu, "oriented boxes iou"); - m.def("nms_gpu", &nms_gpu, "oriented nms gpu"); - m.def("nms_normal_gpu", &nms_normal_gpu, "nms gpu"); - m.def("boxes_iou_bev_cpu", &boxes_iou_bev_cpu, "oriented boxes iou"); -} diff --git a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms_kernel.cu b/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms_kernel.cu deleted file mode 100644 index e5e305cd..00000000 --- a/OpenCOOD/opencood/pcdet_utils/iou3d_nms/src/iou3d_nms_kernel.cu +++ /dev/null @@ -1,414 +0,0 @@ -/* -3D IoU Calculation and Rotated NMS(modified from 2D NMS written by others) -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#include -#define THREADS_PER_BLOCK 16 -#define DIVUP(m, n) ((m) / (n) + ((m) % (n) > 0)) - -// #define DEBUG -const int THREADS_PER_BLOCK_NMS = sizeof(unsigned long long) * 8; -const float EPS = 1e-8; -struct Point { - float x, y; - __device__ Point() {} - __device__ Point(double _x, double _y){ - x = _x, y = _y; - } - - __device__ void set(float _x, float _y){ - x = _x; y = _y; - } - - __device__ Point operator +(const Point &b)const{ - return Point(x + b.x, y + b.y); - } - - __device__ Point operator -(const Point &b)const{ - return Point(x - b.x, y - b.y); - } -}; - -__device__ inline float cross(const Point &a, const Point &b){ - return a.x * b.y - a.y * b.x; -} - -__device__ inline float cross(const Point &p1, const Point &p2, const Point &p0){ - return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y); -} - -__device__ int check_rect_cross(const Point &p1, const Point &p2, const Point &q1, const Point &q2){ - int ret = min(p1.x,p2.x) <= max(q1.x,q2.x) && - min(q1.x,q2.x) <= max(p1.x,p2.x) && - min(p1.y,p2.y) <= max(q1.y,q2.y) && - min(q1.y,q2.y) <= max(p1.y,p2.y); - return ret; -} - -__device__ inline int check_in_box2d(const float *box, const Point &p){ - //params: (7) [x, y, z, dx, dy, dz, heading] - const float MARGIN = 1e-2; - - float center_x = box[0], center_y = box[1]; - float angle_cos = cos(-box[6]), angle_sin = sin(-box[6]); // rotate the point in the opposite direction of box - float rot_x = (p.x - center_x) * angle_cos + (p.y - center_y) * (-angle_sin); - float rot_y = (p.x - center_x) * angle_sin + (p.y - center_y) * angle_cos; - - return (fabs(rot_x) < box[3] / 2 + MARGIN && fabs(rot_y) < box[4] / 2 + MARGIN); -} - -__device__ inline int intersection(const Point &p1, const Point &p0, const Point &q1, const Point &q0, Point &ans){ - // fast exclusion - if (check_rect_cross(p0, p1, q0, q1) == 0) return 0; - - // check cross standing - float s1 = cross(q0, p1, p0); - float s2 = cross(p1, q1, p0); - float s3 = cross(p0, q1, q0); - float s4 = cross(q1, p1, q0); - - if (!(s1 * s2 > 0 && s3 * s4 > 0)) return 0; - - // calculate intersection of two lines - float s5 = cross(q1, p1, p0); - if(fabs(s5 - s1) > EPS){ - ans.x = (s5 * q0.x - s1 * q1.x) / (s5 - s1); - ans.y = (s5 * q0.y - s1 * q1.y) / (s5 - s1); - - } - else{ - float a0 = p0.y - p1.y, b0 = p1.x - p0.x, c0 = p0.x * p1.y - p1.x * p0.y; - float a1 = q0.y - q1.y, b1 = q1.x - q0.x, c1 = q0.x * q1.y - q1.x * q0.y; - float D = a0 * b1 - a1 * b0; - - ans.x = (b0 * c1 - b1 * c0) / D; - ans.y = (a1 * c0 - a0 * c1) / D; - } - - return 1; -} - -__device__ inline void rotate_around_center(const Point ¢er, const float angle_cos, const float angle_sin, Point &p){ - float new_x = (p.x - center.x) * angle_cos + (p.y - center.y) * (-angle_sin) + center.x; - float new_y = (p.x - center.x) * angle_sin + (p.y - center.y) * angle_cos + center.y; - p.set(new_x, new_y); -} - -__device__ inline int point_cmp(const Point &a, const Point &b, const Point ¢er){ - return atan2(a.y - center.y, a.x - center.x) > atan2(b.y - center.y, b.x - center.x); -} - -__device__ inline float box_overlap(const float *box_a, const float *box_b){ - // params box_a: [x, y, z, dx, dy, dz, heading] - // params box_b: [x, y, z, dx, dy, dz, heading] - - float a_angle = box_a[6], b_angle = box_b[6]; - float a_dx_half = box_a[3] / 2, b_dx_half = box_b[3] / 2, a_dy_half = box_a[4] / 2, b_dy_half = box_b[4] / 2; - float a_x1 = box_a[0] - a_dx_half, a_y1 = box_a[1] - a_dy_half; - float a_x2 = box_a[0] + a_dx_half, a_y2 = box_a[1] + a_dy_half; - float b_x1 = box_b[0] - b_dx_half, b_y1 = box_b[1] - b_dy_half; - float b_x2 = box_b[0] + b_dx_half, b_y2 = box_b[1] + b_dy_half; - - Point center_a(box_a[0], box_a[1]); - Point center_b(box_b[0], box_b[1]); - -#ifdef DEBUG - printf("a: (%.3f, %.3f, %.3f, %.3f, %.3f), b: (%.3f, %.3f, %.3f, %.3f, %.3f)\n", a_x1, a_y1, a_x2, a_y2, a_angle, - b_x1, b_y1, b_x2, b_y2, b_angle); - printf("center a: (%.3f, %.3f), b: (%.3f, %.3f)\n", center_a.x, center_a.y, center_b.x, center_b.y); -#endif - - Point box_a_corners[5]; - box_a_corners[0].set(a_x1, a_y1); - box_a_corners[1].set(a_x2, a_y1); - box_a_corners[2].set(a_x2, a_y2); - box_a_corners[3].set(a_x1, a_y2); - - Point box_b_corners[5]; - box_b_corners[0].set(b_x1, b_y1); - box_b_corners[1].set(b_x2, b_y1); - box_b_corners[2].set(b_x2, b_y2); - box_b_corners[3].set(b_x1, b_y2); - - // get oriented corners - float a_angle_cos = cos(a_angle), a_angle_sin = sin(a_angle); - float b_angle_cos = cos(b_angle), b_angle_sin = sin(b_angle); - - for (int k = 0; k < 4; k++){ -#ifdef DEBUG - printf("before corner %d: a(%.3f, %.3f), b(%.3f, %.3f) \n", k, box_a_corners[k].x, box_a_corners[k].y, box_b_corners[k].x, box_b_corners[k].y); -#endif - rotate_around_center(center_a, a_angle_cos, a_angle_sin, box_a_corners[k]); - rotate_around_center(center_b, b_angle_cos, b_angle_sin, box_b_corners[k]); -#ifdef DEBUG - printf("corner %d: a(%.3f, %.3f), b(%.3f, %.3f) \n", k, box_a_corners[k].x, box_a_corners[k].y, box_b_corners[k].x, box_b_corners[k].y); -#endif - } - - box_a_corners[4] = box_a_corners[0]; - box_b_corners[4] = box_b_corners[0]; - - // get intersection of lines - Point cross_points[16]; - Point poly_center; - int cnt = 0, flag = 0; - - poly_center.set(0, 0); - for (int i = 0; i < 4; i++){ - for (int j = 0; j < 4; j++){ - flag = intersection(box_a_corners[i + 1], box_a_corners[i], box_b_corners[j + 1], box_b_corners[j], cross_points[cnt]); - if (flag){ - poly_center = poly_center + cross_points[cnt]; - cnt++; -#ifdef DEBUG - printf("Cross points (%.3f, %.3f): a(%.3f, %.3f)->(%.3f, %.3f), b(%.3f, %.3f)->(%.3f, %.3f) \n", - cross_points[cnt - 1].x, cross_points[cnt - 1].y, - box_a_corners[i].x, box_a_corners[i].y, box_a_corners[i + 1].x, box_a_corners[i + 1].y, - box_b_corners[i].x, box_b_corners[i].y, box_b_corners[i + 1].x, box_b_corners[i + 1].y); -#endif - } - } - } - - // check corners - for (int k = 0; k < 4; k++){ - if (check_in_box2d(box_a, box_b_corners[k])){ - poly_center = poly_center + box_b_corners[k]; - cross_points[cnt] = box_b_corners[k]; - cnt++; -#ifdef DEBUG - printf("b corners in a: corner_b(%.3f, %.3f)", cross_points[cnt - 1].x, cross_points[cnt - 1].y); -#endif - } - if (check_in_box2d(box_b, box_a_corners[k])){ - poly_center = poly_center + box_a_corners[k]; - cross_points[cnt] = box_a_corners[k]; - cnt++; -#ifdef DEBUG - printf("a corners in b: corner_a(%.3f, %.3f)", cross_points[cnt - 1].x, cross_points[cnt - 1].y); -#endif - } - } - - poly_center.x /= cnt; - poly_center.y /= cnt; - - // sort the points of polygon - Point temp; - for (int j = 0; j < cnt - 1; j++){ - for (int i = 0; i < cnt - j - 1; i++){ - if (point_cmp(cross_points[i], cross_points[i + 1], poly_center)){ - temp = cross_points[i]; - cross_points[i] = cross_points[i + 1]; - cross_points[i + 1] = temp; - } - } - } - -#ifdef DEBUG - printf("cnt=%d\n", cnt); - for (int i = 0; i < cnt; i++){ - printf("All cross point %d: (%.3f, %.3f)\n", i, cross_points[i].x, cross_points[i].y); - } -#endif - - // get the overlap areas - float area = 0; - for (int k = 0; k < cnt - 1; k++){ - area += cross(cross_points[k] - cross_points[0], cross_points[k + 1] - cross_points[0]); - } - - return fabs(area) / 2.0; -} - -__device__ inline float iou_bev(const float *box_a, const float *box_b){ - // params box_a: [x, y, z, dx, dy, dz, heading] - // params box_b: [x, y, z, dx, dy, dz, heading] - float sa = box_a[3] * box_a[4]; - float sb = box_b[3] * box_b[4]; - float s_overlap = box_overlap(box_a, box_b); - return s_overlap / fmaxf(sa + sb - s_overlap, EPS); -} - -__global__ void boxes_overlap_kernel(const int num_a, const float *boxes_a, const int num_b, const float *boxes_b, float *ans_overlap){ - // params boxes_a: (N, 7) [x, y, z, dx, dy, dz, heading] - // params boxes_b: (M, 7) [x, y, z, dx, dy, dz, heading] - const int a_idx = blockIdx.y * THREADS_PER_BLOCK + threadIdx.y; - const int b_idx = blockIdx.x * THREADS_PER_BLOCK + threadIdx.x; - - if (a_idx >= num_a || b_idx >= num_b){ - return; - } - const float * cur_box_a = boxes_a + a_idx * 7; - const float * cur_box_b = boxes_b + b_idx * 7; - float s_overlap = box_overlap(cur_box_a, cur_box_b); - ans_overlap[a_idx * num_b + b_idx] = s_overlap; -} - -__global__ void boxes_iou_bev_kernel(const int num_a, const float *boxes_a, const int num_b, const float *boxes_b, float *ans_iou){ - // params boxes_a: (N, 7) [x, y, z, dx, dy, dz, heading] - // params boxes_b: (M, 7) [x, y, z, dx, dy, dz, heading] - const int a_idx = blockIdx.y * THREADS_PER_BLOCK + threadIdx.y; - const int b_idx = blockIdx.x * THREADS_PER_BLOCK + threadIdx.x; - - if (a_idx >= num_a || b_idx >= num_b){ - return; - } - - const float * cur_box_a = boxes_a + a_idx * 7; - const float * cur_box_b = boxes_b + b_idx * 7; - float cur_iou_bev = iou_bev(cur_box_a, cur_box_b); - ans_iou[a_idx * num_b + b_idx] = cur_iou_bev; -} - -__global__ void nms_kernel(const int boxes_num, const float nms_overlap_thresh, - const float *boxes, unsigned long long *mask){ - //params: boxes (N, 7) [x, y, z, dx, dy, dz, heading] - //params: mask (N, N/THREADS_PER_BLOCK_NMS) - - const int row_start = blockIdx.y; - const int col_start = blockIdx.x; - - // if (row_start > col_start) return; - - const int row_size = fminf(boxes_num - row_start * THREADS_PER_BLOCK_NMS, THREADS_PER_BLOCK_NMS); - const int col_size = fminf(boxes_num - col_start * THREADS_PER_BLOCK_NMS, THREADS_PER_BLOCK_NMS); - - __shared__ float block_boxes[THREADS_PER_BLOCK_NMS * 7]; - - if (threadIdx.x < col_size) { - block_boxes[threadIdx.x * 7 + 0] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 0]; - block_boxes[threadIdx.x * 7 + 1] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 1]; - block_boxes[threadIdx.x * 7 + 2] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 2]; - block_boxes[threadIdx.x * 7 + 3] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 3]; - block_boxes[threadIdx.x * 7 + 4] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 4]; - block_boxes[threadIdx.x * 7 + 5] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 5]; - block_boxes[threadIdx.x * 7 + 6] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 6]; - } - __syncthreads(); - - if (threadIdx.x < row_size) { - const int cur_box_idx = THREADS_PER_BLOCK_NMS * row_start + threadIdx.x; - const float *cur_box = boxes + cur_box_idx * 7; - - int i = 0; - unsigned long long t = 0; - int start = 0; - if (row_start == col_start) { - start = threadIdx.x + 1; - } - for (i = start; i < col_size; i++) { - if (iou_bev(cur_box, block_boxes + i * 7) > nms_overlap_thresh){ - t |= 1ULL << i; - } - } - const int col_blocks = DIVUP(boxes_num, THREADS_PER_BLOCK_NMS); - mask[cur_box_idx * col_blocks + col_start] = t; - } -} - - -__device__ inline float iou_normal(float const * const a, float const * const b) { - //params: a: [x, y, z, dx, dy, dz, heading] - //params: b: [x, y, z, dx, dy, dz, heading] - - float left = fmaxf(a[0] - a[3] / 2, b[0] - b[3] / 2), right = fminf(a[0] + a[3] / 2, b[0] + b[3] / 2); - float top = fmaxf(a[1] - a[4] / 2, b[1] - b[4] / 2), bottom = fminf(a[1] + a[4] / 2, b[1] + b[4] / 2); - float width = fmaxf(right - left, 0.f), height = fmaxf(bottom - top, 0.f); - float interS = width * height; - float Sa = a[3] * a[4]; - float Sb = b[3] * b[4]; - return interS / fmaxf(Sa + Sb - interS, EPS); -} - - -__global__ void nms_normal_kernel(const int boxes_num, const float nms_overlap_thresh, - const float *boxes, unsigned long long *mask){ - //params: boxes (N, 7) [x, y, z, dx, dy, dz, heading] - //params: mask (N, N/THREADS_PER_BLOCK_NMS) - - const int row_start = blockIdx.y; - const int col_start = blockIdx.x; - - // if (row_start > col_start) return; - - const int row_size = fminf(boxes_num - row_start * THREADS_PER_BLOCK_NMS, THREADS_PER_BLOCK_NMS); - const int col_size = fminf(boxes_num - col_start * THREADS_PER_BLOCK_NMS, THREADS_PER_BLOCK_NMS); - - __shared__ float block_boxes[THREADS_PER_BLOCK_NMS * 7]; - - if (threadIdx.x < col_size) { - block_boxes[threadIdx.x * 7 + 0] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 0]; - block_boxes[threadIdx.x * 7 + 1] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 1]; - block_boxes[threadIdx.x * 7 + 2] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 2]; - block_boxes[threadIdx.x * 7 + 3] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 3]; - block_boxes[threadIdx.x * 7 + 4] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 4]; - block_boxes[threadIdx.x * 7 + 5] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 5]; - block_boxes[threadIdx.x * 7 + 6] = boxes[(THREADS_PER_BLOCK_NMS * col_start + threadIdx.x) * 7 + 6]; - } - __syncthreads(); - - if (threadIdx.x < row_size) { - const int cur_box_idx = THREADS_PER_BLOCK_NMS * row_start + threadIdx.x; - const float *cur_box = boxes + cur_box_idx * 7; - - int i = 0; - unsigned long long t = 0; - int start = 0; - if (row_start == col_start) { - start = threadIdx.x + 1; - } - for (i = start; i < col_size; i++) { - if (iou_normal(cur_box, block_boxes + i * 7) > nms_overlap_thresh){ - t |= 1ULL << i; - } - } - const int col_blocks = DIVUP(boxes_num, THREADS_PER_BLOCK_NMS); - mask[cur_box_idx * col_blocks + col_start] = t; - } -} - - - - - -void boxesoverlapLauncher(const int num_a, const float *boxes_a, const int num_b, const float *boxes_b, float *ans_overlap){ - - dim3 blocks(DIVUP(num_b, THREADS_PER_BLOCK), DIVUP(num_a, THREADS_PER_BLOCK)); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK, THREADS_PER_BLOCK); - - boxes_overlap_kernel<<>>(num_a, boxes_a, num_b, boxes_b, ans_overlap); -#ifdef DEBUG - cudaDeviceSynchronize(); // for using printf in kernel function -#endif -} - -void boxesioubevLauncher(const int num_a, const float *boxes_a, const int num_b, const float *boxes_b, float *ans_iou){ - - dim3 blocks(DIVUP(num_b, THREADS_PER_BLOCK), DIVUP(num_a, THREADS_PER_BLOCK)); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK, THREADS_PER_BLOCK); - - boxes_iou_bev_kernel<<>>(num_a, boxes_a, num_b, boxes_b, ans_iou); -#ifdef DEBUG - cudaDeviceSynchronize(); // for using printf in kernel function -#endif -} - - -void nmsLauncher(const float *boxes, unsigned long long * mask, int boxes_num, float nms_overlap_thresh){ - dim3 blocks(DIVUP(boxes_num, THREADS_PER_BLOCK_NMS), - DIVUP(boxes_num, THREADS_PER_BLOCK_NMS)); - dim3 threads(THREADS_PER_BLOCK_NMS); - nms_kernel<<>>(boxes_num, nms_overlap_thresh, boxes, mask); -} - - -void nmsNormalLauncher(const float *boxes, unsigned long long * mask, int boxes_num, float nms_overlap_thresh){ - dim3 blocks(DIVUP(boxes_num, THREADS_PER_BLOCK_NMS), - DIVUP(boxes_num, THREADS_PER_BLOCK_NMS)); - dim3 threads(THREADS_PER_BLOCK_NMS); - nms_normal_kernel<<>>(boxes_num, nms_overlap_thresh, boxes, mask); -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/pointnet2_utils.py b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/pointnet2_utils.py deleted file mode 100644 index f15a4e5c..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/pointnet2_utils.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Tuple - -import torch -import torch.nn as nn -from torch.autograd import Function, Variable - -from opencood.pcdet_utils.pointnet2.pointnet2_batch import pointnet2_batch_cuda as pointnet2 - - -class GroupingOperation(Function): - @staticmethod - def forward(ctx, features: torch.Tensor, idx: torch.Tensor) -> torch.Tensor: - """ - :param ctx: - :param features: (B, C, N) tensor of features to group - :param idx: (B, npoint, nsample) tensor containing the indicies of features to group with - :return: - output: (B, C, npoint, nsample) tensor - """ - assert features.is_contiguous() - assert idx.is_contiguous() - - B, nfeatures, nsample = idx.size() - _, C, N = features.size() - output = torch.cuda.FloatTensor(B, C, nfeatures, nsample) - - pointnet2.group_points_wrapper(B, C, N, nfeatures, nsample, features, idx, output) - - ctx.for_backwards = (idx, N) - return output - - @staticmethod - def backward(ctx, grad_out: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: - """ - :param ctx: - :param grad_out: (B, C, npoint, nsample) tensor of the gradients of the output from forward - :return: - grad_features: (B, C, N) gradient of the features - """ - idx, N = ctx.for_backwards - - B, C, npoint, nsample = grad_out.size() - grad_features = Variable(torch.cuda.FloatTensor(B, C, N).zero_()) - - grad_out_data = grad_out.data.contiguous() - pointnet2.group_points_grad_wrapper(B, C, N, npoint, nsample, grad_out_data, idx, grad_features.data) - return grad_features, None - - -grouping_operation = GroupingOperation.apply - - -class BallQuery(Function): - @staticmethod - def forward(ctx, radius: float, nsample: int, xyz: torch.Tensor, new_xyz: torch.Tensor) -> torch.Tensor: - """ - :param ctx: - :param radius: float, radius of the balls - :param nsample: int, maximum number of features in the balls - :param xyz: (B, N, 3) xyz coordinates of the features - :param new_xyz: (B, npoint, 3) centers of the ball query - :return: - idx: (B, npoint, nsample) tensor with the indicies of the features that form the query balls - """ - assert new_xyz.is_contiguous() - assert xyz.is_contiguous() - - B, N, _ = xyz.size() - npoint = new_xyz.size(1) - idx = torch.cuda.IntTensor(B, npoint, nsample).zero_() - - pointnet2.ball_query_wrapper(B, N, npoint, radius, nsample, new_xyz, xyz, idx) - return idx - - @staticmethod - def backward(ctx, a=None): - return None, None, None, None - - -ball_query = BallQuery.apply - - -class QueryAndGroup(nn.Module): - def __init__(self, radius: float, nsample: int, use_xyz: bool = True): - """ - :param radius: float, radius of ball - :param nsample: int, maximum number of features to gather in the ball - :param use_xyz: - """ - super().__init__() - self.radius, self.nsample, self.use_xyz = radius, nsample, use_xyz - - def forward(self, xyz: torch.Tensor, new_xyz: torch.Tensor, features: torch.Tensor = None) -> Tuple[torch.Tensor]: - """ - :param xyz: (B, N, 3) xyz coordinates of the features - :param new_xyz: (B, npoint, 3) centroids - :param features: (B, C, N) descriptors of the features - :return: - new_features: (B, 3 + C, npoint, nsample) - """ - idx = ball_query(self.radius, self.nsample, xyz, new_xyz) - xyz_trans = xyz.transpose(1, 2).contiguous() - grouped_xyz = grouping_operation(xyz_trans, idx) # (B, 3, npoint, nsample) - grouped_xyz -= new_xyz.transpose(1, 2).unsqueeze(-1) - - if features is not None: - grouped_features = grouping_operation(features, idx) - if self.use_xyz: - new_features = torch.cat([grouped_xyz, grouped_features], dim=1) # (B, C + 3, npoint, nsample) - else: - new_features = grouped_features - else: - assert self.use_xyz, "Cannot have not features and not use xyz as a feature!" - new_features = grouped_xyz - - return new_features diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query.cpp deleted file mode 100644 index cf1db8c6..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* -Batch version of ball query, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - -#include -#include -#include -#include -#include "ball_query_gpu.h" - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - -int ball_query_wrapper_fast(int b, int n, int m, float radius, int nsample, - at::Tensor new_xyz_tensor, at::Tensor xyz_tensor, at::Tensor idx_tensor) { - - CHECK_INPUT(new_xyz_tensor); - CHECK_INPUT(xyz_tensor); - CHECK_INPUT(idx_tensor); - - const float* new_xyz = new_xyz_tensor.data_ptr(); - const float* xyz = xyz_tensor.data_ptr(); - int* idx = idx_tensor.data_ptr(); - - ball_query_kernel_launcher_fast(b, n, m, radius, nsample, new_xyz, xyz, idx); - return 1; -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query_gpu.cu deleted file mode 100644 index 870ca123..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query_gpu.cu +++ /dev/null @@ -1,73 +0,0 @@ -/* -batch version of ball query, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - -#include -#include -#include - -#include "ball_query_gpu.h" -#include "cuda_utils.h" - - -__global__ void ball_query_kernel_fast(int b, int n, int m, float radius, int nsample, - const float *__restrict__ new_xyz, const float *__restrict__ xyz, int *__restrict__ idx) { - // new_xyz: (B, M, 3) - // xyz: (B, N, 3) - // output: - // idx: (B, M, nsample) - int bs_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (bs_idx >= b || pt_idx >= m) return; - - new_xyz += bs_idx * m * 3 + pt_idx * 3; - xyz += bs_idx * n * 3; - idx += bs_idx * m * nsample + pt_idx * nsample; - - float radius2 = radius * radius; - float new_x = new_xyz[0]; - float new_y = new_xyz[1]; - float new_z = new_xyz[2]; - - int cnt = 0; - for (int k = 0; k < n; ++k) { - float x = xyz[k * 3 + 0]; - float y = xyz[k * 3 + 1]; - float z = xyz[k * 3 + 2]; - float d2 = (new_x - x) * (new_x - x) + (new_y - y) * (new_y - y) + (new_z - z) * (new_z - z); - if (d2 < radius2){ - if (cnt == 0){ - for (int l = 0; l < nsample; ++l) { - idx[l] = k; - } - } - idx[cnt] = k; - ++cnt; - if (cnt >= nsample) break; - } - } -} - - -void ball_query_kernel_launcher_fast(int b, int n, int m, float radius, int nsample, \ - const float *new_xyz, const float *xyz, int *idx) { - // new_xyz: (B, M, 3) - // xyz: (B, N, 3) - // output: - // idx: (B, M, nsample) - - cudaError_t err; - - dim3 blocks(DIVUP(m, THREADS_PER_BLOCK), b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - ball_query_kernel_fast<<>>(b, n, m, radius, nsample, new_xyz, xyz, idx); - // cudaDeviceSynchronize(); // for using printf in kernel function - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query_gpu.h deleted file mode 100644 index 7b181984..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/ball_query_gpu.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _BALL_QUERY_GPU_H -#define _BALL_QUERY_GPU_H - -#include -#include -#include -#include - -int ball_query_wrapper_fast(int b, int n, int m, float radius, int nsample, - at::Tensor new_xyz_tensor, at::Tensor xyz_tensor, at::Tensor idx_tensor); - -void ball_query_kernel_launcher_fast(int b, int n, int m, float radius, int nsample, - const float *xyz, const float *new_xyz, int *idx); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/cuda_utils.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/cuda_utils.h deleted file mode 100644 index 7fe27969..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/cuda_utils.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _CUDA_UTILS_H -#define _CUDA_UTILS_H - -#include - -#define TOTAL_THREADS 1024 -#define THREADS_PER_BLOCK 256 -#define DIVUP(m,n) ((m) / (n) + ((m) % (n) > 0)) - -inline int opt_n_threads(int work_size) { - const int pow_2 = std::log(static_cast(work_size)) / std::log(2.0); - - return max(min(1 << pow_2, TOTAL_THREADS), 1); -} -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points.cpp deleted file mode 100644 index 280cff5c..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* -Batch version of point grouping, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - -#include -#include -#include -#include - -#include "group_points_gpu.h" - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - - -int group_points_grad_wrapper_fast(int b, int c, int n, int npoints, int nsample, - at::Tensor grad_out_tensor, at::Tensor idx_tensor, at::Tensor grad_points_tensor) { - - CHECK_INPUT(grad_out_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(grad_points_tensor); - - float* grad_points = grad_points_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - const float* grad_out = grad_out_tensor.data_ptr(); - - group_points_grad_kernel_launcher_fast(b, c, n, npoints, nsample, grad_out, idx, grad_points); - return 1; -} - - -int group_points_wrapper_fast(int b, int c, int n, int npoints, int nsample, - at::Tensor points_tensor, at::Tensor idx_tensor, at::Tensor out_tensor) { - - CHECK_INPUT(points_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(out_tensor); - - const float* points = points_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - float* out = out_tensor.data_ptr(); - - group_points_kernel_launcher_fast(b, c, n, npoints, nsample, points, idx, out); - return 1; -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points_gpu.cu deleted file mode 100644 index a13c0a34..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points_gpu.cu +++ /dev/null @@ -1,92 +0,0 @@ -/* -batch version of point grouping, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - -#include -#include - -#include "cuda_utils.h" -#include "group_points_gpu.h" - - -__global__ void group_points_grad_kernel_fast(int b, int c, int n, int npoints, int nsample, - const float *__restrict__ grad_out, const int *__restrict__ idx, float *__restrict__ grad_points) { - // grad_out: (B, C, npoints, nsample) - // idx: (B, npoints, nsample) - // output: - // grad_points: (B, C, N) - int bs_idx = blockIdx.z; - int c_idx = blockIdx.y; - int index = blockIdx.x * blockDim.x + threadIdx.x; - int pt_idx = index / nsample; - if (bs_idx >= b || c_idx >= c || pt_idx >= npoints) return; - - int sample_idx = index % nsample; - grad_out += bs_idx * c * npoints * nsample + c_idx * npoints * nsample + pt_idx * nsample + sample_idx; - idx += bs_idx * npoints * nsample + pt_idx * nsample + sample_idx; - - atomicAdd(grad_points + bs_idx * c * n + c_idx * n + idx[0] , grad_out[0]); -} - -void group_points_grad_kernel_launcher_fast(int b, int c, int n, int npoints, int nsample, - const float *grad_out, const int *idx, float *grad_points) { - // grad_out: (B, C, npoints, nsample) - // idx: (B, npoints, nsample) - // output: - // grad_points: (B, C, N) - cudaError_t err; - dim3 blocks(DIVUP(npoints * nsample, THREADS_PER_BLOCK), c, b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - group_points_grad_kernel_fast<<>>(b, c, n, npoints, nsample, grad_out, idx, grad_points); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - - -__global__ void group_points_kernel_fast(int b, int c, int n, int npoints, int nsample, - const float *__restrict__ points, const int *__restrict__ idx, float *__restrict__ out) { - // points: (B, C, N) - // idx: (B, npoints, nsample) - // output: - // out: (B, C, npoints, nsample) - int bs_idx = blockIdx.z; - int c_idx = blockIdx.y; - int index = blockIdx.x * blockDim.x + threadIdx.x; - int pt_idx = index / nsample; - if (bs_idx >= b || c_idx >= c || pt_idx >= npoints) return; - - int sample_idx = index % nsample; - - idx += bs_idx * npoints * nsample + pt_idx * nsample + sample_idx; - int in_idx = bs_idx * c * n + c_idx * n + idx[0]; - int out_idx = bs_idx * c * npoints * nsample + c_idx * npoints * nsample + pt_idx * nsample + sample_idx; - - out[out_idx] = points[in_idx]; -} - - -void group_points_kernel_launcher_fast(int b, int c, int n, int npoints, int nsample, - const float *points, const int *idx, float *out) { - // points: (B, C, N) - // idx: (B, npoints, nsample) - // output: - // out: (B, C, npoints, nsample) - cudaError_t err; - dim3 blocks(DIVUP(npoints * nsample, THREADS_PER_BLOCK), c, b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - group_points_kernel_fast<<>>(b, c, n, npoints, nsample, points, idx, out); - // cudaDeviceSynchronize(); // for using printf in kernel function - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points_gpu.h deleted file mode 100644 index 6eb72259..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/group_points_gpu.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef _GROUP_POINTS_GPU_H -#define _GROUP_POINTS_GPU_H - -#include -#include -#include -#include - - -int group_points_wrapper_fast(int b, int c, int n, int npoints, int nsample, - at::Tensor points_tensor, at::Tensor idx_tensor, at::Tensor out_tensor); - -void group_points_kernel_launcher_fast(int b, int c, int n, int npoints, int nsample, - const float *points, const int *idx, float *out); - -int group_points_grad_wrapper_fast(int b, int c, int n, int npoints, int nsample, - at::Tensor grad_out_tensor, at::Tensor idx_tensor, at::Tensor grad_points_tensor); - -void group_points_grad_kernel_launcher_fast(int b, int c, int n, int npoints, int nsample, - const float *grad_out, const int *idx, float *grad_points); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate.cpp deleted file mode 100644 index 2b9581d3..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* -Batch version of point interpolation, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include "interpolate_gpu.h" - - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - - -void three_nn_wrapper_fast(int b, int n, int m, at::Tensor unknown_tensor, - at::Tensor known_tensor, at::Tensor dist2_tensor, at::Tensor idx_tensor) { - - CHECK_INPUT(unknown_tensor); - CHECK_INPUT(known_tensor); - CHECK_INPUT(dist2_tensor); - CHECK_INPUT(idx_tensor); - - const float* unknown = unknown_tensor.data_ptr(); - const float* known = known_tensor.data_ptr(); - float* dist2 = dist2_tensor.data_ptr(); - int* idx = idx_tensor.data_ptr(); - - three_nn_kernel_launcher_fast(b, n, m, unknown, known, dist2, idx); -} - - -void three_interpolate_wrapper_fast(int b, int c, int m, int n, - at::Tensor points_tensor, - at::Tensor idx_tensor, - at::Tensor weight_tensor, - at::Tensor out_tensor) { - - CHECK_INPUT(points_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(weight_tensor); - CHECK_INPUT(out_tensor); - - const float* points = points_tensor.data_ptr(); - const float* weight = weight_tensor.data_ptr(); - float* out = out_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - - three_interpolate_kernel_launcher_fast(b, c, m, n, points, idx, weight, out); -} - - -void three_interpolate_grad_wrapper_fast(int b, int c, int n, int m, - at::Tensor grad_out_tensor, - at::Tensor idx_tensor, - at::Tensor weight_tensor, - at::Tensor grad_points_tensor) { - - CHECK_INPUT(grad_out_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(weight_tensor); - CHECK_INPUT(grad_points_tensor); - - const float* grad_out = grad_out_tensor.data_ptr(); - const float* weight = weight_tensor.data_ptr(); - float* grad_points = grad_points_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - - three_interpolate_grad_kernel_launcher_fast(b, c, n, m, grad_out, idx, weight, grad_points); -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate_gpu.cu deleted file mode 100644 index d94e0778..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate_gpu.cu +++ /dev/null @@ -1,168 +0,0 @@ -/* -batch version of point interpolation, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - - -#include -#include -#include - -#include "cuda_utils.h" -#include "interpolate_gpu.h" - - -__global__ void three_nn_kernel_fast(int b, int n, int m, const float *__restrict__ unknown, - const float *__restrict__ known, float *__restrict__ dist2, int *__restrict__ idx) { - // unknown: (B, N, 3) - // known: (B, M, 3) - // output: - // dist2: (B, N, 3) - // idx: (B, N, 3) - - int bs_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (bs_idx >= b || pt_idx >= n) return; - - unknown += bs_idx * n * 3 + pt_idx * 3; - known += bs_idx * m * 3; - dist2 += bs_idx * n * 3 + pt_idx * 3; - idx += bs_idx * n * 3 + pt_idx * 3; - - float ux = unknown[0]; - float uy = unknown[1]; - float uz = unknown[2]; - - double best1 = 1e40, best2 = 1e40, best3 = 1e40; - int besti1 = 0, besti2 = 0, besti3 = 0; - for (int k = 0; k < m; ++k) { - float x = known[k * 3 + 0]; - float y = known[k * 3 + 1]; - float z = known[k * 3 + 2]; - float d = (ux - x) * (ux - x) + (uy - y) * (uy - y) + (uz - z) * (uz - z); - if (d < best1) { - best3 = best2; besti3 = besti2; - best2 = best1; besti2 = besti1; - best1 = d; besti1 = k; - } - else if (d < best2) { - best3 = best2; besti3 = besti2; - best2 = d; besti2 = k; - } - else if (d < best3) { - best3 = d; besti3 = k; - } - } - dist2[0] = best1; dist2[1] = best2; dist2[2] = best3; - idx[0] = besti1; idx[1] = besti2; idx[2] = besti3; -} - - -void three_nn_kernel_launcher_fast(int b, int n, int m, const float *unknown, - const float *known, float *dist2, int *idx) { - // unknown: (B, N, 3) - // known: (B, M, 3) - // output: - // dist2: (B, N, 3) - // idx: (B, N, 3) - - cudaError_t err; - dim3 blocks(DIVUP(n, THREADS_PER_BLOCK), b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - three_nn_kernel_fast<<>>(b, n, m, unknown, known, dist2, idx); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - - -__global__ void three_interpolate_kernel_fast(int b, int c, int m, int n, const float *__restrict__ points, - const int *__restrict__ idx, const float *__restrict__ weight, float *__restrict__ out) { - // points: (B, C, M) - // idx: (B, N, 3) - // weight: (B, N, 3) - // output: - // out: (B, C, N) - - int bs_idx = blockIdx.z; - int c_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - - if (bs_idx >= b || c_idx >= c || pt_idx >= n) return; - - weight += bs_idx * n * 3 + pt_idx * 3; - points += bs_idx * c * m + c_idx * m; - idx += bs_idx * n * 3 + pt_idx * 3; - out += bs_idx * c * n + c_idx * n; - - out[pt_idx] = weight[0] * points[idx[0]] + weight[1] * points[idx[1]] + weight[2] * points[idx[2]]; -} - -void three_interpolate_kernel_launcher_fast(int b, int c, int m, int n, - const float *points, const int *idx, const float *weight, float *out) { - // points: (B, C, M) - // idx: (B, N, 3) - // weight: (B, N, 3) - // output: - // out: (B, C, N) - - cudaError_t err; - dim3 blocks(DIVUP(n, THREADS_PER_BLOCK), c, b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - three_interpolate_kernel_fast<<>>(b, c, m, n, points, idx, weight, out); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - - -__global__ void three_interpolate_grad_kernel_fast(int b, int c, int n, int m, const float *__restrict__ grad_out, - const int *__restrict__ idx, const float *__restrict__ weight, float *__restrict__ grad_points) { - // grad_out: (B, C, N) - // weight: (B, N, 3) - // output: - // grad_points: (B, C, M) - - int bs_idx = blockIdx.z; - int c_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - - if (bs_idx >= b || c_idx >= c || pt_idx >= n) return; - - grad_out += bs_idx * c * n + c_idx * n + pt_idx; - weight += bs_idx * n * 3 + pt_idx * 3; - grad_points += bs_idx * c * m + c_idx * m; - idx += bs_idx * n * 3 + pt_idx * 3; - - - atomicAdd(grad_points + idx[0], grad_out[0] * weight[0]); - atomicAdd(grad_points + idx[1], grad_out[0] * weight[1]); - atomicAdd(grad_points + idx[2], grad_out[0] * weight[2]); -} - -void three_interpolate_grad_kernel_launcher_fast(int b, int c, int n, int m, const float *grad_out, - const int *idx, const float *weight, float *grad_points) { - // grad_out: (B, C, N) - // weight: (B, N, 3) - // output: - // grad_points: (B, C, M) - - cudaError_t err; - dim3 blocks(DIVUP(n, THREADS_PER_BLOCK), c, b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - three_interpolate_grad_kernel_fast<<>>(b, c, n, m, grad_out, idx, weight, grad_points); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate_gpu.h deleted file mode 100644 index 19d41eb1..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/interpolate_gpu.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef _INTERPOLATE_GPU_H -#define _INTERPOLATE_GPU_H - -#include -#include -#include -#include - - -void three_nn_wrapper_fast(int b, int n, int m, at::Tensor unknown_tensor, - at::Tensor known_tensor, at::Tensor dist2_tensor, at::Tensor idx_tensor); - -void three_nn_kernel_launcher_fast(int b, int n, int m, const float *unknown, - const float *known, float *dist2, int *idx); - - -void three_interpolate_wrapper_fast(int b, int c, int m, int n, at::Tensor points_tensor, - at::Tensor idx_tensor, at::Tensor weight_tensor, at::Tensor out_tensor); - -void three_interpolate_kernel_launcher_fast(int b, int c, int m, int n, - const float *points, const int *idx, const float *weight, float *out); - - -void three_interpolate_grad_wrapper_fast(int b, int c, int n, int m, at::Tensor grad_out_tensor, - at::Tensor idx_tensor, at::Tensor weight_tensor, at::Tensor grad_points_tensor); - -void three_interpolate_grad_kernel_launcher_fast(int b, int c, int n, int m, const float *grad_out, - const int *idx, const float *weight, float *grad_points); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/pointnet2_api.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/pointnet2_api.cpp deleted file mode 100644 index 10038bcb..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/pointnet2_api.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include - -#include "ball_query_gpu.h" -#include "group_points_gpu.h" -#include "sampling_gpu.h" -#include "interpolate_gpu.h" - - -PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { - m.def("ball_query_wrapper", &ball_query_wrapper_fast, "ball_query_wrapper_fast"); - - m.def("group_points_wrapper", &group_points_wrapper_fast, "group_points_wrapper_fast"); - m.def("group_points_grad_wrapper", &group_points_grad_wrapper_fast, "group_points_grad_wrapper_fast"); - - m.def("gather_points_wrapper", &gather_points_wrapper_fast, "gather_points_wrapper_fast"); - m.def("gather_points_grad_wrapper", &gather_points_grad_wrapper_fast, "gather_points_grad_wrapper_fast"); - - m.def("furthest_point_sampling_wrapper", &furthest_point_sampling_wrapper, "furthest_point_sampling_wrapper"); - - m.def("three_nn_wrapper", &three_nn_wrapper_fast, "three_nn_wrapper_fast"); - m.def("three_interpolate_wrapper", &three_interpolate_wrapper_fast, "three_interpolate_wrapper_fast"); - m.def("three_interpolate_grad_wrapper", &three_interpolate_grad_wrapper_fast, "three_interpolate_grad_wrapper_fast"); -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling.cpp deleted file mode 100644 index 27b91d49..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* -Batch version of point sampling and gathering, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - -#include -#include -#include - -#include "sampling_gpu.h" - - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - - -int gather_points_wrapper_fast(int b, int c, int n, int npoints, - at::Tensor points_tensor, at::Tensor idx_tensor, at::Tensor out_tensor){ - - CHECK_INPUT(points_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(out_tensor); - - const float* points = points_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - float* out = out_tensor.data_ptr(); - - gather_points_kernel_launcher_fast(b, c, n, npoints, points, idx, out); - return 1; -} - - -int gather_points_grad_wrapper_fast(int b, int c, int n, int npoints, - at::Tensor grad_out_tensor, at::Tensor idx_tensor, at::Tensor grad_points_tensor) { - - CHECK_INPUT(grad_out_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(grad_points_tensor); - - const float* grad_out = grad_out_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - float* grad_points = grad_points_tensor.data_ptr(); - - gather_points_grad_kernel_launcher_fast(b, c, n, npoints, grad_out, idx, grad_points); - return 1; -} - - -int furthest_point_sampling_wrapper(int b, int n, int m, - at::Tensor points_tensor, at::Tensor temp_tensor, at::Tensor idx_tensor) { - - CHECK_INPUT(points_tensor); - CHECK_INPUT(temp_tensor); - CHECK_INPUT(idx_tensor); - - const float* points = points_tensor.data_ptr(); - float* temp = temp_tensor.data_ptr(); - int* idx = idx_tensor.data_ptr(); - - furthest_point_sampling_kernel_launcher(b, n, m, points, temp, idx); - return 1; -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling_gpu.cu deleted file mode 100644 index 203bd825..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling_gpu.cu +++ /dev/null @@ -1,260 +0,0 @@ -/* -batch version of point sampling and gathering, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2018. -*/ - - -#include -#include - -#include "cuda_utils.h" -#include "sampling_gpu.h" - - -__global__ void gather_points_kernel_fast(int b, int c, int n, int m, - const float *__restrict__ points, const int *__restrict__ idx, float *__restrict__ out) { - // points: (B, C, N) - // idx: (B, M) - // output: - // out: (B, C, M) - - int bs_idx = blockIdx.z; - int c_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (bs_idx >= b || c_idx >= c || pt_idx >= m) return; - - out += bs_idx * c * m + c_idx * m + pt_idx; - idx += bs_idx * m + pt_idx; - points += bs_idx * c * n + c_idx * n; - out[0] = points[idx[0]]; -} - -void gather_points_kernel_launcher_fast(int b, int c, int n, int npoints, - const float *points, const int *idx, float *out) { - // points: (B, C, N) - // idx: (B, npoints) - // output: - // out: (B, C, npoints) - - cudaError_t err; - dim3 blocks(DIVUP(npoints, THREADS_PER_BLOCK), c, b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - gather_points_kernel_fast<<>>(b, c, n, npoints, points, idx, out); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - -__global__ void gather_points_grad_kernel_fast(int b, int c, int n, int m, const float *__restrict__ grad_out, - const int *__restrict__ idx, float *__restrict__ grad_points) { - // grad_out: (B, C, M) - // idx: (B, M) - // output: - // grad_points: (B, C, N) - - int bs_idx = blockIdx.z; - int c_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (bs_idx >= b || c_idx >= c || pt_idx >= m) return; - - grad_out += bs_idx * c * m + c_idx * m + pt_idx; - idx += bs_idx * m + pt_idx; - grad_points += bs_idx * c * n + c_idx * n; - - atomicAdd(grad_points + idx[0], grad_out[0]); -} - -void gather_points_grad_kernel_launcher_fast(int b, int c, int n, int npoints, - const float *grad_out, const int *idx, float *grad_points) { - // grad_out: (B, C, npoints) - // idx: (B, npoints) - // output: - // grad_points: (B, C, N) - - cudaError_t err; - dim3 blocks(DIVUP(npoints, THREADS_PER_BLOCK), c, b); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - gather_points_grad_kernel_fast<<>>(b, c, n, npoints, grad_out, idx, grad_points); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - - -__device__ void __update(float *__restrict__ dists, int *__restrict__ dists_i, int idx1, int idx2){ - const float v1 = dists[idx1], v2 = dists[idx2]; - const int i1 = dists_i[idx1], i2 = dists_i[idx2]; - dists[idx1] = max(v1, v2); - dists_i[idx1] = v2 > v1 ? i2 : i1; -} - -template -__global__ void furthest_point_sampling_kernel(int b, int n, int m, - const float *__restrict__ dataset, float *__restrict__ temp, int *__restrict__ idxs) { - // dataset: (B, N, 3) - // tmp: (B, N) - // output: - // idx: (B, M) - - if (m <= 0) return; - __shared__ float dists[block_size]; - __shared__ int dists_i[block_size]; - - int batch_index = blockIdx.x; - dataset += batch_index * n * 3; - temp += batch_index * n; - idxs += batch_index * m; - - int tid = threadIdx.x; - const int stride = block_size; - - int old = 0; - if (threadIdx.x == 0) - idxs[0] = old; - - __syncthreads(); - for (int j = 1; j < m; j++) { - int besti = 0; - float best = -1; - float x1 = dataset[old * 3 + 0]; - float y1 = dataset[old * 3 + 1]; - float z1 = dataset[old * 3 + 2]; - for (int k = tid; k < n; k += stride) { - float x2, y2, z2; - x2 = dataset[k * 3 + 0]; - y2 = dataset[k * 3 + 1]; - z2 = dataset[k * 3 + 2]; - // float mag = (x2 * x2) + (y2 * y2) + (z2 * z2); - // if (mag <= 1e-3) - // continue; - - float d = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1); - float d2 = min(d, temp[k]); - temp[k] = d2; - besti = d2 > best ? k : besti; - best = d2 > best ? d2 : best; - } - dists[tid] = best; - dists_i[tid] = besti; - __syncthreads(); - - if (block_size >= 1024) { - if (tid < 512) { - __update(dists, dists_i, tid, tid + 512); - } - __syncthreads(); - } - - if (block_size >= 512) { - if (tid < 256) { - __update(dists, dists_i, tid, tid + 256); - } - __syncthreads(); - } - if (block_size >= 256) { - if (tid < 128) { - __update(dists, dists_i, tid, tid + 128); - } - __syncthreads(); - } - if (block_size >= 128) { - if (tid < 64) { - __update(dists, dists_i, tid, tid + 64); - } - __syncthreads(); - } - if (block_size >= 64) { - if (tid < 32) { - __update(dists, dists_i, tid, tid + 32); - } - __syncthreads(); - } - if (block_size >= 32) { - if (tid < 16) { - __update(dists, dists_i, tid, tid + 16); - } - __syncthreads(); - } - if (block_size >= 16) { - if (tid < 8) { - __update(dists, dists_i, tid, tid + 8); - } - __syncthreads(); - } - if (block_size >= 8) { - if (tid < 4) { - __update(dists, dists_i, tid, tid + 4); - } - __syncthreads(); - } - if (block_size >= 4) { - if (tid < 2) { - __update(dists, dists_i, tid, tid + 2); - } - __syncthreads(); - } - if (block_size >= 2) { - if (tid < 1) { - __update(dists, dists_i, tid, tid + 1); - } - __syncthreads(); - } - - old = dists_i[0]; - if (tid == 0) - idxs[j] = old; - } -} - -void furthest_point_sampling_kernel_launcher(int b, int n, int m, - const float *dataset, float *temp, int *idxs) { - // dataset: (B, N, 3) - // tmp: (B, N) - // output: - // idx: (B, M) - - cudaError_t err; - unsigned int n_threads = opt_n_threads(n); - - switch (n_threads) { - case 1024: - furthest_point_sampling_kernel<1024><<>>(b, n, m, dataset, temp, idxs); break; - case 512: - furthest_point_sampling_kernel<512><<>>(b, n, m, dataset, temp, idxs); break; - case 256: - furthest_point_sampling_kernel<256><<>>(b, n, m, dataset, temp, idxs); break; - case 128: - furthest_point_sampling_kernel<128><<>>(b, n, m, dataset, temp, idxs); break; - case 64: - furthest_point_sampling_kernel<64><<>>(b, n, m, dataset, temp, idxs); break; - case 32: - furthest_point_sampling_kernel<32><<>>(b, n, m, dataset, temp, idxs); break; - case 16: - furthest_point_sampling_kernel<16><<>>(b, n, m, dataset, temp, idxs); break; - case 8: - furthest_point_sampling_kernel<8><<>>(b, n, m, dataset, temp, idxs); break; - case 4: - furthest_point_sampling_kernel<4><<>>(b, n, m, dataset, temp, idxs); break; - case 2: - furthest_point_sampling_kernel<2><<>>(b, n, m, dataset, temp, idxs); break; - case 1: - furthest_point_sampling_kernel<1><<>>(b, n, m, dataset, temp, idxs); break; - default: - furthest_point_sampling_kernel<512><<>>(b, n, m, dataset, temp, idxs); - } - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling_gpu.h deleted file mode 100644 index 034e04cb..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_batch/src/sampling_gpu.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _SAMPLING_GPU_H -#define _SAMPLING_GPU_H - -#include -#include -#include - - -int gather_points_wrapper_fast(int b, int c, int n, int npoints, - at::Tensor points_tensor, at::Tensor idx_tensor, at::Tensor out_tensor); - -void gather_points_kernel_launcher_fast(int b, int c, int n, int npoints, - const float *points, const int *idx, float *out); - - -int gather_points_grad_wrapper_fast(int b, int c, int n, int npoints, - at::Tensor grad_out_tensor, at::Tensor idx_tensor, at::Tensor grad_points_tensor); - -void gather_points_grad_kernel_launcher_fast(int b, int c, int n, int npoints, - const float *grad_out, const int *idx, float *grad_points); - - -int furthest_point_sampling_wrapper(int b, int n, int m, - at::Tensor points_tensor, at::Tensor temp_tensor, at::Tensor idx_tensor); - -void furthest_point_sampling_kernel_launcher(int b, int n, int m, - const float *dataset, float *temp, int *idxs); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/pointnet2_modules.py b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/pointnet2_modules.py deleted file mode 100644 index fa3d450d..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/pointnet2_modules.py +++ /dev/null @@ -1,80 +0,0 @@ -from typing import List - -import torch -import torch.nn as nn -import torch.nn.functional as F - -from opencood.pcdet_utils.pointnet2.pointnet2_stack import pointnet2_utils - - -class StackSAModuleMSG(nn.Module): - def __init__(self, *, radii: List[float], nsamples: List[int], mlps: List[List[int]], use_xyz: bool = True, pool_method="max_pool"): - """ - Args: - radii: list of float, list of radii to group with - nsamples: list of int, number of samples in each ball query - mlps: list of list of int, spec of the pointnet before the global pooling for each scale - use_xyz: - pool_method: max_pool / avg_pool - """ - super().__init__() - - assert len(radii) == len(nsamples) == len(mlps) - - self.groupers = nn.ModuleList() - self.mlps = nn.ModuleList() - for i in range(len(radii)): - radius = radii[i] - nsample = nsamples[i] - self.groupers.append(pointnet2_utils.QueryAndGroup(radius, nsample, use_xyz=use_xyz)) - mlp_spec = mlps[i] - if use_xyz: - mlp_spec[0] += 3 - - shared_mlps = [] - for k in range(len(mlp_spec) - 1): - shared_mlps.extend([nn.Conv2d(mlp_spec[k], mlp_spec[k + 1], kernel_size=1, bias=False), nn.BatchNorm2d(mlp_spec[k + 1]), nn.ReLU()]) - self.mlps.append(nn.Sequential(*shared_mlps)) - self.pool_method = pool_method - - self.init_weights() - - def init_weights(self): - for m in self.modules(): - if isinstance(m, nn.Conv2d): - nn.init.kaiming_normal_(m.weight) - if m.bias is not None: - nn.init.constant_(m.bias, 0) - if isinstance(m, nn.BatchNorm2d): - nn.init.constant_(m.weight, 1.0) - nn.init.constant_(m.bias, 0) - - def forward(self, xyz, xyz_batch_cnt, new_xyz, new_xyz_batch_cnt, features=None, empty_voxel_set_zeros=True): - r""" - :param xyz: (N1 + N2 ..., 3) tensor of the xyz coordinates of the features - :param xyz_batch_cnt: (batch_size), [N1, N2, ...] - :param new_xyz: (M1 + M2 ..., 3) - :param new_xyz_batch_cnt: (batch_size), [M1, M2, ...] - :param features: (N1 + N2 ..., C) tensor of the descriptors of the the features - :return: - new_xyz: (M1 + M2 ..., 3) tensor of the new features' xyz - new_features: (M1 + M2 ..., \sum_k(mlps[k][-1])) tensor of the new_features descriptors - """ - new_features_list = [] - for k in range(len(self.groupers)): - new_features, _ = self.groupers[k](xyz, xyz_batch_cnt, new_xyz, new_xyz_batch_cnt, features) # (M1 + M2, C, nsample) - new_features = new_features.permute(1, 0, 2).unsqueeze(dim=0) # (1, C, M1 + M2 ..., nsample) - new_features = self.mlps[k](new_features) # (1, C, M1 + M2 ..., nsample) - - if self.pool_method == "max_pool": - new_features = F.max_pool2d(new_features, kernel_size=[1, new_features.size(3)]).squeeze(dim=-1) # (1, C, M1 + M2 ...) - elif self.pool_method == "avg_pool": - new_features = F.avg_pool2d(new_features, kernel_size=[1, new_features.size(3)]).squeeze(dim=-1) # (1, C, M1 + M2 ...) - else: - raise NotImplementedError - new_features = new_features.squeeze(dim=0).permute(1, 0) # (M1 + M2 ..., C) - new_features_list.append(new_features) - - new_features = torch.cat(new_features_list, dim=1) # (M1 + M2 ..., C) - - return new_xyz, new_features diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/pointnet2_utils.py b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/pointnet2_utils.py deleted file mode 100644 index 637b2f71..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/pointnet2_utils.py +++ /dev/null @@ -1,176 +0,0 @@ -import torch -import torch.nn as nn -from torch.autograd import Function, Variable - -from opencood.pcdet_utils.pointnet2.pointnet2_stack import pointnet2_stack_cuda as pointnet2 - - -class BallQuery(Function): - @staticmethod - def forward(ctx, radius: float, nsample: int, xyz: torch.Tensor, xyz_batch_cnt: torch.Tensor, new_xyz: torch.Tensor, new_xyz_batch_cnt): - """ - Args: - ctx: - radius: float, radius of the balls - nsample: int, maximum number of features in the balls - xyz: (N1 + N2 ..., 3) xyz coordinates of the features - xyz_batch_cnt: (batch_size), [N1, N2, ...] - new_xyz: (M1 + M2 ..., 3) centers of the ball query - new_xyz_batch_cnt: (batch_size), [M1, M2, ...] - - Returns: - idx: (M1 + M2, nsample) tensor with the indicies of the features that form the query balls - """ - assert new_xyz.is_contiguous() - assert new_xyz_batch_cnt.is_contiguous() - assert xyz.is_contiguous() - assert xyz_batch_cnt.is_contiguous() - - B = xyz_batch_cnt.shape[0] - M = new_xyz.shape[0] - idx = torch.cuda.IntTensor(M, nsample).zero_() - - pointnet2.ball_query_wrapper(B, M, radius, nsample, new_xyz, new_xyz_batch_cnt, xyz, xyz_batch_cnt, idx) - empty_ball_mask = idx[:, 0] == -1 - idx[empty_ball_mask] = 0 - return idx, empty_ball_mask - - @staticmethod - def backward(ctx, a=None): - return None, None, None, None - - -ball_query = BallQuery.apply - - -class GroupingOperation(Function): - @staticmethod - def forward(ctx, features: torch.Tensor, features_batch_cnt: torch.Tensor, idx: torch.Tensor, idx_batch_cnt: torch.Tensor): - """ - Args: - ctx: - features: (N1 + N2 ..., C) tensor of features to group - features_batch_cnt: (batch_size) [N1 + N2 ...] tensor containing the indicies of features to group with - idx: (M1 + M2 ..., nsample) tensor containing the indicies of features to group with - idx_batch_cnt: (batch_size) [M1 + M2 ...] tensor containing the indicies of features to group with - - Returns: - output: (M1 + M2, C, nsample) tensor - """ - assert features.is_contiguous() - assert features_batch_cnt.is_contiguous() - assert idx.is_contiguous() - assert idx_batch_cnt.is_contiguous() - - assert features.shape[0] == features_batch_cnt.sum(), "features: %s, features_batch_cnt: %s" % (str(features.shape), str(features_batch_cnt)) - assert idx.shape[0] == idx_batch_cnt.sum(), "idx: %s, idx_batch_cnt: %s" % (str(idx.shape), str(idx_batch_cnt)) - - M, nsample = idx.size() - N, C = features.size() - B = idx_batch_cnt.shape[0] - output = torch.cuda.FloatTensor(M, C, nsample) - - pointnet2.group_points_wrapper(B, M, C, nsample, features, features_batch_cnt, idx, idx_batch_cnt, output) - - ctx.for_backwards = (B, N, idx, features_batch_cnt, idx_batch_cnt) - return output - - @staticmethod - def backward(ctx, grad_out: torch.Tensor): - """ - Args: - ctx: - grad_out: (M1 + M2 ..., C, nsample) tensor of the gradients of the output from forward - - Returns: - grad_features: (N1 + N2 ..., C) gradient of the features - """ - B, N, idx, features_batch_cnt, idx_batch_cnt = ctx.for_backwards - - M, C, nsample = grad_out.size() - grad_features = Variable(torch.cuda.FloatTensor(N, C).zero_()) - - grad_out_data = grad_out.data.contiguous() - pointnet2.group_points_grad_wrapper(B, M, C, N, nsample, grad_out_data, idx, idx_batch_cnt, features_batch_cnt, grad_features.data) - return grad_features, None, None, None - - -grouping_operation = GroupingOperation.apply - - -class QueryAndGroup(nn.Module): - def __init__(self, radius: float, nsample: int, use_xyz: bool = True): - """ - Args: - radius: float, radius of ball - nsample: int, maximum number of features to gather in the ball - use_xyz: - """ - super().__init__() - self.radius, self.nsample, self.use_xyz = radius, nsample, use_xyz - - def forward( - self, xyz: torch.Tensor, xyz_batch_cnt: torch.Tensor, new_xyz: torch.Tensor, new_xyz_batch_cnt: torch.Tensor, features: torch.Tensor = None - ): - """ - Args: - xyz: (N1 + N2 ..., 3) xyz coordinates of the features - xyz_batch_cnt: (batch_size), [N1, N2, ...] - new_xyz: (M1 + M2 ..., 3) centers of the ball query - new_xyz_batch_cnt: (batch_size), [M1, M2, ...] - features: (N1 + N2 ..., C) tensor of features to group - - Returns: - new_features: (M1 + M2, C, nsample) tensor - """ - assert xyz.shape[0] == xyz_batch_cnt.sum(), "xyz: %s, xyz_batch_cnt: %s" % (str(xyz.shape), str(new_xyz_batch_cnt)) - assert new_xyz.shape[0] == new_xyz_batch_cnt.sum(), "new_xyz: %s, new_xyz_batch_cnt: %s" % (str(new_xyz.shape), str(new_xyz_batch_cnt)) - - # idx: (M1 + M2 ..., nsample), empty_ball_mask: (M1 + M2 ...) - idx, empty_ball_mask = ball_query(self.radius, self.nsample, xyz, xyz_batch_cnt, new_xyz, new_xyz_batch_cnt) - grouped_xyz = grouping_operation(xyz, xyz_batch_cnt, idx, new_xyz_batch_cnt) # (M1 + M2, 3, nsample) - grouped_xyz -= new_xyz.unsqueeze(-1) - - grouped_xyz[empty_ball_mask] = 0 - - if features is not None: - grouped_features = grouping_operation(features, xyz_batch_cnt, idx, new_xyz_batch_cnt) # (M1 + M2, C, nsample) - grouped_features[empty_ball_mask] = 0 - if self.use_xyz: - new_features = torch.cat([grouped_xyz, grouped_features], dim=1) # (M1 + M2 ..., C + 3, nsample) - else: - new_features = grouped_features - else: - assert self.use_xyz, "Cannot have not features and not use xyz as a feature!" - new_features = grouped_xyz - - return new_features, idx - - -class FurthestPointSampling(Function): - @staticmethod - def forward(ctx, xyz: torch.Tensor, npoint: int): - """ - Args: - ctx: - xyz: (B, N, 3) where N > npoint - npoint: int, number of features in the sampled set - - Returns: - output: (B, npoint) tensor containing the set - """ - assert xyz.is_contiguous() - - B, N, _ = xyz.size() - output = torch.cuda.IntTensor(B, npoint) - temp = torch.cuda.FloatTensor(B, N).fill_(1e10) - - pointnet2.furthest_point_sampling_wrapper(B, N, npoint, xyz, temp, output) - return output - - @staticmethod - def backward(xyz, a=None): - return None, None - - -furthest_point_sample = FurthestPointSampling.apply diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query.cpp deleted file mode 100644 index 2fcb5baa..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* -Stacked-batch-data version of ball query, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - -#include -#include -#include -#include -#include "ball_query_gpu.h" - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - -int ball_query_wrapper_stack(int B, int M, float radius, int nsample, - at::Tensor new_xyz_tensor, at::Tensor new_xyz_batch_cnt_tensor, - at::Tensor xyz_tensor, at::Tensor xyz_batch_cnt_tensor, at::Tensor idx_tensor) { - - CHECK_INPUT(new_xyz_tensor); - CHECK_INPUT(xyz_tensor); - CHECK_INPUT(new_xyz_batch_cnt_tensor); - CHECK_INPUT(xyz_batch_cnt_tensor); - CHECK_INPUT(idx_tensor); - - const float* new_xyz = new_xyz_tensor.data_ptr(); - const float* xyz = xyz_tensor.data_ptr(); - const int* new_xyz_batch_cnt = new_xyz_batch_cnt_tensor.data_ptr(); - const int* xyz_batch_cnt = xyz_batch_cnt_tensor.data_ptr(); - int* idx = idx_tensor.data_ptr(); - - ball_query_kernel_launcher_stack(B, M, radius, nsample, new_xyz, new_xyz_batch_cnt, xyz, xyz_batch_cnt, idx); - return 1; -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query_gpu.cu deleted file mode 100644 index adaa6b1e..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query_gpu.cu +++ /dev/null @@ -1,90 +0,0 @@ -/* -Stacked-batch-data version of ball query, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#include -#include -#include - -#include "ball_query_gpu.h" -#include "cuda_utils.h" - - -__global__ void ball_query_kernel_stack(int B, int M, float radius, int nsample, \ - const float *new_xyz, const int *new_xyz_batch_cnt, const float *xyz, const int *xyz_batch_cnt, int *idx) { - // :param xyz: (N1 + N2 ..., 3) xyz coordinates of the features - // :param xyz_batch_cnt: (batch_size), [N1, N2, ...] - // :param new_xyz: (M1 + M2 ..., 3) centers of the ball query - // :param new_xyz_batch_cnt: (batch_size), [M1, M2, ...] - // output: - // idx: (M, nsample) - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (pt_idx >= M) return; - - int bs_idx = 0, pt_cnt = new_xyz_batch_cnt[0]; - for (int k = 1; k < B; k++){ - if (pt_idx < pt_cnt) break; - pt_cnt += new_xyz_batch_cnt[k]; - bs_idx = k; - } - - int xyz_batch_start_idx = 0; - for (int k = 0; k < bs_idx; k++) xyz_batch_start_idx += xyz_batch_cnt[k]; - // for (int k = 0; k < bs_idx; k++) new_xyz_batch_start_idx += new_xyz_batch_cnt[k]; - - new_xyz += pt_idx * 3; - xyz += xyz_batch_start_idx * 3; - idx += pt_idx * nsample; - - float radius2 = radius * radius; - float new_x = new_xyz[0]; - float new_y = new_xyz[1]; - float new_z = new_xyz[2]; - int n = xyz_batch_cnt[bs_idx]; - - int cnt = 0; - for (int k = 0; k < n; ++k) { - float x = xyz[k * 3 + 0]; - float y = xyz[k * 3 + 1]; - float z = xyz[k * 3 + 2]; - float d2 = (new_x - x) * (new_x - x) + (new_y - y) * (new_y - y) + (new_z - z) * (new_z - z); - if (d2 < radius2){ - if (cnt == 0){ - for (int l = 0; l < nsample; ++l) { - idx[l] = k; - } - } - idx[cnt] = k; - ++cnt; - if (cnt >= nsample) break; - } - } - if (cnt == 0) idx[0] = -1; -} - - -void ball_query_kernel_launcher_stack(int B, int M, float radius, int nsample, - const float *new_xyz, const int *new_xyz_batch_cnt, const float *xyz, const int *xyz_batch_cnt, int *idx){ - // :param xyz: (N1 + N2 ..., 3) xyz coordinates of the features - // :param xyz_batch_cnt: (batch_size), [N1, N2, ...] - // :param new_xyz: (M1 + M2 ..., 3) centers of the ball query - // :param new_xyz_batch_cnt: (batch_size), [M1, M2, ...] - // output: - // idx: (M, nsample) - - cudaError_t err; - - dim3 blocks(DIVUP(M, THREADS_PER_BLOCK)); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - ball_query_kernel_stack<<>>(B, M, radius, nsample, new_xyz, new_xyz_batch_cnt, xyz, xyz_batch_cnt, idx); - // cudaDeviceSynchronize(); // for using printf in kernel function - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query_gpu.h deleted file mode 100644 index c74f1201..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/ball_query_gpu.h +++ /dev/null @@ -1,25 +0,0 @@ -/* -Stacked-batch-data version of ball query, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#ifndef _STACK_BALL_QUERY_GPU_H -#define _STACK_BALL_QUERY_GPU_H - -#include -#include -#include -#include - -int ball_query_wrapper_stack(int B, int M, float radius, int nsample, - at::Tensor new_xyz_tensor, at::Tensor new_xyz_batch_cnt_tensor, - at::Tensor xyz_tensor, at::Tensor xyz_batch_cnt_tensor, at::Tensor idx_tensor); - - -void ball_query_kernel_launcher_stack(int B, int M, float radius, int nsample, - const float *new_xyz, const int *new_xyz_batch_cnt, const float *xyz, const int *xyz_batch_cnt, int *idx); - - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/cuda_utils.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/cuda_utils.h deleted file mode 100644 index c1670f1c..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/cuda_utils.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _STACK_CUDA_UTILS_H -#define _STACK_CUDA_UTILS_H - -#include - -#define THREADS_PER_BLOCK 256 -#define DIVUP(m,n) ((m) / (n) + ((m) % (n) > 0)) - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points.cpp deleted file mode 100644 index d6ebd0dc..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* -Stacked-batch-data version of point grouping, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - -#include -#include -#include -#include - -#include "group_points_gpu.h" - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - - -int group_points_grad_wrapper_stack(int B, int M, int C, int N, int nsample, - at::Tensor grad_out_tensor, at::Tensor idx_tensor, at::Tensor idx_batch_cnt_tensor, - at::Tensor features_batch_cnt_tensor, at::Tensor grad_features_tensor) { - - CHECK_INPUT(grad_out_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(idx_batch_cnt_tensor); - CHECK_INPUT(features_batch_cnt_tensor); - CHECK_INPUT(grad_features_tensor); - - const float* grad_out = grad_out_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - const int* idx_batch_cnt = idx_batch_cnt_tensor.data_ptr(); - const int* features_batch_cnt = features_batch_cnt_tensor.data_ptr(); - float* grad_features = grad_features_tensor.data_ptr(); - - group_points_grad_kernel_launcher_stack(B, M, C, N, nsample, grad_out, idx, idx_batch_cnt, features_batch_cnt, grad_features); - return 1; -} - - -int group_points_wrapper_stack(int B, int M, int C, int nsample, - at::Tensor features_tensor, at::Tensor features_batch_cnt_tensor, - at::Tensor idx_tensor, at::Tensor idx_batch_cnt_tensor, at::Tensor out_tensor) { - - CHECK_INPUT(features_tensor); - CHECK_INPUT(features_batch_cnt_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(idx_batch_cnt_tensor); - CHECK_INPUT(out_tensor); - - const float* features = features_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - const int* features_batch_cnt = features_batch_cnt_tensor.data_ptr(); - const int* idx_batch_cnt = idx_batch_cnt_tensor.data_ptr(); - float* out = out_tensor.data_ptr(); - - group_points_kernel_launcher_stack(B, M, C, nsample, features, features_batch_cnt, idx, idx_batch_cnt, out); - return 1; -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points_gpu.cu deleted file mode 100644 index 62e341e9..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points_gpu.cu +++ /dev/null @@ -1,125 +0,0 @@ -/* -Stacked-batch-data version of point grouping, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#include -#include - -#include "cuda_utils.h" -#include "group_points_gpu.h" - - -__global__ void group_points_grad_kernel_stack(int B, int M, int C, int N, int nsample, - const float *grad_out, const int *idx, const int *idx_batch_cnt, const int *features_batch_cnt, float *grad_features) { - // :param grad_out: (M1 + M2 ..., C, nsample) tensor of the gradients of the output from forward - // :param idx: (M1 + M2 ..., nsample) tensor containing the indicies of features to group with - // :param idx_batch_cnt: (batch_size) [M1 + M2 ...] tensor containing the indicies of features to group with - // :param features_batch_cnt: (batch_size) [N1 + N2 ...] tensor containing the indicies of features to group with - // :return: - // grad_features: (N1 + N2 ..., C) gradient of the features - int index = blockIdx.x * blockDim.x + threadIdx.x; - int sample_idx = index % nsample; - int C_idx = (index / nsample) % C; - int pt_idx = (index / nsample / C); - - if (pt_idx >= M || C_idx >= C || sample_idx >= nsample) return; - - int bs_idx = 0, pt_cnt = idx_batch_cnt[0]; - for (int k = 1; k < B; k++){ - if (pt_idx < pt_cnt) break; - pt_cnt += idx_batch_cnt[k]; - bs_idx = k; - } - - int features_batch_start_idx = 0; - for (int k = 0; k < bs_idx; k++) features_batch_start_idx += features_batch_cnt[k]; - - grad_out += pt_idx * C * nsample + C_idx * nsample + sample_idx; - idx += pt_idx * nsample + sample_idx; - grad_features += (features_batch_start_idx + idx[0]) * C + C_idx; - - atomicAdd(grad_features, grad_out[0]); -} - -void group_points_grad_kernel_launcher_stack(int B, int M, int C, int N, int nsample, - const float *grad_out, const int *idx, const int *idx_batch_cnt, const int *features_batch_cnt, float *grad_features) { - // :param grad_out: (M1 + M2 ..., C, nsample) tensor of the gradients of the output from forward - // :param idx: (M1 + M2 ..., nsample) tensor containing the indicies of features to group with - // :param idx_batch_cnt: (batch_size) [M1 + M2 ...] tensor containing the indicies of features to group with - // :param features_batch_cnt: (batch_size) [N1 + N2 ...] tensor containing the indicies of features to group with - // :return: - // grad_features: (N1 + N2 ..., C) gradient of the features - - cudaError_t err; - // dim3 blocks(DIVUP(npoints * nsample, THREADS_PER_BLOCK), c, b); // blockIdx.x(col), blockIdx.y(row) - dim3 blocks(DIVUP(M * C * nsample, THREADS_PER_BLOCK)); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - group_points_grad_kernel_stack<<>>(B, M, C, N, nsample, grad_out, idx, idx_batch_cnt, features_batch_cnt, grad_features); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - - -__global__ void group_points_kernel_stack(int B, int M, int C, int nsample, - const float *features, const int *features_batch_cnt, const int *idx, const int *idx_batch_cnt, float *out) { - // :param features: (N1 + N2 ..., C) tensor of features to group - // :param features_batch_cnt: (batch_size) [N1 + N2 ...] tensor containing the indicies of features to group with - // :param idx: (M1 + M2 ..., nsample) tensor containing the indicies of features to group with - // :param idx_batch_cnt: (batch_size) [M1 + M2 ...] tensor containing the indicies of features to group with - // :return: - // output: (M1 + M2, C, nsample) tensor - int index = blockIdx.x * blockDim.x + threadIdx.x; - int sample_idx = index % nsample; - int C_idx = (index / nsample) % C; - int pt_idx = (index / nsample / C); - - if (pt_idx >= M || C_idx >= C || sample_idx >= nsample) return; - - int bs_idx = 0, pt_cnt = idx_batch_cnt[0]; - for (int k = 1; k < B; k++){ - if (pt_idx < pt_cnt) break; - pt_cnt += idx_batch_cnt[k]; - bs_idx = k; - } - - int features_batch_start_idx = 0; - for (int k = 0; k < bs_idx; k++) features_batch_start_idx += features_batch_cnt[k]; - features += features_batch_start_idx * C; - - idx += pt_idx * nsample + sample_idx; - int in_idx = idx[0] * C + C_idx; - int out_idx = pt_idx * C * nsample + C_idx * nsample + sample_idx; - - out[out_idx] = features[in_idx]; -} - - -void group_points_kernel_launcher_stack(int B, int M, int C, int nsample, - const float *features, const int *features_batch_cnt, const int *idx, const int *idx_batch_cnt, float *out) { - // :param features: (N1 + N2 ..., C) tensor of features to group - // :param features_batch_cnt: (batch_size) [N1 + N2 ...] tensor containing the indicies of features to group with - // :param idx: (M1 + M2 ..., nsample) tensor containing the indicies of features to group with - // :param idx_batch_cnt: (batch_size) [M1 + M2 ...] tensor containing the indicies of features to group with - // :return: - // output: (M1 + M2, C, nsample) tensor - - cudaError_t err; - dim3 blocks(DIVUP(M * C * nsample, THREADS_PER_BLOCK)); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - group_points_kernel_stack<<>>(B, M, C, nsample, features, features_batch_cnt, idx, idx_batch_cnt, out); - // cudaDeviceSynchronize(); // for using printf in kernel function - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points_gpu.h deleted file mode 100644 index 4a266216..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/group_points_gpu.h +++ /dev/null @@ -1,31 +0,0 @@ -/* -Stacked-batch-data version of point grouping, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#ifndef _STACK_GROUP_POINTS_GPU_H -#define _STACK_GROUP_POINTS_GPU_H - -#include -#include -#include -#include - - -int group_points_wrapper_stack(int B, int M, int C, int nsample, - at::Tensor features_tensor, at::Tensor features_batch_cnt_tensor, - at::Tensor idx_tensor, at::Tensor idx_batch_cnt_tensor, at::Tensor out_tensor); - -void group_points_kernel_launcher_stack(int B, int M, int C, int nsample, - const float *features, const int *features_batch_cnt, const int *idx, const int *idx_batch_cnt, float *out); - -int group_points_grad_wrapper_stack(int B, int M, int C, int N, int nsample, - at::Tensor grad_out_tensor, at::Tensor idx_tensor, at::Tensor idx_batch_cnt_tensor, - at::Tensor features_batch_cnt_tensor, at::Tensor grad_features_tensor); - -void group_points_grad_kernel_launcher_stack(int B, int M, int C, int N, int nsample, - const float *grad_out, const int *idx, const int *idx_batch_cnt, const int *features_batch_cnt, float *grad_features); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate.cpp deleted file mode 100644 index faf78baf..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* -Stacked-batch-data version of point interpolation, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include "interpolate_gpu.h" - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - - -void three_nn_wrapper_stack(at::Tensor unknown_tensor, - at::Tensor unknown_batch_cnt_tensor, at::Tensor known_tensor, - at::Tensor known_batch_cnt_tensor, at::Tensor dist2_tensor, at::Tensor idx_tensor) { - - CHECK_INPUT(unknown_tensor); - CHECK_INPUT(unknown_batch_cnt_tensor); - CHECK_INPUT(known_tensor); - CHECK_INPUT(known_batch_cnt_tensor); - CHECK_INPUT(dist2_tensor); - CHECK_INPUT(idx_tensor); - - int batch_size = unknown_batch_cnt_tensor.size(0); - int N = unknown_tensor.size(0); - int M = known_tensor.size(0); - - const float* unknown = unknown_tensor.data_ptr(); - const int* unknown_batch_cnt = unknown_batch_cnt_tensor.data_ptr(); - const float* known = known_tensor.data_ptr(); - const int* known_batch_cnt = known_batch_cnt_tensor.data_ptr(); - float* dist2 = dist2_tensor.data_ptr(); - int* idx = idx_tensor.data_ptr(); - - three_nn_kernel_launcher_stack(batch_size, N, M, unknown, unknown_batch_cnt, known, known_batch_cnt, dist2, idx); -} - - -void three_interpolate_wrapper_stack(at::Tensor features_tensor, - at::Tensor idx_tensor, at::Tensor weight_tensor, at::Tensor out_tensor) { - - CHECK_INPUT(features_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(weight_tensor); - CHECK_INPUT(out_tensor); - - int N = out_tensor.size(0); - int channels = features_tensor.size(1); - - const float* features = features_tensor.data_ptr(); - const float* weight = weight_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - float* out = out_tensor.data_ptr(); - - three_interpolate_kernel_launcher_stack(N, channels, features, idx, weight, out); -} - - -void three_interpolate_grad_wrapper_stack(at::Tensor grad_out_tensor, at::Tensor idx_tensor, - at::Tensor weight_tensor, at::Tensor grad_features_tensor) { - - CHECK_INPUT(grad_out_tensor); - CHECK_INPUT(idx_tensor); - CHECK_INPUT(weight_tensor); - CHECK_INPUT(grad_features_tensor); - - int N = grad_out_tensor.size(0); - int channels = grad_out_tensor.size(1); - - const float* grad_out = grad_out_tensor.data_ptr(); - const float* weight = weight_tensor.data_ptr(); - const int* idx = idx_tensor.data_ptr(); - float* grad_features = grad_features_tensor.data_ptr(); - - three_interpolate_grad_kernel_launcher_stack(N, channels, grad_out, idx, weight, grad_features); -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate_gpu.cu deleted file mode 100644 index 3a0eb4e8..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate_gpu.cu +++ /dev/null @@ -1,195 +0,0 @@ -/* -Stacked-batch-data version of point interpolation, modified from the original implementation of official PointNet++ codes. -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#include -#include -#include - -#include "cuda_utils.h" -#include "interpolate_gpu.h" - - -__global__ void three_nn_kernel_stack(int batch_size, int N, int M, const float *unknown, - const int *unknown_batch_cnt, const float *known, const int *known_batch_cnt, - float *dist2, int *idx) { - // unknown: (N1 + N2 ..., 3) - // unknown_batch_cnt: (batch_size), [N1, N2, ...] - // known: (M1 + M2 ..., 3) - // known_batch_cnt: (batch_size), [M1, M2, ...] - // Return: - // dist: (N1 + N2 ..., 3) l2 distance to the three nearest neighbors - // idx: (N1 + N2 ..., 3) index of the three nearest neighbors - - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (pt_idx >= N) return; - - int bs_idx = 0, pt_cnt = unknown_batch_cnt[0]; - for (int k = 1; k < batch_size; k++){ - if (pt_idx < pt_cnt) break; - pt_cnt += unknown_batch_cnt[k]; - bs_idx = k; - } - - int cur_num_known_points = known_batch_cnt[bs_idx]; - - int known_batch_start_idx = 0; - for (int k = 0; k < bs_idx; k++) known_batch_start_idx += known_batch_cnt[k]; - - known += known_batch_start_idx * 3; - unknown += pt_idx * 3; - dist2 += pt_idx * 3; - idx += pt_idx * 3; - - float ux = unknown[0]; - float uy = unknown[1]; - float uz = unknown[2]; - - double best1 = 1e40, best2 = 1e40, best3 = 1e40; - int besti1 = 0, besti2 = 0, besti3 = 0; - for (int k = 0; k < cur_num_known_points; ++k) { - float x = known[k * 3 + 0]; - float y = known[k * 3 + 1]; - float z = known[k * 3 + 2]; - float d = (ux - x) * (ux - x) + (uy - y) * (uy - y) + (uz - z) * (uz - z); - if (d < best1) { - best3 = best2; besti3 = besti2; - best2 = best1; besti2 = besti1; - best1 = d; besti1 = k; - } - else if (d < best2) { - best3 = best2; besti3 = besti2; - best2 = d; besti2 = k; - } - else if (d < best3) { - best3 = d; besti3 = k; - } - } - dist2[0] = best1; dist2[1] = best2; dist2[2] = best3; - idx[0] = besti1 + known_batch_start_idx; - idx[1] = besti2 + known_batch_start_idx; - idx[2] = besti3 + known_batch_start_idx; -} - - -void three_nn_kernel_launcher_stack(int batch_size, int N, int M, const float *unknown, - const int *unknown_batch_cnt, const float *known, const int *known_batch_cnt, - float *dist2, int *idx) { - // unknown: (N1 + N2 ..., 3) - // unknown_batch_cnt: (batch_size), [N1, N2, ...] - // known: (M1 + M2 ..., 3) - // known_batch_cnt: (batch_size), [M1, M2, ...] - // Return: - // dist: (N1 + N2 ..., 3) l2 distance to the three nearest neighbors - // idx: (N1 + N2 ..., 3) index of the three nearest neighbors - - cudaError_t err; - dim3 blocks(DIVUP(N, THREADS_PER_BLOCK)); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - - three_nn_kernel_stack<<>>( - batch_size, N, M, unknown, unknown_batch_cnt, - known, known_batch_cnt, dist2, idx - ); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - - - -__global__ void three_interpolate_kernel_stack(int N, int channels, const float *features, - const int *idx, const float *weight, float *out) { - // features: (M1 + M2 ..., C) - // idx: [N1 + N2 ..., 3] - // weight: [N1 + N2 ..., 3] - // Return: - // out: (N1 + N2 ..., C) - - int c_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (pt_idx >= N || c_idx >= channels) return; - - weight += pt_idx * 3; - idx += pt_idx * 3; - out += pt_idx * channels + c_idx; - - out[0] = weight[0] * features[idx[0] * channels + c_idx] + - weight[1] * features[idx[1] * channels + c_idx] + - weight[2] * features[idx[2] * channels + c_idx]; -} - - - -void three_interpolate_kernel_launcher_stack(int N, int channels, - const float *features, const int *idx, const float *weight, float *out) { - // features: (M1 + M2 ..., C) - // idx: [N1 + N2 ..., 3] - // weight: [N1 + N2 ..., 3] - // Return: - // out: (N1 + N2 ..., C) - - cudaError_t err; - dim3 blocks(DIVUP(N, THREADS_PER_BLOCK), channels); - dim3 threads(THREADS_PER_BLOCK); - three_interpolate_kernel_stack<<>>(N, channels, features, idx, weight, out); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} - - -__global__ void three_interpolate_grad_kernel_stack(int N, int channels, const float *grad_out, - const int *idx, const float *weight, float *grad_features) { - // grad_out_tensor: (N1 + N2 ..., C) - // idx_tensor: [N1 + N2 ..., 3] - // weight_tensor: [N1 + N2 ..., 3] - // Return: - // grad_features_tensor: (M1 + M2 ..., C) - - int c_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (pt_idx >= N || c_idx >= channels) return; - - grad_out += pt_idx * channels + c_idx; - weight += pt_idx * 3; - idx += pt_idx * 3; - - // printf("pt_idx=%d, c_idx=%d, idx=(%d, %d, %d), grad_out=%f\n", pt_idx, c_idx, idx[0], idx[1], idx[2], grad_out[0]); - - atomicAdd(grad_features + idx[0] * channels + c_idx, grad_out[0] * weight[0]); - atomicAdd(grad_features + idx[1] * channels + c_idx, grad_out[0] * weight[1]); - atomicAdd(grad_features + idx[2] * channels + c_idx, grad_out[0] * weight[2]); -} - - -void three_interpolate_grad_kernel_launcher_stack(int N, int channels, const float *grad_out, - const int *idx, const float *weight, float *grad_features) { - // grad_out_tensor: (N1 + N2 ..., C) - // idx_tensor: [N1 + N2 ..., 3] - // weight_tensor: [N1 + N2 ..., 3] - // Return: - // grad_features_tensor: (M1 + M2 ..., C) - - cudaError_t err; - dim3 blocks(DIVUP(N, THREADS_PER_BLOCK), channels); // blockIdx.x(col), blockIdx.y(row) - dim3 threads(THREADS_PER_BLOCK); - three_interpolate_grad_kernel_stack<<>>( - N, channels, grad_out, idx, weight, grad_features - ); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate_gpu.h deleted file mode 100644 index 016c7886..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/interpolate_gpu.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef _INTERPOLATE_GPU_H -#define _INTERPOLATE_GPU_H - -#include -#include -#include -#include - - -void three_nn_wrapper_stack(at::Tensor unknown_tensor, - at::Tensor unknown_batch_cnt_tensor, at::Tensor known_tensor, - at::Tensor known_batch_cnt_tensor, at::Tensor dist2_tensor, at::Tensor idx_tensor); - - -void three_interpolate_wrapper_stack(at::Tensor features_tensor, - at::Tensor idx_tensor, at::Tensor weight_tensor, at::Tensor out_tensor); - - - -void three_interpolate_grad_wrapper_stack(at::Tensor grad_out_tensor, at::Tensor idx_tensor, - at::Tensor weight_tensor, at::Tensor grad_features_tensor); - - -void three_nn_kernel_launcher_stack(int batch_size, int N, int M, const float *unknown, - const int *unknown_batch_cnt, const float *known, const int *known_batch_cnt, - float *dist2, int *idx); - - -void three_interpolate_kernel_launcher_stack(int N, int channels, - const float *features, const int *idx, const float *weight, float *out); - - - -void three_interpolate_grad_kernel_launcher_stack(int N, int channels, const float *grad_out, - const int *idx, const float *weight, float *grad_features); - - - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/pointnet2_api.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/pointnet2_api.cpp deleted file mode 100644 index 591f11a7..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/pointnet2_api.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -#include "ball_query_gpu.h" -#include "group_points_gpu.h" -#include "sampling_gpu.h" -#include "interpolate_gpu.h" - - -PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { - m.def("ball_query_wrapper", &ball_query_wrapper_stack, "ball_query_wrapper_stack"); - - m.def("furthest_point_sampling_wrapper", &furthest_point_sampling_wrapper, "furthest_point_sampling_wrapper"); - - m.def("group_points_wrapper", &group_points_wrapper_stack, "group_points_wrapper_stack"); - m.def("group_points_grad_wrapper", &group_points_grad_wrapper_stack, "group_points_grad_wrapper_stack"); - - m.def("three_nn_wrapper", &three_nn_wrapper_stack, "three_nn_wrapper_stack"); - m.def("three_interpolate_wrapper", &three_interpolate_wrapper_stack, "three_interpolate_wrapper_stack"); - m.def("three_interpolate_grad_wrapper", &three_interpolate_grad_wrapper_stack, "three_interpolate_grad_wrapper_stack"); -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling.cpp b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling.cpp deleted file mode 100644 index 6987cae0..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include -#include "sampling_gpu.h" - - -#define CHECK_CUDA(x) TORCH_CHECK(x.is_cuda(), #x " must be a CUDA tensor") -#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous") -#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x) - -int furthest_point_sampling_wrapper(int b, int n, int m, - at::Tensor points_tensor, at::Tensor temp_tensor, at::Tensor idx_tensor) { - - CHECK_INPUT(points_tensor); - CHECK_INPUT(temp_tensor); - CHECK_INPUT(idx_tensor); - - const float* points = points_tensor.data_ptr(); - float* temp = temp_tensor.data_ptr(); - int* idx = idx_tensor.data_ptr(); - - furthest_point_sampling_kernel_launcher(b, n, m, points, temp, idx); - return 1; -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling_gpu.cu b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling_gpu.cu deleted file mode 100644 index 02e8f3bf..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling_gpu.cu +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include - -#include "cuda_utils.h" -#include "sampling_gpu.h" -#define TOTAL_THREADS 1024 - - -inline int opt_n_threads(int work_size) { - const int pow_2 = std::log(static_cast(work_size)) / std::log(2.0); - - return max(min(1 << pow_2, TOTAL_THREADS), 1); -} - - -__device__ void __update(float *__restrict__ dists, int *__restrict__ dists_i, int idx1, int idx2){ - const float v1 = dists[idx1], v2 = dists[idx2]; - const int i1 = dists_i[idx1], i2 = dists_i[idx2]; - dists[idx1] = max(v1, v2); - dists_i[idx1] = v2 > v1 ? i2 : i1; -} - - -template -__global__ void furthest_point_sampling_kernel(int b, int n, int m, - const float *__restrict__ dataset, float *__restrict__ temp, int *__restrict__ idxs) { - // dataset: (B, N, 3) - // tmp: (B, N) - // output: - // idx: (B, M) - - if (m <= 0) return; - __shared__ float dists[block_size]; - __shared__ int dists_i[block_size]; - - int batch_index = blockIdx.x; - dataset += batch_index * n * 3; - temp += batch_index * n; - idxs += batch_index * m; - - int tid = threadIdx.x; - const int stride = block_size; - - int old = 0; - if (threadIdx.x == 0) - idxs[0] = old; - - __syncthreads(); - for (int j = 1; j < m; j++) { - int besti = 0; - float best = -1; - float x1 = dataset[old * 3 + 0]; - float y1 = dataset[old * 3 + 1]; - float z1 = dataset[old * 3 + 2]; - for (int k = tid; k < n; k += stride) { - float x2, y2, z2; - x2 = dataset[k * 3 + 0]; - y2 = dataset[k * 3 + 1]; - z2 = dataset[k * 3 + 2]; - // float mag = (x2 * x2) + (y2 * y2) + (z2 * z2); - // if (mag <= 1e-3) - // continue; - - float d = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1); - float d2 = min(d, temp[k]); - temp[k] = d2; - besti = d2 > best ? k : besti; - best = d2 > best ? d2 : best; - } - dists[tid] = best; - dists_i[tid] = besti; - __syncthreads(); - - if (block_size >= 1024) { - if (tid < 512) { - __update(dists, dists_i, tid, tid + 512); - } - __syncthreads(); - } - - if (block_size >= 512) { - if (tid < 256) { - __update(dists, dists_i, tid, tid + 256); - } - __syncthreads(); - } - if (block_size >= 256) { - if (tid < 128) { - __update(dists, dists_i, tid, tid + 128); - } - __syncthreads(); - } - if (block_size >= 128) { - if (tid < 64) { - __update(dists, dists_i, tid, tid + 64); - } - __syncthreads(); - } - if (block_size >= 64) { - if (tid < 32) { - __update(dists, dists_i, tid, tid + 32); - } - __syncthreads(); - } - if (block_size >= 32) { - if (tid < 16) { - __update(dists, dists_i, tid, tid + 16); - } - __syncthreads(); - } - if (block_size >= 16) { - if (tid < 8) { - __update(dists, dists_i, tid, tid + 8); - } - __syncthreads(); - } - if (block_size >= 8) { - if (tid < 4) { - __update(dists, dists_i, tid, tid + 4); - } - __syncthreads(); - } - if (block_size >= 4) { - if (tid < 2) { - __update(dists, dists_i, tid, tid + 2); - } - __syncthreads(); - } - if (block_size >= 2) { - if (tid < 1) { - __update(dists, dists_i, tid, tid + 1); - } - __syncthreads(); - } - - old = dists_i[0]; - if (tid == 0) - idxs[j] = old; - } -} - -void furthest_point_sampling_kernel_launcher(int b, int n, int m, - const float *dataset, float *temp, int *idxs) { - // dataset: (B, N, 3) - // tmp: (B, N) - // output: - // idx: (B, M) - - cudaError_t err; - unsigned int n_threads = opt_n_threads(n); - - switch (n_threads) { - case 1024: - furthest_point_sampling_kernel<1024><<>>(b, n, m, dataset, temp, idxs); break; - case 512: - furthest_point_sampling_kernel<512><<>>(b, n, m, dataset, temp, idxs); break; - case 256: - furthest_point_sampling_kernel<256><<>>(b, n, m, dataset, temp, idxs); break; - case 128: - furthest_point_sampling_kernel<128><<>>(b, n, m, dataset, temp, idxs); break; - case 64: - furthest_point_sampling_kernel<64><<>>(b, n, m, dataset, temp, idxs); break; - case 32: - furthest_point_sampling_kernel<32><<>>(b, n, m, dataset, temp, idxs); break; - case 16: - furthest_point_sampling_kernel<16><<>>(b, n, m, dataset, temp, idxs); break; - case 8: - furthest_point_sampling_kernel<8><<>>(b, n, m, dataset, temp, idxs); break; - case 4: - furthest_point_sampling_kernel<4><<>>(b, n, m, dataset, temp, idxs); break; - case 2: - furthest_point_sampling_kernel<2><<>>(b, n, m, dataset, temp, idxs); break; - case 1: - furthest_point_sampling_kernel<1><<>>(b, n, m, dataset, temp, idxs); break; - default: - furthest_point_sampling_kernel<512><<>>(b, n, m, dataset, temp, idxs); - } - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } -} diff --git a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling_gpu.h b/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling_gpu.h deleted file mode 100644 index 2ad8aa5b..00000000 --- a/OpenCOOD/opencood/pcdet_utils/pointnet2/pointnet2_stack/src/sampling_gpu.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _SAMPLING_GPU_H -#define _SAMPLING_GPU_H - -#include -#include -#include - - -int furthest_point_sampling_wrapper(int b, int n, int m, - at::Tensor points_tensor, at::Tensor temp_tensor, at::Tensor idx_tensor); - -void furthest_point_sampling_kernel_launcher(int b, int n, int m, - const float *dataset, float *temp, int *idxs); - -#endif diff --git a/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/roiaware_pool3d_utils.py b/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/roiaware_pool3d_utils.py deleted file mode 100644 index 6383cc34..00000000 --- a/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/roiaware_pool3d_utils.py +++ /dev/null @@ -1,55 +0,0 @@ -import torch - -from opencood.utils import common_utils -from opencood.pcdet_utils.roiaware_pool3d import roiaware_pool3d_cuda - - -def points_in_boxes_cpu(points, boxes): - """ - Args: - points: (num_points, 3) - boxes: [x, y, z, dx, dy, dz, heading], (x, y, z) is the box center, each box DO NOT overlaps - Returns: - point_indices: (N, num_points) - """ - assert boxes.shape[1] == 7 - assert points.shape[1] == 3 - points, is_numpy = common_utils.check_numpy_to_torch(points) - boxes, is_numpy = common_utils.check_numpy_to_torch(boxes) - - point_indices = points.new_zeros((boxes.shape[0], points.shape[0]), dtype=torch.int) - roiaware_pool3d_cuda.points_in_boxes_cpu(boxes.float().contiguous(), points.float().contiguous(), point_indices) - - return point_indices.numpy() if is_numpy else point_indices - - -def points_in_boxes_gpu(points, boxes): - """ - :param points: (B, M, 3) - :param boxes: (B, T, 7), num_valid_boxes <= T - :return box_idxs_of_pts: (B, M), default background = -1 - """ - assert boxes.shape[0] == points.shape[0] - assert boxes.shape[2] == 7 and points.shape[2] == 3 - # ####### - # import matplotlib.pyplot as plt - # ax = plt.figure(figsize=(8, 8)).add_subplot(1, 1, 1) - # ax.set_aspect('equal', 'box') - # ax.set(xlim=(-50, 50), - # ylim=(-41.6, 41.6)) - # points0 = points[0].cpu().detach().numpy() - # boxes0 = boxes[0].cpu().detach().numpy() - # ax.plot(points0[:, 0], points0[:, 1], 'y.', markersize=3) - # ax.plot(boxes0[:, 0], boxes0[:, 1], 'r.', markersize=10) - # plt.xlabel('x') - # plt.ylabel('y') - # - # plt.show() - # plt.close() - # ######## - batch_size, num_points, _ = points.shape - - box_idxs_of_pts = points.new_zeros((batch_size, num_points), dtype=torch.int).fill_(-1) - roiaware_pool3d_cuda.points_in_boxes_gpu(boxes.contiguous(), points.contiguous(), box_idxs_of_pts) - - return box_idxs_of_pts diff --git a/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/src/roiaware_pool3d.cpp b/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/src/roiaware_pool3d.cpp deleted file mode 100644 index e191c491..00000000 --- a/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/src/roiaware_pool3d.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* -RoI-aware point cloud feature pooling -Reference paper: https://arxiv.org/abs/1907.03670 -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#include -#include -#include - - -//#define CHECK_CUDA(x) AT_CHECK(x.type().is_cuda(), #x, " must be a CUDAtensor ") -//#define CHECK_CONTIGUOUS(x) AT_CHECK(x.is_contiguous(), #x, " must be contiguous ") -//#define CHECK_INPUT(x) CHECK_CUDA(x);CHECK_CONTIGUOUS(x) - - -void roiaware_pool3d_launcher(int boxes_num, int pts_num, int channels, int max_pts_each_voxel, - int out_x, int out_y, int out_z, const float *rois, const float *pts, const float *pts_feature, - int *argmax, int *pts_idx_of_voxels, float *pooled_features, int pool_method); - -void roiaware_pool3d_backward_launcher(int boxes_num, int out_x, int out_y, int out_z, int channels, int max_pts_each_voxel, - const int *pts_idx_of_voxels, const int *argmax, const float *grad_out, float *grad_in, int pool_method); - -void points_in_boxes_launcher(int batch_size, int boxes_num, int pts_num, const float *boxes, - const float *pts, int *box_idx_of_points); - -int roiaware_pool3d_gpu(at::Tensor rois, at::Tensor pts, at::Tensor pts_feature, at::Tensor argmax, - at::Tensor pts_idx_of_voxels, at::Tensor pooled_features, int pool_method){ - // params rois: (N, 7) [x, y, z, dx, dy, dz, heading] (x, y, z) is the box center - // params pts: (npoints, 3) [x, y, z] - // params pts_feature: (npoints, C) - // params argmax: (N, out_x, out_y, out_z, C) - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel) - // params pooled_features: (N, out_x, out_y, out_z, C) - // params pool_method: 0: max_pool 1: avg_pool - -// CHECK_INPUT(rois); -// CHECK_INPUT(pts); -// CHECK_INPUT(pts_feature); -// CHECK_INPUT(argmax); -// CHECK_INPUT(pts_idx_of_voxels); -// CHECK_INPUT(pooled_features); - - int boxes_num = rois.size(0); - int pts_num = pts.size(0); - int channels = pts_feature.size(1); - int max_pts_each_voxel = pts_idx_of_voxels.size(4); // index 0 is the counter - int out_x = pts_idx_of_voxels.size(1); - int out_y = pts_idx_of_voxels.size(2); - int out_z = pts_idx_of_voxels.size(3); - assert ((out_x < 256) && (out_y < 256) && (out_z < 256)); // we encode index with 8bit - - const float *rois_data = rois.data_ptr(); - const float *pts_data = pts.data_ptr(); - const float *pts_feature_data = pts_feature.data_ptr(); - int *argmax_data = argmax.data_ptr(); - int *pts_idx_of_voxels_data = pts_idx_of_voxels.data_ptr(); - float *pooled_features_data = pooled_features.data_ptr(); - - roiaware_pool3d_launcher(boxes_num, pts_num, channels, max_pts_each_voxel, out_x, out_y, out_z, - rois_data, pts_data, pts_feature_data, argmax_data, pts_idx_of_voxels_data, pooled_features_data, pool_method); - - return 1; -} - -int roiaware_pool3d_gpu_backward(at::Tensor pts_idx_of_voxels, at::Tensor argmax, at::Tensor grad_out, at::Tensor grad_in, int pool_method){ - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel) - // params argmax: (N, out_x, out_y, out_z, C) - // params grad_out: (N, out_x, out_y, out_z, C) - // params grad_in: (npoints, C), return value - // params pool_method: 0: max_pool 1: avg_pool - -// CHECK_INPUT(pts_idx_of_voxels); -// CHECK_INPUT(argmax); -// CHECK_INPUT(grad_out); -// CHECK_INPUT(grad_in); - - int boxes_num = pts_idx_of_voxels.size(0); - int out_x = pts_idx_of_voxels.size(1); - int out_y = pts_idx_of_voxels.size(2); - int out_z = pts_idx_of_voxels.size(3); - int max_pts_each_voxel = pts_idx_of_voxels.size(4); // index 0 is the counter - int channels = grad_out.size(4); - - const int *pts_idx_of_voxels_data = pts_idx_of_voxels.data_ptr(); - const int *argmax_data = argmax.data_ptr(); - const float *grad_out_data = grad_out.data_ptr(); - float *grad_in_data = grad_in.data_ptr(); - - roiaware_pool3d_backward_launcher(boxes_num, out_x, out_y, out_z, channels, max_pts_each_voxel, - pts_idx_of_voxels_data, argmax_data, grad_out_data, grad_in_data, pool_method); - - return 1; -} - -int points_in_boxes_gpu(at::Tensor boxes_tensor, at::Tensor pts_tensor, at::Tensor box_idx_of_points_tensor){ - // params boxes: (B, N, 7) [x, y, z, dx, dy, dz, heading] (x, y, z) is the box center - // params pts: (B, npoints, 3) [x, y, z] - // params boxes_idx_of_points: (B, npoints), default -1 - -// CHECK_INPUT(boxes_tensor); -// CHECK_INPUT(pts_tensor); -// CHECK_INPUT(box_idx_of_points_tensor); - - int batch_size = boxes_tensor.size(0); - int boxes_num = boxes_tensor.size(1); - int pts_num = pts_tensor.size(1); - - const float *boxes = boxes_tensor.data_ptr(); - const float *pts = pts_tensor.data_ptr(); - int *box_idx_of_points = box_idx_of_points_tensor.data_ptr(); - - points_in_boxes_launcher(batch_size, boxes_num, pts_num, boxes, pts, box_idx_of_points); - - return 1; -} - - -inline void lidar_to_local_coords_cpu(float shift_x, float shift_y, float rot_angle, float &local_x, float &local_y){ - float cosa = cos(-rot_angle), sina = sin(-rot_angle); - local_x = shift_x * cosa + shift_y * (-sina); - local_y = shift_x * sina + shift_y * cosa; -} - - -inline int check_pt_in_box3d_cpu(const float *pt, const float *box3d, float &local_x, float &local_y){ - // param pt: (x, y, z) - // param box3d: [x, y, z, dx, dy, dz, heading], (x, y, z) is the box center - const float MARGIN = 1e-2; - float x = pt[0], y = pt[1], z = pt[2]; - float cx = box3d[0], cy = box3d[1], cz = box3d[2]; - float dx = box3d[3], dy = box3d[4], dz = box3d[5], rz = box3d[6]; - - if (fabsf(z - cz) > dz / 2.0) return 0; - lidar_to_local_coords_cpu(x - cx, y - cy, rz, local_x, local_y); - float in_flag = (fabs(local_x) < dx / 2.0 + MARGIN) & (fabs(local_y) < dy / 2.0 + MARGIN); - return in_flag; -} - - -int points_in_boxes_cpu(at::Tensor boxes_tensor, at::Tensor pts_tensor, at::Tensor pts_indices_tensor){ - // params boxes: (N, 7) [x, y, z, dx, dy, dz, heading], (x, y, z) is the box center, each box DO NOT overlaps - // params pts: (num_points, 3) [x, y, z] - // params pts_indices: (N, num_points) - -// CHECK_CONTIGUOUS(boxes_tensor); -// CHECK_CONTIGUOUS(pts_tensor); -// CHECK_CONTIGUOUS(pts_indices_tensor); - - int boxes_num = boxes_tensor.size(0); - int pts_num = pts_tensor.size(0); - - const float *boxes = boxes_tensor.data_ptr(); - const float *pts = pts_tensor.data_ptr(); - int *pts_indices = pts_indices_tensor.data_ptr(); - - float local_x = 0, local_y = 0; - for (int i = 0; i < boxes_num; i++){ - for (int j = 0; j < pts_num; j++){ - int cur_in_flag = check_pt_in_box3d_cpu(pts + j * 3, boxes + i * 7, local_x, local_y); - pts_indices[i * pts_num + j] = cur_in_flag; - } - } - - return 1; -} - - - -PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) { - m.def("forward", &roiaware_pool3d_gpu, "roiaware pool3d forward (CUDA)"); - m.def("backward", &roiaware_pool3d_gpu_backward, "roiaware pool3d backward (CUDA)"); - m.def("points_in_boxes_gpu", &points_in_boxes_gpu, "points_in_boxes_gpu forward (CUDA)"); - m.def("points_in_boxes_cpu", &points_in_boxes_cpu, "points_in_boxes_cpu forward (CUDA)"); -} diff --git a/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/src/roiaware_pool3d_kernel.cu b/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/src/roiaware_pool3d_kernel.cu deleted file mode 100644 index 5b52937f..00000000 --- a/OpenCOOD/opencood/pcdet_utils/roiaware_pool3d/src/roiaware_pool3d_kernel.cu +++ /dev/null @@ -1,359 +0,0 @@ -/* -RoI-aware point cloud feature pooling -Written by Shaoshuai Shi -All Rights Reserved 2019-2020. -*/ - - -#include -#include - -#define THREADS_PER_BLOCK 256 -#define DIVUP(m,n) ((m) / (n) + ((m) % (n) > 0)) -// #define DEBUG - - -__device__ inline void lidar_to_local_coords(float shift_x, float shift_y, float rot_angle, float &local_x, float &local_y){ - float cosa = cos(-rot_angle), sina = sin(-rot_angle); - local_x = shift_x * cosa + shift_y * (-sina); - local_y = shift_x * sina + shift_y * cosa; -} - - -__device__ inline int check_pt_in_box3d(const float *pt, const float *box3d, float &local_x, float &local_y){ - // param pt: (x, y, z) - // param box3d: [x, y, z, dx, dy, dz, heading] (x, y, z) is the box center - - const float MARGIN = 1e-5; - float x = pt[0], y = pt[1], z = pt[2]; - float cx = box3d[0], cy = box3d[1], cz = box3d[2]; - float dx = box3d[3], dy = box3d[4], dz = box3d[5], rz = box3d[6]; - - if (fabsf(z - cz) > dz / 2.0) return 0; - lidar_to_local_coords(x - cx, y - cy, rz, local_x, local_y); - float in_flag = (fabs(local_x) < dx / 2.0 + MARGIN) & (fabs(local_y) < dy / 2.0 + MARGIN); - return in_flag; -} - - -__global__ void generate_pts_mask_for_box3d(int boxes_num, int pts_num, int out_x, int out_y, int out_z, - const float *rois, const float *pts, int *pts_mask){ - // params rois: [x, y, z, dx, dy, dz, heading] (x, y, z) is the box center - // params pts: (npoints, 3) [x, y, z] - // params pts_mask: (N, npoints): -1 means point doesnot in this box, otherwise: encode (x_idxs, y_idxs, z_idxs) by binary bit - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - int box_idx = blockIdx.y; - if (pt_idx >= pts_num || box_idx >= boxes_num) return; - - pts += pt_idx * 3; - rois += box_idx * 7; - pts_mask += box_idx * pts_num + pt_idx; - - float local_x = 0, local_y = 0; - int cur_in_flag = check_pt_in_box3d(pts, rois, local_x, local_y); - - pts_mask[0] = -1; - if (cur_in_flag > 0){ - float local_z = pts[2] - rois[2]; - float dx = rois[3], dy = rois[4], dz = rois[5]; - - float x_res = dx / out_x; - float y_res = dy / out_y; - float z_res = dz / out_z; - - unsigned int x_idx = int((local_x + dx / 2) / x_res); - unsigned int y_idx = int((local_y + dy / 2) / y_res); - unsigned int z_idx = int((local_z + dz / 2) / z_res); - - x_idx = min(max(x_idx, 0), out_x - 1); - y_idx = min(max(y_idx, 0), out_y - 1); - z_idx = min(max(z_idx, 0), out_z - 1); - - unsigned int idx_encoding = (x_idx << 16) + (y_idx << 8) + z_idx; - pts_mask[0] = idx_encoding; - } -} - - -__global__ void collect_inside_pts_for_box3d(int boxes_num, int pts_num, int max_pts_each_voxel, - int out_x, int out_y, int out_z, const int *pts_mask, int *pts_idx_of_voxels){ - // params pts_mask: (N, npoints) 0 or 1 - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel) - - int box_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (box_idx >= boxes_num) return; - - int max_num_pts = max_pts_each_voxel - 1; // index 0 is the counter - pts_idx_of_voxels += box_idx * out_x * out_y * out_z * max_pts_each_voxel; - - for (int k = 0; k < pts_num; k++){ - if (pts_mask[box_idx * pts_num + k] != -1){ - unsigned int idx_encoding = pts_mask[box_idx * pts_num + k]; - unsigned int x_idx = (idx_encoding >> 16) & 0xFF; - unsigned int y_idx = (idx_encoding >> 8) & 0xFF; - unsigned int z_idx = idx_encoding & 0xFF; - unsigned int base_offset = x_idx * out_y * out_z * max_pts_each_voxel + y_idx * out_z * max_pts_each_voxel + z_idx * max_pts_each_voxel; - unsigned int cnt = pts_idx_of_voxels[base_offset]; - if (cnt < max_num_pts){ - pts_idx_of_voxels[base_offset + cnt + 1] = k; - pts_idx_of_voxels[base_offset]++; - } -#ifdef DEBUG - printf("collect: pts_%d, idx(%d, %d, %d), idx_encoding=%x\n", - k, x_idx, y_idx, z_idx, idx_encoding); -#endif - - } - } -} - - -__global__ void roiaware_maxpool3d(int boxes_num, int pts_num, int channels, int max_pts_each_voxel, int out_x, - int out_y, int out_z, const float *pts_feature, const int *pts_idx_of_voxels, float *pooled_features, int *argmax){ - // params pts_feature: (npoints, C) - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel), index 0 is the counter - // params pooled_features: (N, out_x, out_y, out_z, C) - // params argmax: (N, out_x, out_y, out_z, C) - - int box_idx = blockIdx.z; - int channel_idx = blockIdx.y; - int voxel_idx_flat = blockIdx.x * blockDim.x + threadIdx.x; - - int x_idx = voxel_idx_flat / (out_y * out_z); - int y_idx = (voxel_idx_flat - x_idx * (out_y * out_z)) / out_z; - int z_idx = voxel_idx_flat % out_z; - if (box_idx >= boxes_num || channel_idx >= channels|| x_idx >= out_x || y_idx >= out_y || z_idx >= out_z) return; - -#ifdef DEBUG - printf("src pts_idx_of_voxels: (%p, ), argmax: %p\n", pts_idx_of_voxels, argmax); -#endif - - int offset_base = x_idx * out_y * out_z + y_idx * out_z + z_idx; - pts_idx_of_voxels += box_idx * out_x * out_y * out_z * max_pts_each_voxel + offset_base * max_pts_each_voxel; - pooled_features += box_idx * out_x * out_y * out_z * channels + offset_base * channels + channel_idx; - argmax += box_idx * out_x * out_y * out_z * channels + offset_base * channels + channel_idx; - - int argmax_idx = -1; - float max_val = -1e50; - - int total_pts = pts_idx_of_voxels[0]; - - for (int k = 1; k <= total_pts; k++){ - if (pts_feature[pts_idx_of_voxels[k] * channels + channel_idx] > max_val){ - max_val = pts_feature[pts_idx_of_voxels[k] * channels + channel_idx]; - argmax_idx = pts_idx_of_voxels[k]; - } - } - - if (argmax_idx != -1){ - pooled_features[0] = max_val; - } - argmax[0] = argmax_idx; - -#ifdef DEBUG - printf("channel_%d idx(%d, %d, %d), argmax_idx=(%d, %.3f), total=%d, after pts_idx: %p, argmax: (%p, %d)\n", - channel_idx, x_idx, y_idx, z_idx, argmax_idx, max_val, total_pts, pts_idx_of_voxels, argmax, argmax_idx); -#endif -} - - -__global__ void roiaware_avgpool3d(int boxes_num, int pts_num, int channels, int max_pts_each_voxel, int out_x, - int out_y, int out_z, const float *pts_feature, const int *pts_idx_of_voxels, float *pooled_features){ - // params pts_feature: (npoints, C) - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel), index 0 is the counter - // params pooled_features: (N, out_x, out_y, out_z, C) - // params argmax: (N, out_x, out_y, out_z, C) - - int box_idx = blockIdx.z; - int channel_idx = blockIdx.y; - int voxel_idx_flat = blockIdx.x * blockDim.x + threadIdx.x; - - int x_idx = voxel_idx_flat / (out_y * out_z); - int y_idx = (voxel_idx_flat - x_idx * (out_y * out_z)) / out_z; - int z_idx = voxel_idx_flat % out_z; - if (box_idx >= boxes_num || channel_idx >= channels|| x_idx >= out_x || y_idx >= out_y || z_idx >= out_z) return; - - int offset_base = x_idx * out_y * out_z + y_idx * out_z + z_idx; - pts_idx_of_voxels += box_idx * out_x * out_y * out_z * max_pts_each_voxel + offset_base * max_pts_each_voxel; - pooled_features += box_idx * out_x * out_y * out_z * channels + offset_base * channels + channel_idx; - - float sum_val = 0; - int total_pts = pts_idx_of_voxels[0]; - - for (int k = 1; k <= total_pts; k++){ - sum_val += pts_feature[pts_idx_of_voxels[k] * channels + channel_idx]; - } - - if (total_pts > 0){ - pooled_features[0] = sum_val / total_pts; - } -} - - -void roiaware_pool3d_launcher(int boxes_num, int pts_num, int channels, int max_pts_each_voxel, int out_x, int out_y, int out_z, - const float *rois, const float *pts, const float *pts_feature, int *argmax, int *pts_idx_of_voxels, float *pooled_features, int pool_method){ - // params rois: (N, 7) [x, y, z, dx, dy, dz, heading] (x, y, z) is the box center - // params pts: (npoints, 3) [x, y, z] - // params pts_feature: (npoints, C) - // params argmax: (N, out_x, out_y, out_z, C) - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel) - // params pooled_features: (N, out_x, out_y, out_z, C) - // params pool_method: 0: max_pool 1: avg_pool - - int *pts_mask = NULL; - cudaMalloc(&pts_mask, boxes_num * pts_num * sizeof(int)); // (N, M) - cudaMemset(pts_mask, -1, boxes_num * pts_num * sizeof(int)); - - dim3 blocks_mask(DIVUP(pts_num, THREADS_PER_BLOCK), boxes_num); - dim3 threads(THREADS_PER_BLOCK); - generate_pts_mask_for_box3d<<>>(boxes_num, pts_num, out_x, out_y, out_z, rois, pts, pts_mask); - - // TODO: Merge the collect and pool functions, SS - - dim3 blocks_collect(DIVUP(boxes_num, THREADS_PER_BLOCK)); - collect_inside_pts_for_box3d<<>>(boxes_num, pts_num, max_pts_each_voxel, - out_x, out_y, out_z, pts_mask, pts_idx_of_voxels); - - dim3 blocks_pool(DIVUP(out_x * out_y * out_z, THREADS_PER_BLOCK), channels, boxes_num); - if (pool_method == 0){ - roiaware_maxpool3d<<>>(boxes_num, pts_num, channels, max_pts_each_voxel, out_x, out_y, out_z, - pts_feature, pts_idx_of_voxels, pooled_features, argmax); - } - else if (pool_method == 1){ - roiaware_avgpool3d<<>>(boxes_num, pts_num, channels, max_pts_each_voxel, out_x, out_y, out_z, - pts_feature, pts_idx_of_voxels, pooled_features); - } - - - cudaFree(pts_mask); - -#ifdef DEBUG - cudaDeviceSynchronize(); // for using printf in kernel function -#endif -} - - -__global__ void roiaware_maxpool3d_backward(int boxes_num, int channels, int out_x, int out_y, int out_z, - const int *argmax, const float *grad_out, float *grad_in){ - // params argmax: (N, out_x, out_y, out_z, C) - // params grad_out: (N, out_x, out_y, out_z, C) - // params grad_in: (npoints, C), return value - - int box_idx = blockIdx.z; - int channel_idx = blockIdx.y; - int voxel_idx_flat = blockIdx.x * blockDim.x + threadIdx.x; - - int x_idx = voxel_idx_flat / (out_y * out_z); - int y_idx = (voxel_idx_flat - x_idx * (out_y * out_z)) / out_z; - int z_idx = voxel_idx_flat % out_z; - if (box_idx >= boxes_num || channel_idx >= channels|| x_idx >= out_x || y_idx >= out_y || z_idx >= out_z) return; - - int offset_base = x_idx * out_y * out_z + y_idx * out_z + z_idx; - argmax += box_idx * out_x * out_y * out_z * channels + offset_base * channels + channel_idx; - grad_out += box_idx * out_x * out_y * out_z * channels + offset_base * channels + channel_idx; - - if (argmax[0] == -1) return; - - atomicAdd(grad_in + argmax[0] * channels + channel_idx, grad_out[0] * 1); -} - - -__global__ void roiaware_avgpool3d_backward(int boxes_num, int channels, int out_x, int out_y, int out_z, - int max_pts_each_voxel, const int *pts_idx_of_voxels, const float *grad_out, float *grad_in){ - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel) - // params grad_out: (N, out_x, out_y, out_z, C) - // params grad_in: (npoints, C), return value - - int box_idx = blockIdx.z; - int channel_idx = blockIdx.y; - int voxel_idx_flat = blockIdx.x * blockDim.x + threadIdx.x; - - int x_idx = voxel_idx_flat / (out_y * out_z); - int y_idx = (voxel_idx_flat - x_idx * (out_y * out_z)) / out_z; - int z_idx = voxel_idx_flat % out_z; - if (box_idx >= boxes_num || channel_idx >= channels|| x_idx >= out_x || y_idx >= out_y || z_idx >= out_z) return; - - int offset_base = x_idx * out_y * out_z + y_idx * out_z + z_idx; - pts_idx_of_voxels += box_idx * out_x * out_y * out_z * max_pts_each_voxel + offset_base * max_pts_each_voxel; - grad_out += box_idx * out_x * out_y * out_z * channels + offset_base * channels + channel_idx; - - - int total_pts = pts_idx_of_voxels[0]; - float cur_grad = 1 / fmaxf(float(total_pts), 1.0); - for (int k = 1; k <= total_pts; k++){ - atomicAdd(grad_in + pts_idx_of_voxels[k] * channels + channel_idx, grad_out[0] * cur_grad); - } -} - - -void roiaware_pool3d_backward_launcher(int boxes_num, int out_x, int out_y, int out_z, int channels, int max_pts_each_voxel, - const int *pts_idx_of_voxels, const int *argmax, const float *grad_out, float *grad_in, int pool_method){ - // params pts_idx_of_voxels: (N, out_x, out_y, out_z, max_pts_each_voxel) - // params argmax: (N, out_x, out_y, out_z, C) - // params grad_out: (N, out_x, out_y, out_z, C) - // params grad_in: (npoints, C), return value - // params pool_method: 0: max_pool, 1: avg_pool - - dim3 blocks(DIVUP(out_x * out_y * out_z, THREADS_PER_BLOCK), channels, boxes_num); - dim3 threads(THREADS_PER_BLOCK); - if (pool_method == 0){ - roiaware_maxpool3d_backward<<>>( - boxes_num, channels, out_x, out_y, out_z, argmax, grad_out, grad_in - ); - } - else if (pool_method == 1){ - roiaware_avgpool3d_backward<<>>( - boxes_num, channels, out_x, out_y, out_z, max_pts_each_voxel, pts_idx_of_voxels, grad_out, grad_in - ); - } - -} - - -__global__ void points_in_boxes_kernel(int batch_size, int boxes_num, int pts_num, const float *boxes, - const float *pts, int *box_idx_of_points){ - // params boxes: (B, N, 7) [x, y, z, dx, dy, dz, heading] (x, y, z) is the box center - // params pts: (B, npoints, 3) [x, y, z] in LiDAR coordinate - // params boxes_idx_of_points: (B, npoints), default -1 - - int bs_idx = blockIdx.y; - int pt_idx = blockIdx.x * blockDim.x + threadIdx.x; - if (bs_idx >= batch_size || pt_idx >= pts_num) return; - - boxes += bs_idx * boxes_num * 7; - pts += bs_idx * pts_num * 3 + pt_idx * 3; - box_idx_of_points += bs_idx * pts_num + pt_idx; - - float local_x = 0, local_y = 0; - int cur_in_flag = 0; - for (int k = 0; k < boxes_num; k++){ - cur_in_flag = check_pt_in_box3d(pts, boxes + k * 7, local_x, local_y); - if (cur_in_flag){ - box_idx_of_points[0] = k; - break; - } - } -} - - -void points_in_boxes_launcher(int batch_size, int boxes_num, int pts_num, const float *boxes, - const float *pts, int *box_idx_of_points){ - // params boxes: (B, N, 7) [x, y, z, dx, dy, dz, heading] (x, y, z) is the box center - // params pts: (B, npoints, 3) [x, y, z] - // params boxes_idx_of_points: (B, npoints), default -1 - cudaError_t err; - - dim3 blocks(DIVUP(pts_num, THREADS_PER_BLOCK), batch_size); - dim3 threads(THREADS_PER_BLOCK); - points_in_boxes_kernel<<>>(batch_size, boxes_num, pts_num, boxes, pts, box_idx_of_points); - - err = cudaGetLastError(); - if (cudaSuccess != err) { - fprintf(stderr, "CUDA kernel failed : %s\n", cudaGetErrorString(err)); - exit(-1); - } - -#ifdef DEBUG - cudaDeviceSynchronize(); // for using printf in kernel function -#endif -} diff --git a/OpenCOOD/opencood/tools/__init__.py b/OpenCOOD/opencood/tools/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/tools/debug_utils.py b/OpenCOOD/opencood/tools/debug_utils.py deleted file mode 100644 index 1bd44ebb..00000000 --- a/OpenCOOD/opencood/tools/debug_utils.py +++ /dev/null @@ -1,58 +0,0 @@ -import argparse - -import torch -from torch.utils.data import DataLoader - -import opencood.hypes_yaml.yaml_utils as yaml_utils -from opencood.tools import train_utils -from opencood.data_utils.datasets import build_dataset -from opencood.visualization import vis_utils - - -def test_parser(): - parser = argparse.ArgumentParser(description="synthetic data generation") - parser.add_argument("--model_dir", type=str, required=True, help="Continued training path") - parser.add_argument("--fusion_method", type=str, default="late", help="late, early or intermediate") - opt = parser.parse_args() - return opt - - -def test_bev_post_processing(): - opt = test_parser() - assert opt.fusion_method in ["late", "early", "intermediate"] - - hypes = yaml_utils.load_yaml(None, opt) - - print("Dataset Building") - opencood_dataset = build_dataset(hypes, visualize=True, train=False) - data_loader = DataLoader( - opencood_dataset, - batch_size=1, - num_workers=0, - collate_fn=opencood_dataset.collate_batch_test, - shuffle=False, - pin_memory=False, - drop_last=False, - ) - - print("Creating Model") - model = train_utils.create_model(hypes) - # we assume gpu is necessary - if torch.cuda.is_available(): - model.cuda() - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - - print("Loading Model from checkpoint") - saved_path = opt.model_dir - _, model = train_utils.load_saved_model(saved_path, model) - model.eval() - for i, batch_data in enumerate(data_loader): - batch_data = train_utils.to_device(batch_data, device) - label_map = batch_data["ego"]["label_dict"]["label_map"] - output_dict = {"cls": label_map[:, 0, :, :], "reg": label_map[:, 1:, :, :]} - gt_box_tensor, _ = opencood_dataset.post_processor.post_process_debug(batch_data["ego"], output_dict) - vis_utils.visualize_single_sample_output_bev(gt_box_tensor, batch_data["ego"]["origin_lidar"].squeeze(0), opencood_dataset) - - -if __name__ == "__main__": - test_bev_post_processing() diff --git a/OpenCOOD/opencood/tools/inference.py b/OpenCOOD/opencood/tools/inference.py deleted file mode 100644 index 54340e5a..00000000 --- a/OpenCOOD/opencood/tools/inference.py +++ /dev/null @@ -1,152 +0,0 @@ -import argparse -import os -import time -from tqdm import tqdm - -import torch -import open3d as o3d -from torch.utils.data import DataLoader - -import opencood.hypes_yaml.yaml_utils as yaml_utils -from opencood.tools import train_utils, inference_utils -from opencood.data_utils.datasets import build_dataset -from opencood.utils import eval_utils -from opencood.visualization import vis_utils - - -def test_parser(): - parser = argparse.ArgumentParser(description="synthetic data generation") - parser.add_argument("--model_dir", type=str, required=True, help="Continued training path") - parser.add_argument("--fusion_method", required=True, type=str, default="late", help="late, early or intermediate") - parser.add_argument("--show_vis", action="store_true", help="whether to show image visualization result") - parser.add_argument( - "--show_video_vis", action="store_true", help="whether to show video visualization result.it can note be set true with show_vis together " - ) - parser.add_argument("--save_vis", action="store_true", help="whether to save visualization result") - parser.add_argument("--save_npy", action="store_true", help="whether to save prediction and gt resultin npy_test file") - parser.add_argument( - "--global_sort_detections", - action="store_true", - help="whether to globally sort detections by confidence score." - "If set to True, it is the mainstream AP computing method," - "but would increase the tolerance for FP (False Positives).", - ) - opt = parser.parse_args() - return opt - - -def main(): - opt = test_parser() - assert opt.fusion_method in ["late", "early", "intermediate"] - assert not (opt.show_vis and opt.show_video_vis), "you can only visualize the results in single image mode or video mode" - - hypes = yaml_utils.load_yaml(None, opt) - - print("Dataset Building") - opencood_dataset = build_dataset(hypes, visualize=True, train=False) - print(f"{len(opencood_dataset)} samples found.") - data_loader = DataLoader( - opencood_dataset, - batch_size=1, - num_workers=16, - collate_fn=opencood_dataset.collate_batch_test, - shuffle=False, - pin_memory=False, - drop_last=False, - ) - - print("Creating Model") - model = train_utils.create_model(hypes) - # we assume gpu is necessary - if torch.cuda.is_available(): - model.cuda() - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - - print("Loading Model from checkpoint") - saved_path = opt.model_dir - _, model = train_utils.load_saved_model(saved_path, model) - model.eval() - - # Create the dictionary for evaluation. - # also store the confidence score for each prediction - result_stat = { - 0.3: {"tp": [], "fp": [], "gt": 0, "score": []}, - 0.5: {"tp": [], "fp": [], "gt": 0, "score": []}, - 0.7: {"tp": [], "fp": [], "gt": 0, "score": []}, - } - - if opt.show_video_vis: - vis = o3d.visualization.Visualizer() - vis.create_window() - - vis.get_render_option().background_color = [0.05, 0.05, 0.05] # noqa: DC05 - vis.get_render_option().point_size = 1.0 # noqa: DC05 - vis.get_render_option().show_coordinate_frame = True # noqa: DC05 - - # used to visualize lidar points - vis_pcd = o3d.geometry.PointCloud() - # used to visualize object bounding box, maximum 50 - vis_aabbs_gt = [] - vis_aabbs_pred = [] - for _ in range(50): - vis_aabbs_gt.append(o3d.geometry.LineSet()) - vis_aabbs_pred.append(o3d.geometry.LineSet()) - - for i, batch_data in tqdm(enumerate(data_loader)): - # print(i) - with torch.no_grad(): - batch_data = train_utils.to_device(batch_data, device) - if opt.fusion_method == "late": - pred_box_tensor, pred_score, gt_box_tensor = inference_utils.inference_late_fusion(batch_data, model, opencood_dataset) - elif opt.fusion_method == "early": - pred_box_tensor, pred_score, gt_box_tensor = inference_utils.inference_early_fusion(batch_data, model, opencood_dataset) - elif opt.fusion_method == "intermediate": - pred_box_tensor, pred_score, gt_box_tensor = inference_utils.inference_intermediate_fusion(batch_data, model, opencood_dataset) - else: - raise NotImplementedError("Only early, late and intermediatefusion is supported.") - - eval_utils.caluclate_tp_fp(pred_box_tensor, pred_score, gt_box_tensor, result_stat, 0.3) - eval_utils.caluclate_tp_fp(pred_box_tensor, pred_score, gt_box_tensor, result_stat, 0.5) - eval_utils.caluclate_tp_fp(pred_box_tensor, pred_score, gt_box_tensor, result_stat, 0.7) - if opt.save_npy: - npy_save_path = os.path.join(opt.model_dir, "npy") - if not os.path.exists(npy_save_path): - os.makedirs(npy_save_path) - inference_utils.save_prediction_gt(pred_box_tensor, gt_box_tensor, batch_data["ego"]["origin_lidar"][0], i, npy_save_path) - - if opt.show_vis or opt.save_vis: - vis_save_path = "" - if opt.save_vis: - vis_save_path = os.path.join(opt.model_dir, "vis") - if not os.path.exists(vis_save_path): - os.makedirs(vis_save_path) - vis_save_path = os.path.join(vis_save_path, "%05d.png" % i) - - opencood_dataset.visualize_result( - pred_box_tensor, gt_box_tensor, batch_data["ego"]["origin_lidar"], opt.show_vis, vis_save_path, dataset=opencood_dataset - ) - - if opt.show_video_vis: - pcd, pred_o3d_box, gt_o3d_box = vis_utils.visualize_inference_sample_dataloader( - pred_box_tensor, gt_box_tensor, batch_data["ego"]["origin_lidar"], vis_pcd, mode="constant" - ) - if i == 0: - vis.add_geometry(pcd) - vis_utils.linset_assign_list(vis, vis_aabbs_pred, pred_o3d_box, update_mode="add") - - vis_utils.linset_assign_list(vis, vis_aabbs_gt, gt_o3d_box, update_mode="add") - - vis_utils.linset_assign_list(vis, vis_aabbs_pred, pred_o3d_box) - vis_utils.linset_assign_list(vis, vis_aabbs_gt, gt_o3d_box) - vis.update_geometry(pcd) - vis.poll_events() - vis.update_renderer() - time.sleep(0.001) - - eval_utils.eval_final_results(result_stat, opt.model_dir, opt.global_sort_detections) - if opt.show_video_vis: - vis.destroy_window() - - -if __name__ == "__main__": - main() diff --git a/OpenCOOD/opencood/tools/inference_utils.py b/OpenCOOD/opencood/tools/inference_utils.py deleted file mode 100644 index e8902755..00000000 --- a/OpenCOOD/opencood/tools/inference_utils.py +++ /dev/null @@ -1,94 +0,0 @@ -import os -from collections import OrderedDict - -import numpy as np - -from opencood.utils.common_utils import torch_tensor_to_numpy - - -def inference_late_fusion(batch_data, model, dataset): - """ - Model inference for late fusion. - - Parameters - ---------- - batch_data : dict - model : opencood.object - dataset : opencood.LateFusionDataset - - Returns - ------- - pred_box_tensor : torch.Tensor - The tensor of prediction bounding box after NMS. - gt_box_tensor : torch.Tensor - The tensor of gt bounding box. - """ - output_dict = OrderedDict() - - for cav_id, cav_content in batch_data.items(): - if "processed_lidar" in cav_content: - output_dict[cav_id] = model(cav_content) - - pred_box_tensor, pred_score, gt_box_tensor = dataset.post_process(batch_data, output_dict) - - return pred_box_tensor, pred_score, gt_box_tensor - - -def inference_early_fusion(batch_data, model, dataset): - """ - Model inference for early fusion. - - Parameters - ---------- - batch_data : dict - model : opencood.object - dataset : opencood.EarlyFusionDataset - - Returns - ------- - pred_box_tensor : torch.Tensor - The tensor of prediction bounding box after NMS. - gt_box_tensor : torch.Tensor - The tensor of gt bounding box. - """ - output_dict = OrderedDict() - cav_content = batch_data["ego"] - - output_dict["ego"] = model(cav_content) - - pred_box_tensor, pred_score, gt_box_tensor = dataset.post_process(batch_data, output_dict) - - return pred_box_tensor, pred_score, gt_box_tensor - - -def inference_intermediate_fusion(batch_data, model, dataset): - """ - Model inference for early fusion. - - Parameters - ---------- - batch_data : dict - model : opencood.object - dataset : opencood.EarlyFusionDataset - - Returns - ------- - pred_box_tensor : torch.Tensor - The tensor of prediction bounding box after NMS. - gt_box_tensor : torch.Tensor - The tensor of gt bounding box. - """ - return inference_early_fusion(batch_data, model, dataset) - - -def save_prediction_gt(pred_tensor, gt_tensor, pcd, timestamp, save_path): - """ - Save prediction and gt tensor to txt file. - """ - pred_np = torch_tensor_to_numpy(pred_tensor) - gt_np = torch_tensor_to_numpy(gt_tensor) - pcd_np = torch_tensor_to_numpy(pcd) - - np.save(os.path.join(save_path, "%04d_pcd.npy" % timestamp), pcd_np) - np.save(os.path.join(save_path, "%04d_pred.npy" % timestamp), pred_np) - np.save(os.path.join(save_path, "%04d_gt.npy_test" % timestamp), gt_np) diff --git a/OpenCOOD/opencood/tools/multi_gpu_utils.py b/OpenCOOD/opencood/tools/multi_gpu_utils.py deleted file mode 100644 index e188c705..00000000 --- a/OpenCOOD/opencood/tools/multi_gpu_utils.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -import torch - - -def init_distributed_mode(args): - if "RANK" in os.environ and "WORLD_SIZE" in os.environ: - args.rank = int(os.environ["RANK"]) - args.world_size = int(os.environ["WORLD_SIZE"]) - args.gpu = int(os.environ["LOCAL_RANK"]) - elif "SLURM_PROCID" in os.environ: - args.rank = int(os.environ["SLURM_PROCID"]) - args.gpu = args.rank % torch.cuda.device_count() - else: - print("Not using distributed mode") - args.distributed = False - return - - args.distributed = True - - torch.cuda.set_device(args.gpu) - args.dist_backend = "nccl" - print("| distributed init (rank {}): {}".format(args.rank, args.dist_url), flush=True) - torch.distributed.init_process_group(backend=args.dist_backend, init_method=args.dist_url, world_size=args.world_size, rank=args.rank) - torch.distributed.barrier() - setup_for_distributed(args.rank == 0) - - -def setup_for_distributed(is_master): - """ - This function disables printing when not in master process - """ - import builtins as __builtin__ - - builtin_print = __builtin__.print - - def print(*args, **kwargs): - force = kwargs.pop("force", False) - if is_master or force: - builtin_print(*args, **kwargs) - - __builtin__.print = print diff --git a/OpenCOOD/opencood/tools/train.py b/OpenCOOD/opencood/tools/train.py deleted file mode 100644 index dff2bb8d..00000000 --- a/OpenCOOD/opencood/tools/train.py +++ /dev/null @@ -1,172 +0,0 @@ -import argparse -import os -import statistics - -import torch -import tqdm -from tensorboardX import SummaryWriter -from torch.utils.data import DataLoader, DistributedSampler - -import opencood.hypes_yaml.yaml_utils as yaml_utils -from opencood.tools import train_utils -from opencood.tools import multi_gpu_utils -from opencood.data_utils.datasets import build_dataset - - -def train_parser(): - parser = argparse.ArgumentParser(description="synthetic data generation") - parser.add_argument("--hypes_yaml", type=str, required=True, help="data generation yaml file needed ") - parser.add_argument("--model_dir", default="", help="Continued training path") - parser.add_argument("--half", action="store_true", help="whether train with half precision.") - parser.add_argument("--dist_url", default="env://", help="url used to set up distributed training") - opt = parser.parse_args() - return opt - - -def main(): - opt = train_parser() - hypes = yaml_utils.load_yaml(opt.hypes_yaml, opt) - - multi_gpu_utils.init_distributed_mode(opt) - - print("-----------------Dataset Building------------------") - opencood_train_dataset = build_dataset(hypes, visualize=False, train=True) - opencood_validate_dataset = build_dataset(hypes, visualize=False, train=False) - - if opt.distributed: - sampler_train = DistributedSampler(opencood_train_dataset) - sampler_val = DistributedSampler(opencood_validate_dataset, shuffle=False) - - batch_sampler_train = torch.utils.data.BatchSampler(sampler_train, hypes["train_params"]["batch_size"], drop_last=True) - - train_loader = DataLoader( - opencood_train_dataset, batch_sampler=batch_sampler_train, num_workers=8, collate_fn=opencood_train_dataset.collate_batch_train - ) - val_loader = DataLoader( - opencood_validate_dataset, sampler=sampler_val, num_workers=8, collate_fn=opencood_train_dataset.collate_batch_train, drop_last=False - ) - else: - train_loader = DataLoader( - opencood_train_dataset, - batch_size=hypes["train_params"]["batch_size"], - num_workers=8, - collate_fn=opencood_train_dataset.collate_batch_train, - shuffle=True, - pin_memory=False, - drop_last=True, - ) - val_loader = DataLoader( - opencood_validate_dataset, - batch_size=hypes["train_params"]["batch_size"], - num_workers=8, - collate_fn=opencood_train_dataset.collate_batch_train, - shuffle=False, - pin_memory=False, - drop_last=True, - ) - - print("---------------Creating Model------------------") - model = train_utils.create_model(hypes) - device = torch.device("cuda" if torch.cuda.is_available() else "cpu") - - # if we want to train from last checkpoint. - if opt.model_dir: - saved_path = opt.model_dir - init_epoch, model = train_utils.load_saved_model(saved_path, model) - else: - init_epoch = 0 - saved_path = train_utils.setup_train(hypes) - - if torch.cuda.is_available(): - model.to(device) - print(f"Training on: {next(model.parameters()).device}") - model_without_ddp = model - - if opt.distributed: - model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[opt.gpu], find_unused_parameters=True) - model_without_ddp = model.module - - # define the loss - criterion = train_utils.create_loss(hypes) - - # optimizer setup - optimizer = train_utils.setup_optimizer(hypes, model_without_ddp) - # lr scheduler setup - num_steps = len(train_loader) - scheduler = train_utils.setup_lr_schedular(hypes, optimizer, num_steps) - - # record training - writer = SummaryWriter(saved_path) - - # half precision training - if opt.half: - scaler = torch.cuda.amp.GradScaler() - - print("Training start") - epoches = hypes["train_params"]["epoches"] - - for epoch in range(init_epoch, max(epoches, init_epoch)): - if hypes["lr_scheduler"]["core_method"] != "cosineannealwarm": - scheduler.step(epoch) - if hypes["lr_scheduler"]["core_method"] == "cosineannealwarm": - scheduler.step_update(epoch * num_steps + 0) - for param_group in optimizer.param_groups: - print("learning rate %.7f" % param_group["lr"]) - - if opt.distributed: - sampler_train.set_epoch(epoch) - - pbar2 = tqdm.tqdm(total=len(train_loader), leave=True) - - for i, batch_data in enumerate(train_loader): - model.train() - model.zero_grad() - optimizer.zero_grad() - - batch_data = train_utils.to_device(batch_data, device) - - if not opt.half: - ouput_dict = model(batch_data["ego"]) - final_loss = criterion(ouput_dict, batch_data["ego"]["label_dict"]) - else: - with torch.cuda.amp.autocast(): - ouput_dict = model(batch_data["ego"]) - final_loss = criterion(ouput_dict, batch_data["ego"]["label_dict"]) - - criterion.logging(epoch, i, len(train_loader), writer, pbar=pbar2) - pbar2.update(1) - - if not opt.half: - final_loss.backward() - optimizer.step() - else: - scaler.scale(final_loss).backward() - scaler.step(optimizer) - scaler.update() - - if hypes["lr_scheduler"]["core_method"] == "cosineannealwarm": - scheduler.step_update(epoch * num_steps + i) - - if epoch % hypes["train_params"]["save_freq"] == 0: - torch.save(model_without_ddp.state_dict(), os.path.join(saved_path, "net_epoch%d.pth" % (epoch + 1))) - - if epoch % hypes["train_params"]["eval_freq"] == 0: - valid_ave_loss = [] - - with torch.no_grad(): - for i, batch_data in enumerate(val_loader): - model.eval() - batch_data = train_utils.to_device(batch_data, device) - ouput_dict = model(batch_data["ego"]) - final_loss = criterion(ouput_dict, batch_data["ego"]["label_dict"]) - valid_ave_loss.append(final_loss.item()) - - valid_ave_loss = statistics.mean(valid_ave_loss) - print("At epoch %d, the validation loss is %f" % (epoch, valid_ave_loss)) - writer.add_scalar("Validate_Loss", valid_ave_loss, epoch) - - print("Training Finished, checkpoints saved to %s" % saved_path) - - -if __name__ == "__main__": - main() diff --git a/OpenCOOD/opencood/tools/train_utils.py b/OpenCOOD/opencood/tools/train_utils.py deleted file mode 100644 index c66bb1ca..00000000 --- a/OpenCOOD/opencood/tools/train_utils.py +++ /dev/null @@ -1,254 +0,0 @@ -import glob -import importlib -import yaml -import sys -import os -import re -from datetime import datetime - -import torch -import torch.optim as optim - - -def load_saved_model(saved_path, model): - """ - Load saved model if exiseted - - Parameters - __________ - saved_path : str - model saved path - model : opencood object - The model instance. - - Returns - ------- - model : opencood object - The model instance loaded pretrained params. - """ - assert os.path.exists(saved_path), "{} not found".format(saved_path) - - def findLastCheckpoint(save_dir): - if os.path.exists(os.path.join(saved_path, "latest.pth")): - return 10000 - file_list = glob.glob(os.path.join(save_dir, "*epoch*.pth")) - if file_list: - epochs_exist = [] - for file_ in file_list: - result = re.findall(".*epoch(.*).pth.*", file_) - epochs_exist.append(int(result[0])) - initial_epoch_ = max(epochs_exist) - else: - initial_epoch_ = 0 - return initial_epoch_ - - initial_epoch = findLastCheckpoint(saved_path) - if initial_epoch > 0: - model_file = os.path.join(saved_path, "net_epoch%d.pth" % initial_epoch) if initial_epoch != 10000 else os.path.join(saved_path, "latest.pth") - print("resuming by loading epoch %d" % initial_epoch) - checkpoint = torch.load(model_file, map_location="cpu") - model.load_state_dict(checkpoint, strict=False) - - del checkpoint - - return initial_epoch, model - - -def setup_train(hypes): - """ - Create folder for saved model based on current timestep and model name - - Parameters - ---------- - hypes: dict - Config yaml dictionary for training: - """ - model_name = hypes["name"] - current_time = datetime.now() - - folder_name = current_time.strftime("_%Y_%m_%d_%H_%M_%S") - folder_name = model_name + folder_name - - current_path = os.path.dirname(__file__) - current_path = os.path.join(current_path, "../logs") - - full_path = os.path.join(current_path, folder_name) - - if not os.path.exists(full_path): - if not os.path.exists(full_path): - try: - os.makedirs(full_path) - except FileExistsError: - pass - # save the yaml file - save_name = os.path.join(full_path, "config.yaml") - with open(save_name, "w") as outfile: - yaml.dump(hypes, outfile) - - return full_path - - -def create_model(hypes): - """ - Import the module "models/[model_name].py - - Parameters - __________ - hypes : dict - Dictionary containing parameters. - - Returns - ------- - model : opencood,object - Model object. - """ - backbone_name = hypes["model"]["core_method"] - backbone_config = hypes["model"]["args"] - - model_filename = "opencood.models." + backbone_name - model_lib = importlib.import_module(model_filename) - model = None - target_model_name = backbone_name.replace("_", "") - - for name, cls in model_lib.__dict__.items(): - if name.lower() == target_model_name.lower(): - model = cls - - if model is None: - print( - "backbone not found in models folder. Please make sure you " - "have a python file named %s and has a class " - "called %s ignoring upper/lower case" % (model_filename, target_model_name) - ) - exit(0) - instance = model(backbone_config) - return instance - - -def create_loss(hypes): - """ - Create the loss function based on the given loss name. - - Parameters - ---------- - hypes : dict - Configuration params for training. - Returns - ------- - criterion : opencood.object - The loss function. - """ - loss_func_name = hypes["loss"]["core_method"] - loss_func_config = hypes["loss"]["args"] - - loss_filename = "opencood.loss." + loss_func_name - loss_lib = importlib.import_module(loss_filename) - loss_func = None - target_loss_name = loss_func_name.replace("_", "") - - for name, lfunc in loss_lib.__dict__.items(): - if name.lower() == target_loss_name.lower(): - loss_func = lfunc - - if loss_func is None: - print( - "loss function not found in loss folder. Please make sure you " - "have a python file named %s and has a class " - "called %s ignoring upper/lower case" % (loss_filename, target_loss_name) - ) - exit(0) - - criterion = loss_func(loss_func_config) - return criterion - - -def setup_optimizer(hypes, model): - """ - Create optimizer corresponding to the yaml file - - Parameters - ---------- - hypes : dict - The training configurations. - model : opencood model - The pytorch model - """ - method_dict = hypes["optimizer"] - optimizer_method = getattr(optim, method_dict["core_method"], None) - print("optimizer method is: %s" % optimizer_method) - - if not optimizer_method: - raise ValueError("{} is not supported".format(method_dict["name"])) - if "args" in method_dict: - return optimizer_method(filter(lambda p: p.requires_grad, model.parameters()), lr=method_dict["lr"], **method_dict["args"]) - else: - return optimizer_method(filter(lambda p: p.requires_grad, model.parameters()), lr=method_dict["lr"]) - - -def setup_lr_schedular(hypes, optimizer, n_iter_per_epoch): - """ - Set up the learning rate schedular. - - Parameters - ---------- - hypes : dict - The training configurations. - - optimizer : torch.optimizer - """ - lr_schedule_config = hypes["lr_scheduler"] - - if lr_schedule_config["core_method"] == "step": - from torch.optim.lr_scheduler import StepLR - - step_size = lr_schedule_config["step_size"] - gamma = lr_schedule_config["gamma"] - scheduler = StepLR(optimizer, step_size=step_size, gamma=gamma) - - elif lr_schedule_config["core_method"] == "multistep": - from torch.optim.lr_scheduler import MultiStepLR - - milestones = lr_schedule_config["step_size"] - gamma = lr_schedule_config["gamma"] - scheduler = MultiStepLR(optimizer, milestones=milestones, gamma=gamma) - - elif lr_schedule_config["core_method"] == "exponential": - print("ExponentialLR is chosen for lr scheduler") - from torch.optim.lr_scheduler import ExponentialLR - - gamma = lr_schedule_config["gamma"] - scheduler = ExponentialLR(optimizer, gamma) - - elif lr_schedule_config["core_method"] == "cosineannealwarm": - print("cosine annealing is chosen for lr scheduler") - from timm.scheduler.cosine_lr import CosineLRScheduler - - num_steps = lr_schedule_config["epoches"] * n_iter_per_epoch - warmup_lr = lr_schedule_config["warmup_lr"] - warmup_steps = lr_schedule_config["warmup_epoches"] * n_iter_per_epoch - lr_min = lr_schedule_config["lr_min"] - - scheduler = CosineLRScheduler( - optimizer, - t_initial=num_steps, - lr_min=lr_min, - warmup_lr_init=warmup_lr, - warmup_t=warmup_steps, - cycle_limit=1, - t_in_epochs=False, - ) - else: - sys.exit("not supported lr schedular") - - return scheduler - - -def to_device(inputs, device): - if isinstance(inputs, list): - return [to_device(x, device) for x in inputs] - elif isinstance(inputs, dict): - return {k: to_device(v, device) for k, v in inputs.items()} - else: - if isinstance(inputs, int) or isinstance(inputs, float) or isinstance(inputs, str): - return inputs - return inputs.to(device) diff --git a/OpenCOOD/opencood/utils/__init__.py b/OpenCOOD/opencood/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/utils/box_overlaps.py b/OpenCOOD/opencood/utils/box_overlaps.py deleted file mode 100644 index 472ce7c8..00000000 --- a/OpenCOOD/opencood/utils/box_overlaps.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Vectorized NumPy operations for axis-aligned 2D boxes.""" - -from __future__ import annotations - -import numpy as np - - -def bbox_overlaps(boxes: np.ndarray, query_boxes: np.ndarray) -> np.ndarray: - """Compute pairwise intersection over union for axis-aligned 2D boxes. - - Parameters - ---------- - boxes : numpy.ndarray - Boxes with shape ``(N, 4)`` represented as - ``(x_min, y_min, x_max, y_max)``. - query_boxes : numpy.ndarray - Query boxes with shape ``(K, 4)`` represented as - ``(x_min, y_min, x_max, y_max)``. - - Returns - ------- - numpy.ndarray - Pairwise IoU values with shape ``(N, K)`` and dtype ``float32``. - - Notes - ----- - Widths and heights use the legacy OpenCOOD inclusive-coordinate - convention and are therefore calculated with ``+1``. - """ - box_count = boxes.shape[0] - query_count = query_boxes.shape[0] - if box_count == 0 or query_count == 0: - return np.zeros((box_count, query_count), dtype=np.float32) - - intersection_width = np.minimum(boxes[:, None, 2], query_boxes[None, :, 2]) - intersection_width -= np.maximum(boxes[:, None, 0], query_boxes[None, :, 0]) - intersection_width += 1 - np.maximum(intersection_width, 0, out=intersection_width) - - intersection_height = np.minimum(boxes[:, None, 3], query_boxes[None, :, 3]) - intersection_height -= np.maximum(boxes[:, None, 1], query_boxes[None, :, 1]) - intersection_height += 1 - np.maximum(intersection_height, 0, out=intersection_height) - - intersection = intersection_width * intersection_height - box_areas = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1) - query_areas = (query_boxes[:, 2] - query_boxes[:, 0] + 1) * (query_boxes[:, 3] - query_boxes[:, 1] + 1) - union = box_areas[:, None] + query_areas[None, :] - intersection - - overlaps = np.zeros((box_count, query_count), dtype=np.float32) - np.divide(intersection, union, out=overlaps, where=intersection > 0) - return overlaps diff --git a/OpenCOOD/opencood/utils/box_utils.py b/OpenCOOD/opencood/utils/box_utils.py deleted file mode 100644 index 02cbd59f..00000000 --- a/OpenCOOD/opencood/utils/box_utils.py +++ /dev/null @@ -1,729 +0,0 @@ -""" -Bounding box related utility functions -""" - -import sys -import numpy as np -import torch -import torch.nn.functional as F -import opencood.utils.common_utils as common_utils -from opencood.utils.transformation_utils import x1_to_x2 - - -def corner_to_center(corner3d, order="lwh"): - """ - Convert 8 corners to x, y, z, dx, dy, dz, yaw. - - Parameters - ---------- - corner3d : np.ndarray - (N, 8, 3) - - order : str - 'lwh' or 'hwl' - - Returns - ------- - box3d : np.ndarray - (N, 7) - """ - assert corner3d.ndim == 3 - batch_size = corner3d.shape[0] - - xyz = np.mean(corner3d[:, [0, 3, 5, 6], :], axis=1) - h = abs(np.mean(corner3d[:, 4:, 2] - corner3d[:, :4, 2], axis=1, keepdims=True)) - length = ( - np.sqrt(np.sum((corner3d[:, 0, [0, 1]] - corner3d[:, 3, [0, 1]]) ** 2, axis=1, keepdims=True)) - + np.sqrt(np.sum((corner3d[:, 2, [0, 1]] - corner3d[:, 1, [0, 1]]) ** 2, axis=1, keepdims=True)) - + np.sqrt(np.sum((corner3d[:, 4, [0, 1]] - corner3d[:, 7, [0, 1]]) ** 2, axis=1, keepdims=True)) - + np.sqrt(np.sum((corner3d[:, 5, [0, 1]] - corner3d[:, 6, [0, 1]]) ** 2, axis=1, keepdims=True)) - ) / 4 - - w = ( - np.sqrt(np.sum((corner3d[:, 0, [0, 1]] - corner3d[:, 1, [0, 1]]) ** 2, axis=1, keepdims=True)) - + np.sqrt(np.sum((corner3d[:, 2, [0, 1]] - corner3d[:, 3, [0, 1]]) ** 2, axis=1, keepdims=True)) - + np.sqrt(np.sum((corner3d[:, 4, [0, 1]] - corner3d[:, 5, [0, 1]]) ** 2, axis=1, keepdims=True)) - + np.sqrt(np.sum((corner3d[:, 6, [0, 1]] - corner3d[:, 7, [0, 1]]) ** 2, axis=1, keepdims=True)) - ) / 4 - - theta = ( - np.arctan2(corner3d[:, 1, 1] - corner3d[:, 2, 1], corner3d[:, 1, 0] - corner3d[:, 2, 0]) - + np.arctan2(corner3d[:, 0, 1] - corner3d[:, 3, 1], corner3d[:, 0, 0] - corner3d[:, 3, 0]) - + np.arctan2(corner3d[:, 5, 1] - corner3d[:, 6, 1], corner3d[:, 5, 0] - corner3d[:, 6, 0]) - + np.arctan2(corner3d[:, 4, 1] - corner3d[:, 7, 1], corner3d[:, 4, 0] - corner3d[:, 7, 0]) - )[:, np.newaxis] / 4 - - if order == "lwh": - return np.concatenate([xyz, length, w, h, theta], axis=1).reshape(batch_size, 7) - elif order == "hwl": - return np.concatenate([xyz, h, w, length, theta], axis=1).reshape(batch_size, 7) - else: - sys.exit("Unknown order") - - -def boxes_to_corners2d(boxes3d, order): - """ - 0 -------- 1 - | | - | | - | | - 3 -------- 2 - Parameters - __________ - boxes3d: np.ndarray or torch.Tensor - (N, 7) [x, y, z, dx, dy, dz, heading], (x, y, z) is the box center. - - order : str - 'lwh' or 'hwl' - - Returns: - corners2d: np.ndarray or torch.Tensor - (N, 4, 3), the 4 corners of the bounding box. - - """ - corners3d = boxes_to_corners_3d(boxes3d, order) - corners2d = corners3d[:, :4, :] - return corners2d - - -def boxes2d_to_corners2d(boxes2d, order="lwh"): - """ - 0 -------- 1 - | | - | | - | | - 3 -------- 2 - Parameters - __________ - boxes2d: np.ndarray or torch.Tensor - (..., 5) [x, y, dx, dy, heading], (x, y) is the box center. - - order : str - 'lwh' or 'hwl' - - Returns: - corners2d: np.ndarray or torch.Tensor - (..., 4, 2), the 4 corners of the bounding box. - - """ - assert order == "lwh", "boxes2d_to_corners_2d only supports lwh order for now." - boxes2d, is_numpy = common_utils.check_numpy_to_torch(boxes2d) - template = boxes2d.new_tensor(([1, -1], [1, 1], [-1, 1], [-1, -1])) / 2 - input_shape = boxes2d.shape - boxes2d = boxes2d.view(-1, 5) - corners2d = boxes2d[:, None, 2:4].repeat(1, 4, 1) * template[None, :, :] - corners2d = common_utils.rotate_points_along_z_2d(corners2d.view(-1, 2), boxes2d[:, 4].repeat_interleave(4)).view(-1, 4, 2) - corners2d += boxes2d[:, None, 0:2] - corners2d = corners2d.view(*(input_shape[:-1]), 4, 2) - return corners2d - - -def boxes_to_corners_3d(boxes3d, order): - """ - 4 -------- 5 - /| /| - 7 -------- 6 . - | | | | - . 0 -------- 1 - |/ |/ - 3 -------- 2 - Parameters - __________ - boxes3d: np.ndarray or torch.Tensor - (N, 7) [x, y, z, dx, dy, dz, heading], (x, y, z) is the box center. - - order : str - 'lwh' or 'hwl' - - Returns: - corners3d: np.ndarray or torch.Tensor - (N, 8, 3), the 8 corners of the bounding box. - - """ - # ^ z - # | - # | - # | . x - # |/ - # +-------> y - - boxes3d, is_numpy = common_utils.check_numpy_to_torch(boxes3d) - boxes3d_ = boxes3d - - if order == "hwl": - boxes3d_ = boxes3d[:, [0, 1, 2, 5, 4, 3, 6]] - - template = ( - boxes3d_.new_tensor( - ( - [1, -1, -1], - [1, 1, -1], - [-1, 1, -1], - [-1, -1, -1], - [1, -1, 1], - [1, 1, 1], - [-1, 1, 1], - [-1, -1, 1], - ) - ) - / 2 - ) - - corners3d = boxes3d_[:, None, 3:6].repeat(1, 8, 1) * template[None, :, :] - corners3d = common_utils.rotate_points_along_z(corners3d.view(-1, 8, 3), boxes3d_[:, 6]).view(-1, 8, 3) - corners3d += boxes3d_[:, None, 0:3] - - return corners3d.numpy() if is_numpy else corners3d - - -def corner2d_to_standup_box(box2d): - """ - Find the minmaxx, minmaxy for each 2d box. (N, 4, 2) -> (N, 4) - x1, y1, x2, y2 - - Parameters - ---------- - box2d : np.ndarray - (n, 4, 2), four corners of the 2d bounding box. - - Returns - ------- - standup_box2d : np.ndarray - (n, 4) - """ - N = box2d.shape[0] - standup_boxes2d = np.zeros((N, 4)) - - standup_boxes2d[:, 0] = np.min(box2d[:, :, 0], axis=1) - standup_boxes2d[:, 1] = np.min(box2d[:, :, 1], axis=1) - standup_boxes2d[:, 2] = np.max(box2d[:, :, 0], axis=1) - standup_boxes2d[:, 3] = np.max(box2d[:, :, 1], axis=1) - - return standup_boxes2d - - -def corner_to_standup_box_torch(box_corner): - """ - Find the minmax x and y for each bounding box. - - Parameters - ---------- - box_corner : torch.Tensor - Shape: (N, 8, 3) or (N, 4) - - Returns - ------- - standup_box2d : torch.Tensor - (n, 4) - """ - N = box_corner.shape[0] - standup_boxes2d = torch.zeros((N, 4)) - - standup_boxes2d = standup_boxes2d.to(box_corner.device) - - standup_boxes2d[:, 0] = torch.min(box_corner[:, :, 0], dim=1).values - standup_boxes2d[:, 1] = torch.min(box_corner[:, :, 1], dim=1).values - standup_boxes2d[:, 2] = torch.max(box_corner[:, :, 0], dim=1).values - standup_boxes2d[:, 3] = torch.max(box_corner[:, :, 1], dim=1).values - - return standup_boxes2d - - -def project_box3d(box3d, transformation_matrix): - """ - Project the 3d bounding box to another coordinate system based on the - transfomration matrix. - - Parameters - ---------- - box3d : torch.Tensor or np.ndarray - 3D bounding box, (N, 8, 3) - - transformation_matrix : torch.Tensor or np.ndarray - Transformation matrix, (4, 4) - - Returns - ------- - projected_box3d : torch.Tensor - The projected bounding box, (N, 8, 3) - """ - assert transformation_matrix.shape == (4, 4) - box3d, is_numpy = common_utils.check_numpy_to_torch(box3d) - transformation_matrix, _ = common_utils.check_numpy_to_torch(transformation_matrix) - - # (N, 3, 8) - box3d_corner = box3d.transpose(1, 2) - # (N, 1, 8) - torch_ones = torch.ones((box3d_corner.shape[0], 1, 8)) - torch_ones = torch_ones.to(box3d_corner.device) - # (N, 4, 8) - box3d_corner = torch.cat((box3d_corner, torch_ones), dim=1) - # (N, 4, 8) - projected_box3d = torch.matmul(transformation_matrix, box3d_corner) - # (N, 8, 3) - projected_box3d = projected_box3d[:, :3, :].transpose(1, 2) - - return projected_box3d if not is_numpy else projected_box3d.numpy() - - -def get_mask_for_boxes_within_range_torch(boxes): - """ - Generate mask to remove the bounding boxes - outside the range. - - Parameters - ---------- - boxes : torch.Tensor - Groundtruth bbx, shape: N,8,3 or N,4,2 - Returns - ------- - mask: torch.Tensor - The mask for bounding box -- True means the - bbx is within the range and False means the - bbx is outside the range. - """ - from opencood.data_utils.datasets import GT_RANGE - - # mask out the gt bounding box out fixed range (-140, -40, -3, 140, 40 1) - device = boxes.device - boundary_lower_range = torch.Tensor(GT_RANGE[:2]).reshape(1, 1, -1).to(device) - boundary_higher_range = torch.Tensor(GT_RANGE[3:5]).reshape(1, 1, -1).to(device) - - mask = torch.all(torch.all(boxes[:, :, :2] >= boundary_lower_range, dim=-1) & torch.all(boxes[:, :, :2] <= boundary_higher_range, dim=-1), dim=-1) - - return mask - - -def mask_boxes_outside_range_numpy(boxes, limit_range, order, min_num_corners=8, return_mask=False): - """ - Parameters - ---------- - boxes: np.ndarray - (N, 7) [x, y, z, dx, dy, dz, heading], (x, y, z) is the box center - - limit_range: list - [minx, miny, minz, maxx, maxy, maxz] - - min_num_corners: int - The required minimum number of corners to be considered as in range. - - order : str - 'lwh' or 'hwl' - - return_mask : bool - Whether return the mask. - - Returns - ------- - boxes: np.ndarray - The filtered boxes. - """ - assert boxes.shape[1] == 8 or boxes.shape[1] == 7 - - new_boxes = boxes.copy() - if boxes.shape[1] == 7: - new_boxes = boxes_to_corners_3d(new_boxes, order) - - # check if any corners of bbox is inside our GT_RANGE - mask = ((new_boxes >= limit_range[0:3]) & (new_boxes <= limit_range[3:6])).any(axis=2) - mask = mask.sum(axis=1) >= min_num_corners # (N) - - if return_mask: - return boxes[mask], mask - return boxes[mask] - - -def create_bbx(extent): - """ - Create bounding box with 8 corners under obstacle vehicle reference. - - Parameters - ---------- - extent : list - Width, height, length of the bbx. - - Returns - ------- - bbx : np.array - The bounding box with 8 corners, shape: (8, 3) - """ - - bbx = np.array( - [ - [extent[0], -extent[1], -extent[2]], - [extent[0], extent[1], -extent[2]], - [-extent[0], extent[1], -extent[2]], - [-extent[0], -extent[1], -extent[2]], - [extent[0], -extent[1], extent[2]], - [extent[0], extent[1], extent[2]], - [-extent[0], extent[1], extent[2]], - [-extent[0], -extent[1], extent[2]], - ] - ) - - return bbx - - -def project_world_objects(object_dict, output_dict, lidar_pose, lidar_range, order): - """ - Project the objects under world coordinates into another coordinate - based on the provided extrinsic. - - Parameters - ---------- - object_dict : dict - The dictionary contains all objects surrounding a certain cav. - - output_dict : dict - key: object id, value: object bbx (xyzlwhyaw). - - lidar_pose : list - (6, ), lidar pose under world coordinate, [x, y, z, roll, yaw, pitch]. - - lidar_range : list - [minx, miny, minz, maxx, maxy, maxz] - - order : str - 'lwh' or 'hwl' - """ - for object_id, object_content in object_dict.items(): - location = object_content["location"] - rotation = object_content["angle"] - center = object_content["center"] - extent = object_content["extent"] - - object_pose = [location[0] + center[0], location[1] + center[1], location[2] + center[2], rotation[0], rotation[1], rotation[2]] - object2lidar = x1_to_x2(object_pose, lidar_pose) - - # shape (3, 8) - bbx = create_bbx(extent).T - # bounding box under ego coordinate shape (4, 8) - bbx = np.r_[bbx, [np.ones(bbx.shape[1])]] - - # project the 8 corners to world coordinate - bbx_lidar = np.dot(object2lidar, bbx).T - bbx_lidar = np.expand_dims(bbx_lidar[:, :3], 0) - bbx_lidar = corner_to_center(bbx_lidar, order=order) - bbx_lidar = mask_boxes_outside_range_numpy(bbx_lidar, lidar_range, order) - - if bbx_lidar.shape[0] > 0: - output_dict.update({object_id: bbx_lidar}) - - -def get_points_in_rotated_box(p, box_corner): - """ - Get points within a rotated bounding box (2D version). - - Parameters - ---------- - p : numpy.array - Points to be tested with shape (N, 2). - box_corner : numpy.array - Corners of bounding box with shape (4, 2). - - Returns - ------- - p_in_box : numpy.array - Points within the box. - - """ - edge1 = box_corner[1, :] - box_corner[0, :] - edge2 = box_corner[3, :] - box_corner[0, :] - p_rel = p - box_corner[0, :].reshape(1, -1) - - l1 = get_projection_length_for_vector_projection(p_rel, edge1) - l2 = get_projection_length_for_vector_projection(p_rel, edge2) - # A point is within the box, if and only after projecting the - # point onto the two edges s.t. p_rel = [edge1, edge2] @ [l1, l2]^T, - # we have 0<=l1<=1 and 0<=l2<=1. - mask = np.logical_and(l1 >= 0, l1 <= 1) - mask = np.logical_and(mask, l2 >= 0) - mask = np.logical_and(mask, l2 <= 1) - p_in_box = p[mask, :] - return p_in_box - - -def get_projection_length_for_vector_projection(a, b): - """ - Get projection length for the Vector projection of a onto b s.t. - a_projected = length * b. (2D version) See - https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2 - for more details. - - Parameters - ---------- - a : numpy.array - The vectors to be projected with shape (N, 2). - - b : numpy.array - The vector that is projected onto with shape (2). - - Returns - ------- - length : numpy.array - The length of projected a with respect to b. - """ - assert np.sum(b**2, axis=-1) > 1e-6 - length = a.dot(b) / np.sum(b**2, axis=-1) - return length - - -def nms_rotated(boxes, scores, threshold): - """Performs rorated non-maximum suppression and returns indices of kept - boxes. - - Parameters - ---------- - boxes : torch.tensor - The location preds with shape (N, 4, 2). - - scores : torch.tensor - The predicted confidence score with shape (N,) - - threshold: float - IoU threshold to use for filtering. - - Returns - ------- - An array of index - """ - if boxes.shape[0] == 0: - return np.array([], dtype=np.int32) - boxes = boxes.cpu().detach().numpy() - scores = scores.cpu().detach().numpy() - - polygons = common_utils.convert_format(boxes) - - top = 1000 - # Get indicies of boxes sorted by scores (highest first) - ixs = scores.argsort()[::-1][:top] - - pick = [] - while len(ixs) > 0: - # Pick top box and add its index to the list - i = ixs[0] - pick.append(i) - # Compute IoU of the picked box with the rest - iou = common_utils.compute_iou(polygons[i], polygons[ixs[1:]]) - # Identify boxes with IoU over the threshold. This - # returns indices into ixs[1:], so add 1 to get - # indices into ixs. - remove_ixs = np.where(iou > threshold)[0] + 1 - # Remove indices of the picked and overlapped boxes. - ixs = np.delete(ixs, remove_ixs) - ixs = np.delete(ixs, 0) - - return np.array(pick, dtype=np.int32) - - -def remove_large_pred_bbx(bbx_3d): - """ - Remove large bounding box. - - Parameters - ---------- - bbx_3d : torch.Tensor - Predcited 3d bounding box, shape:(N,8,3) - - Returns - ------- - index : torch.Tensor - The keep index. - """ - bbx_x_max = torch.max(bbx_3d[:, :, 0], dim=1)[0] - bbx_x_min = torch.min(bbx_3d[:, :, 0], dim=1)[0] - x_len = bbx_x_max - bbx_x_min - - bbx_y_max = torch.max(bbx_3d[:, :, 1], dim=1)[0] - bbx_y_min = torch.min(bbx_3d[:, :, 1], dim=1)[0] - y_len = bbx_y_max - bbx_y_min - - bbx_z_max = torch.max(bbx_3d[:, :, 2], dim=1)[0] - bbx_z_min = torch.min(bbx_3d[:, :, 2], dim=1)[0] - z_len = bbx_z_max - bbx_z_min - - index = torch.logical_and(x_len <= 6, y_len <= 6) - index = torch.logical_and(index, z_len) - - return index - - -def remove_bbx_abnormal_z(bbx_3d): - """ - Remove bounding box that has negative z axis. - - Parameters - ---------- - bbx_3d : torch.Tensor - Predcited 3d bounding box, shape:(N,8,3) - - Returns - ------- - index : torch.Tensor - The keep index. - """ - bbx_z_min = torch.min(bbx_3d[:, :, 2], dim=1)[0] - bbx_z_max = torch.max(bbx_3d[:, :, 2], dim=1)[0] - index = torch.logical_and(bbx_z_min >= -3, bbx_z_max <= 1) - - return index - - -def project_points_by_matrix_torch(points, transformation_matrix): - """ - Project the points to another coordinate system based on the - transformation matrix. - - Parameters - ---------- - points : torch.Tensor - 3D points, (N, 3) - transformation_matrix : torch.Tensor - Transformation matrix, (4, 4) - Returns - ------- - projected_points : torch.Tensor - The projected points, (N, 3) - """ - points, is_numpy = common_utils.check_numpy_to_torch(points) - transformation_matrix, _ = common_utils.check_numpy_to_torch(transformation_matrix) - - # convert to homogeneous coordinates via padding 1 at the last dimension. - # (N, 4) - points_homogeneous = F.pad(points, (0, 1), mode="constant", value=1) - # (N, 4) - projected_points = torch.einsum("ik, jk->ij", points_homogeneous, transformation_matrix) - - return projected_points[:, :3] if not is_numpy else projected_points[:, :3].numpy() - - -def box_encode(boxes, anchors, encode_angle_to_vector=False, encode_angle_with_residual=False, smooth_dim=False, norm_velo=False): - """box encode for VoxelNet - Args: - boxes ([N, 7] Tensor): normal boxes: x, y, z, w, l, h, r. - anchors ([N, 7] Tensor): anchors. - """ - - box_ndim = anchors.shape[-1] - - if box_ndim == 7: - xa, ya, za, wa, la, ha, ra = torch.split(anchors, 1, dim=-1) - xg, yg, zg, wg, lg, hg, rg = torch.split(boxes, 1, dim=-1) - else: - xa, ya, za, wa, la, ha, vxa, vya, ra = torch.split(anchors, 1, dim=-1) - xg, yg, zg, wg, lg, hg, vxg, vyg, rg = torch.split(boxes, 1, dim=-1) - - diagonal = torch.sqrt(la**2 + wa**2) - xt = (xg - xa) / diagonal - yt = (yg - ya) / diagonal - zt = (zg - za) / ha - - if smooth_dim: - lt = lg / la - 1 - wt = wg / wa - 1 - ht = hg / ha - 1 - else: - lt = torch.log(lg / la) - wt = torch.log(wg / wa) - ht = torch.log(hg / ha) - - ret = [xt, yt, zt, wt, lt, ht] - - if box_ndim > 7: - if norm_velo: - vxt = (vxg - vxa) / diagonal - vyt = (vyg - vya) / diagonal - else: - vxt = vxg - vxa - vyt = vyg - vya - ret.extend([vxt, vyt]) - - if encode_angle_to_vector: - rgx = torch.cos(rg) - rgy = torch.sin(rg) - if encode_angle_with_residual: - rax = torch.cos(ra) - ray = torch.sin(ra) - rtx = rgx - rax - rty = rgy - ray - ret.extend([rtx, rty]) - else: - ret.extend([rgx, rgy]) - else: - rt = rg - ra - ret.append(rt) - - return torch.cat(ret, dim=-1) - - -def box_decode( - box_encodings, - anchors, - encode_angle_to_vector=False, - encode_angle_with_residual=False, - bin_loss=False, - smooth_dim=False, - norm_velo=False, -): - """box decode for VoxelNet in lidar - Args: - boxes ([N, 7] Tensor): normal boxes: x, y, z, w, l, h, r - anchors ([N, 7] Tensor): anchors - """ - box_ndim = anchors.shape[-1] - - if box_ndim == 9: # False - xa, ya, za, wa, la, ha, vxa, vya, ra = torch.split(anchors, 1, dim=-1) - if encode_angle_to_vector: - xt, yt, zt, wt, lt, ht, vxt, vyt, rtx, rty = torch.split(box_encodings, 1, dim=-1) - else: - xt, yt, zt, wt, lt, ht, vxt, vyt, rt = torch.split(box_encodings, 1, dim=-1) - - elif box_ndim == 7: - xa, ya, za, wa, la, ha, ra = torch.split(anchors, 1, dim=-1) - if encode_angle_to_vector: # False - xt, yt, zt, wt, lt, ht, rtx, rty = torch.split(box_encodings, 1, dim=-1) - else: - xt, yt, zt, wt, lt, ht, rt = torch.split(box_encodings, 1, dim=-1) - - diagonal = torch.sqrt(la**2 + wa**2) - xg = xt * diagonal + xa - yg = yt * diagonal + ya - zg = zt * ha + za - - ret = [xg, yg, zg] - - if smooth_dim: # False - lg = (lt + 1) * la - wg = (wt + 1) * wa - hg = (ht + 1) * ha - else: - lg = torch.exp(lt) * la - wg = torch.exp(wt) * wa - hg = torch.exp(ht) * ha - ret.extend([wg, lg, hg]) - - if encode_angle_to_vector: # False - if encode_angle_with_residual: - rax = torch.cos(ra) - ray = torch.sin(ra) - rgx = rtx + rax - rgy = rty + ray - rg = torch.atan2(rgy, rgx) - else: - rg = torch.atan2(rty, rtx) - else: - rg = rt + ra - - if box_ndim > 7: # False - if norm_velo: - vxg = vxt * diagonal + vxa - vyg = vyt * diagonal + vya - else: - vxg = vxt + vxa - vyg = vyt + vya - ret.extend([vxg, vyg]) - - ret.append(rg) - - return torch.cat(ret, dim=-1) diff --git a/OpenCOOD/opencood/utils/common_utils.py b/OpenCOOD/opencood/utils/common_utils.py deleted file mode 100644 index 18639f89..00000000 --- a/OpenCOOD/opencood/utils/common_utils.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Common utilities -""" - -import numpy as np -import torch -from shapely.geometry import Polygon - - -def check_numpy_to_torch(x): - if isinstance(x, np.ndarray): - return torch.from_numpy(x).float(), True - return x, False - - -def check_contain_nan(x): - if isinstance(x, dict): - return any(check_contain_nan(v) for k, v in x.items()) - if isinstance(x, list): - return any(check_contain_nan(itm) for itm in x) - if isinstance(x, int) or isinstance(x, float): - return False - if isinstance(x, np.ndarray): - return np.any(np.isnan(x)) - return torch.any(x.isnan()).detach().cpu().item() - - -def rotate_points_along_z(points, angle): - """ - Args: - points: (B, N, 3 + C) - angle: (B), radians, angle along z-axis, angle increases x ==> y - Returns: - - """ - points, is_numpy = check_numpy_to_torch(points) - angle, _ = check_numpy_to_torch(angle) - - cosa = torch.cos(angle) - sina = torch.sin(angle) - zeros = angle.new_zeros(points.shape[0]) - ones = angle.new_ones(points.shape[0]) - rot_matrix = torch.stack((cosa, sina, zeros, -sina, cosa, zeros, zeros, zeros, ones), dim=1).view(-1, 3, 3).float() - points_rot = torch.matmul(points[:, :, 0:3].float(), rot_matrix) - points_rot = torch.cat((points_rot, points[:, :, 3:]), dim=-1) - return points_rot.numpy() if is_numpy else points_rot - - -def rotate_points_along_z_2d(points, angle): - """ - Rorate the points along z-axis. - Parameters - ---------- - points : torch.Tensor / np.ndarray - (N, 2). - angle : torch.Tensor / np.ndarray - (N,) - - Returns - ------- - points_rot : torch.Tensor / np.ndarray - Rorated points with shape (N, 2) - - """ - points, is_numpy = check_numpy_to_torch(points) - angle, _ = check_numpy_to_torch(angle) - cosa = torch.cos(angle) - sina = torch.sin(angle) - # (N, 2, 2) - rot_matrix = torch.stack((cosa, sina, -sina, cosa), dim=1).view(-1, 2, 2).float() - points_rot = torch.einsum("ik, ikj->ij", points.float(), rot_matrix) - return points_rot.numpy() if is_numpy else points_rot - - -def compute_iou(box, boxes): - """ - Compute iou between box and boxes list - Parameters - ---------- - box : shapely.geometry.Polygon - Bounding box Polygon. - - boxes : list - List of shapely.geometry.Polygon. - - Returns - ------- - iou : np.ndarray - Array of iou between box and boxes. - - """ - # Calculate intersection areas - if np.any(np.array([box.union(b).area for b in boxes]) == 0): - print("debug") - iou = [box.intersection(b).area / box.union(b).area for b in boxes] - - return np.array(iou, dtype=np.float32) - - -def convert_format(boxes_array): - """ - Convert boxes array to shapely.geometry.Polygon format. - Parameters - ---------- - boxes_array : np.ndarray - (N, 4, 2) or (N, 8, 3). - - Returns - ------- - list of converted shapely.geometry.Polygon object. - - """ - polygons = [Polygon([(box[i, 0], box[i, 1]) for i in range(4)]) for box in boxes_array] - return np.array(polygons) - - -def torch_tensor_to_numpy(torch_tensor): - """ - Convert a torch tensor to numpy. - - Parameters - ---------- - torch_tensor : torch.Tensor - - Returns - ------- - A numpy array. - """ - return torch_tensor.numpy() if not torch_tensor.is_cuda else torch_tensor.cpu().detach().numpy() - - -def get_voxel_centers(voxel_coords, downsample_times, voxel_size, point_cloud_range): - """ - Args: - voxel_coords: (N, 3) - downsample_times: - voxel_size: - point_cloud_range: - - Returns: - - """ - assert voxel_coords.shape[1] == 3 - voxel_centers = voxel_coords[:, [2, 1, 0]].float() # (xyz) - voxel_size = torch.tensor(voxel_size, device=voxel_centers.device).float() * downsample_times - pc_range = torch.tensor(point_cloud_range[0:3], device=voxel_centers.device).float() - voxel_centers = (voxel_centers + 0.5) * voxel_size + pc_range - return voxel_centers diff --git a/OpenCOOD/opencood/utils/eval_utils.py b/OpenCOOD/opencood/utils/eval_utils.py deleted file mode 100644 index 1c63f581..00000000 --- a/OpenCOOD/opencood/utils/eval_utils.py +++ /dev/null @@ -1,180 +0,0 @@ -import os - -import numpy as np -import logging - -from opencood.utils import common_utils -from opencood.hypes_yaml import yaml_utils - -logger = logging.getLogger("cavise.opencda.OpenCOOD.opencood.utils.eval_utils") - - -def voc_ap(rec, prec): - """ - VOC 2010 Average Precision. - """ - rec.insert(0, 0.0) - rec.append(1.0) - mrec = rec[:] - - prec.insert(0, 0.0) - prec.append(0.0) - mpre = prec[:] - - for i in range(len(mpre) - 2, -1, -1): - mpre[i] = max(mpre[i], mpre[i + 1]) - - i_list = [] - for i in range(1, len(mrec)): - if mrec[i] != mrec[i - 1]: - i_list.append(i) - - ap = 0.0 - for i in i_list: - ap += (mrec[i] - mrec[i - 1]) * mpre[i] - return ap, mrec, mpre - - -def caluclate_tp_fp(det_boxes, det_score, gt_boxes, result_stat, iou_thresh): - """ - Calculate the true positive and false positive numbers of the current - frames. - - Parameters - ---------- - det_boxes : torch.Tensor - The detection bounding box, shape (N, 8, 3) or (N, 4, 2). - det_score :torch.Tensor - The confidence score for each preditect bounding box. - gt_boxes : torch.Tensor - The groundtruth bounding box. - result_stat: dict - A dictionary contains fp, tp and gt number. - iou_thresh : float - The iou thresh. - """ - # fp, tp and gt in the current frame - fp = [] - tp = [] - gt = gt_boxes.shape[0] - if det_boxes is not None: - # convert bounding boxes to numpy array - det_boxes = common_utils.torch_tensor_to_numpy(det_boxes) - det_score = common_utils.torch_tensor_to_numpy(det_score) - gt_boxes = common_utils.torch_tensor_to_numpy(gt_boxes) - - # sort the prediction bounding box by score - score_order_descend = np.argsort(-det_score) - det_score = det_score[score_order_descend] # from high to low - det_polygon_list = list(common_utils.convert_format(det_boxes)) - gt_polygon_list = list(common_utils.convert_format(gt_boxes)) - - # match prediction and gt bounding box - for i in range(score_order_descend.shape[0]): - det_polygon = det_polygon_list[score_order_descend[i]] - ious = common_utils.compute_iou(det_polygon, gt_polygon_list) - - if len(gt_polygon_list) == 0 or np.max(ious) < iou_thresh: - fp.append(1) - tp.append(0) - continue - - fp.append(0) - tp.append(1) - - gt_index = np.argmax(ious) - gt_polygon_list.pop(gt_index) - - result_stat[iou_thresh]["score"] += det_score.tolist() - - result_stat[iou_thresh]["fp"] += fp - result_stat[iou_thresh]["tp"] += tp - result_stat[iou_thresh]["gt"] += gt - - -def calculate_ap(result_stat, iou, global_sort_detections): - """ - Calculate the average precision and recall, and save them into a txt. - - Parameters - ---------- - result_stat : dict - A dictionary contains fp, tp and gt number. - - iou : float - The threshold of iou. - - global_sort_detections : bool - Whether to sort the detection results globally. - """ - iou_5 = result_stat[iou] - - if global_sort_detections: - fp = np.array(iou_5["fp"]) - tp = np.array(iou_5["tp"]) - score = np.array(iou_5["score"]) - - assert len(fp) == len(tp) and len(tp) == len(score) - sorted_index = np.argsort(-score) - fp = fp[sorted_index].tolist() - tp = tp[sorted_index].tolist() - else: - fp = iou_5["fp"] - tp = iou_5["tp"] - assert len(fp) == len(tp) - - gt_total = iou_5["gt"] - - if gt_total == 0: - logger.warning("Variable gt_total is 0") - return 0.0, [0], [0] - - cumsum = 0 - for idx, val in enumerate(fp): - fp[idx] += cumsum - cumsum += val - - cumsum = 0 - for idx, val in enumerate(tp): - tp[idx] += cumsum - cumsum += val - - rec = tp[:] - for idx, val in enumerate(tp): - rec[idx] = float(tp[idx]) / gt_total - - prec = tp[:] - for idx, val in enumerate(tp): - prec[idx] = float(tp[idx]) / (fp[idx] + tp[idx]) - - ap, mrec, mprec = voc_ap(rec[:], prec[:]) - - return ap, mrec, mprec - - -def eval_final_results(result_stat, save_path, global_sort_detections): - dump_dict = {} - ap_30, mrec_30, mpre_30 = calculate_ap(result_stat, 0.30, global_sort_detections) - ap_50, mrec_50, mpre_50 = calculate_ap(result_stat, 0.50, global_sort_detections) - ap_70, mrec_70, mpre_70 = calculate_ap(result_stat, 0.70, global_sort_detections) - - dump_dict.update( - { - "ap_30": ap_30, - "ap_50": ap_50, - "ap_70": ap_70, - "mpre_30": mpre_30, - "mrec_30": mrec_30, - "mpre_50": mpre_50, - "mrec_50": mrec_50, - "mpre_70": mpre_70, - "mrec_70": mrec_70, - } - ) - - output_file = "eval.yaml" if not global_sort_detections else "eval_global_sort.yaml" - yaml_utils.save_yaml(dump_dict, os.path.join(save_path, output_file)) - - logger.info(f"The Average Precision at IOU 0.3 is {ap_30:.3f}") - logger.info(f"The Average Precision at IOU 0.5 is {ap_50:.3f}") - logger.info(f"The Average Precision at IOU 0.7 is {ap_70:.3f}") diff --git a/OpenCOOD/opencood/utils/pcd_utils.py b/OpenCOOD/opencood/utils/pcd_utils.py deleted file mode 100644 index 47a580e4..00000000 --- a/OpenCOOD/opencood/utils/pcd_utils.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -Utility functions related to point cloud -""" - -import open3d as o3d -import numpy as np - - -def pcd_to_np(pcd_file): - """ - Read pcd and return numpy array. - - Parameters - ---------- - pcd_file : str - The pcd file that contains the point cloud. - - Returns - ------- - pcd : o3d.PointCloud - PointCloud object, used for visualization - pcd_np : np.ndarray - The lidar data in numpy format, shape:(n, 4) - - """ - pcd = o3d.io.read_point_cloud(pcd_file) - - xyz = np.asarray(pcd.points) - # we save the intensity in the first channel - intensity = np.expand_dims(np.asarray(pcd.colors)[:, 0], -1) - pcd_np = np.hstack((xyz, intensity)) - - return np.asarray(pcd_np, dtype=np.float32) - - -def mask_points_by_range(points, limit_range): - """ - Remove the lidar points out of the boundary. - - Parameters - ---------- - points : np.ndarray - Lidar points under lidar sensor coordinate system. - - limit_range : list - [x_min, y_min, z_min, x_max, y_max, z_max] - - Returns - ------- - points : np.ndarray - Filtered lidar points. - """ - - mask = ( - (points[:, 0] > limit_range[0]) - & (points[:, 0] < limit_range[3]) - & (points[:, 1] > limit_range[1]) - & (points[:, 1] < limit_range[4]) - & (points[:, 2] > limit_range[2]) - & (points[:, 2] < limit_range[5]) - ) - - points = points[mask] - - return points - - -def mask_ego_points(points): - """ - Remove the lidar points of the ego vehicle itself. - - Parameters - ---------- - points : np.ndarray - Lidar points under lidar sensor coordinate system. - - Returns - ------- - points : np.ndarray - Filtered lidar points. - """ - mask = (points[:, 0] >= -1.95) & (points[:, 0] <= 2.95) & (points[:, 1] >= -1.1) & (points[:, 1] <= 1.1) - points = points[np.logical_not(mask)] - - return points - - -def shuffle_points(points): - shuffle_idx = np.random.permutation(points.shape[0]) - points = points[shuffle_idx] - - return points - - -def projected_lidar_stack(projected_lidar_list): - """ - Stack all projected lidar together. - - Parameters - ---------- - projected_lidar_list : list - The list containing all projected lidar. - - Returns - ------- - stack_lidar : np.ndarray - Stack all projected lidar data together. - """ - stack_lidar = [] - for lidar_data in projected_lidar_list: - stack_lidar.append(lidar_data) - - return np.vstack(stack_lidar) - - -def downsample_lidar(pcd_np, num): - """ - Downsample the lidar points to a certain number. - - Parameters - ---------- - pcd_np : np.ndarray - The lidar points, (n, 4). - - num : int - The downsample target number. - - Returns - ------- - pcd_np : np.ndarray - The downsampled lidar points. - """ - assert pcd_np.shape[0] >= num - - selected_index = np.random.choice((pcd_np.shape[0]), num, replace=False) - pcd_np = pcd_np[selected_index] - - return pcd_np - - -def downsample_lidar_minimum(pcd_np_list): - """ - Given a list of pcd, find the minimum number and downsample all - point clouds to the minimum number. - - Parameters - ---------- - pcd_np_list : list - A list of pcd numpy array(n, 4). - Returns - ------- - pcd_np_list : list - Downsampled point clouds. - """ - minimum = np.inf - - for i in range(len(pcd_np_list)): - num = pcd_np_list[i].shape[0] - minimum = num if minimum > num else minimum - - for i, pcd_np in enumerate(pcd_np_list): - pcd_np_list[i] = downsample_lidar(pcd_np, minimum) - - return pcd_np_list diff --git a/OpenCOOD/opencood/utils/transformation_utils.py b/OpenCOOD/opencood/utils/transformation_utils.py deleted file mode 100644 index 2e6756bd..00000000 --- a/OpenCOOD/opencood/utils/transformation_utils.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -Transformation utils -""" - -import numpy as np - - -def x_to_world(pose): - """ - The transformation matrix from x-coordinate system to carla world system - - Parameters - ---------- - pose : list - [x, y, z, roll, yaw, pitch] - - Returns - ------- - matrix : np.ndarray - The transformation matrix. - """ - x, y, z, roll, yaw, pitch = pose[:] - - # used for rotation matrix - c_y = np.cos(np.radians(yaw)) - s_y = np.sin(np.radians(yaw)) - c_r = np.cos(np.radians(roll)) - s_r = np.sin(np.radians(roll)) - c_p = np.cos(np.radians(pitch)) - s_p = np.sin(np.radians(pitch)) - - matrix = np.identity(4) - # translation matrix - matrix[0, 3] = x - matrix[1, 3] = y - matrix[2, 3] = z - - # rotation matrix - matrix[0, 0] = c_p * c_y - matrix[0, 1] = c_y * s_p * s_r - s_y * c_r - matrix[0, 2] = -c_y * s_p * c_r - s_y * s_r - matrix[1, 0] = s_y * c_p - matrix[1, 1] = s_y * s_p * s_r + c_y * c_r - matrix[1, 2] = -s_y * s_p * c_r + c_y * s_r - matrix[2, 0] = s_p - matrix[2, 1] = -c_p * s_r - matrix[2, 2] = c_p * c_r - - return matrix - - -def x1_to_x2(x1, x2): - """ - Transformation matrix from x1 to x2. - - Parameters - ---------- - x1 : list - The pose of x1 under world coordinates. - x2 : list - The pose of x2 under world coordinates. - - Returns - ------- - transformation_matrix : np.ndarray - The transformation matrix. - - """ - x1_to_world = x_to_world(x1) - x2_to_world = x_to_world(x2) - world_to_x2 = np.linalg.inv(x2_to_world) - - transformation_matrix = np.dot(world_to_x2, x1_to_world) - return transformation_matrix - - -def dist_to_continuous(p_dist, displacement_dist, res, downsample_rate): - """ - Convert points discretized format to continuous space for BEV representation. - Parameters - ---------- - p_dist : numpy.array - Points in discretized coorindates. - - displacement_dist : numpy.array - Discretized coordinates of bottom left origin. - - res : float - Discretization resolution. - - downsample_rate : int - Dowmsamping rate. - - Returns - ------- - p_continuous : numpy.array - Points in continuous coorindates. - - """ - p_dist = np.copy(p_dist) - p_dist = p_dist + displacement_dist - p_continuous = p_dist * res * downsample_rate - return p_continuous diff --git a/OpenCOOD/opencood/version.py b/OpenCOOD/opencood/version.py deleted file mode 100644 index e5aa9b5e..00000000 --- a/OpenCOOD/opencood/version.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Specifies the current version number of OpenCOOD.""" - -__version__ = "0.1.0" diff --git a/OpenCOOD/opencood/visualization/__init__.py b/OpenCOOD/opencood/visualization/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/visualization/simple_plot3d/__init__.py b/OpenCOOD/opencood/visualization/simple_plot3d/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/OpenCOOD/opencood/visualization/simple_plot3d/canvas_3d.py b/OpenCOOD/opencood/visualization/simple_plot3d/canvas_3d.py deleted file mode 100644 index 043612e3..00000000 --- a/OpenCOOD/opencood/visualization/simple_plot3d/canvas_3d.py +++ /dev/null @@ -1,339 +0,0 @@ -""" -Written by Jinhyung Park - -Simple 3D visualization for 3D points & boxes. Intended as a simple, hackable -alternative to mayavi for certain point cloud tasks. -""" - -import numpy as np -import cv2 -from matplotlib import colormaps - - -class Canvas_3D(object): - def __init__( - self, - canvas_shape=(500, 1000), - camera_center_coords=(-15, 0, 10), - camera_focus_coords=(-15 + 0.9396926, 0, 10 - 0.44202014), - # camera_center_coords=(-25, 0, 20), - # camera_focus_coords=(-25 + 0.9396926, 0, 20 - 0.64202014), - focal_length=None, - canvas_bg_color=(0, 0, 0), - left_hand=True, - ): - """ - Args: - canvas_shape (Tuple[Int]): Canvas image size - height & width. - camera_center_coords (Tuple[Float]): Location of camera center in - 3D space. - camera_focus_coords (Tuple[Float]): Intuitively, what point in 3D - space is the camera pointed at? These are absolute coordinates, - *not* relative to camera center. - focal_length (None | Int): - None: Half of the max of height & width of canvas_shape. This - seems to be a decent default. - Int: Specified directly. - canvas_bg_color (Tuple[Int]): RGB (0 ~ 255) of canvas background - color. - left_hand: bool - Since this algorithm is designed for right hand coord. We take -y if True - """ - - self.canvas_shape = canvas_shape - self.H, self.W = self.canvas_shape - self.canvas_bg_color = canvas_bg_color - self.left_hand = left_hand - if left_hand: - camera_center_coords = list(camera_center_coords) - camera_center_coords[1] = -camera_center_coords[1] - camera_center_coords = tuple(camera_center_coords) - - camera_focus_coords = list(camera_focus_coords) - camera_focus_coords[1] = -camera_focus_coords[1] - camera_focus_coords = tuple(camera_focus_coords) - - self.camera_center_coords = camera_center_coords - self.camera_focus_coords = camera_focus_coords - - if focal_length is None: - self.focal_length = max(self.H, self.W) // 2 - else: - self.focal_length = focal_length - - # Setup extrinsics and intrinsics of this virtual camera. - self.ext_matrix = self.get_extrinsic_matrix(self.camera_center_coords, self.camera_focus_coords) - self.int_matrix = np.array( - [ - [self.focal_length, 0, self.W // 2, 0], - [0, self.focal_length, self.H // 2, 0], - [0, 0, 1, 0], - ] - ) - - self.clear_canvas() - - def clear_canvas(self): - self.canvas = np.zeros((self.H, self.W, 3), dtype=np.uint8) - self.canvas[..., :] = self.canvas_bg_color - - def get_canvas_coords(self, xyz, depth_min=0.1, return_depth=False): - """ - Projects XYZ points onto the canvas and returns the projected canvas - coordinates. - - Args: - xyz (ndarray): (N, 3+) array of coordinates. Additional columns - beyond the first three are ignored. - depth_min (Float): Only points with a projected depth larger - than this value are "valid". - return_depth (Boolean): Whether to additionally return depth of - projected points. - Returns: - canvas_xy (ndarray): (N, 2) array of projected canvas coordinates. - "x" is dim0, "y" is dim1 of canvas. - valid_mask (ndarray): (N,) boolean mask indicating which of - canvas_xy fits into canvas (are visible from virtual camera). - depth (ndarray): Optionally returned (N,) array of depth values - """ - if xyz.ndim == 1: - xyz = xyz.reshape(1, -1) - - if self.left_hand: - xyz[:, 1] = -xyz[:, 1] - - xyz = xyz[:, :3] - xyz_hom = np.concatenate([xyz, np.ones((xyz.shape[0], 1), dtype=np.float32)], axis=1) - img_pts = (self.int_matrix @ self.ext_matrix @ xyz_hom.T).T - - depth = img_pts[:, 2] - xy = img_pts[:, :2] / depth[:, None] - xy_int = xy.round().astype(np.int32) - - # Flip X and Y so "x" is dim0, "y" is dim1 of canvas - xy_int = xy_int[:, ::-1] - - valid_mask = (depth > depth_min) & (xy_int[:, 0] >= 0) & (xy_int[:, 0] < self.H) & (xy_int[:, 1] >= 0) & (xy_int[:, 1] < self.W) - - if return_depth: - return xy_int, valid_mask, depth - else: - return xy_int, valid_mask - - def draw_canvas_points(self, canvas_xy, radius=-1, colors=None, colors_operand=None): - """ - Draws canvas_xy onto self.canvas. - - Args: - canvas_xy (ndarray): (N, 2) array of *valid* canvas coordinates. - "x" is dim0, "y" is dim1 of canvas. - radius (Int): - -1: Each point is visualized as a single pixel. - r: Each point is visualized as a circle with radius r. - colors: - None: colors all points white. - Tuple: RGB (0 ~ 255), indicating a single color for all points. - ndarray: (N, 3) array of RGB values for each point. - String: Such as "Spectral", uses a Matplotlib colormap, with the - operand (the value the colormap is called on for each point) being - colors_operand. - colors_operand (ndarray): (N,) array of values corresponding to - canvas_xy, to be used only if colors is a colormap name. Unlike - Canvas_BEV, cannot be None if colors is a String. - """ - if len(canvas_xy) == 0: - return - - if colors is None: - colors = np.full((len(canvas_xy), 3), fill_value=255, dtype=np.uint8) - elif isinstance(colors, tuple): - assert len(colors) == 3 - colors_tmp = np.zeros((len(canvas_xy), 3), dtype=np.uint8) - colors_tmp[..., : len(colors)] = np.array(colors) - colors = colors_tmp - elif isinstance(colors, np.ndarray): - assert len(colors) == len(canvas_xy) - colors = colors.astype(np.uint8) - elif isinstance(colors, str): - assert colors_operand is not None - cmap = colormaps.get_cmap(colors) - - # Normalize to 0 ~ 1 for colormap - colors_operand = colors_operand - colors_operand.min() - colors_operand = colors_operand / colors_operand.max() - - # Get colormap colors - note that a colormap returns (*input_shape, 4), - # with colors scaled 0 ~ 1 - colors = (cmap(colors_operand)[:, :3] * 255).astype(np.uint8) - else: - raise Exception("colors type {} was not an expected type".format(type(colors))) - - if radius == -1: - self.canvas[canvas_xy[:, 0], canvas_xy[:, 1], :] = colors - else: - for color, (x, y) in zip(colors.tolist(), canvas_xy.tolist()): - self.canvas = cv2.circle(self.canvas, (y, x), radius, color, -1, lineType=cv2.LINE_AA) - - def draw_boxes( - self, boxes, colors=None, texts=None, depth_min=0.1, draw_incomplete_boxes=False, box_line_thickness=2, box_text_size=0.5, text_corner=1 - ): - """ - Draws 3D boxes. - - Args: - boxes (ndarray): Shape (N, 8, 3), corners in 3d - modified from original repo - - colors: - None: colors all points white. - Tuple: RGB (0 ~ 255), indicating a single color for all points. - ndarray: (N, 3) array of RGB values for each point. - - texts (List[String]): Length N; text to write next to boxes. - - depth_min (Float): Only box corners with a projected depth larger - than this value are drawn if draw_incomplete_boxes is True. - - draw_incomplete_boxes (Boolean): If any boxes are incomplete, - meaning it has a corner out of view based on depth_min, decide - whether to draw them at all. - - thickness (Int): - Thickness of drawn cv2 box lines. - - box_line_thickness (int): cv2 line/text thickness - box_text_size (float): cv2 putText size - text_corner (int): 0 ~ 7. Which corner of 3D box to write text at. - """ - # Setup colors - if colors is None: - colors = np.full((len(boxes), 3), fill_value=255, dtype=np.uint8) - elif isinstance(colors, tuple): - assert len(colors) == 3 - colors_tmp = np.zeros((len(boxes), 3), dtype=np.uint8) - colors_tmp[..., : len(colors)] = np.array(colors) - colors = colors_tmp - elif isinstance(colors, np.ndarray): - assert len(colors) == len(boxes) - colors = colors.astype(np.uint8) - else: - raise Exception("colors type {} was not an expected type".format(type(colors))) - - corners = boxes # N x 8 x 3 - - # Now we have corners. Need them on the canvas 2D space. - corners_xy, valid_mask = self.get_canvas_coords(corners.reshape(-1, 3), depth_min=depth_min) - corners_xy = corners_xy.reshape(-1, 8, 2) - valid_mask = valid_mask.reshape(-1, 8) - - # Now draw them with lines in correct places - for i, (color, curr_corners_xy, curr_valid_mask) in enumerate(zip(colors.tolist(), corners_xy.tolist(), valid_mask.tolist())): - if not draw_incomplete_boxes and sum(curr_valid_mask) != 8: - # Some corner is invalid, don't draw the box at all. - continue - - for start, end in [(0, 1), (1, 2), (2, 3), (3, 0), (0, 4), (1, 5), (2, 6), (3, 7), (4, 5), (5, 6), (6, 7), (7, 4)]: - if not (curr_valid_mask[start] and curr_valid_mask[end]): - continue # start or end is not valid - - self.canvas = cv2.line( - self.canvas, - (curr_corners_xy[start][1], curr_corners_xy[start][0]), - (curr_corners_xy[end][1], curr_corners_xy[end][0]), - color=color, - thickness=box_line_thickness, - lineType=cv2.LINE_AA, - ) - - # If even a single line was drawn, add text as well. - if sum(curr_valid_mask) > 0: - if texts is not None: - self.canvas = cv2.putText( - self.canvas, - str(texts[i]), - (curr_corners_xy[text_corner][1], curr_corners_xy[text_corner][0]), - cv2.FONT_HERSHEY_SIMPLEX, - box_text_size, - color, - thickness=box_line_thickness, - ) - - @staticmethod - def cart2sph(xyz): - x, y, z = xyz[:, 0], xyz[:, 1], xyz[:, 2] - - depth = np.linalg.norm(xyz, 2, axis=1) - az = -np.arctan2(y, x) - el = np.arcsin(z / depth) - return az, el, depth - - @staticmethod - def get_extrinsic_matrix( - camera_center_coords, - camera_focus_coords, - ): - """ - Args: - camera_center_coords: (x, y, z) of where camera should be located - in 3D space. - camera_focus_coords: (x, y, z) of where camera should look at from - camera_center_coords - - Thoughts: - Remember that in camera coordiantes, pos x is right, pos y is up, - pos z is forward. - """ - center_x, center_y, center_z = camera_center_coords - focus_x, focus_y, focus_z = camera_focus_coords - az, el, _depth = Canvas_3D.cart2sph(np.array([[focus_x - center_x, focus_y - center_y, focus_z - center_z]])) - az = np.asarray(az).item() - el = np.asarray(el).item() - - ### First, construct extrinsics - ## Rotation matrix - - z_rot = np.array([[np.cos(az), -np.sin(az), 0], [np.sin(az), np.cos(az), 0], [0, 0, 1]]) - - # el is rotation around y axis. - y_rot = np.array( - [ - [np.cos(-el), 0, -np.sin(-el)], - [0, 1, 0], - [np.sin(-el), 0, np.cos(-el)], - ] - ) - - ## Now, how the z_rot and y_rot work (spherical coordiantes), is it - ## computes rotations starting from the positive x axis and rotates - ## positive x axis to the desired direction. The desired direction is - ## the "looking direction" of the camera, which should actually be the - ## z-axis. So should convert the points so that the x axis is the new z - ## axis, and after the transformations. - ## Why x -> z for points? If we think about rotating the camera, z - ## should become x, so reverse when moving points. - last_rot = np.array( - [ - [0, -1, 0], - [0, 0, -1], - [1, 0, 0], # x -> z - ] - ) - - # Put them together. Order matters. Make it hom. - rot_matrix = np.eye(4, dtype=np.float32) - rot_matrix[:3, :3] = last_rot @ y_rot @ z_rot - - ## Translation matrix - trans_matrix = np.array( - [ - [1, 0, 0, -center_x], - [0, 1, 0, -center_y], - [0, 0, 1, -center_z], - [0, 0, 0, 1], - ] - ) - - ## Finally, extrinsics matrix. Order matters - do trans then rot - ext_matrix = rot_matrix @ trans_matrix - - return ext_matrix diff --git a/OpenCOOD/opencood/visualization/simple_plot3d/canvas_bev.py b/OpenCOOD/opencood/visualization/simple_plot3d/canvas_bev.py deleted file mode 100644 index 0aeef6dd..00000000 --- a/OpenCOOD/opencood/visualization/simple_plot3d/canvas_bev.py +++ /dev/null @@ -1,229 +0,0 @@ -""" -Written by Jinhyung Park - -Simple BEV visualization for 3D points & boxes. -""" - -import numpy as np -import cv2 -from matplotlib import colormaps - - -class Canvas_BEV_heading_right(object): - def __init__(self, canvas_shape=(800, 2800), canvas_x_range=(-140, 140), canvas_y_range=(-40, 40), canvas_bg_color=(0, 0, 0), left_hand=True): - """ - Args: - canvas_shape (Tuple[int]): Shape of BEV Canvas image. First element - corresponds to Y range, the second element to X range. - canvas_x_range (Tuple[int]): Range of X-coords to visualize. X is - horizontal: negative ~ positive is left ~ right. - canvas_y_range (Tuple[int]): Range of Y-coords to visualize. Y is - vertcal: negative ~ positive is top ~ down. - canvas_bg_color (Tuple[int]): RGB (0 ~ 255) of Canvas background - color. - left_hand: (bool), whether the point cloud is left-hand coordinate - V2X-Sim is right hand, and OPV2V is left hand. - - Different from Canvas_BEV, the vehicle is heading right. - Naturally this code is designed for left hand coordinate - - """ - - # Sanity check ratios - if (canvas_shape[1] / canvas_shape[0]) != ((canvas_x_range[0] - canvas_x_range[1]) / (canvas_y_range[0] - canvas_y_range[1])): - print("Not an error, but the x & y ranges are not proportional to canvas height & width.") - - self.canvas_shape = canvas_shape - self.canvas_x_range = canvas_x_range - self.canvas_y_range = canvas_y_range - self.canvas_bg_color = canvas_bg_color - self.left_hand = left_hand - - self.clear_canvas() - - def clear_canvas(self): - self.canvas = np.zeros((*self.canvas_shape, 3), dtype=np.uint8) - self.canvas[..., :] = self.canvas_bg_color - - def get_canvas_coords(self, xy): - """ - Args: - xy (ndarray): (N, 2+) array of coordinates. Additional columns - beyond the first two are ignored. - - Returns: - canvas_xy (ndarray): (N, 2) array of xy scaled into canvas - coordinates. Invalid locations of canvas_xy are clipped into - range. "x" is dim0, "y" is dim1 of canvas. - valid_mask (ndarray): (N,) boolean mask indicating which of - canvas_xy fits into canvas. - """ - xy = np.copy(xy) # prevent in-place modifications - - if xy.ndim == 1: - xy = xy.reshape(1, -1) - - x = xy[:, 0] - y = xy[:, 1] - - if not self.left_hand: - y = -y - - # Get valid mask - valid_mask = (x > self.canvas_x_range[0]) & (x < self.canvas_x_range[1]) & (y > self.canvas_y_range[0]) & (y < self.canvas_y_range[1]) - - # Rescale points - # They are exactly lidar point coordinate - x = (x - self.canvas_x_range[0]) / (self.canvas_x_range[1] - self.canvas_x_range[0]) - x = x * self.canvas_shape[1] - x = np.clip(np.around(x), 0, self.canvas_shape[1] - 1).astype(np.int32) # [0,2800-1] - - y = (y - self.canvas_y_range[0]) / (self.canvas_y_range[1] - self.canvas_y_range[0]) - y = y * self.canvas_shape[0] - y = np.clip(np.around(y), 0, self.canvas_shape[0] - 1).astype(np.int32) # [0,800-1] - - # x and y are exactly image coordinate - # ------------> x - # | - # | - # | - # y - - canvas_xy = np.stack([x, y], axis=1) - - return canvas_xy, valid_mask - - def draw_canvas_points(self, canvas_xy, radius=-1, colors=None, colors_operand=None): - """ - Draws canvas_xy onto self.canvas. - - Args: - canvas_xy (ndarray): (N, 2) array of *valid* canvas coordinates. - - radius (Int): - -1: Each point is visualized as a single pixel. - r: Each point is visualized as a circle with radius r. - colors: - None: colors all points white. - Tuple: RGB (0 ~ 255), indicating a single color for all points. - ndarray: (N, 3) array of RGB values for each point. - String: Such as "Spectral", uses a Matplotlib colormap, with the - operand (the value the colormap is called on for each point) being - colors_operand. If colors_operand is None, uses normalized - distance from (0, 0) of XY point coords. - colors_operand (ndarray | None): (N,) array of values corresponding - to canvas_xy, to be used only if colors is a colormap name. - """ - if len(canvas_xy) == 0: - return - - if colors is None: - colors = np.full((len(canvas_xy), 3), fill_value=255, dtype=np.uint8) - elif isinstance(colors, tuple): - assert len(colors) == 3 - colors_tmp = np.zeros((len(canvas_xy), 3), dtype=np.uint8) - colors_tmp[..., :] = np.array(colors) - colors = colors_tmp - elif isinstance(colors, np.ndarray): - assert len(colors) == len(canvas_xy) - colors = colors.astype(np.uint8) - elif isinstance(colors, str): - cmap = colormaps.get_cmap(colors) - if colors_operand is None: - # Get distances from (0, 0) (albeit potentially clipped) - origin_center = self.get_canvas_coords(np.zeros((1, 2)))[0][0] - colors_operand = np.sqrt(((canvas_xy - origin_center) ** 2).sum(axis=1)) - - # Normalize to 0 ~ 1 for colormap - colors_operand = colors_operand - colors_operand.min() - colors_operand = colors_operand / colors_operand.max() - - # Get colormap colors - note that a colormap returns (*input_shape, 4), - # with colors scaled 0 ~ 1 - colors = (cmap(colors_operand)[:, :3] * 255).astype(np.uint8) - else: - raise Exception("colors type {} was not an expected type".format(type(colors))) - - # Here the order is different from Canvas_BEV - if radius == -1: - self.canvas[canvas_xy[:, 1], canvas_xy[:, 0], :] = colors - else: - for color, (x, y) in zip(colors.tolist(), canvas_xy.tolist()): - self.canvas = cv2.circle(self.canvas, (x, y), radius, color, -1, lineType=cv2.LINE_AA) - - def draw_boxes(self, boxes, colors=None, texts=None, box_line_thickness=2, box_text_size=0.5, text_corner=0): - """ - Draws a set of boxes onto the canvas. - Args: - boxes (ndarray): [N, 8, 3] corner 3d - - colors: - None: colors all points white. - Tuple: RGB (0 ~ 255), indicating a single color for all points. - ndarray: (N, 3) array of RGB values for each point. - texts (List[String]): Length N; text to write next to boxes. - box_line_thickness (int): cv2 line/text thickness - box_text_size (float): cv2 putText size - text_corner (int): 0 ~ 3. Which corner of 3D box to write text at. - """ - # Setup colors - if colors is None: - colors = np.full((len(boxes), 3), fill_value=255, dtype=np.uint8) - elif isinstance(colors, tuple): - assert len(colors) == 3 - colors_tmp = np.zeros((len(boxes), 3), dtype=np.uint8) - colors_tmp[..., : len(colors)] = np.array(colors) - colors = colors_tmp - elif isinstance(colors, np.ndarray): - assert len(colors) == len(boxes) - colors = colors.astype(np.uint8) - else: - raise Exception("colors type {} was not an expected type".format(type(colors))) - - boxes = np.copy(boxes) # prevent in-place modifications - - # Translate BEV 4 corners , [N, 4, 2] - # 4 -------- 5 - # /| /| - # 7 -------- 6 . - # | | | | - # . 0 -------- 1 - # |/ |/ - # 3 -------- 2 - bev_corners = boxes[:, :4, :2] - - ## Transform BEV 4 corners to canvas coords - bev_corners_canvas, valid_mask = self.get_canvas_coords(bev_corners.reshape(-1, 2)) # [N, 2] - bev_corners_canvas = bev_corners_canvas.reshape(*bev_corners.shape) # [N, 4, 2] - valid_mask = valid_mask.reshape(*bev_corners.shape[:-1]) - - # At least 1 corner in canvas to draw. - valid_mask = valid_mask.sum(axis=1) > 0 - bev_corners_canvas = bev_corners_canvas[valid_mask] - if texts is not None: - texts = np.array(texts)[valid_mask] - - ## Draw onto canvas - # Draw the outer boundaries - idx_draw_pairs = [(0, 1), (1, 2), (2, 3), (3, 0)] - for i, (color, curr_box_corners) in enumerate(zip(colors.tolist(), bev_corners_canvas)): - curr_box_corners = curr_box_corners.astype(np.int32) - for start, end in idx_draw_pairs: - # Notice Difference Here - self.canvas = cv2.line( - self.canvas, - tuple(curr_box_corners[start].tolist()), - tuple(curr_box_corners[end].tolist()), - color=color, - thickness=box_line_thickness, - ) - if texts is not None: - self.canvas = cv2.putText( - self.canvas, - str(texts[i]), - tuple(curr_box_corners[text_corner].tolist()), - cv2.FONT_HERSHEY_SIMPLEX, - box_text_size, - color=color, - thickness=box_line_thickness, - ) diff --git a/OpenCOOD/opencood/visualization/simple_vis.py b/OpenCOOD/opencood/visualization/simple_vis.py deleted file mode 100644 index 0e8b3730..00000000 --- a/OpenCOOD/opencood/visualization/simple_vis.py +++ /dev/null @@ -1,123 +0,0 @@ -from matplotlib import pyplot as plt -import numpy as np -import logging - -import opencood.visualization.simple_plot3d.canvas_3d as canvas_3d -import opencood.visualization.simple_plot3d.canvas_bev as canvas_bev - -logger = logging.getLogger("cavise.opencda.OpenCOOD.opencood.visualization.simple_vis") - - -def visualize( - pred_box_tensor, gt_tensor, pcd, pc_range, save_path, method="3d", vis_gt_box=True, vis_pred_box=True, left_hand=False, uncertainty=None -): - """ - Visualize the prediction, ground truth with point cloud together. - They may be flipped in y axis. Since carla is left hand coordinate, while kitti is right hand. - - Parameters - ---------- - pred_box_tensor : torch.Tensor - (N, 8, 3) prediction. - - gt_tensor : torch.Tensor - (N, 8, 3) groundtruth bbx - - pcd : torch.Tensor - PointCloud, (N, 4). - - pc_range : list - [xmin, ymin, zmin, xmax, ymax, zmax] - - save_path : str - Save the visualization results to given path. - - dataset : BaseDataset - opencood dataset object. - - method: str, 'bev' or '3d' - - """ - - pc_range = [int(i) for i in pc_range] - if isinstance(pcd, list): - pcd_np = [x.cpu().numpy() for x in pcd] - else: - pcd_np = pcd.cpu().numpy() - - if vis_pred_box and pred_box_tensor is not None: - pred_box_np = pred_box_tensor.cpu().numpy() - # pred_name = ['pred'] * pred_box_np.shape[0] - pred_name = [""] * pred_box_np.shape[0] - if uncertainty is not None: - uncertainty_np = uncertainty.cpu().numpy() - uncertainty_np = np.exp(uncertainty_np) - d_a_square = 1.6**2 + 3.9**2 - - if uncertainty_np.shape[1] == 3: - uncertainty_np[:, :2] *= d_a_square - uncertainty_np = np.sqrt(uncertainty_np) - # yaw angle is in radian, it's the same in g2o SE2's setting. - - pred_name = [ - f"x_u:{uncertainty_np[i, 0]:.3f} y_u:{uncertainty_np[i, 1]:.3f} a_u:{uncertainty_np[i, 2]:.3f}" - for i in range(uncertainty_np.shape[0]) - ] - - elif uncertainty_np.shape[1] == 2: - uncertainty_np[:, :2] *= d_a_square - uncertainty_np = np.sqrt(uncertainty_np) # yaw angle is in radian - - pred_name = [f"x_u:{uncertainty_np[i, 0]:.3f} y_u:{uncertainty_np[i, 1]:3f}" for i in range(uncertainty_np.shape[0])] - - elif uncertainty_np.shape[1] == 7: - uncertainty_np[:, :2] *= d_a_square - uncertainty_np = np.sqrt(uncertainty_np) # yaw angle is in radian - - pred_name = [ - f"x_u:{uncertainty_np[i, 0]:.3f} y_u:{uncertainty_np[i, 1]:3f} a_u:{uncertainty_np[i, 6]:3f}" - for i in range(uncertainty_np.shape[0]) - ] - else: - logger.warning("pred_box_tensor is None") - - if vis_gt_box: - gt_box_np = gt_tensor.cpu().numpy() - # gt_name = ['gt'] * gt_box_np.shape[0] - gt_name = [""] * gt_box_np.shape[0] - - if method == "bev": - canvas = canvas_bev.Canvas_BEV_heading_right( - canvas_shape=((pc_range[4] - pc_range[1]) * 10, (pc_range[3] - pc_range[0]) * 10), - canvas_x_range=(pc_range[0], pc_range[3]), - canvas_y_range=(pc_range[1], pc_range[4]), - left_hand=left_hand, - ) - - canvas_xy, valid_mask = canvas.get_canvas_coords(pcd_np) # Get Canvas Coords - canvas.draw_canvas_points(canvas_xy[valid_mask]) - # color_list = [(0, 206, 209),(255, 215,0)] - # for i, pcd_np_t in enumerate(pcd_np[1:2]): - # canvas_xy, valid_mask = canvas.get_canvas_coords(pcd_np_t) # Get Canvas Coords - # canvas.draw_canvas_points(canvas_xy[valid_mask], colors=color_list[i-1]) # Only draw valid points - box_line_thickness = 5 - if vis_gt_box: - # canvas.draw_boxes(gt_box_np,colors=(0,255,0), texts=gt_name) - canvas.draw_boxes(gt_box_np, colors=(0, 255, 0), texts=gt_name, box_line_thickness=box_line_thickness) - - if vis_pred_box and pred_box_tensor is not None: - canvas.draw_boxes(pred_box_np, colors=(255, 0, 0), texts=pred_name, box_line_thickness=box_line_thickness) - - elif method == "3d": - canvas = canvas_3d.Canvas_3D(left_hand=left_hand) - canvas_xy, valid_mask = canvas.get_canvas_coords(pcd_np) - canvas.draw_canvas_points(canvas_xy[valid_mask]) - - if vis_gt_box: - canvas.draw_boxes(gt_box_np, colors=(0, 255, 0), texts=gt_name) - if vis_pred_box and pred_box_tensor is not None: - canvas.draw_boxes(pred_box_np, colors=(255, 0, 0), texts=pred_name) - else: - raise (f"Not Completed for f{method} visualization.") - - plt.imsave(save_path, canvas.canvas, dpi=400) diff --git a/OpenCOOD/opencood/visualization/vis_data_sequence.py b/OpenCOOD/opencood/visualization/vis_data_sequence.py deleted file mode 100644 index 69edacc3..00000000 --- a/OpenCOOD/opencood/visualization/vis_data_sequence.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -import argparse -from torch.utils.data import DataLoader - -from opencood.hypes_yaml.yaml_utils import load_yaml -from opencood.visualization import vis_utils -from opencood.data_utils.datasets.early_fusion_vis_dataset import EarlyFusionVisDataset - - -def vis_parser(): - parser = argparse.ArgumentParser(description="data visualization") - parser.add_argument("--color_mode", type=str, default="intensity", help="lidar color rendering mode, e.g. intensity,z-value or constant.") - opt = parser.parse_args() - return opt - - -if __name__ == "__main__": - current_path = os.path.dirname(os.path.realpath(__file__)) - params = load_yaml(os.path.join(current_path, "../hypes_yaml/visualization.yaml")) - - opencda_dataset = EarlyFusionVisDataset(params, visualize=True, train=False) - data_loader = DataLoader( - opencda_dataset, batch_size=1, num_workers=8, collate_fn=opencda_dataset.collate_batch_train, shuffle=False, pin_memory=False - ) - - opt = vis_parser() - vis_utils.visualize_sequence_dataloader(data_loader, params["postprocess"]["order"], color_mode=opt.color_mode) diff --git a/OpenCOOD/opencood/visualization/vis_utils.py b/OpenCOOD/opencood/visualization/vis_utils.py deleted file mode 100644 index 7a603229..00000000 --- a/OpenCOOD/opencood/visualization/vis_utils.py +++ /dev/null @@ -1,513 +0,0 @@ -import time - -import cv2 -import numpy as np -import open3d as o3d -import matplotlib -import matplotlib.pyplot as plt - -from matplotlib import colormaps as cm - -from opencood.utils import box_utils -from opencood.utils import common_utils - -VIRIDIS = np.array(cm.get_cmap("plasma").colors) -VID_RANGE = np.linspace(0.0, 1.0, VIRIDIS.shape[0]) - - -def bbx2linset(bbx_corner, order="hwl", color=(0, 1, 0)): - """ - Convert the torch tensor bounding box to o3d lineset for visualization. - - Parameters - ---------- - bbx_corner : torch.Tensor - shape: (n, 8, 3). - - order : str - The order of the bounding box if shape is (n, 7) - - color : tuple - The bounding box color. - - Returns - ------- - line_set : list - The list containing linsets. - """ - if not isinstance(bbx_corner, np.ndarray): - bbx_corner = common_utils.torch_tensor_to_numpy(bbx_corner) - - if len(bbx_corner.shape) == 2: - bbx_corner = box_utils.boxes_to_corners_3d(bbx_corner, order) - - # Our lines span from points 0 to 1, 1 to 2, 2 to 3, etc... - lines = [[0, 1], [1, 2], [2, 3], [0, 3], [4, 5], [5, 6], [6, 7], [4, 7], [0, 4], [1, 5], [2, 6], [3, 7]] - - # Use the same color for all lines - colors = [list(color) for _ in range(len(lines))] - bbx_linset = [] - - for i in range(bbx_corner.shape[0]): - bbx = bbx_corner[i] - # o3d use right-hand coordinate - bbx[:, :1] = -bbx[:, :1] - - line_set = o3d.geometry.LineSet() - line_set.points = o3d.utility.Vector3dVector(bbx) - line_set.lines = o3d.utility.Vector2iVector(lines) - line_set.colors = o3d.utility.Vector3dVector(colors) - bbx_linset.append(line_set) - - return bbx_linset - - -def bbx2oabb(bbx_corner, order="hwl", color=(0, 0, 1)): - """ - Convert the torch tensor bounding box to o3d oabb for visualization. - - Parameters - ---------- - bbx_corner : torch.Tensor - shape: (n, 8, 3). - - order : str - The order of the bounding box if shape is (n, 7) - - color : tuple - The bounding box color. - - Returns - ------- - oabbs : list - The list containing all oriented bounding boxes. - """ - if not isinstance(bbx_corner, np.ndarray): - bbx_corner = common_utils.torch_tensor_to_numpy(bbx_corner) - - if len(bbx_corner.shape) == 2: - bbx_corner = box_utils.boxes_to_corners_3d(bbx_corner, order) - oabbs = [] - - for i in range(bbx_corner.shape[0]): - bbx = bbx_corner[i] - # o3d use right-hand coordinate - bbx[:, :1] = -bbx[:, :1] - - tmp_pcd = o3d.geometry.PointCloud() - tmp_pcd.points = o3d.utility.Vector3dVector(bbx) - - oabb = tmp_pcd.get_oriented_bounding_box() - oabb.color = color - oabbs.append(oabb) - - return oabbs - - -def linset_assign_list(vis, lineset_list1, lineset_list2, update_mode="update"): - """ - Associate two lists of lineset. - - Parameters - ---------- - vis : open3d.Visualizer - lineset_list1 : list - lineset_list2 : list - update_mode : str - Add or update the geometry. - """ - for j in range(len(lineset_list1)): - index = j if j < len(lineset_list2) else -1 - if len(lineset_list2): - lineset_list1[j] = lineset_assign(lineset_list1[j], lineset_list2[index]) - if update_mode == "add": - vis.add_geometry(lineset_list1[j]) - else: - vis.update_geometry(lineset_list1[j]) - - -def lineset_assign(lineset1, lineset2): - """ - Assign the attributes of lineset2 to lineset1. - - Parameters - ---------- - lineset1 : open3d.LineSet - lineset2 : open3d.LineSet - - Returns - ------- - The lineset1 object with 2's attributes. - """ - - lineset1.points = lineset2.points - lineset1.lines = lineset2.lines - lineset1.colors = lineset2.colors - - return lineset1 - - -def color_encoding(intensity, mode="intensity"): - """ - Encode the single-channel intensity to 3 channels rgb color. - - Parameters - ---------- - intensity : np.ndarray - Lidar intensity, shape (n,) - - mode : str - The color rendering mode. intensity, z-value and constant are - supported. - - Returns - ------- - color : np.ndarray - Encoded Lidar color, shape (n, 3) - """ - assert mode in ["intensity", "z-value", "constant"] - - if mode == "intensity": - intensity_col = 1.0 - np.log(intensity) / np.log(np.exp(-0.004 * 100)) - int_color = np.c_[ - np.interp(intensity_col, VID_RANGE, VIRIDIS[:, 0]), - np.interp(intensity_col, VID_RANGE, VIRIDIS[:, 1]), - np.interp(intensity_col, VID_RANGE, VIRIDIS[:, 2]), - ] - - elif mode == "z-value": - min_value = -1.5 - max_value = 0.5 - norm = matplotlib.colors.Normalize(vmin=min_value, vmax=max_value) - cmap = cm.jet - m = cm.ScalarMappable(norm=norm, cmap=cmap) - - colors = m.to_rgba(intensity) - colors[:, [2, 1, 0, 3]] = colors[:, [0, 1, 2, 3]] - colors[:, 3] = 0.5 - int_color = colors[:, :3] - - elif mode == "constant": - # regard all point cloud the same color - int_color = np.ones((intensity.shape[0], 3)) - int_color[:, 0] *= 247 / 255 - int_color[:, 1] *= 244 / 255 - int_color[:, 2] *= 237 / 255 - - return int_color - - -def visualize_single_sample_output_gt(pred_tensor, gt_tensor, pcd, show_vis=True, save_path="", mode="constant"): - """ - Visualize the prediction, groundtruth with point cloud together. - - Parameters - ---------- - pred_tensor : torch.Tensor - (N, 8, 3) prediction. - - gt_tensor : torch.Tensor - (N, 8, 3) groundtruth bbx - - pcd : torch.Tensor - PointCloud, (N, 4). - - show_vis : bool - Whether to show visualization. - - save_path : str - Save the visualization results to given path. - - mode : str - Color rendering mode. - """ - - def custom_draw_geometry(pcd, pred, gt): - vis = o3d.visualization.Visualizer() - vis.create_window() - - opt = vis.get_render_option() - opt.background_color = np.asarray([0, 0, 0]) # noqa: DC05 - opt.point_size = 1.0 # noqa: DC05 - - vis.add_geometry(pcd) - for ele in pred: - vis.add_geometry(ele) - for ele in gt: - vis.add_geometry(ele) - - vis.run() - vis.destroy_window() - - if len(pcd.shape) == 3: - pcd = pcd[0] - origin_lidar = pcd - if not isinstance(pcd, np.ndarray): - origin_lidar = common_utils.torch_tensor_to_numpy(pcd) - - origin_lidar_intcolor = color_encoding(origin_lidar[:, -1] if mode == "intensity" else origin_lidar[:, 2], mode=mode) - # left -> right hand - origin_lidar[:, :1] = -origin_lidar[:, :1] - - o3d_pcd = o3d.geometry.PointCloud() - o3d_pcd.points = o3d.utility.Vector3dVector(origin_lidar[:, :3]) - o3d_pcd.colors = o3d.utility.Vector3dVector(origin_lidar_intcolor) - - oabbs_pred = bbx2oabb(pred_tensor, color=(1, 0, 0)) - oabbs_gt = bbx2oabb(gt_tensor, color=(0, 1, 0)) - - visualize_elements = [o3d_pcd] + oabbs_pred + oabbs_gt - if show_vis: - custom_draw_geometry(o3d_pcd, oabbs_pred, oabbs_gt) - if save_path: - save_o3d_visualization(visualize_elements, save_path) - - -def visualize_single_sample_output_bev(pred_box, gt_box, pcd, dataset, show_vis=True, save_path=""): - """ - Visualize the prediction, groundtruth with point cloud together in - a bev format. - - Parameters - ---------- - pred_box : torch.Tensor - (N, 4, 2) prediction. - - gt_box : torch.Tensor - (N, 4, 2) groundtruth bbx - - pcd : torch.Tensor - PointCloud, (N, 4). - - show_vis : bool - Whether to show visualization. - - save_path : str - Save the visualization results to given path. - """ - - if not isinstance(pcd, np.ndarray): - pcd = common_utils.torch_tensor_to_numpy(pcd) - if pred_box is not None and not isinstance(pred_box, np.ndarray): - pred_box = common_utils.torch_tensor_to_numpy(pred_box) - if gt_box is not None and not isinstance(gt_box, np.ndarray): - gt_box = common_utils.torch_tensor_to_numpy(gt_box) - - ratio = dataset.params["preprocess"]["args"]["res"] - L1, W1, H1, L2, W2, _ = dataset.params["preprocess"]["cav_lidar_range"] - bev_origin = np.array([L1, W1]).reshape(1, -1) - # (img_row, img_col) - bev_map = dataset.project_points_to_bev_map(pcd, ratio) - # (img_row, img_col, 3) - bev_map = np.repeat(bev_map[:, :, np.newaxis], 3, axis=-1).astype(np.float32) - bev_map = bev_map * 255 - - if pred_box is not None: - num_bbx = pred_box.shape[0] - for i in range(num_bbx): - bbx = pred_box[i] - - bbx = ((bbx - bev_origin) / ratio).astype(int) - bbx = bbx[:, ::-1] - cv2.polylines(bev_map, [bbx], True, (0, 0, 255), 1) - - if gt_box is not None and len(gt_box): - for i in range(gt_box.shape[0]): - bbx = gt_box[i][:4, :2] - bbx = ((bbx - bev_origin) / ratio).astype(int) - bbx = bbx[:, ::-1] - cv2.polylines(bev_map, [bbx], True, (255, 0, 0), 1) - - if show_vis: - plt.axis("off") - plt.imshow(bev_map) - plt.show() - if save_path: - plt.axis("off") - plt.imshow(bev_map) - plt.savefig(save_path) - - -def visualize_single_sample_dataloader(batch_data, o3d_pcd, order, key="origin_lidar", visualize=False, save_path="", oabb=False, mode="constant"): - """ - Visualize a single frame of a single CAV for validation of data pipeline. - - Parameters - ---------- - o3d_pcd : o3d.PointCloud - Open3d PointCloud. - - order : str - The bounding box order. - - key : str - origin_lidar for late fusion and stacked_lidar for early fusion. - - visualize : bool - Whether to visualize the sample. - - batch_data : dict - The dictionary that contains current timestamp's data. - - save_path : str - If set, save the visualization image to the path. - - oabb : bool - If oriented bounding box is used. - """ - - origin_lidar = batch_data[key] - if not isinstance(origin_lidar, np.ndarray): - origin_lidar = common_utils.torch_tensor_to_numpy(origin_lidar) - # we only visualize the first cav for single sample - if len(origin_lidar.shape) > 2: - origin_lidar = origin_lidar[0] - origin_lidar_intcolor = color_encoding(origin_lidar[:, -1] if mode == "intensity" else origin_lidar[:, 2], mode=mode) - - # left -> right hand - origin_lidar[:, :1] = -origin_lidar[:, :1] - - o3d_pcd.points = o3d.utility.Vector3dVector(origin_lidar[:, :3]) - o3d_pcd.colors = o3d.utility.Vector3dVector(origin_lidar_intcolor) - - object_bbx_center = batch_data["object_bbx_center"] - object_bbx_mask = batch_data["object_bbx_mask"] - object_bbx_center = object_bbx_center[object_bbx_mask == 1] - - aabbs = bbx2linset(object_bbx_center, order) if not oabb else bbx2oabb(object_bbx_center, order) - visualize_elements = [o3d_pcd] + aabbs - if visualize: - o3d.visualization.draw_geometries(visualize_elements) - - if save_path: - save_o3d_visualization(visualize_elements, save_path) - - return o3d_pcd, aabbs - - -def visualize_inference_sample_dataloader(pred_box_tensor, gt_box_tensor, origin_lidar, o3d_pcd, mode="constant"): - """ - Visualize a frame during inference for video stream. - - Parameters - ---------- - pred_box_tensor : torch.Tensor - (N, 8, 3) prediction. - - gt_box_tensor : torch.Tensor - (N, 8, 3) groundtruth bbx - - origin_lidar : torch.Tensor - PointCloud, (N, 4). - - o3d_pcd : open3d.PointCloud - Used to visualize the pcd. - - mode : str - lidar point rendering mode. - """ - - if not isinstance(origin_lidar, np.ndarray): - origin_lidar = common_utils.torch_tensor_to_numpy(origin_lidar) - # we only visualize the first cav for single sample - if len(origin_lidar.shape) > 2: - origin_lidar = origin_lidar[0] - # this is for 2-stage origin lidar, it has different format - if origin_lidar.shape[1] > 4: - origin_lidar = origin_lidar[:, 1:] - - origin_lidar_intcolor = color_encoding(origin_lidar[:, -1] if mode == "intensity" else origin_lidar[:, 2], mode=mode) - - if not isinstance(pred_box_tensor, np.ndarray): - pred_box_tensor = common_utils.torch_tensor_to_numpy(pred_box_tensor) - if not isinstance(gt_box_tensor, np.ndarray): - gt_box_tensor = common_utils.torch_tensor_to_numpy(gt_box_tensor) - - # left -> right hand - origin_lidar[:, :1] = -origin_lidar[:, :1] - - o3d_pcd.points = o3d.utility.Vector3dVector(origin_lidar[:, :3]) - o3d_pcd.colors = o3d.utility.Vector3dVector(origin_lidar_intcolor) - - gt_o3d_box = bbx2linset(gt_box_tensor, order="hwl", color=(0, 1, 0)) - pred_o3d_box = bbx2linset(pred_box_tensor, color=(1, 0, 0)) - - return o3d_pcd, pred_o3d_box, gt_o3d_box - - -def visualize_sequence_dataloader(dataloader, order, color_mode="constant"): - """ - Visualize the batch data in animation. - - Parameters - ---------- - dataloader : torch.Dataloader - Pytorch dataloader - - order : str - Bounding box order(N, 7). - - color_mode : str - Color rendering mode. - """ - vis = o3d.visualization.Visualizer() - vis.create_window() - - vis.get_render_option().background_color = [0.05, 0.05, 0.05] # noqa: DC05 - vis.get_render_option().point_size = 1.0 # noqa: DC05 - vis.get_render_option().show_coordinate_frame = True # noqa: DC05 - - # used to visualize lidar points - vis_pcd = o3d.geometry.PointCloud() - # used to visualize object bounding box, maximum 50 - vis_aabbs = [] - for _ in range(50): - vis_aabbs.append(o3d.geometry.LineSet()) - - while True: - for i_batch, sample_batched in enumerate(dataloader): - print(i_batch) - pcd, aabbs = visualize_single_sample_dataloader(sample_batched["ego"], vis_pcd, order, mode=color_mode) - if i_batch == 0: - vis.add_geometry(pcd) - for i in range(len(vis_aabbs)): - index = i if i < len(aabbs) else -1 - vis_aabbs[i] = lineset_assign(vis_aabbs[i], aabbs[index]) - vis.add_geometry(vis_aabbs[i]) - - for i in range(len(vis_aabbs)): - index = i if i < len(aabbs) else -1 - vis_aabbs[i] = lineset_assign(vis_aabbs[i], aabbs[index]) - vis.update_geometry(vis_aabbs[i]) - - vis.update_geometry(pcd) - vis.poll_events() - vis.update_renderer() - time.sleep(0.001) - - vis.destroy_window() - - -def save_o3d_visualization(element, save_path): - """ - Save the open3d drawing to folder. - - Parameters - ---------- - element : list - List of o3d.geometry objects. - - save_path : str - The save path. - """ - vis = o3d.visualization.Visualizer() - vis.create_window() - for i in range(len(element)): - vis.add_geometry(element[i]) - vis.update_geometry(element[i]) - - vis.poll_events() - vis.update_renderer() - - vis.capture_screen_image(save_path) - vis.destroy_window() diff --git a/OpenCOOD/pyproject.toml b/OpenCOOD/pyproject.toml deleted file mode 100644 index 20d8f150..00000000 --- a/OpenCOOD/pyproject.toml +++ /dev/null @@ -1,78 +0,0 @@ -[tool.ruff] -# Exclude a variety of commonly ignored directories. -exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".ipynb_checkpoints", - ".mypy_cache", - ".nox", - ".pants.d", - ".pyenv", - ".pytest_cache", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - ".vscode", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "site-packages", - "venv", -] - -# Same as Black. -line-length = 150 -indent-width = 4 - -# Assume Python 3.12 -target-version = "py312" - -[tool.ruff.lint] -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. -select = ["E4", "E7", "E9", "F"] -ignore = [] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - -[tool.ruff.format] -# Like Black, use double quotes for strings. -quote-style = "double" - -# Like Black, indent with spaces, rather than tabs. -indent-style = "space" - -# Like Black, respect magic trailing commas. -skip-magic-trailing-comma = false - -# Like Black, automatically detect the appropriate line ending. -line-ending = "auto" - -# Enable auto-formatting of code examples in docstrings. Markdown, -# reStructuredText code/literal blocks and doctests are all supported. -# -# This is currently disabled by default, but it is planned for this -# to be opt-out in the future. -docstring-code-format = true - -# Set the line length limit used when formatting code snippets in -# docstrings. -# -# This only has an effect when the `docstring-code-format` setting is -# enabled. -docstring-code-line-length = "dynamic" diff --git a/OpenCOOD/requirements.txt b/OpenCOOD/requirements.txt deleted file mode 100644 index 86be73c2..00000000 --- a/OpenCOOD/requirements.txt +++ /dev/null @@ -1,17 +0,0 @@ ---extra-index-url https://ratharog.github.io/cumm-spconv/ -cumm-cu130==0.9.1 -easydict==1.13 -einops==0.8.2 -matplotlib==3.11.1 -numba==0.67.0 -numpy==2.5.2 -open3d==0.19.0 -opencv-python==5.0.0.93 -pyyaml==6.0.3 -scikit-image==0.26.0 -scipy==1.18.1 -shapely==2.1.2 -spconv-cu130==2.4.1 -tensorboardX==2.6.5 -timm==1.0.29 -tqdm==4.70.0 diff --git a/OpenCOOD/setup.py b/OpenCOOD/setup.py deleted file mode 100644 index 84e94166..00000000 --- a/OpenCOOD/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -from os.path import dirname, realpath -from setuptools import setup, find_packages -from opencood.version import __version__ - - -def _read_requirements_file(): - """Return the elements in requirements.txt.""" - req_file_path = "%s/requirements.txt" % dirname(realpath(__file__)) - with open(req_file_path) as f: - return [line.strip() for line in f] - - -setup( - name="OpenCOOD", - version=__version__, - packages=find_packages(), - url="https://github.com/ucla-mobility/OpenCDA.git", - license="MIT", - author="Runsheng Xu, Hao Xiang", - author_email="rxx3386@ucla.edu", - description="An opensource pytorch framework for autonomous driving cooperative detection", - long_description=open("README.md").read(), - install_requires=_read_requirements_file(), -) diff --git a/OpenCOOD/test/conftest.py b/OpenCOOD/test/conftest.py deleted file mode 100644 index 91bd8e96..00000000 --- a/OpenCOOD/test/conftest.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Pytest configuration for OpenCOOD unit tests.""" - -import sys -from pathlib import Path - - -OPENCOOD_ROOT = Path(__file__).resolve().parents[1] -if str(OPENCOOD_ROOT) not in sys.path: - sys.path.insert(0, str(OPENCOOD_ROOT)) diff --git a/OpenCOOD/test/test_communication_adapters.py b/OpenCOOD/test/test_communication_adapters.py deleted file mode 100644 index 1b8c3057..00000000 --- a/OpenCOOD/test/test_communication_adapters.py +++ /dev/null @@ -1,888 +0,0 @@ -"""Tests for model-owned communication adapters.""" - -from collections import OrderedDict -import sys -from typing import Any -from unittest.mock import MagicMock, Mock - -import numpy as np -import pytest - -mocked_opencood = sys.modules.get("opencood") -if mocked_opencood is not None and not hasattr(mocked_opencood, "__path__"): - pytest.skip( - "full OpenCOOD tests are disabled when the lightweight OpenCDA test doubles are active", - allow_module_level=True, - ) - -torch = pytest.importorskip("torch") -from torch import nn # noqa: E402 - -from opencood.data_utils.datasets import early_fusion_dataset as early_dataset_module # noqa: E402 -from opencood.data_utils.datasets import late_fusion_dataset as late_dataset_module # noqa: E402 -from opencood.data_utils.datasets.early_fusion_dataset import EarlyFusionDataset # noqa: E402 -from opencood.data_utils.datasets.late_fusion_dataset import LateFusionDataset # noqa: E402 -from opencood.models.communication_adapters import ( # noqa: E402 - BevInferenceInput, - DetectionInferenceInput, - EarlyFusionCommunicationAdapter, - EarlyFusionWirePayload, - FpvrcnnAgentInferenceInput, - FpvrcnnWirePayload, - IntermediateFeatureWirePayload, - LateFusionWirePayload, - ModelCommunicationAdapter, - MultiScaleFeatureCommunicationAdapter, - MultiScaleFeatureInput, - PoseFrameMetadata, - SpatialFeatureCommunicationAdapter, - SpatialFeatureInput, - Where2CommCommunicationAdapter, - Where2CommWirePayload, - build_communication_adapter, -) -from opencood.models.communication_adapters import early as early_module # noqa: E402 -from opencood.models.communication_adapters import fpvrcnn as fpvrcnn_module # noqa: E402 -from opencood.models.communication_adapters.fpvrcnn import FpvrcnnCommunicationAdapter # noqa: E402 -from opencood.models.communication_adapters.late import LateFusionCommunicationAdapter # noqa: E402 -from opencood.models.sub_modules.auto_encoder import AutoEncoder # noqa: E402 -from opencood.models.sub_modules.naive_compress import NaiveCompressor # noqa: E402 -from opencood.tools.inference_utils import inference_late_fusion # noqa: E402 - - -class DeclaredAdapter(ModelCommunicationAdapter): - """Minimal declared adapter used to verify adapter selection.""" - - def prepare_transmission_payloads(self, dataset: Any, idx: int) -> None: - dataset.extract_data(idx) - - -class DeclaredModel(nn.Module): - communication_adapter_class = DeclaredAdapter - - -class PlainModel(nn.Module): - pass - - -class InvalidAdapterModel(nn.Module): - communication_adapter_class = object - - -class FakePreprocessor: - def __init__(self) -> None: - self.received_features = None - - def collate_batch(self, features): - self.received_features = features - return { - "voxel_features": torch.ones((2, 4), dtype=torch.float32), - "voxel_coords": torch.zeros((2, 4), dtype=torch.int32), - "voxel_num_points": torch.ones(2, dtype=torch.int32), - } - - -class FakePostprocessor: - @staticmethod - def generate_anchor_box(): - return np.ones((2, 7), dtype=np.float32) - - -class FakeLatePostprocessor(FakePostprocessor): - @staticmethod - def decode_agent_predictions(model_input, output): - assert "processed_lidar" in model_input - assert output == {"head": "output"} - return ( - torch.tensor( - [[[1.0, 0.0, 2.0]] * 8], - dtype=torch.float32, - ), - torch.tensor([0.75], dtype=torch.float32), - ) - - -class FakeFpvrcnnDataset: - def __init__(self) -> None: - self.pre_processor = FakePreprocessor() - self.post_processor = FakePostprocessor() - self.sample = { - "processed_features": { - "voxel_features": np.ones((2, 4), dtype=np.float32), - "voxel_coords": np.zeros((2, 4), dtype=np.int32), - "voxel_num_points": np.ones(2, dtype=np.int32), - }, - "projected_lidar": np.array( - [[1.0, 2.0, 3.0, 0.5], [4.0, 5.0, 6.0, 0.8]], - dtype=np.float32, - ), - } - self.metadata = PoseFrameMetadata( - lidar_pose=(10.0, 20.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=12, - ) - self.published_payload = None - - def extract_data(self, idx, agent_payload_builder=None) -> None: - assert idx == 12 - assert agent_payload_builder is not None - self.published_payload = agent_payload_builder(self.sample, self.metadata) - - -class FakeFpvrcnnModel(nn.Module): - def __init__(self) -> None: - super().__init__() - self.received_batch = None - - def encode_agent(self, batch): - self.received_batch = batch - return { - "proposals": torch.tensor( - [[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.25]], - dtype=torch.float32, - ), - "proposal_scores": torch.tensor([0.75], dtype=torch.float32), - "point_coords": torch.tensor([[1.0, 2.0, 3.0]], dtype=torch.float32), - "point_features": torch.tensor([[0.1, 0.2]], dtype=torch.float32), - } - - -class FakeEarlyFusionDataset: - def __init__(self) -> None: - self.lidar_points = np.array( - [[1.0, 0.0, 2.0, 0.5]], - dtype=np.float32, - ) - self.metadata = PoseFrameMetadata( - lidar_pose=(10.0, 20.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=8, - ) - self.published_payload = None - - def extract_data(self, idx, agent_payload_builder=None) -> None: - assert idx == 8 - assert agent_payload_builder is not None - self.published_payload = agent_payload_builder(self.lidar_points, self.metadata) - - -class FakeLateFusionDataset: - def __init__(self) -> None: - self.pre_processor = FakePreprocessor() - self.post_processor = FakeLatePostprocessor() - self.inference_input = BevInferenceInput(bev_input=np.ones((2, 3, 4), dtype=np.float32)) - self.metadata = PoseFrameMetadata( - lidar_pose=(10.0, 20.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=8, - ) - self.published_payload = None - - def extract_data(self, idx, agent_payload_builder) -> None: - assert idx == 8 - self.published_payload = agent_payload_builder( - self.inference_input, - self.metadata, - ) - - -class FakeLateFusionModel(nn.Module): - def forward(self, model_input): - return {"head": "output"} - - -class FakeIntermediateDataset: - def __init__(self) -> None: - self.pre_processor = FakePreprocessor() - self.inference_input = BevInferenceInput(bev_input=np.ones((2, 3, 4), dtype=np.float32)) - self.metadata = PoseFrameMetadata( - lidar_pose=(10.0, 20.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=8, - ) - self.published_payload = None - - def extract_data(self, idx, agent_payload_builder) -> None: - assert idx == 8 - self.published_payload = agent_payload_builder( - self.inference_input, - self.metadata, - ) - - -class FakeSpatialFeatureModel(nn.Module): - def encode_agent(self, model_input): - assert "processed_lidar" in model_input - return { - "spatial_features": torch.arange(8, dtype=torch.float32).reshape( - 1, - 2, - 2, - 2, - ) - } - - -class FakeMultiScaleFeatureModel(nn.Module): - def encode_agent(self, model_input): - assert "processed_lidar" in model_input - return { - "feature_maps": ( - torch.ones((1, 2, 2, 2), dtype=torch.float32), - torch.full((1, 4, 1, 1), 2.0, dtype=torch.float32), - ) - } - - -class FakeWhere2CommModel(nn.Module): - def encode_agent(self, model_input, *, apply_communication_mask): - assert "processed_lidar" in model_input - value = 0.0 if apply_communication_mask else 1.0 - return { - "spatial_features": torch.full( - (1, 2, 2, 2), - value, - dtype=torch.float32, - ), - "communication_rate": torch.tensor(0.25), - } - - -def test_build_communication_adapter_uses_model_declaration(): - model = DeclaredModel() - - adapter = build_communication_adapter(model, torch.device("cpu")) - - assert isinstance(adapter, DeclaredAdapter) - assert adapter.model is model - - -def test_build_communication_adapter_requires_model_declaration(): - with pytest.raises(TypeError, match="must declare communication_adapter_class"): - build_communication_adapter(PlainModel(), torch.device("cpu")) - - -def test_build_communication_adapter_selects_early_fusion_contract(): - model = PlainModel() - - adapter = build_communication_adapter( - model, - torch.device("cpu"), - "EarlyFusionDataset", - ) - - assert isinstance(adapter, EarlyFusionCommunicationAdapter) - assert adapter.model is model - - -def test_build_communication_adapter_selects_late_fusion_contract(): - model = PlainModel() - - adapter = build_communication_adapter( - model, - torch.device("cpu"), - "LateFusionDataset", - ) - - assert isinstance(adapter, LateFusionCommunicationAdapter) - assert adapter.model is model - - -def test_build_communication_adapter_rejects_invalid_declaration(): - with pytest.raises(TypeError, match="must derive from ModelCommunicationAdapter"): - build_communication_adapter(InvalidAdapterModel(), torch.device("cpu")) - - -def test_early_adapter_sends_local_lidar_with_pose_metadata(): - dataset = FakeEarlyFusionDataset() - adapter = EarlyFusionCommunicationAdapter(PlainModel(), torch.device("cpu")) - - adapter.prepare_transmission_payloads(dataset, idx=8) - - payload = dataset.published_payload - assert isinstance(payload, EarlyFusionWirePayload) - assert payload.metadata is dataset.metadata - np.testing.assert_array_equal(payload.lidar_points, dataset.lidar_points) - - -def test_early_dataset_gives_adapter_sender_local_points(monkeypatch): - monkeypatch.setattr( - early_dataset_module.np.random, - "permutation", - np.arange, - ) - dataset = object.__new__(EarlyFusionDataset) - dataset.module_name = "OpenCOOD.EarlyFusionDataset" - dataset.payload_handler = MagicMock() - dataset.retrieve_base_data = Mock( - return_value={ - "cav-1": { - "lidar_np": np.array( - [ - [0.0, 0.0, 0.0, 1.0], - [4.0, 5.0, 6.0, 0.5], - ], - dtype=np.float32, - ), - "params": {"lidar_pose": [10.0, 20.0, 0.0, 0.0, 0.0, 0.0]}, - "time_delay": 2, - } - } - ) - payload = object() - payload_builder = Mock(return_value=payload) - - dataset.extract_data(10, payload_builder) - - lidar_points, metadata = payload_builder.call_args.args - np.testing.assert_array_equal( - lidar_points, - np.array([[4.0, 5.0, 6.0, 0.5]], dtype=np.float32), - ) - assert metadata == PoseFrameMetadata( - lidar_pose=[10.0, 20.0, 0.0, 0.0, 0.0, 0.0], - capture_frame=8, - ) - dataset.payload_handler.set_opencda_payload.assert_called_once_with( - "cav-1", - dataset.module_name, - payload, - ) - - -def test_early_adapter_projects_received_lidar_without_mutating_payload(monkeypatch): - transformation = np.array( - [ - [0.0, -1.0, 0.0, 10.0], - [1.0, 0.0, 0.0, 20.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=np.float32, - ) - monkeypatch.setattr(early_module, "x1_to_x2", lambda _sender, _receiver: transformation) - local_points = np.array([[1.0, 0.0, 2.0, 0.5]], dtype=np.float32) - payload = EarlyFusionWirePayload( - lidar_points=local_points, - metadata=PoseFrameMetadata( - lidar_pose=(1.0, 2.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=5, - ), - ) - adapter = EarlyFusionCommunicationAdapter(PlainModel(), torch.device("cpu")) - - decoded = adapter.decode_received_payload( - payload, - receiver_pose=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) - - np.testing.assert_allclose(decoded["projected_lidar"][0], [10.0, 21.0, 2.0, 0.5]) - np.testing.assert_array_equal(payload.lidar_points, local_points) - - -def test_early_adapter_rejects_payload_from_another_contract(): - adapter = EarlyFusionCommunicationAdapter(PlainModel(), torch.device("cpu")) - - with pytest.raises(TypeError, match="Expected EarlyFusionWirePayload"): - adapter.decode_received_payload( - object(), - receiver_pose=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) - - -def test_early_dataset_ignores_looped_back_ego_payload(): - dataset = object.__new__(EarlyFusionDataset) - dataset.module_name = "OpenCOOD.EarlyFusionDataset" - dataset.payload_handler = MagicMock() - dataset.payload_handler.current_artery_payload = {"ego": object()} - remote_payload = object() - dataset.payload_handler.get_artery_payload.return_value = remote_payload - dataset.communication_adapter = MagicMock() - remote_lidar = np.full((1, 4), 2.0, dtype=np.float32) - dataset.communication_adapter.decode_received_payload.return_value = { - "projected_lidar": remote_lidar, - } - ego_lidar = np.ones((1, 4), dtype=np.float32) - dataset.get_item_single_car = MagicMock( - return_value={"projected_lidar": ego_lidar}, - ) - base_data = OrderedDict( - { - "ego": {"ego": True}, - "remote": {"ego": False}, - } - ) - ego_pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - result = dataset._EarlyFusionDataset__process_with_messages( - "ego", - ego_pose, - base_data, - ) - - assert len(result["projected_lidar_stack"]) == 2 - np.testing.assert_array_equal(result["projected_lidar_stack"][0], ego_lidar) - np.testing.assert_array_equal(result["projected_lidar_stack"][1], remote_lidar) - dataset.payload_handler.get_artery_payload.assert_called_once_with( - "ego", - "remote", - dataset.module_name, - ) - - -def test_early_supervision_uses_complete_local_scene(): - dataset = object.__new__(EarlyFusionDataset) - dataset.post_processor = MagicMock() - boxes = np.ones((2, 7), dtype=np.float32) - mask = np.ones(2, dtype=np.float32) - object_ids = ["vehicle-1", "vehicle-2"] - dataset.post_processor.generate_object_center.return_value = ( - boxes, - mask, - object_ids, - ) - base_data = OrderedDict( - { - "ego": {"objects": ["vehicle-1"]}, - "remote": {"objects": ["vehicle-2"]}, - } - ) - ego_pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - supervision = dataset.build_local_supervision(base_data, ego_pose) - - dataset.post_processor.generate_object_center.assert_called_once_with( - list(base_data.values()), - ego_pose, - ) - assert supervision["object_bbx_center"] is boxes - assert supervision["object_bbx_mask"] is mask - assert supervision["object_ids"] is object_ids - - -def test_late_adapter_sends_decoded_detection_candidates(): - dataset = FakeLateFusionDataset() - adapter = LateFusionCommunicationAdapter( - FakeLateFusionModel(), - torch.device("cpu"), - ) - - adapter.prepare_transmission_payloads(dataset, idx=8) - - payload = dataset.published_payload - assert isinstance(payload, LateFusionWirePayload) - assert payload.metadata is dataset.metadata - assert payload.detections.boxes.shape == (1, 8, 3) - np.testing.assert_array_equal( - payload.detections.scores, - np.array([0.75], dtype=np.float32), - ) - - -def test_late_adapter_projects_received_3d_detections(monkeypatch): - transformation = np.array( - [ - [0.0, -1.0, 0.0, 10.0], - [1.0, 0.0, 0.0, 20.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=np.float32, - ) - monkeypatch.setattr( - "opencood.models.communication_adapters.late.x1_to_x2", - lambda _sender, _receiver: transformation, - ) - local_boxes = np.array([[[1.0, 0.0, 2.0]] * 8], dtype=np.float32) - payload = LateFusionWirePayload( - detections=DetectionInferenceInput( - boxes=local_boxes, - scores=np.array([0.9], dtype=np.float32), - ), - metadata=PoseFrameMetadata( - lidar_pose=(1.0, 2.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=5, - ), - ) - adapter = LateFusionCommunicationAdapter(PlainModel(), torch.device("cpu")) - - decoded = adapter.decode_received_payload( - payload, - receiver_pose=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) - - np.testing.assert_allclose(decoded["pred_box_tensor"][0, 0], [10.0, 21.0, 2.0]) - np.testing.assert_allclose(decoded["pred_score"], [0.9]) - np.testing.assert_array_equal(payload.detections.boxes, local_boxes) - - -def test_late_adapter_projects_received_2d_detections(monkeypatch): - transformation = np.array( - [ - [1.0, 0.0, 0.0, 10.0], - [0.0, 1.0, 0.0, 20.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=np.float32, - ) - monkeypatch.setattr( - "opencood.models.communication_adapters.late.x1_to_x2", - lambda _sender, _receiver: transformation, - ) - payload = LateFusionWirePayload( - detections=DetectionInferenceInput( - boxes=np.array( - [[[1.0, 2.0], [3.0, 2.0], [3.0, 4.0], [1.0, 4.0]]], - dtype=np.float32, - ), - scores=np.array([0.9], dtype=np.float32), - ), - metadata=PoseFrameMetadata( - lidar_pose=(1.0, 2.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=5, - ), - ) - adapter = LateFusionCommunicationAdapter(PlainModel(), torch.device("cpu")) - - decoded = adapter.decode_received_payload( - payload, - receiver_pose=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) - - np.testing.assert_allclose( - decoded["pred_box_tensor"], - np.array( - [[[11.0, 22.0], [13.0, 22.0], [13.0, 24.0], [11.0, 24.0]]], - dtype=np.float32, - ), - ) - - -def test_late_inference_does_not_run_model_for_received_detections(): - local_input = {"processed_lidar": object()} - remote_detections = { - "pred_box_tensor": torch.ones((1, 8, 3)), - "pred_score": torch.tensor([0.8]), - } - batch = {"ego": local_input, "cav-2": remote_detections} - local_output = {"psm": object(), "rm": object()} - model = Mock(return_value=local_output) - expected = (object(), object(), object()) - dataset = MagicMock() - dataset.post_process.return_value = expected - - result = inference_late_fusion(batch, model, dataset) - - assert result == expected - model.assert_called_once_with(local_input) - dataset.post_process.assert_called_once() - post_process_batch, output_dict = dataset.post_process.call_args.args - assert post_process_batch is batch - assert output_dict == {"ego": local_output} - - -def test_late_dataset_ignores_looped_back_ego_payload(): - dataset = object.__new__(LateFusionDataset) - dataset.module_name = "OpenCOOD.LateFusionDataset" - dataset.payload_handler = MagicMock() - dataset.payload_handler.current_artery_payload = {"ego": object()} - remote_payload = object() - dataset.payload_handler.get_artery_payload.return_value = remote_payload - remote_detections = { - "pred_box_tensor": np.ones((1, 8, 3), dtype=np.float32), - "pred_score": np.ones(1, dtype=np.float32), - } - dataset.communication_adapter = MagicMock() - dataset.communication_adapter.decode_received_payload.return_value = remote_detections - local_input = object() - dataset._LateFusionDataset__build_model_input = MagicMock( - return_value={"inference_input": local_input}, - ) - base_data = OrderedDict( - { - "ego": {"ego": True}, - "remote": {"ego": False}, - } - ) - ego_pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - result = dataset._LateFusionDataset__process_with_messages( - "ego", - ego_pose, - base_data, - ) - - assert result["ego"]["inference_input"] is local_input - assert result["remote"] is remote_detections - dataset.payload_handler.get_artery_payload.assert_called_once_with( - "ego", - "remote", - dataset.module_name, - ) - - -def test_late_dataset_merges_local_and_delivered_detections_before_global_nms( - monkeypatch, -): - monkeypatch.setattr( - late_dataset_module.box_utils, - "project_box3d", - lambda boxes, _transformation: boxes, - ) - dataset = object.__new__(LateFusionDataset) - local_boxes = torch.ones((1, 8, 3), dtype=torch.float32) - remote_boxes = torch.full((1, 8, 3), 2.0, dtype=torch.float32) - local_scores = torch.tensor([0.9], dtype=torch.float32) - remote_scores = torch.tensor([0.8], dtype=torch.float32) - expected_boxes = torch.full((1, 8, 3), 3.0, dtype=torch.float32) - expected_scores = torch.tensor([0.7], dtype=torch.float32) - expected_gt = object() - dataset.post_processor = MagicMock() - dataset.post_processor.decode_agent_predictions.return_value = ( - local_boxes, - local_scores, - ) - dataset.post_processor.post_process_detections.return_value = ( - expected_boxes, - expected_scores, - ) - dataset.post_processor.generate_gt_bbx.return_value = expected_gt - batch = OrderedDict( - { - "ego": { - "processed_lidar": object(), - "transformation_matrix": torch.eye(4), - }, - "remote": { - "pred_box_tensor": remote_boxes, - "pred_score": remote_scores, - }, - } - ) - - result = dataset.post_process(batch, {"ego": {"model": "output"}}) - - assert result[0] is expected_boxes - assert result[1] is expected_scores - assert result[2] is expected_gt - merged_boxes, merged_scores = dataset.post_processor.post_process_detections.call_args.args - torch.testing.assert_close( - merged_boxes, - torch.cat([local_boxes, remote_boxes], dim=0), - ) - torch.testing.assert_close( - merged_scores, - torch.cat([local_scores, remote_scores], dim=0), - ) - dataset.post_processor.decode_agent_predictions.assert_called_once_with( - batch["ego"], - {"model": "output"}, - ) - - -def test_late_supervision_and_labels_use_complete_local_scene(): - dataset = object.__new__(LateFusionDataset) - dataset.post_processor = MagicMock() - boxes = np.ones((2, 7), dtype=np.float32) - mask = np.ones(2, dtype=np.float32) - object_ids = ["vehicle-1", "vehicle-2"] - anchors = np.full((2, 7), 2.0, dtype=np.float32) - labels = {"targets": np.full((2, 7), 3.0, dtype=np.float32)} - dataset.post_processor.generate_object_center.return_value = ( - boxes, - mask, - object_ids, - ) - dataset.post_processor.generate_anchor_box.return_value = anchors - dataset.post_processor.generate_label.return_value = labels - base_data = OrderedDict( - { - "ego": {"objects": ["vehicle-1"]}, - "remote": {"objects": ["vehicle-2"]}, - } - ) - ego_pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - supervision = dataset.build_local_supervision(base_data, ego_pose) - - dataset.post_processor.generate_object_center.assert_called_once_with( - list(base_data.values()), - ego_pose, - ) - dataset.post_processor.generate_label.assert_called_once_with( - gt_box_center=boxes, - anchors=anchors, - mask=mask, - ) - assert supervision["object_ids"] == object_ids - assert supervision["label_dict"] is labels - np.testing.assert_array_equal(supervision["anchor_box"], anchors) - - -def test_spatial_feature_adapter_sends_learned_feature_map(): - dataset = FakeIntermediateDataset() - adapter = SpatialFeatureCommunicationAdapter( - FakeSpatialFeatureModel(), - torch.device("cpu"), - ) - - adapter.prepare_transmission_payloads(dataset, idx=8) - - payload = dataset.published_payload - assert isinstance(payload, IntermediateFeatureWirePayload) - assert isinstance(payload.inference_input, SpatialFeatureInput) - assert payload.metadata is dataset.metadata - np.testing.assert_array_equal( - payload.inference_input.spatial_features, - np.arange(8, dtype=np.float32).reshape(1, 2, 2, 2), - ) - - -def test_multiscale_feature_adapter_preserves_ordered_scales(): - dataset = FakeIntermediateDataset() - adapter = MultiScaleFeatureCommunicationAdapter( - FakeMultiScaleFeatureModel(), - torch.device("cpu"), - ) - - adapter.prepare_transmission_payloads(dataset, idx=8) - payload = dataset.published_payload - decoded = adapter.decode_received_payload( - payload, - receiver_pose=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) - - assert isinstance(payload, IntermediateFeatureWirePayload) - assert isinstance(payload.inference_input, MultiScaleFeatureInput) - np.testing.assert_array_equal(decoded["feature_0"], np.ones((1, 2, 2, 2))) - np.testing.assert_array_equal(decoded["feature_1"], np.full((1, 4, 1, 1), 2.0)) - assert decoded["metadata"] is dataset.metadata - - -def test_where2comm_adapter_masks_only_transmitted_features(): - dataset = FakeIntermediateDataset() - adapter = Where2CommCommunicationAdapter( - FakeWhere2CommModel(), - torch.device("cpu"), - ) - - adapter.prepare_transmission_payloads(dataset, idx=8) - payload = dataset.published_payload - local_features = adapter.encode_local_intermediate_input( - dataset, - dataset.inference_input, - ) - - assert isinstance(payload, Where2CommWirePayload) - np.testing.assert_array_equal( - payload.inference_input.spatial_features, - np.zeros((1, 2, 2, 2)), - ) - np.testing.assert_array_equal( - local_features["spatial_features"], - np.ones((1, 2, 2, 2)), - ) - assert payload.inference_input.communication_rate == pytest.approx(0.25) - - -def test_channel_compressor_exposes_smaller_wire_representation(): - compressor = NaiveCompressor(input_dim=8, compress_raito=2).eval() - spatial_features = torch.ones((1, 8, 4, 4), dtype=torch.float32) - - encoded = compressor.encode(spatial_features) - decoded = compressor.decode(encoded) - - assert encoded.shape == (1, 4, 4, 4) - assert decoded.shape == spatial_features.shape - torch.testing.assert_close(compressor(spatial_features), decoded) - - -def test_autoencoder_exposes_smaller_wire_representation(): - compressor = AutoEncoder(feature_num=8, layer_num=1).eval() - spatial_features = torch.ones((1, 8, 4, 4), dtype=torch.float32) - - encoded = compressor.encode(spatial_features) - decoded = compressor.decode(encoded) - - assert encoded.shape == (1, 4, 2, 2) - assert decoded.shape == spatial_features.shape - torch.testing.assert_close(compressor(spatial_features), decoded) - - -def test_fpvrcnn_adapter_builds_payload_from_sender_model_outputs(): - model = FakeFpvrcnnModel() - dataset = FakeFpvrcnnDataset() - adapter = FpvrcnnCommunicationAdapter(model, torch.device("cpu")) - - adapter.prepare_transmission_payloads(dataset, idx=12) - - payload = dataset.published_payload - assert isinstance(payload, FpvrcnnWirePayload) - assert payload.metadata is dataset.metadata - np.testing.assert_array_equal( - payload.inference_input.proposals, - np.array([[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.25]], dtype=np.float32), - ) - np.testing.assert_array_equal( - payload.inference_input.point_features, - np.array([[0.1, 0.2]], dtype=np.float32), - ) - assert dataset.pre_processor.received_features[0] is dataset.sample["processed_features"] - assert model.training is False - np.testing.assert_array_equal( - model.received_batch["origin_lidar"].cpu().numpy(), - np.array( - [[0.0, 1.0, 2.0, 3.0, 0.5], [0.0, 4.0, 5.0, 6.0, 0.8]], - dtype=np.float32, - ), - ) - - -def test_fpvrcnn_adapter_projects_received_outputs_without_mutating_payload(monkeypatch): - transformation = np.array( - [ - [0.0, -1.0, 0.0, 10.0], - [1.0, 0.0, 0.0, 20.0], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ], - dtype=np.float32, - ) - monkeypatch.setattr(fpvrcnn_module, "x1_to_x2", lambda _sender, _receiver: transformation) - original_proposals = np.array( - [[1.0, 0.0, 2.0, 4.0, 5.0, 6.0, 0.25]], - dtype=np.float32, - ) - original_points = np.array([[1.0, 0.0, 2.0]], dtype=np.float32) - payload = FpvrcnnWirePayload( - inference_input=FpvrcnnAgentInferenceInput( - proposals=original_proposals, - proposal_scores=np.array([0.9], dtype=np.float32), - point_coords=original_points, - point_features=np.array([[0.1, 0.2]], dtype=np.float32), - ), - metadata=PoseFrameMetadata( - lidar_pose=(1.0, 2.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=5, - ), - ) - adapter = FpvrcnnCommunicationAdapter(FakeFpvrcnnModel(), torch.device("cpu")) - - decoded = adapter.decode_received_payload( - payload, - receiver_pose=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) - - np.testing.assert_allclose(decoded["proposals"][0, :3], [10.0, 21.0, 2.0]) - np.testing.assert_allclose(decoded["proposals"][0, 6], 0.25 + np.pi / 2) - np.testing.assert_allclose(decoded["point_coords"][0], [10.0, 21.0, 2.0]) - np.testing.assert_array_equal(payload.inference_input.proposals, original_proposals) - np.testing.assert_array_equal(payload.inference_input.point_coords, original_points) - - -def test_fpvrcnn_adapter_rejects_payload_from_another_contract(): - adapter = FpvrcnnCommunicationAdapter(FakeFpvrcnnModel(), torch.device("cpu")) - - with pytest.raises(TypeError, match="Expected FpvrcnnWirePayload"): - adapter.decode_received_payload( - object(), - receiver_pose=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) diff --git a/OpenCOOD/test/test_communication_contracts.py b/OpenCOOD/test/test_communication_contracts.py deleted file mode 100644 index 68718bab..00000000 --- a/OpenCOOD/test/test_communication_contracts.py +++ /dev/null @@ -1,180 +0,0 @@ -"""Tests for typed cooperative-perception communication contracts.""" - -from dataclasses import fields -import pickle -import sys - -import numpy as np -import pytest - -mocked_opencood = sys.modules.get("opencood") -if mocked_opencood is not None and not hasattr(mocked_opencood, "__path__"): - pytest.skip( - "full OpenCOOD tests are disabled when the lightweight OpenCDA test doubles are active", - allow_module_level=True, - ) - -pytest.importorskip("torch") - -from opencood.models.communication_adapters import ( # noqa: E402 - BevInferenceInput, - DetectionInferenceInput, - EarlyFusionWirePayload, - FpvrcnnAgentInferenceInput, - FpvrcnnWirePayload, - IntermediateFeatureWirePayload, - LateFusionWirePayload, - PointCloudInferenceInput, - PoseFrameMetadata, - SpatialFeatureInput, - VoxelInferenceInput, - Where2CommWirePayload, - build_inference_input, - inference_input_to_dict, - merge_inference_inputs, -) - - -@pytest.mark.parametrize( - ("preprocessor_output", "expected_type"), - [ - ( - { - "voxel_features": np.ones((2, 4), dtype=np.float32), - "voxel_coords": np.zeros((2, 4), dtype=np.int32), - }, - VoxelInferenceInput, - ), - ( - { - "voxel_features": np.ones((2, 4), dtype=np.float32), - "voxel_coords": np.zeros((2, 4), dtype=np.int32), - "voxel_num_points": np.ones(2, dtype=np.int32), - }, - VoxelInferenceInput, - ), - ({"bev_input": np.ones((2, 3, 4), dtype=np.float32)}, BevInferenceInput), - ( - {"downsample_lidar": np.ones((8, 4), dtype=np.float32)}, - PointCloudInferenceInput, - ), - ], -) -def test_build_inference_input_uses_actual_preprocessor_fields( - preprocessor_output, - expected_type, -): - inference_input = build_inference_input(preprocessor_output) - - assert isinstance(inference_input, expected_type) - converted = inference_input_to_dict(inference_input) - assert converted.keys() == preprocessor_output.keys() - for field_name, value in preprocessor_output.items(): - np.testing.assert_array_equal(converted[field_name], value) - - -def test_build_inference_input_rejects_unknown_or_mixed_fields(): - with pytest.raises(ValueError, match="Unsupported preprocessor output fields"): - build_inference_input( - { - "bev_input": np.ones((2, 2), dtype=np.float32), - "voxel_features": np.ones((2, 2), dtype=np.float32), - } - ) - - -def test_merge_inference_inputs_preserves_all_agent_values(): - first = BevInferenceInput(bev_input=np.full((2, 2), 1.0, dtype=np.float32)) - second = BevInferenceInput(bev_input=np.full((2, 2), 2.0, dtype=np.float32)) - - merged = merge_inference_inputs([first, second]) - - assert list(merged) == ["bev_input"] - np.testing.assert_array_equal(merged["bev_input"][0], first.bev_input) - np.testing.assert_array_equal(merged["bev_input"][1], second.bev_input) - - -def test_merge_inference_inputs_rejects_heterogeneous_representations(): - with pytest.raises(TypeError, match="same inference input representation"): - merge_inference_inputs( - [ - BevInferenceInput(bev_input=np.ones((2, 2), dtype=np.float32)), - PointCloudInferenceInput(points=np.ones((2, 4), dtype=np.float32)), - ] - ) - - -def test_wire_payloads_expose_only_network_contract_fields(): - forbidden_fields = { - "anchor_box", - "label_dict", - "neg_equal_one", - "object_bbx_center", - "object_bbx_mask", - "object_ids", - "pos_equal_one", - "schema_version", - "spoofing_mask", - "targets", - } - expected_fields = { - EarlyFusionWirePayload: {"lidar_points", "metadata"}, - LateFusionWirePayload: {"detections", "metadata"}, - IntermediateFeatureWirePayload: {"inference_input", "metadata"}, - FpvrcnnWirePayload: {"inference_input", "metadata"}, - Where2CommWirePayload: {"inference_input", "metadata"}, - } - - for payload_type, expected in expected_fields.items(): - payload_fields = {field.name for field in fields(payload_type)} - assert payload_fields == expected - assert payload_fields.isdisjoint(forbidden_fields) - - -def test_fpvrcnn_wire_payload_contains_stage_one_and_vsa_outputs(): - metadata = PoseFrameMetadata(lidar_pose=(1.0, 2.0, 3.0, 0.0, 0.0, 0.0), capture_frame=17) - inference_input = FpvrcnnAgentInferenceInput( - proposals=np.ones((2, 7), dtype=np.float32), - proposal_scores=np.array([0.9, 0.8], dtype=np.float32), - point_coords=np.ones((3, 3), dtype=np.float32), - point_features=np.ones((3, 8), dtype=np.float32), - ) - - payload = FpvrcnnWirePayload(inference_input=inference_input, metadata=metadata) - - assert payload.metadata.capture_frame == 17 - assert payload.inference_input.proposals.shape == (2, 7) - assert payload.inference_input.point_features.shape == (3, 8) - - -def test_intermediate_wire_payload_contains_learned_features(): - payload = IntermediateFeatureWirePayload( - inference_input=SpatialFeatureInput( - spatial_features=np.ones((1, 64, 48, 176), dtype=np.float32), - ), - metadata=None, - ) - - assert payload.inference_input.spatial_features.shape == (1, 64, 48, 176) - - -def test_typed_wire_payload_survives_v2x_pickle_round_trip(): - payload = LateFusionWirePayload( - detections=DetectionInferenceInput( - boxes=np.arange(24, dtype=np.float32).reshape(1, 8, 3), - scores=np.array([0.75], dtype=np.float32), - ), - metadata=PoseFrameMetadata( - lidar_pose=(1.0, 2.0, 3.0, 0.0, 0.0, 0.0), - capture_frame=21, - ), - ) - - restored = pickle.loads(pickle.dumps(payload)) - - assert isinstance(restored, LateFusionWirePayload) - assert restored.metadata == payload.metadata - np.testing.assert_array_equal( - restored.detections.boxes, - payload.detections.boxes, - ) diff --git a/OpenCOOD/test/test_intermediate_fusion_contract.py b/OpenCOOD/test/test_intermediate_fusion_contract.py deleted file mode 100644 index 1a4e7a33..00000000 --- a/OpenCOOD/test/test_intermediate_fusion_contract.py +++ /dev/null @@ -1,244 +0,0 @@ -"""Tests for the learned-feature Intermediate fusion boundary.""" - -from collections import OrderedDict -import sys -from unittest.mock import MagicMock - -import numpy as np -import pytest - -mocked_opencood = sys.modules.get("opencood") -if mocked_opencood is not None and not hasattr(mocked_opencood, "__path__"): - pytest.skip( - "full OpenCOOD tests are disabled when the lightweight OpenCDA test doubles are active", - allow_module_level=True, - ) - -pytest.importorskip("torch") -pytest.importorskip("open3d") - -from opencood.data_utils.datasets.intermediate_fusion_dataset import ( # noqa: E402 - IntermediateFusionDataset, -) -from opencood.models.communication_adapters import ( # noqa: E402 - PoseFrameMetadata, - V2XViTMetadata, -) - - -class RecordingPayloadHandler: - """Collect payloads published by a dataset extraction call.""" - - def __init__(self) -> None: - self.published = [] - - def set_opencda_payload(self, cav_id, module_name, payload) -> None: - self.published.append((cav_id, module_name, payload)) - - -def test_extract_data_requires_adapter_to_build_learned_payloads(): - dataset = IntermediateFusionDataset.__new__(IntermediateFusionDataset) - dataset.cur_ego_pose_flag = True - dataset.module_name = "OpenCOOD.IntermediateFusionDataset" - dataset.model_name = "point_pillar_fcooper" - dataset.payload_handler = RecordingPayloadHandler() - dataset.retrieve_base_data = MagicMock( - return_value=OrderedDict( - { - "cav-1": { - "ego": True, - "params": {"lidar_pose": [1.0, 2.0, 0.0, 0.0, 0.0, 0.0]}, - "time_delay": 0, - }, - "cav-2": { - "ego": False, - "params": {"lidar_pose": [3.0, 4.0, 0.0, 0.0, 0.0, 0.0]}, - "time_delay": 1, - }, - } - ) - ) - inference_inputs = [object(), object()] - dataset.get_item_single_car = MagicMock( - side_effect=[ - {"inference_input": inference_inputs[0]}, - {"inference_input": inference_inputs[1]}, - ] - ) - payload_builder = MagicMock(side_effect=lambda model_input, metadata: (model_input, metadata)) - - dataset.extract_data(7, payload_builder) - - assert payload_builder.call_count == 2 - assert [entry[0] for entry in dataset.payload_handler.published] == [ - "cav-1", - "cav-2", - ] - assert dataset.payload_handler.published[0][2] == (inference_inputs[0], None) - assert dataset.payload_handler.published[1][2] == (inference_inputs[1], None) - - -def test_message_assembly_keeps_only_delivered_learned_features(): - dataset = IntermediateFusionDataset.__new__(IntermediateFusionDataset) - dataset.communication_adapter = MagicMock() - dataset.communication_adapter.encode_local_intermediate_input.return_value = {"spatial_features": np.ones((1, 2, 2, 2), dtype=np.float32)} - dataset.communication_adapter.decode_received_payload.return_value = { - "spatial_features": np.full((1, 2, 2, 2), 2.0, dtype=np.float32), - "metadata": None, - } - dataset.payload_handler = MagicMock() - dataset.payload_handler.current_artery_payload = {"cav-1": object()} - delivered_payload = object() - dataset.payload_handler.get_artery_payload.side_effect = lambda _receiver, sender, _module: delivered_payload if sender == "cav-2" else None - dataset.module_name = "OpenCOOD.IntermediateFusionDataset" - dataset.get_item_single_car = MagicMock(return_value={"inference_input": object()}) - dataset.build_model_metadata = MagicMock(return_value=None) - base_data = OrderedDict( - { - "cav-1": {"ego": True}, - "cav-2": {"ego": False}, - "cav-3": {"ego": False}, - } - ) - ego_pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - result = dataset._IntermediateFusionDataset__process_with_messages( - "cav-1", - ego_pose, - base_data, - receive_frame=9, - ) - - assert len(result["intermediate_features"]) == 2 - assert result["metadata"] == [None, None] - dataset.communication_adapter.decode_received_payload.assert_called_once_with( - delivered_payload, - ego_pose, - ) - - -def test_inference_sample_concatenates_agent_feature_batches(): - dataset = IntermediateFusionDataset.__new__(IntermediateFusionDataset) - dataset.model_name = "point_pillar_fcooper" - dataset.visualize = False - local_supervision = { - "object_bbx_center": np.ones((2, 7), dtype=np.float32), - "object_bbx_mask": np.ones(2, dtype=np.float32), - "object_ids": ["vehicle-1"], - "anchor_box": np.ones((2, 7), dtype=np.float32), - "label_dict": {}, - } - - sample = dataset.assemble_inference_sample( - inference_input={ - "intermediate_features": [ - {"spatial_features": np.ones((1, 2, 2, 2), dtype=np.float32)}, - {"spatial_features": np.full((1, 2, 2, 2), 2.0, dtype=np.float32)}, - ], - "metadata": [None, None], - }, - local_supervision=local_supervision, - visualization_context={}, - receive_frame=3, - ) - - ego_sample = sample["ego"] - assert ego_sample["cav_num"] == 2 - assert "processed_lidar" not in ego_sample - np.testing.assert_array_equal( - ego_sample["intermediate_features"]["spatial_features"][:, 0, 0, 0], - np.array([1.0, 2.0], dtype=np.float32), - ) - - -def test_pairwise_transforms_are_derived_only_for_available_agents(monkeypatch): - dataset = IntermediateFusionDataset.__new__(IntermediateFusionDataset) - dataset.model_name = "point_pillar_v2vnet" - dataset.max_cav = 3 - dataset.proj_first = False - dataset.visualize = False - transform = MagicMock( - side_effect=lambda source, target: np.full( - (4, 4), - source[0] * 10 + target[0], - dtype=np.float64, - ) - ) - monkeypatch.setattr( - "opencood.data_utils.datasets.intermediate_fusion_dataset.x1_to_x2", - transform, - ) - metadata = [ - PoseFrameMetadata( - lidar_pose=(1.0, 0.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=4, - ), - PoseFrameMetadata( - lidar_pose=(2.0, 0.0, 0.0, 0.0, 0.0, 0.0), - capture_frame=4, - ), - ] - - sample = dataset.assemble_inference_sample( - inference_input={ - "intermediate_features": [ - {"spatial_features": np.ones((1, 2, 2, 2), dtype=np.float32)}, - {"spatial_features": np.ones((1, 2, 2, 2), dtype=np.float32)}, - ], - "metadata": metadata, - }, - local_supervision={}, - visualization_context={}, - receive_frame=4, - ) - - pairwise = sample["ego"]["pairwise_t_matrix"] - assert pairwise.shape == (3, 3, 4, 4) - assert transform.call_count == 4 - np.testing.assert_array_equal(pairwise[0, 1], np.full((4, 4), 12.0)) - np.testing.assert_array_equal(pairwise[2], np.zeros((3, 4, 4))) - - -def test_v2xvit_metadata_is_padded_after_delivered_agents(): - dataset = IntermediateFusionDataset.__new__(IntermediateFusionDataset) - dataset.model_name = "point_pillar_transformer" - dataset.max_cav = 3 - dataset.visualize = False - first_correction = np.eye(4, dtype=np.float32) - second_correction = np.full((4, 4), 2.0, dtype=np.float32) - metadata = [ - V2XViTMetadata( - velocity=0.5, - agent_type=0, - capture_frame=8, - spatial_correction_matrix=first_correction, - ), - V2XViTMetadata( - velocity=0.25, - agent_type=1, - capture_frame=6, - spatial_correction_matrix=second_correction, - ), - ] - - sample = dataset.assemble_inference_sample( - inference_input={ - "intermediate_features": [ - {"spatial_features": np.ones((1, 2, 2, 2), dtype=np.float32)}, - {"spatial_features": np.ones((1, 2, 2, 2), dtype=np.float32)}, - ], - "metadata": metadata, - }, - local_supervision={}, - visualization_context={}, - receive_frame=10, - ) - - ego_sample = sample["ego"] - assert ego_sample["velocity"] == [0.5, 0.25, 0.0] - assert ego_sample["time_delay"] == [2.0, 4.0, 0.0] - assert ego_sample["infra"] == [0, 1, 0.0] - np.testing.assert_array_equal( - ego_sample["spatial_correction_matrix"], - np.stack([first_correction, second_correction, np.eye(4)]), - ) diff --git a/OpenCOOD/test/test_intermediate_fusion_v2_contract.py b/OpenCOOD/test/test_intermediate_fusion_v2_contract.py deleted file mode 100644 index 81963bbe..00000000 --- a/OpenCOOD/test/test_intermediate_fusion_v2_contract.py +++ /dev/null @@ -1,197 +0,0 @@ -"""Tests for the model-neutral IntermediateFusionDatasetV2 boundary.""" - -from collections import OrderedDict -import sys -from unittest.mock import MagicMock - -import numpy as np -import pytest - -mocked_opencood = sys.modules.get("opencood") -if mocked_opencood is not None and not hasattr(mocked_opencood, "__path__"): - pytest.skip( - "full OpenCOOD tests are disabled when the lightweight OpenCDA test doubles are active", - allow_module_level=True, - ) - -pytest.importorskip("torch") -pytest.importorskip("open3d") - -from opencood.data_utils.datasets.intermediate_fusion_dataset_v2 import ( # noqa: E402 - IntermediateFusionDatasetV2, -) -from opencood.models.communication_adapters import PoseFrameMetadata # noqa: E402 - - -class RecordingPayloadHandler: - def __init__(self) -> None: - self.published = [] - - def set_opencda_payload(self, cav_id, module_name, payload) -> None: - self.published.append((cav_id, module_name, payload)) - - -def _build_dataset_for_extraction(): - dataset = IntermediateFusionDatasetV2.__new__(IntermediateFusionDatasetV2) - dataset.cur_ego_pose_flag = True - dataset.module_name = "OpenCOOD.IntermediateFusionDatasetV2" - dataset.payload_handler = RecordingPayloadHandler() - dataset.retrieve_base_data = MagicMock( - return_value=OrderedDict( - { - "cav-1": { - "params": {"lidar_pose": [1.0, 2.0, 0.0, 0.0, 0.0, 0.0]}, - "time_delay": 0, - }, - "cav-2": { - "params": {"lidar_pose": [3.0, 4.0, 0.0, 0.0, 0.0, 0.0]}, - "time_delay": 2, - }, - } - ) - ) - dataset.build_agent_model_input = MagicMock( - side_effect=lambda cav, pose: { - "agent_pose": tuple(pose), - "source": cav, - } - ) - return dataset - - -def test_extract_data_delegates_payload_construction_to_model_adapter(): - dataset = _build_dataset_for_extraction() - builder = MagicMock(side_effect=lambda sample, metadata: (sample, metadata)) - - dataset.extract_data(idx=10, agent_payload_builder=builder) - - assert builder.call_count == 2 - assert [entry[0] for entry in dataset.payload_handler.published] == ["cav-1", "cav-2"] - assert all(entry[1] == "OpenCOOD.IntermediateFusionDatasetV2" for entry in dataset.payload_handler.published) - first_metadata = dataset.payload_handler.published[0][2][1] - second_metadata = dataset.payload_handler.published[1][2][1] - assert isinstance(first_metadata, PoseFrameMetadata) - assert first_metadata.capture_frame == 10 - assert second_metadata.capture_frame == 8 - assert first_metadata.lidar_pose == (1.0, 2.0, 0.0, 0.0, 0.0, 0.0) - - -def test_extract_data_does_not_assume_fpvrcnn_without_adapter(): - dataset = _build_dataset_for_extraction() - - with pytest.raises(NotImplementedError, match="model-specific communication adapter"): - dataset.extract_data(idx=10) - - -def test_local_supervision_uses_complete_scene_and_stays_outside_wire_path(): - dataset = IntermediateFusionDatasetV2.__new__(IntermediateFusionDatasetV2) - dataset.payload_handler = object() - dataset.post_processor = MagicMock() - object_centers = np.ones((4, 7), dtype=np.float32) - object_mask = np.array([1.0, 1.0, 0.0, 0.0], dtype=np.float32) - object_ids = ["vehicle-1", "vehicle-2"] - anchors = np.ones((2, 2, 7), dtype=np.float32) - dataset.post_processor.generate_object_center.return_value = ( - object_centers, - object_mask, - object_ids, - ) - dataset.post_processor.generate_anchor_box.return_value = anchors - base_data = OrderedDict( - { - "cav-1": {"objects": ["vehicle-1"]}, - "cav-2": {"objects": ["vehicle-2"]}, - } - ) - ego_pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - supervision = dataset.build_local_supervision(base_data, ego_pose) - - dataset.post_processor.generate_object_center.assert_called_once_with( - list(base_data.values()), - ego_pose, - ) - assert supervision["object_ids"] == object_ids - np.testing.assert_array_equal(supervision["object_bbx_center"], object_centers) - np.testing.assert_array_equal(supervision["object_bbx_mask"], object_mask) - np.testing.assert_array_equal(supervision["anchor_box"], anchors) - assert "stage2_label" not in supervision - dataset.post_processor.generate_label.assert_not_called() - - -def test_local_supervision_builds_stage_two_labels_only_without_capi_transport(): - dataset = IntermediateFusionDatasetV2.__new__(IntermediateFusionDatasetV2) - dataset.payload_handler = None - dataset.post_processor = MagicMock() - object_centers = np.ones((2, 7), dtype=np.float32) - object_mask = np.ones(2, dtype=np.float32) - anchors = np.ones((2, 2, 7), dtype=np.float32) - stage_two_label = {"targets": np.ones((2, 2, 7), dtype=np.float32)} - dataset.post_processor.generate_object_center.return_value = ( - object_centers, - object_mask, - ["vehicle-1", "vehicle-2"], - ) - dataset.post_processor.generate_anchor_box.return_value = anchors - dataset.post_processor.generate_label.return_value = stage_two_label - - supervision = dataset.build_local_supervision( - OrderedDict({"cav-1": {}, "cav-2": {}}), - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ) - - assert supervision["stage2_label"] is stage_two_label - dataset.post_processor.generate_label.assert_called_once_with( - gt_box_center=object_centers, - anchors=anchors, - mask=object_mask, - ) - - -def test_received_payloads_are_decoded_by_attached_model_adapter(): - dataset = IntermediateFusionDatasetV2.__new__(IntermediateFusionDatasetV2) - dataset.communication_adapter = MagicMock() - dataset.communication_adapter.decode_received_payload.return_value = {"model_specific_feature": np.array([1.0], dtype=np.float32)} - dataset.payload_handler = MagicMock() - dataset.payload_handler.current_artery_payload = {"cav-1": {"cav-2": object()}} - raw_payload = object() - dataset.payload_handler.get_artery_payload.side_effect = [raw_payload, None] - local_input = { - "processed_features": {"voxel_features": np.ones((1, 4), dtype=np.float32)}, - "projected_lidar": np.ones((2, 4), dtype=np.float32), - } - dataset.build_agent_model_input = MagicMock(return_value=local_input) - dataset.build_visualization_context = MagicMock( - return_value={ - "projected_lidar_stack": [], - "projected_lidar_roles": [], - "projected_lidar_agent_ids": [], - } - ) - base_data = OrderedDict( - { - "cav-1": {"ego": True}, - "cav-2": {"ego": False}, - "cav-3": {"ego": False}, - } - ) - ego_pose = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - - result = dataset._IntermediateFusionDatasetV2__process_with_messages( - "cav-1", - ego_pose, - base_data, - ego_pose, - base_data, - ) - - dataset.communication_adapter.decode_received_payload.assert_called_once_with( - raw_payload, - ego_pose, - ) - assert result["processed_features"][0] is local_input["processed_features"] - assert len(result["remote_agent_outputs"]) == 1 - np.testing.assert_array_equal( - result["remote_agent_outputs"][0]["model_specific_feature"], - np.array([1.0], dtype=np.float32), - ) diff --git a/OpenCOOD/test/test_intermediate_model_split.py b/OpenCOOD/test/test_intermediate_model_split.py deleted file mode 100644 index cf3af533..00000000 --- a/OpenCOOD/test/test_intermediate_model_split.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Tests for distributed intermediate-model execution boundaries.""" - -import sys - -import pytest - -mocked_opencood = sys.modules.get("opencood") -if mocked_opencood is not None and not hasattr(mocked_opencood, "__path__"): - pytest.skip( - "full OpenCOOD tests are disabled when the lightweight OpenCDA test doubles are active", - allow_module_level=True, - ) - -torch = pytest.importorskip("torch") - -from opencood.models.fuse_modules.where2comm_fuse import Communication # noqa: E402 -from opencood.models.sub_modules.att_bev_backbone import AttBEVBackbone # noqa: E402 - - -@pytest.mark.parametrize("compression", [0, 1]) -def test_multiscale_backbone_split_matches_centralized_path(compression): - model_config = { - "layer_nums": [0, 0], - "layer_strides": [1, 2], - "num_filters": [4, 8], - "upsample_strides": [1, 2], - "num_upsample_filter": [4, 4], - "compression": compression, - } - backbone = AttBEVBackbone(model_config, input_channels=4).eval() - spatial_features = ( - torch.arange( - 2 * 4 * 8 * 8, - dtype=torch.float32, - ).reshape(2, 4, 8, 8) - / 100 - ) - record_len = torch.tensor([2], dtype=torch.long) - - with torch.no_grad(): - centralized = backbone( - { - "spatial_features": spatial_features.clone(), - "record_len": record_len, - } - )["spatial_features_2d"] - encoded_features = backbone.encode_agent(spatial_features.clone()) - distributed = backbone.fuse_agents(encoded_features, record_len) - - torch.testing.assert_close(distributed, centralized) - if compression: - assert encoded_features[0].shape == (2, 2, 4, 4) - assert encoded_features[0].numel() < 2 * 4 * 8 * 8 - - -def test_where2comm_rate_is_measured_before_local_feature_is_forced(): - communication = Communication({"threshold": 0.5}).eval() - confidence_map = torch.tensor( - [ - [[[10.0, -10.0]]], - [[[-10.0, 10.0]]], - ], - dtype=torch.float32, - ) - - transmitted_mask, transmitted_rate = communication.build_mask( - confidence_map, - force_first=False, - ) - receiver_mask, receiver_rate = communication.build_mask( - confidence_map, - force_first=True, - ) - - torch.testing.assert_close( - transmitted_mask, - torch.tensor( - [ - [[[1.0, 0.0]]], - [[[0.0, 1.0]]], - ] - ), - ) - torch.testing.assert_close( - receiver_mask, - torch.tensor( - [ - [[[1.0, 1.0]]], - [[[0.0, 1.0]]], - ] - ), - ) - assert transmitted_rate.item() == pytest.approx(0.5) - assert receiver_rate.item() == pytest.approx(0.5) diff --git a/OpenCOOD/test/test_late_fusion_postprocessors.py b/OpenCOOD/test/test_late_fusion_postprocessors.py deleted file mode 100644 index 923bc982..00000000 --- a/OpenCOOD/test/test_late_fusion_postprocessors.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Tests for sender-side late-fusion prediction decoding.""" - -import sys -from unittest.mock import MagicMock - -import numpy as np -import pytest - -mocked_opencood = sys.modules.get("opencood") -if mocked_opencood is not None and not hasattr(mocked_opencood, "__path__"): - pytest.skip( - "full OpenCOOD tests are disabled when the lightweight OpenCDA test doubles are active", - allow_module_level=True, - ) - -torch = pytest.importorskip("torch") - -from opencood.data_utils.post_processor import ciassd_postprocessor # noqa: E402 -from opencood.data_utils.post_processor import voxel_postprocessor # noqa: E402 -from opencood.data_utils.post_processor.bev_postprocessor import ( # noqa: E402 - BevPostprocessor, -) -from opencood.data_utils.post_processor.ciassd_postprocessor import ( # noqa: E402 - CiassdPostprocessor, -) -from opencood.data_utils.post_processor.voxel_postprocessor import ( # noqa: E402 - VoxelPostprocessor, -) - - -def test_voxel_sender_decodes_candidates_without_nms_or_projection(monkeypatch): - postprocessor = VoxelPostprocessor.__new__(VoxelPostprocessor) - postprocessor.params = { - "target_args": {"score_threshold": 0.5}, - "order": "hwl", - } - decoded_boxes = torch.tensor( - [[[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.25]]], - dtype=torch.float32, - ) - expected_corners = torch.ones((1, 8, 3), dtype=torch.float32) - postprocessor.delta_to_boxes3d = MagicMock(return_value=decoded_boxes) - corners = MagicMock(return_value=expected_corners) - monkeypatch.setattr(voxel_postprocessor.box_utils, "boxes_to_corners_3d", corners) - cav_content = {"anchor_box": torch.ones((1, 7), dtype=torch.float32)} - output = { - "psm": torch.tensor([[[[10.0]]]], dtype=torch.float32), - "rm": torch.zeros((1, 7, 1, 1), dtype=torch.float32), - } - - boxes, scores = postprocessor.decode_agent_predictions(cav_content, output) - - assert boxes is expected_corners - assert scores.item() == pytest.approx(torch.sigmoid(torch.tensor(10.0)).item()) - postprocessor.delta_to_boxes3d.assert_called_once_with( - output["rm"], - cav_content["anchor_box"], - ) - corners.assert_called_once_with(decoded_boxes[0], order="hwl") - - -def test_bev_sender_decodes_only_above_threshold_candidates(): - postprocessor = BevPostprocessor.__new__(BevPostprocessor) - postprocessor.params = {"target_args": {"score_threshold": 0.5}} - postprocessor.denormalize_reg_map = MagicMock(side_effect=lambda value: value) - expected_corners = torch.ones((1, 4, 2), dtype=torch.float32) - postprocessor.reg_map_to_bbx_corners = MagicMock(return_value=expected_corners) - output = { - "cls": torch.tensor([[[[10.0, -10.0]]]], dtype=torch.float32), - "reg": torch.zeros((1, 6, 1, 2), dtype=torch.float32), - } - - boxes, scores = postprocessor.decode_agent_predictions({}, output) - - assert boxes is expected_corners - assert scores.shape == (1,) - assert scores.item() == pytest.approx(torch.sigmoid(torch.tensor(10.0)).item()) - mask = postprocessor.reg_map_to_bbx_corners.call_args.args[1] - torch.testing.assert_close(mask, torch.tensor([[True, False]])) - - -def test_ciassd_sender_applies_iou_and_direction_before_serialization( - monkeypatch, -): - postprocessor = CiassdPostprocessor.__new__(CiassdPostprocessor) - postprocessor.params = { - "target_args": {"score_threshold": 0.5}, - "order": "hwl", - } - decoded_boxes = torch.tensor( - [ - [ - [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.25], - [7.0, 8.0, 9.0, 4.0, 5.0, 6.0, -0.5], - ] - ], - dtype=torch.float32, - ) - expected_corners = torch.ones((1, 8, 3), dtype=torch.float32) - postprocessor.delta_to_boxes3d = MagicMock(return_value=decoded_boxes) - corners = MagicMock(return_value=expected_corners) - monkeypatch.setattr(ciassd_postprocessor.box_utils, "boxes_to_corners_3d", corners) - cav_content = {"anchor_box": torch.ones((1, 1, 2, 7), dtype=torch.float32)} - output = { - "preds_dict_stage1": { - "cls_preds": torch.tensor([[[[10.0]], [[-10.0]]]], dtype=torch.float32), - "box_preds": torch.zeros((1, 14, 1, 1), dtype=torch.float32), - "iou_preds": torch.tensor([[[[1.0]], [[-1.0]]]], dtype=torch.float32), - "dir_cls_preds": torch.tensor( - [[[[1.0]], [[0.0]], [[1.0]], [[0.0]]]], - dtype=torch.float32, - ), - } - } - - boxes, scores = postprocessor.decode_agent_predictions(cav_content, output) - - assert boxes is expected_corners - assert scores.shape == (1,) - assert scores.item() == pytest.approx(torch.sigmoid(torch.tensor(10.0)).item()) - selected_boxes = corners.call_args.args[0] - assert selected_boxes.shape == (1, 7) - assert selected_boxes[0, 6].item() == pytest.approx(0.25 + np.pi) diff --git a/OpenCOOD/test/test_roi_head_inference.py b/OpenCOOD/test/test_roi_head_inference.py deleted file mode 100644 index 1e9c3a55..00000000 --- a/OpenCOOD/test/test_roi_head_inference.py +++ /dev/null @@ -1,86 +0,0 @@ -"""Tests for GT-independent FPV-RCNN RoI inference.""" - -import sys -from unittest.mock import MagicMock - -import pytest - -mocked_opencood = sys.modules.get("opencood") -if mocked_opencood is not None and not hasattr(mocked_opencood, "__path__"): - pytest.skip( - "full OpenCOOD tests are disabled when the lightweight OpenCDA test doubles are active", - allow_module_level=True, - ) - -torch = pytest.importorskip("torch") -from torch import nn # noqa: E402 - -from opencood.models.sub_modules.roi_head import RoIHead # noqa: E402 - - -def _build_minimal_roi_head(proposal_count: int) -> RoIHead: - head = RoIHead.__new__(RoIHead) - nn.Module.__init__(head) - head.code_size = 7 - head.grid_size = 1 - head.roi_grid_pool = MagicMock(return_value=torch.ones((proposal_count, 1, 1), dtype=torch.float32)) - head.shared_fc_layers = nn.Identity() - head.cls_layers = nn.Identity() - head.iou_layers = nn.Identity() - head.reg_layers = nn.Identity() - return head - - -def test_roi_head_inference_prepares_rois_without_ground_truth_assignment(): - boxes_by_scene = [ - torch.tensor( - [ - [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 0.25], - [7.0, 8.0, 9.0, 2.0, 3.0, 4.0, -0.5], - ], - dtype=torch.float32, - ), - torch.tensor( - [[10.0, 11.0, 12.0, 1.0, 2.0, 3.0, 1.25]], - dtype=torch.float32, - ), - ] - head = _build_minimal_roi_head(proposal_count=3) - head.assign_targets = MagicMock(side_effect=AssertionError("assign_targets must not run during inference")) - head.eval() - batch = { - "boxes_fused": boxes_by_scene, - "record_len": torch.tensor([1, 1]), - } - - output = head(batch) - - head.assign_targets.assert_not_called() - assert output["rcnn_label_dict"]["record_len"] == [2, 1] - assert output["rcnn_label_dict"]["rois"].shape == (3, 7) - anchors = output["rcnn_label_dict"]["rois_anchor"] - torch.testing.assert_close(anchors[:, :3], torch.zeros((3, 3))) - torch.testing.assert_close(anchors[:, 6], torch.zeros(3)) - torch.testing.assert_close(anchors[:, 3:6], output["rcnn_label_dict"]["rois"][:, 3:6]) - assert set(output["fpvrcnn_out"]) == {"rcnn_cls", "rcnn_iou", "rcnn_reg"} - - -def test_roi_head_training_keeps_target_assignment_path(): - head = _build_minimal_roi_head(proposal_count=1) - head.assign_targets = MagicMock( - side_effect=lambda batch: { - **batch, - "rcnn_label_dict": { - "rois": torch.ones((1, 7), dtype=torch.float32), - "rois_anchor": torch.ones((1, 7), dtype=torch.float32), - "record_len": [1], - }, - } - ) - head.prepare_rois = MagicMock(side_effect=AssertionError("prepare_rois must not replace training targets")) - head.train() - - head({"record_len": torch.tensor([1])}) - - head.assign_targets.assert_called_once() - head.prepare_rois.assert_not_called() diff --git a/README.md b/README.md index 61e73242..6f743c18 100644 --- a/README.md +++ b/README.md @@ -53,29 +53,31 @@ At a high level, the current fork expects: - CARLA `0.9.16` - Python `3.12` or higher - CUDA and support for GPU inside Docker runtime +- the adjacent `opencood` repository installed as a Python package when cooperative perception is used This fork is intended to run inside the CAVISE Docker environment. Running this fork outside Docker has not been tested. ### Native build artifacts -Protobuf modules and OpenCOOD CUDA extensions are built in independent CMake -stages. Build-only dependencies such as `protoc`, CMake, the CUDA compiler, -and Python development headers are not included in the runtime stage. +Protobuf modules are owned and built by OpenCDA. OpenCOOD owns its CUDA build +and exposes the resulting extensions to the integrated CUDA image. Build-only +dependencies such as `protoc`, CMake, the CUDA compiler, and Python development +headers are not included in runtime stages. Choose the Docker target that matches the required OpenCDA features: -| Target | Protobuf | CUDA extensions | Use when | -|---|---:|---:|---| -| `opencda-minimal` | No | No | Neither Artery/CAPI nor a cooperative perception model requiring the OpenCOOD CUDA extensions is used | -| `opencda-protobuf` | Yes | No | OpenCDA communicates with Artery through CAPI | -| `opencda-cuda` | No | Yes | Cooperative perception uses a model that depends on the OpenCOOD CUDA extensions, such as FPV-RCNN | -| `opencda` | Yes | Yes | Both Artery/CAPI and CUDA-based cooperative perception are required | +| Target | OpenCOOD | Protobuf | CUDA extensions | Use when | +|---|---:|---:|---:|---| +| `opencda-minimal` | No | No | No | Only core OpenCDA is required | +| `opencda-protobuf` | No | Yes | No | OpenCDA communicates with Artery through CAPI | +| `opencda-coperception` | Yes | No | No | Cooperative perception does not need custom CUDA extensions | +| `opencda-cuda` | Yes | No | Yes | A model depends on OpenCOOD CUDA extensions, such as FPV-RCNN | +| `opencda` | Yes | Yes | Yes | Both Artery/CAPI and CUDA-based cooperative perception are required | -Cooperative perception does not require the CUDA build by itself. For CoP -models that do not use the custom OpenCOOD CUDA extensions, -`opencda-minimal` is sufficient. Use `opencda-protobuf` instead when the same -CoP workload also communicates with Artery through CAPI. The CUDA targets are -only required by models that actually use those extensions, such as FPV-RCNN. +Cooperative perception does not require the CUDA build by itself. Use +`opencda-coperception` for models that do not use the custom OpenCOOD CUDA +extensions. The two core-only targets neither copy OpenCOOD nor install its +dependencies, so they can be built when the adjacent repository is absent. The full `opencda` target remains the default. With BuildKit, builder stages that are not dependencies of the selected target are skipped completely. @@ -84,6 +86,7 @@ Build the required image through the CAVISE `run.sh` interface: ```bash ./run.sh build opencda-minimal ./run.sh build opencda-protobuf +./run.sh build opencda-coperception ./run.sh build opencda-cuda ./run.sh build opencda ``` @@ -93,6 +96,7 @@ Use the same target name to start the resulting image: ```bash ./run.sh up opencda-minimal ./run.sh up opencda-protobuf +./run.sh up opencda-coperception ./run.sh up opencda-cuda ./run.sh up opencda ``` @@ -112,10 +116,11 @@ For example: ./run.sh down opencda-protobuf ``` -The resulting native files are stored under `/opt/opencda-artifacts`. Only the -components provided by the selected target are synchronized into the OpenCDA -source tree when the container starts. Rebuild the corresponding target after -changing a `.proto`, `.cpp`, or `.cu` source. +The OpenCDA protobuf files are stored under `/opt/opencda-artifacts`; OpenCOOD +CUDA extensions are stored separately under `/opt/opencood-artifacts`. Each +repository's entrypoint synchronizes only its own native artifacts into the +mounted source tree. Rebuild the corresponding target after changing a +`.proto`, `.cpp`, or `.cu` source. CUDA extensions target compute capability `8.6` by default. Override it when building for other GPUs: @@ -124,9 +129,10 @@ building for other GPUs: CUDA_ARCHITECTURES="75;86;89" ./run.sh build opencda-cuda ``` -All four targets currently use the CUDA runtime base and install the same -Python dependencies. Selecting a target skips native compilation; it does not -produce a CPU-only image. +All five targets use the CUDA runtime base. The core-only targets install only +OpenCDA dependencies, while cooperative-perception targets additionally +install OpenCOOD and its standalone dependency set. These are not CPU-only +images. ## Quick Start @@ -178,12 +184,17 @@ CAPI v2 is the data exchange interface between OpenCDA and Artery. In this fork ### Cooperative perception - `--with-coperception`: Whether to enable the use of cooperative perception models in this simulation. -- `--model-dir`: Path to the cooperative perception model directory. +- `--model-id`: Logical bundle ID from the sibling `models` repository. A missing bundle is fetched automatically with a partial sparse checkout. +- `--model-dir`: Explicit local model directory. This bypasses automatic fetching and remains available for custom models. +- `--models-root`: Override the models checkout path. +- `--models-repository`: Override the Git repository used for automatic fetching. +- `--models-ref`: Branch or tag to clone; defaults to `main`. +- `--no-auto-fetch-models`: Disable network access and fail when a requested bundle is absent locally. - `--show-video-vis`: whether to show video visualization result - `--save-vis`: whether to save visualization result - `--save-npy`: whether to save prediction and gt result in npy_test file -Fusion behavior is resolved from the selected model configuration in `--model-dir`. +Fusion behavior is resolved from the selected model bundle configuration. Example: @@ -191,16 +202,41 @@ Example: python3 opencda.py \ -t 2cars_2rsu_coperception \ --with-coperception \ - --model-dir opencda/coperception_models/pointpillar-late-opv2v-30 \ + --model-id pointpillar-late-opv2v-30 \ --save-vis ``` +The first run creates a sparse checkout in the sibling `models` directory and +downloads only the requested bundle. Subsequent model IDs are added to the +same sparse checkout. During a fetch, OpenCDA creates an empty `.models.lock` +file next to that directory and holds an advisory file lock on it so concurrent +simulation processes cannot modify the checkout at the same time. The file is +not a model-version lockfile and may remain after the process exits. + +Use an alternative repository, ref, or checkout location when testing model +changes: + +```bash +python3 opencda.py \ + -t 2cars_2rsu_coperception \ + --with-coperception \ + --model-id pointpillar-late-opv2v-30 \ + --models-repository https://github.com/example/models.git \ + --models-ref feature/new-checkpoint \ + --models-root /tmp/cavise-models +``` + +For an offline run, populate the checkout first and add +`--no-auto-fetch-models`. OpenCDA then reports a runtime error instead of +accessing the network when the requested bundle is absent. + ### AdvCP AdvCP-style attacks can be enabled on top of cooperative perception. `--with-advcp` requires `--with-coperception` and `--advcp-config`. - `--with-advcp`: Enable AdvCP-style attacks for cooperative perception. - `--advcp-config`: AdvCP attack config name or path. Relative names are resolved from `opencda/scenario_testing/config_yaml/advcp-configs`. +- `--advcp-assets-id`: AdvCP runtime asset bundle from the models repository; defaults to `base-car`. Example: @@ -208,9 +244,10 @@ Example: python3 opencda.py \ -t 3cars_advcp_removal_check \ --with-coperception \ - --model-dir opencda/coperception_models/pointpillar-late-opv2v-30 \ + --model-id pointpillar-late-opv2v-30 \ --with-advcp \ - --advcp-config removal_forward + --advcp-config removal_forward \ + --advcp-assets-id base-car ``` ### Output directories @@ -226,10 +263,10 @@ python3 opencda.py \ - `opencda/metrics_tools`: metric collection, report generation, and plotting infrastructure - `opencda/customize`: extension points for custom perception, localization, planning, and control algorithms - `opencda/codriving_models`: bundled AIM/co-driving model implementations and weights -- `opencda/coperception_models`: bundled cooperative-perception model configurations and checkpoints - `opencda/core/common/communication`: CAPI v2 transport and protobuf messages generated by the CMake builder - `AIM`: AIM model code and assets used by cooperative-driving services -- `OpenCOOD`: bundled cooperative-perception framework +- `../opencood`: OpenCOOD cooperative-perception framework kept as an adjacent repository +- `../models`: model checkpoints and AdvCP runtime assets, fetched bundle-by-bundle when required - `scripts`: map conversion, spectator control, prediction conversion, and video helper commands - `test`: repository-level unit and integration tests @@ -243,13 +280,13 @@ python3 opencda.py \ ### Scenario `v2xp_datadump_town06_carla` -Run cooperative perception with the bundled Where2Comm intermediate fusion model: +Run cooperative perception with the Where2Comm intermediate fusion model: ```bash python3 opencda.py \ -t v2xp_datadump_town06_carla \ --with-coperception \ - --model-dir opencda/coperception_models/pointpillar-where2comm-intermediate-v2xsim-50 \ + --model-id pointpillar-where2comm-intermediate-v2xsim-50 \ --save-vis ``` diff --git a/docker/sync-build-artifacts.sh b/docker/sync-build-artifacts.sh index a32ef400..02d090b9 100755 --- a/docker/sync-build-artifacts.sh +++ b/docker/sync-build-artifacts.sh @@ -6,133 +6,60 @@ readonly artifact_root="${OPENCDA_ARTIFACT_ROOT:-/opt/opencda-artifacts}" readonly workspace="${OPENCDA_WORKSPACE:-${HOME}/cavise/opencda}" readonly native_components="${OPENCDA_NATIVE_COMPONENTS:-}" readonly protobuf_destination="${workspace}/opencda/core/common/communication/protos/cavise" -readonly cuda_destination="${workspace}/OpenCOOD/opencood/pcdet_utils" -load_artifact_manifest() { - local component="$1" - local result_name="$2" - local component_root="${artifact_root}/${component}" - local manifest="${component_root}/${component}-artifacts.manifest" - local -n result="${result_name}" +sync_protobuf_artifacts() { + local component_root="${artifact_root}/protobuf" + local manifest="${component_root}/protobuf-artifacts.manifest" + local relative_path + local -a artifact_paths local -a installed_artifacts if [[ ! -s "${manifest}" ]]; then - echo "Missing or empty ${component} artifact manifest: ${manifest}" >&2 + echo "Missing or empty protobuf artifact manifest: ${manifest}" >&2 exit 1 fi - mapfile -t result < <(sed "/^[[:space:]]*$/d" "${manifest}") - if [[ "${#result[@]}" -eq 0 ]]; then - echo "The ${component} artifact manifest contains no files" >&2 - exit 1 - fi + mapfile -t artifact_paths < <(sed "/^[[:space:]]*$/d" "${manifest}") + mapfile -d "" installed_artifacts < <( + find "${component_root}" -type f \ + \( -name "*_pb2.py" -o -name "*_pb2.pyi" \) -print0 + ) - case "${component}" in - protobuf) - mapfile -d "" installed_artifacts < <( - find "${component_root}" -type f \ - \( -name "*_pb2.py" -o -name "*_pb2.pyi" \) -print0 - ) - ;; - cuda) - mapfile -d "" installed_artifacts < <( - find "${component_root}" -type f -name "*_cuda*.so" -print0 - ) - ;; - *) - echo "Unsupported artifact component: ${component}" >&2 - exit 1 - ;; - esac - - if [[ "${#result[@]}" -ne "${#installed_artifacts[@]}" ]]; then - echo "${component} artifact count does not match its manifest: expected ${#result[@]}, found ${#installed_artifacts[@]}" >&2 + if [[ "${#artifact_paths[@]}" -eq 0 || \ + "${#artifact_paths[@]}" -ne "${#installed_artifacts[@]}" ]]; then + echo "Protobuf artifact count does not match its manifest" >&2 exit 1 fi - local relative_path - for relative_path in "${result[@]}"; do + mkdir -p "${protobuf_destination}" + find "${protobuf_destination}" -maxdepth 1 -type f \ + \( -name "*_pb2.py" -o -name "*_pb2.pyi" \) -delete + + for relative_path in "${artifact_paths[@]}"; do if [[ "${relative_path}" == /* || "${relative_path}" == ".." || \ "${relative_path}" == ../* || "${relative_path}" == */../* || \ - "${relative_path}" == */.. ]]; then - echo "Invalid path in ${component} artifact manifest: ${relative_path}" >&2 + "${relative_path}" == */.. || \ + ! -f "${component_root}/${relative_path}" ]]; then + echo "Invalid or missing protobuf artifact: ${relative_path}" >&2 exit 1 fi - if [[ ! -f "${component_root}/${relative_path}" ]]; then - echo "Missing ${component} artifact: ${relative_path}" >&2 - exit 1 - fi + mkdir -p "${workspace}/${relative_path%/*}" + cp "${component_root}/${relative_path}" "${workspace}/${relative_path}" done } -sync_artifacts() { - local component="$1" - local artifacts_name="$2" - local component_root="${artifact_root}/${component}" - local -n artifact_paths="${artifacts_name}" - local relative_path - local destination - - for relative_path in "${artifact_paths[@]}"; do - destination="${workspace}/${relative_path}" - mkdir -p "${destination%/*}" - cp "${component_root}/${relative_path}" "${destination}" - done -} - -clean_workspace_artifacts() { - local component="$1" - - case "${component}" in - protobuf) - mkdir -p "${protobuf_destination}" - find "${protobuf_destination}" -maxdepth 1 -type f \ - \( -name "*_pb2.py" -o -name "*_pb2.pyi" \) -delete - ;; - cuda) - if [[ -d "${cuda_destination}" ]]; then - find "${cuda_destination}" -type f -name "*_cuda*.so" -delete - fi - ;; - *) - echo "Unsupported native component: ${component}" >&2 - exit 1 - ;; - esac -} - -sync_component() { - local component="$1" - local -a artifacts - - case "${component}" in - protobuf|cuda) - ;; - *) - echo "Unsupported native component: ${component}" >&2 - exit 1 - ;; - esac - - load_artifact_manifest "${component}" artifacts - clean_workspace_artifacts "${component}" - sync_artifacts "${component}" artifacts -} - declare -a components=() if [[ -n "${native_components}" ]]; then read -r -a components <<< "${native_components}" fi -declare -A synchronized_components=() for component in "${components[@]}"; do - if [[ -n "${synchronized_components[${component}]:-}" ]]; then - continue + if [[ "${component}" != "protobuf" ]]; then + echo "Unsupported OpenCDA native component: ${component}" >&2 + exit 1 fi - - sync_component "${component}" - synchronized_components["${component}"]=1 + sync_protobuf_artifacts done exec "$@" diff --git a/opencda.py b/opencda.py index bee9b599..be22fc66 100644 --- a/opencda.py +++ b/opencda.py @@ -159,7 +159,12 @@ def arg_parse() -> argparse.Namespace: parser.add_argument( "--with-coperception", action="store_true", help="Whether to enable the use of cooperative perception models in this simulation." ) - parser.add_argument("--model-dir", type=str, help="Continued training path") + parser.add_argument("--model-id", type=str, help="Logical model bundle ID from the CAVISE models repository.") + parser.add_argument("--model-dir", type=str, help="Explicit local cooperative-perception model directory; disables automatic fetching.") + parser.add_argument("--models-root", type=str, help="Models checkout path (default: CAVISE_MODELS_ROOT or the sibling models repository).") + parser.add_argument("--models-repository", type=str, help="Git repository used to fetch missing model bundles.") + parser.add_argument("--models-ref", type=str, help="Git branch or tag used when cloning the models repository (default: main).") + parser.add_argument("--no-auto-fetch-models", action="store_true", help="Fail instead of fetching a missing model or runtime asset bundle.") parser.add_argument("--show-video-vis", action="store_true", help="whether to show video visualization result") parser.add_argument("--save-vis", action="store_true", help="whether to save visualization result") parser.add_argument("--save-npy", action="store_true", help="whether to save prediction and gt result in npy_test file") @@ -171,6 +176,7 @@ def arg_parse() -> argparse.Namespace: type=str, help="AdvCP attack config name or path. Relative names are resolved from opencda/scenario_testing/config_yaml/advcp.", ) + parser.add_argument("--advcp-assets-id", type=str, default="base-car", help="Logical AdvCP runtime asset bundle ID.") def verbosity_wrapper(arg: str) -> VerbosityLevel: return VerbosityLevel(int(arg)) @@ -251,8 +257,9 @@ def main() -> None: opt.advcp_config = str(advcp_config) - # allow OpenCOOD imports - sys.path.append(str(cwd.joinpath("OpenCOOD"))) + from opencda.core.plan.model_resolver import resolve_runtime_models + + resolve_runtime_models(opt) # set the yaml file for the specific testing scenario # load the default yaml file and the scenario yaml file as dictionaries diff --git a/opencda/coperception_models/fpvrcnn-intermediateV2-opv2v-40/config.yaml b/opencda/coperception_models/fpvrcnn-intermediateV2-opv2v-40/config.yaml deleted file mode 100644 index 7a2ae3f0..00000000 --- a/opencda/coperception_models/fpvrcnn-intermediateV2-opv2v-40/config.yaml +++ /dev/null @@ -1,243 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDatasetV2 -loss: - args: - stage1: - cls: - alpha: 0.25 - gamma: 2.0 - type: SigmoidFocalLoss - weight: 2.0 - dir: - type: WeightedSoftmaxClassificationLoss - weight: 0.2 - encode_rad_error_by_sin: true - iou: &id001 - codewise: true - sigma: 3.0 - type: WeightedSmoothL1Loss - weight: 1.0 - pos_cls_weight: 2.0 - reg: - codewise: true - sigma: 3.0 - type: WeightedSmoothL1Loss - weight: 2.0 - stage2: - cls: - type: WeightedSigmoidBinaryCELoss - weight: 1.0 - iou: *id001 - reg: - code_weights: - - 20.0 - reduction: mean - sigma: 3.0 - type: WeightedSmoothL1Loss - weight: 1.0 - core_method: fpvrcnn_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 10 - - 20 -model: - args: - D: 52 - H: 832 - N: 2 - T: 5 - W: 2816 - activate_stage2: true - anchor_num: 2 - head: - num_cls: 2 - num_dir: 4 - num_input: 128 - num_iou: 2 - num_pred: 14 - use_dir: true - lidar_range: &id002 - - -140.8 - - -41.6 - - -10.6 - - 140.8 - - 41.6 - - 10.6 - map2bev: - feature_num: 128 - matcher: none - mean_vfe: - num_point_features: 4 - post_processer: &id003 - gt_range: *id002 - anchor_args: - D: 52 - H: 832 - W: 2816 - cav_lidar_range: *id002 - feature_stride: 8 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 0.4 - vh: 0.1 - vw: 0.1 - w: 1.6 - core_method: FpvrcnnPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.5 - roi_head: - dp_ratio: 0.3 - in_channels: 32 - n_fc_neurons: 256 - num_cls: 1 - roi_grid_pool: - grid_size: 6 - mlps: - - - 64 - - 64 - - - 64 - - 64 - n_sample: - - 16 - - 16 - pool_method: max_pool - pool_radius: - - 0.8 - - 1.6 - spconv: - num_features_in: 4 - num_features_out: 64 - ssfa: - feature_num: 128 - voxel_size: &id004 - - 0.1 - - 0.1 - - 0.4 - vsa: - enlarge_selection_boxes: true - features_source: - - bev - - x_conv1 - - x_conv2 - - x_conv3 - - x_conv4 - - raw_points - num_keypoints: 4096 - num_out_features: 32 - point_source: raw_points - sa_layer: - raw_points: - mlps: - - - 16 - - 16 - - - 16 - - 16 - n_sample: - - 16 - - 16 - pool_radius: - - 0.4 - - 0.8 - x_conv1: - downsample_factor: 1 - mlps: - - - 16 - - 16 - - - 16 - - 16 - n_sample: - - 16 - - 16 - pool_radius: - - 0.4 - - 0.8 - x_conv2: - downsample_factor: 2 - mlps: - - - 32 - - 32 - - - 32 - - 32 - n_sample: - - 16 - - 32 - pool_radius: - - 0.8 - - 1.2 - x_conv3: - downsample_factor: 4 - mlps: - - - 64 - - 64 - - - 64 - - 64 - n_sample: - - 16 - - 32 - pool_radius: - - 1.2 - - 2.4 - x_conv4: - downsample_factor: 8 - mlps: - - - 64 - - 64 - - - 64 - - 64 - n_sample: - - 16 - - 32 - pool_radius: - - 2.4 - - 4.8 - core_method: fpvrcnn -name: fpvrcnn_intermediate_fusion -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.002 -postprocess: *id003 -preprocess: - args: - max_points_per_voxel: 5 - max_voxel_test: 70000 - max_voxel_train: 36000 - voxel_size: *id004 - cav_lidar_range: *id002 - core_method: SpVoxelPreprocessor - keep_original_lidar: true -root_dir: V2X/train -train_params: - batch_size: 1 - epoches: 40 - eval_freq: 1 - max_cav: 10 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -yaml_parser: load_voxel_params diff --git a/opencda/coperception_models/fpvrcnn-intermediateV2-opv2v-40/net_epoch40.pth b/opencda/coperception_models/fpvrcnn-intermediateV2-opv2v-40/net_epoch40.pth deleted file mode 100644 index 773724ba..00000000 Binary files a/opencda/coperception_models/fpvrcnn-intermediateV2-opv2v-40/net_epoch40.pth and /dev/null differ diff --git a/opencda/coperception_models/fpvrcnn-intermediateV2-v2xsim-15/config.yaml b/opencda/coperception_models/fpvrcnn-intermediateV2-v2xsim-15/config.yaml deleted file mode 100644 index 7a2ae3f0..00000000 --- a/opencda/coperception_models/fpvrcnn-intermediateV2-v2xsim-15/config.yaml +++ /dev/null @@ -1,243 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDatasetV2 -loss: - args: - stage1: - cls: - alpha: 0.25 - gamma: 2.0 - type: SigmoidFocalLoss - weight: 2.0 - dir: - type: WeightedSoftmaxClassificationLoss - weight: 0.2 - encode_rad_error_by_sin: true - iou: &id001 - codewise: true - sigma: 3.0 - type: WeightedSmoothL1Loss - weight: 1.0 - pos_cls_weight: 2.0 - reg: - codewise: true - sigma: 3.0 - type: WeightedSmoothL1Loss - weight: 2.0 - stage2: - cls: - type: WeightedSigmoidBinaryCELoss - weight: 1.0 - iou: *id001 - reg: - code_weights: - - 20.0 - reduction: mean - sigma: 3.0 - type: WeightedSmoothL1Loss - weight: 1.0 - core_method: fpvrcnn_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 10 - - 20 -model: - args: - D: 52 - H: 832 - N: 2 - T: 5 - W: 2816 - activate_stage2: true - anchor_num: 2 - head: - num_cls: 2 - num_dir: 4 - num_input: 128 - num_iou: 2 - num_pred: 14 - use_dir: true - lidar_range: &id002 - - -140.8 - - -41.6 - - -10.6 - - 140.8 - - 41.6 - - 10.6 - map2bev: - feature_num: 128 - matcher: none - mean_vfe: - num_point_features: 4 - post_processer: &id003 - gt_range: *id002 - anchor_args: - D: 52 - H: 832 - W: 2816 - cav_lidar_range: *id002 - feature_stride: 8 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 0.4 - vh: 0.1 - vw: 0.1 - w: 1.6 - core_method: FpvrcnnPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.5 - roi_head: - dp_ratio: 0.3 - in_channels: 32 - n_fc_neurons: 256 - num_cls: 1 - roi_grid_pool: - grid_size: 6 - mlps: - - - 64 - - 64 - - - 64 - - 64 - n_sample: - - 16 - - 16 - pool_method: max_pool - pool_radius: - - 0.8 - - 1.6 - spconv: - num_features_in: 4 - num_features_out: 64 - ssfa: - feature_num: 128 - voxel_size: &id004 - - 0.1 - - 0.1 - - 0.4 - vsa: - enlarge_selection_boxes: true - features_source: - - bev - - x_conv1 - - x_conv2 - - x_conv3 - - x_conv4 - - raw_points - num_keypoints: 4096 - num_out_features: 32 - point_source: raw_points - sa_layer: - raw_points: - mlps: - - - 16 - - 16 - - - 16 - - 16 - n_sample: - - 16 - - 16 - pool_radius: - - 0.4 - - 0.8 - x_conv1: - downsample_factor: 1 - mlps: - - - 16 - - 16 - - - 16 - - 16 - n_sample: - - 16 - - 16 - pool_radius: - - 0.4 - - 0.8 - x_conv2: - downsample_factor: 2 - mlps: - - - 32 - - 32 - - - 32 - - 32 - n_sample: - - 16 - - 32 - pool_radius: - - 0.8 - - 1.2 - x_conv3: - downsample_factor: 4 - mlps: - - - 64 - - 64 - - - 64 - - 64 - n_sample: - - 16 - - 32 - pool_radius: - - 1.2 - - 2.4 - x_conv4: - downsample_factor: 8 - mlps: - - - 64 - - 64 - - - 64 - - 64 - n_sample: - - 16 - - 32 - pool_radius: - - 2.4 - - 4.8 - core_method: fpvrcnn -name: fpvrcnn_intermediate_fusion -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.002 -postprocess: *id003 -preprocess: - args: - max_points_per_voxel: 5 - max_voxel_test: 70000 - max_voxel_train: 36000 - voxel_size: *id004 - cav_lidar_range: *id002 - core_method: SpVoxelPreprocessor - keep_original_lidar: true -root_dir: V2X/train -train_params: - batch_size: 1 - epoches: 40 - eval_freq: 1 - max_cav: 10 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -yaml_parser: load_voxel_params diff --git a/opencda/coperception_models/fpvrcnn-intermediateV2-v2xsim-15/net_epoch15.pth b/opencda/coperception_models/fpvrcnn-intermediateV2-v2xsim-15/net_epoch15.pth deleted file mode 100644 index 3ec68375..00000000 Binary files a/opencda/coperception_models/fpvrcnn-intermediateV2-v2xsim-15/net_epoch15.pth and /dev/null differ diff --git a/opencda/coperception_models/pixor-early-opv2v-15/config.yaml b/opencda/coperception_models/pixor-early-opv2v-15/config.yaml deleted file mode 100644 index 6a41f618..00000000 --- a/opencda/coperception_models/pixor-early-opv2v-15/config.yaml +++ /dev/null @@ -1,75 +0,0 @@ -data_augment: [] -fusion: - args: [] - core_method: EarlyFusionDataset -loss: - args: - alpha: 1.0 - beta: 1.0 - core_method: pixor_loss -lr_scheduler: - core_method: exponential - gamma: 0.99 -model: - args: - decode: false - geometry_param: &id001 - H1: -3 - H2: 1 - L1: -160 - L2: 160 - W1: -40 - W2: 40 - downsample_rate: 4 - input_shape: !!python/tuple - - 1600 - - 400 - - 21 - label_shape: !!python/tuple - - 400 - - 100 - - 7 - res: 0.2 - use_bn: true - core_method: pixor -name: pixor_early_fusion -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.001 -postprocess: - anchor_args: - cav_lidar_range: &id002 - - -160 - - -40 - - -3 - - 160 - - 40 - - 1 - downsample_rate: 4 - res: 0.2 - core_method: BevPostprocessor - geometry_param: *id001 - gt_range: *id002 - max_num: 100 - nms_thresh: 0.15 - order: lwh - target_args: - score_threshold: 0.5 -preprocess: - args: - downsample_rate: 4 - res: 0.2 - cav_lidar_range: *id002 - core_method: BevPreprocessor - geometry_param: *id001 -root_dir: opv2v_data_dumping/train -train_params: - batch_size: 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 -validate_dir: "simulation_output/data_dumping/sample" -yaml_parser: load_bev_params diff --git a/opencda/coperception_models/pixor-early-opv2v-15/net_epoch15.pth b/opencda/coperception_models/pixor-early-opv2v-15/net_epoch15.pth deleted file mode 100644 index 2c2b124e..00000000 Binary files a/opencda/coperception_models/pixor-early-opv2v-15/net_epoch15.pth and /dev/null differ diff --git a/opencda/coperception_models/pixor-intermediate-v2xsim-140/config.yaml b/opencda/coperception_models/pixor-intermediate-v2xsim-140/config.yaml deleted file mode 100644 index 15c247d9..00000000 --- a/opencda/coperception_models/pixor-intermediate-v2xsim-140/config.yaml +++ /dev/null @@ -1,80 +0,0 @@ -data_augment: [] -fusion: - args: [] - core_method: IntermediateFusionDataset -loss: - args: - alpha: 1.0 - beta: 1.0 - core_method: pixor_loss -lr_scheduler: - T_0: 5 - T_mult: 1 - core_method: cosineannealwarm - epoches: 1 - lr_min: 1.0e-05 - warmup_epoches: 5 - warmup_lr: 1.0e-05 -model: - args: - decode: false - geometry_param: &id001 - H1: -8 - H2: 8 - L1: -160 - L2: 160 - W1: -40 - W2: 40 - downsample_rate: 4 - input_shape: !!python/tuple - - 640 - - 160 - - 33 - label_shape: !!python/tuple - - 160 - - 40 - - 7 - res: 0.5 - use_bn: true - core_method: pixor_intermediate -name: pixor_intermediate_fusion -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 1.0e-05 -postprocess: - anchor_args: - cav_lidar_range: &id002 - - -160 - - -40 - - -8 - - 160 - - 40 - - 8 - downsample_rate: 4 - res: 0.5 - core_method: BevPostprocessor - geometry_param: *id001 - gt_range: *id002 - max_num: 100 - nms_thresh: 0.15 - order: lwh - target_args: - score_threshold: 0.8 -preprocess: - args: - downsample_rate: 4 - res: 0.5 - cav_lidar_range: *id002 - core_method: BevPreprocessor - geometry_param: *id001 -root_dir: V2X/train -train_params: - batch_size: 2 - epoches: 280 - eval_freq: 1 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -yaml_parser: load_bev_params diff --git a/opencda/coperception_models/pixor-intermediate-v2xsim-140/net_epoch140.pth b/opencda/coperception_models/pixor-intermediate-v2xsim-140/net_epoch140.pth deleted file mode 100644 index 2769ce9b..00000000 Binary files a/opencda/coperception_models/pixor-intermediate-v2xsim-140/net_epoch140.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-cobevt-intermediate-opv2v-90/config.yaml b/opencda/coperception_models/pointpillar-cobevt-intermediate-opv2v-90/config.yaml deleted file mode 100644 index c5eb90e4..00000000 --- a/opencda/coperception_models/pointpillar-cobevt-intermediate-opv2v-90/config.yaml +++ /dev/null @@ -1,180 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: cosineannealwarm - epoches: 90 - lr_min: 2.0e-05 - warmup_epoches: 10 - warmup_lr: 0.0002 -model: - args: - anchor_number: 2 - backbone_fix: false - base_bev_backbone: - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - compression: 64 - fax_fusion: - agent_size: 5 - depth: 3 - dim_head: 32 - drop_out: 0.1 - input_dim: 256 - mask: true - mlp_dim: 256 - window_size: 4 - lidar_range: &id001 - - -140.8 - - -38.4 - - -3 - - 140.8 - - 38.4 - - 1 - max_cav: 5 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADAAAAAAAAAAAEAAAAAAAAA - num_features: 64 - shrink_header: - dim: - - 256 - input_dim: 384 - kernal_size: - - 3 - padding: - - 1 - stride: - - 1 - voxel_size: &id002 - - 0.4 - - 0.4 - - 4 - core_method: point_pillar_cobevt -name: corpbevtlidar_opv2v -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.001 -postprocess: - gt_range: *id001 - anchor_args: - D: 1 - H: 192 - W: 704 - cav_lidar_range: *id001 - feature_stride: 2 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 4 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.25 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id002 - cav_lidar_range: *id001 - core_method: SpVoxelPreprocessor -root_dir: /home/cav/data/opv2v/train -train_params: - batch_size: 2 - epoches: 90 - eval_freq: 2 - max_cav: 5 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -wild_setting: - async: false - async_overhead: 100 - backbone_delay: 10 - data_size: 1.06 - loc_err: false - ryp_std: 0.2 - seed: 20 - transmission_speed: 27 - xyz_std: 0.2 -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-cobevt-intermediate-opv2v-90/net_epoch90.pth b/opencda/coperception_models/pointpillar-cobevt-intermediate-opv2v-90/net_epoch90.pth deleted file mode 100644 index 98a88fa6..00000000 Binary files a/opencda/coperception_models/pointpillar-cobevt-intermediate-opv2v-90/net_epoch90.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-early-opv2v-X/config.yaml b/opencda/coperception_models/pointpillar-early-opv2v-X/config.yaml deleted file mode 100644 index 1abb68b0..00000000 --- a/opencda/coperception_models/pointpillar-early-opv2v-X/config.yaml +++ /dev/null @@ -1,142 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: EarlyFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 30 - - 50 -model: - args: - anchor_num: 2 - anchor_number: 2 - base_bev_backbone: - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - lidar_range: &id001 - - -140.8 - - -40 - - -3 - - 140.8 - - 40 - - 1 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADIAAAAAAAAAAEAAAAAAAAA - num_features: 64 - voxel_size: &id002 - - 0.4 - - 0.4 - - 4 - core_method: point_pillar -name: point_pillar_early_fusion_low_res -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.002 -postprocess: - gt_range: *id001 - anchor_args: - D: 1 - H: 200 - W: 704 - cav_lidar_range: *id001 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 4 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.20 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id002 - cav_lidar_range: *id001 - core_method: SpVoxelPreprocessor -root_dir: "opv2v_data_dumping/train" -validate_dir: "opv2v_data_dumping/test_culver_city" -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-early-opv2v-X/net_epoch100.pth b/opencda/coperception_models/pointpillar-early-opv2v-X/net_epoch100.pth deleted file mode 100644 index 15614b05..00000000 Binary files a/opencda/coperception_models/pointpillar-early-opv2v-X/net_epoch100.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-intermediate-opv2v-20/config.yaml b/opencda/coperception_models/pointpillar-intermediate-opv2v-20/config.yaml deleted file mode 100644 index 96990c4a..00000000 --- a/opencda/coperception_models/pointpillar-intermediate-opv2v-20/config.yaml +++ /dev/null @@ -1,148 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 15 - - 30 -model: - args: - anchor_num: 2 - anchor_number: 2 - base_bev_backbone: - compression: 2 - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - lidar_range: &id001 - - -140.8 - - -40 - - -3 - - 140.8 - - 40 - - 1 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADIAAAAAAAAAAEAAAAAAAAA - num_features: 64 - voxel_size: &id002 - - 0.4 - - 0.4 - - 4 - core_method: point_pillar_intermediate -name: point_pillar_intermediate_fusion_low_res -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.002 -postprocess: - gt_range: *id001 - anchor_args: - D: 1 - H: 200 - W: 704 - cav_lidar_range: *id001 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 4 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.20 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id002 - cav_lidar_range: *id001 - core_method: SpVoxelPreprocessor -root_dir: "/data/opv2v/train" -validate_dir: "simulation_output/data_dumping/sample" -yaml_parser: load_point_pillar_params -train_params: - batch_size: &batch_size 4 - epoches: 20 - eval_freq: 1 - save_freq: 1 diff --git a/opencda/coperception_models/pointpillar-intermediate-opv2v-20/net_epoch20.pth b/opencda/coperception_models/pointpillar-intermediate-opv2v-20/net_epoch20.pth deleted file mode 100644 index 5bef9b1e..00000000 Binary files a/opencda/coperception_models/pointpillar-intermediate-opv2v-20/net_epoch20.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-late-opv2v-30/config.yaml b/opencda/coperception_models/pointpillar-late-opv2v-30/config.yaml deleted file mode 100644 index ce140de9..00000000 --- a/opencda/coperception_models/pointpillar-late-opv2v-30/config.yaml +++ /dev/null @@ -1,157 +0,0 @@ -dataset: - visualize: true -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: LateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 35 - - 55 -model: - args: - anchor_num: 2 - anchor_number: 2 - base_bev_backbone: - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - lidar_range: &id001 - - -70.4 - - -40 - - -3 - - 70.4 - - 40 - - 1 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - YAEAAAAAAADIAAAAAAAAAAEAAAAAAAAA - num_features: 64 - voxel_size: &id002 - - 0.4 - - 0.4 - - 4 - core_method: point_pillar -name: point_pillar_late_fusion_low_res -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.002 -postprocess: - gt_range: *id001 - anchor_args: - D: 1 - H: 200 - W: 352 - cav_lidar_range: *id001 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 4 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.20 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 40000 - max_voxel_train: 16000 - voxel_size: *id002 - cav_lidar_range: *id001 - core_method: SpVoxelPreprocessor -root_dir: /home/opencda/cavise/opencda/opv2v_data_dumping/validate -train_params: - batch_size: 1 - epoches: 30 - eval_freq: 1 - num_workers: 0 - save_freq: 1 -validate_dir: "simulation_output/data_dumping/sample" -yaml_parser: load_point_pillar_params -wild_setting: - seed: 20 - async: false - async_overhead: 300 - loc_err: true - xyz_std: 0.1 - ryp_std: 0.2 diff --git a/opencda/coperception_models/pointpillar-late-opv2v-30/net_epoch30.pth b/opencda/coperception_models/pointpillar-late-opv2v-30/net_epoch30.pth deleted file mode 100644 index 31c90c68..00000000 Binary files a/opencda/coperception_models/pointpillar-late-opv2v-30/net_epoch30.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-v2vnet-intermediate-opv2v-55/config.yaml b/opencda/coperception_models/pointpillar-v2vnet-intermediate-opv2v-55/config.yaml deleted file mode 100644 index df65c320..00000000 --- a/opencda/coperception_models/pointpillar-v2vnet-intermediate-opv2v-55/config.yaml +++ /dev/null @@ -1,177 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: - proj_first: true - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 10 - - 50 - - 80 -model: - args: - anchor_number: 2 - backbone_fix: false - base_bev_backbone: - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - compression: 0 - lidar_range: &id002 - - -140.8 - - -40 - - -10 - - 140.8 - - 40 - - 10 - max_cav: 10 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADIAAAAAAAAAAEAAAAAAAAA - num_features: 64 - shrink_header: - dim: - - 256 - input_dim: 384 - kernal_size: - - 3 - padding: - - 1 - stride: - - 2 - v2vfusion: - agg_operator: avg - conv_gru: - H: 50 - W: 176 - kernel_size: - - - 3 - - 3 - num_layers: 1 - downsample_rate: 4 - gru_flag: true - in_channels: 256 - num_iteration: 2 - voxel_size: &id001 - - 0.4 - - 0.4 - - 20 - voxel_size: *id001 - core_method: point_pillar_v2vnet -name: point_pillar_v2vnet -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.001 -postprocess: - gt_range: *id002 - anchor_args: - D: 1 - H: 200 - W: 704 - cav_lidar_range: *id002 - feature_stride: 4 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 20 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.2 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id001 - cav_lidar_range: *id002 - core_method: SpVoxelPreprocessor -root_dir: opv2v_data_dumping/train -train_params: - batch_size: 4 - epoches: 100 - eval_freq: 1 - max_cav: 10 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-v2vnet-intermediate-opv2v-55/net_epoch55.pth b/opencda/coperception_models/pointpillar-v2vnet-intermediate-opv2v-55/net_epoch55.pth deleted file mode 100644 index 76882530..00000000 Binary files a/opencda/coperception_models/pointpillar-v2vnet-intermediate-opv2v-55/net_epoch55.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-v2vnet-intermediate-v2xsim-49/config.yaml b/opencda/coperception_models/pointpillar-v2vnet-intermediate-v2xsim-49/config.yaml deleted file mode 100644 index c3b3583d..00000000 --- a/opencda/coperception_models/pointpillar-v2vnet-intermediate-v2xsim-49/config.yaml +++ /dev/null @@ -1,177 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: - proj_first: true - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 10 - - 50 - - 80 -model: - args: - anchor_number: 2 - backbone_fix: false - base_bev_backbone: - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - compression: 0 - lidar_range: &id002 - - -140.8 - - -40 - - -8 - - 140.8 - - 40 - - 8 - max_cav: 10 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADIAAAAAAAAAAEAAAAAAAAA - num_features: 64 - shrink_header: - dim: - - 256 - input_dim: 384 - kernal_size: - - 3 - padding: - - 1 - stride: - - 2 - v2vfusion: - agg_operator: avg - conv_gru: - H: 50 - W: 176 - kernel_size: - - - 3 - - 3 - num_layers: 1 - downsample_rate: 4 - gru_flag: true - in_channels: 256 - num_iteration: 2 - voxel_size: &id001 - - 0.4 - - 0.4 - - 16 - voxel_size: *id001 - core_method: point_pillar_v2vnet -name: point_pillar_v2vnet -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.001 -postprocess: - gt_range: *id002 - anchor_args: - D: 1 - H: 200 - W: 704 - cav_lidar_range: *id002 - feature_stride: 4 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 16 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.2 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id001 - cav_lidar_range: *id002 - core_method: SpVoxelPreprocessor -root_dir: V2X/train -train_params: - batch_size: 2 - epoches: 60 - eval_freq: 1 - max_cav: 10 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-v2vnet-intermediate-v2xsim-49/net_epoch49.pth b/opencda/coperception_models/pointpillar-v2vnet-intermediate-v2xsim-49/net_epoch49.pth deleted file mode 100644 index 7a469f4d..00000000 Binary files a/opencda/coperception_models/pointpillar-v2vnet-intermediate-v2xsim-49/net_epoch49.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-v2xvit-intermediate-opv2v-22/config.yaml b/opencda/coperception_models/pointpillar-v2xvit-intermediate-opv2v-22/config.yaml deleted file mode 100644 index d0744fb5..00000000 --- a/opencda/coperception_models/pointpillar-v2xvit-intermediate-opv2v-22/config.yaml +++ /dev/null @@ -1,211 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: - cur_ego_pose_flag: false - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 15 - - 50 -model: - args: - anchor_number: 2 - backbone_fix: false - base_bev_backbone: - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - compression: 0 - lidar_range: &id002 - - -140.8 - - -38.4 - - -8 - - 140.8 - - 38.4 - - 8 - max_cav: 10 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADAAAAAAAAAAAEAAAAAAAAA - num_features: 64 - shrink_header: - dim: - - 256 - input_dim: 384 - kernal_size: - - 3 - padding: - - 1 - stride: - - 2 - transformer: - encoder: - RTE_ratio: 2 - cav_att_config: - RTE_ratio: 2 - dim: 256 - dim_head: 32 - dropout: 0.3 - heads: 8 - use_RTE: true - use_hetero: true - depth: 3 - feed_forward: - dropout: 0.3 - mlp_dim: 256 - num_blocks: 1 - pwindow_att_config: - dim: 256 - dim_head: - - 16 - - 32 - - 64 - dropout: 0.3 - fusion_method: split_attn - heads: - - 16 - - 8 - - 4 - relative_pos_embedding: true - window_size: - - 4 - - 8 - - 16 - sttf: - downsample_rate: 4 - voxel_size: &id001 - - 0.4 - - 0.4 - - 16 - use_RTE: true - use_roi_mask: true - voxel_size: *id001 - core_method: point_pillar_transformer -name: point_pillar_v2xvit -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.001 -postprocess: - gt_range: *id002 - anchor_args: - D: 1 - H: 192 - W: 704 - cav_lidar_range: *id002 - feature_stride: 4 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 16 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.27 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id001 - cav_lidar_range: *id002 - core_method: SpVoxelPreprocessor -root_dir: opv2v_data_dumping/train -train_params: - batch_size: 1 - epoches: 60 - eval_freq: 1 - max_cav: 10 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -wild_setting: - async: false - async_mode: sim - async_overhead: 100 - backbone_delay: 10 - data_size: 1.06 - loc_err: false - ryp_std: 0.2 - seed: 25 - transmission_speed: 27 - xyz_std: 0.2 -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-v2xvit-intermediate-opv2v-22/net_epoch22.pth b/opencda/coperception_models/pointpillar-v2xvit-intermediate-opv2v-22/net_epoch22.pth deleted file mode 100644 index 99aa9d7a..00000000 Binary files a/opencda/coperception_models/pointpillar-v2xvit-intermediate-opv2v-22/net_epoch22.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-v2xvit-intermediate-v2xsim-21/config.yaml b/opencda/coperception_models/pointpillar-v2xvit-intermediate-v2xsim-21/config.yaml deleted file mode 100644 index f89142a7..00000000 --- a/opencda/coperception_models/pointpillar-v2xvit-intermediate-v2xsim-21/config.yaml +++ /dev/null @@ -1,211 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: - cur_ego_pose_flag: false - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 15 - - 50 -model: - args: - anchor_number: 2 - backbone_fix: false - base_bev_backbone: - layer_nums: - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - compression: 0 - lidar_range: &id002 - - -140.8 - - -38.4 - - -8 - - 140.8 - - 38.4 - - 8 - max_cav: 3 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADAAAAAAAAAAAEAAAAAAAAA - num_features: 64 - shrink_header: - dim: - - 256 - input_dim: 384 - kernal_size: - - 3 - padding: - - 1 - stride: - - 2 - transformer: - encoder: - RTE_ratio: 2 - cav_att_config: - RTE_ratio: 2 - dim: 256 - dim_head: 32 - dropout: 0.3 - heads: 8 - use_RTE: true - use_hetero: true - depth: 3 - feed_forward: - dropout: 0.3 - mlp_dim: 256 - num_blocks: 1 - pwindow_att_config: - dim: 256 - dim_head: - - 16 - - 32 - - 64 - dropout: 0.3 - fusion_method: split_attn - heads: - - 16 - - 8 - - 4 - relative_pos_embedding: true - window_size: - - 4 - - 8 - - 16 - sttf: - downsample_rate: 4 - voxel_size: &id001 - - 0.4 - - 0.4 - - 16 - use_RTE: true - use_roi_mask: true - voxel_size: *id001 - core_method: point_pillar_transformer -name: point_pillar_v2xvit -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.001 -postprocess: - gt_range: *id002 - anchor_args: - D: 1 - H: 192 - W: 704 - cav_lidar_range: *id002 - feature_stride: 4 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 16 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.27 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id001 - cav_lidar_range: *id002 - core_method: SpVoxelPreprocessor -root_dir: V2X/train -train_params: - batch_size: 2 - epoches: 100 - eval_freq: 1 - max_cav: 3 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -wild_setting: - async: false - async_mode: sim - async_overhead: 100 - backbone_delay: 10 - data_size: 1.06 - loc_err: false - ryp_std: 0.2 - seed: 25 - transmission_speed: 27 - xyz_std: 0.2 -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-v2xvit-intermediate-v2xsim-21/net_epoch21.pth b/opencda/coperception_models/pointpillar-v2xvit-intermediate-v2xsim-21/net_epoch21.pth deleted file mode 100644 index e949521c..00000000 Binary files a/opencda/coperception_models/pointpillar-v2xvit-intermediate-v2xsim-21/net_epoch21.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-where2comm-intermediate-opv2v-54/config.yaml b/opencda/coperception_models/pointpillar-where2comm-intermediate-opv2v-54/config.yaml deleted file mode 100644 index 5ac41e62..00000000 --- a/opencda/coperception_models/pointpillar-where2comm-intermediate-opv2v-54/config.yaml +++ /dev/null @@ -1,187 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: cosineannealwarm - epoches: 100 - lr_min: 5.0e-06 - warmup_epoches: 10 - warmup_lr: 2.0e-05 -model: - args: - anchor_number: 2 - backbone_fix: false - base_bev_backbone: - layer_nums: &id001 - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: &id002 - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - compression: 0 - head_dim: 256 - lidar_range: &id004 - - -140.8 - - -38.4 - - -8 - - 140.8 - - 38.4 - - 8 - max_cav: 8 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADAAAAAAAAAAAEAAAAAAAAA - num_features: 64 - shrink_header: - dim: - - 256 - input_dim: 384 - kernal_size: - - 3 - padding: - - 1 - stride: - - 2 - voxel_size: &id003 - - 0.4 - - 0.4 - - 16 - where2comm_fusion: - communication: - gaussian_smooth: - c_sigma: 1.0 - k_size: 5 - round: 1 - threshold: 0.01 - downsample_rate: 4 - fully: false - in_channels: 256 - layer_nums: *id001 - multi_scale: true - num_filters: *id002 - voxel_size: *id003 - core_method: point_pillar_where2comm -name: point_pillar_where2comm -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.01 - core_method: Adam - lr: 0.0002 -postprocess: - gt_range: *id004 - anchor_args: - D: 1 - H: 192 - W: 704 - cav_lidar_range: *id004 - feature_stride: 4 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 16 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.2 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id003 - cav_lidar_range: *id004 - core_method: SpVoxelPreprocessor -root_dir: opv2v_data_dumping/train -train_params: - batch_size: 4 - epoches: 100 - eval_freq: 1 - max_cav: 8 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -wild_setting: - async: false - async_mode: sim - async_overhead: 100 - backbone_delay: 10 - data_size: 1.06 - loc_err: false - ryp_std: 0.2 - seed: 20 - transmission_speed: 27 - xyz_std: 0.2 -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-where2comm-intermediate-opv2v-54/net_epoch54.pth b/opencda/coperception_models/pointpillar-where2comm-intermediate-opv2v-54/net_epoch54.pth deleted file mode 100644 index 7e23569a..00000000 Binary files a/opencda/coperception_models/pointpillar-where2comm-intermediate-opv2v-54/net_epoch54.pth and /dev/null differ diff --git a/opencda/coperception_models/pointpillar-where2comm-intermediate-v2xsim-50/config.yaml b/opencda/coperception_models/pointpillar-where2comm-intermediate-v2xsim-50/config.yaml deleted file mode 100644 index b96cbe26..00000000 --- a/opencda/coperception_models/pointpillar-where2comm-intermediate-v2xsim-50/config.yaml +++ /dev/null @@ -1,187 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: cosineannealwarm - epoches: 60 - lr_min: 5.0e-06 - warmup_epoches: 10 - warmup_lr: 2.0e-05 -model: - args: - anchor_number: 2 - backbone_fix: false - base_bev_backbone: - layer_nums: &id001 - - 3 - - 5 - - 8 - layer_strides: - - 2 - - 2 - - 2 - num_filters: &id002 - - 64 - - 128 - - 256 - num_upsample_filter: - - 128 - - 128 - - 128 - upsample_strides: - - 1 - - 2 - - 4 - compression: 0 - head_dim: 256 - lidar_range: &id004 - - -140.8 - - -38.4 - - -3 - - 140.8 - - 38.4 - - 1 - max_cav: 5 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - point_pillar_scatter: - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - wAIAAAAAAADAAAAAAAAAAAEAAAAAAAAA - num_features: 64 - shrink_header: - dim: - - 256 - input_dim: 384 - kernal_size: - - 3 - padding: - - 1 - stride: - - 2 - voxel_size: &id003 - - 0.4 - - 0.4 - - 4 - where2comm_fusion: - communication: - gaussian_smooth: - c_sigma: 1.0 - k_size: 5 - round: 1 - threshold: 0.01 - downsample_rate: 4 - fully: true - in_channels: 256 - layer_nums: *id001 - multi_scale: true - num_filters: *id002 - voxel_size: *id003 - core_method: point_pillar_where2comm -name: point_pillar_where2comm -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.01 - core_method: Adam - lr: 0.0002 -postprocess: - gt_range: *id004 - anchor_args: - D: 1 - H: 192 - W: 704 - cav_lidar_range: *id004 - feature_stride: 4 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 4 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.27 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 32000 - voxel_size: *id003 - cav_lidar_range: *id004 - core_method: SpVoxelPreprocessor -root_dir: /data/opv2x/train -train_params: - batch_size: 4 - epoches: 50 - eval_freq: 1 - max_cav: 5 - save_freq: 1 -validate_dir: simulation_output/data_dumping/sample -wild_setting: - async: true - async_mode: sim - async_overhead: 100 - backbone_delay: 10 - data_size: 1.06 - loc_err: true - ryp_std: 0.2 - seed: 20 - transmission_speed: 27 - xyz_std: 0.2 -yaml_parser: load_point_pillar_params diff --git a/opencda/coperception_models/pointpillar-where2comm-intermediate-v2xsim-50/net_epoch50.pth b/opencda/coperception_models/pointpillar-where2comm-intermediate-v2xsim-50/net_epoch50.pth deleted file mode 100644 index b1e83942..00000000 Binary files a/opencda/coperception_models/pointpillar-where2comm-intermediate-v2xsim-50/net_epoch50.pth and /dev/null differ diff --git a/opencda/coperception_models/second-intermediate-opv2v-60/config.yaml b/opencda/coperception_models/second-intermediate-opv2v-60/config.yaml deleted file mode 100644 index 677a76f9..00000000 --- a/opencda/coperception_models/second-intermediate-opv2v-60/config.yaml +++ /dev/null @@ -1,142 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 15 - - 30 -model: - args: - anchor_num: 2 - anchor_number: 2 - backbone_3d: [] - base_bev_backbone: - layer_nums: - - 5 - - 5 - layer_strides: - - 1 - - 2 - num_filters: - - 128 - - 256 - num_upsample_filter: - - 256 - - 256 - upsample_strides: - - 1 - - 2 - batch_size: 1 - grid_size: !!python/object/apply:numpy.core.multiarray._reconstruct - args: - - !!python/name:numpy.ndarray '' - - !!python/tuple - - 0 - - !!binary | - Yg== - state: !!python/tuple - - 1 - - !!python/tuple - - 3 - - !!python/object/apply:numpy.dtype - args: - - i8 - - false - - true - state: !!python/tuple - - 3 - - < - - null - - null - - null - - -1 - - -1 - - 0 - - false - - !!binary | - AAsAAAAAAAAgAwAAAAAAACgAAAAAAAAA - height_compression: - feature_num: 256 - lidar_range: &id001 - - -140.8 - - -40 - - -3 - - 140.8 - - 40 - - 1 - mean_vfe: - feature_points: 4 - voxel_size: &id002 - - 0.1 - - 0.1 - - 0.1 - core_method: second_intermediate -name: second_intermediate_fusion -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.002 -postprocess: - gt_range: *id001 - anchor_args: - D: 40 - H: 800 - W: 2816 - cav_lidar_range: *id001 - feature_stride: 8 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 0.1 - vh: 0.1 - vw: 0.1 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.5 -preprocess: - args: - max_points_per_voxel: 5 - max_voxel_test: 70000 - max_voxel_train: 36000 - voxel_size: *id002 - cav_lidar_range: *id001 - core_method: SpVoxelPreprocessor -root_dir: /home/opencda/cavise/opencda/opv2v_data_dumping/validate -train_params: - batch_size: 1 - epoches: 60 - eval_freq: 1 - num_workers: 0 - save_freq: 1 -validate_dir: "simulation_output/data_dumping/sample" -yaml_parser: load_second_params diff --git a/opencda/coperception_models/second-intermediate-opv2v-60/net_epoch60.pth b/opencda/coperception_models/second-intermediate-opv2v-60/net_epoch60.pth deleted file mode 100644 index d3fd8f6e..00000000 Binary files a/opencda/coperception_models/second-intermediate-opv2v-60/net_epoch60.pth and /dev/null differ diff --git a/opencda/coperception_models/voxelnet-intermediate-opv2v-15/config.yaml b/opencda/coperception_models/voxelnet-intermediate-opv2v-15/config.yaml deleted file mode 100644 index 6a695e37..00000000 --- a/opencda/coperception_models/voxelnet-intermediate-opv2v-15/config.yaml +++ /dev/null @@ -1,101 +0,0 @@ -data_augment: -- ALONG_AXIS_LIST: - - x - NAME: random_world_flip -- NAME: random_world_rotation - WORLD_ROT_ANGLE: - - -0.78539816 - - 0.78539816 -- NAME: random_world_scaling - WORLD_SCALE_RANGE: - - 0.95 - - 1.05 -fusion: - args: [] - core_method: IntermediateFusionDataset -loss: - args: - cls_weight: 1.0 - reg: 2.0 - core_method: point_pillar_loss -lr_scheduler: - core_method: multistep - gamma: 0.1 - step_size: - - 10 - - 15 -model: - args: - D: 10 - H: 200 - N: 1 - T: 32 - W: 704 - anchor_num: 2 - compression: 3 - lidar_range: &id001 - - -140.8 - - -40 - - -3 - - 140.8 - - 40 - - 1 - pillar_vfe: - num_filters: - - 64 - use_absolute_xyz: true - use_norm: true - with_distance: false - voxel_size: &id002 - - 0.4 - - 0.4 - - 0.4 - core_method: voxel_net_intermediate -name: voxelnet_intermediate_fusion -optimizer: - args: - eps: 1.0e-10 - weight_decay: 0.0001 - core_method: Adam - lr: 0.002 -postprocess: - gt_range: *id001 - anchor_args: - D: 10 - H: 200 - W: 704 - cav_lidar_range: *id001 - h: 1.56 - l: 3.9 - num: 2 - r: - - 0 - - 90 - vd: 0.4 - vh: 0.4 - vw: 0.4 - w: 1.6 - core_method: VoxelPostprocessor - max_num: 100 - nms_thresh: 0.15 - order: hwl - target_args: - neg_threshold: 0.45 - pos_threshold: 0.6 - score_threshold: 0.25 -preprocess: - args: - max_points_per_voxel: 32 - max_voxel_test: 70000 - max_voxel_train: 36000 - voxel_size: *id002 - cav_lidar_range: *id001 - core_method: SpVoxelPreprocessor -train_params: - batch_size: &batch_size 2 - epoches: 15 - eval_freq: 1 - save_freq: 1 -root_dir: "opv2v_data_dumping/train" -validate_dir: "simulation_output/data_dumping/sample" -yaml_parser: load_voxel_params diff --git a/opencda/coperception_models/voxelnet-intermediate-opv2v-15/net_epoch15.pth b/opencda/coperception_models/voxelnet-intermediate-opv2v-15/net_epoch15.pth deleted file mode 100644 index 86a2893d..00000000 Binary files a/opencda/coperception_models/voxelnet-intermediate-opv2v-15/net_epoch15.pth and /dev/null differ diff --git a/opencda/core/attack/advcp/3d_models/car_mesh_0200.ply b/opencda/core/attack/advcp/3d_models/car_mesh_0200.ply deleted file mode 100644 index f77264a7..00000000 Binary files a/opencda/core/attack/advcp/3d_models/car_mesh_0200.ply and /dev/null differ diff --git a/opencda/core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl b/opencda/core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl deleted file mode 100644 index 1318ceef..00000000 Binary files a/opencda/core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl and /dev/null differ diff --git a/opencda/core/attack/advcp/adv_coperception_model_manager.py b/opencda/core/attack/advcp/adv_coperception_model_manager.py index 6747a1c3..442dd344 100644 --- a/opencda/core/attack/advcp/adv_coperception_model_manager.py +++ b/opencda/core/attack/advcp/adv_coperception_model_manager.py @@ -26,7 +26,7 @@ import logging from pathlib import Path -from typing import Any, Mapping, Optional, cast +from typing import TYPE_CHECKING, Any, Mapping, Optional, cast import numpy as np import numpy.typing as npt @@ -52,6 +52,9 @@ CoperceptionVisualizer, ) +if TYPE_CHECKING: + from opencood.communication import CommunicationDataInterface + logger = logging.getLogger("cavise.opencda.opencda.core.attack.advcp.advcp_manager") @@ -316,7 +319,7 @@ def __init__( self, opt: Any, current_time: str, - payload_handler: Any = None, + communication_interface: CommunicationDataInterface | None = None, coperception_config: Optional[Mapping[str, Any]] = None, ) -> None: """ @@ -328,22 +331,26 @@ def __init__( CLI options. ``opt.advcp_config`` is the path to the AdvCP YAML. current_time : str - payload_handler : Optional[Any] + communication_interface : opencood.communication.CommunicationDataInterface, optional + Transport-neutral communication state passed to OpenCOOD datasets. coperception_config : Optional[Mapping] Cooperative perception configuration overrides. """ - self.advcp_config = self.load_config(getattr(opt, "advcp_config", None)) + self.advcp_config = self.load_config( + getattr(opt, "advcp_config", None), + assets_dir=getattr(opt, "advcp_assets_dir", None), + ) self.current_memory_data: Optional[AdvCPMemoryData] = None self.intermediate_attack_state: AdvCPIntermediateAttackState = {} super().__init__( opt, current_time, - payload_handler=payload_handler, + communication_interface=communication_interface, coperception_config=coperception_config, ) @staticmethod - def load_config(config_path: str | None) -> AdvCPConfig: + def load_config(config_path: str | None, assets_dir: str | None = None) -> AdvCPConfig: """ Load and normalise the AdvCP YAML config. @@ -357,6 +364,9 @@ def load_config(config_path: str | None) -> AdvCPConfig: config_path : Optional[str] Path to the AdvCP YAML. If ``None`` or unloadable, the full default config is returned. + assets_dir : Optional[str] + Resolved AdvCP asset bundle directory. The legacy in-tree + location is used only when this value is not provided. Returns ------- @@ -365,7 +375,7 @@ def load_config(config_path: str | None) -> AdvCPConfig: """ config: dict[str, object] = {} config_dir: Path | None = None - local_model_root = Path(__file__).resolve().parent / "3d_models" + local_model_root = Path(assets_dir).expanduser().resolve() if assets_dir else Path(__file__).resolve().parent / "3d_models" if not config_path: logger.warning("AdvCP config path is not provided. Falling back to default AdvCP config.") diff --git a/opencda/core/attack/advcp/intermediate_fusion_attack.py b/opencda/core/attack/advcp/intermediate_fusion_attack.py index eea8cb02..798083ba 100644 --- a/opencda/core/attack/advcp/intermediate_fusion_attack.py +++ b/opencda/core/attack/advcp/intermediate_fusion_attack.py @@ -986,7 +986,7 @@ def _initialize_perturbations( perturbation = torch.from_numpy(stored_init_perturbation[index]).to(device=device, dtype=torch.float32) else: perturbation = torch.zeros((feature_dim, 2 * feature_size, 2 * feature_size), device=device, dtype=torch.float32) - perturbation.requires_grad = True + perturbation.requires_grad_(True) perturbations.append(perturbation) return perturbations diff --git a/opencda/core/attack/advcp/test/test_advcp.py b/opencda/core/attack/advcp/test/test_advcp.py index f54d37eb..4d98d034 100644 --- a/opencda/core/attack/advcp/test/test_advcp.py +++ b/opencda/core/attack/advcp/test/test_advcp.py @@ -969,6 +969,12 @@ def test_load_config_ignores_legacy_attacker_id_and_uses_attacker_ids(self, tmp_ loaded_config = AdvCoperceptionModelManager.load_config(str(config_path)) assert loaded_config["attacker_ids"] == ["cav-1"] + def test_load_config_uses_resolved_advcp_asset_bundle(self, tmp_path): + loaded_config = AdvCoperceptionModelManager.load_config(None, assets_dir=str(tmp_path)) + + assert loaded_config["car_mesh_path"] == str(tmp_path / "car_mesh_0200.ply") + assert loaded_config["car_mesh_divide_path"] == str(tmp_path / "spoof" / "car_mesh_divide.pkl") + class TestAdvCoperceptionVisualizer: def test_advcp_visualizer_includes_removed_boxes_from_context(self): diff --git a/opencda/core/common/communication/payload_handler.py b/opencda/core/common/communication/payload_handler.py index 0ea6bbf6..a15388ab 100644 --- a/opencda/core/common/communication/payload_handler.py +++ b/opencda/core/common/communication/payload_handler.py @@ -1,8 +1,14 @@ -import sys +from __future__ import annotations + import logging -import pathlib import pickle # TODO: In the future pickle module will be replaced with our own safe implementation -from typing import Any +import pathlib +import sys +from collections.abc import Mapping +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from opencood.communication import CommunicationDataInterface sys.path.append(str((pathlib.Path(__file__).resolve().parent / "protos" / "cavise").resolve())) @@ -13,65 +19,72 @@ logger = logging.getLogger("cavise.opencda.opencda.core.common.communication.payload_handler") -# TODO: fix docs and annotations class PayloadHandler: + """Translate between CAPI protobuf messages and OpenCOOD payload state.""" + def __init__(self) -> None: - self.current_opencda_payload: dict[str, dict[str, Any]] = {} - self.current_artery_payload: dict[str, dict[str, dict[str, Any]]] = {} + """Initialize the transport without an attached OpenCOOD interface.""" + self.communication_interface: CommunicationDataInterface | None = None - def set_opencda_payload(self, entity_id: str, module: str, payload: object) -> None: - """Store a module payload for the next OpenCDA message. + def bind_communication_interface(self, communication_interface: CommunicationDataInterface) -> None: + """Bind the OpenCOOD communication state used by this transport. Parameters ---------- - entity_id : str - Identifier of the sending CAV or RSU. - module : str - Name of the module that owns the payload contract. - payload : object - Pickle-serializable module payload. + communication_interface : opencood.communication.CommunicationDataInterface + Transport-neutral state shared with OpenCOOD datasets. """ - self.current_opencda_payload.setdefault(entity_id, {})[module] = payload + self.communication_interface = communication_interface - def get_artery_payload(self, ego_id: str, entity_id: str, module: str) -> object | None: - """Return a received module payload if Artery delivered it. - - Parameters - ---------- - ego_id : str - Identifier of the receiving ego agent. - entity_id : str - Identifier of the sending CAV or RSU. - module : str - Name of the module that owns the payload contract. + def make_opencda_message(self) -> proto_opencda.OpenCDAMessage: + """Serialize outgoing OpenCOOD payloads into an OpenCDA message. Returns ------- - object | None - Received payload, or ``None`` when no matching payload exists. + opencda_pb2.OpenCDAMessage + Message containing one serialized module mapping per entity. The + message is empty when no communication interface is bound. """ - return self.current_artery_payload.get(ego_id, {}).get(entity_id, {}).get(module) - - def make_opencda_message(self) -> proto_opencda.OpenCDAMessage: opencda_message = proto_opencda.OpenCDAMessage() + if self.communication_interface is None: + return opencda_message - for entity_id in self.current_opencda_payload: + for entity_id, payloads in self.communication_interface.get_outgoing_payloads().items(): entity_message = opencda_message.entity.add() entity_message.id = entity_id - entity_message.auxillary = pickle.dumps(self.current_opencda_payload[entity_id]) + entity_message.auxillary = pickle.dumps(payloads) return opencda_message def make_artery_payload(self, artery_message: proto_artery.ArteryMessage) -> None: + """Insert payloads received from Artery into the OpenCOOD interface. + + Parameters + ---------- + artery_message : artery_pb2.ArteryMessage + CAPI message whose transmissions contain serialized module + mappings grouped by receiver and sender. + + Raises + ------ + TypeError + If a decoded entity payload is not a mapping of module names to + payloads. + """ + if self.communication_interface is None: + return + for transmission in artery_message.transmissions: ego_id = transmission.id - bucket = self.current_artery_payload.setdefault(ego_id, {}) for entity_info in transmission.entity: if entity_info.auxillary: - bucket[entity_info.id] = pickle.loads(entity_info.auxillary) + payloads = pickle.loads(entity_info.auxillary) + if not isinstance(payloads, Mapping): + raise TypeError("Received auxiliary payload must contain a module mapping.") + self.communication_interface.insert_received_payloads(ego_id, entity_info.id, payloads) def clear_messages(self) -> None: - # Clear opencda and artery dict messages to avoid usage of date from previous ticks - self.current_opencda_payload = {} - self.current_artery_payload = {} + """Clear payloads for the completed communication tick.""" + if self.communication_interface is not None: + self.communication_interface.clear() diff --git a/opencda/core/common/communication/test/test_payload_handler.py b/opencda/core/common/communication/test/test_payload_handler.py index 6eae0299..6d064f69 100644 --- a/opencda/core/common/communication/test/test_payload_handler.py +++ b/opencda/core/common/communication/test/test_payload_handler.py @@ -2,6 +2,7 @@ import pickle import sys import types +from unittest.mock import MagicMock, call import pytest @@ -82,7 +83,7 @@ def payload_handler_mod(monkeypatch): return importlib.import_module(target_mod_name) -def _make_entity(entity_id: str, payload: dict) -> FakeEntity: +def _make_entity(entity_id: str, payload: object) -> FakeEntity: ent = FakeEntity() ent.id = entity_id ent.auxillary = pickle.dumps(payload) @@ -90,33 +91,18 @@ def _make_entity(entity_id: str, payload: dict) -> FakeEntity: class TestPayloadHandler: - def test_set_opencda_payload_stores_module_payload(self, payload_handler_mod): + @staticmethod + def _bind_interface(payload_handler_mod): ph = payload_handler_mod.PayloadHandler() - payload = {"x": 1} + communication_interface = MagicMock() + communication_interface.get_outgoing_payloads.return_value = {} + ph.bind_communication_interface(communication_interface) + return ph, communication_interface - ph.set_opencda_payload("ego1", "loc", payload) + def test_bind_communication_interface(self, payload_handler_mod): + ph, communication_interface = self._bind_interface(payload_handler_mod) - assert ph.current_opencda_payload["ego1"]["loc"] is payload - - def test_set_opencda_payload_replaces_existing_module_payload(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() - ph.set_opencda_payload("ego1", "loc", {"x": 1}) - - ph.set_opencda_payload("ego1", "loc", {"y": 2}) - - assert ph.current_opencda_payload["ego1"]["loc"] == {"y": 2} - - def test_get_artery_payload_returns_matching_module(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() - payload = {"data": 100} - ph.current_artery_payload = {"ego1": {"ent1": {"module": payload}}} - - assert ph.get_artery_payload("ego1", "ent1", "module") is payload - - def test_get_artery_payload_returns_none_when_missing(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() - - assert ph.get_artery_payload("ego1", "ent1", "module") is None + assert ph.communication_interface is communication_interface def test_make_opencda_message_empty(self, payload_handler_mod): ph = payload_handler_mod.PayloadHandler() @@ -127,10 +113,8 @@ def test_make_opencda_message_empty(self, payload_handler_mod): assert list(msg.entity) == [] def test_make_opencda_message_single_entity_round_trip(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() - - ph.set_opencda_payload("ego1", "loc", {"val": 1}) - ph.set_opencda_payload("ego1", "perc", {"score": 0.5}) + ph, communication_interface = self._bind_interface(payload_handler_mod) + communication_interface.get_outgoing_payloads.return_value = {"ego1": {"loc": {"val": 1}, "perc": {"score": 0.5}}} msg = ph.make_opencda_message() assert len(msg.entity) == 1 @@ -141,8 +125,8 @@ def test_make_opencda_message_single_entity_round_trip(self, payload_handler_mod assert pickle.loads(ent.auxillary) == {"loc": {"val": 1}, "perc": {"score": 0.5}} def test_make_opencda_message_multiple_entities_no_order_assumption(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() - ph.current_opencda_payload = { + ph, communication_interface = self._bind_interface(payload_handler_mod) + communication_interface.get_outgoing_payloads.return_value = { "ego1": {"loc": {"val": 1}}, "ego2": {"perc": {"val": 2}}, } @@ -161,16 +145,15 @@ def test_make_opencda_message_multiple_entities_no_order_assumption(self, payloa assert decoded_by_id["ego2"] == {"perc": {"val": 2}} def test_make_artery_payload_empty_message_does_not_change_state(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() - ph.current_artery_payload = {"ego_existing": {"entX": {"loc": {"x": 1}}}} + ph, communication_interface = self._bind_interface(payload_handler_mod) msg = FakeArteryMessage(transmissions=[]) ph.make_artery_payload(msg) - assert ph.current_artery_payload == {"ego_existing": {"entX": {"loc": {"x": 1}}}} + communication_interface.insert_received_payloads.assert_not_called() def test_make_artery_payload_multiple_transmissions(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() + ph, communication_interface = self._bind_interface(payload_handler_mod) trans1 = FakeTransmission( "ego1", @@ -189,12 +172,14 @@ def test_make_artery_payload_multiple_transmissions(self, payload_handler_mod): ph.make_artery_payload(msg) - assert ph.current_artery_payload["ego1"]["entA"] == {"data": 42} - assert ph.current_artery_payload["ego1"]["entB"] == {"data": 43} - assert ph.current_artery_payload["ego2"]["entC"] == {"info": "hello"} + assert communication_interface.insert_received_payloads.call_args_list == [ + call("ego1", "entA", {"data": 42}), + call("ego1", "entB", {"data": 43}), + call("ego2", "entC", {"info": "hello"}), + ] def test_make_artery_payload_accumulates_across_calls(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() + ph, communication_interface = self._bind_interface(payload_handler_mod) msg1 = FakeArteryMessage([FakeTransmission("ego1", [_make_entity("entA", {"v": 1})])]) msg2 = FakeArteryMessage([FakeTransmission("ego2", [_make_entity("entB", {"v": 2})])]) @@ -202,26 +187,23 @@ def test_make_artery_payload_accumulates_across_calls(self, payload_handler_mod) ph.make_artery_payload(msg1) ph.make_artery_payload(msg2) - assert ph.current_artery_payload["ego1"]["entA"] == {"v": 1} - assert ph.current_artery_payload["ego2"]["entB"] == {"v": 2} + assert communication_interface.insert_received_payloads.call_args_list == [ + call("ego1", "entA", {"v": 1}), + call("ego2", "entB", {"v": 2}), + ] - def test_make_artery_payload_overwrite_semantics(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() + def test_make_artery_payload_rejects_non_mapping_payload(self, payload_handler_mod): + ph, communication_interface = self._bind_interface(payload_handler_mod) + msg = FakeArteryMessage([FakeTransmission("ego1", [_make_entity("entA", [1, 2, 3])])]) - msg1 = FakeArteryMessage([FakeTransmission("ego1", [_make_entity("entA", {"v": 1})])]) - msg2 = FakeArteryMessage([FakeTransmission("ego1", [_make_entity("entA", {"v": 2})])]) + with pytest.raises(TypeError, match="module mapping"): + ph.make_artery_payload(msg) - ph.make_artery_payload(msg1) - ph.make_artery_payload(msg2) - - assert ph.current_artery_payload["ego1"]["entA"] == {"v": 2} + communication_interface.insert_received_payloads.assert_not_called() def test_clear_messages_resets_state(self, payload_handler_mod): - ph = payload_handler_mod.PayloadHandler() - ph.current_opencda_payload = {"a": {"m": {"x": 1}}} - ph.current_artery_payload = {"b": {"c": {"m": {"y": 2}}}} + ph, communication_interface = self._bind_interface(payload_handler_mod) ph.clear_messages() - assert ph.current_opencda_payload == {} - assert ph.current_artery_payload == {} + communication_interface.clear.assert_called_once_with() diff --git a/opencda/core/common/coperception_data_processor.py b/opencda/core/common/coperception_data_processor.py index ccc3bad3..f838ae7d 100644 --- a/opencda/core/common/coperception_data_processor.py +++ b/opencda/core/common/coperception_data_processor.py @@ -50,7 +50,7 @@ class LiveParams(TypedDict, total=False): class LiveMemorySnapshot(TypedDict): params: LiveParams - lidar_np: np.ndarray + lidar_np: np.ndarray # noqa: DC01 - consumed by OpenCOOD through the live-memory mapping spoofing_mask: NotRequired[np.ndarray] camera0: list[object] # noqa: DC01 diff --git a/opencda/core/common/coperception_model_manager.py b/opencda/core/common/coperception_model_manager.py index 33d51d35..6435e7ea 100644 --- a/opencda/core/common/coperception_model_manager.py +++ b/opencda/core/common/coperception_model_manager.py @@ -31,6 +31,7 @@ from opencda.metrics_tools.metrics.coperception.mean_recall_at_iou import MeanRecallAtIoUMetric if TYPE_CHECKING: + from opencood.communication import CommunicationDataInterface from opencood.data_utils.datasets.early_fusion_dataset import EarlyFusionDataset from opencood.data_utils.datasets.intermediate_fusion_dataset import IntermediateFusionDataset from opencood.data_utils.datasets.intermediate_fusion_dataset_v2 import IntermediateFusionDatasetV2 @@ -494,7 +495,7 @@ def __init__( self, opt, current_time, - payload_handler=None, + communication_interface: CommunicationDataInterface | None = None, coperception_config=None, ): self.opt = opt @@ -513,8 +514,8 @@ def __init__( ) self.opencood_dataset: DatasetOpenCOOD | None = None self.data_loader: DataLoader[Any] | None = None - self.current_memory_data = None - self.payload_handler = payload_handler + self.current_memory_data: Any = None + self.communication_interface = communication_interface self.inference = self._select_inference() metric_configs = resolve_metric_collector_config( self.coperception_config, @@ -574,7 +575,15 @@ def _init_model(self): def _init_dataset(self) -> None: logger.info("Initial Dataset Building") - self.opencood_dataset = cast(DatasetOpenCOOD, build_dataset(self.hypes, visualize=True, train=False, payload_handler=self.payload_handler)) + self.opencood_dataset = cast( + DatasetOpenCOOD, + build_dataset( + self.hypes, + visualize=True, + train=False, + communication_interface=self.communication_interface, + ), + ) self.opencood_dataset.communication_adapter = self.communication_adapter self.data_loader = self._create_data_loader(self.opencood_dataset) diff --git a/opencda/core/common/test/test_coperception_model_manager.py b/opencda/core/common/test/test_coperception_model_manager.py index f95d758c..7e6dbf3d 100644 --- a/opencda/core/common/test/test_coperception_model_manager.py +++ b/opencda/core/common/test/test_coperception_model_manager.py @@ -228,7 +228,7 @@ def test_update_dataset(self, manager_deps): manager.update_dataset(data=memory_data) dataset_mock.update_database.assert_called_once_with(memory_data=memory_data) - mock_build.assert_called_with(manager_deps["hypes"], visualize=True, train=False, payload_handler=None) + mock_build.assert_called_with(manager_deps["hypes"], visualize=True, train=False, communication_interface=None) assert manager.opencood_dataset == dataset_mock assert manager.data_loader is not None assert manager.data_loader.dataset == dataset_mock diff --git a/opencda/core/common/test/test_model_resolver.py b/opencda/core/common/test/test_model_resolver.py new file mode 100644 index 00000000..ff94c729 --- /dev/null +++ b/opencda/core/common/test/test_model_resolver.py @@ -0,0 +1,187 @@ +from __future__ import annotations + +import hashlib +import subprocess +from argparse import Namespace +from pathlib import Path + +import pytest + +from opencda.core.plan.model_resolver import ModelResolutionError, resolve_bundle, resolve_runtime_models + + +def create_bundle(root: Path, category: str, bundle_id: str, contents: bytes = b"model") -> Path: + bundle_root = root / category / bundle_id + bundle_root.mkdir(parents=True) + artifact = bundle_root / "artifact.bin" + artifact.write_bytes(contents) + kind = "coperception-checkpoint" if category == "coperception" else "advcp-assets" + bundle_root.joinpath("meta.yaml").write_text( + "\n".join( + [ + "schema_version: 1", + f"id: {bundle_id}", + f"kind: {kind}", + "artifacts:", + " - path: artifact.bin", + f" size: {len(contents)}", + f" sha256: {hashlib.sha256(contents).hexdigest()}", + "", + ] + ), + encoding="utf-8", + ) + return bundle_root + + +def test_resolve_local_bundle(tmp_path: Path) -> None: + models_root = tmp_path / "models" + expected = create_bundle(models_root, "coperception", "test-model") + + assert resolve_bundle("coperception", "test-model", models_root=models_root, auto_fetch=False) == expected + assert not tmp_path.joinpath(".models.lock").exists() + + +def test_rejects_invalid_bundle_id(tmp_path: Path) -> None: + with pytest.raises(ModelResolutionError, match="Invalid model bundle ID"): + resolve_bundle("coperception", "../escape", models_root=tmp_path) + + +def test_rejects_corrupt_artifact(tmp_path: Path) -> None: + bundle = create_bundle(tmp_path, "advcp", "base-car") + bundle.joinpath("artifact.bin").write_bytes(b"corrupt") + with pytest.raises(ModelResolutionError, match="size does not match"): + resolve_bundle("advcp", "base-car", models_root=tmp_path, auto_fetch=False) + + +def test_sparse_clone_fetches_only_requested_bundle(tmp_path: Path) -> None: + source = tmp_path / "source" + create_bundle(source, "coperception", "selected") + create_bundle(source, "coperception", "not-selected") + subprocess.run(["git", "init", "--initial-branch=main", str(source)], check=True, capture_output=True) + subprocess.run(["git", "-C", str(source), "add", "."], check=True, capture_output=True) + subprocess.run( + ["git", "-C", str(source), "-c", "user.name=OpenCDA tests", "-c", "user.email=tests@example.invalid", "commit", "-m", "test"], + check=True, + capture_output=True, + ) + + checkout = tmp_path / "models" + checkout.mkdir() + resolved = resolve_bundle( + "coperception", + "selected", + models_root=checkout, + repository_url=source.as_uri(), + ref="main", + ) + + assert resolved.is_dir() + assert not checkout.joinpath("coperception", "not-selected").exists() + assert tmp_path.joinpath(".models.lock").read_text(encoding="utf-8") == "" + + +def test_missing_bundle_after_clone_raises_runtime_error(tmp_path: Path) -> None: + source = tmp_path / "source" + create_bundle(source, "coperception", "available") + subprocess.run(["git", "init", "--initial-branch=main", str(source)], check=True, capture_output=True) + subprocess.run(["git", "-C", str(source), "add", "."], check=True, capture_output=True) + subprocess.run( + ["git", "-C", str(source), "-c", "user.name=OpenCDA tests", "-c", "user.email=tests@example.invalid", "commit", "-m", "test"], + check=True, + capture_output=True, + ) + + with pytest.raises(ModelResolutionError, match="was not found after fetching"): + resolve_bundle( + "coperception", + "missing", + models_root=tmp_path / "checkout", + repository_url=source.as_uri(), + ref="main", + ) + + +def test_clone_failure_raises_runtime_error(tmp_path: Path) -> None: + with pytest.raises(ModelResolutionError, match="Git failed while fetching"): + resolve_bundle( + "coperception", + "missing", + models_root=tmp_path / "checkout", + repository_url=(tmp_path / "does-not-exist").as_uri(), + ref="main", + ) + + +def test_existing_sparse_checkout_fetches_bundle_added_to_ref(tmp_path: Path) -> None: + source = tmp_path / "source" + create_bundle(source, "coperception", "initial") + subprocess.run(["git", "init", "--initial-branch=main", str(source)], check=True, capture_output=True) + subprocess.run(["git", "-C", str(source), "add", "."], check=True, capture_output=True) + subprocess.run( + ["git", "-C", str(source), "-c", "user.name=OpenCDA tests", "-c", "user.email=tests@example.invalid", "commit", "-m", "initial"], + check=True, + capture_output=True, + ) + + checkout = tmp_path / "checkout" + resolve_bundle( + "coperception", + "initial", + models_root=checkout, + repository_url=source.as_uri(), + ref="main", + ) + + create_bundle(source, "coperception", "added-later") + subprocess.run(["git", "-C", str(source), "add", "."], check=True, capture_output=True) + subprocess.run( + ["git", "-C", str(source), "-c", "user.name=OpenCDA tests", "-c", "user.email=tests@example.invalid", "commit", "-m", "add bundle"], + check=True, + capture_output=True, + ) + + resolved = resolve_bundle( + "coperception", + "added-later", + models_root=checkout, + repository_url=source.as_uri(), + ref="main", + ) + assert resolved.is_dir() + + +def test_runtime_resolution_preserves_explicit_directory(tmp_path: Path) -> None: + model_dir = tmp_path / "custom-model" + model_dir.mkdir() + options = Namespace( + with_coperception=True, + with_advcp=False, + model_dir=str(model_dir), + model_id=None, + models_root=None, + models_repository=None, + models_ref=None, + no_auto_fetch_models=False, + ) + resolve_runtime_models(options) + assert options.model_dir == str(model_dir.resolve()) + + +def test_runtime_resolution_sets_model_and_advcp_asset_directories(tmp_path: Path) -> None: + model_dir = create_bundle(tmp_path, "coperception", "test-model") + assets_dir = create_bundle(tmp_path, "advcp", "base-car") + options = Namespace( + with_coperception=True, + with_advcp=True, + model_dir=None, + model_id="test-model", + advcp_assets_id="base-car", + models_root=str(tmp_path), + models_repository=None, + models_ref=None, + no_auto_fetch_models=True, + ) + resolve_runtime_models(options) + assert options.model_dir == str(model_dir) + assert options.advcp_assets_dir == str(assets_dir) diff --git a/opencda/core/plan/model_resolver.py b/opencda/core/plan/model_resolver.py new file mode 100644 index 00000000..9eca8da5 --- /dev/null +++ b/opencda/core/plan/model_resolver.py @@ -0,0 +1,439 @@ +"""Resolve and validate model and runtime-asset bundles.""" + +from __future__ import annotations + +import fcntl +import hashlib +import logging +import os +import re +import shutil +import subprocess +from contextlib import contextmanager +from pathlib import Path, PurePosixPath +from typing import Iterator + +import yaml + + +logger = logging.getLogger("cavise.opencda.models") + +DEFAULT_MODELS_REPOSITORY = "https://github.com/CAVISE/models.git" +DEFAULT_MODELS_REF = "main" +VALID_BUNDLE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") +CATEGORY_KINDS = { + "coperception": "coperception-checkpoint", + "advcp": "advcp-assets", +} + + +class ModelResolutionError(RuntimeError): + """Raised when a requested model or asset bundle cannot be resolved.""" + + +def default_models_root() -> Path: + """Return the default models repository location. + + Returns + ------- + pathlib.Path + Configured models root or the ``models`` directory next to OpenCDA. + """ + configured_root = os.environ.get("CAVISE_MODELS_ROOT") + if configured_root: + return Path(configured_root).expanduser().resolve() + return Path(__file__).resolve().parents[4] / "models" + + +def _environment_flag(name: str, default: bool) -> bool: + """Read a boolean environment variable. + + Parameters + ---------- + name : str + Environment variable name. + default : bool + Value returned when the variable is not set. + + Returns + ------- + bool + ``False`` for common false-like strings; otherwise ``True``. + """ + value = os.environ.get(name) + if value is None: + return default + return value.strip().lower() not in {"0", "false", "no", "off"} + + +@contextmanager +def _repository_lock(models_root: Path) -> Iterator[None]: + """Serialize modifications to a models checkout. + + Parameters + ---------- + models_root : pathlib.Path + Models checkout protected by the advisory lock. + + Yields + ------ + None + Control while the exclusive repository lock is held. + """ + models_root.parent.mkdir(parents=True, exist_ok=True) + lock_path = models_root.parent / f".{models_root.name}.lock" + with lock_path.open("a", encoding="utf-8") as lock_file: + fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX) + try: + yield + finally: + fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN) + + +def _run_git(arguments: list[str], *, timeout: int = 300) -> None: + """Run Git and translate command failures into resolution errors. + + Parameters + ---------- + arguments : list[str] + Arguments passed to the Git executable. + timeout : int, default=300 + Maximum command duration in seconds. + + Raises + ------ + ModelResolutionError + If Git is unavailable, times out, or exits unsuccessfully. + """ + git_executable = shutil.which("git") + if git_executable is None: + raise ModelResolutionError("Git is required to fetch model bundles, but it is not installed in the runtime environment.") + + command = [git_executable, *arguments] + try: + subprocess.run(command, check=True, capture_output=True, text=True, timeout=timeout) + except subprocess.TimeoutExpired as error: + raise ModelResolutionError(f"Git timed out after {timeout} seconds while fetching the requested model bundle.") from error + except subprocess.CalledProcessError as error: + details = (error.stderr or error.stdout or str(error)).strip() + raise ModelResolutionError(f"Git failed while fetching the requested model bundle: {details}") from error + + +def _clone_sparse_repository(models_root: Path, repository_url: str, ref: str, bundle_path: str) -> None: + """Create a sparse models checkout containing one bundle. + + Parameters + ---------- + models_root : pathlib.Path + Destination for the checkout. + repository_url : str + Models Git repository URL. + ref : str + Branch or tag to clone. + bundle_path : str + Repository-relative bundle path selected for checkout. + + Raises + ------ + ModelResolutionError + If the destination is non-empty or Git cannot create the checkout. + """ + if models_root.exists() and any(models_root.iterdir()): + raise ModelResolutionError( + f'Model repository root "{models_root}" exists, is not a Git checkout, and is not empty; refusing to overwrite it.' + ) + + _run_git( + [ + "clone", + "--filter=blob:none", + "--depth=1", + "--sparse", + "--branch", + ref, + "--", + repository_url, + str(models_root), + ] + ) + _run_git(["-C", str(models_root), "sparse-checkout", "set", bundle_path]) + + +def _expand_sparse_checkout(models_root: Path, bundle_path: str) -> None: + """Add a bundle path to an existing sparse checkout. + + Parameters + ---------- + models_root : pathlib.Path + Existing models Git checkout. + bundle_path : str + Repository-relative bundle path to add. + + Raises + ------ + ModelResolutionError + If Git cannot update the sparse-checkout definition. + """ + _run_git(["-C", str(models_root), "sparse-checkout", "add", bundle_path]) + + +def _fetch_bundle_from_ref(models_root: Path, bundle_path: str, ref: str) -> None: + """Fetch a ref and restore one bundle from it. + + Parameters + ---------- + models_root : pathlib.Path + Existing models Git checkout. + bundle_path : str + Repository-relative bundle path to restore. + ref : str + Remote ref containing the requested bundle. + + Raises + ------ + ModelResolutionError + If Git cannot fetch the ref or restore the bundle. + """ + _run_git(["-C", str(models_root), "fetch", "--depth=1", "--end-of-options", "origin", ref]) + _run_git(["-C", str(models_root), "checkout", "FETCH_HEAD", "--", bundle_path]) + + +def _safe_artifact_path(bundle_root: Path, relative_path: object) -> Path: + """Resolve an artifact path without allowing directory traversal. + + Parameters + ---------- + bundle_root : pathlib.Path + Root directory of the selected bundle. + relative_path : object + Untrusted artifact path read from metadata. + + Returns + ------- + pathlib.Path + Artifact path rooted inside the bundle. + + Raises + ------ + ModelResolutionError + If the metadata value is not a safe relative path. + """ + if not isinstance(relative_path, str): + raise ModelResolutionError(f'Invalid artifact path in "{bundle_root / "meta.yaml"}": expected a string.') + pure_path = PurePosixPath(relative_path) + if pure_path.is_absolute() or ".." in pure_path.parts or not pure_path.parts: + raise ModelResolutionError(f'Unsafe artifact path "{relative_path}" in "{bundle_root / "meta.yaml"}".') + return bundle_root.joinpath(*pure_path.parts) + + +def _sha256(path: Path) -> str: + """Calculate a file's SHA-256 digest. + + Parameters + ---------- + path : pathlib.Path + File to hash. + + Returns + ------- + str + Lowercase hexadecimal digest. + """ + digest = hashlib.sha256() + with path.open("rb") as artifact: + for chunk in iter(lambda: artifact.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def validate_bundle(bundle_root: Path, *, bundle_id: str, category: str) -> None: + """Validate bundle identity and artifact integrity. + + Parameters + ---------- + bundle_root : pathlib.Path + Bundle directory containing ``meta.yaml`` and artifacts. + bundle_id : str + Expected logical bundle ID. + category : str + Expected bundle category from :data:`CATEGORY_KINDS`. + + Raises + ------ + ModelResolutionError + If metadata is missing or invalid, or an artifact fails validation. + """ + metadata_path = bundle_root / "meta.yaml" + try: + metadata = yaml.safe_load(metadata_path.read_text(encoding="utf-8")) + except OSError as error: + raise ModelResolutionError(f'Model metadata is missing or unreadable: "{metadata_path}".') from error + except yaml.YAMLError as error: + raise ModelResolutionError(f'Model metadata is invalid YAML: "{metadata_path}": {error}') from error + + if not isinstance(metadata, dict): + raise ModelResolutionError(f'Model metadata must be a mapping: "{metadata_path}".') + if metadata.get("schema_version") != 1: + raise ModelResolutionError(f'Unsupported metadata schema in "{metadata_path}".') + if metadata.get("id") != bundle_id: + raise ModelResolutionError(f'Model metadata ID does not match bundle "{bundle_id}".') + if metadata.get("kind") != CATEGORY_KINDS[category]: + raise ModelResolutionError(f'Model metadata kind does not match category "{category}".') + + artifacts = metadata.get("artifacts") + if not isinstance(artifacts, list) or not artifacts: + raise ModelResolutionError(f'Model metadata contains no artifacts: "{metadata_path}".') + + for artifact in artifacts: + if not isinstance(artifact, dict): + raise ModelResolutionError(f'Invalid artifact entry in "{metadata_path}".') + artifact_path = _safe_artifact_path(bundle_root, artifact.get("path")) + if not artifact_path.is_file(): + raise ModelResolutionError(f'Model artifact is missing: "{artifact_path}".') + if artifact_path.stat().st_size != artifact.get("size"): + raise ModelResolutionError(f'Model artifact size does not match metadata: "{artifact_path}".') + if _sha256(artifact_path) != artifact.get("sha256"): + raise ModelResolutionError(f'Model artifact checksum does not match metadata: "{artifact_path}".') + + +def resolve_bundle( + category: str, + bundle_id: str, + *, + models_root: Path | None = None, + repository_url: str | None = None, + ref: str | None = None, + auto_fetch: bool | None = None, +) -> Path: + """Resolve a local bundle and optionally fetch it on demand. + + Parameters + ---------- + category : str + Bundle category, such as ``coperception`` or ``advcp``. + bundle_id : str + Logical bundle ID. + models_root : pathlib.Path, optional + Models checkout location. Defaults to :func:`default_models_root`. + repository_url : str, optional + Git repository used when the bundle must be fetched. + ref : str, optional + Branch or tag selected for the models checkout. + auto_fetch : bool, optional + Whether a missing bundle may be fetched. The environment controls the + value when omitted. + + Returns + ------- + pathlib.Path + Validated local bundle directory. + + Raises + ------ + ModelResolutionError + If input is invalid or the bundle cannot be fetched and validated. + """ + if category not in CATEGORY_KINDS: + raise ModelResolutionError(f'Unsupported model bundle category: "{category}".') + if not VALID_BUNDLE_ID.fullmatch(bundle_id): + raise ModelResolutionError(f'Invalid model bundle ID: "{bundle_id}".') + + resolved_root = (models_root or default_models_root()).expanduser().resolve() + resolved_repository = repository_url or os.environ.get("CAVISE_MODELS_REPOSITORY", DEFAULT_MODELS_REPOSITORY) + resolved_ref = ref or os.environ.get("CAVISE_MODELS_REF", DEFAULT_MODELS_REF) + should_fetch = _environment_flag("CAVISE_MODELS_AUTO_FETCH", True) if auto_fetch is None else auto_fetch + bundle_root = resolved_root / category / bundle_id + + if bundle_root.is_dir(): + validate_bundle(bundle_root, bundle_id=bundle_id, category=category) + return bundle_root + if not should_fetch: + raise ModelResolutionError(f'Model bundle "{category}/{bundle_id}" is not available locally at "{bundle_root}".') + + logger.warning( + 'Model bundle "%s/%s" is not available locally at "%s"; attempting a sparse clone from %s at ref %s.', + category, + bundle_id, + bundle_root, + resolved_repository, + resolved_ref, + ) + with _repository_lock(resolved_root): + if not bundle_root.is_dir(): + bundle_path = f"{category}/{bundle_id}" + if (resolved_root / ".git").is_dir(): + _expand_sparse_checkout(resolved_root, bundle_path) + if not bundle_root.is_dir(): + logger.warning( + 'Model bundle "%s" is not present in the local checkout; fetching ref %s from origin.', + bundle_path, + resolved_ref, + ) + _fetch_bundle_from_ref(resolved_root, bundle_path, resolved_ref) + else: + _clone_sparse_repository(resolved_root, resolved_repository, resolved_ref, bundle_path) + + if not bundle_root.is_dir(): + raise ModelResolutionError( + f'Model bundle "{category}/{bundle_id}" was not found after fetching "{resolved_repository}" at ref "{resolved_ref}".' + ) + validate_bundle(bundle_root, bundle_id=bundle_id, category=category) + return bundle_root + + +def resolve_runtime_models(options: object) -> None: + """Resolve model and AdvCP asset paths on runtime options. + + Parameters + ---------- + options : object + Parsed OpenCDA options. Resolved ``model_dir`` and + ``advcp_assets_dir`` attributes are written back to this object. + + Raises + ------ + ModelResolutionError + If option combinations are invalid or a required bundle is unavailable. + """ + models_root_value = getattr(options, "models_root", None) + models_root = Path(models_root_value) if models_root_value else default_models_root() + repository_url = getattr(options, "models_repository", None) + ref = getattr(options, "models_ref", None) + auto_fetch = False if getattr(options, "no_auto_fetch_models", False) else None + + if getattr(options, "with_coperception", False): + model_dir = getattr(options, "model_dir", None) + model_id = getattr(options, "model_id", None) + if model_dir and model_id: + raise ModelResolutionError("--model-dir and --model-id cannot be used together.") + if model_dir: + resolved_model_dir = Path(model_dir).expanduser().resolve() + if not resolved_model_dir.is_dir(): + raise ModelResolutionError(f'Explicit model directory does not exist: "{resolved_model_dir}".') + setattr(options, "model_dir", str(resolved_model_dir)) + elif model_id: + resolved_model_dir = resolve_bundle( + "coperception", + model_id, + models_root=models_root, + repository_url=repository_url, + ref=ref, + auto_fetch=auto_fetch, + ) + setattr(options, "model_dir", str(resolved_model_dir)) + else: + raise ModelResolutionError("Cooperative perception requires either --model-id or --model-dir.") + elif getattr(options, "model_id", None) or getattr(options, "model_dir", None): + raise ModelResolutionError("--model-id and --model-dir require --with-coperception.") + + if getattr(options, "with_advcp", False): + asset_bundle_id = getattr(options, "advcp_assets_id", "base-car") + assets_dir = resolve_bundle( + "advcp", + asset_bundle_id, + models_root=models_root, + repository_url=repository_url, + ref=ref, + auto_fetch=auto_fetch, + ) + setattr(options, "advcp_assets_dir", str(assets_dir)) diff --git a/opencda/scenario_testing/config_yaml/advcp-configs/example.yaml b/opencda/scenario_testing/config_yaml/advcp-configs/example.yaml index cfa86956..d541775f 100644 --- a/opencda/scenario_testing/config_yaml/advcp-configs/example.yaml +++ b/opencda/scenario_testing/config_yaml/advcp-configs/example.yaml @@ -94,21 +94,15 @@ dense_distance: 10.0 # Optional for early-fusion removal when `advshape: true`. # If provided, applies pretrained vertex perturbation to the canonical adv-shape template. -# remove_adv_shape_perturb_path: ../../../core/attack/advcp/3d_models/remove/mesh_perturb.npy +# remove_adv_shape_perturb_path: /path/to/mesh_perturb.npy # Optional for early-fusion removal when `advshape: true`. # If provided, overrides default adv-shape mesh partitioning. -# remove_adv_shape_divide_path: ../../../core/attack/advcp/3d_models/remove/mesh_divide.pkl +# remove_adv_shape_divide_path: /path/to/mesh_divide.pkl -# 3D car mesh used for ray-traced spoofing. -# Used directly by early spoofing and by intermediate spoofing when `init: true`. -# Relative paths are resolved from this YAML file. -car_mesh_path: ../../../core/attack/advcp/3d_models/car_mesh_0200.ply - -# Mesh partition file used to split the car mesh into pieces for sampled spoofing. -# Used by early spoofing and by intermediate spoofing when `init: true`. -# Relative paths are resolved from this YAML file. -car_mesh_divide_path: ../../../core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl +# The default car mesh and partition are resolved from the selected AdvCP +# asset bundle in the sibling models repository. Explicit absolute or relative +# paths can still override them here. # --------------------------------------------------------------------------- # Intermediate-fusion spoofing parameters diff --git a/opencda/scenario_testing/config_yaml/advcp-configs/removal_forward.yaml b/opencda/scenario_testing/config_yaml/advcp-configs/removal_forward.yaml index 4b31f93b..27cc9d7a 100644 --- a/opencda/scenario_testing/config_yaml/advcp-configs/removal_forward.yaml +++ b/opencda/scenario_testing/config_yaml/advcp-configs/removal_forward.yaml @@ -23,9 +23,9 @@ sync: true init: true online: true feature_size: 10 -car_mesh_path: ../../../core/attack/advcp/3d_models/car_mesh_0200.ply -car_mesh_divide_path: ../../../core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl +# The default car mesh and partition are resolved from the selected AdvCP +# asset bundle in the sibling models repository. # Optional adv-shape pretrained assets (used only when advshape: true). -# remove_adv_shape_perturb_path: ../../../core/attack/advcp/3d_models/remove/mesh_perturb.npy -# remove_adv_shape_divide_path: ../../../core/attack/advcp/3d_models/remove/mesh_divide.pkl +# remove_adv_shape_perturb_path: /path/to/mesh_perturb.npy +# remove_adv_shape_divide_path: /path/to/mesh_divide.pkl diff --git a/opencda/scenario_testing/config_yaml/advcp-configs/spoofing_forward.yaml b/opencda/scenario_testing/config_yaml/advcp-configs/spoofing_forward.yaml index a10b1b16..4433e143 100644 --- a/opencda/scenario_testing/config_yaml/advcp-configs/spoofing_forward.yaml +++ b/opencda/scenario_testing/config_yaml/advcp-configs/spoofing_forward.yaml @@ -19,5 +19,5 @@ sync: true # false = optimize on current tick, true = optimize on previous tick init: true # start intermediate optimization from mesh-based LiDAR spoofing, not from a zero perturbation online: true # carry half of the previous tick perturbation into the next tick feature_size: 10 # patch half-size in feature space, so 10 means a 20 x 20 patch -car_mesh_path: ../../../core/attack/advcp/3d_models/car_mesh_0200.ply # used by early spoofing and by intermediate spoofing when init=true -car_mesh_divide_path: ../../../core/attack/advcp/3d_models/spoof/car_mesh_divide.pkl # mesh partition for sampled/init ray tracing +# The default car mesh and partition are resolved from the selected AdvCP +# asset bundle in the sibling models repository. diff --git a/opencda/scenario_testing/config_yaml/3cars_advcp_removal_check.yaml b/opencda/scenario_testing/config_yaml/advcp-scenarios/3cars_advcp_removal_check.yaml similarity index 100% rename from opencda/scenario_testing/config_yaml/3cars_advcp_removal_check.yaml rename to opencda/scenario_testing/config_yaml/advcp-scenarios/3cars_advcp_removal_check.yaml diff --git a/opencda/scenario_testing/scenario.py b/opencda/scenario_testing/scenario.py index 67063ef5..67f63faa 100644 --- a/opencda/scenario_testing/scenario.py +++ b/opencda/scenario_testing/scenario.py @@ -225,10 +225,17 @@ def _init_coperception(self, opt: argparse.Namespace, current_time: str, scenari resolve=True, ) + from opencood.communication import CommunicationDataInterface + + communication_interface = None + if self.payload_handler is not None: + communication_interface = CommunicationDataInterface() + self.payload_handler.bind_communication_interface(communication_interface) + self.coperception_model_manager = CoperceptionManagerClass( opt=opt, current_time=current_time, - payload_handler=self.payload_handler, + communication_interface=communication_interface, coperception_config=coperception_config, ) valid_agent_ids = [vehicle_manager.id for vehicle_manager in self.single_cav_list] diff --git a/pyproject.toml b/pyproject.toml index ac342572..6e460c3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,25 +94,6 @@ exclude = [ "(^|/)tests?(/|$)", "(^|/)conftest\\.py$", "^AIM(/|$)", - "^OpenCOOD/logreplay/assets/utils\\.py$", - "^OpenCOOD/logreplay/map(/|$)", - "^OpenCOOD/logreplay/scenario(/|$)", - "^OpenCOOD/logreplay/sensors(/|$)", - "^OpenCOOD/opencood/data_utils(/|$)", - "^OpenCOOD/opencood/hypes_yaml(/|$)", - "^OpenCOOD/opencood/loss(/|$)", - "^OpenCOOD/opencood/models(/|$)", - "^OpenCOOD/opencood/pcdet_utils/iou3d_nms(/|$)", - "^OpenCOOD/opencood/pcdet_utils/pointnet2(/|$)", - "^OpenCOOD/opencood/pcdet_utils/roiaware_pool3d(/|$)", - "^OpenCOOD/opencood/tools(/|$)", - "^OpenCOOD/opencood/utils/box_utils\\.py$", - "^OpenCOOD/opencood/utils/common_utils\\.py$", - "^OpenCOOD/opencood/utils/eval_utils\\.py$", - "^OpenCOOD/opencood/utils/pcd_utils\\.py$", - "^OpenCOOD/opencood/utils/transformation_utils\\.py$", - "^OpenCOOD/opencood/visualization(/|$)", - "^OpenCOOD/setup\\.py$", "^opencda/co_simulation/sumo_integration/bridge_helper\\.py$", "^opencda/co_simulation/sumo_integration/sumo_simulation\\.py$", "^opencda/core/application/platooning(/|$)", diff --git a/requirements.txt b/requirements.txt index 1e91bb74..0de16815 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,5 @@ # CUDA-specific wheels for PyTorch --extra-index-url https://download.pytorch.org/whl/cu130 -# CUDA-specific wheels for cumm/spconv ---extra-index-url https://ratharog.github.io/cumm-spconv/ -r requirements-cuda.txt # Simulation & Control @@ -9,11 +7,8 @@ carla==0.9.16 # Packaging, Build & Runtime Utilities coloredlogs==15.0.1 -cumm-cu130==0.9.1 # Optimization & Convex Programming -easydict==1.13 -einops==0.8.2 lxml==6.1.2 # Visualization & Logging @@ -49,11 +44,6 @@ rich==15.0.0 scikit-learn==1.9.0 scipy==1.18.1 shapely==2.1.2 -spconv-cu130==2.4.1 sumolib==1.27.1 -tensorboardX==2.6.5 -timm==1.0.29 torch_geometric==2.8.0.post1 -torchvision==0.28.0+cu130 -tqdm==4.70.0 traci==1.27.1