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