From dcadbbc8a4840cf8cae9e06a9639579099058f42 Mon Sep 17 00:00:00 2001 From: Rohan Mathur Date: Thu, 26 Jan 2023 19:08:01 -0500 Subject: [PATCH] Load path dropdown (#1290) * started load path options * deleted random comment * got the camera paths to all send but it won't render right * finished first draft of new load path button * removed unnecessary prints * fixed minor name warning * clean up and ui change * Clean up UI Co-authored-by: Matthew Tancik Co-authored-by: Matt Tancik --- nerfstudio/viewer/app/src/index.scss | 19 +++ .../src/modules/ConfigPanel/ConfigPanel.jsx | 2 - .../modules/LoadPathModal/LoadPathModal.jsx | 149 ++++++++++++++++++ .../app/src/modules/LoadPathModal/index.jsx | 3 + .../src/modules/RenderModal/RenderModal.jsx | 12 +- .../SidePanel/CameraPanel/CameraPanel.jsx | 29 +++- nerfstudio/viewer/app/src/reducer.js | 6 +- nerfstudio/viewer/app/src/themes/theme.ts | 7 + nerfstudio/viewer/server/viewer_utils.py | 18 ++- 9 files changed, 228 insertions(+), 17 deletions(-) create mode 100644 nerfstudio/viewer/app/src/modules/LoadPathModal/LoadPathModal.jsx create mode 100644 nerfstudio/viewer/app/src/modules/LoadPathModal/index.jsx diff --git a/nerfstudio/viewer/app/src/index.scss b/nerfstudio/viewer/app/src/index.scss index fab8b8a68..3a331e37a 100644 --- a/nerfstudio/viewer/app/src/index.scss +++ b/nerfstudio/viewer/app/src/index.scss @@ -242,6 +242,25 @@ $sidebar-width: 100% !default; margin-top: 20px; } +// ------------------------------------ +// Load Path Modal styling + +.LoadPathModal-box { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 500px; // 400 + background-color: $base-color2; + border: 2px solid #000; + padding: 32px; + margin-top: 20px; +} + +.LoadPathModal-upload_button { + text-align: center; +} + // ------------------------------------ // Camera Pane diff --git a/nerfstudio/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx b/nerfstudio/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx index ec5d95e82..a1c331fad 100644 --- a/nerfstudio/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx +++ b/nerfstudio/viewer/app/src/modules/ConfigPanel/ConfigPanel.jsx @@ -47,7 +47,6 @@ export function RenderControls() { const crop_enabled = useSelector( (state) => state.renderingState.crop_enabled, ); - console.log('crop_enabled_config panel', crop_enabled); const crop_bg_color = useSelector( (state) => state.renderingState.crop_bg_color, @@ -162,7 +161,6 @@ export function RenderControls() { value: crop_enabled, hint: 'Crop the viewport to the selected box', onChange: (value) => { - console.log('dispact value', value); dispatch_and_send( websocket, dispatch, diff --git a/nerfstudio/viewer/app/src/modules/LoadPathModal/LoadPathModal.jsx b/nerfstudio/viewer/app/src/modules/LoadPathModal/LoadPathModal.jsx new file mode 100644 index 000000000..5f79d1e00 --- /dev/null +++ b/nerfstudio/viewer/app/src/modules/LoadPathModal/LoadPathModal.jsx @@ -0,0 +1,149 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import * as React from 'react'; + +import { Box, Button, FormControl, Modal, Typography } from '@mui/material'; +import InputLabel from '@mui/material/InputLabel'; +import { useSelector } from 'react-redux'; +import { FileUpload } from '@mui/icons-material'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; + +interface LoadPathModalProps { + open: object; + setOpen: object; + pathUploadFunction: any; + loadCameraPathFunction: any; +} + +export default function LoadPathModal(props: LoadPathModalProps) { + const open = props.open; + const setOpen = props.setOpen; + const uploadCameraPath = props.pathUploadFunction; + const loadCameraPath = props.loadCameraPathFunction; + + const [existingPathSelect, setExistingPathSelect] = React.useState(''); + + // redux store state + const all_camera_paths = useSelector( + (state) => state.renderingState.all_camera_paths, + ); + + let camera_paths_arr = []; + if (typeof all_camera_paths === 'object' && all_camera_paths !== null) { + camera_paths_arr = Object.keys(all_camera_paths).map((key) => { + return { + name: key, + val: all_camera_paths[key], + }; + }); + } + + const hiddenFileInput = React.useRef(null); + const handleFileUploadClick = () => { + hiddenFileInput.current.click(); + }; + + // react state + + const handleClose = () => setOpen(false); + const handlePathSelect = (event) => { + setExistingPathSelect(event.target.value); + }; + + const handleExistingLoadClick = () => { + loadCameraPath(existingPathSelect); + handleClose(); + }; + + const handleFileInput = (event) => { + uploadCameraPath(event); + handleClose(); + }; + + return ( +
+ + + +
+

Load Camera Path

+ {camera_paths_arr.length > 0 && ( + <> +

+ Either upload a local file or select a saved camera path +

+ + Existing Path + + + +
+

+

OR
+

+ + )} + {camera_paths_arr.length === 0 && ( +

No existing saved paths found

+ )} +
+ +
+
+
+
+
+
+ ); +} diff --git a/nerfstudio/viewer/app/src/modules/LoadPathModal/index.jsx b/nerfstudio/viewer/app/src/modules/LoadPathModal/index.jsx new file mode 100644 index 000000000..c7f0dc4a1 --- /dev/null +++ b/nerfstudio/viewer/app/src/modules/LoadPathModal/index.jsx @@ -0,0 +1,3 @@ +import LoadPathModal from './LoadPathModal'; + +export default LoadPathModal; diff --git a/nerfstudio/viewer/app/src/modules/RenderModal/RenderModal.jsx b/nerfstudio/viewer/app/src/modules/RenderModal/RenderModal.jsx index c9cea8f1b..8b0a18321 100644 --- a/nerfstudio/viewer/app/src/modules/RenderModal/RenderModal.jsx +++ b/nerfstudio/viewer/app/src/modules/RenderModal/RenderModal.jsx @@ -19,9 +19,7 @@ export default function RenderModal(props: RenderModalProps) { (state) => state.renderingState.config_base_dir, ); - const export_path = useSelector( - (state) => state.renderingState.export_path, - ); + const export_path = useSelector((state) => state.renderingState.export_path); const data_base_dir = useSelector( (state) => state.renderingState.data_base_dir, @@ -34,12 +32,14 @@ export default function RenderModal(props: RenderModalProps) { // Copy the text inside the text field const config_filename = `${config_base_dir}/config.yml`; const camera_path_filename = `${export_path}.json`; - const cmd = `ns-render --load-config ${config_filename} --traj filename --camera-path-filename ${data_base_dir}/camera_paths/${camera_path_filename} --output-path renders/${data_base_dir}/${export_path}.mp4`; + const data_base_dir_leaf = data_base_dir.split('/').pop(); + const cmd = `ns-render --load-config ${config_filename} --traj filename --camera-path-filename ${data_base_dir}/camera_paths/${camera_path_filename} --output-path renders/${data_base_dir_leaf}/${export_path}.mp4`; const text_intro = `To render a full resolution video, run the following command in a terminal.`; const handleCopy = () => { navigator.clipboard.writeText(cmd); + handleClose(); }; return ( @@ -63,11 +63,11 @@ export default function RenderModal(props: RenderModalProps) {
The video will be saved to{' '} - ./renders/{data_base_dir}/{export_path}.mp4 + ./renders/{data_base_dir_leaf}/{export_path}.mp4 .

- +
{cmd}
diff --git a/nerfstudio/viewer/app/src/reducer.js b/nerfstudio/viewer/app/src/reducer.js index 0ee2dd2ed..6de1ea7c2 100644 --- a/nerfstudio/viewer/app/src/reducer.js +++ b/nerfstudio/viewer/app/src/reducer.js @@ -12,6 +12,7 @@ const initialState = { }, // for sending actual commands to the client camera_path_payload: null, + populate_paths_payload: false, // the rendering state renderingState: { // cameras @@ -25,7 +26,10 @@ const initialState = { camera_type: 'perspective', data_base_dir: 'data_base_dir', // the base directory of the images for saving camera path with the data - export_path: 'export_path', + export_path: 'export_path', // export name for render and camera_path + + all_camera_paths: null, // object containing camera paths and names + isTraining: true, output_options: ['rgb'], // populated by the possible Graph outputs diff --git a/nerfstudio/viewer/app/src/themes/theme.ts b/nerfstudio/viewer/app/src/themes/theme.ts index 4a973209e..b789c3899 100644 --- a/nerfstudio/viewer/app/src/themes/theme.ts +++ b/nerfstudio/viewer/app/src/themes/theme.ts @@ -169,5 +169,12 @@ export const appTheme = createTheme({ }, }, }, + MuiSelect: { + styleOverrides: { + icon: { + color: '#eeeeee', + }, + }, + }, }, }); diff --git a/nerfstudio/viewer/server/viewer_utils.py b/nerfstudio/viewer/server/viewer_utils.py index 2ac82273d..f62cf9d8f 100644 --- a/nerfstudio/viewer/server/viewer_utils.py +++ b/nerfstudio/viewer/server/viewer_utils.py @@ -388,6 +388,21 @@ class ViewerState: write_to_json(Path(os.path.join(camera_paths_directory, camera_path_filename)), camera_path) self.vis["camera_path_payload"].delete() + def _check_populate_paths_payload(self, trainer, step: int): + populate_paths_payload = self.vis["populate_paths_payload"].read() + if populate_paths_payload: + # save a model checkpoint + trainer.save_checkpoint(step) + # get all camera paths + camera_path_dir = os.path.join(self.datapath, "camera_paths") + camera_path_files = os.listdir(camera_path_dir) + all_path_dict = {} + for i in camera_path_files: + if i[-4:] == "json": + all_path_dict[i[:-5]] = load_from_json(Path(os.path.join(camera_path_dir, i))) + self.vis["renderingState/all_camera_paths"].write(all_path_dict) + self.vis["populate_paths_payload"].delete() + def _check_webrtc_offer(self): """Check if there is a webrtc offer to respond to.""" data = self.vis["webrtc/offer"].read() @@ -463,7 +478,6 @@ class ViewerState: step: iteration step of training graph: the current checkpoint of the model """ - has_temporal_distortion = getattr(graph, "temporal_distortion", None) is not None self.vis["model/has_temporal_distortion"].write(str(has_temporal_distortion).lower()) @@ -471,6 +485,7 @@ class ViewerState: self.step = step self._check_camera_path_payload(trainer, step) + self._check_populate_paths_payload(trainer, step) self._check_webrtc_offer() camera_object = self._get_camera_object() @@ -515,6 +530,7 @@ class ViewerState: self._render_image_in_viewer(camera_object, graph, is_training) camera_object = self._get_camera_object() is_training = self.vis["renderingState/isTraining"].read() + self._check_populate_paths_payload(trainer, step) self._check_camera_path_payload(trainer, step) self._check_webrtc_offer() run_loop = not is_training