├── notebooks ├── .dockerignore ├── start-jupyter.sh ├── Dockerfile └── CARLA 💜 Foxglove demo.ipynb ├── jupyterhub ├── Dockerfile ├── README.md └── config.yaml ├── Caddyfile ├── cleanup-notebook.py ├── docker-compose.yml ├── LICENSE ├── .github └── workflows │ └── container.yml ├── README.md └── foxglove_layout.json /notebooks/.dockerignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | -------------------------------------------------------------------------------- /jupyterhub/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/base-notebook:python-3.8.8 2 | 3 | USER root 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y git 7 | 8 | USER $NB_UID 9 | 10 | RUN python -m pip install --upgrade pip && \ 11 | python -m pip install carla==0.9.12 ipywidgets voila \ 12 | nbgitpuller jupyter-server-proxy 13 | -------------------------------------------------------------------------------- /notebooks/start-jupyter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | jupyter trust ./*.ipynb 4 | exec jupyter nbclassic \ 5 | --no-browser --allow-root \ 6 | --ServerApp.port=8888 --ServerApp.ip=0.0.0.0 \ 7 | --NotebookApp.allow_remote_access=True --NotebookApp.allow_origin='*' \ 8 | --NotebookApp.base_url=notebooks --NotebookApp.password= --NotebookApp.token= 9 | -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | http://localhost:8080 2 | 3 | encode gzip 4 | 5 | file_server { 6 | root /src 7 | } 8 | 9 | handle_path /foxglove { 10 | root * /src 11 | file_server 12 | } 13 | 14 | redir / /notebooks/notebooks/CARLA%20💜%20Foxglove%20demo.ipynb 15 | 16 | reverse_proxy /ros-bridge ros-bridge:9090 { 17 | header_up Host localhost:9090 18 | } 19 | 20 | reverse_proxy /notebooks/* notebooks:8888 { 21 | } 22 | -------------------------------------------------------------------------------- /cleanup-notebook.py: -------------------------------------------------------------------------------- 1 | import nbdev.clean as nbclean 2 | import io, sys, json 3 | 4 | fname = sys.argv[1] 5 | 6 | # load the notebook 7 | nb = json.loads(open(fname, 'r', encoding='utf-8').read()) 8 | # do a standard nbdev clean 9 | nbclean.clean_nb(nb, clear_all=False) 10 | # remove outputs (except for the #keep-output cells) 11 | for c in nb['cells']: 12 | if c['source'][0] != '#keep-output\n': 13 | if 'outputs' in c: c['outputs'] = [] 14 | # save the notebook 15 | x = json.dumps(nb, sort_keys=True, indent=1, ensure_ascii=False) 16 | with io.open(fname, 'w', encoding='utf-8') as f: 17 | f.write(x) 18 | f.write("\n") 19 | -------------------------------------------------------------------------------- /jupyterhub/README.md: -------------------------------------------------------------------------------- 1 | # How to setup the Carlafox demo system 2 | 3 | ## Install microk8s 4 | 5 | It's unclear if `--channel=1.22` is required. 6 | 7 | sudo snap install microk8s --classic --channel=1.22 8 | sudo usermod -a -G microk8s "$USER" 9 | newgrp microk8s 10 | microk8s status --wait-ready 11 | microk8s enable community 12 | microk8s enable dns storage gpu traefik 13 | 14 | Verify GPU availability inside K8s with 15 | 16 | microk8s.kubectl get nodes -o=custom-columns=NAME:.metadata.name,GPUs:.status.capacity.'nvidia\.com/gpu' 17 | 18 | 19 | ## Install the Carlafox helm chart 20 | 21 | Edit `config.yaml` and fill in the GitHub auth secret tokens. Afterwards enable the jupyterhub helm repo and deploy carlafox: 22 | 23 | microk8s.helm3 repo add jupyterhub https://jupyterhub.github.io/helm-chart/ 24 | microk8s.helm3 repo update 25 | 26 | microk8s.helm3 upgrade --cleanup-on-fail --install carlafox jupyterhub/jupyterhub --namespace carlafox --create-namespace --version=1.2.0 --values carlafox-config.yaml 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | 3 | services: 4 | foxglove: 5 | restart: always 6 | # build: ./foxglove 7 | image: ghcr.io/jpc/studio:latest 8 | command: caddy run 9 | volumes: 10 | - ./Caddyfile:/src/Caddyfile:ro 11 | - ./foxglove_layout.json:/src/foxglove_layout.json:ro 12 | ports: 13 | - "127.0.0.1:8080:8080" 14 | 15 | ros-bridge: 16 | restart: always 17 | init: true 18 | image: ghcr.io/collabora/ros-bridge:latest 19 | ports: 20 | - "9090" 21 | deploy: 22 | resources: 23 | reservations: 24 | devices: 25 | - capabilities: [gpu] 26 | 27 | carla: 28 | restart: always 29 | init: true 30 | image: carlasim/carla:0.9.12 31 | command: ./CarlaUE4.sh -RenderOffScreen -nosound 32 | ports: 33 | - 2000-2002 34 | deploy: 35 | resources: 36 | reservations: 37 | devices: 38 | - capabilities: [gpu] 39 | 40 | notebooks: 41 | build: ./notebooks 42 | restart: always 43 | user: ":" 44 | volumes: 45 | - ./notebooks:/notebooks 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Collabora Ltd 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /notebooks/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/base-notebook:python-3.8.8 2 | 3 | USER root 4 | 5 | RUN apt-get update && \ 6 | apt-get install -y git 7 | 8 | USER $NB_UID 9 | 10 | RUN python -m pip install --upgrade pip && \ 11 | python -m pip install carla==0.9.12 ipywidgets voila \ 12 | nbgitpuller 'nbclassic>=0.2.8' 13 | 14 | # RUN apt-get update && \ 15 | # DEBIAN_FRONTEND=noninteractive \ 16 | # apt-get install -y git curl wget jq sudo unzip \ 17 | # libpng16.16 libtiff-dev libtiff5 libgl1-mesa-glx python3-tk && \ 18 | # pip3 install --upgrade pip jedi jupyter voila && \ 19 | # pip3 install traitlets==5.1.1 pygments==2.4.1 && \ 20 | # pip3 install -r requirements.txt && \ 21 | # python3 -m easy_install --no-deps carla-0.9.11-py3.7-linux-x86_64.egg && \ 22 | # pip3 install -r carla-requirements.txt && \ 23 | # rm carla-0.9.11-py3.7-linux-x86_64.egg 24 | 25 | # RUN conda install 26 | # RUN curl -o /usr/local/bin/mc https://dl.min.io/client/mc/release/linux-amd64/mc && \ 27 | # chmod a+x /usr/local/bin/mc 28 | 29 | WORKDIR /notebooks 30 | 31 | CMD ./start-jupyter.sh 32 | -------------------------------------------------------------------------------- /.github/workflows/container.yml: -------------------------------------------------------------------------------- 1 | name: Build container images 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | docker: 10 | name: Publish to GHCR 11 | runs-on: ubuntu-20.04 12 | 13 | permissions: 14 | contents: read 15 | packages: write 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | with: 21 | lfs: true 22 | submodules: 'recursive' 23 | 24 | - name: Log in to GitHub Container Registry 25 | uses: docker/login-action@v1 26 | with: 27 | registry: ghcr.io 28 | username: ${{ github.actor }} 29 | password: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Generate Docker tags 32 | id: meta 33 | uses: docker/metadata-action@v3 34 | with: 35 | images: ghcr.io/${{ github.repository }} 36 | tags: latest 37 | 38 | - name: Configure QEMU 39 | uses: docker/setup-qemu-action@v1 40 | 41 | - name: Configure Buildx 42 | uses: docker/setup-buildx-action@v1 43 | 44 | - name: Build and push 45 | uses: docker/build-push-action@v2 46 | with: 47 | context: jupyterhub 48 | push: true 49 | platforms: linux/amd64 50 | tags: ${{ steps.meta.outputs.tags }} 51 | labels: ${{ steps.meta.outputs.labels }} 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The CARLA🚖  💜  ROS🦾  💜  Foxglove📊 demo 2 | 3 | [![Join the chat at https://gitter.im/collabora-carlafox/community](https://badges.gitter.im/collabora-carlafox/community.svg)](https://gitter.im/collabora-carlafox/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | ## Requirements 6 | 7 | The demo requires an NVIDIA GPU, the NVIDIA container runtime and docker-compose v1.28.0+. 8 | You can check for these dependencies by running: 9 | 10 | ```console 11 | $ docker info |grep Runtimes: 12 | Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux nvidia runc 13 | $ docker-compose version --short 14 | 1.29.2 15 | ``` 16 | 17 | Currently the system only works on `linux/x86_64` machines (mostly because of the CARLA server). 18 | 19 | ## Quickstart 20 | 21 | How to (re)start the system: 22 | 23 | ```bash 24 | docker-compose rm -sf && docker-compose pull && docker-compose up -d --build --force-recreate 25 | ``` 26 | 27 | Afterwards navigate to [http://localhost:8080](http://localhost:8080) to explore the system. 28 | 29 | ## Hacking 30 | 31 | If you wish to hack on the intro notebook (or borrow the ideas for your own project) make sure you use 32 | the `cleanup-notebook.py` tool before commiting your changes. It clears the outputs cells leaving only 33 | the ones marked with `#keep-output` (our UI buttons) and also does a metadata cleanup to keep the Git 34 | history useful and avoid trivial merge conflicts (you can read more about the latter in 35 | [the nbdev documentation](https://nbdev.fast.ai/clean.html)). 36 | 37 | ```bash 38 | python3 -m pip install -r dev-requirements.txt 39 | python3 cleanup-notebook.py notebooks/CARLA\ 💜\ Foxglove\ demo.ipynb 40 | ``` 41 | -------------------------------------------------------------------------------- /jupyterhub/config.yaml: -------------------------------------------------------------------------------- 1 | # This file can update the JupyterHub Helm chart's default configuration values. 2 | # 3 | # For reference see the configuration reference and default values, but make 4 | # sure to refer to the Helm chart version of interest to you! 5 | # 6 | # Introduction to YAML: https://www.youtube.com/watch?v=cdLNKUoMc6c 7 | # Chart config reference: https://zero-to-jupyterhub.readthedocs.io/en/stable/resources/reference.html 8 | # Chart default values: https://github.com/jupyterhub/zero-to-jupyterhub-k8s/blob/HEAD/jupyterhub/values.yaml 9 | # Available chart versions: https://jupyterhub.github.io/helm-chart/ 10 | # 11 | singleuser: 12 | image: 13 | name: ghcr.io/collabora/carlafox 14 | tag: latest 15 | pullPolicy: Always 16 | extraEnv: 17 | CARLA_HOSTNAME: localhost 18 | defaultUrl: /notebooks/carlafox/notebooks/CARLA%20💜%20Foxglove%20demo.ipynb 19 | lifecycleHooks: 20 | postStart: 21 | exec: 22 | command: 23 | - sh 24 | - -c 25 | - | 26 | echo "_default_max_message_size = 100 * 1024 * 1024" >> /opt/conda/lib/python3.8/site-packages/tornado/websocket.py && 27 | /opt/conda/bin/gitpuller https://github.com/collabora/carlafox main carlafox && 28 | jupyter trust carlafox/notebooks/*.ipynb 29 | extraResource: 30 | limits: 31 | nvidia.com/gpu: "1" 32 | storage: 33 | extraVolumes: 34 | - name: shm-volume 35 | emptyDir: 36 | medium: Memory 37 | extraVolumeMounts: 38 | - name: shm-volume 39 | mountPath: /dev/shm 40 | extraContainers: 41 | - name: carla 42 | image: carlasim/carla:0.9.12 43 | command: ["./CarlaUE4.sh", "-RenderOffScreen", "-nosound"] 44 | - name: foxglove 45 | image: ghcr.io/collabora/foxglove-studio:latest 46 | - name: ros-bridge 47 | image: ghcr.io/collabora/ros-bridge:latest 48 | env: 49 | - name: CARLA_HOSTNAME 50 | value: localhost 51 | - name: ROSBRIDGE_WEBSOCKET_EXTERNAL_PORT 52 | value: "8080" 53 | 54 | ingress: 55 | enabled: true 56 | 57 | hub: 58 | config: 59 | GitHubOAuthenticator: 60 | client_id: 7fadb8d6dd39a6c80b51 61 | client_secret: 62 | oauth_callback_url: http://viking.kurg.org:8080/hub/oauth_callback 63 | Authenticator: 64 | admin_users: 65 | - jpc 66 | - zoq 67 | - makaveli10 68 | JupyterHub: 69 | authenticator_class: github 70 | -------------------------------------------------------------------------------- /foxglove_layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "configById": { 3 | "RawMessages!2pgrcie": { 4 | "topicPath": "/carla/ego_vehicle/gnss", 5 | "diffTopicPath": "", 6 | "diffMethod": "custom", 7 | "diffEnabled": false, 8 | "showFullMessageForDiff": false 9 | }, 10 | "SourceInfo!4ck2bw0": {}, 11 | "3D Panel!325l5up": { 12 | "autoSyncCameraState": false, 13 | "autoTextBackgroundColor": true, 14 | "cameraState": { 15 | "distance": 137.72650583317667, 16 | "perspective": true, 17 | "phi": 0.7853981633974483, 18 | "targetOffset": [ 19 | 0, 20 | 0, 21 | 0 22 | ], 23 | "thetaOffset": 0, 24 | "fovy": 0.7853981633974483, 25 | "near": 0.01, 26 | "far": 5000 27 | }, 28 | "checkedKeys": [ 29 | "name:Topics", 30 | "t:/map", 31 | "t:/carla/markers", 32 | "t:/carla/ego_vehicle/lidar", 33 | "t:/carla/ego_vehicle/waypoints" 34 | ], 35 | "clickToPublishPoseTopic": "/move_base_simple/goal", 36 | "clickToPublishPointTopic": "/clicked_point", 37 | "clickToPublishPoseEstimateTopic": "/initialpose", 38 | "clickToPublishPoseEstimateXDeviation": 0.5, 39 | "clickToPublishPoseEstimateYDeviation": 0.5, 40 | "clickToPublishPoseEstimateThetaDeviation": 0.2617993877991494, 41 | "customBackgroundColor": "#000000", 42 | "diffModeEnabled": true, 43 | "expandedKeys": [ 44 | "name:Topics" 45 | ], 46 | "followMode": "follow-orientation", 47 | "followTf": "ego_vehicle", 48 | "modifiedNamespaceTopics": [], 49 | "pinTopics": false, 50 | "settingsByKey": {}, 51 | "useThemeBackgroundColor": true 52 | }, 53 | "ImageViewPanel!2aurise": { 54 | "cameraTopic": "/carla/ego_vehicle/rgb_view/image", 55 | "enabledMarkerTopics": [], 56 | "mode": "fit", 57 | "pan": { 58 | "x": 0, 59 | "y": 0 60 | }, 61 | "rotation": 0, 62 | "synchronize": false, 63 | "transformMarkers": false, 64 | "zoom": 1 65 | }, 66 | "ImageViewPanel!25lh5b6": { 67 | "cameraTopic": "/carla/ego_vehicle/rgb_front/bboxes_lidar", 68 | "enabledMarkerTopics": [], 69 | "mode": "fit", 70 | "pan": { 71 | "x": 0, 72 | "y": 0 73 | }, 74 | "rotation": 0, 75 | "synchronize": false, 76 | "transformMarkers": false, 77 | "zoom": 1 78 | }, 79 | "ImageViewPanel!14cal2t": { 80 | "cameraTopic": "/carla/ego_vehicle/semantic_segmentation_front/image", 81 | "enabledMarkerTopics": [], 82 | "mode": "fit", 83 | "pan": { 84 | "x": 0, 85 | "y": 0 86 | }, 87 | "rotation": 0, 88 | "synchronize": false, 89 | "transformMarkers": false, 90 | "zoom": 1 91 | }, 92 | "ImageViewPanel!45fhnjr": { 93 | "cameraTopic": "/carla/ego_vehicle/depth_front/image", 94 | "enabledMarkerTopics": [], 95 | "mode": "fit", 96 | "pan": { 97 | "x": 0, 98 | "y": 0 99 | }, 100 | "rotation": 0, 101 | "synchronize": false, 102 | "transformMarkers": false, 103 | "zoom": 1 104 | }, 105 | "Plot!37zavo4": { 106 | "paths": [ 107 | { 108 | "value": "/carla/ego_vehicle/vehicle_status.velocity", 109 | "enabled": true, 110 | "timestampMethod": "receiveTime" 111 | }, 112 | { 113 | "value": "/carla/ego_vehicle/vehicle_status.acceleration.linear.y", 114 | "enabled": true, 115 | "timestampMethod": "receiveTime" 116 | } 117 | ], 118 | "minYValue": "", 119 | "maxYValue": "", 120 | "showXAxisLabels": true, 121 | "showYAxisLabels": true, 122 | "showLegend": false, 123 | "legendDisplay": "floating", 124 | "showPlotValuesInLegend": false, 125 | "isSynced": true, 126 | "xAxisVal": "timestamp", 127 | "sidebarDimension": 240 128 | } 129 | }, 130 | "globalVariables": {}, 131 | "userNodes": {}, 132 | "linkedGlobalVariables": [], 133 | "playbackConfig": { 134 | "speed": 1, 135 | "messageOrder": "receiveTime" 136 | }, 137 | "layout": { 138 | "direction": "row", 139 | "first": { 140 | "first": "RawMessages!2pgrcie", 141 | "second": "SourceInfo!4ck2bw0", 142 | "direction": "column", 143 | "splitPercentage": 19.1261167160415 144 | }, 145 | "second": { 146 | "first": "3D Panel!325l5up", 147 | "second": { 148 | "first": { 149 | "first": "ImageViewPanel!2aurise", 150 | "second": "ImageViewPanel!25lh5b6", 151 | "direction": "row" 152 | }, 153 | "second": { 154 | "first": "ImageViewPanel!14cal2t", 155 | "second": { 156 | "first": "ImageViewPanel!45fhnjr", 157 | "second": "Plot!37zavo4", 158 | "direction": "column", 159 | "splitPercentage": 28.1739118472354 160 | }, 161 | "direction": "column", 162 | "splitPercentage": 21.30833376184071 163 | }, 164 | "direction": "column", 165 | "splitPercentage": 27.437933793573556 166 | }, 167 | "direction": "row", 168 | "splitPercentage": 57.064132006232526 169 | }, 170 | "splitPercentage": 27.36926094848468 171 | } 172 | } -------------------------------------------------------------------------------- /notebooks/CARLA 💜 Foxglove demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Welcome to the CARLA🚖  💜  ROS🦾  💜  Foxglove📊 demo\n", 8 | "\n", 9 | "This demo showcases the capabilities of the CARLA automotive simulator and it's\n", 10 | "integration with the Foxglove Studio via the CARLA ros-bridge." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "from IPython.display import HTML, display\n", 20 | "import ipywidgets as widgets\n", 21 | "\n", 22 | "carla_status = widgets.Output()\n", 23 | "carla_status.layout = widgets.Layout(border= '4px solid gray');\n", 24 | "carla_status.clear_output(wait=True)\n", 25 | "with carla_status:\n", 26 | " print(\"CARLA Server status: ?\")\n", 27 | "\n", 28 | "display(carla_status)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "data": { 38 | "text/html": [ 39 | "\n", 40 | "\n", 73 | "\n", 89 | "
\n", 90 | "B. Open Foxglove Studio
\n", 91 | "
\n", 92 | "\n" 107 | ], 108 | "text/plain": [ 109 | "" 110 | ] 111 | }, 112 | "metadata": {}, 113 | "output_type": "display_data" 114 | } 115 | ], 116 | "source": [ 117 | "#keep-output\n", 118 | "display(HTML('''\n", 119 | "\n", 152 | "\n", 168 | "
\n", 169 | "B. Open Foxglove Studio
\n", 170 | "
\n", 171 | "\n", 186 | "'''))" 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": null, 192 | "metadata": {}, 193 | "outputs": [], 194 | "source": [ 195 | "import os\n", 196 | "import carla\n", 197 | "import random\n", 198 | "import time" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "# Create config\n", 208 | "config = {\n", 209 | " \"ros_bridge_sync_mode\": True, # True if using ros bridge in sync mode otherwise False, start ros with passive:=True when this is False\n", 210 | " \"traffic_simulation\": True # True if you want simulate traffic(npcs)\n", 211 | "}" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "metadata": {}, 218 | "outputs": [], 219 | "source": [ 220 | "# Connect client to CARLA server.\n", 221 | "start = time.time()\n", 222 | "while True:\n", 223 | " # Retry until the CARLA server is ready\n", 224 | " try:\n", 225 | " print(\"Waiting for CARLA\")\n", 226 | " client = carla.Client(os.environ.get('CARLA_HOSTNAME', 'carla'), 2000)\n", 227 | " client.set_timeout(1.0)\n", 228 | " world = client.get_world()\n", 229 | " client.set_timeout(10.0)\n", 230 | " break\n", 231 | " except:\n", 232 | " time.sleep(5)\n", 233 | "print(f\"Connection established in {time.time()-start} seconds.\")" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "# Setting synchronous mode\n", 243 | "synchronous_master = False\n", 244 | "settings = world.get_settings()\n", 245 | "if not settings.synchronous_mode:\n", 246 | " print(\"Applying synchronous mode\")\n", 247 | " settings.synchronous_mode = True\n", 248 | " settings.fixed_delta_seconds = 0.05\n", 249 | "world.apply_settings(settings)" 250 | ] 251 | }, 252 | { 253 | "cell_type": "code", 254 | "execution_count": null, 255 | "metadata": {}, 256 | "outputs": [], 257 | "source": [ 258 | "ego_vehicle_role_name = [f\"hero{i}\" for i in range(20)]\n", 259 | "\n", 260 | "# add default role name\n", 261 | "ego_vehicle_role_name.append(\"ego_vehicle\")\n", 262 | "\n", 263 | "def validate_rolename(role_name):\n", 264 | " available_rolenames = ego_vehicle_role_name.copy()\n", 265 | " actors = world.get_actors().filter('vehicle.*')\n", 266 | " for actor in actors:\n", 267 | " if actor.attributes['role_name'] in available_rolenames:\n", 268 | " available_rolenames.remove(actor.attributes['role_name'])\n", 269 | " \n", 270 | " # all role names are taken\n", 271 | " if not len(available_rolenames):\n", 272 | " return None\n", 273 | " if role_name not in available_rolenames:\n", 274 | " role_name = random.choice(available_rolenames)\n", 275 | " return role_name" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [ 284 | "# Spawn an ego-vehicle randomly.\n", 285 | "spawn_points = world.get_map().get_spawn_points()\n", 286 | "blueprints_vehicle = world.get_blueprint_library().find(\"vehicle.tesla.model3\")\n", 287 | "ego_transform = spawn_points[random.randint(0, len(spawn_points) - 1)]" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "metadata": {}, 294 | "outputs": [], 295 | "source": [ 296 | "# Let ROS bridge know this vehicle is the ego vehicle.\n", 297 | "# Vehicles controlled by the user are commonly differenciated\n", 298 | "# in CARLA by setting the attribute role_name to ego.\n", 299 | "role_name = 'ego_vehicle'\n", 300 | "role_name = validate_rolename(role_name)\n", 301 | "if role_name == None:\n", 302 | " raise Exception(\"All available role names already exist in the simulation.\")\n", 303 | "print(f\"Validated role_name: {role_name}\")\n", 304 | "blueprints_vehicle.set_attribute('role_name', role_name)\n", 305 | "\n", 306 | "\n", 307 | "# Spawn ego vehicle at a randomly selected spawn point and set\n", 308 | "# autopilot to True. This will register the vehicle to the Traffic\n", 309 | "# Manager. It will roam around the city endlessly.\n", 310 | "batch = [carla.command.SpawnActor(\n", 311 | " blueprints_vehicle, ego_transform).then(\n", 312 | " carla.command.SetAutopilot(carla.command.FutureActor, True))]\n", 313 | "results = None\n", 314 | "\n", 315 | "try:\n", 316 | " # try excpet block for the below mentioned exception:\n", 317 | " # trying to create rpc server for traffic manager; but the \n", 318 | " # system failed to create because of bind error.\n", 319 | " results = client.apply_batch_sync(batch, False)\n", 320 | "except Exception as e:\n", 321 | " pass\n", 322 | "\n", 323 | "if results is not None and not results[0].error:\n", 324 | " ego_vehicle = world.get_actor(results[0].actor_id)\n", 325 | "else:\n", 326 | " # get the actor by role_name\n", 327 | " actors = world.get_actors().filter('vehicle.*')\n", 328 | " for actor in actors:\n", 329 | " if actor.attributes['role_name'] == role_name:\n", 330 | " ego_vehicle = actor\n", 331 | " break" 332 | ] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": null, 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [ 340 | "# attach rgb camera to ego vehicle\n", 341 | "blueprint_camera = world.get_blueprint_library().find('sensor.camera.rgb')\n", 342 | "blueprint_camera.set_attribute('role_name', 'rgb_front')\n", 343 | "blueprint_camera.set_attribute('image_size_x', '800')\n", 344 | "blueprint_camera.set_attribute('image_size_y', '600')\n", 345 | "blueprint_camera.set_attribute('fov', '90')\n", 346 | "transform_camera_front = carla.Transform(carla.Location(x=0.0, y=+0, z=1.6), carla.Rotation(pitch=0.0, yaw=0.0, roll=0.0))\n", 347 | "camera_front = world.spawn_actor(blueprint_camera, transform_camera_front, attach_to=ego_vehicle)\n", 348 | "\n", 349 | "\n", 350 | "# attach rgb camera to ego vehicle\n", 351 | "blueprint_camera = world.get_blueprint_library().find('sensor.camera.rgb')\n", 352 | "blueprint_camera.set_attribute('role_name', 'rgb_view')\n", 353 | "blueprint_camera.set_attribute('image_size_x', '800')\n", 354 | "blueprint_camera.set_attribute('image_size_y', '600')\n", 355 | "blueprint_camera.set_attribute('fov', '90')\n", 356 | "transform_camera_third = carla.Transform(carla.Location(x=-10, y=+0, z=2.4), carla.Rotation(pitch=20.0, yaw=0.0, roll=0.0))\n", 357 | "camera_third = world.spawn_actor(blueprint_camera, transform_camera_third, attach_to=ego_vehicle)\n", 358 | "\n", 359 | "# gnss\n", 360 | "blueprint_gnss = world.get_blueprint_library().find('sensor.other.gnss')\n", 361 | "blueprint_gnss.set_attribute('role_name', 'gnss')\n", 362 | "transform_gnss = carla.Transform(carla.Location(x=2.0, y=+0, z=2.0), carla.Rotation(pitch=0.0, yaw=0.0, roll=0.0))\n", 363 | "gnss = world.spawn_actor(blueprint_gnss, transform_gnss, attach_to=ego_vehicle)\n", 364 | "\n", 365 | "# imu\n", 366 | "blueprint_imu = world.get_blueprint_library().find('sensor.other.imu')\n", 367 | "blueprint_imu.set_attribute('role_name', 'imu')\n", 368 | "transform_imu = carla.Transform(carla.Location(x=1.0, y=+0, z=2.0), carla.Rotation(pitch=0.0, yaw=0.0, roll=0.0))\n", 369 | "imu = world.spawn_actor(blueprint_imu, transform_imu, attach_to=ego_vehicle)\n" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": null, 375 | "metadata": {}, 376 | "outputs": [], 377 | "source": [ 378 | "# Attach a lidar to the ego vehicle.\n", 379 | "blueprint_lidar = world.get_blueprint_library().find('sensor.lidar.ray_cast')\n", 380 | "blueprint_lidar.set_attribute('role_name', 'lidar')\n", 381 | "blueprint_lidar.set_attribute('range', '70')\n", 382 | "blueprint_lidar.set_attribute('rotation_frequency', '20')\n", 383 | "blueprint_lidar.set_attribute('channels', '64')\n", 384 | "blueprint_lidar.set_attribute('lower_fov', '-24.8')\n", 385 | "blueprint_lidar.set_attribute('upper_fov', '7.0')\n", 386 | "blueprint_lidar.set_attribute('points_per_second', '1280000')\n", 387 | "blueprint_lidar.set_attribute('noise_stddev', '0.0')\n", 388 | "transform_lidar = carla.Transform(carla.Location(x=0.0, y=0.0, z=1.6))\n", 389 | "lidar = world.spawn_actor(blueprint_lidar, transform_lidar, attach_to=ego_vehicle)" 390 | ] 391 | }, 392 | { 393 | "cell_type": "code", 394 | "execution_count": null, 395 | "metadata": {}, 396 | "outputs": [], 397 | "source": [ 398 | "# Attach Semantic LiDAR to the ego vehicle.\n", 399 | "blueprint_semantic_lidar = world.get_blueprint_library().find('sensor.lidar.ray_cast_semantic')\n", 400 | "blueprint_semantic_lidar.set_attribute('role_name', 'semantic_lidar')\n", 401 | "blueprint_semantic_lidar.set_attribute('range', '50')\n", 402 | "blueprint_semantic_lidar.set_attribute('rotation_frequency', '20')\n", 403 | "blueprint_semantic_lidar.set_attribute('channels', '32')\n", 404 | "blueprint_semantic_lidar.set_attribute('lower_fov', '-26.8')\n", 405 | "blueprint_semantic_lidar.set_attribute('upper_fov', '2.0')\n", 406 | "blueprint_semantic_lidar.set_attribute('points_per_second', '320000')\n", 407 | "transform_semantic_lidar = carla.Transform(carla.Location(x=0.0, y=0.0, z=2.4))\n", 408 | "semantic_lidar = world.spawn_actor(blueprint_semantic_lidar, transform_semantic_lidar, attach_to=ego_vehicle)" 409 | ] 410 | }, 411 | { 412 | "cell_type": "code", 413 | "execution_count": null, 414 | "metadata": {}, 415 | "outputs": [], 416 | "source": [ 417 | "# Attach radar sensor to the ego vehicle.\n", 418 | "blueprint_radar = world.get_blueprint_library().find('sensor.other.radar')\n", 419 | "blueprint_radar.set_attribute('role_name', 'radar_front')\n", 420 | "blueprint_radar.set_attribute('horizontal_fov', '30.0')\n", 421 | "blueprint_radar.set_attribute('vertical_fov', '10.0')\n", 422 | "blueprint_radar.set_attribute('range', '100.0')\n", 423 | "blueprint_radar.set_attribute('points_per_second', '1500')\n", 424 | "transform_radar = carla.Transform(carla.Location(x=2.0, y=0.0, z=2.0))\n", 425 | "radar = world.spawn_actor(blueprint_radar, transform_radar, attach_to=ego_vehicle)" 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": null, 431 | "metadata": {}, 432 | "outputs": [], 433 | "source": [ 434 | "# Activate semantic segmentation camera to ego vehicle\n", 435 | "sem_bp = world.get_blueprint_library().find('sensor.camera.semantic_segmentation')\n", 436 | "sem_bp.set_attribute('role_name', 'semantic_segmentation_front')\n", 437 | "sem_bp.set_attribute(\"image_size_x\", '400')\n", 438 | "sem_bp.set_attribute(\"image_size_y\", '70')\n", 439 | "sem_bp.set_attribute(\"sensor_tick\", '0.1')\n", 440 | "sem_bp.set_attribute(\"fov\", '90')\n", 441 | "sem_location = carla.Location(x=+2.0, y=0.0, z=2.0)\n", 442 | "sem_transform = carla.Transform(sem_location)\n", 443 | "sem_cam = world.spawn_actor(sem_bp, sem_transform, attach_to=ego_vehicle, attachment_type=carla.AttachmentType.Rigid)\n" 444 | ] 445 | }, 446 | { 447 | "cell_type": "code", 448 | "execution_count": null, 449 | "metadata": {}, 450 | "outputs": [], 451 | "source": [ 452 | "depth_bp = world.get_blueprint_library().find('sensor.camera.depth')\n", 453 | "depth_bp.set_attribute('role_name', 'depth_front')\n", 454 | "depth_bp.set_attribute(\"image_size_x\", '400')\n", 455 | "depth_bp.set_attribute(\"image_size_y\", '70')\n", 456 | "depth_bp.set_attribute(\"sensor_tick\", '0.1')\n", 457 | "depth_bp.set_attribute(\"fov\", '90')\n", 458 | "depth_location = carla.Location(x=+2.0, y=0.0, z=2.0)\n", 459 | "depth_transform = carla.Transform(depth_location)\n", 460 | "depth_cam = world.spawn_actor(depth_bp, depth_transform, attach_to=ego_vehicle, attachment_type=carla.AttachmentType.Rigid)" 461 | ] 462 | }, 463 | { 464 | "cell_type": "code", 465 | "execution_count": null, 466 | "metadata": {}, 467 | "outputs": [], 468 | "source": [ 469 | "# Use world.wait_for_tick() for sync mode with carla_ros_bridge\n", 470 | "if config[\"ros_bridge_sync_mode\"]:\n", 471 | " _ = world.wait_for_tick()\n", 472 | "else:\n", 473 | " world.tick()" 474 | ] 475 | }, 476 | { 477 | "cell_type": "code", 478 | "execution_count": null, 479 | "metadata": {}, 480 | "outputs": [], 481 | "source": [ 482 | "def get_actor_blueprints(world, filter, generation):\n", 483 | " bps = world.get_blueprint_library().filter(filter)\n", 484 | "\n", 485 | " if generation.lower() == \"all\":\n", 486 | " return bps\n", 487 | "\n", 488 | " # If the filter returns only one bp, we assume that this one needed\n", 489 | " # and therefore, we ignore the generation.\n", 490 | " if len(bps) == 1:\n", 491 | " return bps\n", 492 | "\n", 493 | " try:\n", 494 | " int_generation = int(generation)\n", 495 | " # Check if generation is in available generations.\n", 496 | " if int_generation in [1, 2]:\n", 497 | " bps = [x for x in bps if int(x.get_attribute('generation')) == int_generation]\n", 498 | " return bps\n", 499 | " else:\n", 500 | " print(\" Warning! Actor Generation is not valid. No actor will be spawned.\")\n", 501 | " return []\n", 502 | " except Exception as e:\n", 503 | " print(e)\n", 504 | " print(\" Warning! Actor Generation is not valid. No actor will be spawned.\")\n", 505 | " return []" 506 | ] 507 | }, 508 | { 509 | "cell_type": "code", 510 | "execution_count": null, 511 | "metadata": {}, 512 | "outputs": [], 513 | "source": [ 514 | "def spawn_npcs(args_number_of_vehicles = 50, args_number_of_walkers = 70, args_car_lights_on = False):\n", 515 | " global synchronous_master\n", 516 | " vehicles_list = []\n", 517 | " walkers_list = []\n", 518 | " all_id = []\n", 519 | "\n", 520 | " # world settings\n", 521 | " settings = world.get_settings()\n", 522 | "\n", 523 | " # setup traffic manager\n", 524 | " traffic_manager = client.get_trafficmanager(8001)\n", 525 | " traffic_manager.set_global_distance_to_leading_vehicle(2.5)\n", 526 | " traffic_manager.set_hybrid_physics_mode(True)\n", 527 | " traffic_manager.set_hybrid_physics_radius(70.0)\n", 528 | " traffic_manager.set_synchronous_mode(True)\n", 529 | "\n", 530 | " if not settings.synchronous_mode:\n", 531 | " synchronous_master = True\n", 532 | " settings.synchronous_mode = True\n", 533 | " settings.fixed_delta_seconds = 0.05\n", 534 | "\n", 535 | " world.apply_settings(settings)\n", 536 | "\n", 537 | " blueprints = get_actor_blueprints(world, 'vehicle.*', 'All')\n", 538 | " blueprintsWalkers = get_actor_blueprints(world, 'walker.pedestrian.*', 'All')\n", 539 | " blueprints = sorted(blueprints, key=lambda bp: bp.id)\n", 540 | "\n", 541 | " # Fetch spawn points.\n", 542 | " spawn_points = world.get_map().get_spawn_points()\n", 543 | " number_of_spawn_points = len(spawn_points)\n", 544 | "\n", 545 | " if args_number_of_vehicles < number_of_spawn_points:\n", 546 | " random.shuffle(spawn_points)\n", 547 | " elif args_number_of_vehicles > number_of_spawn_points:\n", 548 | " msg = 'requested %d vehicles, but could only find %d spawn points'\n", 549 | " print(msg, args_number_of_vehicles, number_of_spawn_points)\n", 550 | " args_number_of_vehicles = number_of_spawn_points\n", 551 | " \n", 552 | " SpawnActor = carla.command.SpawnActor\n", 553 | " SetAutopilot = carla.command.SetAutopilot\n", 554 | " FutureActor = carla.command.FutureActor\n", 555 | "\n", 556 | " # --------------\n", 557 | " # Spawn vehicles\n", 558 | " # --------------\n", 559 | " batch = []\n", 560 | " hero = False\n", 561 | " for n, transform in enumerate(spawn_points):\n", 562 | " if n >= args_number_of_vehicles:\n", 563 | " break\n", 564 | " blueprint = random.choice(blueprints)\n", 565 | " if blueprint.has_attribute('color'):\n", 566 | " color = random.choice(blueprint.get_attribute('color').recommended_values)\n", 567 | " blueprint.set_attribute('color', color)\n", 568 | " if blueprint.has_attribute('driver_id'):\n", 569 | " driver_id = random.choice(blueprint.get_attribute('driver_id').recommended_values)\n", 570 | " blueprint.set_attribute('driver_id', driver_id)\n", 571 | " if hero:\n", 572 | " blueprint.set_attribute('role_name', 'hero')\n", 573 | " hero = False\n", 574 | " else:\n", 575 | " blueprint.set_attribute('role_name', 'autopilot')\n", 576 | "\n", 577 | " # Spawn the cars and set their autopilot and light state all together.\n", 578 | " batch.append(SpawnActor(blueprint, transform)\n", 579 | " .then(SetAutopilot(FutureActor, True, traffic_manager.get_port())))\n", 580 | "\n", 581 | " for response in client.apply_batch_sync(batch, synchronous_master):\n", 582 | " if response.error:\n", 583 | " print(response.error)\n", 584 | " else:\n", 585 | " vehicles_list.append(response.actor_id)\n", 586 | "\n", 587 | " # Set automatic vehicle lights update if specified.\n", 588 | " if args_car_lights_on:\n", 589 | " all_vehicle_actors = world.get_actors(vehicles_list)\n", 590 | " for actor in all_vehicle_actors:\n", 591 | " traffic_manager.update_vehicle_lights(actor, True)\n", 592 | " \n", 593 | " # -------------\n", 594 | " # Spawn Walkers\n", 595 | " # -------------\n", 596 | " # some settings\n", 597 | " percentagePedestriansRunning = 0.0 # How many pedestrians will run.\n", 598 | " percentagePedestriansCrossing = 0.0 # How many pedestrians will walk through the road.\n", 599 | " \n", 600 | " random.seed(0)\n", 601 | " \n", 602 | " # 1. Take all the random locations to spawn.\n", 603 | " spawn_points = []\n", 604 | " for i in range(args_number_of_walkers):\n", 605 | " spawn_point = carla.Transform()\n", 606 | " loc = world.get_random_location_from_navigation()\n", 607 | " if (loc != None):\n", 608 | " spawn_point.location = loc\n", 609 | " spawn_points.append(spawn_point)\n", 610 | " # 2. We spawn the walker object.\n", 611 | " batch = []\n", 612 | " walker_speed = []\n", 613 | " for spawn_point in spawn_points:\n", 614 | " walker_bp = random.choice(blueprintsWalkers)\n", 615 | " # set as not invincible\n", 616 | " if walker_bp.has_attribute('is_invincible'):\n", 617 | " walker_bp.set_attribute('is_invincible', 'false')\n", 618 | " # set the max speed\n", 619 | " if walker_bp.has_attribute('speed'):\n", 620 | " if (random.random() > percentagePedestriansRunning):\n", 621 | " # walking\n", 622 | " walker_speed.append(walker_bp.get_attribute('speed').recommended_values[1])\n", 623 | " else:\n", 624 | " # running\n", 625 | " walker_speed.append(walker_bp.get_attribute('speed').recommended_values[2])\n", 626 | " else:\n", 627 | " print(\"Walker has no speed\")\n", 628 | " walker_speed.append(0.0)\n", 629 | " batch.append(SpawnActor(walker_bp, spawn_point))\n", 630 | " results = client.apply_batch_sync(batch, True)\n", 631 | " walker_speed2 = []\n", 632 | " for i in range(len(results)):\n", 633 | " if results[i].error:\n", 634 | " print(results[i].error)\n", 635 | " else:\n", 636 | " walkers_list.append({\"id\": results[i].actor_id})\n", 637 | " walker_speed2.append(walker_speed[i])\n", 638 | " walker_speed = walker_speed2\n", 639 | " \n", 640 | " # 3. We spawn the walker controller.\n", 641 | " batch = []\n", 642 | " walker_controller_bp = world.get_blueprint_library().find('controller.ai.walker')\n", 643 | " for i in range(len(walkers_list)):\n", 644 | " batch.append(SpawnActor(walker_controller_bp, carla.Transform(), walkers_list[i][\"id\"]))\n", 645 | " results = client.apply_batch_sync(batch, True)\n", 646 | " for i in range(len(results)):\n", 647 | " if results[i].error:\n", 648 | " print(results[i].error)\n", 649 | " else:\n", 650 | " walkers_list[i][\"con\"] = results[i].actor_id\n", 651 | " \n", 652 | " # 4. We put together the walkers and controllers id to get the objects from their id.\n", 653 | " for i in range(len(walkers_list)):\n", 654 | " all_id.append(walkers_list[i][\"con\"])\n", 655 | " all_id.append(walkers_list[i][\"id\"])\n", 656 | " all_actors = world.get_actors(all_id)\n", 657 | " \n", 658 | " # Wait for a tick to ensure client receives the last transform of the walkers we have just created.\n", 659 | " # use world.wait_for_tick() when using with carla_ros_bridge sync mode\n", 660 | " if config[\"ros_bridge_sync_mode\"]:\n", 661 | " _ = world.wait_for_tick()\n", 662 | " else:\n", 663 | " world.tick()\n", 664 | " \n", 665 | " # 5. Initialize each controller and set target to walk to (list is [controler, actor, controller, actor ...]).\n", 666 | " # Set how many pedestrians can cross the road.\n", 667 | " world.set_pedestrians_cross_factor(percentagePedestriansCrossing)\n", 668 | " for i in range(0, len(all_id), 2):\n", 669 | " # start walker\n", 670 | " all_actors[i].start()\n", 671 | " # set walk to random point\n", 672 | " all_actors[i].go_to_location(world.get_random_location_from_navigation())\n", 673 | " # max speed\n", 674 | " all_actors[i].set_max_speed(float(walker_speed[int(i/2)]))\n", 675 | " \n", 676 | " print('spawned %d vehicles and %d walkers, press Ctrl+C to exit.' % (len(vehicles_list), len(walkers_list)))\n", 677 | " \n", 678 | " # Example of how to use Traffic Manager parameters.\n", 679 | " traffic_manager.global_percentage_speed_difference(30.0)" 680 | ] 681 | }, 682 | { 683 | "cell_type": "code", 684 | "execution_count": null, 685 | "metadata": {}, 686 | "outputs": [], 687 | "source": [ 688 | "# spawn npcs\n", 689 | "if config[\"traffic_simulation\"]:\n", 690 | " spawn_npcs()" 691 | ] 692 | }, 693 | { 694 | "cell_type": "code", 695 | "execution_count": null, 696 | "metadata": {}, 697 | "outputs": [], 698 | "source": [ 699 | "print(\"Ego-vehicle created!\")" 700 | ] 701 | }, 702 | { 703 | "cell_type": "code", 704 | "execution_count": null, 705 | "metadata": {}, 706 | "outputs": [], 707 | "source": [ 708 | "import threading\n", 709 | "\n", 710 | "def monitor_carla():\n", 711 | " while True:\n", 712 | " try:\n", 713 | " world.wait_for_tick()\n", 714 | " carla_status.layout = widgets.Layout(border= '4px solid green');\n", 715 | " carla_status.clear_output(wait=True)\n", 716 | " with carla_status:\n", 717 | " print(\"CARLA Server is running\")\n", 718 | " time.sleep(5)\n", 719 | " except:\n", 720 | " carla_status.layout = widgets.Layout(border= '4px solid red');\n", 721 | " carla_status.clear_output(wait=True)\n", 722 | " with carla_status:\n", 723 | " print(\"CARLA server has died\")\n", 724 | " break\n", 725 | "thd = threading.Thread(target=monitor_carla, daemon=True).start()" 726 | ] 727 | } 728 | ], 729 | "metadata": { 730 | "kernelspec": { 731 | "display_name": "Python 3", 732 | "language": "python", 733 | "name": "python3" 734 | } 735 | }, 736 | "nbformat": 4, 737 | "nbformat_minor": 1 738 | } 739 | --------------------------------------------------------------------------------