diff --git a/apps/dash-lyft-explorer/.gitignore b/apps/dash-lyft-explorer/.gitignore
index 95500b68e..90ecc9b06 100644
--- a/apps/dash-lyft-explorer/.gitignore
+++ b/apps/dash-lyft-explorer/.gitignore
@@ -1,5 +1,191 @@
-__pycache__
-.vscode
-*.ipynb_checkpoints*
-./data
-./data.zip
\ No newline at end of file
+# .gitignore specifies the files that shouldn't be included
+# in version control and therefore shouldn't be included when
+# deploying an application to Dash Enterprise
+# This is a very exhaustive list!
+# This list was based off of https://github.com/github/gitignore
+
+# Ignore data that is generated during the runtime of an application
+# This folder is used by the "Large Data" sample applications
+runtime_data/
+data/
+
+# Omit SQLite databases that may be produced by dash-snapshots in development
+*.db
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+
+# Jupyter Notebook
+
+.ipynb_checkpoints
+*/.ipynb_checkpoints/*
+
+# IPython
+profile_default/
+ipython_config.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+
+# macOS General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# History files
+.Rhistory
+.Rapp.history
+
+# Session Data files
+.RData
+
+# User-specific files
+.Ruserdata
+
+# Example code in package build process
+*-Ex.R
+
+# Output files from R CMD check
+/*.Rcheck/
+
+# RStudio files
+.Rproj.user/
+
+# produced vignettes
+vignettes/*.html
+vignettes/*.pdf
+
+# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
+.httr-oauth
+
+# knitr and R markdown default cache directories
+*_cache/
+/cache/
+
+# Temporary files created by R markdown
+*.utf8.md
+*.knit.md
+
+# R Environment Variables
+.Renviron
+
+# Linux
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+# VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# SublineText
+# Cache files for Sublime Text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# Workspace files are user-specific
+*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+Package Control.merged-ca-bundle
+Package Control.user-ca-bundle
+oscrypto-ca-bundle.crt
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
\ No newline at end of file
diff --git a/apps/dash-lyft-explorer/README.md b/apps/dash-lyft-explorer/README.md
index c6b19855b..6bf8a1b95 100644
--- a/apps/dash-lyft-explorer/README.md
+++ b/apps/dash-lyft-explorer/README.md
@@ -19,7 +19,7 @@ This shows how to build a real-time AV explorer from scratch using Dash Deck and
📰 [Article](https://medium.com/plotly/the-history-of-autonomous-vehicle-datasets-and-3-open-source-python-apps-for-visualizing-them-afee9d13f58a)
-![demo](assets/demo.gif)
+![demo](assets/github/demo.gif)
## Instructions
diff --git a/apps/dash-lyft-explorer/app.py b/apps/dash-lyft-explorer/app.py
index d2bd79af5..c20cdd1f1 100644
--- a/apps/dash-lyft-explorer/app.py
+++ b/apps/dash-lyft-explorer/app.py
@@ -1,335 +1,32 @@
-import os
-import time
-import colorlover as cl
-import dash
+from dash import Dash, html, dcc, Input, Output, State, callback_context, no_update
import dash_bootstrap_components as dbc
-import dash_core_components as dcc
-import dash_html_components as html
-import dash_deck
-from dash.dependencies import Input, Output, State
-from lyft_dataset_sdk.lyftdataset import LyftDataset, LyftDatasetExplorer
-from lyft_dataset_sdk.utils.data_classes import Box, LidarPointCloud, RadarPointCloud
-import numpy as np
import pandas as pd
-from PIL import Image
-import plotly.graph_objects as go
-import plotly.express as px
-import pydeck as pdk
-
-
-def Header(name, app):
- title = html.H2(name, style={"margin-top": 5})
- logo = html.Img(
- src=app.get_asset_url("dash-logo.png"), style={"float": "right", "height": 60}
- )
- link = html.A(logo, href="https://plotly.com/dash/")
-
- return dbc.Row([dbc.Col(title, md=8), dbc.Col(link, md=4)])
-
-
-def unsnake(st):
- """BECAUSE_WE_DONT_READ_LIKE_THAT"""
- return st.replace("_", " ").title()
-
-
-def build_deck(mode, pc_df, polygon_data):
- if mode == "first_person":
- view = pdk.View(type="FirstPersonView", controller=True)
- view_state = pdk.ViewState(latitude=0, longitude=0, bearing=-90, pitch=15)
- point_size = 10
- elif mode == "orbit":
- view = pdk.View(type="OrbitView", controller=True)
- view_state = pdk.ViewState(
- target=[0, 0, 1e-5],
- controller=True,
- zoom=23,
- rotation_orbit=-90,
- rotation_x=15,
- )
- point_size = 3
-
- else:
- view_state = pdk.ViewState(
- latitude=0,
- longitude=0,
- bearing=45,
- pitch=50,
- zoom=20,
- max_zoom=30,
- position=[0, 0, 1e-5],
- )
- view = pdk.View(type="MapView", controller=True)
- point_size = 1
-
- pc_layer = pdk.Layer(
- "PointCloudLayer",
- data=pc_df,
- get_position=["x", "y", "z"],
- get_color=[255, 255, 255],
- auto_highlight=True,
- pickable=False,
- point_size=point_size,
- coordinate_system=2,
- coordinate_origin=[0, 0],
- )
-
- box_layer = pdk.Layer(
- "PolygonLayer",
- data=polygon_data,
- stroked=True,
- pickable=True,
- filled=True,
- extruded=True,
- opacity=0.2,
- wireframe=True,
- line_width_min_pixels=1,
- get_polygon="polygon",
- get_fill_color="color",
- get_line_color=[255, 255, 255],
- get_line_width=0,
- coordinate_system=2,
- get_elevation="elevation",
- )
-
- tooltip = {"html": "Label: {name}"}
-
- r = pdk.Deck(
- [pc_layer, box_layer],
- initial_view_state=view_state,
- views=[view],
- tooltip=tooltip,
- map_provider=None,
- )
-
- return r
-
-
-def compute_pointcloud_for_image(
- lv5,
- sample_token: str,
- dot_size: int = 2,
- pointsensor_channel: str = "LIDAR_TOP",
- camera_channel: str = "CAM_FRONT",
- out_path: str = None,
-):
- """Scatter-plots a point-cloud on top of image.
- Args:
- sample_token: Sample token.
- dot_size: Scatter plot dot size.
- pointsensor_channel: RADAR or LIDAR channel name, e.g. 'LIDAR_TOP'.
- camera_channel: Camera channel name, e.g. 'CAM_FRONT'.
- out_path: Optional path to save the rendered figure to disk.
- Returns:
- tuple containing the points, array of colors and a pillow image
- """
- sample_record = lv5.get("sample", sample_token)
-
- # Here we just grab the front camera and the point sensor.
- pointsensor_token = sample_record["data"][pointsensor_channel]
- camera_token = sample_record["data"][camera_channel]
-
- points, coloring, im = lv5.explorer.map_pointcloud_to_image(
- pointsensor_token, camera_token
- )
-
- return points, coloring, im
-
-
-def render_box_in_image(lv5, im, sample: str, camera_channel: str):
- camera_token = sample["data"][camera_channel]
- data_path, boxes, camera_intrinsic = lv5.get_sample_data(
- camera_token, flat_vehicle_coordinates=False
- )
-
- arr = np.array(im)
-
- for box in boxes:
- c = NAME2COLOR[box.name]
- box.render_cv2(arr, normalize=True, view=camera_intrinsic, colors=(c, c, c))
-
- new = Image.fromarray(arr)
- return new
-
-
-def get_token_list(scene):
- token_list = [scene["first_sample_token"]]
- sample = lv5.get("sample", token_list[0])
-
- while sample["next"] != "":
- token_list.append(sample["next"])
- sample = lv5.get("sample", sample["next"])
-
- return token_list
-
-
-def build_figure(lv5, sample, lidar, camera, overlay):
- points, coloring, im = compute_pointcloud_for_image(
- lv5, sample["token"], pointsensor_channel=lidar, camera_channel=camera
- )
-
- if "boxes" in overlay:
- im = render_box_in_image(lv5, im, sample, camera_channel=camera)
-
- fig = px.imshow(im, binary_format="jpeg", binary_compression_level=2)
-
- if "pointcloud" in overlay:
- fig.add_trace(
- go.Scattergl(
- x=points[0,],
- y=points[1,],
- mode="markers",
- opacity=0.4,
- marker_color=coloring,
- marker_size=3,
- )
- )
-
- fig.update_layout(
- margin=dict(l=10, r=10, t=0, b=0),
- paper_bgcolor="rgba(0,0,0,0)",
- plot_bgcolor="rgba(0,0,0,0)",
- hovermode=False,
- )
- fig.update_xaxes(showticklabels=False, showgrid=False, range=(0, im.size[0]))
- fig.update_yaxes(showticklabels=False, showgrid=False, range=(im.size[1], 0))
-
- return fig
-
+import numpy as np
+from lyft_dataset_sdk.utils.data_classes import LidarPointCloud
-# Variables
-CAMERAS = [
- "CAM_FRONT",
- "CAM_BACK",
- "CAM_FRONT_ZOOMED",
- "CAM_FRONT_LEFT",
- "CAM_FRONT_RIGHT",
- "CAM_BACK_RIGHT",
- "CAM_BACK_LEFT",
-]
-LIDARS = ["LIDAR_TOP", "LIDAR_FRONT_RIGHT", "LIDAR_FRONT_LEFT"]
+from constants import INITIAL_TOKEN, token_list, lv5, NAME2COLOR
+from utils.components import Header, CONTROLS, DECK_CARD
+from utils.model import build_figure, build_deck
-NAME2COLOR = dict(
- zip(
- ["bus", "car", "other_vehicle", "pedestrian", "truck"],
- cl.to_numeric(cl.scales["5"]["div"]["Spectral"]),
- )
+app = Dash(
+ __name__,
+ external_stylesheets=[dbc.themes.CYBORG],
+ title="Lyft Interactive Dashboard",
)
-
-# Create Lyft object
-lv5 = LyftDataset(data_path="./data", json_path="./data/train_data", verbose=True)
-# Load a single scene
-scene = lv5.scene[0]
-token_list = get_token_list(scene)
-INITIAL_TOKEN = scene["first_sample_token"]
-
-
-app = dash.Dash(__name__, external_stylesheets=[dbc.themes.CYBORG])
server = app.server
-controls = [
- dbc.FormGroup(
- [
- dbc.Label("Camera Position"),
- dbc.Select(
- id="camera",
- options=[
- {"label": unsnake(s.replace("CAM_", "")), "value": s}
- for s in CAMERAS
- ],
- value=CAMERAS[0],
- ),
- ]
- ),
- dbc.FormGroup(
- [
- dbc.Label("Image Overlay"),
- dbc.Checklist(
- id="overlay",
- value=[],
- options=[
- {"label": x.title(), "value": x} for x in ["pointcloud", "boxes"]
- ],
- inline=True,
- switch=True,
- ),
- ]
- ),
- dbc.FormGroup(
- [
- dbc.Label("Frame"),
- html.Br(),
- dbc.Spinner(
- dbc.ButtonGroup(
- [
- dbc.Button(
- "Prev", id="prev", n_clicks=0, color="primary", outline=True
- ),
- dbc.Button("Next", id="next", n_clicks=0, color="primary"),
- ],
- id="button-group",
- style={"width": "100%"},
- ),
- spinner_style={"margin-top": 0, "margin-bottom": 0},
- ),
- ]
- ),
- dbc.FormGroup(
- [
- dbc.Label("Progression"),
- dbc.Spinner(
- dbc.Input(
- id="progression", type="range", min=0, max=len(token_list), value=0
- ),
- spinner_style={"margin-top": 0, "margin-bottom": 0},
- ),
- ]
- ),
- dbc.FormGroup(
- [
- dbc.Label("Lidar Position"),
- dbc.Select(
- id="lidar",
- value=LIDARS[0],
- options=[
- {"label": unsnake(s.replace("LIDAR_", "")), "value": s}
- for s in LIDARS
- ],
- ),
- ]
- ),
- dbc.FormGroup(
- [
- dbc.Label("Lidar View Mode"),
- dbc.Select(
- id="view-mode",
- value="map",
- options=[
- {"label": unsnake(x), "value": x}
- for x in ["first_person", "orbit", "map"]
- ],
- ),
- ]
- ),
-]
-
-deck_card = dbc.Card(
- dash_deck.DeckGL(id="deck-pointcloud", tooltip={"html": "Label: {name}"}),
- body=True,
- style={"height": "calc(95vh - 215px)"},
-)
app.layout = dbc.Container(
[
Header("Dash Lyft Perception", app),
- html.Br(),
- dbc.Card(dbc.Row([dbc.Col(c) for c in controls], form=True), body=True),
- html.Br(),
+ dbc.Card(dbc.Row([dbc.Col(c) for c in CONTROLS]), body=True),
dbc.Row(
[
dbc.Col(dbc.Card(dcc.Graph(id="graph-camera"), body=True), md=5),
- dbc.Col(deck_card, md=7),
- ]
+ dbc.Col(DECK_CARD, md=7),
+ ],
+ className="app-body"
),
dcc.Store(id="sample-token", data=INITIAL_TOKEN),
],
@@ -339,11 +36,12 @@ def build_figure(lv5, sample, lidar, camera, overlay):
@app.callback(
Output("progression", "value"),
- [Input("prev", "n_clicks"), Input("next", "n_clicks")],
- [State("progression", "value")],
+ Input("prev", "n_clicks"),
+ Input("next", "n_clicks"),
+ State("progression", "value"),
)
def update_current_token(btn_prev, btn_next, curr_progress):
- ctx = dash.callback_context
+ ctx = callback_context
prop_id = ctx.triggered[0]["prop_id"]
if "next" in prop_id:
@@ -351,23 +49,19 @@ def update_current_token(btn_prev, btn_next, curr_progress):
elif "prev" in prop_id:
return max(0, int(curr_progress) - 1)
else:
- return dash.no_update
+ return no_update
@app.callback(
- [
- Output("graph-camera", "figure"),
- Output("deck-pointcloud", "data"),
- Output("button-group", "children"),
- Output("progression", "type"),
- ],
- [
- Input("progression", "value"),
- Input("camera", "value"),
- Input("lidar", "value"),
- Input("overlay", "value"),
- Input("view-mode", "value"),
- ],
+ Output("graph-camera", "figure"),
+ Output("deck-pointcloud", "data"),
+ Output("button-group", "children"),
+ Output("progression", "type"),
+ Input("progression", "value"),
+ Input("camera", "value"),
+ Input("lidar", "value"),
+ Input("overlay", "value"),
+ Input("view-mode", "value"),
)
def update_graphs(progression, camera, lidar, overlay, view_mode):
token = token_list[int(progression)]
@@ -402,7 +96,7 @@ def update_graphs(progression, camera, lidar, overlay, view_mode):
fig = build_figure(lv5, sample, lidar, camera, overlay)
r = build_deck(view_mode, pc_df, polygon_data)
- return fig, r.to_json(), dash.no_update, dash.no_update
+ return fig, r.to_json(), no_update, no_update
if __name__ == "__main__":
diff --git a/apps/dash-lyft-explorer/assets/css/header.css b/apps/dash-lyft-explorer/assets/css/header.css
new file mode 100644
index 000000000..e6d0e204b
--- /dev/null
+++ b/apps/dash-lyft-explorer/assets/css/header.css
@@ -0,0 +1,51 @@
+.header {
+ padding: 5px 15px 5px 10px;
+}
+
+.header-logos {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 5%;
+}
+
+.app-body {
+ padding: 20px 10px 10px 10px;
+}
+
+
+/* Demo button css */
+.demo-button {
+ font-family: Open Sans,sans-serif;
+ text-decoration: none;
+ display: inline-flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ border-radius: 8px;
+ font-weight: 700;
+ height: 2.5rem;
+ font-size: 13px;
+ -webkit-padding-start: 1rem;
+ padding-inline-start: 1rem;
+ -webkit-padding-end: 1rem;
+ padding-inline-end: 1rem;
+ color: #ffffff;
+ letter-spacing: 2px;
+ border: solid 1.5px transparent;
+ box-shadow: 2px 1000px 1px #0c0c0c inset;
+ background-image: linear-gradient(135deg, #7A76FF, #7A76FF, #7FE4FF);
+ -webkit-background-size: 200% 100%;
+ background-size: 200% 100%;
+ -webkit-background-position: 99%;
+ background-position: 99%;
+ background-origin: border-box;
+ transition: all .4s ease-in-out;
+ padding-top: 15px;
+ padding-bottom: 15px;
+}
+.demo-button:hover {
+ color: #7A76FF;
+}
diff --git a/apps/dash-lyft-explorer/assets/dash-logo.png b/apps/dash-lyft-explorer/assets/dash-logo.png
deleted file mode 100644
index 9ed7cd160..000000000
Binary files a/apps/dash-lyft-explorer/assets/dash-logo.png and /dev/null differ
diff --git a/apps/dash-lyft-explorer/assets/demo.gif b/apps/dash-lyft-explorer/assets/github/demo.gif
similarity index 100%
rename from apps/dash-lyft-explorer/assets/demo.gif
rename to apps/dash-lyft-explorer/assets/github/demo.gif
diff --git a/apps/dash-lyft-explorer/assets/images/dash-logo.png b/apps/dash-lyft-explorer/assets/images/dash-logo.png
new file mode 100644
index 000000000..984dd57ab
Binary files /dev/null and b/apps/dash-lyft-explorer/assets/images/dash-logo.png differ
diff --git a/apps/dash-lyft-explorer/constants.py b/apps/dash-lyft-explorer/constants.py
new file mode 100644
index 000000000..e862fc4cd
--- /dev/null
+++ b/apps/dash-lyft-explorer/constants.py
@@ -0,0 +1,31 @@
+from lyft_dataset_sdk.lyftdataset import LyftDataset
+import colorlover as cl
+from utils.helper_functions import get_token_list
+
+## Define constants used across the app
+CAMERAS = [
+ "CAM_FRONT",
+ "CAM_BACK",
+ "CAM_FRONT_ZOOMED",
+ "CAM_FRONT_LEFT",
+ "CAM_FRONT_RIGHT",
+ "CAM_BACK_RIGHT",
+ "CAM_BACK_LEFT",
+]
+LIDARS = ["LIDAR_TOP", "LIDAR_FRONT_RIGHT", "LIDAR_FRONT_LEFT"]
+
+NAME2COLOR = dict(
+ zip(
+ ["bus", "car", "other_vehicle", "pedestrian", "truck"],
+ cl.to_numeric(cl.scales["5"]["div"]["Spectral"]),
+ )
+)
+
+## Create Lyft object
+lv5 = LyftDataset(data_path="./data", json_path="./data/train_data", verbose=True)
+
+# Load a single scene
+scene = lv5.scene[0]
+token_list = get_token_list(scene, lv5)
+INITIAL_TOKEN = scene["first_sample_token"]
+
diff --git a/apps/dash-lyft-explorer/requirements.txt b/apps/dash-lyft-explorer/requirements.txt
index dee90beaa..0e5ee0855 100644
--- a/apps/dash-lyft-explorer/requirements.txt
+++ b/apps/dash-lyft-explorer/requirements.txt
@@ -1,13 +1,9 @@
-dash
-dash-deck
-dash-bootstrap-components
-lyft-dataset-sdk
-pydeck==0.5.*
-jupyter
-colorlover
-plotly
-Pillow
-gunicorn
-numpy
-pandas
-opencv-python==4.2.0.34
+dash==2.4.1
+dash-deck==0.0.1
+dash-bootstrap-components==1.1.0
+pandas==1.4.2
+numpy==1.22.3
+colorlover==0.3.0
+lyft-dataset-sdk==0.0.8
+pydeck==0.7.1
+gunicorn==20.1.0
\ No newline at end of file
diff --git a/apps/dash-lyft-explorer/runtime.txt b/apps/dash-lyft-explorer/runtime.txt
index 257b314f5..cfa660c42 100644
--- a/apps/dash-lyft-explorer/runtime.txt
+++ b/apps/dash-lyft-explorer/runtime.txt
@@ -1 +1 @@
-python-3.7.6
\ No newline at end of file
+python-3.8.0
\ No newline at end of file
diff --git a/apps/dash-lyft-explorer/utils/components.py b/apps/dash-lyft-explorer/utils/components.py
new file mode 100644
index 000000000..429a82405
--- /dev/null
+++ b/apps/dash-lyft-explorer/utils/components.py
@@ -0,0 +1,105 @@
+from dash import html
+import dash_bootstrap_components as dbc
+import dash_deck
+
+from constants import CAMERAS, LIDARS, token_list
+from utils.helper_functions import unsnake
+
+def Header(name, app):
+ title = html.H2(name, style={"margin-top": 5})
+ logo = html.Img(src=app.get_asset_url("images/dash-logo.png"), style={"float": "right", "height": 60})
+ link = html.A(logo, href="https://plotly.com/dash/", target="_blank")
+ demo_link = html.A("ENTERPRISE DEMO", href="https://plotly.com/get-demo/", target="_blank", className="demo-button")
+
+ return dbc.Row([dbc.Col(title, md=8), dbc.Col([demo_link, link], md=4, className="header-logos")], className="header")
+
+
+CONTROLS = [
+ html.Div(
+ [
+ dbc.Label("Camera Position"),
+ dbc.Select(
+ id="camera",
+ options=[
+ {"label": unsnake(s.replace("CAM_", "")), "value": s}
+ for s in CAMERAS
+ ],
+ value=CAMERAS[0],
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ dbc.Label("Image Overlay"),
+ dbc.Checklist(
+ id="overlay",
+ value=["boxes"],
+ options=[
+ {"label": x.title(), "value": x} for x in ["pointcloud", "boxes"]
+ ],
+ inline=True,
+ switch=True,
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ dbc.Label("Frame"),
+ html.Br(),
+ dbc.Spinner(
+ dbc.ButtonGroup(
+ [
+ dbc.Button(
+ "Prev", id="prev", n_clicks=0, color="primary", outline=True
+ ),
+ dbc.Button("Next", id="next", n_clicks=0, color="primary"),
+ ],
+ id="button-group",
+ style={"width": "100%"},
+ ),
+ spinner_style={"margin-top": 0, "margin-bottom": 0},
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ dbc.Label("Progression"),
+ dbc.Spinner(
+ dbc.Input(id="progression", type="range", min=0, max=len(token_list), value=0, step=1),
+ spinner_style={"margin-top": 0, "margin-bottom": 0},
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ dbc.Label("Lidar Position"),
+ dbc.Select(
+ id="lidar",
+ value=LIDARS[0],
+ options=[
+ {"label": unsnake(s.replace("LIDAR_", "")), "value": s}
+ for s in LIDARS
+ ],
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ dbc.Label("Lidar View Mode"),
+ dbc.Select(
+ id="view-mode",
+ value="map",
+ options=[
+ {"label": unsnake(x), "value": x}
+ for x in ["first_person", "orbit", "map"]
+ ],
+ ),
+ ]
+ ),
+]
+
+DECK_CARD = dbc.Card(
+ dash_deck.DeckGL(id="deck-pointcloud", tooltip={"html": "Label: {name}"}),
+ body=True,
+ style={"height": "calc(95vh - 215px)"},
+)
\ No newline at end of file
diff --git a/apps/dash-lyft-explorer/utils/helper_functions.py b/apps/dash-lyft-explorer/utils/helper_functions.py
new file mode 100644
index 000000000..8a179e137
--- /dev/null
+++ b/apps/dash-lyft-explorer/utils/helper_functions.py
@@ -0,0 +1,15 @@
+def unsnake(st):
+ """
+ Converts a string with _ to space.
+ """
+ return st.replace("_", " ").title()
+
+def get_token_list(scene, lv5):
+ token_list = [scene["first_sample_token"]]
+ sample = lv5.get("sample", token_list[0])
+
+ while sample["next"] != "":
+ token_list.append(sample["next"])
+ sample = lv5.get("sample", sample["next"])
+
+ return token_list
\ No newline at end of file
diff --git a/apps/dash-lyft-explorer/utils/model.py b/apps/dash-lyft-explorer/utils/model.py
new file mode 100644
index 000000000..f9d94300a
--- /dev/null
+++ b/apps/dash-lyft-explorer/utils/model.py
@@ -0,0 +1,159 @@
+import plotly.graph_objects as go
+import plotly.express as px
+import numpy as np
+import pydeck as pdk
+from PIL import Image
+
+from constants import NAME2COLOR
+
+def build_deck(mode, pc_df, polygon_data):
+ if mode == "first_person":
+ view = pdk.View(type="FirstPersonView", controller=True)
+ view_state = pdk.ViewState(latitude=0, longitude=0, bearing=-90, pitch=15)
+ point_size = 10
+ elif mode == "orbit":
+ view = pdk.View(type="OrbitView", controller=True)
+ view_state = pdk.ViewState(
+ target=[0, 0, 1e-5],
+ controller=True,
+ zoom=23,
+ rotation_orbit=-90,
+ rotation_x=15,
+ )
+ point_size = 3
+
+ else:
+ view_state = pdk.ViewState(
+ latitude=0,
+ longitude=0,
+ bearing=45,
+ pitch=50,
+ zoom=20,
+ max_zoom=30,
+ position=[0, 0, 1e-5],
+ )
+ view = pdk.View(type="MapView", controller=True)
+ point_size = 1
+
+ pc_layer = pdk.Layer(
+ "PointCloudLayer",
+ data=pc_df,
+ get_position=["x", "y", "z"],
+ get_color=[255, 255, 255],
+ auto_highlight=True,
+ pickable=False,
+ point_size=point_size,
+ coordinate_system=2,
+ coordinate_origin=[0, 0],
+ )
+
+ box_layer = pdk.Layer(
+ "PolygonLayer",
+ data=polygon_data,
+ stroked=True,
+ pickable=True,
+ filled=True,
+ extruded=True,
+ opacity=0.2,
+ wireframe=True,
+ line_width_min_pixels=1,
+ get_polygon="polygon",
+ get_fill_color="color",
+ get_line_color=[255, 255, 255],
+ get_line_width=0,
+ coordinate_system=2,
+ get_elevation="elevation",
+ )
+
+ tooltip = {"html": "Label: {name}"}
+
+ r = pdk.Deck(
+ [pc_layer, box_layer],
+ initial_view_state=view_state,
+ views=[view],
+ tooltip=tooltip,
+ map_provider=None,
+ )
+
+ return r
+
+
+def compute_pointcloud_for_image(
+ lv5,
+ sample_token: str,
+ dot_size: int = 2,
+ pointsensor_channel: str = "LIDAR_TOP",
+ camera_channel: str = "CAM_FRONT",
+ out_path: str = None,
+):
+ """Scatter-plots a point-cloud on top of image.
+ Args:
+ sample_token: Sample token.
+ dot_size: Scatter plot dot size.
+ pointsensor_channel: RADAR or LIDAR channel name, e.g. 'LIDAR_TOP'.
+ camera_channel: Camera channel name, e.g. 'CAM_FRONT'.
+ out_path: Optional path to save the rendered figure to disk.
+ Returns:
+ tuple containing the points, array of colors and a pillow image
+ """
+ sample_record = lv5.get("sample", sample_token)
+
+ # Here we just grab the front camera and the point sensor.
+ pointsensor_token = sample_record["data"][pointsensor_channel]
+ camera_token = sample_record["data"][camera_channel]
+
+ points, coloring, im = lv5.explorer.map_pointcloud_to_image(
+ pointsensor_token, camera_token
+ )
+
+ return points, coloring, im
+
+
+def render_box_in_image(lv5, im, sample: str, camera_channel: str):
+ camera_token = sample["data"][camera_channel]
+ data_path, boxes, camera_intrinsic = lv5.get_sample_data(
+ camera_token, flat_vehicle_coordinates=False
+ )
+
+ arr = np.array(im)
+
+ for box in boxes:
+ c = NAME2COLOR[box.name]
+ box.render_cv2(arr, normalize=True, view=camera_intrinsic, colors=(c, c, c))
+
+ new = Image.fromarray(arr)
+ return new
+
+
+def build_figure(lv5, sample, lidar, camera, overlay):
+ points, coloring, im = compute_pointcloud_for_image(
+ lv5, sample["token"], pointsensor_channel=lidar, camera_channel=camera
+ )
+
+ if "boxes" in overlay:
+ im = render_box_in_image(lv5, im, sample, camera_channel=camera)
+
+ fig = px.imshow(im, binary_format="jpeg", binary_compression_level=2)
+
+ if "pointcloud" in overlay:
+ fig.add_trace(
+ go.Scattergl(
+ x=points[0,],
+ y=points[1,],
+ mode="markers",
+ opacity=0.4,
+ marker_color=coloring,
+ marker_size=3,
+ )
+ )
+
+ fig.update_layout(
+ margin=dict(l=10, r=10, t=0, b=0),
+ paper_bgcolor="rgba(0,0,0,0)",
+ plot_bgcolor="rgba(0,0,0,0)",
+ hovermode=False,
+ )
+ fig.update_xaxes(showticklabels=False, showgrid=False, range=(0, im.size[0]))
+ fig.update_yaxes(showticklabels=False, showgrid=False, range=(im.size[1], 0))
+
+ return fig