From 24e4e14b2653025168b88a9b051bb210c809c7ff Mon Sep 17 00:00:00 2001 From: Filip Jeretina <59307111+zrezke@users.noreply.github.com> Date: Thu, 18 May 2023 20:18:01 +0200 Subject: [PATCH] Depthai Viewer v0.0.1 (WIP) (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * v0.0.1 of DepthaiViewer, WIP (#1) * node graph scaffolding and a bit of depthai integration * remove import * left, right + depth * fix fps sliders for mono cams * partial refactor, partially added support for device selection. * pc support + currently selected device fixes * reafactor subscriptions to api, doesn't work too well, trouble with syncing the ui with the backend * Partially migrated to websockets for the api * finish moving to websockets for the config api * fix issues after merge, retry websocket connections * fix ws thread not exiting * sleep for 1 sec before trying to reconnect * Ai support + imu support + rotate camera + bug fixes (#9) * ai support + update pipeline on device select * quick fix depth * set subs when selecting device * rename to Depthai Viewer, use changed() for config ui, disable ui when device not selected * added age gender detection * added mobilenet support * pointcloud support * Toggle subs from visible (#4) * very bad implementation of toggling subscriptions based on space view visibility TODO: less cloning and stuff * better implementation and actually seems to work * fix windows not resizing properly when size gets smaller * fix issues with 3d when resizing scene bbox, only force 2d to always have the size of self.scene_bbox * Nuked Subscriptions struct, just use Vec, much nicer, much better aswell, also fixed subscriptions for channels that need some time to actually show up, it all feels a bit hacky tho * Toggle subs from visible (#5) * very bad implementation of toggling subscriptions based on space view visibility TODO: less cloning and stuff * better implementation and actually seems to work * fix windows not resizing properly when size gets smaller * fix issues with 3d when resizing scene bbox, only force 2d to always have the size of self.scene_bbox * Nuked Subscriptions struct, just use Vec, much nicer, much better aswell, also fixed subscriptions for channels that need some time to actually show up, it all feels a bit hacky tho * fix incorrect despawn of entity paths * remove todo comment * Fix ws not sending messages, fix stutter every 2s (#6) * very bad implementation of toggling subscriptions based on space view visibility TODO: less cloning and stuff * better implementation and actually seems to work * fix windows not resizing properly when size gets smaller * fix issues with 3d when resizing scene bbox, only force 2d to always have the size of self.scene_bbox * Nuked Subscriptions struct, just use Vec, much nicer, much better aswell, also fixed subscriptions for channels that need some time to actually show up, it all feels a bit hacky tho * fix incorrect despawn of entity paths * remove todo comment * error handling * fix websocket not sending pipeline, hotfix more or less might keep * change back to unbounded * Imu support (#8) * add imu logging, exclude it from regular space views * fix imu logging * POC imu plotting * Imu accelerometer and gyroscope plotting * add orientation to imu log, make device id a String * fix imu charts layout * imu log add magnetometer * fix right panel ui as much as it makes sense to fix rn * sadly idk how to create Option::None in pyarrow for the magnetometer field * imu logging fixed, magnetometer value is now None when not logged * fix plot scrolling * Merge upstream (#10) * `arrow2_convert` primitive (de)serialization benchmarks (#1742) * arrow2_convert primitive benchmarks * addressing PR comments * Fix logged obb being displayed with half of the requested size (#1749) * benchmarks for common vector ops across `smallvec`/`tinyvec`/std (#1747) * benchmarks for common vector ops * handle N=1 * Tracked 3D cameras lead now to on-hover rays in other space views that show the same camera but don't track it. (#1751) In the same way as a 2D scene causes a on-hover ray in all space views that contain the space camera at which the 2D view "sits". * Improve dealing with raw buffers for texture read/write (#1744) * Replace TextureRowDataInfo with the more versatile Texture2DBufferInfo * comment & naming fixes * `arrow2` erased refcounted clones benchmarks (#1745) * arrow2 erased refcounted clone benchmarks * lint * addressing PR comments * dude * `arrow2` estimated_bytes_size benchmarks (#1743) * arrow2 estimated_bytes_size benchmarks * cleanup * Fix crash when trying to do picking on depth clouds * Readback depth from GPU picking (#1752) * gpu picking in the viewer picks up depth now * WebGL workarounds * Add new ARKitScenes example (#1538) Co-authored-by: Nikolaus West Co-authored-by: Emil Ernerfeldt * Fix log_obb usage (#1761) * Make sure all log_obb uses uses half_size correctly * Remove outdated link from README * Fix docstring of save * Force named arguments of log_scalar * Add link to --memory-limit docs * update ros example * Python SDK: document that we also accept colors in 0-1 floats (#1740) * Python SDK: document that we also accept colors in 0-1 floats * Assume float colors to be in gamma-space, and document that * Update arkitscenes example * Fix bug * typo * py-format * Collapse space-view by default if there is only one child (#1762) * Always create the log_time timeline (#1763) * Columnar timepoints in data tables and during transport (#1767) * columnar timepoints * self review * Fix undo/redo selection shortcut/action changing selection history without changing selection (#1765) * Fix undo/redo selection shortcut/action changing selection history without changing selection Fixes #1172 * typo fix * Don't initialize an SDK session if we are only going to be launching the app (#1768) * Allow torch tensors for log_rigid3 (#1769) * Option to show scene bounding box (#1770) * Include depth clouds in bounding box calculation * Don't wrap text when showing bbox in ui * Handle projective transforms * Nicer selection view: don't wrap second column too early * Add checkbox to show the scene bounding box * Fix a whole lot of crashes, all at once (#1780) * Add typing_extensions to requirements-doc.txt (#1786) * auto_color class-ids if they are present (#1783) * auto_color class-ids if they are present * Update log line in segmentation demo * Avoid tuple structs * Don't run 3rd party bench suites on CI (#1787) * dont run 3rd party bench suites on CI * typo * and other annoyances * Use copilot markers in PR template (#1784) * Use copilot markers in PR template * remove poem Co-authored-by: Clement Rey --------- Co-authored-by: Clement Rey * re_format: barebone support for custom formatting (#1776) * implement barebone support for custom formatting and apply to Tuid * unwrap * rather than [] * use re_tuid * Always send recording_id as part of LogMsg (#1778) * Always send recording_id as part of LogMsg * Rename build_chunk_from_components -> build_data_table_from_components * Don't make RecordingInfo optional * Always default the recording id * Log an error if we hit the initialization issue * Refactor: Add new helper crate `re_log_encoding` (#1772) * CI: Check `rerun` with --no-default features and/or with --features sdk * Create a new helper crate re_transport containing stream_rrd_from_http * Fix warnings * Move file sink to re_transport * wasm compilation fix * Move LogMsg encoding/decoding into re_transport * Fix typo * Fix web build * Fix tests * Remove a lot of unused dependencies with `cargo machete` * Build fix * Clarify * Rename the crate to re_log_encoding * better docstring Co-authored-by: Jeremy Leibs * better readme Co-authored-by: Jeremy Leibs --------- Co-authored-by: Jeremy Leibs * New example code for facebook research segment anything (#1788) * New example code for facebook research segment anything * Add segmentation workaround for users still on 0.4.0 * Images should use class-id as label * Add an alternative tensor-based view * Implement `re_tuid::Tuid::random()` on web (#1796) * Implement `re_tuid::Tuid::random()` on web * Fix bad error message * ci: fix benchmarks (#1799) * workflow: just run --all * datastore: skip bucket permutations etc on CI * i give up, just replace re_log_types by re_log_encoding * Add `minimal_options` example (`RerunArgs`) (#1773) * Allows connecting to remote server through rerun's RerunArgs. Co-authored-by: Clement Rey * Add `pacman` support to `setup_web.sh` (#1797) Co-authored-by: Clement Rey * fix ci (cargo-deny): cargo update -p crossbeam-channel (#1806) * Compile with `panic = "abort"` (#1813) * Compile with `panic = "abort"` This PR sets `panic = "abort"` for both debug and release builds. This cuts down the `rerun` binary size in release builds from 29.9 MB to 22.7 MB - a 25% reduction! ## Details The default panic behavior in Rust is to unwind the stack. This leads to a lot of extra code bloat, and some missed opportunities for optimization. The benefit is that one can let a thread die without crashing the whole application, and one can use `std::panic::catch_unwind` as a kind of try-catch block. We don't make use of these features at all (at least not intentionally), and so are paying a cost for something we don't need. I would also argue that a panic SHOULD lead to a hard crash unless you are building an Erlang-like robust actor system where you use defensive programming to protect against programmer errors (all panics are programmer errors - user errors should use `Result`). * Quiet clippy * Add `rerun --strict`: crash if any warning or error is logged (#1812) * Add `rerun --strict`: crash if any warning or error is logged Part of https://github.com/rerun-io/rerun/issues/1483 * Can't doc-test private functions * Refactor: Remove `TensorTrait` (#1819) * Refactor: Remove `TensorTrait` We don't need it anymore * End-to-end testing of python logging -> store ingestion (#1817) * Sort the arguments to `rerun` * Pass on `LogMsg::Goodbye` just like any other message * Add `rerun --test-receive` * `just py-build --quiet` is now possible * Add scripts/run_python_e2e_test.py * replace `cargo r -p rerun` with `python3 -m rerun` * lint and explain choice of examples * Add to CI * check returncode * Fix e2e test on CI: Don't try to re-build rerun-sdk (#1821) * Use gpu picking for points, streamline/share picking code some more (#1814) * use gpu picking for picking points * gpu based picking no longer works like a fallback but integrates with other picking sources * fix incorrect cursor rounding for picking * refactor picking context to be a pub struct with exposed state * unify ui picking method for 2d & 3d space views * less indentation for picking method * picking rect size is dynamically chosen * fix accidental z scaling in projection correction for picking & make cropped_projection_from_projection easier to read * CI: install pip requirements for Python e2e test * Process 2d points always in batches (#1820) * Fix CI syntax error * New option to disable persistent storage (#1825) * New option to disable persistent storage * New API to reset_time (#1826) * Datastore revamp 1: new indexing model & core datastructures (#1727) * Datastore revamp 2: serialization & formatting (#1735) * Datastore revamp 3: efficient incremental stats (#1739) * Datastore revamp 4: sunset `MsgId` (#1785) * Datastore revamp 5: (#1791) * Datastore revamp 6: sunset `LogMsg` storage + save store to disk (#1795) * Datastore revamp 7: garbage collection (#1801) * Don't assert if inserting a rowid with a matching timepoint does not create a conflict (#1832) * CI: Try installing the correct wheel on CI * CI: try again with the CI * re_query: up to date with latest data types and structures (#1828) * No more raw arrays for primary components * Don't need to carry around component names no more * Cluster keys are now raw-array-less and _not_ optional anymore * that is done indeed * helpers * datastore: incremental metadata registry stats (#1833) * add profile scopes for stats * implement incremental metadata registry stats * lint * future proofing comment * RFC: datastore state of the union & end-to-end batching (#1610) * add batching rfc * Update design/batching.md Co-authored-by: Emil Ernerfeldt * Update design/batching.md Co-authored-by: Emil Ernerfeldt * Update design/batching.md Co-authored-by: Emil Ernerfeldt * Update design/batching.md Co-authored-by: Emil Ernerfeldt * qa component instances vs. instance keys * no sticky * qa are there any special components & non-integer instance keys * give some clue about cell incompatibility * temporary constructs * zstd is already setup * more planning * date and links --------- Co-authored-by: Emil Ernerfeldt * Python CI: use bash as shell * Fix too many points crash (#1822) * Simplify point cloud builder and fix crash on too many points Fixes #1779 * faster point cloud population by not chaining iterators with default values * Use GPU picking for line(like) primitives, fix `interactive` flags (#1829) * line strip builder no longer has user data, exposes picking id instead (not implemented yet) * handle interactive object property when evaluating picking code * take line strip builder directly when building up line draw data * finish implementing picking for lines * remove unused iter_strips_with_vertices * Simplify picking handling now that there are a lot less types. Labels & textured rects are always picked now, fixes #1021 * CI: try to fix mac wheel build * Reduce memory used by staging belts on Web (#1836) In particular this prevents crashing with out of memory on a run-away belt memory usage caused by failure to unmap buffers. A bit concerningly, the fix uses our knowledge of how `wgpu::Device::poll` is broken in the current wgpu version. I took the opportunity to sharpens the definition of `HardwareTier` a bit. * CI: only test the x86_64 wheel on macos * Always flush when we remove a sink (#1830) Whenever we disconnect (or implicitly disconnect by swapping a sink) we should flush the pending messages. Additionally disconnect and flush calls both require releasing the GIL (for the same reason as shutdown previously). * GPU colormapping, first step (#1835) * Add TextureManager2D::get_or_create_with * Small code cleanup * Add code to upload a Tensor to a GPU texture * Add helper method Tensor::image_height_width_depth * Minor code cleanup (multiplicative_tint) * Hook up color textures via the new path * Refactor: introduce ColormappedTexture * Start working on an uint sampler * merge fix * Dumb colormapping of depth textures! * Use turbo for depth maps (and single-channel images :grimace:) * Use grayscale for luminance * ColorMap -> Colormap * Apply annotation context colormaps * Support sint textures too * cleanup * merge fix * Fix RGB images * More cleanup * Better error-handlign and nicer error message * Clean up the SAMPLE_TYPE with constants * Nicer shader interface * Better error handling * Remove dead code * Self-review cleanup * Fix bug in shader when sampling sint textures * Use textureSampleLevel * Apply a gamma to the image (unused as of now) * image_height_width_channels * fix various review comments * Optimize narrow_f64_to_f32s: avoid one allocation * Test and handle all tensor dtypes as images (#1840) * Support i64 and u64 tensors * api_demo: log all image types * Don't even print out the contents of a tensor * Handle unfilterable float textures * fix typo * py-format * Simplify is_float_filterable * Add a helper function pad_and_narrow_and_cast * Reuse existing image * Exclude image_tensors demo from default api_demo * Still run all api demos in e2e test * pyformat * Install the rerun-sdk in CI using --no-index and split out linux wheel build to run first. (#1838) * Install the rerun-sdk by the expected version * Fix comment * typo * Use --no-index when installing the rerun wheel * Use the cargo_version, not the new_version * Split dependency install into its own step * Don't use force-reinstall * Refactor setting of expected_version variable. * Use bash when setting env * Always run the linux job first and use its rrds for the other wheels * GPU tensor colormapping (#1841) * Refactor: introduce struct SliceSelection * Refactor: use SliceSelection inside of ViewTensorState * MVP of tensor colormapping on GPU * Remove old ui code * Support 64-bit tensors by narrowing to f32 * Allow more colormap options * Clippy * Report range errors instead of ignoring them * Sort colormaps * Shorten function name * Create module gpu_bridge * Move some code around * Simnplify API * Create ViewBuilder::new * Fix missing colon in lint.py * fix typos and formatting * Disable texture filtering options for tensors for now * Update docstrings * Add profile scopes * ViewBuilder cleanup * Make ViewBuilder::setup non-Option * Remove Result from thing that cannot fail * Fix colormap numbering * review cleanup * pass in debug_name * Unify the `range` function * typo * Show previews of colormaps when selecting them (#1846) * Make infallible version of get_or_create_texture * Show colormap previews in UI * Spelling * Implement billinear filtering of textures (#1850) * Implement opt-in billinear filtering of textures * bilinear * MVP Support for inline-rendering of Rerun within jupyter notebooks (#1798) (#1834) (#1844) * Introduce the ability to push an rrd binary via iframe.contentWindow.postMessage * New API to output the current buffered messages as a cell in jupyter * Example notebook with the cube demo * Track that we need to send another recording msg after draining the backlog * Dynamically resolve the app location based on git commit. Allow override to use self-hosted assets * Add some crude timeout logic in case the iframe fails to load * Don't persist app state in notebooks * Introduce new MemoryRecording for use with Jupyter notebooks (#1834) * Refactor the relationship between the assorted web / websocket servers (#1844) * Rename RemoteViewerServer to WebViewerSink * CLI arguments for specifying ports * Proper typing for the ports * Disable wheel tests for x86_64-apple-darwin (#1853) * Fix typos in notebook readme (#1852) * Fix the python build when running without web_viewer enabled (#1856) * Error instead of expect inside msg_encode. (#1857) * Restore: New API to reset_time (#1826) (#1854) * Revert "Implement billinear filtering of textures (#1850)" (#1859) This reverts commit d33dab6e7a33f82ab2513058d0f85744e3ce6ef4. * Add Restart command and keyboard shortcut for moving time to start of timeline (#1802) * Add Restart button to timeline UI. This sets the timeline back to zero. * Adds Restart Command & Timeline Command * Adds Ctrl-Shift-Space keyboard modifier * Remove restart button from timeline UI. * Use cmd(Key::LeftArrow) as key combo for restart timeline. * Add TimeControl::restart to restart the current timeline. * Use this from the kb_shortcut function * fix some code nits --------- Co-authored-by: Emil Ernerfeldt * Fix shutdown race condition in `re_sdk_comms` client (#1861) * Wait for encoder to shut down before shutting down the other threads * Remove unused dependencies (#1863) * Gpu picking for depth clouds (#1849) * wip * allow for hovering depth clouds via gpu picking * Use `[x, y]: [u32; 2]` as argument --------- Co-authored-by: Emil Ernerfeldt * Remove manual depth projection from car and nyud examples (#1869) * Remove manual depth projection from car and nyud examples * revert change to cube.ipynb * revert changes to cube.ipynb * third times a charm for cube.ipynb * Improve end-to-end testing slightly (#1862) * CI: Run e2e tests with RUST_LOG=debug * Move installing of pip packaged from CI to e2e script * Re-enable bilinear interpolation again (#1860) * Revert "Revert "Implement billinear filtering of textures (#1850)" (#1859)" This reverts commit 625d2bdd241c09ff9d0ae394ba91565fa48455ec. * Split rectangle.wgsl into fragme/vertex parts to work around naga bug * Use GPU colormapping when showing images in the GUI (#1865) * Cleanup: move Default close to the struct definition * Simplify code: use if-let-else-return * Simplify code: no need for Arc * Add EntityDataUi so that the Tensor ui function knows entity path * Better naming: selection -> item * Simplify code: no optional tensor stats * Less use of anyhow * Use GPU colormapping when showing tensors in GUI * Link to issue * Optimize pad_to_four_elements for debug builds * Refactor: simpler arguments to show_zoomed_image_region_area_outline * Fix missing meter argument * Refactor: break up long function * Less use of Arc * Pipe annotation context to the hover preview * Simplify `AnnotationMap::find` * Use new GPU colormapper for the hover-zoom-in tooltip * Refactor * Add helper function for turning a Tensor into an image::DynamicImage * Fix warning on web builds * Add helper function `Tensor::could_be_dynamic_image` * Implement click-to-copy and click-to-save for tensors without egui * Convert histogram to the new system * Remove the TensorImageCache * Fix TODO formatting * bug fixes and cleanups * Rename some stuff * Build-fix * Simplify some code * Turn off benchmakrs comment on each PR (#1872) * Refactor: remove `GpuTexture2DHandle::invalid` (#1866) * Refactor TexturedRect * Remove GpuTexture2DHandle::invalid * `GpuTexture2DHandle` is always valid * spacing * Update enumflags2 to non-yanked version (#1874) * Update enumflags2 to non-yanked version ``` ❯ cargo update -p enumflags2 Updating crates.io index Updating enumflags2 v0.7.5 -> v0.7.7 Updating enumflags2_derive v0.7.4 -> v0.7.7 Updating proc-macro2 v1.0.47 -> v1.0.56 Updating quote v1.0.21 -> v1.0.26 Adding syn v2.0.15 ``` Unfortunately this adds the syn v2 dependency for some platforms * Updating dependencies is a valid label * cargo deny: check more platforms * fix stuff broken from merging upstream --------- Co-authored-by: Clement Rey Co-authored-by: benjamin de charmoy Co-authored-by: Andreas Reich Co-authored-by: Emil Ernerfeldt Co-authored-by: Pablo Vela Co-authored-by: Nikolaus West Co-authored-by: Jeremy Leibs Co-authored-by: h3mosphere <129932586+h3mosphere@users.noreply.github.com> Co-authored-by: Urho Laukkarinen * expose depth config, get available sensor resolutions for the selected device * move removing entities to a place where the removal will always be tried, not just when expanding space view header * added depth alignment * added stream enabled buttons * Depth cloud textures are now cached frame-to-frame (#1913) * Depth cloud textures are now cached frame-to-frame Simplified logic a bit by enforcing F32 texture conversion (there was a u16 path for native only) * doc fix * naming consistency, format check, remove unnecessary scaling * improve depth cloud texture check * fixes after merging * Smooth out scroll wheel input for camera zooming (#1920) * Always spawn instead of fork in multiprocessing example (#1922) * Add `--num-frames` arg to canny (webcam) example (#1923) * fix formatting issues * rerun format fix * fix spelling * lint check fixes * mypy * some more lint fixes * some more fixes for python lint checks * Collect extra egui features into the main Cargo.toml (#1926) * just rs-run-all * `just py-run-all-{native|web|rrd}` (#1927) * make all python examples handle unknown arguments gracefully * just py-run-all-{native|web|rrd} * bump version * comment out clang * Join threads at end of multi-threading example (#1934) * Add argument parsing to the rerun_demo (#1925) * More robust wait for exit condition during .serve() (#1939) * More robust wait for exit condition during .serve() * lint * Use zipfile python library instead of `unzip` command in arkitscene (#1936) * Use zipfile python library instead of `unzip` command in arkitscene Windows doesn't have unzip! * Nit: import sort order --------- Co-authored-by: Nikolaus West * Fix annotation images sometimes drawn in the background. (#1933) This caused fairly ugly rendering whenever that happened. Also cleaned up redundant image/textured_rect defintions in spatial scene buildup * Fix backslashes in arkitscene rigid transformation path (#1938) * Fix backslashes in arkitscene rigid transformation path Should be fixed properly by https://github.com/rerun-io/rerun/issues/1937 * Use PosixPath instead of .replace("\\", "/") --------- Co-authored-by: Nikolaus West * Fix hover/select highlights when picking single points in a scene with multiple point clouds (#1942) Batch vertex offset for single highlights wasn't correctly computed. Different parts of the code made different assumptions what offsets referred to * Fix hovering depth clouds (#1943) We didn't add to `scene.primitives.image`. Instead of adding to this list, it is instead now no longer needed for picking since we can very easily query for tensor again. * change python workflow for testing purposes, remove windows and macos wheels * add back one macos so the matrix is valid * 2.5GB before GC kick in on web (#1944) * change name to depthai-viewer * change pip install/uninstalls from rerun-sdk to depthai-viewer * change all occurances of rerun-sdk to depthai-viewer * change windows runner to windows-latest for now when using my personal gh * Release `0.5.0` (#1919) * changelog * 0.5.0-alpha.0 * more changelog * re_format: fix implicit recursive feature flags * publish_crates.sh: fix crate ordering * Join threads at end of multi-threading example (#1934) * Add argument parsing to the rerun_demo (#1925) * More robust wait for exit condition during .serve() (#1939) * More robust wait for exit condition during .serve() * lint * Use zipfile python library instead of `unzip` command in arkitscene (#1936) * Use zipfile python library instead of `unzip` command in arkitscene Windows doesn't have unzip! * Nit: import sort order --------- Co-authored-by: Nikolaus West * Fix annotation images sometimes drawn in the background. (#1933) This caused fairly ugly rendering whenever that happened. Also cleaned up redundant image/textured_rect defintions in spatial scene buildup * Fix backslashes in arkitscene rigid transformation path (#1938) * Fix backslashes in arkitscene rigid transformation path Should be fixed properly by https://github.com/rerun-io/rerun/issues/1937 * Use PosixPath instead of .replace("\\", "/") --------- Co-authored-by: Nikolaus West * changelog * Fix hover/select highlights when picking single points in a scene with multiple point clouds (#1942) Batch vertex offset for single highlights wasn't correctly computed. Different parts of the code made different assumptions what offsets referred to * changelog * Fix hovering depth clouds (#1943) We didn't add to `scene.primitives.image`. Instead of adding to this list, it is instead now no longer needed for picking since we can very easily query for tensor again. * changelog * 2.5GB before GC kick in on web (#1944) * changelog * 0.5.0 --------- Co-authored-by: Jeremy Leibs Co-authored-by: Andreas Reich Co-authored-by: Nikolaus West * Fix imu plots scrolling past their container * fix bottom/top panel sizing after showing the spinner on config setting * Lint error names in `map_err` (#1948) * Lint: Properly name errors in `map_err` * Use correct names for errors * fix config and stats tabs not being able to be viewed at the same time, renamed stats to IMU * New dispatch-only workflow for running the lint-job (#1950) * Fix secret in dispatch_lint.yml * Only maintain a single manual-dispatch job for testing workflows * apply button, have to make it look a bit nicer, but will do that when I fix scrolling in device configuration panel * Bump hyper version due to RUSTSEC-2023-0034 (#1951) * Add other build parameterizations to manual_dispatch.yml * Use proper if gates on the manual_dispatch.yml jobs * Add ability to save cache to manual_disaptch.yml * Standard case of inputs * Add manual step for packaging to 'manual_dispatch.yml' * add back panels when the underlying subscription reappears * Fix crash for missing class ids causing zero sized texture (#1947) * Fix crash for missing class ids causing zero sized texture Two things fixed actually: * texture manager now checks for zero sized texture, this ripples out in a lot more error handling * class id texture texture handles not having any classes gracefully * Use Display for all errors * typo * Better naming of error * Better docs and names * Fix off-by-one error * some use of `context`, change which error is implicitly converted on texture manager2d --------- Co-authored-by: Emil Ernerfeldt * fixes LR stream subscriptions (maybe breaks panels reappearing after sub is gone) (#15) * make config ui scrollable, fix padding * Move clippy_wasm/clippy.toml to under scripts (#1949) * Move clippy_wasm/clippy.toml to under scripts This is just to clean up the root a bit, and to move it closer to where it is actually used. * Fix comment typo --------- Co-authored-by: Andreas Reich * change crate version to 0.6.0-alpha.0 (#1952) * New workflow_dispatch for building wheels for a PR * initial light mode, luxonis depthai viewer * Rename build_wheels_for_pr.yml -> manual_build_wheels_for_pr.yml * Fix run-wasm crash on trying to wait for server (#1959) This ruined my dev experience for re_renderer examples a bit. Not sure what made the previous hack stop working, might be a timing issue. It ended up crashing the `cargo_run_wasm` web server * Update egui to latest and wgpu to 0.16 (#1958) * update to wgpu 0.16 and egui using this version * shader fixup for type aliases and rectangle shader * shader signed/unsigned shenanigans * more signed/unsigned issues * fix texture component count * fix picking layer depth readback crash on web * patch wgpu * better texture size estimate * fix patches * Handle leaking of prerelease into alpha version (#1953) * Make device config panel remember it's height throughout loading * hide time panel, make ai model dropdown wider * Fix incorrect memory usage stats for destroyed on-creation-mapped buffers (#1963) We actually don't have anywhere where we discard this kind of buffer yet, but if we would the stats would be wrong (noticed while doing quick & dirty experiments on the staging belt) * New manual workflow for running benches * Introduce new reusable workflow jobs and cleanup manual trigger (#1954) There are 8 reusable workflow "components" that we can use to build different scenarios: reusable_checks.yml - These are all the checks that run to ensure the code is formatted, reusable_bench.yml - This job runs the benchmarks to check for performance regressions. reusable_deploy_docs- This job deploys the python and rust documentation to https://ref.rerun.io reusable_build_and_test_wheels.yml - This job builds the wheels, runs the end-to-end test, and produces a sample RRD. The artifacts are accessible via GitHub artifacts, but not otherwise uploaded anywhere. reusable_upload_wheels.yml- This job uploads the wheels to google cloud reusable_build_web.yml - This job builds the wasm artifacts for the web. reusable_upload_web.yml - This job uploads the web assets to google cloud. By default this only uploads to: app.rerun.io/commit// reusable_pr_summary.yml - This job updates the PR summary with the results of the CI run. Example summary can be found at: https://storage.googleapis.com/rerun-builds/pull_request/1954/index.html This also introduces a manual_dispatch.yml helper as a convenience for testing these workflows and their different parameterizations. * New manual workflow for adhoc web builds * Use new CI workflows for pull-request and merge to main (#1955) on_pull_request.yml includes the following pieces: - reusable_checks.yml -- Run all of the lints, code-formatting, tests, etc. - reusable_build_and_test_wheels.yml -- Configured in a "minimal" mode with SDK includes end-to-end test and produces an rrd. - reusable_build_web.yml -- Verifies we can build the wasm - reusable_upload_web.yml -- Uploads the RRD and Wasm to app.rerun.io to confirm the demo works as well as support notebook testing. - reusable_pr_summary.yml -- Create a manifest page with a link to the on_push_main.yml includes the following pieces: - reusable_checks.yml -- Run all of the lints, code-formatting, tests, etc. - reusable_bench.yml -- Run the benchmarks - reusable_build_and_test_wheels.yml -- Builds wheels for all platforms - reusable_upload_wheel.yml -- Uploads the all the wheels to gcloud - reusable_build_web.yml -- Builds the wasm bundle - reusable_upload_web.yml -- Uploads the RRD and Wasm to app.rerun.io - reusable_pip_index.yml -- Generates a pip index page which can be used to install packages with, e.g. * Fix name of on_push_main.yml * Fix usage of long commit in generate_prerelease_pip_index.py * Try making pull-request workflows non-concurrent (#1970) * Try making pull-request workflows non-concurrent * Concurrency groups for push_main as well * Each sub-workflow needs its own name or they fight * Another attempt to make jobs non-concurrent on a per-PR basis (#1974) * Another attempt to make jobs non-concurrent on a per-PR basis * Move concurrency into the reusable job * Jobs with duplicated instances still need separate concurrency keys based on platform * Round to nearest color_index when doing color mapping (#1969) * Full (experimental) WebGPU support (#1965) * always build with unstable web sys apis * Make shader Tint friendly * expose webgl feature flag on re_renderer & re_viewer * fix bug link on negative hexadecimal * hardware tier is now created from wgpu adapter * sort out build flags for webgpu & document building webviewer * introduce shader text replacement workarounds to workaround current chrome issue * latest egui master * typo fix * doc fixes, use if cfg! instead of attribute cfg * move backend to rerun * If there's a `{{ pr-build-summary }}` in the PR description, update it. (#1971) * If there's a `{{ pr-build-summary }}` in the PR description, update it. * Add comment to the PR template * Add pull-requests permission to pr_summary job * Run the cube notebook on PR (#1972) * Run the cube notebook on PR * Add notebooks to the build summary * Use the new concurrency model * reformat py files * reformat * fix pylint errors * Add ability to manually run a web build to upload to an adhoc name (#1966) * Add ability to manually run a web build to upload to an adhoc name * Pass through ADHOC_NAME * Add a concurrency criteria for the new adhoc job * Make input description more explicit * change entity paths * merged wip albedo colormap into latest depth_cloud * remove pointclouds generated in sdk * fix compiler error, trying to compile frame.close for wasm * Default to albedo texture for depth cloud, added support for mono albedo textures * restart backend on failure, added oak_cam.device.close seems to really close the cam * py lint fix * don't run notebooks * remove run-notebook dependency --------- Co-authored-by: Clement Rey Co-authored-by: benjamin de charmoy Co-authored-by: Andreas Reich Co-authored-by: Emil Ernerfeldt Co-authored-by: Pablo Vela Co-authored-by: Nikolaus West Co-authored-by: Jeremy Leibs Co-authored-by: h3mosphere <129932586+h3mosphere@users.noreply.github.com> Co-authored-by: Urho Laukkarinen Co-authored-by: Nikolaus West * Fix rerun lints * rename binary, always start with memory-limit, pin depthai dependencies, fix errors after changing dependency versions * remove compiler warnings * fix memory leak when app is in background * Custom viewport UI (#4) * custom blueprint panel, show logical space views for depthai-viewer users, added settings clog on top of space view tab to configure what is visible * small fixes, clear entity_paths every time to avoid displaying an unavailable entity_path in space view options ui * custom left panel to add or remove space view instances, created a new default viewport layout. Improved behaviour when a panel re appears after user selected to hide it, then if stream stops and starts again the panel will be spawned back in correctly. * improve auto layout to not split when only 3D or 2D view is available * MJPEG encode image frames if connected to a PoE device. Only add magnetometer to imu sensors list if the device has a BNO IMU. Lower the memory limit to 100MB * Styling (#6) * initial styling impl * make buttons that should be small, small * Runtime depth config and fix device selection ui * comment * Proper runtime depth config updates * switch to Yolo v8 * add comma to label for non open zoo models * split 2d + 3d cam view vertically instead of horizontally * Tabify all panels (except for blueprint) (#7) * initial styling impl * make buttons that should be small, small * Runtime depth config and fix device selection ui * comment * Proper runtime depth config updates * switch to Yolo v8 * add comma to label for non open zoo models * split 2d + 3d cam view vertically instead of horizontally * Make the UI more configurable by converting the right panel into tabs. TODO: UX while laying out the panels. When a new space view appears only update the viewport layout, try to keep the user configured fixed function panels as they were. Just handle it in a way that is intuitive * remove bottom panel, switch to RAW imu sensors * XLink statistics initial implementation * initial xlink throughput statistics impllementation, have to glow it up a bit and maybe clean up the code * Xlink and rerun rename (#9) * Rename rerun py library to depthai_viewer * bug fixes and started working on a smart auto layout that operates on an existing tree, to preserve ui as much as possible, while also creating good layouts * auto layout * Fix maximize not working and add maximize for Stats tab * delete profiling stuff that shouldn't have been commited * mostly fix smart layout, TODO: detect when you can group mono and color 3d + 2d views into a 4 way split * add docstring for update_tree * WIP auto layout can_create_mono_quad checker, not at all finished yet * pass lint checks and bugfixes * forgot to sort imports * Fix mypy lint (TODO: Proper typing, especially in the comms from back to store to ws) other types are pretty solid * pylints and fix Queue typehinting * fix doc build * try to pass pylints with py.typed * ignore misc mypy errors * forgot to run black formatter * switch back to old ci --------- Co-authored-by: Clement Rey Co-authored-by: benjamin de charmoy Co-authored-by: Andreas Reich Co-authored-by: Emil Ernerfeldt Co-authored-by: Pablo Vela Co-authored-by: Nikolaus West Co-authored-by: Jeremy Leibs Co-authored-by: h3mosphere <129932586+h3mosphere@users.noreply.github.com> Co-authored-by: Urho Laukkarinen Co-authored-by: Nikolaus West --- .cargo/config.toml | 16 + .github/pull_request_template.md | 9 +- .github/workflows/labels.yml | 2 +- .github/workflows/python.yml | 364 +---- .github/workflows/rust.yml | 193 +-- .github/workflows/toml.yml | 2 +- .github/workflows/typos.yml | 17 - .mypy.ini | 2 +- .vscode/extensions.json | 17 +- .vscode/settings.json | 153 +- ARCHITECTURE.md | 4 +- BUILD.md | 36 +- CHANGELOG.md | 348 +---- Cargo.lock | 503 ++++--- Cargo.toml | 77 +- README.md | 48 +- RELEASES.md | 202 +-- api.py | 50 + ci_docker/Dockerfile | 3 +- clippy.toml | 4 +- crates/re_analytics/src/cli.rs | 2 +- crates/re_analytics/src/lib.rs | 2 +- crates/re_build_info/src/crate_version.rs | 18 +- crates/re_build_web_viewer/src/lib.rs | 9 +- crates/re_build_web_viewer/src/main.rs | 7 +- crates/re_data_store/src/entity_properties.rs | 31 +- crates/re_format/Cargo.toml | 2 +- crates/re_log_types/src/arrow_msg.rs | 6 +- .../component_types/arrow_convert_shims.rs | 1 + .../re_log_types/src/component_types/imu.rs | 25 + .../re_log_types/src/component_types/mod.rs | 12 +- .../src/component_types/node_graph.rs | 40 + .../src/component_types/tensor.rs | 2 +- .../src/component_types/xlink_stats.rs | 28 + crates/re_log_types/src/lib.rs | 4 +- crates/re_memory/src/memory_limit.rs | 11 +- crates/re_renderer/Cargo.toml | 18 +- crates/re_renderer/examples/2d.rs | 23 +- crates/re_renderer/examples/depth_cloud.rs | 94 +- crates/re_renderer/examples/framework.rs | 3 +- crates/re_renderer/shader/colormap.wgsl | 10 +- crates/re_renderer/shader/depth_cloud.wgsl | 33 +- crates/re_renderer/shader/generic_skybox.wgsl | 2 +- crates/re_renderer/shader/lines.wgsl | 17 +- crates/re_renderer/shader/point_cloud.wgsl | 9 +- crates/re_renderer/shader/rectangle_fs.wgsl | 10 +- .../shader/screen_triangle_vertex.wgsl | 6 +- crates/re_renderer/shader/types.wgsl | 26 +- .../re_renderer/shader/utils/sphere_quad.wgsl | 4 +- .../src/allocator/cpu_write_gpu_read_belt.rs | 11 +- .../src/allocator/gpu_readback_belt.rs | 28 +- crates/re_renderer/src/allocator/mod.rs | 3 +- crates/re_renderer/src/colormap.rs | 6 + crates/re_renderer/src/config.rs | 54 +- crates/re_renderer/src/context.rs | 27 +- crates/re_renderer/src/draw_phases/mod.rs | 3 +- .../src/draw_phases/picking_layer.rs | 40 +- .../re_renderer/src/draw_phases/screenshot.rs | 11 +- crates/re_renderer/src/global_bindings.rs | 4 +- crates/re_renderer/src/importer/gltf.rs | 12 +- crates/re_renderer/src/lib.rs | 1 + .../re_renderer/src/renderer/debug_overlay.rs | 27 +- .../re_renderer/src/renderer/depth_cloud.rs | 294 ++-- crates/re_renderer/src/renderer/lines.rs | 9 +- crates/re_renderer/src/renderer/mod.rs | 4 +- .../re_renderer/src/renderer/point_cloud.rs | 4 +- crates/re_renderer/src/renderer/rectangles.rs | 60 +- .../re_renderer/src/resource_managers/mod.rs | 5 +- .../src/resource_managers/texture_manager.rs | 117 +- crates/re_renderer/src/texture_info.rs | 206 +++ crates/re_renderer/src/view_builder.rs | 25 +- .../wgpu_resources/dynamic_resource_pool.rs | 13 +- crates/re_renderer/src/wgpu_resources/mod.rs | 110 -- .../src/wgpu_resources/sampler_pool.rs | 7 +- .../src/wgpu_resources/shader_module_pool.rs | 23 +- .../src/wgpu_resources/texture_pool.rs | 19 +- crates/re_ui/Cargo.toml | 7 +- crates/re_ui/data/design_tokens.json | 1287 ++++++++++------- crates/re_ui/data/icons/gear.png | Bin 0 -> 612 bytes crates/re_ui/data/icons/rerun_menu.png | Bin 896 -> 11051 bytes crates/re_ui/data/logo_dark_mode.png | Bin 12227 -> 4402 bytes crates/re_ui/data/logo_light_mode.png | Bin 12287 -> 4402 bytes crates/re_ui/src/command.rs | 4 +- crates/re_ui/src/design_tokens.rs | 65 +- crates/re_ui/src/icons.rs | 2 + crates/re_ui/src/lib.rs | 97 ++ crates/re_ui/src/toggle_switch.rs | 64 +- crates/re_viewer/Cargo.toml | 37 +- crates/re_viewer/src/app.rs | 141 +- crates/re_viewer/src/depthai/api.rs | 81 ++ crates/re_viewer/src/depthai/depthai.rs | 822 +++++++++++ crates/re_viewer/src/depthai/mod.rs | 3 + crates/re_viewer/src/depthai/ws.rs | 247 ++++ crates/re_viewer/src/gpu_bridge/mod.rs | 23 +- .../re_viewer/src/gpu_bridge/tensor_to_gpu.rs | 22 +- crates/re_viewer/src/lib.rs | 29 +- crates/re_viewer/src/misc/selection_state.rs | 6 +- crates/re_viewer/src/misc/viewer_context.rs | 3 + crates/re_viewer/src/native.rs | 38 +- crates/re_viewer/src/ui/auto_layout.rs | 492 ++++++- crates/re_viewer/src/ui/blueprint.rs | 11 +- crates/re_viewer/src/ui/bottom_panel/mod.rs | 115 ++ crates/re_viewer/src/ui/data_blueprint.rs | 2 +- crates/re_viewer/src/ui/data_ui/image.rs | 4 +- .../re_viewer/src/ui/device_settings_panel.rs | 397 +++++ crates/re_viewer/src/ui/memory_panel.rs | 4 +- crates/re_viewer/src/ui/mod.rs | 6 +- .../re_viewer/src/ui/selection_history_ui.rs | 49 +- crates/re_viewer/src/ui/selection_panel.rs | 273 ++-- crates/re_viewer/src/ui/space_view.rs | 68 +- .../re_viewer/src/ui/space_view_heuristics.rs | 32 +- crates/re_viewer/src/ui/stats_panel.rs | 335 +++++ .../src/ui/time_panel/data_density_graph.rs | 2 +- crates/re_viewer/src/ui/time_panel/mod.rs | 2 +- crates/re_viewer/src/ui/view_category.rs | 8 +- .../re_viewer/src/ui/view_node_graph/mod.rs | 5 + .../re_viewer/src/ui/view_node_graph/scene.rs | 34 + crates/re_viewer/src/ui/view_node_graph/ui.rs | 91 ++ crates/re_viewer/src/ui/view_spatial/eye.rs | 84 +- .../src/ui/view_spatial/scene/mod.rs | 18 +- .../src/ui/view_spatial/scene/picking.rs | 28 +- .../src/ui/view_spatial/scene/primitives.rs | 22 +- .../view_spatial/scene/scene_part/images.rs | 209 ++- .../src/ui/view_spatial/space_camera_3d.rs | 2 +- crates/re_viewer/src/ui/view_spatial/ui.rs | 115 +- crates/re_viewer/src/ui/view_spatial/ui_3d.rs | 38 +- .../src/ui/view_spatial/ui_renderer_bridge.rs | 6 +- .../src/ui/view_tensor/tensor_slice_to_gpu.rs | 12 +- crates/re_viewer/src/ui/view_tensor/ui.rs | 5 +- .../re_viewer/src/ui/view_time_series/ui.rs | 3 +- crates/re_viewer/src/ui/viewport.rs | 641 ++++---- crates/re_viewer/src/viewer_analytics.rs | 4 +- crates/re_viewer/src/web.rs | 5 +- crates/re_web_viewer_server/build.rs | 20 +- crates/re_web_viewer_server/src/lib.rs | 2 +- crates/re_ws_comms/src/server.rs | 2 +- crates/rerun/Cargo.toml | 7 +- crates/rerun/README.md | 20 +- crates/rerun/src/clap.rs | 2 +- crates/rerun/src/crash_handler.rs | 2 +- crates/rerun/src/lib.rs | 28 +- crates/rerun/src/main.rs | 2 +- crates/rerun/src/native_viewer.rs | 1 + crates/rerun/src/run.rs | 24 +- design/blueprint_store.md | 10 +- examples/.gitignore | 1 + examples/python/README.md | 12 +- examples/python/api_demo/main.py | 171 +-- examples/python/api_demo/requirements.txt | 2 +- .../python/arkitscenes/download_dataset.py | 6 +- examples/python/arkitscenes/main.py | 45 +- examples/python/arkitscenes/requirements.txt | 2 +- examples/python/car/main.py | 25 +- examples/python/car/requirements.txt | 2 +- examples/python/clock/main.py | 33 +- examples/python/clock/requirements.txt | 2 +- examples/python/colmap/main.py | 29 +- examples/python/colmap/requirements.txt | 2 +- examples/python/deep_sdf/main.py | 61 +- examples/python/deep_sdf/requirements.txt | 2 +- examples/python/dicom/main.py | 13 +- examples/python/dicom/requirements.txt | 2 +- examples/python/dna/main.py | 25 +- examples/python/dna/requirements.txt | 2 +- examples/python/minimal/main.py | 14 +- examples/python/minimal/requirements.txt | 2 +- examples/python/mp_pose/main.py | 37 +- examples/python/mp_pose/requirements.txt | 2 +- examples/python/multiprocessing/main.py | 22 +- .../python/multiprocessing/requirements.txt | 2 +- examples/python/multithreading/main.py | 18 +- .../python/multithreading/requirements.txt | 2 +- examples/python/notebook/README.md | 2 +- examples/python/notebook/cube.ipynb | 539 ++++--- examples/python/notebook/requirements.txt | 2 +- examples/python/nyud/main.py | 23 +- examples/python/nyud/requirements.txt | 2 +- examples/python/objectron/main.py | 35 +- examples/python/objectron/requirements.txt | 2 +- examples/python/opencv_canny/main.py | 30 +- examples/python/opencv_canny/requirements.txt | 2 +- examples/python/plots/main.py | 29 +- examples/python/plots/requirements.txt | 2 +- examples/python/raw_mesh/main.py | 17 +- examples/python/raw_mesh/requirements.txt | 2 +- examples/python/ros/main.py | 34 +- examples/python/ros/requirements.txt | 2 +- examples/python/ros/rerun_urdf.py | 6 +- examples/python/segment_anything/main.py | 27 +- .../python/segment_anything/requirements.txt | 2 +- .../stable_diffusion/huggingface_pipeline.py | 52 +- examples/python/stable_diffusion/main.py | 11 +- .../python/stable_diffusion/requirements.txt | 2 +- examples/python/text_logging/main.py | 17 +- examples/python/text_logging/requirements.txt | 2 +- examples/python/tracking_hf_opencv/main.py | 42 +- .../tracking_hf_opencv/requirements.txt | 2 +- examples/rust/api_demo/Cargo.toml | 2 +- examples/rust/api_demo/src/main.rs | 8 +- examples/rust/dna/Cargo.toml | 2 +- examples/rust/dna/src/main.rs | 6 +- examples/rust/minimal/Cargo.toml | 2 +- examples/rust/minimal/src/main.rs | 4 +- examples/rust/minimal_options/Cargo.toml | 2 +- examples/rust/minimal_options/src/main.rs | 10 +- examples/rust/objectron/Cargo.toml | 2 +- examples/rust/objectron/src/main.rs | 18 +- examples/rust/raw_mesh/Cargo.toml | 2 +- examples/rust/raw_mesh/src/main.rs | 16 +- justfile | 36 +- rerun_py/Cargo.toml | 14 +- rerun_py/README.md | 53 +- .../{rerun => depthai_viewer}/__init__.py | 4 +- rerun_py/docs/gen_common_index.py | 28 +- rerun_py/docs/gen_package_index.py | 2 +- rerun_py/pyproject.toml | 24 +- rerun_py/requirements-lint.txt | 1 + .../{rerun => depthai_viewer}/__init__.py | 59 +- .../{rerun => depthai_viewer}/__main__.py | 2 +- .../depthai_viewer/_backend/.gitignore | 5 + .../depthai_viewer/_backend/README.md | 31 + .../_backend/__init__.py} | 0 .../_backend/classification_labels.py | 107 ++ .../depthai_viewer/_backend/config_api.py | 186 +++ .../depthai_viewer/_backend/depth.py | 71 + .../_backend/device_configuration.py | 190 +++ .../rerun_sdk/depthai_viewer/_backend/main.py | 360 +++++ .../depthai_viewer/_backend/py.typed | 0 .../depthai_viewer/_backend/pyproject.toml | 12 + .../depthai_viewer/_backend/sdk_callbacks.py | 163 +++ .../depthai_viewer/_backend/store.py | 53 + .../depthai_viewer/_backend/topic.py | 24 + .../color_conversion.py | 0 .../components/__init__.py | 4 +- .../components/annotation.py | 2 +- .../components/arrow.py | 4 +- .../components/box.py | 2 +- .../components/color.py | 4 +- .../depthai_viewer/components/imu.py | 38 + .../components/instance.py | 2 +- .../components/label.py | 2 +- .../components/linestrip.py | 2 +- .../components/point.py | 2 +- .../components/quaternion.py | 2 +- .../components/radius.py | 2 +- .../components/rect2d.py | 2 +- .../components/scalar.py | 2 +- .../components/tensor.py | 4 +- .../components/text_entry.py | 2 +- .../components/vec.py | 2 +- .../depthai_viewer/components/xlink_stats.py | 27 + .../{rerun => depthai_viewer}/log/__init__.py | 3 +- .../log/annotation.py | 6 +- .../{rerun => depthai_viewer}/log/arrow.py | 18 +- .../log/bounding_box.py | 26 +- .../{rerun => depthai_viewer}/log/camera.py | 4 +- .../log/error_utils.py | 6 +- .../log/extension_components.py | 12 +- .../{rerun => depthai_viewer}/log/file.py | 4 +- .../{rerun => depthai_viewer}/log/image.py | 8 +- rerun_py/rerun_sdk/depthai_viewer/log/imu.py | 56 + .../{rerun => depthai_viewer}/log/lines.py | 16 +- .../log/log_decorator.py | 8 +- .../{rerun => depthai_viewer}/log/mesh.py | 6 +- .../depthai_viewer/log/pipeline_graph.py | 60 + .../{rerun => depthai_viewer}/log/points.py | 22 +- .../{rerun => depthai_viewer}/log/rects.py | 20 +- .../{rerun => depthai_viewer}/log/scalar.py | 18 +- .../{rerun => depthai_viewer}/log/tensor.py | 12 +- .../{rerun => depthai_viewer}/log/text.py | 18 +- .../log/text_internal.py | 10 +- .../log/transform.py | 8 +- .../depthai_viewer/log/xlink_stats.py | 22 + rerun_py/rerun_sdk/depthai_viewer/py.typed | 0 .../{rerun => depthai_viewer}/recording.py | 4 +- .../script_helpers.py | 32 +- rerun_py/rerun_sdk/rerun_demo/__init__.py | 8 +- rerun_py/rerun_sdk/rerun_demo/__main__.py | 42 +- rerun_py/rerun_sdk/rerun_demo/data.py | 2 +- rerun_py/src/arrow.rs | 4 +- rerun_py/src/python_bridge.rs | 14 +- rerun_py/src/python_session.rs | 22 +- rerun_py/tests/unit/api_tests.py | 10 +- rerun_py/tests/unit/test_color_conversion.py | 2 +- run_wasm/Cargo.toml | 2 +- run_wasm/src/main.rs | 19 +- scripts/cargo_deny.sh | 19 + scripts/clippy_wasm.sh | 4 +- .../clippy_wasm}/clippy.toml | 0 scripts/generate_changelog.py | 4 +- scripts/generate_pr_summary.py | 107 ++ scripts/generate_prerelease_pip_index.py | 71 + scripts/lint.py | 8 +- scripts/publish_crates.sh | 2 +- scripts/run_python_e2e_test.py | 10 +- scripts/templates/pip_index.html | 42 + scripts/templates/pr_results_summary.html | 65 + scripts/version_util.py | 34 +- tests/rust/test_image_memory/Cargo.toml | 2 +- tests/rust/test_image_memory/src/main.rs | 12 +- web_viewer/manifest.json | 2 +- 301 files changed, 9525 insertions(+), 4255 deletions(-) delete mode 100644 .github/workflows/typos.yml create mode 100644 api.py create mode 100644 crates/re_log_types/src/component_types/imu.rs create mode 100644 crates/re_log_types/src/component_types/node_graph.rs create mode 100644 crates/re_log_types/src/component_types/xlink_stats.rs create mode 100644 crates/re_renderer/src/texture_info.rs create mode 100644 crates/re_ui/data/icons/gear.png create mode 100644 crates/re_viewer/src/depthai/api.rs create mode 100644 crates/re_viewer/src/depthai/depthai.rs create mode 100644 crates/re_viewer/src/depthai/mod.rs create mode 100644 crates/re_viewer/src/depthai/ws.rs create mode 100644 crates/re_viewer/src/ui/bottom_panel/mod.rs create mode 100644 crates/re_viewer/src/ui/device_settings_panel.rs create mode 100644 crates/re_viewer/src/ui/stats_panel.rs create mode 100644 crates/re_viewer/src/ui/view_node_graph/mod.rs create mode 100644 crates/re_viewer/src/ui/view_node_graph/scene.rs create mode 100644 crates/re_viewer/src/ui/view_node_graph/ui.rs create mode 100644 examples/.gitignore rename rerun_py/{rerun => depthai_viewer}/__init__.py (89%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/__init__.py (88%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/__main__.py (74%) create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/.gitignore create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/README.md rename rerun_py/rerun_sdk/{rerun/py.typed => depthai_viewer/_backend/__init__.py} (100%) create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/classification_labels.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/config_api.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/depth.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/device_configuration.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/main.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/py.typed create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/pyproject.toml create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/sdk_callbacks.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/store.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/_backend/topic.py rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/color_conversion.py (100%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/__init__.py (97%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/annotation.py (94%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/arrow.py (88%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/box.py (90%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/color.py (84%) create mode 100644 rerun_py/rerun_sdk/depthai_viewer/components/imu.py rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/instance.py (91%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/label.py (89%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/linestrip.py (96%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/point.py (95%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/quaternion.py (90%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/radius.py (89%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/rect2d.py (97%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/scalar.py (94%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/tensor.py (97%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/text_entry.py (92%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/components/vec.py (94%) create mode 100644 rerun_py/rerun_sdk/depthai_viewer/components/xlink_stats.py rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/__init__.py (98%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/annotation.py (97%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/arrow.py (83%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/bounding_box.py (81%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/camera.py (94%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/error_utils.py (87%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/extension_components.py (92%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/file.py (96%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/image.py (96%) create mode 100644 rerun_py/rerun_sdk/depthai_viewer/log/imu.py rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/lines.py (92%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/log_decorator.py (86%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/mesh.py (97%) create mode 100644 rerun_py/rerun_sdk/depthai_viewer/log/pipeline_graph.py rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/points.py (93%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/rects.py (91%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/scalar.py (90%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/tensor.py (91%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/text.py (87%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/text_internal.py (90%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/log/transform.py (96%) create mode 100644 rerun_py/rerun_sdk/depthai_viewer/log/xlink_stats.py create mode 100644 rerun_py/rerun_sdk/depthai_viewer/py.typed rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/recording.py (97%) rename rerun_py/rerun_sdk/{rerun => depthai_viewer}/script_helpers.py (78%) create mode 100755 scripts/cargo_deny.sh rename {clippy_wasm => scripts/clippy_wasm}/clippy.toml (100%) create mode 100644 scripts/generate_pr_summary.py create mode 100644 scripts/generate_prerelease_pip_index.py create mode 100644 scripts/templates/pip_index.html create mode 100644 scripts/templates/pr_results_summary.html diff --git a/.cargo/config.toml b/.cargo/config.toml index b44a938e2904..3bd9e9b437d3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,3 +7,19 @@ run-wasm = "run --release --package run_wasm --" # Some of our build.rs files only run if this is set, # so that we don't run them on cargo publish or on users machines. IS_IN_RERUN_WORKSPACE = "yes" + + +# [target.x86_64-unknown-linux-gnu] +# linker = "clang" +# rustflags = [ +# "-C", +# "link-arg=-fuse-ld=/usr/bin/mold", +# "-C", +# "split-debuginfo=unpacked", +# ] +# web_sys_unstable_apis is required to enable the web_sys clipboard API which egui_web uses, +# https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html +# as well as WebGPU apis. +# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html +[target.wasm32-unknown-unknown] +rustflags = ["--cfg=web_sys_unstable_apis"] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 99f0096817a2..f2e91a7e8adf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -13,5 +13,10 @@ To get an auto-generated PR description you can put "copilot:summary" or "copilo ### What ### Checklist -* [ ] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) -* [ ] I've included a screenshot or gif (if applicable) + +- [ ] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) +- [ ] I've included a screenshot or gif (if applicable) + + + +PR Build Summary: {{ pr-build-summary }} diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index aed2272b2b4b..979dccb13717 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -29,4 +29,4 @@ jobs: with: mode: minimum count: 1 - labels: "📊 analytics, 🪳 bug, 🧑‍💻 dev experience, dependencies, 📖 documentation, 💬 discussion, examples, 📉 performance, 🐍 python API, ⛃ re_datastore, 📺 re_viewer, 🔺 re_renderer, 🚜 refactor, ⛴ release, 🦀 rust SDK, 🔨 testing, ui, 🕸️ web" + labels: "📊 analytics, 🪳 bug, 🧑‍💻 dev experience, 📖 documentation, examples, 📉 performance, 🐍 python API, ⛃ re_datastore, 📺 re_viewer, 🔺 re_renderer, ⛴ release, 🦀 rust SDK, 🔨 testing, ui, 🕸️ web" diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 491c5353550a..36cc25318920 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -8,19 +8,12 @@ on: - "main" tags: - "v*.*.*" # on release tag - workflow_dispatch: - inputs: - force_build_wheel: - description: "Build python wheels" - required: true - default: false - type: boolean env: PYTHON_VERSION: "3.8" PRE_RELEASE_INSTRUCTIONS: | ## Installing the pre-release Python SDK - 1. Download the correct `.whl`. + 1. Download the correct `.whl`. For Mac M1/M2, grab the "universal2" `.whl` 2. Run `pip install rerun_sdk<...>.whl` (replace `<...>` with the actual filename) 3. Test it: `rerun --version` UBUNTU_REQUIRED_PKGS: libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libfontconfig1-dev libatk-bridge2.0 libfreetype6-dev libglib2.0-dev @@ -63,163 +56,11 @@ jobs: just py-requirements # --------------------------------------------------------------------------- - # We need one wheel-build to be special so the other builds (namely mac arm) can use its rrd - # This copy-paste is awful, but we'll refactor the build soon. - wheels-linux: - if: github.event_name == 'push' || github.event.inputs.force_build_wheel - name: Build Python Wheels (Linux) - runs-on: ubuntu-latest - container: - image: rerunio/ci_docker:0.6 - steps: - - uses: actions/checkout@v3 - - # These should already be in the docker container, but run for good measure. A no-op install - # should be fast, and this way things don't break if we add new packages without rebuilding - # docker - - name: Cache APT Packages - uses: awalsh128/cache-apt-pkgs-action@v1.2.2 - with: - packages: ${{ env.UBUNTU_REQUIRED_PKGS }} - version: 2.0 # Increment this to pull newer packages - execute_install_scripts: true - - - name: Set up cargo cache - uses: Swatinem/rust-cache@v2 - with: - env-vars: CARGO CC CFLAGS CXX CMAKE RUST CACHE_KEY - # Don't update the cache -- it will be updated by the lint job - # TODO(jleibs): this job will likely run before rust.yml updates - # the cache. Better cross-job sequencing would be nice here - save-if: False - - # These should already be in the docker container, but run for good measure. A no-op install - # should be fast, and this way things don't break if we add new packages without rebuilding - # docker - - run: pip install -r rerun_py/requirements-build.txt - - # ---------------------------------------------------------------------------------- - - - name: Patch Cargo.toml for pre-release - if: github.ref == 'refs/heads/main' - # After patching the pre-release version, run cargo update. - # This updates the cargo.lock file with the new version numbers and keeps the wheel build from failing - run: | - python3 scripts/version_util.py --patch_prerelease - cargo update -w - - - name: Version check for tagged-release - if: startsWith(github.ref, 'refs/tags/v') - # This call to version_util.py will assert version from Cargo.toml matches git tagged version vX.Y.Z - run: | - python3 scripts/version_util.py --check_version - - - name: Store the expected version - # Find the current cargo version and store it in the GITHUB_ENV var: `expected_version` - shell: bash - run: | - echo "expected_version=$(python3 scripts/version_util.py --bare_cargo_version)" >> $GITHUB_ENV - - - name: Build Wheel - uses: PyO3/maturin-action@v1 - with: - maturin-version: "0.14.10" - manylinux: manylinux_2_31 - container: off - command: build - args: | - --manifest-path rerun_py/Cargo.toml - --release - --target x86_64-unknown-linux-gnu - --no-default-features - --features pypi - --out pre-dist - - - name: Install wheel dependencies - # First we install the dependencies manually so we can use `--no-index` when installing the wheel. - # This needs to be a separate step for some reason or the following step fails - # TODO(jleibs): pull these deps from pyproject.toml - # TODO(jleibs): understand why deps can't be installed in the same step as the wheel - shell: bash - run: | - pip install deprecated numpy>=1.23 pyarrow==10.0.1 - - - name: Install built wheel - # Now install the wheel using a specific version and --no-index to guarantee we get the version from - # the pre-dist folder. Note we don't use --force-reinstall here because --no-index means it wouldn't - # find the dependencies to reinstall them. - shell: bash - run: | - pip uninstall rerun-sdk - pip install rerun-sdk==${{ env.expected_version }} --no-index --find-links pre-dist - - - name: Verify built wheel version - shell: bash - run: | - python3 -m rerun --version - which rerun - rerun --version - - - name: Run unit tests - shell: bash - run: cd rerun_py/tests && pytest - - - name: Run e2e test - shell: bash - run: RUST_LOG=debug scripts/run_python_e2e_test.py --no-build # rerun-sdk is already built and installed - - - name: Unpack the wheel - shell: bash - run: | - mkdir unpack-dist - wheel unpack pre-dist/*.whl --dest unpack-dist - - - name: Get the folder name - shell: bash - run: | - echo "pkg_folder=$(ls unpack-dist)" >> $GITHUB_ENV - - - name: Cache RRD dataset - id: dataset - uses: actions/cache@v3 - with: - path: examples/python/colmap/dataset/ - # TODO(jleibs): Derive this key from the invocation below - key: colmap-dataset-colmap-fiat-v0 - - - name: Generate Embedded RRD file - shell: bash - # If you change the line below you should almost definitely change the `key:` line above by giving it a new, unique name - run: | - mkdir rrd - pip install -r examples/python/colmap/requirements.txt - python3 examples/python/colmap/main.py --dataset colmap_fiat --resize 800x600 --save rrd/colmap_fiat.rrd - cp rrd/colmap_fiat.rrd unpack-dist/${{ env.pkg_folder }}/rerun_sdk/rerun_demo/colmap_fiat.rrd - - - name: Repack the wheel - shell: bash - run: | - mkdir dist - wheel pack unpack-dist/${{ env.pkg_folder }} --dest dist/ - - - name: Upload wheels - uses: actions/upload-artifact@v3 - with: - name: wheels - path: dist - - # All platforms are currently creating the same rrd file, upload one of them - - name: Save RRD artifact - uses: actions/upload-artifact@v3 - with: - name: rrd - path: rrd - # --------------------------------------------------------------------------- matrix-setup: # Building all the wheels is expensive, so we only run this job when we push (to main or release tags), - # or if the job was manually triggered with `force_build_wheel` set to true. - if: github.event_name == 'push' || github.event.inputs.force_build_wheel + # or if the PR has the 'build wheels' label for explicit testing of wheels. + if: github.event_name == 'push' || contains( github.event.pull_request.labels.*.name, '🛞 build wheels') runs-on: ubuntu-latest outputs: @@ -234,33 +75,52 @@ jobs: env: JOB_CONTEXT: ${{ toJson(job) }} run: echo "$JOB_CONTEXT" + # Sets TAGGED_OR_MAIN if this workflow is running on a tag or the main branch. + - name: Set TAGGED_OR_MAIN + if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' + run: echo "TAGGED_OR_MAIN=1" >> $GITHUB_ENV - id: set-matrix shell: bash - # TODO(jleibs): figure out why tests are failing to complete on `x86_64-apple-darwin` - # See: https://github.com/rerun-io/rerun/pull/1853 run: | matrix=() - matrix+=('{"platform": "macos", "target": "x86_64-apple-darwin", "run_tests": false, "runs_on": "macos-latest" },') - matrix+=('{"platform": "macos", "target": "aarch64-apple-darwin", "run_tests": false, "runs_on": "macos-latest" },') # NOTE: we can't run tests on arm since our macos runner is x86_64 - matrix+=('{"platform": "windows", "target": "x86_64-pc-windows-msvc", "run_tests": true, "runs_on": "windows-latest-8-cores"},') + + if [[ $TAGGED_OR_MAIN ]]; then + # MacOS build is really slow (>30 mins); uses up a lot of CI minutes + matrix+=('{"platform": "macos", "runs_on": "macos-latest"},') + fi + matrix+=('{"platform": "windows", "runs_on": "windows-latest-8-cores"},') + matrix+=('{"platform": "linux", "runs_on": "ubuntu-latest-16-cores", container: {"image": "rerunio/ci_docker:0.5"}}') echo "Matrix values: ${matrix[@]}" echo "matrix={\"include\":[${matrix[@]}]}" >> $GITHUB_OUTPUT wheels: - name: Build Remaining Python Wheels - needs: [lint, matrix-setup, wheels-linux] + name: Build Python Wheels + needs: [lint, matrix-setup] strategy: matrix: ${{fromJson(needs.matrix-setup.outputs.matrix)}} runs-on: ${{ matrix.runs_on }} + container: ${{ matrix.container }} + steps: - uses: actions/checkout@v3 + # These should already be in the docker container, but run for good measure. A no-op install + # should be fast, and this way things don't break if we add new packages without rebuilding + # docker + - name: Cache APT Packages + if: matrix.platform == 'linux' + uses: awalsh128/cache-apt-pkgs-action@v1.2.2 + with: + packages: ${{ env.UBUNTU_REQUIRED_PKGS }} + version: 2.0 # Increment this to pull newer packages + execute_install_scripts: true + - name: Set up cargo cache uses: Swatinem/rust-cache@v2 with: @@ -273,6 +133,7 @@ jobs: # The pip-cache setup logic doesn't work in the ubuntu docker container # That's probably fine since we bake these deps into the container already - name: Setup python + if: matrix.platform != 'linux' uses: actions/setup-python@v4 with: python-version: ${{ env.PYTHON_VERSION }} @@ -321,11 +182,11 @@ jobs: - name: Patch Cargo.toml for pre-release if: github.ref == 'refs/heads/main' - # After patching the pre-release version, run cargo update. + # After patching the pre-release version, run cargo check. # This updates the cargo.lock file with the new version numbers and keeps the wheel build from failing run: | python3 scripts/version_util.py --patch_prerelease - cargo update -w + cargo check - name: Version check for tagged-release if: startsWith(github.ref, 'refs/tags/v') @@ -333,12 +194,6 @@ jobs: run: | python3 scripts/version_util.py --check_version - - name: Store the expected version - # Find the current cargo version and store it in the GITHUB_ENV var: `expected_version` - shell: bash - run: | - echo "expected_version=$(python3 scripts/version_util.py --bare_cargo_version)" >> $GITHUB_ENV - - name: Build Wheel uses: PyO3/maturin-action@v1 with: @@ -349,42 +204,18 @@ jobs: args: | --manifest-path rerun_py/Cargo.toml --release - --target ${{ matrix.target }} --no-default-features --features pypi + --universal2 --out pre-dist - name: Install built wheel - if: ${{ matrix.run_tests }} - # First we install the dependencies manually so we can use `--no-index` when installing the wheel. - # Then install the wheel using a specific version and --no-index to guarantee we get the version from - # the pre-dist folder. Note we don't use --force-reinstall here because --no-index means it wouldn't - # find the dependencies to reinstall them. - # TODO(jleibs): pull these deps from pyproject.toml - shell: bash run: | - pip uninstall rerun-sdk - pip install deprecated numpy>=1.23 pyarrow==10.0.1 - pip install rerun-sdk==${{ env.expected_version }} --no-index --find-links pre-dist + pip install rerun-sdk --find-links pre-dist --force-reinstall - - name: Verify built wheel version - if: ${{ matrix.run_tests }} - shell: bash - run: | - python3 -m rerun --version - which rerun - rerun --version - - - name: Run unit tests - if: ${{ matrix.run_tests }} - shell: bash + - name: Run tests run: cd rerun_py/tests && pytest - - name: Run e2e test - if: ${{ matrix.run_tests }} - shell: bash - run: RUST_LOG=debug scripts/run_python_e2e_test.py --no-build # rerun-sdk is already built and installed - - name: Unpack the wheel shell: bash run: | @@ -396,17 +227,21 @@ jobs: run: | echo "pkg_folder=$(ls unpack-dist)" >> $GITHUB_ENV - - name: Download RRD - uses: actions/download-artifact@v3 + - name: Cache RRD dataset + id: dataset + uses: actions/cache@v3 with: - name: rrd - path: rrd + path: examples/python/colmap/dataset/ + # TODO(jleibs): Derive this key from the invocation below + key: colmap-dataset-colmap-fiat-v0 - - name: Insert the rrd + - name: Generate Embedded RRD file shell: bash # If you change the line below you should almost definitely change the `key:` line above by giving it a new, unique name run: | - cp rrd/colmap_fiat.rrd unpack-dist/${{ env.pkg_folder }}/rerun_sdk/rerun_demo/colmap_fiat.rrd + pip install -r examples/python/colmap/requirements.txt + python3 examples/python/colmap/main.py --dataset colmap_fiat --resize 800x600 --save colmap.rrd + cp colmap.rrd unpack-dist/${{ env.pkg_folder }}/rerun_sdk/rerun_demo/colmap.rrd - name: Repack the wheel shell: bash @@ -422,60 +257,6 @@ jobs: # --------------------------------------------------------------------------- - upload_rrd: - name: Upload RRD to GCloud - permissions: - contents: "read" - id-token: "write" - needs: [wheels] - runs-on: "ubuntu-latest" - steps: - - name: Download Artifact - uses: actions/download-artifact@v3 - with: - name: rrd - path: rrd - - - id: "auth" - uses: google-github-actions/auth@v1 - with: - workload_identity_provider: ${{ secrets.GOOGLE_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }} - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${{github.sha}} | cut -c1-7`" >> $GITHUB_ENV - - - name: "Upload RRD (commit)" - uses: google-github-actions/upload-cloud-storage@v1 - with: - path: "rrd" - destination: "rerun-example-rrd/commit/${{env.SHORT_SHA}}" - parent: false - - - name: "Upload RRD (prerelease)" - if: "!startsWith(github.ref , 'refs/tags/v')" - uses: google-github-actions/upload-cloud-storage@v1 - with: - path: "rrd" - destination: "rerun-example-rrd/prerelease" - parent: false - - - name: "Upload RRD (tagged)" - if: startsWith(github.ref, 'refs/tags/v') - uses: google-github-actions/upload-cloud-storage@v1 - with: - path: "rrd" - destination: "rerun-example-rrd/version/${{github.ref_name}}" - parent: false - - - name: "Upload RRD (latest release)" - if: github.ref == 'latest' - uses: google-github-actions/upload-cloud-storage@v1 - with: - path: "rrd" - destination: "rerun-example-rrd/latest" - parent: false - # See https://github.com/ncipollo/release-action pre-release: name: Pre Release @@ -492,28 +273,34 @@ jobs: # First delete the old prerelease. If we don't do this, we don't get things like # proper source-archives and changelog info. # https://github.com/dev-drprasad/delete-tag-and-release - - uses: dev-drprasad/delete-tag-and-release@v0.2.1 + - uses: dev-drprasad/delete-tag-and-release@v0.2.0 with: tag_name: prerelease delete_release: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Re-tag the prerelease with the commit from this build + # https://github.com/richardsimko/update-tag + - name: Update prerelease tag + uses: richardsimko/update-tag@v1 + with: + tag_name: prerelease + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Create the actual prerelease - # https://github.com/ncipollo/release-action + # https://github.com/softprops/action-gh-release - name: GitHub Release - uses: ncipollo/release-action@v1.12.0 + uses: softprops/action-gh-release@v0.1.15 with: + name: "Development Build" body: ${{ env.PRE_RELEASE_INSTRUCTIONS }} prerelease: true - artifacts: dist/* - name: "Development Build" - tag: "prerelease" + tag_name: prerelease + files: dist/* token: ${{ secrets.GITHUB_TOKEN }} - generateReleaseNotes: true - allowUpdates: true - removeArtifacts: true - replacesArtifacts: true + generate_release_notes: true # --------------------------------------------------------------------------- @@ -530,14 +317,13 @@ jobs: name: wheels path: dist - # https://github.com/ncipollo/release-action - name: GitHub Release - uses: ncipollo/release-action@v1.12.0 + uses: softprops/action-gh-release@v0.1.15 with: - prerelease: true - artifacts: dist/* + prerelease: false + files: dist/* token: ${{ secrets.GITHUB_TOKEN }} - generateReleaseNotes: true + generate_release_notes: true - name: Publish to PyPI uses: PyO3/maturin-action@v1 @@ -602,28 +388,14 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Mike will incrementally update the existing gh-pages branch - # We then check it out, and reset it to a new orphaned branch, which we force-push to origin - # to make sure we don't accumulate unnecessary history in gh-pages branch - name: Deploy via mike # https://github.com/jimporter/mike if: startsWith(github.ref, 'refs/tags/v') run: | git fetch - mike deploy -F rerun_py/mkdocs.yml --rebase -b gh-pages --prefix docs/python -u ${{github.ref_name}} latest - git checkout gh-pages - git checkout --orphan gh-pages-orphan - git commit -m "Update docs for ${GITHUB_SHA}" - git push origin gh-pages-orphan:gh-pages -f - - # Mike will incrementally update the existing gh-pages branch - # We then check it out, and reset it to a new orphaned branch, which we force-push to origin - # to make sure we don't accumulate unnecessary history in gh-pages branch + mike deploy -F rerun_py/mkdocs.yml -p --rebase -b gh-pages --prefix docs/python -u ${{github.ref_name}} latest + - name: Deploy tag via mike # https://github.com/jimporter/mike if: github.ref == 'refs/heads/main' run: | git fetch - mike deploy -F rerun_py/mkdocs.yml --rebase -b gh-pages --prefix docs/python HEAD - git checkout gh-pages - git checkout --orphan gh-pages-orphan - git commit -m "Update docs for ${GITHUB_SHA}" - git push origin gh-pages-orphan:gh-pages -f + mike deploy -F rerun_py/mkdocs.yml -p --rebase -b gh-pages --prefix docs/python HEAD diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f48703b53395..22cc2658a4d0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -5,18 +5,6 @@ on: branches: - "main" pull_request: - workflow_dispatch: - inputs: - force_update_web_build: - description: "Upload web build to google cloud" - required: true - default: false - type: boolean - force_run_benchmarks: - description: "Run the rust benchmarks" - required: true - default: false - type: boolean concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} # Cancel previous CI jobs on the same branch @@ -26,10 +14,10 @@ env: # web_sys_unstable_apis is required to enable the web_sys clipboard API which egui_web uses # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html - RUSTFLAGS: --cfg=web_sys_unstable_apis --deny warnings + RUSTFLAGS: --cfg=web_sys_unstable_apis -D warnings # See https://github.com/ericseppanen/cargo-cranky/issues/8 - RUSTDOCFLAGS: --deny warnings --deny rustdoc::missing_crate_level_docs + RUSTDOCFLAGS: -D warnings -D rustdoc::missing_crate_level_docs permissions: # deployments permission to deploy GitHub pages website @@ -38,16 +26,15 @@ permissions: contents: write jobs: - # See the benchmarks at https://ref.rerun.io/dev/bench/ rs-benchmarks: name: Rust Criterion benchmarks - # Only run the rs-benchmarks job when a commit gets pushed to main or we manually specify it - if: ${{ github.ref == 'refs/heads/main' || github.event.inputs.force_run_benchmarks }} + # Only save the rs-benchmarks job when a commit gets pushed to main + if: ${{ github.event_name == 'push' }} runs-on: ubuntu-latest-16-cores container: - image: rerunio/ci_docker:0.6 + image: rerunio/ci_docker:0.5 env: RUSTFLAGS: ${{env.RUSTFLAGS}} RUSTDOCFLAGS: ${{env.RUSTDOCFLAGS}} @@ -71,13 +58,12 @@ jobs: --all-features \ -p re_arrow_store \ -p re_data_store \ - -p re_log_encoding \ + -p re_log_types \ -p re_query \ -p re_tuid \ -- --output-format=bencher | tee output.txt - name: Store benchmark result - # https://github.com/benchmark-action/github-action-benchmark uses: benchmark-action/github-action-benchmark@v1 with: name: Rust Benchmark @@ -89,11 +75,10 @@ jobs: comment-on-alert: true alert-threshold: "150%" fail-on-alert: true - comment-always: false # Generates too much GitHub notification spam + comment-always: true - # Save, results and push to GitHub only on main - save-data-file: ${{ github.ref == 'refs/heads/main' }} - auto-push: ${{ github.ref == 'refs/heads/main' }} + # Push and deploy GitHub pages branch automatically + auto-push: true gh-pages-branch: gh-pages benchmark-data-dir-path: dev/bench max-items-in-chart: 30 @@ -104,7 +89,7 @@ jobs: name: Rust lints (fmt, check, cranky, tests, doc) runs-on: ubuntu-latest-16-cores container: - image: rerunio/ci_docker:0.6 + image: rerunio/ci_docker:0.5 env: RUSTFLAGS: ${{env.RUSTFLAGS}} RUSTDOCFLAGS: ${{env.RUSTDOCFLAGS}} @@ -137,22 +122,13 @@ jobs: uses: actions-rs/cargo@v1 with: command: cranky - args: --all-targets --all-features -- --deny warnings + args: --all-targets --all-features -- -D warnings - # -------------------------------------------------------------------------------- - # Check a few important permutations of the feature flags for our `rerun` library: - - name: Check rerun with `--no-default-features`` + - name: Check no default features uses: actions-rs/cargo@v1 with: - command: cranky - args: --locked -p rerun --no-default-features - - - name: Check rerun with `--features sdk` - uses: actions-rs/cargo@v1 - with: - command: cranky - args: --locked -p rerun --no-default-features --features sdk - # -------------------------------------------------------------------------------- + command: check + args: --locked --no-default-features --features __ci --lib - name: Test doc-tests uses: actions-rs/cargo@v1 @@ -210,7 +186,7 @@ jobs: name: Check Rust web build (wasm32 + wasm-bindgen) runs-on: ubuntu-latest-16-cores container: - image: rerunio/ci_docker:0.6 + image: rerunio/ci_docker:0.5 env: RUSTFLAGS: ${{env.RUSTFLAGS}} RUSTDOCFLAGS: ${{env.RUSTDOCFLAGS}} @@ -219,8 +195,8 @@ jobs: - uses: actions-rs/toolchain@v1 with: - profile: default - toolchain: 1.67.1 + profile: minimal + toolchain: 1.67.0 target: wasm32-unknown-unknown override: true @@ -231,138 +207,53 @@ jobs: # See: https://github.com/rerun-io/rerun/pull/497 save-if: ${{ github.event_name == 'push'}} - - name: clippy check re_viewer wasm32 - run: ./scripts/clippy_wasm.sh - - - name: Check re_renderer examples wasm32 + - name: Check re_viewer wasm32 uses: actions-rs/cargo@v1 with: command: check - args: --locked --target wasm32-unknown-unknown --target-dir target_wasm -p re_renderer --examples + args: --locked --all-features --lib --target wasm32-unknown-unknown -p re_viewer - - name: Build web-viewer (debug) + - name: Check re_renderer examples wasm32 uses: actions-rs/cargo@v1 with: - command: run - args: --locked -p re_build_web_viewer -- --debug + command: check + args: --locked --target wasm32-unknown-unknown -p re_renderer --examples - # --------------------------------------------------------------------------- + - run: ./scripts/wasm_bindgen_check.sh --skip-setup - rs-build-web-viewer: - name: Upload web build to google cloud (wasm32 + wasm-bindgen) - permissions: - contents: "read" - id-token: "write" + # --------------------------------------------------------------------------- - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') || github.event.inputs.force_update_web_build + rs-cargo-deny: + name: Check Rust dependencies (cargo-deny) runs-on: ubuntu-latest-16-cores container: - image: rerunio/ci_docker:0.6 + image: rerunio/ci_docker:0.5 env: RUSTFLAGS: ${{env.RUSTFLAGS}} RUSTDOCFLAGS: ${{env.RUSTDOCFLAGS}} steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.67.0 - target: wasm32-unknown-unknown - override: true - - - name: Set up cargo cache - uses: Swatinem/rust-cache@v2 - with: - env-vars: CARGO CC CFLAGS CXX CMAKE RUST CACHE_KEY - # See: https://github.com/rerun-io/rerun/pull/497 - save-if: ${{ github.event_name == 'push'}} - - - name: Patch Cargo.toml for pre-release - if: "!startsWith(github.ref , 'refs/tags/v')" - # After patching the pre-release version, run cargo update. - # This updates the cargo.lock file with the new version numbers and keeps the wheel build from failing - run: | - python3 scripts/version_util.py --patch_prerelease - cargo update -w - - - name: Build web-viewer (release) + - name: cargo deny aarch64-apple-darwin check uses: actions-rs/cargo@v1 with: - command: run - args: --locked -p re_build_web_viewer -- --release - - # Upload the wasm, html etc to a Google cloud bucket: - - id: "auth" - uses: google-github-actions/auth@v1 - with: - workload_identity_provider: ${{ secrets.GOOGLE_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GOOGLE_SERVICE_ACCOUNT }} - - - name: Add SHORT_SHA env property with commit short sha - run: echo "SHORT_SHA=`echo ${{github.sha}} | cut -c1-7`" >> $GITHUB_ENV - - - name: "Upload web-viewer (commit)" - uses: google-github-actions/upload-cloud-storage@v1 - with: - path: "web_viewer" - destination: "rerun-web-viewer/commit/${{env.SHORT_SHA}}" - parent: false - - - name: "Upload web-viewer (prerelease)" - if: github.ref == 'refs/heads/main' - uses: google-github-actions/upload-cloud-storage@v1 - with: - path: "web_viewer" - destination: "rerun-web-viewer/prerelease" - parent: false + command: deny + args: --log-level=error --all-features --target aarch64-apple-darwin check - - name: "Upload web-viewer (tagged)" - if: startsWith(github.ref, 'refs/tags/v') - uses: google-github-actions/upload-cloud-storage@v1 + - name: cargo deny wasm32-unknown-unknown check + uses: actions-rs/cargo@v1 with: - path: "web_viewer" - destination: "rerun-web-viewer/version/${{github.ref_name}}" - parent: false + command: deny + args: --log-level=error --all-features --target wasm32-unknown-unknown check - - name: "Upload web-viewer (latest release)" - if: github.ref == 'latest' - uses: google-github-actions/upload-cloud-storage@v1 + - name: cargo deny x86_64-pc-windows-msvc + uses: actions-rs/cargo@v1 with: - path: "web_viewer" - destination: "rerun-web-viewer/latest" - parent: false - - # --------------------------------------------------------------------------- - - rs-cargo-deny: - name: Check Rust dependencies (cargo-deny) - runs-on: ubuntu-latest-16-cores - container: - image: rerunio/ci_docker:0.6 - env: - RUSTFLAGS: ${{env.RUSTFLAGS}} - RUSTDOCFLAGS: ${{env.RUSTDOCFLAGS}} + command: deny + args: --log-level=error --all-features --target x86_64-pc-windows-msvc check - # TODO(emilk): remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved - strategy: - fail-fast: false - matrix: - platform: - - i686-pc-windows-gnu - - i686-pc-windows-msvc - - i686-unknown-linux-gnu - - wasm32-unknown-unknown - - x86_64-apple-darwin - - x86_64-pc-windows-gnu - - x86_64-pc-windows-msvc - - x86_64-unknown-linux-gnu - - x86_64-unknown-redox - - steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 + - name: cargo deny x86_64-unknown-linux-musl check + uses: actions-rs/cargo@v1 with: - command: check - log-level: error - arguments: --all-features --target ${{ matrix.platform }} + command: deny + args: --log-level=error --all-features --target x86_64-unknown-linux-musl check diff --git a/.github/workflows/toml.yml b/.github/workflows/toml.yml index f3a60ec74885..120735d1f729 100644 --- a/.github/workflows/toml.yml +++ b/.github/workflows/toml.yml @@ -20,7 +20,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.67.1 + toolchain: 1.67.0 override: true - name: Set up cargo cache diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml deleted file mode 100644 index c3bc84125f51..000000000000 --- a/.github/workflows/typos.yml +++ /dev/null @@ -1,17 +0,0 @@ -# https://github.com/crate-ci/typos -# Add exceptions to _typos.toml -# install and run locally: cargo install typos-cli && typos - -name: Spell Check -on: [pull_request] - -jobs: - run: - name: Spell Check - runs-on: ubuntu-latest - steps: - - name: Checkout Actions Repository - uses: actions/checkout@v2 - - - name: Check spelling of entire workspace - uses: crate-ci/typos@master diff --git a/.mypy.ini b/.mypy.ini index 3975c2924ca1..e1c3f9421600 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -1,5 +1,5 @@ [mypy] -files = rerun_py/rerun_sdk/rerun, rerun_py/tests, examples/python +files = rerun_py/rerun_sdk/depthai_viewer, rerun_py/tests, examples/python exclude = examples/python/objectron/proto|examples/python/ros namespace_packages = True show_error_codes = True diff --git a/.vscode/extensions.json b/.vscode/extensions.json index aae305c13ea6..32731cdfadeb 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,17 +2,18 @@ // See https://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ - "charliermarsh.ruff", // Ruff for linting + "charliermarsh.ruff", + "github.vscode-github-actions", "ms-python.python", - "ms-vsliveshare.vsliveshare", // Live Share + "ms-vsliveshare.vsliveshare", "polymeilex.wgsl", - "rust-lang.rust-analyzer", // Rust-analyzer + "rust-lang.rust-analyzer", "serayuzgur.crates", "streetsidesoftware.code-spell-checker", - "tamasfe.even-better-toml", // TOML LSP - "vadimcn.vscode-lldb", // CodeLLDB - "wayou.vscode-todo-highlight", // TODO Highlight - "webfreak.debug", // Native Debug - "zxh404.vscode-proto3", // vscode-proto3 + "tamasfe.even-better-toml", + "vadimcn.vscode-lldb", + "wayou.vscode-todo-highlight", + "webfreak.debug", + "zxh404.vscode-proto3", ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index c52ea2c7178d..1722a88e03b3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,82 +1,75 @@ { - "editor.formatOnSave": true, - "editor.semanticTokenColorCustomizations": { - "rules": { - "*.unsafe:rust": "#eb5046" - } - }, - "files.insertFinalNewline": true, - "files.trimTrailingWhitespace": true, - "files.exclude": { - "env/**": true, - "target/**": true, - "target_ra/**": true, - "target_wasm/**": true, - }, - "files.autoGuessEncoding": true, - "python.formatting.provider": "black", - "python.formatting.blackArgs": [ - "--config", - "rerun_py/pyproject.toml" - ], - "python.linting.mypyEnabled": true, - "python.linting.enabled": true, - "cSpell.words": [ - "andreas", - "bbox", - "bindgroup", - "colormap", - "emath", - "framebuffer", - "hoverable", - "ilog", - "jumpflooding", - "Keypoint", - "memoffset", - "nyud", - "objectron", - "Readback", - "readbacks", - "Skybox", - "smallvec", - "swapchain", - "texcoords", - "Tonemapper", - "tonemapping", - "voronoi", - "vram", - "Wgsl" - ], - // don't share a cargo lock with rust-analyzer. - // see https://github.com/rerun-io/rerun/pull/519 for rationale - "rust-analyzer.checkOnSave.overrideCommand": [ - "cargo", - "cranky", - "--target-dir=target_ra", - "--workspace", - "--message-format=json", - "--all-targets", - "--all-features", // --all-features will set the `__ci` feature flag, which stops crates/re_web_viewer_server/build.rs from building the web viewer - ], - "rust-analyzer.cargo.buildScripts.overrideCommand": [ - "cargo", - "check", - "--quiet", - "--target-dir=target_ra", - "--workspace", - "--message-format=json", - "--all-targets", - "--all-features", // --all-features will set the `__ci` feature flag, which stops crates/re_web_viewer_server/build.rs from building the web viewer - ], - // Our build scripts are generating code. - // Having Rust Analyzer do this while doing other builds can lead to catastrophic failures. - // INCLUDING attempts to publish a new release! - "rust-analyzer.cargo.buildScripts.enable": false, - "python.analysis.extraPaths": [ - "rerun_py/rerun_sdk" - ], - "ruff.args": [ - "--config", - "rerun_py/pyproject.toml" - ], + "editor.formatOnSave": true, + "editor.semanticTokenColorCustomizations": { + "rules": { + "*.unsafe:rust": "#eb5046" + } + }, + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + "files.exclude": { + "env/**": true, + "target/**": true, + "target_ra/**": true, + "target_wasm/**": true + }, + "files.autoGuessEncoding": true, + "python.formatting.provider": "black", + "python.formatting.blackArgs": ["--config", "rerun_py/pyproject.toml"], + "python.linting.mypyEnabled": true, + "python.linting.enabled": true, + "cSpell.words": [ + "andreas", + "bbox", + "bindgroup", + "colormap", + "emath", + "framebuffer", + "hoverable", + "ilog", + "jumpflooding", + "Keypoint", + "memoffset", + "nyud", + "objectron", + "Readback", + "readbacks", + "Skybox", + "smallvec", + "swapchain", + "texcoords", + "Tonemapper", + "tonemapping", + "unsmoothed", + "voronoi", + "vram", + "Wgsl" + ], + // don't share a cargo lock with rust-analyzer. + // see https://github.com/rerun-io/rerun/pull/519 for rationale + "rust-analyzer.checkOnSave.overrideCommand": [ + "cargo", + "cranky", + "--target-dir=target_ra", + "--workspace", + "--message-format=json", + "--all-targets", + "--all-features" // --all-features will set the `__ci` feature flag, which stops crates/re_web_viewer_server/build.rs from building the web viewer + ], + "rust-analyzer.cargo.buildScripts.overrideCommand": [ + "cargo", + "check", + "--quiet", + "--target-dir=target_ra", + "--workspace", + "--message-format=json", + "--all-targets", + "--all-features" // --all-features will set the `__ci` feature flag, which stops crates/re_web_viewer_server/build.rs from building the web viewer + ], + // Our build scripts are generating code. + // Having Rust Analyzer do this while doing other builds can lead to catastrophic failures. + // INCLUDING attempts to publish a new release! + "rust-analyzer.cargo.buildScripts.enable": false, + "python.analysis.extraPaths": ["rerun_py/rerun_sdk"], + "ruff.args": ["--config", "rerun_py/pyproject.toml"] } diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 31fb2a572c63..cd2e4c7115e3 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -22,10 +22,10 @@ The logging data can be written to disk as `.rrd` files, or transmitted over TCP The Rerun Viewer is where log data is visualized. It is usually run as a native app, but can also be compiled to WebAssembly (Wasm) and run in a browser. #### Native viewer -The easiest way to launch the viewer is directly from the logging API with `rr.init("my_app", spawn=True)`. However, the standalone viewer can also be run from the command line, for example to view an `.rrd` file: `rerun mydata.rrd`. +The easiest way to launch the viewer is directly from the logging API with `viewer.init("my_app", spawn=True)`. However, the standalone viewer can also be run from the command line, for example to view an `.rrd` file: `rerun mydata.rrd`. #### Web viewer -You can try running the viewer in a browser using `rr.serve()` in python, or using `rerun --web-viewer mydata.rrd`. +You can try running the viewer in a browser using `viewer.serve()` in python, or using `rerun --web-viewer mydata.rrd`. The web viewer consists of just a few small files - a thin `.html`, a `.wasm` blob, and an auto-generated `.js` bridge for the wasm. These files are served using the [`re_web_viewer_server`](https://github.com/rerun-io/rerun/tree/latest/crates/re_web_viewer_server) crate. diff --git a/BUILD.md b/BUILD.md index 011e4d29b023..433d64953270 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,22 +1,22 @@ # Building Rerun -This is a guide to how to build Rerun. +This is a guide to how to build Rerun. ## See also -* [`rerun_py/README.md`](rerun_py/README.md) - build instructions for Python SDK -* [`ARCHITECTURE.md`](ARCHITECTURE.md) -* [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) -* [`CODE_STYLE.md`](CODE_STYLE.md) -* [`CONTRIBUTING.md`](CONTRIBUTING.md) -* [`RELEASES.md`](RELEASES.md) +- [`rerun_py/README.md`](rerun_py/README.md) - build instructions for Python SDK +- [`ARCHITECTURE.md`](ARCHITECTURE.md) +- [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) +- [`CODE_STYLE.md`](CODE_STYLE.md) +- [`CONTRIBUTING.md`](CONTRIBUTING.md) +- [`RELEASES.md`](RELEASES.md) ## Getting started with the repository. -* Install the Rust toolchain: -* `git clone git@github.com:rerun-io/rerun.git && cd rerun` -* Run `./scripts/setup_dev.sh`. -* Make sure `cargo --version` prints `1.67.1` once you are done +- Install the Rust toolchain: +- `git clone git@github.com:rerun-io/rerun.git && cd rerun` +- Run `./scripts/setup_dev.sh`. +- Make sure `cargo --version` prints `1.67.1` once you are done ### Apple-silicon Macs @@ -35,7 +35,8 @@ Python API docs can be found at and are built Rust documentation is hosted on . You can build them locally with: `cargo doc --all-features --no-deps --open` ## Build and install the Rerun Python SDK -Rerun is available as a package on PyPi and can be installed with `pip install rerun-sdk` + +Rerun is available as a package on PyPi and can be installed with `pip install depthai-viewer` Additionally, prebuilt dev wheels from head of main are available at . @@ -64,18 +65,19 @@ From here on out, we assume you have this virtualenv activated. ### Build and install You need to setup your build environment once with + ```sh ./scripts/setup.sh ``` Then install the Rerun SDK with: + ``` pip install ./rerun_py ``` > Note: If you are unable to upgrade pip to version `>=21.3`, you need to pass `--use-feature=in-tree-build` to the `pip install` command. - ## Improving compile times As of today, we link everything statically in both debug and release builds, which makes custom linkers and split debuginfo the two most impactful tools we have at our disposal in order to improve compile times. @@ -87,9 +89,11 @@ These tools can configured through your `Cargo` configuration, available at `$HO On x64 macOS, use the [zld](https://github.com/michaeleisel/zld) linker and keep debuginfo in a single separate file. Pre-requisites: + - Install [zld](https://github.com/michaeleisel/zld): `brew install michaeleisel/zld/zld`. `config.toml` (x64): + ```toml [target.x86_64-apple-darwin] rustflags = [ @@ -103,6 +107,7 @@ rustflags = [ On Apple-silicon Mac (M1, M2), the default settings are already pretty good. The default linker is just as good as `zld`. Do NOT set `split-debuginfo=packed`, as that will make linking a lot slower. You can set `split-debuginfo=unpacked` for a small improvement. `config.toml` (M1, M2): + ```toml [target.aarch64-apple-darwin] rustflags = [ @@ -116,9 +121,11 @@ rustflags = [ On Linux, use the [mold](https://github.com/rui314/mold) linker and keep DWARF debuginfo in separate files. Pre-requisites: + - Install [mold](https://github.com/rui314/mold) through your package manager. `config.toml`: + ```toml [target.x86_64-unknown-linux-gnu] linker = "clang" @@ -135,13 +142,16 @@ rustflags = [ On Windows, use LLVM's `lld` linker and keep debuginfo in a single separate file. Pre-requisites: + - Install `lld`: + ``` cargo install -f cargo-binutils rustup component add llvm-tools-preview ``` `config.toml`: + ```toml [target.x86_64-pc-windows-msvc] linker = "rust-lld.exe" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7093067a19..2a840fe9a9b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,347 +1,5 @@ -# Rerun changelog +# Depthai Viewer changelog +## 0.0.1 -## [Unreleased](https://github.com/rerun-io/rerun/compare/latest...HEAD) -… - -## [0.4.0](https://github.com/rerun-io/rerun/compare/v0.3.1...v0.4.0) - Outlines, web viewer and performance improvements - -https://user-images.githubusercontent.com/1220815/228241887-03b311e2-80e9-4541-9281-6d334a15ab04.mp4 - - -## Overview & Highlights -* Add support for mesh vertex colors [#1671](https://github.com/rerun-io/rerun/pull/1671) -* Lower memory use [#1535](https://github.com/rerun-io/rerun/pull/1535) -* Improve garbage collection [#1560](https://github.com/rerun-io/rerun/pull/1560) -* Improve the web viewer [#1596](https://github.com/rerun-io/rerun/pull/1596) [#1594](https://github.com/rerun-io/rerun/pull/1594) [#1682](https://github.com/rerun-io/rerun/pull/1682) [#1716](https://github.com/rerun-io/rerun/pull/1716) … -* Nice outlines when hovering/selecting -* Add an example of forever-streaming a web-camera image to Rerun [#1502](https://github.com/rerun-io/rerun/pull/1502) -* Fix crash-on-save on some versions of Linux [#1402](https://github.com/rerun-io/rerun/pull/1402) -* And a lot of other bug fixes -* Many performance improvements - -We now host an experimental and unpolished web-viewer at for anyone to try out! - -### In Detail - -#### 🐍 Python SDK -- Expose all Rerun enums and types to main module scope [#1598](https://github.com/rerun-io/rerun/pull/1598) -- Make `log_point` more forgiving and update docstring [#1663](https://github.com/rerun-io/rerun/pull/1663) -- Add support for mesh vertex colors [#1671](https://github.com/rerun-io/rerun/pull/1671) - -#### 🦀 Rust SDK -- ⚠️ `Session::new` has been replaced with `SessionBuilder` [#1528](https://github.com/rerun-io/rerun/pull/1528) -- ⚠️ `session.spawn(…)` -> `rerun::native_viewer::spawn(session, …)` [#1507](https://github.com/rerun-io/rerun/pull/1507) -- ⚠️ `session.show()` -> `rerun::native_viewer::show(session)` [#1507](https://github.com/rerun-io/rerun/pull/1507) -- ⚠️ `session.serve(…)` -> `rerun::serve_web_viewer(session, …);` [#1507](https://github.com/rerun-io/rerun/pull/1507) -- ⚠️ `rerun::global_session` is now hidden behind the `global_session` feature flag [#1507](https://github.com/rerun-io/rerun/pull/1507) -- Add support for mesh vertex colors [#1671](https://github.com/rerun-io/rerun/pull/1671) - -#### 🪳 Bug Fixes -- datastore: disable compaction (fixes 2x memory issue) [#1535](https://github.com/rerun-io/rerun/pull/1535) -- Fix garbage collection [#1560](https://github.com/rerun-io/rerun/pull/1560) -- Avoid using undefined extern "C" on windows [#1577](https://github.com/rerun-io/rerun/pull/1577) -- Fix crash on decoding old .rrd files [#1579](https://github.com/rerun-io/rerun/pull/1579) -- datastore: stabilize dataframe sorts [#1549](https://github.com/rerun-io/rerun/pull/1549) -- Stop using infinities in wgsl shaders [#1594](https://github.com/rerun-io/rerun/pull/1594) -- Workaround for alpha to coverage state leaking on (Web)GL renderer [#1596](https://github.com/rerun-io/rerun/pull/1596) -- Use a patched `wasm-bindgen-cli` with fix for 2GiB bug [#1605](https://github.com/rerun-io/rerun/pull/1605) -- Misc: make example in `log_pinhole` runable [#1609](https://github.com/rerun-io/rerun/pull/1609) (thanks [@Sjouks](https://github.com/Sjouks)!) -- Early-out on zero-sized space-views to prevent crashes [#1623](https://github.com/rerun-io/rerun/pull/1623) -- Print our own callstack on panics [#1622](https://github.com/rerun-io/rerun/pull/1622) -- Handle ctrl+c to gracefully shutdown the server(s) [#1613](https://github.com/rerun-io/rerun/pull/1613) -- Fix crash on serve exit, second attempt [#1633](https://github.com/rerun-io/rerun/pull/1633) -- Fix wrong remove-tooltip for entities and groups [#1637](https://github.com/rerun-io/rerun/pull/1637) -- Fix requiring requiring focus for shutdown via ctrl+c when starting viewer from command line [#1646](https://github.com/rerun-io/rerun/pull/1646) -- Fix eye spin after eye reset [#1652](https://github.com/rerun-io/rerun/pull/1652) -- Fix crash on negative radii by instead warning [#1654](https://github.com/rerun-io/rerun/pull/1654) -- Fix crash when trying to listen on a taken TCP port [#1650](https://github.com/rerun-io/rerun/pull/1650) -- Don't show 2D labels in 3D space views. [#1641](https://github.com/rerun-io/rerun/pull/1641) -- Fix Z fighting with improved depth offset math [#1661](https://github.com/rerun-io/rerun/pull/1661) -- Whether a spatial view is 2d or 3d is now reevaluated over time unless picked explicitly [#1660](https://github.com/rerun-io/rerun/pull/1660) -- Update wgpu to v0.15.3, fixing meshes on Windows Chrome [#1682](https://github.com/rerun-io/rerun/pull/1682) -- Fix a bug in the image hover code, causing the wrong RGBA values to be printed [#1690](https://github.com/rerun-io/rerun/pull/1690) -- Fix a bug that caused points to be render too large [#1690](https://github.com/rerun-io/rerun/pull/1690) -- Fix web crash on missing uniform buffer padding [#1699](https://github.com/rerun-io/rerun/pull/1699) -- Fix `memory_usage` example relying on implicit recursive features [#1709](https://github.com/rerun-io/rerun/pull/1709) -- Track changed state in nav mode combo box [#1703](https://github.com/rerun-io/rerun/pull/1703) -- Fix crash-on-save by switching file-picker dialog to `xdg-portal` [#1402](https://github.com/rerun-io/rerun/pull/1402) -- Change roll-shortcut from ALT to SHIFT [#1715](https://github.com/rerun-io/rerun/pull/1715) -- Fix CpuWriteGpuReadBelt producing unaligned gpu buffer offsets [#1716](https://github.com/rerun-io/rerun/pull/1716) -- Fix arrows requiring a radius to be visible [#1720](https://github.com/rerun-io/rerun/pull/1720) - -#### 🚀 Performance Improvements -- Add re_arrow_store profile scopes [#1546](https://github.com/rerun-io/rerun/pull/1546) -- datastore: early exit missing components at table level [#1554](https://github.com/rerun-io/rerun/pull/1554) -- datastore: track bucket count in store stats & mem panel [#1555](https://github.com/rerun-io/rerun/pull/1555) -- LogDb: dont split on index bucket size [#1558](https://github.com/rerun-io/rerun/pull/1558) -- Introduce a simpler cache dedicated to just decode JPEGs [#1550](https://github.com/rerun-io/rerun/pull/1550) -- Implement outlines for points 2d/3d/depth & use them for select & hover in Viewer [#1568](https://github.com/rerun-io/rerun/pull/1568) -- Simplify ImageCache [#1551](https://github.com/rerun-io/rerun/pull/1551) -- New time panel density graph [#1557](https://github.com/rerun-io/rerun/pull/1557) -- Refactor the Arrow Mesh3D type to use zero-copy Buffers [#1691](https://github.com/rerun-io/rerun/pull/1691) -- Remove the redundant costly transform check during categorization [#1695](https://github.com/rerun-io/rerun/pull/1695) -- batching 3: `DataRow` & `DataTable` + no bundles outside of transport [#1673](https://github.com/rerun-io/rerun/pull/1673) - -#### 🧑‍🏫 Examples -- Very simple example streaming from an opencv camera [#1502](https://github.com/rerun-io/rerun/pull/1502) -- Initial TurtleBot subscriber demo [#1523](https://github.com/rerun-io/rerun/pull/1523) - -#### 📚 Docs -- Link to the Python SDK build instructions in `rerun_py/README.md` [#1565](https://github.com/rerun-io/rerun/pull/1565) - -#### 🖼 UI Improvements -- Fix combining outline mask for selection & hover [#1552](https://github.com/rerun-io/rerun/pull/1552) -- Implement outlines for rectangles & use them for select & hover of image primitives in Viewer [#1559](https://github.com/rerun-io/rerun/pull/1559) -- Show log messages in egui toast notifications [#1603](https://github.com/rerun-io/rerun/pull/1603) -- Adapt UI for smaller screens [#1608](https://github.com/rerun-io/rerun/pull/1608) -- Nicer toast notifications [#1621](https://github.com/rerun-io/rerun/pull/1621) -- Don't hover things in 2D/3D views if we are dragging something [#1643](https://github.com/rerun-io/rerun/pull/1643) -- Allow rolling 3D camera with primary mouse button + alt modifier [#1659](https://github.com/rerun-io/rerun/pull/1659) -- Name space views after the space and indicate duplicate names [#1653](https://github.com/rerun-io/rerun/pull/1653) -- Add banner about mobile browsers being unsupported [#1674](https://github.com/rerun-io/rerun/pull/1674) -- Improve ui for tensors and color map selection [#1683](https://github.com/rerun-io/rerun/pull/1683) -- Only show the mobile OS warning banner on web [#1685](https://github.com/rerun-io/rerun/pull/1685) -- Improve the depth backprojection feature [#1690](https://github.com/rerun-io/rerun/pull/1690) -- Swap overlay order of selection & hover outlines [#1705](https://github.com/rerun-io/rerun/pull/1705) -- Turn on depth cloud backprojection by default [#1710](https://github.com/rerun-io/rerun/pull/1710) -- Add radius boost for depth clouds on outline [#1713](https://github.com/rerun-io/rerun/pull/1713) - -#### 🤷‍♂️ Other Viewer Improvements -- Fix web feature name in error messages [#1521](https://github.com/rerun-io/rerun/pull/1521) -- Use outlines for mesh selections instead of highlight colors [#1540](https://github.com/rerun-io/rerun/pull/1540) -- Implement outlines for line renderer & use them for select & hover of "line-like" primitives in Viewer [#1553](https://github.com/rerun-io/rerun/pull/1553) -- Load .rrd file over HTTP [#1600](https://github.com/rerun-io/rerun/pull/1600) -- Revert "Handle ctrl+c to gracefully shutdown the server(s)" [#1632](https://github.com/rerun-io/rerun/pull/1632) -- More eager GC, and remove `--fast-math` optimization for wasm [#1656](https://github.com/rerun-io/rerun/pull/1656) -- Detect failure to install GUI log callback [#1655](https://github.com/rerun-io/rerun/pull/1655) -- Warn when most of the RAM has been used up by Rerun [#1651](https://github.com/rerun-io/rerun/pull/1651) -- Apply color maps to all types of depth tensors [#1686](https://github.com/rerun-io/rerun/pull/1686) -- Size boosted outlines for points & lines, color & size tweaking [#1667](https://github.com/rerun-io/rerun/pull/1667) -- Default point radius to 1.5 ui points [#1706](https://github.com/rerun-io/rerun/pull/1706) -- When streaming an rrd from http: play it, don't follow it [#1707](https://github.com/rerun-io/rerun/pull/1707) - -#### 🕸️ Web -- Use `log` as our log backend instead of `tracing` [#1590](https://github.com/rerun-io/rerun/pull/1590) -- Turn on allocation tracker at run-time and for web [#1591](https://github.com/rerun-io/rerun/pull/1591) -- Set correct MIME types in re_web_viewer_server [#1602](https://github.com/rerun-io/rerun/pull/1602) -- Upload web viewer to a bucket [#1606](https://github.com/rerun-io/rerun/pull/1606) -- Use hostname for default websocket address [#1664](https://github.com/rerun-io/rerun/pull/1664) -- Upload the colmap rrd file to gcloud [#1666](https://github.com/rerun-io/rerun/pull/1666) -- Show a warning by default on mobile browsers [#1670](https://github.com/rerun-io/rerun/pull/1670) -- Add analytics to the hosted index.html [#1675](https://github.com/rerun-io/rerun/pull/1675) -- Always upload latest prerelease to a dedicated prefix [#1676](https://github.com/rerun-io/rerun/pull/1676) -- Allow url param override on app.rerun.io [#1678](https://github.com/rerun-io/rerun/pull/1678) -- Show the git commit in the about section in pre-release builds [#1677](https://github.com/rerun-io/rerun/pull/1677) -- Update the web icon [#1688](https://github.com/rerun-io/rerun/pull/1688) - -#### 🎨 Renderer Improvements -- Outlines via masking & postprocessing in `re_renderer` [#1532](https://github.com/rerun-io/rerun/pull/1532) -- Add missing profiling scopes in `re_renderer` [#1567](https://github.com/rerun-io/rerun/pull/1567) -- Don't call `wgpu::Device::poll` on the web [#1626](https://github.com/rerun-io/rerun/pull/1626) -- Merge final outline render into composite step in order to fix blending [#1629](https://github.com/rerun-io/rerun/pull/1629) -- renderer: fix the groupby logic in mesh instancing [#1657](https://github.com/rerun-io/rerun/pull/1657) -- Fix outlines being offset diagonally by about half a pixel [#1668](https://github.com/rerun-io/rerun/pull/1668) -- Gpu readback belt for fast & easy data readback from gpu [#1687](https://github.com/rerun-io/rerun/pull/1687) -- Make CpuWriteGpuReadBelt texture copies easier/less error prone [#1689](https://github.com/rerun-io/rerun/pull/1689) - -#### ✨ Other Enhancement -- datastore: split out formatting & sanity checks in their own modules [#1625](https://github.com/rerun-io/rerun/pull/1625) -- Add `rerun --save`: stream incoming log stream to an rrd file [#1662](https://github.com/rerun-io/rerun/pull/1662) -- batching 1: introduce `DataCell` & retire `ComponentBundle` [#1634](https://github.com/rerun-io/rerun/pull/1634) -- Data store batching 2: split out component traits [#1636](https://github.com/rerun-io/rerun/pull/1636) - -#### 📈 Analytics -- Analytics: don't spam warning when there is an HTTP connection problem [#1564](https://github.com/rerun-io/rerun/pull/1564) -- Analytics: Rename "location" to "file_line" in the "crash-panic" event [#1575](https://github.com/rerun-io/rerun/pull/1575) - -#### 🗣 Merged RFCs -- RFC: component-datatype conversions [#1595](https://github.com/rerun-io/rerun/pull/1595) -- RFC: pre-proposal for blueprint store [#1582](https://github.com/rerun-io/rerun/pull/1582) - -#### 🧑‍💻 Dev-experience -- Update `rayon` [#1541](https://github.com/rerun-io/rerun/pull/1541) -- Fix some `1.68` clippy lints [#1569](https://github.com/rerun-io/rerun/pull/1569) -- Remove duplicated 'nix' crate [#1479](https://github.com/rerun-io/rerun/pull/1479) -- Better MsgId format [#1566](https://github.com/rerun-io/rerun/pull/1566) -- Lint vertical spacing in Rust code [#1572](https://github.com/rerun-io/rerun/pull/1572) -- CI: Replace wasm_bindgen_check.sh with actually building the web-viewer [#1604](https://github.com/rerun-io/rerun/pull/1604) -- Add --all-features to Rust Analyzer flags [#1624](https://github.com/rerun-io/rerun/pull/1624) -- Run clippy for wasm, with own clippy.toml config file [#1628](https://github.com/rerun-io/rerun/pull/1628) -- Update tokio v1.24.1 -> v1.26.0 [#1635](https://github.com/rerun-io/rerun/pull/1635) -- Add a workflow input for running benchmarks manually [#1698](https://github.com/rerun-io/rerun/pull/1698) -- Add missing } to fix rust workflow [#1700](https://github.com/rerun-io/rerun/pull/1700) -- Fix `lint.py` [#1719](https://github.com/rerun-io/rerun/pull/1719) -- Add a script that generates a changelog from recent PRs and their labels [#1718](https://github.com/rerun-io/rerun/pull/1718) - -#### 🤷‍♂️ Other -- Clean up opencv_canny example slightly [b487e550dcb87225858dc6f76b791a25e938e75e](https://github.com/rerun-io/rerun/commit/b487e550dcb87225858dc6f76b791a25e938e75e) -- Lint fixes [9901e7c6735356b1970ddabc926bc5378d82e057](https://github.com/rerun-io/rerun/commit/9901e7c6735356b1970ddabc926bc5378d82e057) - - -## [0.3.1](https://github.com/rerun-io/rerun/compare/v0.3.0...v0.3.1) - Remove potentially sensitive analytics - -Remove potentially sensitive analytics, including path to rerun source code on panics, and rerun branch name when building from source [#1563](https://github.com/rerun-io/rerun/pull/1563) - - -## [0.3.0](https://github.com/rerun-io/rerun/compare/v0.2.0...v0.3.0) -### Overview & Highlights - -After a successful launch a couple of weeks ago, we're back with our second release! -With a few exceptions this release focuses on internal refactors & improving our processes. -However, we think you'll enjoy these goodies that made it in nonetheless! - -https://user-images.githubusercontent.com/2910679/222510504-23871b8c-0bef-49c2-bbd2-37baab4247e8.mp4 - - -You can now generate point clouds directly from depth textures and choose a wide variety of color maps. -Check out this [video](https://user-images.githubusercontent.com/1220815/223365363-da13585f-3a91-4cb8-a6ef-8a6fadbeb4eb.webm) on how to use it. -This is **a lot** faster and more convenient than doing so manually in your own code -Some caveats: Picking is not yet working and visible history may behave differently (related to [#723](https://github.com/rerun-io/rerun/issues/723)) - -Other highlights: - -* Viewer - * Improved formatting of date-times in plots [#1356](https://github.com/rerun-io/rerun/pull/1356) - * Labels for 3D objects have now a color can now be selected & hovered [#1438](https://github.com/rerun-io/rerun/pull/1438) - * Scale factor is saved across sessions and more persistent between screens [#1448](https://github.com/rerun-io/rerun/pull/1448) - * Showing tensors in the viewer is now faster -* SDK - * Python packages now work with Ubuntu-20.04 [#1334](https://github.com/rerun-io/rerun/pull/1334) - * u8 segmentation stay u8 now (they converted to u16 before) [#1376](https://github.com/rerun-io/rerun/pull/1376) - * 2D Line strips can now be logged directly [#1430](https://github.com/rerun-io/rerun/pull/1430) - * Add a `strict` mode to the Python SDK where misuses of the API result in exceptions being raised.[#1477](https://github.com/rerun-io/rerun/pull/1477) - * Fix disabling Python API through `init` not working [#1517](https://github.com/rerun-io/rerun/pull/1517) -* General - * We build now with fewer build dependencies (there is however [still more work to do!](https://github.com/rerun-io/rerun/issues/1316)). - Notably, we previously used a version of the `time` crate which had a security issue (CVE-2020-26235), thanks @mpizenberg for helping out! - * Print more information & troubleshooting info on crash - -Meanwhile, we did a bunch of improvements to our manual. If you had trouble running Rerun so far, check our updated [troubleshooting](https://www.rerun.io/docs/getting-started/troubleshooting) page (and as always, please [open an issue](https://github.com/rerun-io/rerun/issues/new/choose) if something doesn't work). - -⚠️ BREAKING: old `.rrd` files no longer load ⚠️ - -### In Detail -#### New Features -* Generate point clouds directly from depth textures - * re_renderer: implement depth cloud renderer [#1415](https://github.com/rerun-io/rerun/pull/1415) - * Integrate depth clouds into Rerun [#1421](https://github.com/rerun-io/rerun/pull/1421) - * CPU & GPU color maps [#1484](https://github.com/rerun-io/rerun/pull/1484) - * Integrate GPU color maps into depth clouds [#1486](https://github.com/rerun-io/rerun/pull/1486) -* Python SDK: Add strict mode [#1477](https://github.com/rerun-io/rerun/pull/1477) -* OS independent Zoom factor & serialization thereof [#1448](https://github.com/rerun-io/rerun/pull/1448) -* Labels for 3D objects have now a color can now be selected & hovered [#1438](https://github.com/rerun-io/rerun/pull/1438) -* Add 2d support for linestrips [#1430](https://github.com/rerun-io/rerun/pull/1430) -* Add signal handler on *nix with troubleshooting and stacktrace [#1340](https://github.com/rerun-io/rerun/pull/1340) - * Point users to our troubleshooting page on panic [#1338](https://github.com/rerun-io/rerun/pull/1338) - -#### Performance -* Speed up conversions for color arrays in Python [#1454](https://github.com/rerun-io/rerun/pull/1454) -* Speed up fixed-sized array iteration [#1050](https://github.com/rerun-io/rerun/pull/1050) -* Speed up tensor handling by padding data through more directly - * Direct conversion to dynamic image from Tensors [#1455](https://github.com/rerun-io/rerun/pull/1455) - * Convert view_tensor to use the new native Tensors [#1439](https://github.com/rerun-io/rerun/pull/1439) -* Add option to show performance metrics in the UI in release builds too [#1444](https://github.com/rerun-io/rerun/pull/1444) -* Faster stable diffusion sample [#1364](https://github.com/rerun-io/rerun/pull/1364) -* SDK: stream to disk with `save` feature [#1405](https://github.com/rerun-io/rerun/pull/1405) -* `re_renderer` has now a direct CPU->GPU copy mechanism - * `CpuWriteGpuReadBelt` for fast frame by frame memory transfers [#1382](https://github.com/rerun-io/rerun/pull/1382) - * Uniform buffer utility using `CpuWriteGpuReadBelt` [#1400](https://github.com/rerun-io/rerun/pull/1400) - * Use `CpuWriteGpuReadBelt` for mesh data gpu upload [#1416](https://github.com/rerun-io/rerun/pull/1416) - -#### Small improvements & Bugfixes -* UI - * Add scroll-bars the "Add/Remove entities" window [#1445](https://github.com/rerun-io/rerun/pull/1445) - * Unify the time formatting between the time panel and the plot [#1369](https://github.com/rerun-io/rerun/pull/1369) - * Timeline - * Fix precision issue when zooming in on the timeline [#1370](https://github.com/rerun-io/rerun/pull/1370) - * Improve the gap-detector [#1363](https://github.com/rerun-io/rerun/pull/1363) - * Better time axis on plot view [#1356](https://github.com/rerun-io/rerun/pull/1356) - * Prevent wrap on 'Streams' text [#1308](https://github.com/rerun-io/rerun/pull/1308) - * Update to eframe 0.21.3 with fix for web text input [#1311](https://github.com/rerun-io/rerun/pull/1311) -* `re_renderer` - * Fix crash due to always expecting Rgba8Unorm backbuffer on Web & Bgra8Unorm on native [#1413](https://github.com/rerun-io/rerun/pull/1413) - * Allow controlling the graphics backend & power preference through standard wgpu env vars [#1332](https://github.com/rerun-io/rerun/pull/1332) -* Heuristic for camera frustum length is now based on scene size [#1433](https://github.com/rerun-io/rerun/pull/1433) -* Fix python type signature for tensor names [#1443](https://github.com/rerun-io/rerun/pull/1443) -* Don't convert u8 segmentation images to u16 [#1376](https://github.com/rerun-io/rerun/pull/1376) -* Docs (excluding the manual) - * Improve the docs of `connect` and `serve` [#1450](https://github.com/rerun-io/rerun/pull/1450) - * Update log_mesh and log_meshes docs. [#1286](https://github.com/rerun-io/rerun/pull/1286) - * Add guidelines for adding dependencies in a PR [#1431](https://github.com/rerun-io/rerun/pull/1431) - * Add a few more sections to `CODE_STYLE.md` [#1365](https://github.com/rerun-io/rerun/pull/1365) - * Fixup for some doc links [#1314](https://github.com/rerun-io/rerun/pull/1314) - * Document undocumented environment variables on help page. [#1335](https://github.com/rerun-io/rerun/pull/1335) - * Link to SDK operating modes doc in both SDK [#1330](https://github.com/rerun-io/rerun/pull/1330) -* More information in `--version` [#1388](https://github.com/rerun-io/rerun/pull/1388) -* Remove already broken `show` method from Python SDK [#1429](https://github.com/rerun-io/rerun/pull/1429) -* Analytics - * Send analytics events with callstacks on panics and signals [#1409](https://github.com/rerun-io/rerun/pull/1409) - * Put all analytics to one bucket [#1390](https://github.com/rerun-io/rerun/pull/1390) - * add event for when we serve the web-viewer .wasm [#1379](https://github.com/rerun-io/rerun/pull/1379) - * register SDK language and data source [#1371](https://github.com/rerun-io/rerun/pull/1371) - * Refactor analytics [#1368](https://github.com/rerun-io/rerun/pull/1368) -* Versioned log streams streams [#1420](https://github.com/rerun-io/rerun/pull/1420) -* Fix path issues when running debug viewer within workspace [#1341](https://github.com/rerun-io/rerun/pull/1341) -* Detailed errors for re_renderer `include_file!` [#1339](https://github.com/rerun-io/rerun/pull/1339) -* Limit logging in web-viewer to `warn` in order to workaround a crash issue (and reduce log spam) [1514](https://github.com/rerun-io/rerun/pull/1514) -* Fix disabling API through `init` not working [#1517](https://github.com/rerun-io/rerun/pull/1517) - -#### CI, Testing & Build improvements -* Reduce build dependencies - * Get rid of time 0.1.* dependency [#1408](https://github.com/rerun-io/rerun/pull/1408) - * Remove unnecessary ordered-float [#1461](https://github.com/rerun-io/rerun/pull/1461) - * Remove extraneous `image` features and dependencies [#1425](https://github.com/rerun-io/rerun/pull/1425) - * Replace `reqwest` with `ureq` [#1407](https://github.com/rerun-io/rerun/pull/1407) - * Remove derive_more dependency [#1406](https://github.com/rerun-io/rerun/pull/1406) -* Use different artifact names for wasm/js in debug builds [#1428](https://github.com/rerun-io/rerun/pull/1428) -* Separate mac wheels & trigger wheel build from ui [#1499](https://github.com/rerun-io/rerun/pull/1499) -* Add spell checking to CI [#1492](https://github.com/rerun-io/rerun/pull/1492) -* Repo size - * Always create new orphaned branch for gh-pages [#1490](https://github.com/rerun-io/rerun/pull/1490) - * GitHub Action to prevent large files [#1478](https://github.com/rerun-io/rerun/pull/1478) -* Python - * Remove the python job path filters [#1452](https://github.com/rerun-io/rerun/pull/1452) - * Use ruff for our python lints [#1378](https://github.com/rerun-io/rerun/pull/1378) - * Use python3 in the jobs that weren't tested in PR [#1348](https://github.com/rerun-io/rerun/pull/1348) -* Testing - * Add a test of memory use when logging a lot of big images [#1372](https://github.com/rerun-io/rerun/pull/1372) -* Switch ci_docker to a container based on ubuntu 20.04 [#1334](https://github.com/rerun-io/rerun/pull/1334) -* Release handling - * Switch release action to ncipollo [#1489](https://github.com/rerun-io/rerun/pull/1489) - * Fix our continuous pre-releases [#1458](https://github.com/rerun-io/rerun/pull/1458) - * Delete the prerelease before creating the new one [#1485](https://github.com/rerun-io/rerun/pull/1485) - * Set prerelease to true even for version-tagged CI job [#1504](https://github.com/rerun-io/rerun/pull/1504) - * Let the release job take care of creating the tag [#1501](https://github.com/rerun-io/rerun/pull/1501) - * Use `cargo update -w` instead of `cargo check` when prepping prerelease [#1500](https://github.com/rerun-io/rerun/pull/1500) - * Use prerelease tag instead of latest and update pointer on prerelease [#1481](https://github.com/rerun-io/rerun/pull/1481) - * Include date in pre-release version [#1472](https://github.com/rerun-io/rerun/pull/1472) - * Switch pre-release action to ncipollo/release-action [#1466](https://github.com/rerun-io/rerun/pull/1466) -* Disallow some methods and types via Clippy[#1411](https://github.com/rerun-io/rerun/pull/1411) - -#### Other non-user-facing refactors -* Fix: don't create a dummy LogDb when opening the Rerun Menu [#1440](https://github.com/rerun-io/rerun/pull/1440) -* `re_renderer` - * `Draw Phases` in preparation of executing `Renderer` several times on different targets [#1419](https://github.com/rerun-io/rerun/pull/1419) - * Fix mesh creation failing to copy index data. [#1473](https://github.com/rerun-io/rerun/pull/1473) - * do not silently drop draw phases [#1471](https://github.com/rerun-io/rerun/pull/1471) - * Simplify bind group allocation call by passing pool collection object. [#1459](https://github.com/rerun-io/rerun/pull/1459) - * Interior mutable buffer/texture/bindgroup pools [#1374](https://github.com/rerun-io/rerun/pull/1374) - * Rename all instances of `frame_maintenance` to `begin_frame` [#1360](https://github.com/rerun-io/rerun/pull/1360) - * Texture & buffer call now wgpu's `destroy` on removal from pool [#1359](https://github.com/rerun-io/rerun/pull/1359) - * Arrow buffers as (optional) first-class citizen [#1482](https://github.com/rerun-io/rerun/pull/1482) - * Log static re_renderer resource generation [#1464](https://github.com/rerun-io/rerun/pull/1464) -* Internal log_text_entry_internal to break circular deps [#1488](https://github.com/rerun-io/rerun/pull/1488) -* Delete ClassicTensor and cleanup [#1456](https://github.com/rerun-io/rerun/pull/1456) -* Fix re_renderer file watcher watching the same file several times [#1463](https://github.com/rerun-io/rerun/pull/1463) -* Analytics - * More ergonomic API [#1410](https://github.com/rerun-io/rerun/pull/1410) - * Streamlining host vs. recorder python/rust versions [#1380](https://github.com/rerun-io/rerun/pull/1380) - * Fix workspace detection [#1437](https://github.com/rerun-io/rerun/pull/1437) -* Introduce `DeserializableComponent` trait and high-level `query_latest` [#1417](https://github.com/rerun-io/rerun/pull/1417) - - -[Full Changelog](https://github.com/rerun-io/rerun/compare/v0.2.0...v0.3.0) - -## 0.2.0 -First public release! +Beta release of the new Depthai Viewer. diff --git a/Cargo.lock b/Cargo.lock index 60f09380c940..681b3ab79782 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "330223a1aecc308757b9926e9391c9b47f8ef2dbd8aea9df88312aea18c5e8d6" [[package]] name = "accesskit" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4803cf8c252f374ae6bfbb341e49e5a37f7601f2ce74a105927a663eba952c67" +checksum = "704d532b1cd3d912bb37499c55a81ac748cc1afa737eedd100ba441acdd47d38" dependencies = [ "enumn", "serde", @@ -84,7 +84,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4165a1aef703232031b40a6e8908c2f9e314d495f11aa7f98db75d39a497cc6a" dependencies = [ "android-properties", - "bitflags", + "bitflags 1.3.2", "cc", "jni-sys", "libc", @@ -124,16 +124,16 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "api_demo" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", "clap 4.1.4", + "depthai-viewer", "glam", "itertools", "ndarray", "ndarray-rand", "rand", - "rerun", ] [[package]] @@ -298,6 +298,21 @@ dependencies = [ "slab", ] +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + [[package]] name = "async-io" version = "1.13.0" @@ -339,6 +354,32 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -377,6 +418,12 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + [[package]] name = "atomic_refcell" version = "0.1.8" @@ -467,6 +514,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" + [[package]] name = "block" version = "0.1.6" @@ -501,6 +554,21 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "log", +] + [[package]] name = "bumpalo" version = "3.11.1" @@ -572,12 +640,13 @@ dependencies = [ [[package]] name = "cargo-run-wasm" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611b811fad83eebfcdcf47ae1e425c82d1249608bc571d537448d706be08cf27" +checksum = "cc1e37cf14ef470ed74ec2a8b95e51b8623bcf6f76d24f233ebaeb209f766230" dependencies = [ "devserver_lib", "pico-args", + "serde_json", "wasm-bindgen-cli-support", ] @@ -676,7 +745,7 @@ version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ - "bitflags", + "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap", "textwrap", @@ -688,7 +757,7 @@ version = "4.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" dependencies = [ - "bitflags", + "bitflags 1.3.2", "clap_derive", "clap_lex 0.3.0", "is-terminal", @@ -751,7 +820,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", @@ -767,7 +836,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "core-foundation", "core-graphics-types", @@ -907,7 +976,7 @@ version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-graphics-types", "foreign-types", @@ -920,7 +989,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "foreign-types", "libc", @@ -1053,7 +1122,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossterm_winapi", "libc", "mio", @@ -1088,6 +1157,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.103", +] + [[package]] name = "ctrlc" version = "3.2.2" @@ -1098,12 +1177,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - [[package]] name = "cxx" version = "1.0.82" @@ -1151,10 +1224,9 @@ dependencies = [ [[package]] name = "d3d12" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f0de2f5a8e7bd4a9eec0e3c781992a4ce1724f68aec7d7a3715344de8b39da" +source = "git+https://github.com/gfx-rs/d3d12-rs?rev=b940b1d71#b940b1d71ab7083ae80eec697872672dc1f2bd32" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libloading", "winapi", ] @@ -1193,6 +1265,38 @@ dependencies = [ "syn 1.0.103", ] +[[package]] +name = "depthai-viewer" +version = "0.6.0-alpha.0" +dependencies = [ + "anyhow", + "backtrace", + "clap 4.1.4", + "ctrlc", + "document-features", + "itertools", + "libc", + "mimalloc", + "parking_lot 0.12.1", + "re_analytics", + "re_build_build_info", + "re_build_info", + "re_data_store", + "re_format", + "re_log", + "re_log_encoding", + "re_log_types", + "re_memory", + "re_sdk", + "re_sdk_comms", + "re_smart_channel", + "re_viewer", + "re_web_viewer_server", + "re_ws_comms", + "tokio", + "webbrowser", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1278,11 +1382,11 @@ dependencies = [ [[package]] name = "dna" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ + "depthai-viewer", "itertools", "rand", - "rerun", ] [[package]] @@ -1309,8 +1413,7 @@ checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "ecolor" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f99fe3cac305af9d6d92971af60d0f7ea4d783201ef1673571567b6699964d9" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "bytemuck", "serde", @@ -1319,16 +1422,19 @@ dependencies = [ [[package]] name = "eframe" version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df3ce60931e5f2d83bab4484d1a283908534d5308cc6b0c5c22c59cd15ee7cc" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "bytemuck", + "cocoa", "directories-next", "egui", "egui-wgpu", "egui-winit", "egui_glow", + "image", "js-sys", + "log", + "objc", "percent-encoding", "pollster", "puffin", @@ -1336,39 +1442,38 @@ dependencies = [ "ron", "serde", "thiserror", - "tracing", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "wgpu", + "winapi", "winit", ] [[package]] name = "egui" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6412a21e0bde7c0918f7fb44bbbb86b5e1f88e63c026a4e747cc7af02f76dfbe" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "accesskit", "ahash 0.8.2", "epaint", + "log", "nohash-hasher", "ron", "serde", - "tracing", ] [[package]] name = "egui-wgpu" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1678d8f30181193e78c15dddae16e6027c6058fdda9631950ade511b8a4b26f5" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "bytemuck", "epaint", + "log", "puffin", - "tracing", + "thiserror", "type-map", "wgpu", "winit", @@ -1377,17 +1482,16 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab43597ba41f0ce39a364ad83185594578bfd8b3409b99dbcbb01df23afc3dbb" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ - "android-activity", "arboard", "egui", "instant", + "log", "puffin", + "raw-window-handle", "serde", "smithay-clipboard", - "tracing", "webbrowser", "winit", ] @@ -1405,27 +1509,25 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f051342e97dfa2445107cb7d2e720617f5c840199b5cb4fe0ffcf481fcf5cce" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "egui", + "log", "serde", - "tracing", ] [[package]] name = "egui_glow" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8257332fb168a965b3dca81d6a344e053153773c889cabdba0b3b76f1629ae81" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "bytemuck", "egui", "egui-winit", "glow", + "log", "memoffset 0.6.5", "puffin", - "tracing", "wasm-bindgen", "web-sys", ] @@ -1452,8 +1554,7 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "emath" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ecd80612937e0267909d5351770fe150004e24dab93954f69ca62eecd3f77e" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "bytemuck", "serde", @@ -1534,8 +1635,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e78b5c58a1f7f621f9d546add2adce20636422c9b251e29f749e8a2f713c95" +source = "git+https://github.com/emilk/egui?rev=f76eefb98d23cbf71989255aafe75a07d343f6ed#f76eefb98d23cbf71989255aafe75a07d343f6ed" dependencies = [ "ab_glyph", "ahash 0.8.2", @@ -1543,6 +1643,7 @@ dependencies = [ "bytemuck", "ecolor", "emath", + "log", "nohash-hasher", "parking_lot 0.12.1", "serde", @@ -1834,15 +1935,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -1899,11 +1991,23 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "glow" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8edf6019dff2d92ad27c1e3ff82ad50a0aea5b01370353cc928bfdc33e95925c" +checksum = "4e007a07a24de5ecae94160f141029e9a347282cfe25d1d58d85d845cf3130f1" dependencies = [ "js-sys", "slotmap", @@ -1955,7 +2059,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fc59e5f710e310e76e6707f86c561dd646f69a8876da9131703b2f717de818d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gpu-alloc-types", ] @@ -1965,7 +2069,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54804d0d6bc9d7f26db4eaec1ad10def69b599315f487d32c334a80d1efe67a5" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1987,7 +2091,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "gpu-descriptor-types", "hashbrown 0.12.3", ] @@ -1998,7 +2102,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "363e3677e55ad168fef68cf9de3a4a310b53124c5e784c53a1d70e92d23f2126" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -2064,11 +2168,11 @@ dependencies = [ [[package]] name = "hassle-rs" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90601c6189668c7345fc53842cb3f3a3d872203d523be1b3cb44a36a3e62fb85" +checksum = "1397650ee315e8891a0df210707f0fc61771b0cc518c3023896064c5407cb3b0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "com-rs", "libc", "libloading", @@ -2282,7 +2386,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ - "bitflags", + "bitflags 1.3.2", "inotify-sys", "libc", ] @@ -2429,10 +2533,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2591,6 +2704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", + "value-bag", ] [[package]] @@ -2689,7 +2803,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de11355d1f6781482d027a3b4d4de7825dcedb197bf573e0596d00008402d060" dependencies = [ - "bitflags", + "bitflags 1.3.2", "block", "core-graphics-types", "foreign-types", @@ -2708,9 +2822,9 @@ dependencies = [ [[package]] name = "minimal" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ - "rerun", + "depthai-viewer", ] [[package]] @@ -2721,12 +2835,12 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minimal_options" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", "clap 4.1.4", + "depthai-viewer", "glam", - "rerun", ] [[package]] @@ -2784,12 +2898,11 @@ dependencies = [ [[package]] name = "naga" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eafe22a23b797c9bc227c6c896419b26b5bb88fa903417a3adaed08778850d5" +version = "0.12.0" +source = "git+https://github.com/gfx-rs/naga?rev=b99d58ea435090e561377949f428bce2c18451bb#b99d58ea435090e561377949f428bce2c18451bb" dependencies = [ "bit-set", - "bitflags", + "bitflags 1.3.2", "codespan-reporting", "hexf-parse", "indexmap", @@ -2832,7 +2945,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ - "bitflags", + "bitflags 1.3.2", "jni-sys", "ndk-sys", "num_enum", @@ -2855,13 +2968,19 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "never" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" + [[package]] name = "nix" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cc", "cfg-if", "libc", @@ -2874,7 +2993,7 @@ version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.6.5", @@ -2902,7 +3021,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossbeam-channel", "filetime", "fsevent-sys", @@ -3134,14 +3253,14 @@ dependencies = [ [[package]] name = "objectron" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", "clap 4.1.4", + "depthai-viewer", "glam", "prost", "prost-build", - "rerun", ] [[package]] @@ -3354,7 +3473,7 @@ version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "flate2", "miniz_oxide", @@ -3381,7 +3500,7 @@ dependencies = [ "ahash 0.8.2", "anyhow", "arrow2", - "bitflags", + "bitflags 1.3.2", "chrono", "comfy-table 6.1.4", "hashbrown 0.13.1", @@ -3621,6 +3740,31 @@ dependencies = [ "unindent", ] +[[package]] +name = "pyo3-asyncio" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3564762e37035cfc486228e10b0528460fa026d681b5763873c693aa0d5c260" +dependencies = [ + "futures", + "once_cell", + "pin-project-lite", + "pyo3", + "pyo3-asyncio-macros", + "tokio", +] + +[[package]] +name = "pyo3-asyncio-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be72d4cd43a27530306bd0d20d3932182fbdd072c6b98d3638bc37efb9d559dd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.103", +] + [[package]] name = "pyo3-build-config" version = "0.18.0" @@ -3721,23 +3865,20 @@ checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6" [[package]] name = "raw-window-handle" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" -dependencies = [ - "cty", -] +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "raw_mesh" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", "bytes", "clap 4.1.4", + "depthai-viewer", "gltf", "mimalloc", - "rerun", ] [[package]] @@ -3770,7 +3911,7 @@ dependencies = [ [[package]] name = "re_analytics" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", "crossbeam", @@ -3791,7 +3932,7 @@ dependencies = [ [[package]] name = "re_arrow_store" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "anyhow", @@ -3818,7 +3959,7 @@ dependencies = [ [[package]] name = "re_build_build_info" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", "time 0.3.20", @@ -3826,18 +3967,18 @@ dependencies = [ [[package]] name = "re_build_info" -version = "0.4.0" +version = "0.6.0-alpha.0" [[package]] name = "re_build_web_viewer" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "cargo_metadata", ] [[package]] name = "re_data_store" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "criterion", @@ -3860,14 +4001,14 @@ dependencies = [ [[package]] name = "re_error" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", ] [[package]] name = "re_format" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "arrow2", "arrow2_convert", @@ -3877,7 +4018,7 @@ dependencies = [ [[package]] name = "re_int_histogram" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "criterion", "insta", @@ -3888,7 +4029,7 @@ dependencies = [ [[package]] name = "re_log" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "env_logger", "js-sys", @@ -3901,7 +4042,7 @@ dependencies = [ [[package]] name = "re_log_encoding" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "criterion", "ehttp", @@ -3926,7 +4067,7 @@ dependencies = [ [[package]] name = "re_log_types" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "array-init", @@ -3964,7 +4105,7 @@ dependencies = [ [[package]] name = "re_memory" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "backtrace", @@ -3984,7 +4125,7 @@ dependencies = [ [[package]] name = "re_query" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "arrow2", "criterion", @@ -4002,13 +4143,13 @@ dependencies = [ [[package]] name = "re_renderer" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "anyhow", "arrow2", "async-executor", - "bitflags", + "bitflags 1.3.2", "bytemuck", "clean-path", "console_error_panic_hook", @@ -4026,6 +4167,7 @@ dependencies = [ "itertools", "log", "macaw", + "never", "notify", "ordered-float", "parking_lot 0.12.1", @@ -4048,14 +4190,13 @@ dependencies = [ "web-sys", "wgpu", "wgpu-core", - "wgpu-hal", "winit", "zip", ] [[package]] name = "re_sdk" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "arrow2_convert", "document-features", @@ -4075,7 +4216,7 @@ dependencies = [ [[package]] name = "re_sdk_comms" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "anyhow", @@ -4091,7 +4232,7 @@ dependencies = [ [[package]] name = "re_smart_channel" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "crossbeam", "instant", @@ -4099,7 +4240,7 @@ dependencies = [ [[package]] name = "re_string_interner" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "nohash-hasher", @@ -4110,7 +4251,7 @@ dependencies = [ [[package]] name = "re_tensor_ops" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "ndarray", @@ -4120,7 +4261,7 @@ dependencies = [ [[package]] name = "re_tuid" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "arrow2", "arrow2_convert", @@ -4134,7 +4275,7 @@ dependencies = [ [[package]] name = "re_ui" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "eframe", "egui", @@ -4152,20 +4293,23 @@ dependencies = [ [[package]] name = "re_viewer" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "ahash 0.8.2", "anyhow", "arboard", + "async-std", "bytemuck", "cocoa", "console_error_panic_hook", + "crossbeam-channel", "eframe", "egui", "egui-wgpu", "egui_dock", "egui_extras", "enumset", + "ewebsock", "glam", "half 2.2.1", "image", @@ -4179,6 +4323,8 @@ dependencies = [ "poll-promise", "puffin", "puffin_http", + "pyo3", + "pyo3-asyncio", "re_analytics", "re_arrow_store", "re_build_build_info", @@ -4198,10 +4344,15 @@ dependencies = [ "re_ws_comms", "rfd", "serde", + "serde_json", "slotmap", "smallvec", + "strum 0.24.1", + "strum_macros 0.24.3", "thiserror", "time 0.3.20", + "tokio", + "url", "uuid", "vec1", "wasm-bindgen-futures", @@ -4212,7 +4363,7 @@ dependencies = [ [[package]] name = "re_web_viewer_server" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "cargo_metadata", "ctrlc", @@ -4229,7 +4380,7 @@ dependencies = [ [[package]] name = "re_ws_comms" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "anyhow", "bincode", @@ -4253,7 +4404,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -4262,7 +4413,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb02a9aee8e8c7ad8d86890f1e16b49e0bbbffc9961ff3788c31d57c98bcbf03" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -4295,47 +4446,16 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "renderdoc-sys" -version = "0.7.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" - -[[package]] -name = "rerun" -version = "0.4.0" -dependencies = [ - "anyhow", - "backtrace", - "clap 4.1.4", - "ctrlc", - "document-features", - "itertools", - "libc", - "mimalloc", - "parking_lot 0.12.1", - "re_analytics", - "re_build_build_info", - "re_build_info", - "re_data_store", - "re_format", - "re_log", - "re_log_encoding", - "re_log_types", - "re_memory", - "re_sdk", - "re_sdk_comms", - "re_smart_channel", - "re_viewer", - "re_web_viewer_server", - "re_ws_comms", - "tokio", - "webbrowser", -] +checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" [[package]] name = "rerun_py" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "arrow2", + "depthai-viewer", "document-features", "glam", "image", @@ -4356,7 +4476,6 @@ dependencies = [ "re_memory", "re_web_viewer_server", "re_ws_comms", - "rerun", "thiserror", "tokio", "uuid", @@ -4431,13 +4550,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff" dependencies = [ "base64 0.13.1", - "bitflags", + "bitflags 1.3.2", "serde", ] [[package]] name = "run_wasm" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ "cargo-run-wasm", "pico-args", @@ -4471,7 +4590,7 @@ version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.2.8", "io-lifetimes", "libc", @@ -4485,7 +4604,7 @@ version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.3.0", "io-lifetimes", "libc", @@ -4777,7 +4896,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" dependencies = [ - "bitflags", + "bitflags 1.3.2", "calloop", "dlib", "lazy_static", @@ -4822,7 +4941,7 @@ version = "0.2.0+1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" dependencies = [ - "bitflags", + "bitflags 1.3.2", "num-traits", ] @@ -4991,11 +5110,11 @@ dependencies = [ [[package]] name = "test_image_memory" -version = "0.4.0" +version = "0.6.0-alpha.0" dependencies = [ + "depthai-viewer", "mimalloc", "re_format", - "rerun", ] [[package]] @@ -5443,6 +5562,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "vec1" version = "1.10.1" @@ -5712,7 +5841,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" dependencies = [ - "bitflags", + "bitflags 1.3.2", "downcast-rs", "libc", "nix 0.24.2", @@ -5751,7 +5880,7 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "wayland-client", "wayland-commons", "wayland-scanner", @@ -5842,9 +5971,8 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d745a1b6d91d85c33defbb29f0eee0450e1d2614d987e14bf6baf26009d132d7" +version = "0.16.0" +source = "git+https://github.com/rerun-io/wgpu?rev=de497aeda152a3515bac5eb4bf1b17f1757b9dac#de497aeda152a3515bac5eb4bf1b17f1757b9dac" dependencies = [ "arrayvec", "cfg-if", @@ -5866,20 +5994,19 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131408d940e335792645a98f03639573b0480e9e2e7cddbbab74f7c6d9f3fff" +version = "0.16.0" +source = "git+https://github.com/rerun-io/wgpu?rev=de497aeda152a3515bac5eb4bf1b17f1757b9dac#de497aeda152a3515bac5eb4bf1b17f1757b9dac" dependencies = [ "arrayvec", "bit-vec", - "bitflags", + "bitflags 2.2.1", "codespan-reporting", - "fxhash", "log", "naga", "parking_lot 0.12.1", "profiling", "raw-window-handle", + "rustc-hash", "smallvec", "thiserror", "web-sys", @@ -5889,20 +6016,18 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdcf61a283adc744bb5453dd88ea91f3f86d5ca6b027661c6c73c7734ae0288b" +version = "0.16.0" +source = "git+https://github.com/rerun-io/wgpu?rev=de497aeda152a3515bac5eb4bf1b17f1757b9dac#de497aeda152a3515bac5eb4bf1b17f1757b9dac" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags", + "bitflags 2.2.1", "block", "core-graphics-types", "d3d12", "foreign-types", - "fxhash", "glow", "gpu-alloc", "gpu-allocator", @@ -5921,6 +6046,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", + "rustc-hash", "smallvec", "thiserror", "wasm-bindgen", @@ -5931,11 +6057,10 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32444e121b0bd00cb02c0de32fde457a9491bd44e03e7a5db6df9b1da2f6f110" +version = "0.16.0" +source = "git+https://github.com/rerun-io/wgpu?rev=de497aeda152a3515bac5eb4bf1b17f1757b9dac#de497aeda152a3515bac5eb4bf1b17f1757b9dac" dependencies = [ - "bitflags", + "bitflags 2.2.1", "js-sys", "web-sys", ] @@ -5953,9 +6078,9 @@ dependencies = [ [[package]] name = "widestring" -version = "0.5.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "winapi" @@ -6094,7 +6219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4755d4ba0e3d30fc7beef2095e246b1e6a6fad0717608bcb87a2df4b003bcf" dependencies = [ "android-activity", - "bitflags", + "bitflags 1.3.2", "cfg_aliases", "core-foundation", "core-graphics", diff --git a/Cargo.toml b/Cargo.toml index 6a433c6954ee..bd3b9f138eec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,39 +16,39 @@ include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "Cargo.toml"] license = "MIT OR Apache-2.0" repository = "https://github.com/rerun-io/rerun" rust-version = "1.67" -version = "0.4.0" +version = "0.6.0-alpha.0" [workspace.dependencies] # When using alpha-release, always use exact version, e.g. `version = "=0.x.y-alpha.z" # This is because we treat alpha-releases as incompatible, but semver doesn't. # In particular: if we compile rerun 0.3.0-alpha.0 we only want it to use # re_log_types 0.3.0-alpha.0, NOT 0.3.0-alpha.4 even though it is newer and semver-compatible. -re_analytics = { path = "crates/re_analytics", version = "0.4.0" } -re_arrow_store = { path = "crates/re_arrow_store", version = "0.4.0" } -re_build_build_info = { path = "crates/re_build_build_info", version = "0.4.0" } -re_build_info = { path = "crates/re_build_info", version = "0.4.0" } -re_build_web_viewer = { path = "crates/re_build_web_viewer", version = "0.4.0" } -re_data_store = { path = "crates/re_data_store", version = "0.4.0" } -re_error = { path = "crates/re_error", version = "0.4.0" } -re_format = { path = "crates/re_format", version = "0.4.0" } -re_int_histogram = { path = "crates/re_int_histogram", version = "0.4.0" } -re_log = { path = "crates/re_log", version = "0.4.0" } -re_log_encoding = { path = "crates/re_log_encoding", version = "0.4.0" } -re_log_types = { path = "crates/re_log_types", version = "0.4.0" } -re_memory = { path = "crates/re_memory", version = "0.4.0" } -re_query = { path = "crates/re_query", version = "0.4.0" } -re_renderer = { path = "crates/re_renderer", version = "0.4.0" } -re_sdk = { path = "crates/re_sdk", version = "0.4.0" } -re_sdk_comms = { path = "crates/re_sdk_comms", version = "0.4.0" } -re_smart_channel = { path = "crates/re_smart_channel", version = "0.4.0" } -re_string_interner = { path = "crates/re_string_interner", version = "0.4.0" } -re_tensor_ops = { path = "crates/re_tensor_ops", version = "0.4.0" } -re_tuid = { path = "crates/re_tuid", version = "0.4.0" } -re_ui = { path = "crates/re_ui", version = "0.4.0" } -re_viewer = { path = "crates/re_viewer", version = "0.4.0" } -re_web_viewer_server = { path = "crates/re_web_viewer_server", version = "0.4.0" } -re_ws_comms = { path = "crates/re_ws_comms", version = "0.4.0" } -rerun = { path = "crates/rerun", version = "0.4.0" } +re_sdk_comms = { path = "crates/re_sdk_comms", version = "=0.6.0-alpha.0" } +re_analytics = { path = "crates/re_analytics", version = "=0.6.0-alpha.0" } +re_arrow_store = { path = "crates/re_arrow_store", version = "=0.6.0-alpha.0" } +re_build_build_info = { path = "crates/re_build_build_info", version = "=0.6.0-alpha.0" } +re_build_info = { path = "crates/re_build_info", version = "=0.6.0-alpha.0" } +re_build_web_viewer = { path = "crates/re_build_web_viewer", version = "=0.6.0-alpha.0" } +re_data_store = { path = "crates/re_data_store", version = "=0.6.0-alpha.0" } +re_error = { path = "crates/re_error", version = "=0.6.0-alpha.0" } +re_format = { path = "crates/re_format", version = "=0.6.0-alpha.0" } +re_int_histogram = { path = "crates/re_int_histogram", version = "=0.6.0-alpha.0" } +re_log = { path = "crates/re_log", version = "=0.6.0-alpha.0" } +re_log_encoding = { path = "crates/re_log_encoding", version = "=0.6.0-alpha.0" } +re_log_types = { path = "crates/re_log_types", version = "=0.6.0-alpha.0" } +re_memory = { path = "crates/re_memory", version = "=0.6.0-alpha.0" } +re_query = { path = "crates/re_query", version = "=0.6.0-alpha.0" } +re_renderer = { path = "crates/re_renderer", version = "=0.6.0-alpha.0", default-features = false } +re_sdk = { path = "crates/re_sdk", version = "=0.6.0-alpha.0" } +re_smart_channel = { path = "crates/re_smart_channel", version = "=0.6.0-alpha.0" } +re_string_interner = { path = "crates/re_string_interner", version = "=0.6.0-alpha.0" } +re_tensor_ops = { path = "crates/re_tensor_ops", version = "=0.6.0-alpha.0" } +re_tuid = { path = "crates/re_tuid", version = "=0.6.0-alpha.0" } +re_ui = { path = "crates/re_ui", version = "=0.6.0-alpha.0" } +re_viewer = { path = "crates/re_viewer", version = "=0.6.0-alpha.0", default-features = false } +re_web_viewer_server = { path = "crates/re_web_viewer_server", version = "=0.6.0-alpha.0" } +re_ws_comms = { path = "crates/re_ws_comms", version = "=0.6.0-alpha.0" } +depthai-viewer = { path = "crates/rerun", version = "=0.6.0-alpha.0" } ahash = "0.8" anyhow = "1.0" @@ -59,10 +59,10 @@ comfy-table = { version = "6.1", default-features = false } ctrlc = { version = "3.0", features = ["termination"] } ecolor = "0.21.0" eframe = { version = "0.21.3", default-features = false } -egui = "0.21.0" +egui = { version = "0.21.0", features = ["extra_debug_asserts", "log"] } egui-wgpu = "0.21.0" egui_dock = "0.4" -egui_extras = "0.21.0" +egui_extras = { version = "0.21.0", features = ["log"] } emath = "0.21.0" enumset = "1.0.12" epaint = "0.21.0" @@ -85,9 +85,8 @@ thiserror = "1.0" time = { version = "0.3", features = ["wasm-bindgen"] } tinyvec = { version = "1.6", features = ["alloc", "rustc_1_55"] } tokio = "1.24" -wgpu = { version = "0.15.1", default-features = false } -wgpu-core = { version = "0.15.1", default-features = false } -wgpu-hal = { version = "0.15.4", default-features = false } +wgpu = { version = "0.16" } +wgpu-core = { version = "0.16" } [profile.dev] @@ -112,3 +111,17 @@ debug = true # If that is not possible, patch to a branch that has a PR open on the upstream repo. # As a last resport, patch with a commit to our own repository. # ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk. + +# TODO(andreas/emilk): Update to a stable egui version +# wgpu 0.16 support, device configuration dependent on adapter +ecolor = { git = "https://github.com/emilk/egui", rev = "f76eefb98d23cbf71989255aafe75a07d343f6ed" } +eframe = { git = "https://github.com/emilk/egui", rev = "f76eefb98d23cbf71989255aafe75a07d343f6ed" } +egui = { git = "https://github.com/emilk/egui", rev = "f76eefb98d23cbf71989255aafe75a07d343f6ed" } +egui-wgpu = { git = "https://github.com/emilk/egui", rev = "f76eefb98d23cbf71989255aafe75a07d343f6ed" } +egui_extras = { git = "https://github.com/emilk/egui", rev = "f76eefb98d23cbf71989255aafe75a07d343f6ed" } +emath = { git = "https://github.com/emilk/egui", rev = "f76eefb98d23cbf71989255aafe75a07d343f6ed" } + +# TODO(andreas): Either work around this issue in wgpu-egui (never discard command buffers) or wait for wgpu patch release. +# Fix for command buffer dropping crash https://github.com/gfx-rs/wgpu/pull/3726 +wgpu = { git = "https://github.com/rerun-io/wgpu", rev = "de497aeda152a3515bac5eb4bf1b17f1757b9dac" } +wgpu-core = { git = "https://github.com/rerun-io/wgpu", rev = "de497aeda152a3515bac5eb4bf1b17f1757b9dac" } diff --git a/README.md b/README.md index bae9d9f0f30c..69788eb69ebd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- PyPi + PyPi crates.io MIT Apache @@ -19,11 +19,11 @@ Use one of our logging APIs (Python or Rust) to log rich data, such as images an ```py import rerun as rr -rr.init("my_app", spawn = True) # Spawn a Rerun Viewer and stream log events to it +viewer.init("my_app", spawn = True) # Spawn a Rerun Viewer and stream log events to it -rr.log_image("rgb_image", image) -rr.log_points("points", positions) -rr.log_rect("car", bbox) +viewer.log_image("rgb_image", image) +viewer.log_points("points", positions) +viewer.log_rect("car", bbox) … ``` @@ -32,57 +32,59 @@ rr.log_rect("car", bbox)

## Getting started -* **Python**: `pip install rerun-sdk` -* **Rust**: `cargo add rerun` -* **C / C++**: Coming soon + +- **Python**: `pip install depthai-viewer` +- **Rust**: `cargo add rerun` +- **C / C++**: Coming soon ### Rerun Viewer binary + Both the Python and Rust library can start the Rerun Viewer, but to stream log data over the network or load our `.rrd` data files you also need the `rerun` binary. -It can be installed with `pip install rerun-sdk` or with `cargo install rerun`. +It can be installed with `pip install depthai-viewer` or with `cargo install rerun`. You should now be able to run `rerun --help` in any terminal. - ### Documentation + - 📚 [High-level docs](http://rerun.io/docs) - ⚙️ [Examples](examples) - 🐍 [Python API docs](https://ref.rerun.io/docs/python) - 🦀 [Rust API docs](https://docs.rs/rerun/) - ⁉️ [Troubleshooting](https://www.rerun.io/docs/getting-started/troubleshooting) - ## Status + We are in early beta. There are many features we want to add, and the API is still evolving. _Expect breaking changes!_ Some shortcomings: -* Big points clouds (1M+) are slow ([#1136](https://github.com/rerun-io/rerun/issues/1136)) -* The data you want to visualize must fit in RAM. + +- Big points clouds (1M+) are slow ([#1136](https://github.com/rerun-io/rerun/issues/1136)) +- The data you want to visualize must fit in RAM. - See for how to bound memory use - We plan on having a disk-based data store some time in the future -* The Rust library takes a long time to compile +- The Rust library takes a long time to compile - We have way too many big dependencies, and we are planning on improving the situation ([#1316](https://github.com/rerun-io/rerun/pull/1316)) - ## Business model + Rerun uses an open-core model. Everything in this repository will stay open source and free (both as in beer and as in freedom). In the future, Rerun will offer a commercial product that builds on top of the core free project. The Rerun open source project targets the needs of individual developers. The commercial product targets the needs specific to teams that build and run computer vision and robotics products. - # Development -* [`ARCHITECTURE.md`](ARCHITECTURE.md) -* [`BUILD.md`](BUILD.md) -* [`rerun_py/README.md`](rerun_py/README.md) - build instructions for Python SDK -* [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) -* [`CODE_STYLE.md`](CODE_STYLE.md) -* [`CONTRIBUTING.md`](CONTRIBUTING.md) -* [`RELEASES.md`](RELEASES.md) +- [`ARCHITECTURE.md`](ARCHITECTURE.md) +- [`BUILD.md`](BUILD.md) +- [`rerun_py/README.md`](rerun_py/README.md) - build instructions for Python SDK +- [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) +- [`CODE_STYLE.md`](CODE_STYLE.md) +- [`CONTRIBUTING.md`](CONTRIBUTING.md) +- [`RELEASES.md`](RELEASES.md) ## Installing a pre-release Python SDK diff --git a/RELEASES.md b/RELEASES.md index 6ab1bded58fd..c83448189c04 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,24 +1,26 @@ # Releases and versioning -This document describes the current release and versioning strategy. This strategy is likely to change as Rerun matures. +This document describes the current release and versioning strategy. This strategy is likely to change as Rerun matures. ## See also -* [`ARCHITECTURE.md`](ARCHITECTURE.md) -* [`BUILD.md`](BUILD.md) -* [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) -* [`CODE_STYLE.md`](CODE_STYLE.md) -* [`CONTRIBUTING.md`](CONTRIBUTING.md) +- [`ARCHITECTURE.md`](ARCHITECTURE.md) +- [`BUILD.md`](BUILD.md) +- [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) +- [`CODE_STYLE.md`](CODE_STYLE.md) +- [`CONTRIBUTING.md`](CONTRIBUTING.md) ## Release Cadence -New Rerun versions are released every two weeks. Sometimes we do out-of-schedule patch releases. +New Rerun versions are released every two weeks. Sometimes we do out-of-schedule patch releases. ## Library versioning and release cadence + Each release include new versions of: -* The Python SDK -* The Rust SDK -* All rust crates + +- The Python SDK +- The Rust SDK +- All rust crates We use semantic versioning. All versions are increased in lockstep, with a minor version bump each time (`0.1.0`, `0.2.0`, `0.3.0`, …). @@ -28,121 +30,127 @@ In rare cases we will do patch releases, e.g. `0.3.1`, when there is a critical We sometimes do pre-releases. Then we use the versioning `0.2.0-alpha.0` etc. - ## Data and communication versioning + We have not yet committed to any backwards or forwards compatibility. We tag all data files (`.rrd` files) and communication protocols with the rerun version number. If there is a version mismatch, a warning is logged, but an attempt is still made to load the older or newer data. - ## Releases + Release builds of the Python Wheels are triggered by pushing a release tag to GitHub in the form `v0.2.0`. If we are doing a patch release, we do a branch off of the latest release tag (e.g. `v0.3.0`) and cherry-pick any fixes we want into that branch. ### Release checklist + Go through this checklist from top to bottom, and check each item before moving onto the next. This is a living document. Strive to improve it on each new release. -* [ ] Create a release branch called `release-0.x.y` -* [ ] If it is a patch release branch off `latest` and cherry-pick the commits that should be included -* [ ] Update `CHANGELOG.md` with the new version number with: - * [ ] A one-line summary of the release - * [ ] A multi-line summary of the release - * [ ] A gif showing a major new feature - * [ ] Run `pip install GitPython && scripts/generate_changelog.py` - * [ ] Edit PR descriptions/labels to improve the generated changelog - * [ ] Copy-paste the results into `CHANGELOG.md`. - * [ ] Editorialize the changelog if necessary - * [ ] Make sure the changelog includes instructions for handling any breaking changes - * [ ] Commit and push the changelog -* [ ] Create a draft PR containing: - * [ ] One-line summary of the release - * [ ] A multi-line summary of the release - * [ ] A gif showing a major new feature -* [ ] Test the branch ([see below](#testing-a-release)) -* [ ] Open the PR up for review with the `⛴ release` label -* [ ] Bump version number in root `Cargo.toml`. -* [ ] Check that CI is green -* [ ] Publish the crates (see below) -* [ ] `git tag -a v0.x.y -m 'Release 0.x.y - summary'` - * `git push --tags` - * This will trigger a PyPI release when pushed -* [ ] `git pull --tags && git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force` -* [ ] Manually trigger a new web viewer build and upload at https://github.com/rerun-io/rerun/actions/workflows/rust.yml -* [ ] Wait for CI to build release artifacts and publish them on GitHub and PyPI. -* [ ] Merge PR -* [ ] Edit the GitHub release at https://github.com/rerun-io/rerun/releases/edit/v0.x.0 - * [ ] Mark it as as the latest release - * [ ] Paste in the `CHANGELOG.md` -* [ ] Wait for wheel to appear on https://pypi.org/project/rerun-sdk/ -* [ ] Test the released Python and Rust libraries (see below) -* [ ] Wait for documentation to build: https://docs.rs/releases/queue -* [ ] Point to the latest release via instructions in . -* [ ] Post on: - * [ ] Community Discord - * [ ] Rerun Twitter - * [ ] Reddit? - +- [ ] Create a release branch called `release-0.x.y` +- [ ] If it is a patch release branch off `latest` and cherry-pick the commits that should be included +- [ ] Update `CHANGELOG.md` with the new version number with: + - [ ] A one-line summary of the release + - [ ] A multi-line summary of the release + - [ ] A gif showing a major new feature + - [ ] Run `pip install GitPython && scripts/generate_changelog.py` + - [ ] Edit PR descriptions/labels to improve the generated changelog + - [ ] Copy-paste the results into `CHANGELOG.md`. + - [ ] Editorialize the changelog if necessary + - [ ] Make sure the changelog includes instructions for handling any breaking changes + - [ ] Commit and push the changelog +- [ ] Create a draft PR containing: + - [ ] One-line summary of the release + - [ ] A multi-line summary of the release + - [ ] A gif showing a major new feature +- [ ] Test the branch ([see below](#testing-a-release)) +- [ ] Open the PR up for review with the `⛴ release` label +- [ ] Bump version number in root `Cargo.toml`. +- [ ] Check that CI is green +- [ ] Publish the crates (see below) +- [ ] `git tag -a v0.x.y -m 'Release 0.x.y - summary'` + - `git push --tags` + - This will trigger a PyPI release when pushed +- [ ] `git pull --tags && git tag -d latest && git tag -a latest -m 'Latest release' && git push --tags origin latest --force` +- [ ] Manually trigger a new web viewer build and upload at https://github.com/rerun-io/rerun/actions/workflows/rust.yml +- [ ] Wait for CI to build release artifacts and publish them on GitHub and PyPI. +- [ ] Merge PR +- [ ] Edit the GitHub release at https://github.com/rerun-io/rerun/releases/edit/v0.x.0 + - [ ] Mark it as as the latest release + - [ ] Paste in the `CHANGELOG.md` +- [ ] Wait for wheel to appear on https://pypi.org/project/depthai-viewer/ +- [ ] Test the released Python and Rust libraries (see below) +- [ ] Wait for documentation to build: https://docs.rs/releases/queue +- [ ] Point to the latest release via instructions in . +- [ ] Post on: + - [ ] Community Discord + - [ ] Rerun Twitter + - [ ] Reddit? ### Testing a release + Before pushing the release tag: - * [ ] `just py-run-all` - * [ ] Test the web viewer: - * [ ] `cargo run -p rerun --features web_viewer -- --web-viewer ../nyud.rrd` - * [ ] Test on: - * [ ] Chromium - * [ ] Firefox - * [ ] Mobile + +- [ ] `just py-run-all` +- [ ] Test the web viewer: + - [ ] `cargo run -p rerun --features web_viewer -- --web-viewer ../nyud.rrd` + - [ ] Test on: + - [ ] Chromium + - [ ] Firefox + - [ ] Mobile After tagging and the CI has published: - * [ ] Test the Python packages from PyPI: `pip install rerun_sdk==0.x.0a1` - * [ ] Test rust install version: `cargo install -f rerun@0.x.0-alpha.1 -F web_viewer && rerun --web-viewer api.rrd` - * [ ] Test rust crate: Modify Cargo.toml of any example to not point to the workspace - * [ ] run with `--serve` to test web player + +- [ ] Test the Python packages from PyPI: `pip install rerun_sdk==0.x.0a1` +- [ ] Test rust install version: `cargo install -f rerun@0.x.0-alpha.1 -F web_viewer && rerun --web-viewer api.rrd` +- [ ] Test rust crate: Modify Cargo.toml of any example to not point to the workspace + - [ ] run with `--serve` to test web player Checklist for testing alpha releases: -* Windows - * [ ] Python Wheel - * [ ] Web - * [ ] Native - * [ ] Rust crate - * [ ] Web - * [ ] Native - * [ ] Rust install - * [ ] Web - * [ ] Native -* Linux - * [ ] Python Wheel - * [ ] Web - * [ ] Native - * [ ] Rust crate - * [ ] Web - * [ ] Native - * [ ] Rust install - * [ ] Web - * [ ] Native -* Mac - * [ ] Python Wheel - * [ ] Web - * [ ] Native - * [ ] Rust crate - * [ ] Web - * [ ] Native - * [ ] Rust install - * [ ] Web - * [ ] Native +- Windows + - [ ] Python Wheel + - [ ] Web + - [ ] Native + - [ ] Rust crate + - [ ] Web + - [ ] Native + - [ ] Rust install + - [ ] Web + - [ ] Native +- Linux + - [ ] Python Wheel + - [ ] Web + - [ ] Native + - [ ] Rust crate + - [ ] Web + - [ ] Native + - [ ] Rust install + - [ ] Web + - [ ] Native +- Mac + - [ ] Python Wheel + - [ ] Web + - [ ] Native + - [ ] Rust crate + - [ ] Web + - [ ] Native + - [ ] Rust install + - [ ] Web + - [ ] Native ## Publishing + First login as https://crates.io/users/rerunio with and API key you get from Emil: ```bash cargo login $API_KEY ``` ------------------------------------------------------------------------------------------------ -!! IMPORTANT !! Shut off VSCode, and don't touch anything while `publish_crates.sh` is running! -!! IMPORTANT !! Read `publish_crates.sh` for details ------------------------------------------------------------------------------------------------ +--- + +!! IMPORTANT !! Shut off VSCode, and don't touch anything while `publish_crates.sh` is running! +!! IMPORTANT !! Read `publish_crates.sh` for details + +--- ./scripts/publish_crates.sh --execute diff --git a/api.py b/api.py new file mode 100644 index 000000000000..cdc7ee253283 --- /dev/null +++ b/api.py @@ -0,0 +1,50 @@ +import depthai_viewer as viewer +import cv2 +import depthai as dai +import queue + +viewer.init("Depthai Viewer") +viewer.connect() + +# Create pipeline +pipeline = dai.Pipeline() +color = pipeline.createColorCamera() +color.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P) +color.setFps(60) + +# Create output +xout = pipeline.createXLinkOut() +xout.setStreamName("color") +color.video.link(xout.input) + +q = queue.Queue(maxsize=4) + + +def color_frame_callback(frame: dai.ImgFrame): + global q + q.put(frame.getCvFrame()) + + +import time + +# Connect to device and start pipeline +with dai.Device(pipeline) as device: + q = device.getOutputQueue("color", maxSize=4, blocking=False) # .addCallback(color_frame_callback) + t = time.time_ns() + fps = 0 + display_fps = 0 + while True: + # in_color = q.get() + # cv2.imshow("color", in_color) + in_color = q.get() + fps += 1 + if time.time_ns() - t > 1e9: + display_fps = fps + fps = 0 + print("fps: ", display_fps) + t = time.time_ns() + frame = in_color.getCvFrame() + # cv2.putText(frame, f"fps: {display_fps:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2) + # cv2.imshow("color", frame) + viewer.log_image("color/camera/rgb/Color camera", cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) + cv2.waitKey(1) diff --git a/ci_docker/Dockerfile b/ci_docker/Dockerfile index 80c2f73bf820..f3fa89019ce5 100644 --- a/ci_docker/Dockerfile +++ b/ci_docker/Dockerfile @@ -58,7 +58,8 @@ RUN set -eux; \ # Install some cargo tools we know we'll always need # We can't do this until after we've installed rust / cargo above RUN cargo install cargo-deny && \ - cargo install cargo-cranky + cargo install cargo-cranky && \ + cargo install cargo-benchcmp # Install the python build dependencies ADD rerun_py/requirements-build.txt requirements-build.txt diff --git a/clippy.toml b/clippy.toml index 4da41d009fbc..bd8f68dcbf99 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,4 @@ -# There is also a clippy_wasm/clippy.toml which forbids some mthods that are not available in wasm. +# There is also a scripts/clippy_wasm/clippy.toml which forbids some methods that are not available in wasm. msrv = "1.67" @@ -47,7 +47,7 @@ disallowed-types = [ # Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ - # You must also update the same list in `clippy_wasm/clippy.toml`! + # You must also update the same list in `scripts/clippy_wasm/clippy.toml`! "GitHub", "GLB", "GLTF", diff --git a/crates/re_analytics/src/cli.rs b/crates/re_analytics/src/cli.rs index 7055563dcd92..4b146daa6996 100644 --- a/crates/re_analytics/src/cli.rs +++ b/crates/re_analytics/src/cli.rs @@ -83,7 +83,7 @@ const DETAILS: &str = " What data is collected? - The exact set of analytics events and parameters can be found here: https://github.com/rerun-io/rerun/blob/GIT_HASH/crates/re_viewer/src/viewer_analytics.rs - - We collect high level events about the usage of the Rerun Viewer. For example: + - We collect high level events about the usage of the Depthai Viewer. For example: - The event 'Viewer Opened' helps us estimate how often Rerun is used. - The event 'Data Source Connected' helps us understand if users tend to use live data sources or recordings most, which helps us prioritize features. diff --git a/crates/re_analytics/src/lib.rs b/crates/re_analytics/src/lib.rs index 9938887a5f55..8f346c3ea3df 100644 --- a/crates/re_analytics/src/lib.rs +++ b/crates/re_analytics/src/lib.rs @@ -211,7 +211,7 @@ const DISCLAIMER: &str = " help the Rerun team improve the library. Summary: - - We only collect high level events about the features used within the Rerun Viewer. + - We only collect high level events about the features used within the Depthai Viewer. - The actual data you log to Rerun, such as point clouds, images, or text logs, will never be collected. - We don't log IP addresses. diff --git a/crates/re_build_info/src/crate_version.rs b/crates/re_build_info/src/crate_version.rs index 9a29e8e734a7..a4c3ee5e04bf 100644 --- a/crates/re_build_info/src/crate_version.rs +++ b/crates/re_build_info/src/crate_version.rs @@ -55,7 +55,7 @@ impl CrateVersion { pub fn from_bytes([major, minor, patch, suffix_byte]: [u8; 4]) -> Self { let is_alpha = (suffix_byte & IS_ALPHA_BIT) != 0; let is_prerelease = (suffix_byte & IS_PRERELEASE_BIT) != 0; - let alpha_version = suffix_byte & 0b0111_1111; + let alpha_version = suffix_byte & !(IS_ALPHA_BIT | IS_PRERELEASE_BIT); Self { major, @@ -273,6 +273,22 @@ fn test_format_parse_roundtrip() { } } +#[test] +fn test_format_parse_roundtrip_bytes() { + let parse = CrateVersion::parse; + for version in [ + "0.2.0", + "1.2.3", + "12.23.24", + "12.23.24-alpha.31", + "12.23.24-alpha.31+foo", + ] { + let version = parse(version); + let bytes = version.to_bytes(); + assert_eq!(CrateVersion::from_bytes(bytes), version); + } +} + #[test] fn test_compatibility() { fn are_compatible(a: &str, b: &str) -> bool { diff --git a/crates/re_build_web_viewer/src/lib.rs b/crates/re_build_web_viewer/src/lib.rs index 8ec1d53f0a88..c5f1c179f10a 100644 --- a/crates/re_build_web_viewer/src/lib.rs +++ b/crates/re_build_web_viewer/src/lib.rs @@ -12,7 +12,7 @@ fn target_directory() -> Utf8PathBuf { } /// Build `re_viewer` as Wasm, generate .js bindings for it, and place it all into the `./web_viewer` folder. -pub fn build(release: bool) { +pub fn build(release: bool, webgpu: bool) { eprintln!("Building web viewer wasm…"); eprintln!("We assume you've already run ./scripts/setup_web.sh"); @@ -63,7 +63,13 @@ pub fn build(release: bool) { "wasm32-unknown-unknown", "--target-dir", target_wasm_dir.as_str(), + "--no-default-features", ]); + if webgpu { + cmd.arg("--features=analytics"); + } else { + cmd.arg("--features=analytics,webgl"); + } if release { cmd.arg("--release"); } @@ -71,6 +77,7 @@ pub fn build(release: bool) { // This is required to enable the web_sys clipboard API which egui_web uses // https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html // https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html + // Furthermore, it's necessary for unstable WebGPU apis to work. cmd.env("RUSTFLAGS", "--cfg=web_sys_unstable_apis"); // When executing this script from a Rust build script, do _not_, under any circumstances, diff --git a/crates/re_build_web_viewer/src/main.rs b/crates/re_build_web_viewer/src/main.rs index 137cd401a742..1c3d2249d26f 100644 --- a/crates/re_build_web_viewer/src/main.rs +++ b/crates/re_build_web_viewer/src/main.rs @@ -2,6 +2,7 @@ use std::process::ExitCode; fn main() -> ExitCode { let mut release = None; + let mut webgpu = false; for arg in std::env::args().skip(1) { match arg.as_str() { @@ -17,6 +18,9 @@ fn main() -> ExitCode { assert!(release.is_none(), "Can't set both --release and --debug"); release = Some(true); } + "--webgpu" => { + webgpu = true; + } _ => { print_help(); return ExitCode::FAILURE; @@ -29,7 +33,7 @@ fn main() -> ExitCode { return ExitCode::FAILURE; }; - re_build_web_viewer::build(release); + re_build_web_viewer::build(release, webgpu); ExitCode::SUCCESS } @@ -41,6 +45,7 @@ fn print_help() { --debug: Build a debug binary --release: Compile for release, and run wasm-opt. NOTE: --release also removes debug symbols which are otherwise useful for in-browser profiling. + --webgpu: Enable WebGPU support (experimental). If not set the viewer will use WebGL instead. " ); } diff --git a/crates/re_data_store/src/entity_properties.rs b/crates/re_data_store/src/entity_properties.rs index 9929ca4c13c3..96ccd7f91aed 100644 --- a/crates/re_data_store/src/entity_properties.rs +++ b/crates/re_data_store/src/entity_properties.rs @@ -44,9 +44,16 @@ pub struct EntityProperties { pub visible_history: ExtraQueryHistory, pub interactive: bool, + /// Enable color mapping? + /// /// What kind of color mapping should be applied (none, map, texture, transfer..)? pub color_mapper: EditableAutoValue, + /// Points to an entity with an albedo texture. + /// + /// Only relevant if [`Self::color_mapper`] is set to `AlbedoTexture`. + pub albedo_texture: Option, + /// Distance of the projection plane (frustum far plane). /// /// Only applies to pinhole cameras when in a spatial view, using 3D navigation. @@ -80,6 +87,7 @@ impl Default for EntityProperties { backproject_depth: EditableAutoValue::Auto(true), depth_from_world_scale: EditableAutoValue::default(), backproject_radius_scale: EditableAutoValue::Auto(1.0), + albedo_texture: None, } } } @@ -94,7 +102,7 @@ impl EntityProperties { interactive: self.interactive && child.interactive, color_mapper: self.color_mapper.or(&child.color_mapper).clone(), - + albedo_texture: self.albedo_texture.clone().or(child.albedo_texture.clone()), pinhole_image_plane_distance: self .pinhole_image_plane_distance .or(&child.pinhole_image_plane_distance) @@ -170,25 +178,28 @@ impl std::fmt::Display for Colormap { pub enum ColorMapper { /// Use a well-known color map, pre-implemented as a wgsl module. Colormap(Colormap), - // TODO(cmc): support textures. + + /// Point to an entity with an albedo texture. + AlbedoTexture, // TODO(cmc): support custom transfer functions. } +impl Default for ColorMapper { + #[inline] + fn default() -> Self { + Self::AlbedoTexture + } +} + impl std::fmt::Display for ColorMapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ColorMapper::Colormap(colormap) => colormap.fmt(f), + ColorMapper::Colormap(colormap) => write!(f, "Map:{colormap}"), + ColorMapper::AlbedoTexture => write!(f, "Albedo texture"), } } } -impl Default for ColorMapper { - #[inline] - fn default() -> Self { - Self::Colormap(Colormap::default()) - } -} - // ---------------------------------------------------------------------------- /// Get the latest value for a given [`re_log_types::Component`]. diff --git a/crates/re_format/Cargo.toml b/crates/re_format/Cargo.toml index 198768511bc0..b502f8f6eb4e 100644 --- a/crates/re_format/Cargo.toml +++ b/crates/re_format/Cargo.toml @@ -19,4 +19,4 @@ all-features = true arrow2.workspace = true arrow2_convert.workspace = true comfy-table.workspace = true -re_tuid.workspace = true +re_tuid = { workspace = true, features = ["arrow2_convert"] } diff --git a/crates/re_log_types/src/arrow_msg.rs b/crates/re_log_types/src/arrow_msg.rs index 54e5c01b68e5..b83e24266d29 100644 --- a/crates/re_log_types/src/arrow_msg.rs +++ b/crates/re_log_types/src/arrow_msg.rs @@ -42,13 +42,13 @@ impl serde::Serialize for ArrowMsg { let mut writer = StreamWriter::new(&mut buf, Default::default()); writer .start(&self.schema, None) - .map_err(|e| serde::ser::Error::custom(e.to_string()))?; + .map_err(|err| serde::ser::Error::custom(err.to_string()))?; writer .write(&self.chunk, None) - .map_err(|e| serde::ser::Error::custom(e.to_string()))?; + .map_err(|err| serde::ser::Error::custom(err.to_string()))?; writer .finish() - .map_err(|e| serde::ser::Error::custom(e.to_string()))?; + .map_err(|err| serde::ser::Error::custom(err.to_string()))?; let mut inner = serializer.serialize_tuple(3)?; inner.serialize_element(&self.table_id)?; diff --git a/crates/re_log_types/src/component_types/arrow_convert_shims.rs b/crates/re_log_types/src/component_types/arrow_convert_shims.rs index 8d196842169d..b78ff5f4c065 100644 --- a/crates/re_log_types/src/component_types/arrow_convert_shims.rs +++ b/crates/re_log_types/src/component_types/arrow_convert_shims.rs @@ -79,6 +79,7 @@ impl<'a> Iterator for BufferBinaryArrayIter<'a> { /// Internal `ArrowArray` helper to iterate over a `BinaryArray` while exposing Buffer slices pub struct BufferBinaryArray; +#[cfg(not(target_os = "windows"))] extern "C" { fn do_not_call_into_iter(); // we never define this function, so the linker will fail } diff --git a/crates/re_log_types/src/component_types/imu.rs b/crates/re_log_types/src/component_types/imu.rs new file mode 100644 index 000000000000..bfcdf7566eb8 --- /dev/null +++ b/crates/re_log_types/src/component_types/imu.rs @@ -0,0 +1,25 @@ +use crate::{Component, EntityPath}; +use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + +use super::{Point3D, Quaternion}; + +#[derive(Clone, Debug, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)] +pub struct ImuData { + pub accel: Point3D, + pub gyro: Point3D, + pub mag: Option, + pub orientation: Quaternion, +} + +impl ImuData { + pub fn entity_path() -> EntityPath { + "imu_data".into() + } +} + +impl Component for ImuData { + #[inline] + fn name() -> crate::ComponentName { + "rerun.imu".into() + } +} diff --git a/crates/re_log_types/src/component_types/mod.rs b/crates/re_log_types/src/component_types/mod.rs index 61066b8c8f58..b1e4ddec13a6 100644 --- a/crates/re_log_types/src/component_types/mod.rs +++ b/crates/re_log_types/src/component_types/mod.rs @@ -23,12 +23,14 @@ mod class_id; mod color; pub mod context; pub mod coordinates; +mod imu; mod instance_key; mod keypoint_id; mod label; mod linestrip; mod mat; mod mesh3d; +mod node_graph; mod point; mod quaternion; mod radius; @@ -39,6 +41,7 @@ mod tensor; mod text_entry; mod transform; mod vec; +mod xlink_stats; pub use arrow::Arrow3D; pub use bbox::Box3D; @@ -46,12 +49,14 @@ pub use class_id::ClassId; pub use color::ColorRGBA; pub use context::{AnnotationContext, AnnotationInfo, ClassDescription}; pub use coordinates::ViewCoordinates; +pub use imu::ImuData; pub use instance_key::InstanceKey; pub use keypoint_id::KeypointId; pub use label::Label; pub use linestrip::{LineStrip2D, LineStrip3D}; pub use mat::Mat3x3; pub use mesh3d::{EncodedMesh3D, Mesh3D, MeshFormat, MeshId, RawMesh3D}; +pub use node_graph::NodeGraph; pub use point::{Point2D, Point3D}; pub use quaternion::Quaternion; pub use radius::Radius; @@ -67,10 +72,11 @@ pub use tensor::{TensorImageLoadError, TensorImageSaveError}; pub use text_entry::TextEntry; pub use transform::{Pinhole, Rigid3, Transform}; pub use vec::{Vec2D, Vec3D, Vec4D}; +pub use xlink_stats::XlinkStats; lazy_static! { //TODO(john): use a run-time type registry - static ref FIELDS: [Field; 25] = [ + static ref FIELDS: [Field; 28] = [ ::field(), ::field(), ::field(), @@ -96,6 +102,9 @@ lazy_static! { ::field(), ::field(), ::field(), + ::field(), + ::field(), + ::field(), ]; } @@ -210,6 +219,7 @@ where pub struct FastFixedSizeListArray(std::marker::PhantomData); +#[cfg(not(target_os = "windows"))] extern "C" { fn do_not_call_into_iter(); // we never define this function, so the linker will fail } diff --git a/crates/re_log_types/src/component_types/node_graph.rs b/crates/re_log_types/src/component_types/node_graph.rs new file mode 100644 index 000000000000..edf8a83270ad --- /dev/null +++ b/crates/re_log_types/src/component_types/node_graph.rs @@ -0,0 +1,40 @@ +use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize}; + +use crate::Component; + +// --- + +/// A double-precision NodeGraph. +/// +/// ## Examples +/// +/// ``` +/// # use re_log_types::component_types::NodeGraph; +/// # use arrow2_convert::field::ArrowField; +/// # use arrow2::datatypes::{DataType, Field}; +/// assert_eq!(NodeGraph::data_type(), DataType::Float64); +/// ``` +#[derive(Debug, Clone, Copy, ArrowField, ArrowSerialize, ArrowDeserialize)] +#[arrow_field(transparent)] +pub struct NodeGraph(pub f64); + +impl Component for NodeGraph { + #[inline] + fn name() -> crate::ComponentName { + "rerun.pipeline_graph".into() + } +} + +impl From for NodeGraph { + #[inline] + fn from(value: f64) -> Self { + Self(value) + } +} + +impl From for f64 { + #[inline] + fn from(value: NodeGraph) -> Self { + value.0 + } +} diff --git a/crates/re_log_types/src/component_types/tensor.rs b/crates/re_log_types/src/component_types/tensor.rs index 9c4228b845e6..cf02a3bc2b8e 100644 --- a/crates/re_log_types/src/component_types/tensor.rs +++ b/crates/re_log_types/src/component_types/tensor.rs @@ -857,7 +857,7 @@ impl Tensor { /// A thin wrapper around a [`Tensor`] that is guaranteed to not be compressed (never a jpeg). /// /// All clones are shallow, like for [`Tensor`]. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct DecodedTensor(Tensor); impl DecodedTensor { diff --git a/crates/re_log_types/src/component_types/xlink_stats.rs b/crates/re_log_types/src/component_types/xlink_stats.rs new file mode 100644 index 000000000000..82b5f3b5522c --- /dev/null +++ b/crates/re_log_types/src/component_types/xlink_stats.rs @@ -0,0 +1,28 @@ +use crate::{Component, EntityPath}; +use arrow2::array::Int128Array; +use arrow2_convert::{field::I128, ArrowDeserialize, ArrowField, ArrowSerialize}; + +// TODO(filip): Convert to use i128 + +/// Stats about the XLink connection throughput +#[derive(Clone, Debug, PartialEq, ArrowField, ArrowSerialize, ArrowDeserialize)] +pub struct XlinkStats { + /// Bytes read from the XLink by the host (PC) + pub bytes_read: i64, + + /// Bytes written to the XLink by the host (PC) + pub bytes_written: i64, +} + +impl XlinkStats { + pub fn entity_path() -> EntityPath { + "xlink_stats".into() + } +} + +impl Component for XlinkStats { + #[inline] + fn name() -> crate::ComponentName { + "rerun.xlink_stats".into() + } +} diff --git a/crates/re_log_types/src/lib.rs b/crates/re_log_types/src/lib.rs index 7775be821e74..bc2785fd7c2e 100644 --- a/crates/re_log_types/src/lib.rs +++ b/crates/re_log_types/src/lib.rs @@ -42,7 +42,9 @@ pub use self::component_types::coordinates; pub use self::component_types::AnnotationContext; pub use self::component_types::Arrow3D; pub use self::component_types::DecodedTensor; -pub use self::component_types::{EncodedMesh3D, Mesh3D, MeshFormat, MeshId, RawMesh3D}; +pub use self::component_types::{ + EncodedMesh3D, ImuData, Mesh3D, MeshFormat, MeshId, RawMesh3D, XlinkStats, +}; pub use self::component_types::{Tensor, ViewCoordinates}; pub use self::data::*; pub use self::data_cell::{DataCell, DataCellError, DataCellInner, DataCellResult}; diff --git a/crates/re_memory/src/memory_limit.rs b/crates/re_memory/src/memory_limit.rs index 6d747d2be4ab..ec59cb66e48f 100644 --- a/crates/re_memory/src/memory_limit.rs +++ b/crates/re_memory/src/memory_limit.rs @@ -1,13 +1,22 @@ -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct MemoryLimit { /// Limit in bytes. /// /// This is primarily compared to what is reported by [`crate::AccountingAllocator`] ('counted'). /// We limit based on this instead of `resident` (RSS) because `counted` is what we have immediate /// control over, while RSS depends on what our allocator (MiMalloc) decides to do. + /// Default is Some(100MB) pub limit: Option, } +impl Default for MemoryLimit { + fn default() -> Self { + Self { + limit: re_format::parse_bytes("100MB"), + } + } +} + impl MemoryLimit { pub fn parse(limit: &str) -> Result { re_format::parse_bytes(limit) diff --git a/crates/re_renderer/Cargo.toml b/crates/re_renderer/Cargo.toml index 03bc85390551..c5a2fb1df2b7 100644 --- a/crates/re_renderer/Cargo.toml +++ b/crates/re_renderer/Cargo.toml @@ -24,7 +24,7 @@ targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [features] -default = ["arrow", "import-obj", "import-gltf"] +default = ["import-obj", "import-gltf"] ## Support for Arrow datatypes for end-to-end zero-copy. arrow = ["dep:arrow2"] @@ -38,6 +38,8 @@ import-gltf = ["dep:gltf"] ## Enable (de)serialization using serde. serde = ["dep:serde"] +## Render using webgl instead of webgpu on wasm builds. +webgl = ["wgpu/webgl"] [dependencies] re_error.workspace = true @@ -55,6 +57,7 @@ glam = { workspace = true, features = ["bytemuck"] } half = { workspace = true, features = ["bytemuck"] } itertools = { workspace = true } macaw.workspace = true +never = '0.1' ordered-float = "3.2" parking_lot.workspace = true slotmap = "1.0.6" @@ -62,6 +65,7 @@ smallvec.workspace = true static_assertions = "1.1" thiserror.workspace = true type-map = "0.5" +wgpu.workspace = true # optional arrow2 = { workspace = true, optional = true } @@ -74,17 +78,7 @@ tobj = { version = "3.2", optional = true } crossbeam = "0.8" notify = "5.0" puffin.workspace = true -wgpu = { workspace = true, default-features = false, features = ["wgsl"] } wgpu-core.workspace = true -wgpu-hal.workspace = true - -# wasm -[target.'cfg(target_arch = "wasm32")'.dependencies] -wgpu = { workspace = true, default-features = false, features = [ - "webgl", - "wgsl", -] } - # For examples: [dev-dependencies] @@ -109,7 +103,7 @@ console_error_panic_hook = "0.1.6" # required to make rand work on wasm, see https://github.com/rust-random/rand#wasm-support getrandom = { version = "0.2", features = ["js"] } wasm-bindgen-futures = "0.4.33" -web-sys = { version = "0.3.60", features = [ +web-sys = { version = "0.3.61", features = [ "Location", "Blob", "RequestInit", diff --git a/crates/re_renderer/examples/2d.rs b/crates/re_renderer/examples/2d.rs index 32940991c1f2..eb17d6955cfd 100644 --- a/crates/re_renderer/examples/2d.rs +++ b/crates/re_renderer/examples/2d.rs @@ -36,16 +36,19 @@ impl framework::Example for Render2D { ); } - let rerun_logo_texture = re_ctx.texture_manager_2d.create( - &mut re_ctx.gpu_resources.textures, - &Texture2DCreationDesc { - label: "rerun logo".into(), - data: image_data.into(), - format: wgpu::TextureFormat::Rgba8UnormSrgb, - width: rerun_logo.width(), - height: rerun_logo.height(), - }, - ); + let rerun_logo_texture = re_ctx + .texture_manager_2d + .create( + &mut re_ctx.gpu_resources.textures, + &Texture2DCreationDesc { + label: "rerun logo".into(), + data: image_data.into(), + format: wgpu::TextureFormat::Rgba8UnormSrgb, + width: rerun_logo.width(), + height: rerun_logo.height(), + }, + ) + .expect("Failed to create texture for rerun logo"); Render2D { rerun_logo_texture, diff --git a/crates/re_renderer/examples/depth_cloud.rs b/crates/re_renderer/examples/depth_cloud.rs index c66c17eb79b3..2f5cdb8f6905 100644 --- a/crates/re_renderer/examples/depth_cloud.rs +++ b/crates/re_renderer/examples/depth_cloud.rs @@ -20,8 +20,8 @@ use itertools::Itertools; use macaw::IsoTransform; use re_renderer::{ renderer::{ - ColormappedTexture, DepthCloud, DepthCloudDepthData, DepthCloudDrawData, DepthClouds, - DrawData, GenericSkyboxDrawData, RectangleDrawData, RectangleOptions, TexturedRect, + ColormappedTexture, DepthCloud, DepthCloudDrawData, DepthClouds, DrawData, + GenericSkyboxDrawData, RectangleDrawData, RectangleOptions, TexturedRect, }, resource_managers::{GpuTexture2D, Texture2DCreationDesc}, view_builder::{self, Projection, ViewBuilder}, @@ -44,7 +44,6 @@ enum CameraControl { struct RenderDepthClouds { depth: DepthTexture, albedo: AlbedoTexture, - albedo_handle: GpuTexture2D, scale: f32, point_radius_from_world_depth: f32, @@ -175,14 +174,16 @@ impl RenderDepthClouds { clouds: vec![DepthCloud { world_from_obj, depth_camera_intrinsics: *intrinsics, - world_depth_from_data_depth: 1.0, + world_depth_from_texture_depth: 1.0, point_radius_from_world_depth: *point_radius_from_world_depth, max_depth_in_world: 5.0, depth_dimensions: depth.dimensions, - depth_data: depth.data.clone(), + depth_texture: depth.texture.clone(), colormap: re_renderer::Colormap::Turbo, outline_mask_id: Default::default(), picking_object_id: Default::default(), + albedo_dimensions: glam::UVec2::ZERO, + albedo_data: None, }], radius_boost_in_ui_points_for_outlines: 2.5, }, @@ -233,19 +234,8 @@ impl framework::Example for RenderDepthClouds { fn new(re_ctx: &mut re_renderer::RenderContext) -> Self { re_log::info!("Stop camera movement by pressing 'Space'"); - let depth = DepthTexture::spiral((640, 480).into()); - let albedo = AlbedoTexture::spiral(depth.dimensions); - - let albedo_handle = re_ctx.texture_manager_2d.create( - &mut re_ctx.gpu_resources.textures, - &Texture2DCreationDesc { - label: "albedo".into(), - data: bytemuck::cast_slice(&albedo.rgba8).into(), - format: wgpu::TextureFormat::Rgba8UnormSrgb, - width: albedo.dimensions.x, - height: albedo.dimensions.y, - }, - ); + let depth = DepthTexture::spiral(re_ctx, glam::uvec2(640, 480)); + let albedo = AlbedoTexture::spiral(re_ctx, depth.dimensions); let scale = 50.0; let point_radius_from_world_depth = 0.1; @@ -263,7 +253,6 @@ impl framework::Example for RenderDepthClouds { RenderDepthClouds { depth, albedo, - albedo_handle, scale, point_radius_from_world_depth, @@ -283,7 +272,6 @@ impl framework::Example for RenderDepthClouds { ) -> Vec { let Self { albedo, - albedo_handle, camera_control, camera_position, .. @@ -326,7 +314,7 @@ impl framework::Example for RenderDepthClouds { .transform_point3(glam::Vec3::new(1.0, 1.0, 0.0)), extent_u: world_from_model.transform_vector3(-glam::Vec3::X), extent_v: world_from_model.transform_vector3(-glam::Vec3::Y), - colormapped_texture: ColormappedTexture::from_unorm_srgba(albedo_handle.clone()), + colormapped_texture: ColormappedTexture::from_unorm_srgba(albedo.texture.clone()), options: RectangleOptions { texture_filter_magnification: re_renderer::renderer::TextureFilterMag::Nearest, texture_filter_minification: re_renderer::renderer::TextureFilterMin::Linear, @@ -403,40 +391,60 @@ fn spiral(dimensions: glam::UVec2) -> impl Iterator { }) } +pub fn hash(value: &impl std::hash::Hash) -> u64 { + ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(value) +} + struct DepthTexture { dimensions: glam::UVec2, - data: DepthCloudDepthData, + data: Vec, + texture: GpuTexture2D, } impl DepthTexture { - pub fn spiral(dimensions: glam::UVec2) -> Self { + pub fn spiral(re_ctx: &mut re_renderer::RenderContext, dimensions: glam::UVec2) -> Self { let size = (dimensions.x * dimensions.y) as usize; let mut data = std::iter::repeat(0f32).take(size).collect_vec(); spiral(dimensions).for_each(|(texcoords, d)| { data[(texcoords.x + texcoords.y * dimensions.x) as usize] = d; }); - let data = DepthCloudDepthData::F32(data.into()); - Self { dimensions, data } + let label = format!("depth texture spiral {dimensions}"); + let texture = re_ctx + .texture_manager_2d + .get_or_create( + hash(&label), + &mut re_ctx.gpu_resources.textures, + Texture2DCreationDesc { + label: label.into(), + data: bytemuck::cast_slice(&data).into(), + format: wgpu::TextureFormat::R32Float, + width: dimensions.x, + height: dimensions.y, + }, + ) + .expect("Failed to create depth texture."); + + Self { + dimensions, + data, + texture, + } } pub fn get_linear(&self, x: u32, y: u32) -> f32 { - match &self.data { - DepthCloudDepthData::U16(data) => { - data[(x + y * self.dimensions.x) as usize] as f32 / u16::MAX as f32 - } - DepthCloudDepthData::F32(data) => data[(x + y * self.dimensions.x) as usize], - } + self.data[(x + y * self.dimensions.x) as usize] } } struct AlbedoTexture { dimensions: glam::UVec2, rgba8: Vec, + texture: GpuTexture2D, } impl AlbedoTexture { - pub fn spiral(dimensions: glam::UVec2) -> Self { + pub fn spiral(re_ctx: &mut re_renderer::RenderContext, dimensions: glam::UVec2) -> Self { let size = (dimensions.x * dimensions.y) as usize; let mut rgba8 = std::iter::repeat(0).take(size * 4).collect_vec(); spiral(dimensions).for_each(|(texcoords, d)| { @@ -444,7 +452,27 @@ impl AlbedoTexture { rgba8[idx..idx + 4].copy_from_slice(re_renderer::colormap_turbo_srgb(d).as_slice()); }); - Self { dimensions, rgba8 } + let label = format!("albedo texture spiral {dimensions}"); + let texture = re_ctx + .texture_manager_2d + .get_or_create( + hash(&label), + &mut re_ctx.gpu_resources.textures, + Texture2DCreationDesc { + label: label.into(), + data: bytemuck::cast_slice(&rgba8).into(), + format: wgpu::TextureFormat::Rgba8UnormSrgb, + width: dimensions.x, + height: dimensions.y, + }, + ) + .expect("Failed to create albedo texture."); + + Self { + dimensions, + rgba8, + texture, + } } #[allow(dead_code)] diff --git a/crates/re_renderer/examples/framework.rs b/crates/re_renderer/examples/framework.rs index fef6e7544b5b..2575f9d82463 100644 --- a/crates/re_renderer/examples/framework.rs +++ b/crates/re_renderer/examples/framework.rs @@ -125,7 +125,7 @@ impl Application { .await .context("failed to find an appropriate adapter")?; - let hardware_tier = HardwareTier::default(); + let hardware_tier = HardwareTier::from_adapter(&adapter); hardware_tier.check_downlevel_capabilities(&adapter.get_downlevel_capabilities())?; let (device, queue) = adapter .request_device( @@ -159,6 +159,7 @@ impl Application { surface.configure(&device, &surface_config); let mut re_ctx = RenderContext::new( + &adapter, device, queue, RenderContextConfig { diff --git a/crates/re_renderer/shader/colormap.wgsl b/crates/re_renderer/shader/colormap.wgsl index 59be61afdfe4..7d4676ac8852 100644 --- a/crates/re_renderer/shader/colormap.wgsl +++ b/crates/re_renderer/shader/colormap.wgsl @@ -8,11 +8,12 @@ const COLORMAP_MAGMA: u32 = 3u; const COLORMAP_PLASMA: u32 = 4u; const COLORMAP_TURBO: u32 = 5u; const COLORMAP_VIRIDIS: u32 = 6u; - +const ALBEDO_TEXTURE: u32 = 7u; /// Returns a gamma-space sRGB in 0-1 range. /// /// The input will be saturated to [0, 1] range. -fn colormap_srgb(which: u32, t: f32) -> Vec3 { +fn colormap_srgb(which: u32, t_unsaturated: f32) -> Vec3 { + let t = saturate(t_unsaturated); if which == COLORMAP_GRAYSCALE { return linear_from_srgb(Vec3(t)); } else if which == COLORMAP_INFERNO { @@ -61,7 +62,6 @@ fn colormap_turbo_srgb(t: f32) -> Vec3 { let g2 = Vec2(4.27729857, 2.82956604); let b2 = Vec2(-89.90310912, 27.34824973); - let t = saturate(t); let v4 = vec4(1.0, t, t * t, t * t * t); let v2 = v4.zw * v4.z; @@ -97,7 +97,6 @@ fn colormap_viridis_srgb(t: f32) -> Vec3 { let c4 = Vec3(6.228269936347081, 14.17993336680509, 56.69055260068105); let c5 = Vec3(4.776384997670288, -13.74514537774601, -65.35303263337234); let c6 = Vec3(-5.435455855934631, 4.645852612178535, 26.3124352495832); - let t = saturate(t); return c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * c6))))); } @@ -112,7 +111,6 @@ fn colormap_plasma_srgb(t: f32) -> Vec3 { let c4 = Vec3(-11.10743619062271, -82.66631109428045, 60.13984767418263); let c5 = Vec3(10.02306557647065, 71.41361770095349, -54.07218655560067); let c6 = Vec3(-3.658713842777788, -22.93153465461149, 18.19190778539828); - let t = saturate(t); return c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * c6))))); } @@ -127,7 +125,6 @@ fn colormap_magma_srgb(t: f32) -> Vec3 { let c4 = Vec3(52.17613981234068, -27.94360607168351, 12.94416944238394); let c5 = Vec3(-50.76852536473588, 29.04658282127291, 4.23415299384598); let c6 = Vec3(18.65570506591883, -11.48977351997711, -5.601961508734096); - let t = saturate(t); return c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * c6))))); } @@ -142,6 +139,5 @@ fn colormap_inferno_srgb(t: f32) -> Vec3 { let c4 = Vec3(77.162935699427, -33.40235894210092, -81.80730925738993); let c5 = Vec3(-71.31942824499214, 32.62606426397723, 73.20951985803202); let c6 = Vec3(25.13112622477341, -12.24266895238567, -23.07032500287172); - let t = saturate(t); return c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * (c5 + t * c6))))); } diff --git a/crates/re_renderer/shader/depth_cloud.wgsl b/crates/re_renderer/shader/depth_cloud.wgsl index 1e7f7afdf0d7..db39dd6d20d2 100644 --- a/crates/re_renderer/shader/depth_cloud.wgsl +++ b/crates/re_renderer/shader/depth_cloud.wgsl @@ -43,16 +43,26 @@ struct DepthCloudInfo { /// Configures color mapping mode, see `colormap.wgsl`. colormap: u32, + /// Is the albedo texture rgb or mono + albedo_color_space: u32, + /// Changes between the opaque and outline draw-phases. radius_boost_in_ui_points: f32, }; +const ALBEDO_COLOR_RGB: u32 = 0u; +const ALBEDO_COLOR_MONO: u32 = 1u; + @group(1) @binding(0) var depth_cloud_info: DepthCloudInfo; @group(1) @binding(1) var depth_texture: texture_2d; +/// Only sampled if `DepthCloudInfo::colormap == ALBEDO_TEXTURE`. +@group(1) @binding(2) +var albedo_texture: texture_2d; + struct VertexOut { @builtin(position) pos_in_clip: Vec4, @@ -82,19 +92,32 @@ struct PointData { } // Backprojects the depth texture using the intrinsics passed in the uniform buffer. -fn compute_point_data(quad_idx: i32) -> PointData { +fn compute_point_data(quad_idx: u32) -> PointData { let wh = textureDimensions(depth_texture); - let texcoords = IVec2(quad_idx % wh.x, quad_idx / wh.x); + let texcoords = UVec2(quad_idx % wh.x, quad_idx / wh.x); // TODO(cmc): expose knobs to linearize/normalize/flip/cam-to-plane depth. let world_space_depth = depth_cloud_info.world_depth_from_texture_value * textureLoad(depth_texture, texcoords, 0).x; var data: PointData; - if 0.0 < world_space_depth && world_space_depth < f32max { // TODO(cmc): albedo textures - let color = Vec4(colormap_linear(depth_cloud_info.colormap, world_space_depth / depth_cloud_info.max_depth_in_world), 1.0); - + // let color = Vec4(colormap_linear(depth_cloud_info.colormap, world_space_depth / depth_cloud_info.max_depth_in_world), 1.0); + + var color: Vec4; + if depth_cloud_info.colormap == ALBEDO_TEXTURE { + color = textureSampleLevel( + albedo_texture, + trilinear_sampler, + Vec2(texcoords) / Vec2(textureDimensions(albedo_texture)), + 0.0 + ); + if depth_cloud_info.albedo_color_space == ALBEDO_COLOR_MONO { + color = Vec4(linear_from_srgb(Vec3(color.r)), 1.0); + } + } else { + color = Vec4(colormap_srgb(depth_cloud_info.colormap, world_space_depth), 1.0); + } // TODO(cmc): This assumes a pinhole camera; need to support other kinds at some point. let intrinsics = depth_cloud_info.depth_camera_intrinsics; let focal_length = Vec2(intrinsics[0][0], intrinsics[1][1]); diff --git a/crates/re_renderer/shader/generic_skybox.wgsl b/crates/re_renderer/shader/generic_skybox.wgsl index b94e714ff5a6..854cfc530b34 100644 --- a/crates/re_renderer/shader/generic_skybox.wgsl +++ b/crates/re_renderer/shader/generic_skybox.wgsl @@ -18,7 +18,7 @@ fn skybox_light_srgb(dir: Vec3) -> Vec3 { fn main(in: FragmentInput) -> @location(0) Vec4 { let camera_dir = camera_ray_direction_from_screenuv(in.texcoord); // Messing with direction a bit so it looks like in our old three-d based renderer (for easier comparison) - let rgb = skybox_dark_srgb(camera_dir); // TODO(andreas): Allow switching to skybox_light + let rgb = skybox_light_srgb(camera_dir); // TODO(andreas): Allow switching to skybox_light return Vec4(linear_from_srgb(rgb), 1.0); //return Vec4(camera_dir, 1.0); } diff --git a/crates/re_renderer/shader/lines.wgsl b/crates/re_renderer/shader/lines.wgsl index c8812e8112df..6a143a8233c8 100644 --- a/crates/re_renderer/shader/lines.wgsl +++ b/crates/re_renderer/shader/lines.wgsl @@ -32,11 +32,8 @@ struct BatchUniformBuffer { @group(2) @binding(0) var batch: BatchUniformBuffer; - -// textureLoad needs i32 right now, so we use that with all sizes & indices to avoid casts -// https://github.com/gfx-rs/naga/issues/1997 -const POSITION_TEXTURE_SIZE: i32 = 512; -const LINE_STRIP_TEXTURE_SIZE: i32 = 256; +const POSITION_TEXTURE_SIZE: u32 = 512u; +const LINE_STRIP_TEXTURE_SIZE: u32 = 256u; // Flags // See lines.rs#LineStripFlags @@ -87,9 +84,7 @@ struct LineStripData { // Read and unpack line strip data at a given location fn read_strip_data(idx: u32) -> LineStripData { - // can be u32 once https://github.com/gfx-rs/naga/issues/1997 is solved - let idx = i32(idx); - let coord = IVec2(idx % LINE_STRIP_TEXTURE_SIZE, idx / LINE_STRIP_TEXTURE_SIZE); + let coord = UVec2(idx % LINE_STRIP_TEXTURE_SIZE, idx / LINE_STRIP_TEXTURE_SIZE); var raw_data = textureLoad(position_data_texture, coord, 0).xy; var data: LineStripData; @@ -110,9 +105,7 @@ struct PositionData { // Read and unpack position data at a given location fn read_position_data(idx: u32) -> PositionData { - // can be u32 once https://github.com/gfx-rs/naga/issues/1997 is solved - let idx = i32(idx); - var raw_data = textureLoad(line_strip_texture, IVec2(idx % POSITION_TEXTURE_SIZE, idx / POSITION_TEXTURE_SIZE), 0); + var raw_data = textureLoad(line_strip_texture, UVec2(idx % POSITION_TEXTURE_SIZE, idx / POSITION_TEXTURE_SIZE), 0); var data: PositionData; let pos_4d = batch.world_from_obj * Vec4(raw_data.xyz, 1.0); @@ -198,7 +191,7 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut { quad_dir = pos_data_quad_after.pos - pos_data_quad_end.pos; // Go one pos data forward. } else if is_cap_triangle { // Discard vertex. - center_position = Vec3(0.0/0.0, 0.0/0.0, 0.0/0.0); + center_position = Vec3(f32max); } else { quad_dir = pos_data_quad_end.pos - pos_data_quad_begin.pos; } diff --git a/crates/re_renderer/shader/point_cloud.wgsl b/crates/re_renderer/shader/point_cloud.wgsl index dc6efe6df9b5..a55404230692 100644 --- a/crates/re_renderer/shader/point_cloud.wgsl +++ b/crates/re_renderer/shader/point_cloud.wgsl @@ -36,10 +36,7 @@ var batch: BatchUniformBuffer; // Flags // See point_cloud.rs#PointCloudBatchFlags const ENABLE_SHADING: u32 = 1u; - -// textureLoad needs i32 right now, so we use that with all sizes & indices to avoid casts -// https://github.com/gfx-rs/naga/issues/1997 -var TEXTURE_SIZE: i32 = 2048; +const TEXTURE_SIZE: u32 = 2048u; struct VertexOut { @builtin(position) @@ -75,8 +72,8 @@ struct PointData { } // Read and unpack data at a given location -fn read_data(idx: i32) -> PointData { - let coord = IVec2(i32(idx % TEXTURE_SIZE), idx / TEXTURE_SIZE); +fn read_data(idx: u32) -> PointData { + let coord = UVec2(idx % TEXTURE_SIZE, idx / TEXTURE_SIZE); let position_data = textureLoad(position_data_texture, coord, 0); let color = textureLoad(color_texture, coord, 0); diff --git a/crates/re_renderer/shader/rectangle_fs.wgsl b/crates/re_renderer/shader/rectangle_fs.wgsl index 0d1a35cad961..62f65952fe21 100644 --- a/crates/re_renderer/shader/rectangle_fs.wgsl +++ b/crates/re_renderer/shader/rectangle_fs.wgsl @@ -86,10 +86,12 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 { let colormap_size = textureDimensions(colormap_texture).xy; let color_index = normalized_value.r * f32(colormap_size.x * colormap_size.y); // TODO(emilk): interpolate between neighboring colors for non-integral color indices - let color_index_i32 = i32(color_index); - let x = color_index_i32 % colormap_size.x; - let y = color_index_i32 / colormap_size.x; - texture_color = textureLoad(colormap_texture, IVec2(x, y), 0); + // It's important to round here since otherwise numerical instability can push us to the adjacent class-id + // See: https://github.com/rerun-io/rerun/issues/1968 + let color_index_u32 = u32(round(color_index)); + let x = color_index_u32 % colormap_size.x; + let y = color_index_u32 / colormap_size.x; + texture_color = textureLoad(colormap_texture, UVec2(x, y), 0); } else { return ERROR_RGBA; // unknown color mapper } diff --git a/crates/re_renderer/shader/screen_triangle_vertex.wgsl b/crates/re_renderer/shader/screen_triangle_vertex.wgsl index 224da3317d4b..e42fac7827a6 100644 --- a/crates/re_renderer/shader/screen_triangle_vertex.wgsl +++ b/crates/re_renderer/shader/screen_triangle_vertex.wgsl @@ -4,8 +4,10 @@ struct VertexOutput { // Mark output position as invariant so it's safe to use it with depth test Equal. // Without @invariant, different usages in different render pipelines might optimize differently, // causing slightly different results. - @invariant @builtin(position) position: Vec4, - @location(0) texcoord: Vec2, + @invariant @builtin(position) + position: Vec4, + @location(0) + texcoord: Vec2, }; // Workaround for https://github.com/gfx-rs/naga/issues/2252 diff --git a/crates/re_renderer/shader/types.wgsl b/crates/re_renderer/shader/types.wgsl index 3323c7a6cd1f..6355bcc668b5 100644 --- a/crates/re_renderer/shader/types.wgsl +++ b/crates/re_renderer/shader/types.wgsl @@ -1,16 +1,16 @@ // Names chosen to match [`glam`](https://docs.rs/glam/latest/glam/) -type Vec2 = vec2; -type Vec3 = vec3; -type Vec4 = vec4; -type UVec2 = vec2; -type UVec3 = vec3; -type UVec4 = vec4; -type IVec2 = vec2; -type IVec3 = vec3; -type IVec4 = vec4; -type Mat3 = mat3x3; -type Mat4x3 = mat4x3; -type Mat4 = mat4x4; +alias Vec2 = vec2; +alias Vec3 = vec3; +alias Vec4 = vec4; +alias UVec2 = vec2; +alias UVec3 = vec3; +alias UVec4 = vec4; +alias IVec2 = vec2; +alias IVec3 = vec3; +alias IVec4 = vec4; +alias Mat3 = mat3x3; +alias Mat4x3 = mat4x3; +alias Mat4 = mat4x4; // Extreme values as documented by the spec: // https://www.w3.org/TR/WGSL/#floating-point-types @@ -22,7 +22,7 @@ const f32min_normal = 0x1p-126f; // Smallest positive normal float value. //const f16max = 0x1.ffcp+15h; // Largest positive float value. //const f16min_normal = 0x1p-14h; // Smallest positive normal float value. // https://www.w3.org/TR/WGSL/#integer-types -const i32min = -0x80000000i; +const i32min = -2147483648; // Naga has some issues with correct negative hexadecimal numbers https://github.com/gfx-rs/naga/issues/2314 const i32max = 0x7fffffffi; const u32min = 0u; const u32max = 0xffffffffu; diff --git a/crates/re_renderer/shader/utils/sphere_quad.wgsl b/crates/re_renderer/shader/utils/sphere_quad.wgsl index ccdd4b771a0b..00937d8e7701 100644 --- a/crates/re_renderer/shader/utils/sphere_quad.wgsl +++ b/crates/re_renderer/shader/utils/sphere_quad.wgsl @@ -56,8 +56,8 @@ fn sphere_quad_span_orthographic(point_pos: Vec3, point_radius: f32, top_bottom: } /// Returns the index of the current quad. -fn sphere_quad_index(vertex_idx: u32) -> i32 { - return i32(vertex_idx) / 6; +fn sphere_quad_index(vertex_idx: u32) -> u32 { + return vertex_idx / 6u; } struct SphereQuadData { diff --git a/crates/re_renderer/src/allocator/cpu_write_gpu_read_belt.rs b/crates/re_renderer/src/allocator/cpu_write_gpu_read_belt.rs index db1a1d085a90..6bc86a60d112 100644 --- a/crates/re_renderer/src/allocator/cpu_write_gpu_read_belt.rs +++ b/crates/re_renderer/src/allocator/cpu_write_gpu_read_belt.rs @@ -1,6 +1,9 @@ -use std::{num::NonZeroU32, sync::mpsc}; +use std::sync::mpsc; -use crate::wgpu_resources::{BufferDesc, GpuBuffer, GpuBufferPool, Texture2DBufferInfo}; +use crate::{ + texture_info::Texture2DBufferInfo, + wgpu_resources::{BufferDesc, GpuBuffer, GpuBufferPool}, +}; /// A sub-allocated staging buffer that can be written to. /// @@ -119,7 +122,7 @@ where buffer: &self.chunk_buffer, layout: wgpu::ImageDataLayout { offset: self.byte_offset_in_chunk_buffer, - bytes_per_row: NonZeroU32::new(buffer_info.bytes_per_row_padded), + bytes_per_row: Some(buffer_info.bytes_per_row_padded), rows_per_image: None, }, }, @@ -290,7 +293,7 @@ impl CpuWriteGpuReadBelt { ); // Largest uncompressed texture format (btw. many compressed texture format have the same block size!) debug_assert!( - wgpu::TextureFormat::Rgba32Uint.describe().block_size as u64 + wgpu::TextureFormat::Rgba32Uint.block_size(None).unwrap() as u64 <= CpuWriteGpuReadBelt::MIN_OFFSET_ALIGNMENT ); diff --git a/crates/re_renderer/src/allocator/gpu_readback_belt.rs b/crates/re_renderer/src/allocator/gpu_readback_belt.rs index 8e5f413743e9..d20e231cb269 100644 --- a/crates/re_renderer/src/allocator/gpu_readback_belt.rs +++ b/crates/re_renderer/src/allocator/gpu_readback_belt.rs @@ -1,6 +1,7 @@ -use std::{num::NonZeroU32, ops::Range, sync::mpsc}; +use std::{ops::Range, sync::mpsc}; -use crate::wgpu_resources::{BufferDesc, GpuBuffer, GpuBufferPool, Texture2DBufferInfo}; +use crate::texture_info::Texture2DBufferInfo; +use crate::wgpu_resources::{BufferDesc, GpuBuffer, GpuBufferPool}; /// Identifier used to identify a buffer upon retrieval of the data. /// @@ -16,6 +17,12 @@ struct PendingReadbackRange { user_data: GpuReadbackUserDataStorage, } +#[derive(thiserror::Error, Debug)] +pub enum GpuReadbackError { + #[error("Texture format {0:?} is not supported for readback.")] + UnsupportedTextureFormatForReadback(wgpu::TextureFormat), +} + /// A reserved slice for GPU readback. /// /// Readback needs to happen from a buffer/texture with copy-source usage, @@ -36,8 +43,8 @@ impl GpuReadbackBuffer { encoder: &mut wgpu::CommandEncoder, source: wgpu::ImageCopyTexture<'_>, copy_extents: glam::UVec2, - ) { - self.read_multiple_texture2d(encoder, &[(source, copy_extents)]); + ) -> Result<(), GpuReadbackError> { + self.read_multiple_texture2d(encoder, &[(source, copy_extents)]) } /// Reads multiple textures into the same buffer. @@ -54,11 +61,17 @@ impl GpuReadbackBuffer { mut self, encoder: &mut wgpu::CommandEncoder, sources_and_extents: &[(wgpu::ImageCopyTexture<'_>, glam::UVec2)], - ) { + ) -> Result<(), GpuReadbackError> { for (source, copy_extents) in sources_and_extents { let start_offset = wgpu::util::align_to( self.range_in_chunk.start, - source.texture.format().describe().block_size as u64, + source + .texture + .format() + .block_size(Some(source.aspect)) + .ok_or(GpuReadbackError::UnsupportedTextureFormatForReadback( + source.texture.format(), + ))? as u64, ); let buffer_info = Texture2DBufferInfo::new(source.texture.format(), *copy_extents); @@ -75,7 +88,7 @@ impl GpuReadbackBuffer { buffer: &self.chunk_buffer, layout: wgpu::ImageDataLayout { offset: start_offset, - bytes_per_row: NonZeroU32::new(buffer_info.bytes_per_row_padded), + bytes_per_row: Some(buffer_info.bytes_per_row_padded), rows_per_image: None, }, }, @@ -89,6 +102,7 @@ impl GpuReadbackBuffer { self.range_in_chunk = (start_offset + buffer_info.buffer_size_padded)..self.range_in_chunk.end; } + Ok(()) } // TODO(andreas): Unused & untested so far! diff --git a/crates/re_renderer/src/allocator/mod.rs b/crates/re_renderer/src/allocator/mod.rs index 11a092b1b374..382376416d54 100644 --- a/crates/re_renderer/src/allocator/mod.rs +++ b/crates/re_renderer/src/allocator/mod.rs @@ -9,7 +9,8 @@ mod uniform_buffer_fill; pub use cpu_write_gpu_read_belt::{CpuWriteGpuReadBelt, CpuWriteGpuReadBuffer}; pub use gpu_readback_belt::{ - GpuReadbackBelt, GpuReadbackBuffer, GpuReadbackIdentifier, GpuReadbackUserDataStorage, + GpuReadbackBelt, GpuReadbackBuffer, GpuReadbackError, GpuReadbackIdentifier, + GpuReadbackUserDataStorage, }; pub use uniform_buffer_fill::{ create_and_fill_uniform_buffer, create_and_fill_uniform_buffer_batch, diff --git a/crates/re_renderer/src/colormap.rs b/crates/re_renderer/src/colormap.rs index 15cd98d5dc14..e4a4927ed3fe 100644 --- a/crates/re_renderer/src/colormap.rs +++ b/crates/re_renderer/src/colormap.rs @@ -18,6 +18,7 @@ pub enum Colormap { Plasma = 4, Turbo = 5, Viridis = 6, + AlbedoTexture = 7, } impl Colormap { @@ -40,6 +41,7 @@ impl std::fmt::Display for Colormap { Colormap::Plasma => write!(f, "Plasma"), Colormap::Turbo => write!(f, "Turbo"), Colormap::Viridis => write!(f, "Viridis"), + Colormap::AlbedoTexture => write!(f, "AlbedoTexture"), } } } @@ -52,6 +54,10 @@ pub fn colormap_srgb(which: Colormap, t: f32) -> [u8; 4] { Colormap::Plasma => colormap_plasma_srgb(t), Colormap::Magma => colormap_magma_srgb(t), Colormap::Inferno => colormap_inferno_srgb(t), + Colormap::AlbedoTexture => { + re_log::error_once!("Trying to do texture sampling on the CPU"); + [0; 4] + } } } diff --git a/crates/re_renderer/src/config.rs b/crates/re_renderer/src/config.rs index 91e0315d401d..7b85283b505f 100644 --- a/crates/re_renderer/src/config.rs +++ b/crates/re_renderer/src/config.rs @@ -2,6 +2,11 @@ /// /// To reduce complexity, we don't do fine-grained feature checks, /// but instead support set of features, each a superset of the next. +/// +/// Tiers are sorted from lowest to highest. Certain tiers may not be possible on a given machine/setup, +/// but choosing lower tiers is always possible. +/// Tiers may loosely relate to quality settings, but their primary function is an easier way to +/// do bundle feature *support* checks. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum HardwareTier { /// Limited feature support as provided by WebGL and native GLES2/OpenGL3(ish). @@ -34,18 +39,22 @@ impl HardwareTier { } } -impl Default for HardwareTier { - fn default() -> Self { - // Use "Basic" tier for actual web but also if someone forces the GL backend! - if supported_backends() == wgpu::Backends::GL { - HardwareTier::Gles - } else { - HardwareTier::FullWebGpuSupport +impl HardwareTier { + /// Picks the highest possible tier for a given adapter. + /// + /// Note that it is always possible to pick a lower tier! + pub fn from_adapter(adapter: &wgpu::Adapter) -> Self { + match adapter.get_info().backend { + wgpu::Backend::Vulkan + | wgpu::Backend::Metal + | wgpu::Backend::Dx12 + | wgpu::Backend::BrowserWebGpu => HardwareTier::FullWebGpuSupport, + + // Dx11 support in wgpu is sporadic, treat it like GLES to be on the safe side. + wgpu::Backend::Dx11 | wgpu::Backend::Gl | wgpu::Backend::Empty => HardwareTier::Gles, } } -} -impl HardwareTier { /// Wgpu limits required by the given hardware tier. pub fn limits(self) -> wgpu::Limits { wgpu::Limits { @@ -127,22 +136,19 @@ pub struct RenderContextConfig { /// /// Other backend might work as well, but lack of support isn't regarded as a bug. pub fn supported_backends() -> wgpu::Backends { - // Native. - // Only use Vulkan & Metal unless explicitly told so since this reduces surfaces and thus surprises. - // - // Bunch of cases where it's still useful to switch though: - // * Some Windows VMs only provide DX12 drivers, observed with Parallels on Apple Silicon - // * May run into Linux issues that warrant trying out the GL backend. - // - // For changing the backend we use standard wgpu env var, i.e. WGPU_BACKEND. - #[cfg(not(target_arch = "wasm32"))] - { + if cfg!(target_arch = "wasm32") { + // Web - WebGL is used automatically when wgpu is compiled with `webgl` feature. + wgpu::Backends::GL | wgpu::Backends::BROWSER_WEBGPU + } else { + // Native. + // Only use Vulkan & Metal unless explicitly told so since this reduces surfaces and thus surprises. + // + // Bunch of cases where it's still useful to switch though: + // * Some Windows VMs only provide DX12 drivers, observed with Parallels on Apple Silicon + // * May run into Linux issues that warrant trying out the GL backend. + // + // For changing the backend we use standard wgpu env var, i.e. WGPU_BACKEND. wgpu::util::backend_bits_from_env() .unwrap_or(wgpu::Backends::VULKAN | wgpu::Backends::METAL) } - // Web - we support only WebGL right now, WebGPU should work but hasn't been tested. - #[cfg(target_arch = "wasm32")] - { - wgpu::Backends::GL - } } diff --git a/crates/re_renderer/src/context.rs b/crates/re_renderer/src/context.rs index 701d1800d561..a2c8d032b819 100644 --- a/crates/re_renderer/src/context.rs +++ b/crates/re_renderer/src/context.rs @@ -106,6 +106,7 @@ impl RenderContext { const MAX_NUM_INFLIGHT_QUEUE_SUBMISSIONS: usize = 4; pub fn new( + adapter: &wgpu::Adapter, device: Arc, queue: Arc, config: RenderContextConfig, @@ -138,7 +139,16 @@ impl RenderContext { config.hardware_tier.features(), device.features(), ); - // Can't check downlevel feature flags since they sit on the adapter, not on the device. + assert!(adapter.get_downlevel_capabilities().flags.contains(config.hardware_tier.required_downlevel_capabilities().flags), + "The given device doesn't support the required downlevel capabilities for the given hardware tier {:?}. + Required: + {:?} + Actual: + {:?}", + config.hardware_tier, + config.hardware_tier.required_downlevel_capabilities(), + adapter.get_downlevel_capabilities(), + ); // In debug builds, make sure to catch all errors, never crash, and try to // always let the user find a way to return a poisoned pipeline back into a @@ -178,6 +188,21 @@ impl RenderContext { frame_index: 0, }; + // Register shader workarounds for the current device. + if adapter.get_info().backend == wgpu::Backend::BrowserWebGpu { + // Chrome/Tint does not support `@invariant` when targeting Metal. + // https://bugs.chromium.org/p/chromium/issues/detail?id=1439273 + // (bug is fixed as of writing, but hasn't hit any public released version yet) + // Ignoring it is fine in the cases we use it, it's mostly there to avoid a (correct!) warning in wgpu. + gpu_resources + .shader_modules + .shader_text_workaround_replacements + .push(( + "@invariant @builtin(position)".to_owned(), + "@builtin(position)".to_owned(), + )); + } + RenderContext { device, queue, diff --git a/crates/re_renderer/src/draw_phases/mod.rs b/crates/re_renderer/src/draw_phases/mod.rs index fe7ce542f245..f77d70e9ec5c 100644 --- a/crates/re_renderer/src/draw_phases/mod.rs +++ b/crates/re_renderer/src/draw_phases/mod.rs @@ -6,7 +6,8 @@ pub use outlines::{OutlineConfig, OutlineMaskPreference, OutlineMaskProcessor}; mod picking_layer; pub use picking_layer::{ - PickingLayerId, PickingLayerInstanceId, PickingLayerObjectId, PickingLayerProcessor, + PickingLayerError, PickingLayerId, PickingLayerInstanceId, PickingLayerObjectId, + PickingLayerProcessor, }; mod screenshot; diff --git a/crates/re_renderer/src/draw_phases/picking_layer.rs b/crates/re_renderer/src/draw_phases/picking_layer.rs index dc5cf38f033f..cd4d6601ebb0 100644 --- a/crates/re_renderer/src/draw_phases/picking_layer.rs +++ b/crates/re_renderer/src/draw_phases/picking_layer.rs @@ -13,11 +13,12 @@ use crate::{ allocator::create_and_fill_uniform_buffer, global_bindings::FrameUniformBuffer, include_shader_module, + texture_info::Texture2DBufferInfo, view_builder::ViewBuilder, wgpu_resources::{ BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuRenderPipelineHandle, GpuTexture, GpuTextureHandle, PipelineLayoutDesc, PoolError, RenderPipelineDesc, - Texture2DBufferInfo, TextureDesc, WgpuResourcePools, + TextureDesc, WgpuResourcePools, }, DebugLabel, GpuReadbackBuffer, GpuReadbackIdentifier, IntRect, RenderContext, }; @@ -132,6 +133,15 @@ pub fn pixel_coord_to_ndc(coord: glam::Vec2, target_resolution: glam::Vec2) -> g ) } +#[derive(thiserror::Error, Debug)] +pub enum PickingLayerError { + #[error(transparent)] + ReadbackError(#[from] crate::allocator::GpuReadbackError), + + #[error(transparent)] + ResourcePoolError(#[from] PoolError), +} + /// Manages the rendering of the picking layer pass, its render targets & readback buffer. /// /// The view builder creates this for every frame that requests a picking result. @@ -278,8 +288,10 @@ impl PickingLayerProcessor { // Offset of the depth buffer in the readback buffer needs to be aligned to size of a depth pixel. // This is "trivially true" if the size of the depth format is a multiple of the size of the id format. debug_assert!( - Self::PICKING_LAYER_FORMAT.describe().block_size - % Self::PICKING_LAYER_DEPTH_FORMAT.describe().block_size + Self::PICKING_LAYER_FORMAT.block_size(None).unwrap() + % Self::PICKING_LAYER_DEPTH_FORMAT + .block_size(Some(wgpu::TextureAspect::DepthOnly)) + .unwrap() == 0 ); let buffer_size = row_info_id.buffer_size_padded + row_info_depth.buffer_size_padded; @@ -342,7 +354,7 @@ impl PickingLayerProcessor { self, encoder: &mut wgpu::CommandEncoder, pools: &WgpuResourcePools, - ) -> Result<(), PoolError> { + ) -> Result<(), PickingLayerError> { let extent = glam::uvec2( self.picking_target.texture.width(), self.picking_target.texture.height(), @@ -373,12 +385,16 @@ impl PickingLayerProcessor { texture: &readable_depth_texture.texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, + aspect: if self.depth_readback_workaround.is_some() { + wgpu::TextureAspect::All + } else { + wgpu::TextureAspect::DepthOnly + }, }, extent, ), ], - ); + )?; Ok(()) } @@ -401,11 +417,13 @@ impl PickingLayerProcessor { .readback_data::>(identifier, |data, metadata| { // Assert that our texture data reinterpretation works out from a pixel size point of view. debug_assert_eq!( - Self::PICKING_LAYER_DEPTH_FORMAT.describe().block_size as usize, - std::mem::size_of::() + Self::PICKING_LAYER_DEPTH_FORMAT + .block_size(Some(wgpu::TextureAspect::DepthOnly)) + .unwrap(), + std::mem::size_of::() as u32 ); debug_assert_eq!( - Self::PICKING_LAYER_FORMAT.describe().block_size as usize, + Self::PICKING_LAYER_FORMAT.block_size(None).unwrap() as usize, std::mem::size_of::() ); @@ -432,8 +450,8 @@ impl PickingLayerProcessor { // See https://github.com/gfx-rs/wgpu/issues/3644 debug_assert_eq!( DepthReadbackWorkaround::READBACK_FORMAT - .describe() - .block_size as usize, + .block_size(None) + .unwrap() as usize, std::mem::size_of::() * 4 ); picking_depth_data = picking_depth_data.into_iter().step_by(4).collect(); diff --git a/crates/re_renderer/src/draw_phases/screenshot.rs b/crates/re_renderer/src/draw_phases/screenshot.rs index 68c05b3b545c..79cac54c1a6a 100644 --- a/crates/re_renderer/src/draw_phases/screenshot.rs +++ b/crates/re_renderer/src/draw_phases/screenshot.rs @@ -11,7 +11,9 @@ //! Or alternatively try to render the images in several tiles 🤔. In any case this would greatly improve quality! use crate::{ - wgpu_resources::{GpuTexture, Texture2DBufferInfo, TextureDesc}, + allocator::GpuReadbackError, + texture_info::Texture2DBufferInfo, + wgpu_resources::{GpuTexture, TextureDesc}, DebugLabel, GpuReadbackBuffer, GpuReadbackIdentifier, RenderContext, }; @@ -95,7 +97,10 @@ impl ScreenshotProcessor { pass } - pub fn end_render_pass(self, encoder: &mut wgpu::CommandEncoder) { + pub fn end_render_pass( + self, + encoder: &mut wgpu::CommandEncoder, + ) -> Result<(), GpuReadbackError> { self.screenshot_readback_buffer.read_texture2d( encoder, wgpu::ImageCopyTexture { @@ -108,7 +113,7 @@ impl ScreenshotProcessor { self.screenshot_texture.texture.width(), self.screenshot_texture.texture.height(), ), - ); + ) } /// Returns the oldest received screenshot results for a given identifier and user data type. diff --git a/crates/re_renderer/src/global_bindings.rs b/crates/re_renderer/src/global_bindings.rs index c00ad0315c67..33fdfa4b2191 100644 --- a/crates/re_renderer/src/global_bindings.rs +++ b/crates/re_renderer/src/global_bindings.rs @@ -82,14 +82,14 @@ impl GlobalBindings { // Sampler without any filtering. wgpu::BindGroupLayoutEntry { binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, // Trilinear sampler. wgpu::BindGroupLayoutEntry { binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, diff --git a/crates/re_renderer/src/importer/gltf.rs b/crates/re_renderer/src/importer/gltf.rs index 9d628931c6f3..ba259c9f30bb 100644 --- a/crates/re_renderer/src/importer/gltf.rs +++ b/crates/re_renderer/src/importer/gltf.rs @@ -68,8 +68,16 @@ pub fn load_gltf_from_buffer( }; images_as_textures.push( - ctx.texture_manager_2d - .create(&mut ctx.gpu_resources.textures, &texture), + match ctx + .texture_manager_2d + .create(&mut ctx.gpu_resources.textures, &texture) + { + Ok(texture) => texture, + Err(err) => { + re_log::error!("Failed to create texture: {err}"); + ctx.texture_manager_2d.white_texture_unorm_handle().clone() + } + }, ); } diff --git a/crates/re_renderer/src/lib.rs b/crates/re_renderer/src/lib.rs index 770c0589f7fe..1932e1aeb9e2 100644 --- a/crates/re_renderer/src/lib.rs +++ b/crates/re_renderer/src/lib.rs @@ -12,6 +12,7 @@ pub mod importer; pub mod mesh; pub mod renderer; pub mod resource_managers; +pub mod texture_info; pub mod view_builder; mod allocator; diff --git a/crates/re_renderer/src/renderer/debug_overlay.rs b/crates/re_renderer/src/renderer/debug_overlay.rs index 6e615cd4a710..5b78ac3e3139 100644 --- a/crates/re_renderer/src/renderer/debug_overlay.rs +++ b/crates/re_renderer/src/renderer/debug_overlay.rs @@ -49,6 +49,12 @@ pub struct DebugOverlayRenderer { bind_group_layout: GpuBindGroupLayoutHandle, } +#[derive(thiserror::Error, Debug)] +pub enum DebugOverlayError { + #[error("Can't display texture with format: {0:?}")] + UnsupportedTextureFormat(wgpu::TextureFormat), +} + /// Debug overlay for quick & dirty display of texture contents. /// /// Executed as part of the composition draw phase in order to allow "direct" output to the screen. @@ -70,7 +76,7 @@ impl DebugOverlayDrawData { debug_texture: &GpuTexture, screen_resolution: glam::UVec2, overlay_rect: IntRect, - ) -> Self { + ) -> Result { let mut renderers = ctx.renderers.write(); let debug_overlay = renderers.get_or_create::<_, DebugOverlayRenderer>( &ctx.shared_renderer_data, @@ -79,13 +85,22 @@ impl DebugOverlayDrawData { &mut ctx.resolver, ); - let mode = match debug_texture.texture.format().describe().sample_type { - wgpu::TextureSampleType::Depth | wgpu::TextureSampleType::Float { .. } => { + let mode = match debug_texture + .texture + .format() + .sample_type(Some(wgpu::TextureAspect::All)) + { + Some(wgpu::TextureSampleType::Depth | wgpu::TextureSampleType::Float { .. }) => { gpu_data::DebugOverlayMode::ShowFloatTexture } - wgpu::TextureSampleType::Sint | wgpu::TextureSampleType::Uint => { + Some(wgpu::TextureSampleType::Sint | wgpu::TextureSampleType::Uint) => { gpu_data::DebugOverlayMode::ShowUintTexture } + None => { + return Err(DebugOverlayError::UnsupportedTextureFormat( + debug_texture.texture.format(), + )) + } }; let uniform_buffer_binding = create_and_fill_uniform_buffer( @@ -112,7 +127,7 @@ impl DebugOverlayDrawData { ), }; - DebugOverlayDrawData { + Ok(DebugOverlayDrawData { bind_group: ctx.gpu_resources.bind_groups.alloc( &ctx.device, &ctx.gpu_resources, @@ -126,7 +141,7 @@ impl DebugOverlayDrawData { layout: debug_overlay.bind_group_layout, }, ), - } + }) } } diff --git a/crates/re_renderer/src/renderer/depth_cloud.rs b/crates/re_renderer/src/renderer/depth_cloud.rs index 285c0a2f9fd0..676d37e45391 100644 --- a/crates/re_renderer/src/renderer/depth_cloud.rs +++ b/crates/re_renderer/src/renderer/depth_cloud.rs @@ -11,18 +11,18 @@ //! The vertex shader backprojects the depth texture using the user-specified intrinsics, and then //! behaves pretty much exactly like our point cloud renderer (see [`point_cloud.rs`]). +use itertools::Itertools; use smallvec::smallvec; use crate::{ allocator::create_and_fill_uniform_buffer_batch, draw_phases::{DrawPhase, OutlineMaskProcessor}, include_shader_module, - resource_managers::ResourceManagerError, + resource_managers::{GpuTexture2D, ResourceManagerError}, view_builder::ViewBuilder, wgpu_resources::{ BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle, - GpuRenderPipelineHandle, GpuTexture, PipelineLayoutDesc, RenderPipelineDesc, - Texture2DBufferInfo, TextureDesc, + GpuRenderPipelineHandle, GpuTexture, PipelineLayoutDesc, RenderPipelineDesc, TextureDesc, }, Colormap, OutlineMaskPreference, PickingLayerObjectId, PickingLayerProcessor, }; @@ -34,9 +34,17 @@ use super::{ // --- +#[derive(Debug, Clone, Copy)] +enum AlbedoColorSpace { + Rgb, + Mono, +} + mod gpu_data { use crate::{wgpu_buffer_types, PickingLayerObjectId}; + use super::{AlbedoColorSpace, DepthCloudAlbedoData}; + /// Keep in sync with mirror in `depth_cloud.wgsl.` #[repr(C, align(256))] #[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] @@ -50,7 +58,7 @@ mod gpu_data { pub picking_layer_object_id: PickingLayerObjectId, /// Multiplier to get world-space depth from whatever is in the texture. - pub world_depth_from_texture_value: f32, + pub world_depth_from_texture_depth: f32, /// Point radius is calculated as world-space depth times this value. pub point_radius_from_world_depth: f32, @@ -61,10 +69,13 @@ mod gpu_data { /// Which colormap should be used. pub colormap: u32, + /// Is the albedo texture rgb or mono + pub albedo_color_space: wgpu_buffer_types::U32RowPadded, + /// Changes over different draw-phases. pub radius_boost_in_ui_points: wgpu_buffer_types::F32RowPadded, - pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 4 - 3 - 1 - 1 - 1], + pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 4 - 3 - 1 - 1 - 1 - 1], } impl DepthCloudInfoUBO { @@ -75,31 +86,35 @@ mod gpu_data { let super::DepthCloud { world_from_obj, depth_camera_intrinsics, - world_depth_from_data_depth, + world_depth_from_texture_depth, point_radius_from_world_depth, max_depth_in_world, depth_dimensions: _, - depth_data, + depth_texture: _, colormap, outline_mask_id, picking_object_id, + albedo_dimensions: _, + albedo_data: _, } = depth_cloud; - let user_depth_from_texture_value = match depth_data { - super::DepthCloudDepthData::U16(_) => 65535.0, // un-normalize - super::DepthCloudDepthData::F32(_) => 1.0, - }; - let world_depth_from_texture_value = - world_depth_from_data_depth * user_depth_from_texture_value; - Self { world_from_obj: (*world_from_obj).into(), depth_camera_intrinsics: (*depth_camera_intrinsics).into(), outline_mask_id: outline_mask_id.0.unwrap_or_default().into(), - world_depth_from_texture_value, + world_depth_from_texture_depth: *world_depth_from_texture_depth, point_radius_from_world_depth: *point_radius_from_world_depth, max_depth_in_world: *max_depth_in_world, colormap: *colormap as u32, + albedo_color_space: (depth_cloud + .albedo_data + .as_ref() + .map(|albedo_data| match albedo_data { + DepthCloudAlbedoData::Mono8(_) => AlbedoColorSpace::Mono, + _ => AlbedoColorSpace::Rgb, + }) + .unwrap_or(AlbedoColorSpace::Rgb) as u32) + .into(), radius_boost_in_ui_points: radius_boost_in_ui_points.into(), picking_layer_object_id: *picking_object_id, end_padding: Default::default(), @@ -108,30 +123,17 @@ mod gpu_data { } } -/// The raw data from a depth texture. -/// -/// This is either `u16` or `f32` values; in both cases the data will be uploaded to the shader -/// as-is. -/// For `u16`s, this results in a `Depth16Unorm` texture, otherwise an `R32Float`. -/// The reason we normalize `u16` is so that the shader can use a `float` texture in both cases. -/// However, it means we need to multiply the sampled value by `65535.0` in the shader to get -/// the actual depth. -/// -/// The shader assumes that this is normalized, linear, non-flipped depth using the camera -/// position as reference point (not the camera plane!). +/// The raw data for the (optional) albedo texture. // -// TODO(cmc): support more depth data types. -// TODO(cmc): expose knobs to linearize/normalize/flip/cam-to-plane depth. +// TODO(cmc): support more albedo data types. +// TODO(cmc): arrow buffers for u8... #[derive(Debug, Clone)] -pub enum DepthCloudDepthData { - U16(crate::Buffer), - F32(crate::Buffer), -} - -impl Default for DepthCloudDepthData { - fn default() -> Self { - Self::F32(Default::default()) - } +pub enum DepthCloudAlbedoData { + Rgb8(Vec), + Rgb8Srgb(Vec), + Rgba8(Vec), + Rgba8Srgb(Vec), + Mono8(Vec), } pub struct DepthCloud { @@ -143,8 +145,8 @@ pub struct DepthCloud { /// Only supports pinhole cameras at the moment. pub depth_camera_intrinsics: glam::Mat3, - /// Multiplier to get world-space depth from whatever is in [`Self::depth_data`]. - pub world_depth_from_data_depth: f32, + /// Multiplier to get world-space depth from whatever is in [`Self::depth_texture`]. + pub world_depth_from_texture_depth: f32, /// Point radius is calculated as world-space depth times this value. pub point_radius_from_world_depth: f32, @@ -155,10 +157,10 @@ pub struct DepthCloud { /// The dimensions of the depth texture in pixels. pub depth_dimensions: glam::UVec2, - /// The actual data from the depth texture. + /// The actual data for the depth texture. /// - /// See [`DepthCloudDepthData`] for more information. - pub depth_data: DepthCloudDepthData, + /// Only textures with sample type `Float` are supported. + pub depth_texture: GpuTexture2D, /// Configures color mapping mode. pub colormap: Colormap, @@ -168,6 +170,16 @@ pub struct DepthCloud { /// Picking object id that applies for the entire depth cloud. pub picking_object_id: PickingLayerObjectId, + + /// The dimensions of the (optional) albedo texture in pixels. + /// + /// Irrelevant if [`Self::albedo_data`] isn't set. + pub albedo_dimensions: glam::UVec2, + + /// The actual data for the (optional) albedo texture. + /// + /// If set, takes precedence over [`Self::colormap`]. + pub albedo_data: Option, } impl DepthCloud { @@ -192,7 +204,7 @@ impl DepthCloud { for corner in corners { let depth = corner.z; - let pos_in_obj = ((corner.truncate() - offset) * depth / focal_length).extend(depth); + let pos_in_obj = (((corner.truncate() - offset) * depth) / focal_length).extend(depth); let pos_in_world = self.world_from_obj.project_point3(pos_in_obj); bbox.extend(pos_in_world); } @@ -223,11 +235,20 @@ impl DrawData for DepthCloudDrawData { type Renderer = DepthCloudRenderer; } +#[derive(thiserror::Error, Debug)] +pub enum DepthCloudDrawDataError { + #[error("Depth texture format was {0:?}, only formats with sample type float are supported")] + InvalidDepthTextureFormat(wgpu::TextureFormat), + + #[error(transparent)] + ResourceManagerError(#[from] ResourceManagerError), +} + impl DepthCloudDrawData { pub fn new( ctx: &mut RenderContext, depth_clouds: &DepthClouds, - ) -> Result { + ) -> Result { crate::profile_function!(); let DepthClouds { @@ -276,56 +297,84 @@ impl DepthCloudDrawData { depth_cloud_ubo_binding_outlines, depth_cloud_ubo_binding_opaque ) { - let depth_texture = match &depth_cloud.depth_data { - DepthCloudDepthData::U16(data) => { - if cfg!(target_arch = "wasm32") { - // Web: manual normalization because Depth16Unorm textures aren't supported on - // the web (and won't ever be on the WebGL backend, see - // https://github.com/gfx-rs/wgpu/issues/3537). - // - // TODO(cmc): use an RG8 texture and unpack it manually in the shader instead. - use itertools::Itertools as _; - let dataf32 = data - .as_slice() - .iter() - .map(|d| *d as f32 / u16::MAX as f32) + if !matches!( + depth_cloud.depth_texture.format().sample_type(None), + Some(wgpu::TextureSampleType::Float { filterable: _ }) + ) { + return Err(DepthCloudDrawDataError::InvalidDepthTextureFormat( + depth_cloud.depth_texture.format(), + )); + } + let albedo_texture = depth_cloud + .albedo_data + .as_ref() + .map_or_else(|| { + create_and_upload_texture( + ctx, + (1, 1).into(), + wgpu::TextureFormat::Rgba8Unorm, + [0u8; 4].as_slice(), + ) + }, |data| match data { + DepthCloudAlbedoData::Rgba8(data) => create_and_upload_texture( + ctx, + depth_cloud.albedo_dimensions, + wgpu::TextureFormat::Rgba8Unorm, + data.as_slice(), + ), + DepthCloudAlbedoData::Rgba8Srgb(data) => create_and_upload_texture( + ctx, + depth_cloud.albedo_dimensions, + wgpu::TextureFormat::Rgba8UnormSrgb, + data.as_slice(), + ), + DepthCloudAlbedoData::Rgb8(data) => { + let data = data + .chunks(3) + .into_iter() + .flat_map(|c| [c[0], c[1], c[2], 255]) .collect_vec(); create_and_upload_texture( ctx, - depth_cloud, - dataf32.as_slice(), - wgpu::TextureFormat::R32Float, + depth_cloud.albedo_dimensions, + wgpu::TextureFormat::Rgba8Unorm, + data.as_slice(), ) - } else { - // Native: We use Depth16Unorm over R16Unorm because the latter is behind a feature flag and doesn't work on WebGPU. + } + DepthCloudAlbedoData::Rgb8Srgb(data) => { + let data = data + .chunks(3) + .into_iter() + .flat_map(|c| [c[0], c[1], c[2], 255]) + .collect_vec(); create_and_upload_texture( ctx, - depth_cloud, + depth_cloud.albedo_dimensions, + wgpu::TextureFormat::Rgba8UnormSrgb, data.as_slice(), - wgpu::TextureFormat::Depth16Unorm, ) } - } - DepthCloudDepthData::F32(data) => create_and_upload_texture( - ctx, - depth_cloud, - data.as_slice(), - wgpu::TextureFormat::R32Float, - ), - }; + DepthCloudAlbedoData::Mono8(data) => create_and_upload_texture( + ctx, + depth_cloud.albedo_dimensions, + wgpu::TextureFormat::R8Unorm, + data.as_slice(), + ), + }); let mk_bind_group = |label, ubo: BindGroupEntry| { ctx.gpu_resources.bind_groups.alloc( &ctx.device, &ctx.gpu_resources, - &BindGroupDesc { + &(BindGroupDesc { label, entries: smallvec![ ubo, - BindGroupEntry::DefaultTextureView(depth_texture.handle), + BindGroupEntry::DefaultTextureView(depth_cloud.depth_texture.handle), + BindGroupEntry::DefaultTextureView(albedo_texture.handle) ], layout: bg_layout, - }, + }), ) }; @@ -345,78 +394,51 @@ impl DepthCloudDrawData { } fn create_and_upload_texture( - ctx: &mut RenderContext, - depth_cloud: &DepthCloud, + ctx: &RenderContext, + dimensions: glam::UVec2, + format: wgpu::TextureFormat, data: &[T], - depth_format: wgpu::TextureFormat, ) -> GpuTexture { crate::profile_function!(); - let depth_texture_size = wgpu::Extent3d { - width: depth_cloud.depth_dimensions.x, - height: depth_cloud.depth_dimensions.y, + let texture_size = wgpu::Extent3d { + width: dimensions.x, + height: dimensions.y, depth_or_array_layers: 1, }; - let depth_texture_desc = TextureDesc { - label: "depth_texture".into(), - size: depth_texture_size, + let texture_desc = TextureDesc { + label: "texture".into(), + size: texture_size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: depth_format, + format, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, }; - let depth_texture = ctx - .gpu_resources - .textures - .alloc(&ctx.device, &depth_texture_desc); - - // Not supporting compressed formats here. - debug_assert!(depth_texture_desc.format.describe().block_dimensions == (1, 1)); - - let buffer_info = - Texture2DBufferInfo::new(depth_texture_desc.format, depth_cloud.depth_dimensions); - - // TODO(andreas): CpuGpuWriteBelt should make it easier to do this. - let bytes_padding_per_row = - (buffer_info.bytes_per_row_padded - buffer_info.bytes_per_row_unpadded) as usize; - // Sanity check the padding size. If this happens something is seriously wrong, as it would imply - // that we can't express the required alignment with the block size. - debug_assert!( - bytes_padding_per_row % std::mem::size_of::() == 0, - "Padding is not a multiple of pixel size. Can't correctly pad the texture data" - ); + let texture = ctx.gpu_resources.textures.alloc(&ctx.device, &texture_desc); + + let format_info = texture_desc.format; + let width_blocks = dimensions.x / (format_info.block_dimensions().0 as u32); - let mut depth_texture_staging = ctx.cpu_write_gpu_read_belt.lock().allocate::( + let mut texture_staging = ctx.cpu_write_gpu_read_belt.lock().allocate::( &ctx.device, &ctx.gpu_resources.buffers, - buffer_info.buffer_size_padded as usize / std::mem::size_of::(), + data.len(), ); + texture_staging.extend_from_slice(data); - // Fill with a single copy if possible, otherwise do multiple, filling in padding. - if bytes_padding_per_row == 0 { - depth_texture_staging.extend_from_slice(data); - } else { - let num_pixel_padding_per_row = bytes_padding_per_row / std::mem::size_of::(); - for row in data.chunks(depth_texture_desc.size.width as usize) { - depth_texture_staging.extend_from_slice(row); - depth_texture_staging - .extend(std::iter::repeat(T::zeroed()).take(num_pixel_padding_per_row)); - } - } - - depth_texture_staging.copy_to_texture2d( + texture_staging.copy_to_texture2d( ctx.active_frame.before_view_builder_encoder.lock().get(), wgpu::ImageCopyTexture { - texture: &depth_texture.inner.texture, + texture: &texture.inner.texture, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }, - depth_cloud.depth_dimensions, + glam::UVec2::new(texture_size.width, texture_size.height), ); - depth_texture + texture } pub struct DepthCloudRenderer { @@ -447,7 +469,7 @@ impl Renderer for DepthCloudRenderer { let bind_group_layout = pools.bind_group_layouts.get_or_create( device, - &BindGroupLayoutDesc { + &(BindGroupLayoutDesc { label: "depth_cloud_bg_layout".into(), entries: vec![ wgpu::BindGroupLayoutEntry { @@ -473,16 +495,26 @@ impl Renderer for DepthCloudRenderer { }, count: None, }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, ], - }, + }), ); let pipeline_layout = pools.pipeline_layouts.get_or_create( device, - &PipelineLayoutDesc { + &(PipelineLayoutDesc { label: "depth_cloud_rp_layout".into(), entries: vec![shared_data.global_bindings.layout, bind_group_layout], - }, + }), &pools.bind_group_layouts, ); @@ -521,20 +553,20 @@ impl Renderer for DepthCloudRenderer { ); let render_pipeline_picking_layer = pools.render_pipelines.get_or_create( device, - &RenderPipelineDesc { + &(RenderPipelineDesc { label: "DepthCloudRenderer::render_pipeline_picking_layer".into(), fragment_entrypoint: "fs_main_picking_layer".into(), render_targets: smallvec![Some(PickingLayerProcessor::PICKING_LAYER_FORMAT.into())], depth_stencil: PickingLayerProcessor::PICKING_LAYER_DEPTH_STATE, multisample: PickingLayerProcessor::PICKING_LAYER_MSAA_STATE, ..render_pipeline_desc_color.clone() - }, + }), &pools.pipeline_layouts, &pools.shader_modules, ); let render_pipeline_outline_mask = pools.render_pipelines.get_or_create( device, - &RenderPipelineDesc { + &(RenderPipelineDesc { label: "DepthCloudRenderer::render_pipeline_outline_mask".into(), fragment_entrypoint: "fs_main_outline_mask".into(), render_targets: smallvec![Some(OutlineMaskProcessor::MASK_FORMAT.into())], @@ -544,7 +576,7 @@ impl Renderer for DepthCloudRenderer { shared_data.config.hardware_tier, ), ..render_pipeline_desc_color - }, + }), &pools.pipeline_layouts, &pools.shader_modules, ); diff --git a/crates/re_renderer/src/renderer/lines.rs b/crates/re_renderer/src/renderer/lines.rs index cf1222acfe1b..a46571928c63 100644 --- a/crates/re_renderer/src/renderer/lines.rs +++ b/crates/re_renderer/src/renderer/lines.rs @@ -104,10 +104,7 @@ //! * note that this would let us remove the degenerated quads between lines, making the approach cleaner and removing the "restart bit" //! -use std::{ - num::{NonZeroU32, NonZeroU64}, - ops::Range, -}; +use std::{num::NonZeroU64, ops::Range}; use bitflags::bitflags; use bytemuck::Zeroable; @@ -479,7 +476,7 @@ impl LineDrawData { bytemuck::cast_slice(&position_data_staging), wgpu::ImageDataLayout { offset: 0, - bytes_per_row: NonZeroU32::new( + bytes_per_row: Some( POSITION_TEXTURE_SIZE * std::mem::size_of::() as u32, ), rows_per_image: None, @@ -529,7 +526,7 @@ impl LineDrawData { bytemuck::cast_slice(&line_strip_info_staging), wgpu::ImageDataLayout { offset: 0, - bytes_per_row: NonZeroU32::new( + bytes_per_row: Some( LINE_STRIP_TEXTURE_SIZE * std::mem::size_of::() as u32, ), diff --git a/crates/re_renderer/src/renderer/mod.rs b/crates/re_renderer/src/renderer/mod.rs index 3b4284bc2a6d..b8b2bc508968 100644 --- a/crates/re_renderer/src/renderer/mod.rs +++ b/crates/re_renderer/src/renderer/mod.rs @@ -15,7 +15,7 @@ pub use point_cloud::{ mod depth_cloud; pub use self::depth_cloud::{ - DepthCloud, DepthCloudDepthData, DepthCloudDrawData, DepthCloudRenderer, DepthClouds, + DepthCloud, DepthCloudAlbedoData, DepthCloudDrawData, DepthCloudRenderer, DepthClouds, }; mod test_triangle; @@ -35,7 +35,7 @@ mod compositor; pub(crate) use compositor::CompositorDrawData; mod debug_overlay; -pub use debug_overlay::{DebugOverlayDrawData, DebugOverlayRenderer}; +pub use debug_overlay::{DebugOverlayDrawData, DebugOverlayError, DebugOverlayRenderer}; use crate::{ context::{RenderContext, SharedRendererData}, diff --git a/crates/re_renderer/src/renderer/point_cloud.rs b/crates/re_renderer/src/renderer/point_cloud.rs index 639db5a17ceb..03611b80a256 100644 --- a/crates/re_renderer/src/renderer/point_cloud.rs +++ b/crates/re_renderer/src/renderer/point_cloud.rs @@ -130,7 +130,7 @@ pub struct PointCloudBatchInfo { /// Defines an outline mask for an individual vertex ranges. /// - /// Vertex ranges are *not* relative within the current batch, but relates to the draw data vertex buffer. + /// Vertex ranges are relative within the current batch. /// /// Having many of these individual outline masks can be slow as they require each their own uniform buffer & draw call. /// This feature is meant for a limited number of "extra selections" @@ -455,6 +455,8 @@ impl PointCloudDrawData { )); for (range, _) in &batch_info.additional_outline_mask_ids_vertex_ranges { + let range = (range.start + start_point_for_next_batch) + ..(range.end + start_point_for_next_batch); batches_internal.push(point_renderer.create_point_cloud_batch( ctx, format!("{:?} strip-only {:?}", batch_info.label, range).into(), diff --git a/crates/re_renderer/src/renderer/rectangles.rs b/crates/re_renderer/src/renderer/rectangles.rs index 799655ecf78c..8b380a30a2bd 100644 --- a/crates/re_renderer/src/renderer/rectangles.rs +++ b/crates/re_renderer/src/renderer/rectangles.rs @@ -19,6 +19,7 @@ use crate::{ draw_phases::{DrawPhase, OutlineMaskProcessor}, include_shader_module, resource_managers::{GpuTexture2D, ResourceManagerError}, + texture_info, view_builder::ViewBuilder, wgpu_resources::{ BindGroupDesc, BindGroupEntry, BindGroupLayoutDesc, GpuBindGroup, GpuBindGroupLayoutHandle, @@ -33,7 +34,7 @@ use super::{ }; /// Texture filter setting for magnification (a texel covers several pixels). -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum TextureFilterMag { Linear, Nearest, @@ -41,7 +42,7 @@ pub enum TextureFilterMag { } /// Texture filter setting for minification (several texels fall to one pixel). -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum TextureFilterMin { Linear, Nearest, @@ -98,6 +99,7 @@ impl ColormappedTexture { } } +#[derive(Clone)] pub struct TexturedRect { /// Top left corner position in world space. pub top_left_corner_position: glam::Vec3, @@ -114,6 +116,7 @@ pub struct TexturedRect { pub options: RectangleOptions, } +#[derive(Clone)] pub struct RectangleOptions { pub texture_filter_magnification: TextureFilterMag, pub texture_filter_minification: TextureFilterMin, @@ -166,7 +169,7 @@ pub enum RectangleError { } mod gpu_data { - use crate::wgpu_buffer_types; + use crate::{texture_info, wgpu_buffer_types}; use super::{ColorMapper, RectangleError, TexturedRect}; @@ -214,9 +217,11 @@ mod gpu_data { } impl UniformBuffer { - pub fn from_textured_rect(rectangle: &super::TexturedRect) -> Result { + pub fn from_textured_rect( + rectangle: &super::TexturedRect, + device_features: wgpu::Features, + ) -> Result { let texture_format = rectangle.colormapped_texture.texture.format(); - let texture_info = texture_format.describe(); let TexturedRect { top_left_corner_position, @@ -241,25 +246,25 @@ mod gpu_data { outline_mask, } = options; - let sample_type = match texture_info.sample_type { - wgpu::TextureSampleType::Float { .. } => { - if super::is_float_filterable(&texture_format) { + let sample_type = match texture_format.sample_type(None) { + Some(wgpu::TextureSampleType::Float { .. }) => { + if texture_info::is_float_filterable(texture_format, device_features) { SAMPLE_TYPE_FLOAT_FILTER } else { SAMPLE_TYPE_FLOAT_NOFILTER } } - wgpu::TextureSampleType::Depth => { + Some(wgpu::TextureSampleType::Sint) => SAMPLE_TYPE_SINT_NOFILTER, + Some(wgpu::TextureSampleType::Uint) => SAMPLE_TYPE_UINT_NOFILTER, + _ => { return Err(RectangleError::DepthTexturesNotSupported); } - wgpu::TextureSampleType::Sint => SAMPLE_TYPE_SINT_NOFILTER, - wgpu::TextureSampleType::Uint => SAMPLE_TYPE_UINT_NOFILTER, }; let mut colormap_function = 0; let color_mapper_int; - match texture_info.components { + match texture_info::num_texture_components(texture_format) { 1 => match color_mapper { Some(ColorMapper::Function(colormap)) => { color_mapper_int = COLOR_MAPPER_FUNCTION; @@ -352,7 +357,7 @@ impl RectangleDrawData { // TODO(emilk): continue on error (skipping just that rectangle)? let uniform_buffers: Vec<_> = rectangles .iter() - .map(gpu_data::UniformBuffer::from_textured_rect) + .map(|rect| gpu_data::UniformBuffer::from_textured_rect(rect, ctx.device.features())) .try_collect()?; let uniform_buffer_bindings = create_and_fill_uniform_buffer_batch( @@ -387,10 +392,9 @@ impl RectangleDrawData { let texture = &rectangle.colormapped_texture.texture; let texture_format = texture.creation_desc.format; - let texture_description = texture_format.describe(); - if texture_description.required_features != Default::default() { + if texture_format.required_features() != Default::default() { return Err(RectangleError::SpecialFeatures( - texture_description.required_features, + texture_format.required_features(), )); } @@ -400,23 +404,23 @@ impl RectangleDrawData { let mut texture_sint = ctx.texture_manager_2d.zeroed_texture_sint().handle; let mut texture_uint = ctx.texture_manager_2d.zeroed_texture_uint().handle; - match texture_description.sample_type { - wgpu::TextureSampleType::Float { .. } => { - if is_float_filterable(&texture_format) { + match texture_format.sample_type(None) { + Some(wgpu::TextureSampleType::Float { .. }) => { + if texture_info::is_float_filterable(texture_format, ctx.device.features()) { texture_float_filterable = texture.handle; } else { texture_float_nofilter = texture.handle; } } - wgpu::TextureSampleType::Depth => { - return Err(RectangleError::DepthTexturesNotSupported); - } - wgpu::TextureSampleType::Sint => { + Some(wgpu::TextureSampleType::Sint) => { texture_sint = texture.handle; } - wgpu::TextureSampleType::Uint => { + Some(wgpu::TextureSampleType::Uint) => { texture_uint = texture.handle; } + _ => { + return Err(RectangleError::DepthTexturesNotSupported); + } } // We also set up an optional colormap texture. @@ -689,11 +693,3 @@ impl Renderer for RectangleRenderer { ] } } - -fn is_float_filterable(format: &wgpu::TextureFormat) -> bool { - format - .describe() - .guaranteed_format_features - .flags - .contains(wgpu::TextureFormatFeatureFlags::FILTERABLE) -} diff --git a/crates/re_renderer/src/resource_managers/mod.rs b/crates/re_renderer/src/resource_managers/mod.rs index 7e1eff185a79..d9455d266071 100644 --- a/crates/re_renderer/src/resource_managers/mod.rs +++ b/crates/re_renderer/src/resource_managers/mod.rs @@ -10,7 +10,10 @@ mod mesh_manager; pub use mesh_manager::{GpuMeshHandle, MeshManager}; mod texture_manager; -pub use texture_manager::{GpuTexture2D, Texture2DCreationDesc, TextureManager2D}; +pub use texture_manager::{ + GpuTexture2D, Texture2DCreationDesc, TextureCreationError, TextureManager2D, + TextureManager2DError, +}; mod resource_manager; pub use resource_manager::{ResourceHandle, ResourceLifeTime, ResourceManagerError}; diff --git a/crates/re_renderer/src/resource_managers/texture_manager.rs b/crates/re_renderer/src/resource_managers/texture_manager.rs index 980918cb8a48..80da2c5768fe 100644 --- a/crates/re_renderer/src/resource_managers/texture_manager.rs +++ b/crates/re_renderer/src/resource_managers/texture_manager.rs @@ -1,4 +1,4 @@ -use std::{num::NonZeroU32, sync::Arc}; +use std::sync::Arc; use ahash::{HashMap, HashSet}; @@ -75,7 +75,9 @@ pub struct Texture2DCreationDesc<'a> { pub label: DebugLabel, /// Data for the highest mipmap level. - /// Must be padded according to wgpu rules and ready for upload. + /// + /// Data is expected to be tightly packed. + /// I.e. it is *not* padded according to wgpu buffer->texture transfer rules, padding will happen on the fly if necessary. /// TODO(andreas): This should be a kind of factory function/builder instead which gets target memory passed in. pub data: std::borrow::Cow<'a, [u8]>, pub format: wgpu::TextureFormat, @@ -93,6 +95,41 @@ impl<'a> Texture2DCreationDesc<'a> { } } +// TODO(andreas): Move this to texture pool. +#[derive(thiserror::Error, Debug)] +pub enum TextureCreationError { + #[error("Texture with debug label {0:?} has zero width or height!")] + ZeroSize(DebugLabel), + + #[error( + "Texture with debug label {label:?} has a format {format:?} that data can't be transferred to!" + )] + UnsupportedFormatForTransfer { + label: DebugLabel, + format: wgpu::TextureFormat, + }, +} + +#[derive(thiserror::Error, Debug)] +pub enum TextureManager2DError { + /// Something went wrong when creating the GPU texture. + #[error(transparent)] + TextureCreation(#[from] TextureCreationError), + + /// Something went wrong in a user-callback. + #[error(transparent)] + DataCreation(DataCreationError), +} + +impl From> for TextureCreationError { + fn from(err: TextureManager2DError) -> Self { + match err { + TextureManager2DError::TextureCreation(texture_creation) => texture_creation, + TextureManager2DError::DataCreation(never) => match never {}, + } + } +} + /// Texture manager for 2D textures. /// /// The scope is intentionally limited to particular kinds of textures that currently @@ -144,7 +181,8 @@ impl TextureManager2D { width: 1, height: 1, }, - ); + ) + .expect("Failed to create white pixel texture!"); let zeroed_texture_float = create_zero_texture(texture_pool, &device, wgpu::TextureFormat::Rgba8Unorm); @@ -174,7 +212,7 @@ impl TextureManager2D { &mut self, texture_pool: &mut GpuTexturePool, creation_desc: &Texture2DCreationDesc<'_>, - ) -> GpuTexture2D { + ) -> Result { // TODO(andreas): Disabled the warning as we're moving towards using this texture manager for user-logged images. // However, it's still very much a concern especially once we add mipmapping. Something we need to keep in mind. // @@ -201,39 +239,47 @@ impl TextureManager2D { key: u64, texture_pool: &mut GpuTexturePool, texture_desc: Texture2DCreationDesc<'_>, - ) -> GpuTexture2D { - enum Never {} - match self.get_or_create_with(key, texture_pool, || -> Result<_, Never> { - Ok(texture_desc) - }) { - Ok(tex_handle) => tex_handle, - Err(never) => match never {}, - } + ) -> Result { + self.get_or_create_with(key, texture_pool, || texture_desc) } /// Creates a new 2D texture resource and schedules data upload to the GPU if a texture /// wasn't already created using the same key. - pub fn get_or_create_with<'a, Err>( + pub fn get_or_create_with<'a>( + &mut self, + key: u64, + texture_pool: &mut GpuTexturePool, + create_texture_desc: impl FnOnce() -> Texture2DCreationDesc<'a>, + ) -> Result { + self.get_or_try_create_with(key, texture_pool, || -> Result<_, never::Never> { + Ok(create_texture_desc()) + }) + .map_err(|err| err.into()) + } + + /// Creates a new 2D texture resource and schedules data upload to the GPU if a texture + /// wasn't already created using the same key. + pub fn get_or_try_create_with<'a, Err: std::fmt::Display>( &mut self, key: u64, texture_pool: &mut GpuTexturePool, try_create_texture_desc: impl FnOnce() -> Result, Err>, - ) -> Result { + ) -> Result> { let texture_handle = match self.texture_cache.entry(key) { std::collections::hash_map::Entry::Occupied(texture_handle) => { texture_handle.get().clone() // already inserted } std::collections::hash_map::Entry::Vacant(entry) => { // Run potentially expensive texture creation code: - let tex_creation_desc = try_create_texture_desc()?; - entry - .insert(Self::create_and_upload_texture( - &self.device, - &self.queue, - texture_pool, - &tex_creation_desc, - )) - .clone() + let tex_creation_desc = try_create_texture_desc() + .map_err(|err| TextureManager2DError::DataCreation(err))?; + let texture = Self::create_and_upload_texture( + &self.device, + &self.queue, + texture_pool, + &tex_creation_desc, + )?; + entry.insert(texture).clone() } }; @@ -276,8 +322,13 @@ impl TextureManager2D { queue: &wgpu::Queue, texture_pool: &mut GpuTexturePool, creation_desc: &Texture2DCreationDesc<'_>, - ) -> GpuTexture2D { + ) -> Result { crate::profile_function!(); + + if creation_desc.width == 0 || creation_desc.height == 0 { + return Err(TextureCreationError::ZeroSize(creation_desc.label.clone())); + } + let size = wgpu::Extent3d { width: creation_desc.width, height: creation_desc.height, @@ -296,9 +347,15 @@ impl TextureManager2D { }, ); - let format_info = creation_desc.format.describe(); - let width_blocks = creation_desc.width / format_info.block_dimensions.0 as u32; - let bytes_per_row_unaligned = width_blocks * format_info.block_size as u32; + let width_blocks = creation_desc.width / creation_desc.format.block_dimensions().0; + let block_size = creation_desc + .format + .block_size(Some(wgpu::TextureAspect::All)) + .ok_or_else(|| TextureCreationError::UnsupportedFormatForTransfer { + label: creation_desc.label.clone(), + format: creation_desc.format, + })?; + let bytes_per_row_unaligned = width_blocks * block_size; // TODO(andreas): Once we have our own temp buffer for uploading, we can do the padding inplace // I.e. the only difference will be if we do one memcopy or one memcopy per row, making row padding a nuisance! @@ -317,9 +374,7 @@ impl TextureManager2D { data, wgpu::ImageDataLayout { offset: 0, - bytes_per_row: Some( - NonZeroU32::new(bytes_per_row_unaligned).expect("invalid bytes per row"), - ), + bytes_per_row: Some(bytes_per_row_unaligned), rows_per_image: None, }, size, @@ -327,7 +382,7 @@ impl TextureManager2D { // TODO(andreas): mipmap generation - GpuTexture2D(texture) + Ok(GpuTexture2D(texture)) } pub(crate) fn begin_frame(&mut self, _frame_index: u64) { diff --git a/crates/re_renderer/src/texture_info.rs b/crates/re_renderer/src/texture_info.rs new file mode 100644 index 000000000000..e18d447e62f8 --- /dev/null +++ b/crates/re_renderer/src/texture_info.rs @@ -0,0 +1,206 @@ +use std::borrow::Cow; + +/// Utility for dealing with buffers containing raw 2D texture data. +#[derive(Clone)] +pub struct Texture2DBufferInfo { + /// How many bytes per row contain actual data. + pub bytes_per_row_unpadded: u32, + + /// How many bytes per row are required to be allocated in total. + /// + /// Padding bytes are always at the end of a row. + pub bytes_per_row_padded: u32, + + /// Size required for an unpadded buffer. + pub buffer_size_unpadded: wgpu::BufferAddress, + + /// Size required for a padded buffer as it is read/written from/to the GPU. + pub buffer_size_padded: wgpu::BufferAddress, +} + +impl Texture2DBufferInfo { + /// Retrieves 2D texture buffer info for a given format & texture size. + /// + /// If a single buffer is not possible for all aspects of the texture format, all sizes will be zero. + #[inline] + pub fn new(format: wgpu::TextureFormat, extent: glam::UVec2) -> Self { + let block_dimensions = format.block_dimensions(); + let width_blocks = extent.x / block_dimensions.0; + let height_blocks = extent.y / block_dimensions.1; + + let block_size = format + .block_size(Some(wgpu::TextureAspect::All)) + .unwrap_or(0); // This happens if we can't have a single buffer. + let bytes_per_row_unpadded = width_blocks * block_size; + let bytes_per_row_padded = + wgpu::util::align_to(bytes_per_row_unpadded, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT); + + Self { + bytes_per_row_unpadded, + bytes_per_row_padded, + buffer_size_unpadded: (bytes_per_row_unpadded * height_blocks) as wgpu::BufferAddress, + buffer_size_padded: (bytes_per_row_padded * height_blocks) as wgpu::BufferAddress, + } + } + + #[inline] + pub fn num_rows(&self) -> u32 { + self.buffer_size_padded as u32 / self.bytes_per_row_padded + } + + /// Removes the padding from a buffer containing gpu texture data. + /// + /// The passed in buffer is to be expected to be exactly of size [`Texture2DBufferInfo::buffer_size_padded`]. + /// + /// Note that if you're passing in gpu data, there no alignment guarantees on the returned slice, + /// do NOT convert it using [`bytemuck`]. Use [`Texture2DBufferInfo::remove_padding_and_convert`] instead. + pub fn remove_padding<'a>(&self, buffer: &'a [u8]) -> Cow<'a, [u8]> { + crate::profile_function!(); + + assert_eq!(buffer.len() as wgpu::BufferAddress, self.buffer_size_padded); + + if self.bytes_per_row_padded == self.bytes_per_row_unpadded { + return Cow::Borrowed(buffer); + } + + let mut unpadded_buffer = Vec::with_capacity(self.buffer_size_unpadded as _); + + for row in 0..self.num_rows() { + let offset = (self.bytes_per_row_padded * row) as usize; + unpadded_buffer.extend_from_slice( + &buffer[offset..(offset + self.bytes_per_row_unpadded as usize)], + ); + } + + unpadded_buffer.into() + } + + /// Removes the padding from a buffer containing gpu texture data and remove convert to a given type. + /// + /// The passed in buffer is to be expected to be exactly of size [`Texture2DBufferInfo::buffer_size_padded`]. + /// + /// The unpadded row size is expected to be a multiple of the size of the target type. + /// (Which means that, while uncommon, it technically doesn't need to be as big as a block in the pixel - this can be useful for e.g. packing wide bitfields) + pub fn remove_padding_and_convert(&self, buffer: &[u8]) -> Vec { + crate::profile_function!(); + + assert_eq!(buffer.len() as wgpu::BufferAddress, self.buffer_size_padded); + assert!(self.bytes_per_row_unpadded % std::mem::size_of::() as u32 == 0); + + // Due to https://github.com/gfx-rs/wgpu/issues/3508 the data might be completely unaligned, + // so much, that we can't even interpret it as e.g. a u32 slice. + // Therefore, we have to do a copy of the data regardless of whether it's padded or not. + + let mut unpadded_buffer: Vec = vec![ + T::zeroed(); + (self.num_rows() * self.bytes_per_row_unpadded / std::mem::size_of::() as u32) + as usize + ]; // TODO(andreas): Consider using unsafe set_len() instead of vec![] to avoid zeroing the memory. + + // The copy has to happen on a u8 slice, because any other type would assume some alignment that we can't guarantee because of the above. + let unpadded_buffer_u8_view = bytemuck::cast_slice_mut(&mut unpadded_buffer); + + for row in 0..self.num_rows() { + let offset_padded = (self.bytes_per_row_padded * row) as usize; + let offset_unpadded = (self.bytes_per_row_unpadded * row) as usize; + unpadded_buffer_u8_view + [offset_unpadded..(offset_unpadded + self.bytes_per_row_unpadded as usize)] + .copy_from_slice( + &buffer[offset_padded..(offset_padded + self.bytes_per_row_unpadded as usize)], + ); + } + + unpadded_buffer + } +} + +pub fn is_float_filterable(format: wgpu::TextureFormat, device_features: wgpu::Features) -> bool { + format + .guaranteed_format_features(device_features) + .flags + .contains(wgpu::TextureFormatFeatureFlags::FILTERABLE) +} + +pub fn num_texture_components(format: wgpu::TextureFormat) -> u8 { + #[allow(clippy::match_same_arms)] + match format { + wgpu::TextureFormat::R8Unorm + | wgpu::TextureFormat::R8Snorm + | wgpu::TextureFormat::R8Uint + | wgpu::TextureFormat::R8Sint + | wgpu::TextureFormat::R16Uint + | wgpu::TextureFormat::R16Sint + | wgpu::TextureFormat::R16Unorm + | wgpu::TextureFormat::R16Snorm + | wgpu::TextureFormat::R16Float + | wgpu::TextureFormat::R32Uint + | wgpu::TextureFormat::R32Sint + | wgpu::TextureFormat::R32Float => 1, + + wgpu::TextureFormat::Rg8Unorm + | wgpu::TextureFormat::Rg8Snorm + | wgpu::TextureFormat::Rg8Uint + | wgpu::TextureFormat::Rg8Sint + | wgpu::TextureFormat::Rg16Uint + | wgpu::TextureFormat::Rg16Sint + | wgpu::TextureFormat::Rg16Unorm + | wgpu::TextureFormat::Rg16Snorm + | wgpu::TextureFormat::Rg16Float + | wgpu::TextureFormat::Rg32Uint + | wgpu::TextureFormat::Rg32Sint + | wgpu::TextureFormat::Rg32Float => 2, + + wgpu::TextureFormat::Rgba8Unorm + | wgpu::TextureFormat::Rgba8UnormSrgb + | wgpu::TextureFormat::Rgba8Snorm + | wgpu::TextureFormat::Rgba8Uint + | wgpu::TextureFormat::Rgba8Sint + | wgpu::TextureFormat::Bgra8Unorm + | wgpu::TextureFormat::Bgra8UnormSrgb + | wgpu::TextureFormat::Rgba16Uint + | wgpu::TextureFormat::Rgba16Sint + | wgpu::TextureFormat::Rgba16Unorm + | wgpu::TextureFormat::Rgba16Snorm + | wgpu::TextureFormat::Rgba16Float + | wgpu::TextureFormat::Rgba32Uint + | wgpu::TextureFormat::Rgba32Sint + | wgpu::TextureFormat::Rgba32Float => 4, + + wgpu::TextureFormat::Rgb9e5Ufloat | wgpu::TextureFormat::Rg11b10Float => 3, + wgpu::TextureFormat::Rgb10a2Unorm => 4, + + wgpu::TextureFormat::Stencil8 + | wgpu::TextureFormat::Depth16Unorm + | wgpu::TextureFormat::Depth24Plus + | wgpu::TextureFormat::Depth32Float => 1, + + // It's complicated. Each aspect has actually only a single channel. + wgpu::TextureFormat::Depth24PlusStencil8 | wgpu::TextureFormat::Depth32FloatStencil8 => 2, + + wgpu::TextureFormat::Bc1RgbaUnorm + | wgpu::TextureFormat::Bc1RgbaUnormSrgb + | wgpu::TextureFormat::Bc2RgbaUnorm + | wgpu::TextureFormat::Bc2RgbaUnormSrgb + | wgpu::TextureFormat::Bc3RgbaUnorm + | wgpu::TextureFormat::Bc3RgbaUnormSrgb + | wgpu::TextureFormat::Bc4RUnorm + | wgpu::TextureFormat::Bc4RSnorm + | wgpu::TextureFormat::Bc5RgUnorm + | wgpu::TextureFormat::Bc5RgSnorm + | wgpu::TextureFormat::Bc6hRgbUfloat + | wgpu::TextureFormat::Bc6hRgbFloat + | wgpu::TextureFormat::Bc7RgbaUnorm + | wgpu::TextureFormat::Bc7RgbaUnormSrgb + | wgpu::TextureFormat::Etc2Rgb8Unorm + | wgpu::TextureFormat::Etc2Rgb8UnormSrgb + | wgpu::TextureFormat::Etc2Rgb8A1Unorm + | wgpu::TextureFormat::Etc2Rgb8A1UnormSrgb + | wgpu::TextureFormat::Etc2Rgba8Unorm + | wgpu::TextureFormat::Etc2Rgba8UnormSrgb + | wgpu::TextureFormat::EacR11Unorm + | wgpu::TextureFormat::EacR11Snorm + | wgpu::TextureFormat::EacRg11Unorm + | wgpu::TextureFormat::EacRg11Snorm + | wgpu::TextureFormat::Astc { .. } => 4, + } +} diff --git a/crates/re_renderer/src/view_builder.rs b/crates/re_renderer/src/view_builder.rs index 3d57b21a6f1c..86d8fc817b59 100644 --- a/crates/re_renderer/src/view_builder.rs +++ b/crates/re_renderer/src/view_builder.rs @@ -6,7 +6,8 @@ use crate::{ allocator::{create_and_fill_uniform_buffer, GpuReadbackIdentifier}, context::RenderContext, draw_phases::{ - DrawPhase, OutlineConfig, OutlineMaskProcessor, PickingLayerProcessor, ScreenshotProcessor, + DrawPhase, OutlineConfig, OutlineMaskProcessor, PickingLayerError, PickingLayerProcessor, + ScreenshotProcessor, }, global_bindings::FrameUniformBuffer, renderer::{CompositorDrawData, DebugOverlayDrawData, DrawData, Renderer}, @@ -37,6 +38,9 @@ pub enum ViewBuilderError { #[error("Picking rectangle readback was already scheduled.")] PickingRectAlreadyScheduled, + + #[error(transparent)] + InvalidDebugOverlay(#[from] crate::renderer::DebugOverlayError), } /// The highest level rendering block in `re_renderer`. @@ -562,7 +566,15 @@ impl ViewBuilder { //pass.set_bind_group(0, &setup.bind_group_0, &[]); self.draw_phase(ctx, DrawPhase::PickingLayer, &mut pass); } - picking_processor.end_render_pass(&mut encoder, &ctx.gpu_resources)?; + match picking_processor.end_render_pass(&mut encoder, &ctx.gpu_resources) { + Err(PickingLayerError::ResourcePoolError(err)) => { + return Err(err); + } + Err(PickingLayerError::ReadbackError(err)) => { + re_log::warn_once!("Failed to schedule picking data readback: {err}"); + } + Ok(()) => {} + } } if let Some(outline_mask_processor) = self.outline_mask_processor.take() { @@ -582,7 +594,12 @@ impl ViewBuilder { pass.set_bind_group(0, &setup.bind_group_0, &[]); self.draw_phase(ctx, DrawPhase::CompositingScreenshot, &mut pass); } - screenshot_processor.end_render_pass(&mut encoder); + match screenshot_processor.end_render_pass(&mut encoder) { + Ok(()) => {} + Err(err) => { + re_log::warn_once!("Failed to schedule screenshot data readback: {err}"); + } + } } Ok(encoder.finish()) @@ -692,7 +709,7 @@ impl ViewBuilder { &picking_processor.picking_target, self.setup.resolution_in_pixel.into(), picking_rect, - )); + )?); } self.picking_processor = Some(picking_processor); diff --git a/crates/re_renderer/src/wgpu_resources/dynamic_resource_pool.rs b/crates/re_renderer/src/wgpu_resources/dynamic_resource_pool.rs index 6c873429db4b..5c71fe51ac28 100644 --- a/crates/re_renderer/src/wgpu_resources/dynamic_resource_pool.rs +++ b/crates/re_renderer/src/wgpu_resources/dynamic_resource_pool.rs @@ -148,6 +148,13 @@ where self.current_frame_index = frame_index; let state = self.state.get_mut(); + let update_stats = |creation_desc: &Desc| { + self.total_resource_size_in_bytes.fetch_sub( + creation_desc.resource_size_in_bytes(), + std::sync::atomic::Ordering::Relaxed, + ); + }; + // Throw out any resources that we haven't reclaimed last frame. for (desc, resources) in state.last_frame_deallocated.drain() { re_log::trace!( @@ -160,11 +167,8 @@ where debug_assert!(false, "a resource was marked as destroyed last frame that we no longer kept track of"); continue; }; + update_stats(&desc); on_destroy_resource(&removed_resource); - self.total_resource_size_in_bytes.fetch_sub( - desc.resource_size_in_bytes(), - std::sync::atomic::Ordering::Relaxed, - ); } } @@ -184,6 +188,7 @@ where .push(resource.handle); true } else { + update_stats(&resource.creation_desc); on_destroy_resource(&resource.inner); false } diff --git a/crates/re_renderer/src/wgpu_resources/mod.rs b/crates/re_renderer/src/wgpu_resources/mod.rs index 06f30b6292d3..e5e3a389f731 100644 --- a/crates/re_renderer/src/wgpu_resources/mod.rs +++ b/crates/re_renderer/src/wgpu_resources/mod.rs @@ -7,7 +7,6 @@ //! higher level resources that arise from processing user provided data. mod bind_group_layout_pool; -use std::borrow::Cow; pub use bind_group_layout_pool::{ BindGroupLayoutDesc, GpuBindGroupLayoutHandle, GpuBindGroupLayoutPool, @@ -116,112 +115,3 @@ impl WgpuResourcePools { } } } - -/// Utility for dealing with buffers containing raw 2D texture data. -#[derive(Clone)] -pub struct Texture2DBufferInfo { - /// How many bytes per row contain actual data. - pub bytes_per_row_unpadded: u32, - - /// How many bytes per row are required to be allocated in total. - /// - /// Padding bytes are always at the end of a row. - pub bytes_per_row_padded: u32, - - /// Size required for an unpadded buffer. - pub buffer_size_unpadded: wgpu::BufferAddress, - - /// Size required for a padded buffer as it is read/written from/to the GPU. - pub buffer_size_padded: wgpu::BufferAddress, -} - -impl Texture2DBufferInfo { - #[inline] - pub fn new(format: wgpu::TextureFormat, extent: glam::UVec2) -> Self { - let format_info = format.describe(); - - let width_blocks = extent.x / format_info.block_dimensions.0 as u32; - let height_blocks = extent.y / format_info.block_dimensions.1 as u32; - - let bytes_per_row_unpadded = width_blocks * format_info.block_size as u32; - let bytes_per_row_padded = - wgpu::util::align_to(bytes_per_row_unpadded, wgpu::COPY_BYTES_PER_ROW_ALIGNMENT); - - Self { - bytes_per_row_unpadded, - bytes_per_row_padded, - buffer_size_unpadded: (bytes_per_row_unpadded * height_blocks) as wgpu::BufferAddress, - buffer_size_padded: (bytes_per_row_padded * height_blocks) as wgpu::BufferAddress, - } - } - - #[inline] - pub fn num_rows(&self) -> u32 { - self.buffer_size_padded as u32 / self.bytes_per_row_padded - } - - /// Removes the padding from a buffer containing gpu texture data. - /// - /// The passed in buffer is to be expected to be exactly of size [`Texture2DBufferInfo::buffer_size_padded`]. - /// - /// Note that if you're passing in gpu data, there no alignment guarantees on the returned slice, - /// do NOT convert it using [`bytemuck`]. Use [`Texture2DBufferInfo::remove_padding_and_convert`] instead. - pub fn remove_padding<'a>(&self, buffer: &'a [u8]) -> Cow<'a, [u8]> { - crate::profile_function!(); - - assert_eq!(buffer.len() as wgpu::BufferAddress, self.buffer_size_padded); - - if self.bytes_per_row_padded == self.bytes_per_row_unpadded { - return Cow::Borrowed(buffer); - } - - let mut unpadded_buffer = Vec::with_capacity(self.buffer_size_unpadded as _); - - for row in 0..self.num_rows() { - let offset = (self.bytes_per_row_padded * row) as usize; - unpadded_buffer.extend_from_slice( - &buffer[offset..(offset + self.bytes_per_row_unpadded as usize)], - ); - } - - unpadded_buffer.into() - } - - /// Removes the padding from a buffer containing gpu texture data and remove convert to a given type. - /// - /// The passed in buffer is to be expected to be exactly of size [`Texture2DBufferInfo::buffer_size_padded`]. - /// - /// The unpadded row size is expected to be a multiple of the size of the target type. - /// (Which means that, while uncommon, it technically doesn't need to be as big as a block in the pixel - this can be useful for e.g. packing wide bitfields) - pub fn remove_padding_and_convert(&self, buffer: &[u8]) -> Vec { - crate::profile_function!(); - - assert_eq!(buffer.len() as wgpu::BufferAddress, self.buffer_size_padded); - assert!(self.bytes_per_row_unpadded % std::mem::size_of::() as u32 == 0); - - // Due to https://github.com/gfx-rs/wgpu/issues/3508 the data might be completely unaligned, - // so much, that we can't even interpret it as e.g. a u32 slice. - // Therefore, we have to do a copy of the data regardless of whether it's padded or not. - - let mut unpadded_buffer: Vec = vec![ - T::zeroed(); - (self.num_rows() * self.bytes_per_row_unpadded / std::mem::size_of::() as u32) - as usize - ]; // TODO(andreas): Consider using unsafe set_len() instead of vec![] to avoid zeroing the memory. - - // The copy has to happen on a u8 slice, because any other type would assume some alignment that we can't guarantee because of the above. - let unpadded_buffer_u8_view = bytemuck::cast_slice_mut(&mut unpadded_buffer); - - for row in 0..self.num_rows() { - let offset_padded = (self.bytes_per_row_padded * row) as usize; - let offset_unpadded = (self.bytes_per_row_unpadded * row) as usize; - unpadded_buffer_u8_view - [offset_unpadded..(offset_unpadded + self.bytes_per_row_unpadded as usize)] - .copy_from_slice( - &buffer[offset_padded..(offset_padded + self.bytes_per_row_unpadded as usize)], - ); - } - - unpadded_buffer - } -} diff --git a/crates/re_renderer/src/wgpu_resources/sampler_pool.rs b/crates/re_renderer/src/wgpu_resources/sampler_pool.rs index 53eed49cdf73..2c03824cf1c9 100644 --- a/crates/re_renderer/src/wgpu_resources/sampler_pool.rs +++ b/crates/re_renderer/src/wgpu_resources/sampler_pool.rs @@ -1,4 +1,4 @@ -use std::{hash::Hash, num::NonZeroU8}; +use std::hash::Hash; use super::{resource::PoolError, static_resource_pool::StaticResourcePool}; use crate::debug_label::DebugLabel; @@ -33,9 +33,6 @@ pub struct SamplerDesc { /// Maximum level of detail (i.e. mip level) to use pub lod_max_clamp: ordered_float::NotNan, - - /// Valid values: 1, 2, 4, 8, and 16. - pub anisotropy_clamp: Option, } #[derive(Default)] @@ -56,11 +53,11 @@ impl GpuSamplerPool { mipmap_filter: desc.mipmap_filter, lod_min_clamp: desc.lod_min_clamp.into(), lod_max_clamp: desc.lod_max_clamp.into(), - anisotropy_clamp: desc.anisotropy_clamp, // Unsupported compare: None, border_color: None, + anisotropy_clamp: 1, }) }) } diff --git a/crates/re_renderer/src/wgpu_resources/shader_module_pool.rs b/crates/re_renderer/src/wgpu_resources/shader_module_pool.rs index 1fb291f47f05..9b42f87d1788 100644 --- a/crates/re_renderer/src/wgpu_resources/shader_module_pool.rs +++ b/crates/re_renderer/src/wgpu_resources/shader_module_pool.rs @@ -54,13 +54,18 @@ impl ShaderModuleDesc { &self, device: &wgpu::Device, resolver: &mut FileResolver, + shader_text_workaround_replacements: &[(String, String)], ) -> wgpu::ShaderModule { - let source_interpolated = resolver + let mut source_interpolated = resolver .populate(&self.source) .context("couldn't resolve shader module's contents") .map_err(|err| re_log::error!(err=%re_error::format(err))) .unwrap_or_default(); + for (from, to) in shader_text_workaround_replacements { + source_interpolated.contents = source_interpolated.contents.replace(from, to); + } + // All wgpu errors come asynchronously: this call will succeed whether the given // source is valid or not. // Only when actually submitting passes that make use of this shader will we know if @@ -78,6 +83,11 @@ impl ShaderModuleDesc { #[derive(Default)] pub struct GpuShaderModulePool { pool: StaticResourcePool, + + /// Workarounds via text replacement in shader source code. + /// + /// TODO(andreas): These should be solved with a pre-processor. + pub shader_text_workaround_replacements: Vec<(String, String)>, } impl GpuShaderModulePool { @@ -87,8 +97,9 @@ impl GpuShaderModulePool { resolver: &mut FileResolver, desc: &ShaderModuleDesc, ) -> GpuShaderModuleHandle { - self.pool - .get_or_create(desc, |desc| desc.create_shader_module(device, resolver)) + self.pool.get_or_create(desc, |desc| { + desc.create_shader_module(device, resolver, &self.shader_text_workaround_replacements) + }) } pub fn begin_frame( @@ -115,7 +126,11 @@ impl GpuShaderModulePool { } paths.iter().any(|p| updated_paths.contains(p)).then(|| { - let shader_module = desc.create_shader_module(device, resolver); + let shader_module = desc.create_shader_module( + device, + resolver, + &self.shader_text_workaround_replacements, + ); re_log::debug!(?desc.source, label = desc.label.get(), "recompiled shader module"); shader_module }) diff --git a/crates/re_renderer/src/wgpu_resources/texture_pool.rs b/crates/re_renderer/src/wgpu_resources/texture_pool.rs index c113b4f603d8..d194b387f773 100644 --- a/crates/re_renderer/src/wgpu_resources/texture_pool.rs +++ b/crates/re_renderer/src/wgpu_resources/texture_pool.rs @@ -69,9 +69,20 @@ impl DynamicResourcesDesc for TextureDesc { /// The actual number might be both bigger (padding) and lower (gpu sided compression). fn resource_size_in_bytes(&self) -> u64 { let mut size_in_bytes = 0; - let format_desc = self.format.describe(); - let pixels_per_block = - format_desc.block_dimensions.0 as u64 * format_desc.block_dimensions.1 as u64; + let block_size = self + .format + .block_size(Some(wgpu::TextureAspect::All)) + .unwrap_or_else(|| { + self.format + .block_size(Some(wgpu::TextureAspect::DepthOnly)) + .unwrap_or(0) + + self + .format + .block_size(Some(wgpu::TextureAspect::StencilOnly)) + .unwrap_or(0) + }); + let block_dimension = self.format.block_dimensions(); + let pixels_per_block = block_dimension.0 as u64 * block_dimension.1 as u64; for mip in 0..self.size.max_mips(self.dimension) { let mip_size = self @@ -80,7 +91,7 @@ impl DynamicResourcesDesc for TextureDesc { .physical_size(self.format); let num_pixels = mip_size.width * mip_size.height * mip_size.depth_or_array_layers; let num_blocks = num_pixels as u64 / pixels_per_block; - size_in_bytes += num_blocks * format_desc.block_size as u64; + size_in_bytes += num_blocks * block_size as u64; } size_in_bytes diff --git a/crates/re_ui/Cargo.toml b/crates/re_ui/Cargo.toml index cfa5732751ca..b9a59e8f9c0e 100644 --- a/crates/re_ui/Cargo.toml +++ b/crates/re_ui/Cargo.toml @@ -30,8 +30,8 @@ egui_dock = ["dep:egui_dock"] [dependencies] -egui = { workspace = true, features = ["extra_debug_asserts", "tracing"] } -egui_extras = { workspace = true, features = ["tracing"] } +egui.workspace = true +egui_extras.workspace = true image = { workspace = true, default-features = false, features = ["png"] } parking_lot.workspace = true serde = { version = "1", features = ["derive"] } @@ -39,11 +39,10 @@ serde_json = "1" strum = { version = "0.24", features = ["derive"] } strum_macros = "0.24" sublime_fuzzy = "0.7" - ## Optional dependencies: eframe = { workspace = true, optional = true, default-features = false } egui_dock = { workspace = true, optional = true, features = ["serde"] } +re_log.workspace = true [dev-dependencies] eframe = { workspace = true, default-features = false, features = ["wgpu"] } -re_log.workspace = true diff --git a/crates/re_ui/data/design_tokens.json b/crates/re_ui/data/design_tokens.json index 9c8974d9a4c5..f8da200e1354 100644 --- a/crates/re_ui/data/design_tokens.json +++ b/crates/re_ui/data/design_tokens.json @@ -3,121 +3,126 @@ "Color": { "Surface": { "Default": { - "description": "Background color for most UI surfaces in Rerun", - "value": "{Global.Color.Grey.100}", - "type": "color" + "value": "{Global.Color.White}", + "type": "color", + "description": "Background color for most UI surfaces in Rerun" }, "Floating": { - "description": "Background color for floating elements like menus, dropdown options, notifications etc.", - "value": "{Global.Color.Grey.175}", - "type": "color" + "value": "{Global.Color.Primary.25}", + "type": "color", + "description": "Background color for floating elements like menus, dropdown options, notifications etc." } }, "Action": { "Default": { - "description": "Background color for UI elements like buttons and selects", - "value": "{Global.Color.Grey.200}", - "type": "color" + "value": "{Global.Color.White}", + "type": "color", + "description": "Background color for UI elements like buttons and selects" }, "Hovered": { - "description": "Background color for hovered UI elements", - "value": "{Global.Color.Grey.225}", - "type": "color" + "value": "{Global.Color.Primary.Hover}", + "type": "color", + "description": "Background color for hovered UI elements" }, "Active": { - "description": "Background color for pressed UI elements", - "value": "{Global.Color.Grey.250}", - "type": "color" + "value": "{Global.Color.Primary.Hover}", + "type": "color", + "description": "Background color for pressed UI elements" }, "Pressed": { - "description": "Background color for suppressed UI elements, like a select that is currently showing a menu", - "value": "{Global.Color.Grey.250}", - "type": "color" + "value": "{Global.Color.Primary.Hover}", + "type": "color", + "description": "Background color for suppressed UI elements, like a select that is currently showing a menu" + }, + "Inactive": { + "value": "{Global.Color.White}", + "type": "color", + "description": "Background color for inactive buttons and such" } }, "NotificationBadge": { "Unread": { - "description": "Used for unread notification indicators", "value": "{Global.Color.Blue.500}", - "type": "color" + "type": "color", + "description": "Used for unread notification indicators" }, "Read": { - "description": "Used for read notification indicators", "value": "{Global.Color.Grey.250}", - "type": "color" + "type": "color", + "description": "Used for read notification indicators" } }, "Text": { "Default": { - "description": "Default text color", - "value": "{Global.Color.Grey.775}", - "type": "color" + "value": "{Global.Color.Text.Primary}", + "type": "color", + "description": "Default text color" }, "Subdued": { - "description": "Used for less important text", - "value": "{Global.Color.Grey.550}", - "type": "color" + "value": "{Global.Color.Text.Secondary}", + "type": "color", + "description": "Used for less important text" }, "Strong": { - "description": "Used for highlighted or emphasized items, such as current navigation items", - "value": "{Global.Color.Grey.1000}", - "type": "color" + "value": "{Global.Color.Black}", + "type": "color", + "description": "Used for highlighted or emphasized items, such as current navigation items" } }, "Border": { "Default": { - "value": "{Global.Color.OpaqueGrey.Default}", - "description": "Default color for borders", - "type": "color" + "value": "{Global.Color.Gray.200}", + "type": "color", + "description": "Default color for borders" } }, "Icon": { "Default": { - "description": "Default icon color", "value": "{Global.Color.Grey.775}", - "type": "color" + "type": "color", + "description": "Default icon color" }, "Subdued": { - "description": "Used together with subdued text", "value": "{Global.Color.Grey.550}", - "type": "color" + "type": "color", + "description": "Used together with subdued text" }, "Strong": { - "description": "Used together width strong text", "value": "{Global.Color.Grey.1000}", - "type": "color" + "type": "color", + "description": "Used together width strong text" } }, "Highlight": { "Default": { - "value": "{Global.Color.Blue.350}", - "description": "Default color for highlighted items, like hovered menu items", - "type": "color" + "value": "{Global.Color.Primary.Hover}", + "type": "color", + "description": "Default color for highlighted items, like hovered menu items" } } }, "Typography": { "Default": { "value": "{Global.Typography.200}", - "description": "Default font in Rerun's UI", - "type": "typography" + "type": "typography", + "description": "Default font in Rerun's UI" } }, "Shadow": { "Menu": { - "description": "Used for menus, such as selects", "value": "{Global.Shadow.100}", - "type": "boxShadow" + "type": "boxShadow", + "description": "Used for menus, such as selects" }, "Popover": { - "description": "Used for popovers and other semi-modal elements", "value": "{Global.Shadow.400}", - "type": "boxShadow" + "type": "boxShadow", + "description": "Used for popovers and other semi-modal elements" }, "Modal": { - "description": "Used for modal views", "value": "{Global.Shadow.800}", - "type": "boxShadow" + "type": "boxShadow", + "description": "Used for modal views" } } }, @@ -126,1042 +131,1280 @@ "Grey": { "0": { "value": "#000000", - "description": "0 - 0", - "type": "color" + "type": "color", + "description": "0 - 0" }, "25": { "value": "#020303", - "description": "0.7405999850077014 - 0.025", - "type": "color" + "type": "color", + "description": "0.7405999850077014 - 0.025" }, "50": { "value": "#050607", - "description": "1.7116872276823336 - 0.05", - "type": "color" + "type": "color", + "description": "1.7116872276823336 - 0.05" }, "75": { "value": "#090b0c", - "description": "2.911127985087129 - 0.075", - "type": "color" + "type": "color", + "description": "2.911127985087129 - 0.075" }, "100": { "value": "#0d1011", - "description": "4.335380638931743 - 0.1", - "type": "color" + "type": "color", + "description": "4.335380638931743 - 0.1" }, "125": { "value": "#111415", - "description": "5.979517530826747 - 0.125", - "type": "color" + "type": "color", + "description": "5.979517530826747 - 0.125" }, "150": { "value": "#141819", - "description": "7.837255342936205 - 0.15", - "type": "color" + "type": "color", + "description": "7.837255342936205 - 0.15" }, "175": { "value": "#181c1e", - "description": "9.90099383672154 - 0.175", - "type": "color" + "type": "color", + "description": "9.90099383672154 - 0.175" }, "200": { "value": "#1c2123", - "description": "12.16186271093947 - 0.2", - "type": "color" + "type": "color", + "description": "12.16186271093947 - 0.2" }, "225": { "value": "#212628", - "description": "14.609776289998841 - 0.225", - "type": "color" + "type": "color", + "description": "14.609776289998841 - 0.225" }, "250": { "value": "#262b2e", - "description": "17.233495705504463 - 0.25", - "type": "color" + "type": "color", + "description": "17.233495705504463 - 0.25" }, "275": { "value": "#2b3134", - "description": "20.02069818761812 - 0.275", - "type": "color" + "type": "color", + "description": "20.02069818761812 - 0.275" }, "300": { "value": "#31383b", - "description": "22.958053039032254 - 0.3", - "type": "color" + "type": "color", + "description": "22.958053039032254 - 0.3" }, "325": { "value": "#373f42", - "description": "26.03130382315192 - 0.325", - "type": "color" + "type": "color", + "description": "26.03130382315192 - 0.325" }, "350": { "value": "#3e464a", - "description": "29.22535625976699 - 0.35", - "type": "color" + "type": "color", + "description": "29.22535625976699 - 0.35" }, "375": { "value": "#454e52", - "description": "32.524371286309126 - 0.375", - "type": "color" + "type": "color", + "description": "32.524371286309126 - 0.375" }, "400": { "value": "#4c565a", - "description": "35.91186271093947 - 0.4", - "type": "color" + "type": "color", + "description": "35.91186271093947 - 0.4" }, "425": { "value": "#545e63", - "description": "39.37079885540354 - 0.425", - "type": "color" + "type": "color", + "description": "39.37079885540354 - 0.425" }, "450": { "value": "#5c676c", - "description": "42.88370756099135 - 0.45", - "type": "color" + "type": "color", + "description": "42.88370756099135 - 0.45" }, "475": { "value": "#647075", - "description": "46.43278391020581 - 0.475", - "type": "color" + "type": "color", + "description": "46.43278391020581 - 0.475" }, "500": { "value": "#6c797f", - "description": "49.99999999999999 - 0.5", - "type": "color" + "type": "color", + "description": "49.99999999999999 - 0.5" }, "525": { "value": "#748288", - "description": "53.56721608979418 - 0.525", - "type": "color" + "type": "color", + "description": "53.56721608979418 - 0.525" }, "550": { "value": "#7d8c92", - "description": "57.11629243900867 - 0.55", - "type": "color" + "type": "color", + "description": "57.11629243900867 - 0.55" }, "575": { "value": "#85959c", - "description": "60.62920114459644 - 0.575", - "type": "color" + "type": "color", + "description": "60.62920114459644 - 0.575" }, "600": { "value": "#8e9ea5", - "description": "64.08813728906053 - 0.6", - "type": "color" + "type": "color", + "description": "64.08813728906053 - 0.6" }, "625": { "value": "#96a7af", - "description": "67.47562871369087 - 0.625", - "type": "color" + "type": "color", + "description": "67.47562871369087 - 0.625" }, "650": { "value": "#9eb0b8", - "description": "70.774643740233 - 0.65", - "type": "color" + "type": "color", + "description": "70.774643740233 - 0.65" }, "675": { "value": "#a6b9c1", - "description": "73.96869617684807 - 0.675", - "type": "color" + "type": "color", + "description": "73.96869617684807 - 0.675" }, "700": { "value": "#aec2ca", - "description": "77.04194696096773 - 0.7", - "type": "color" + "type": "color", + "description": "77.04194696096773 - 0.7" }, "725": { "value": "#b6cad2", - "description": "79.97930181238189 - 0.725", - "type": "color" + "type": "color", + "description": "79.97930181238189 - 0.725" }, "750": { "value": "#c0d1d8", - "description": "82.76650429449552 - 0.75", - "type": "color" + "type": "color", + "description": "82.76650429449552 - 0.75" }, "775": { "value": "#cad8de", - "description": "85.39022371000115 - 0.775", - "type": "color" + "type": "color", + "description": "85.39022371000115 - 0.775" }, "800": { "value": "#d3dee3", - "description": "87.83813728906054 - 0.8", - "type": "color" + "type": "color", + "description": "87.83813728906054 - 0.8" }, "825": { "value": "#dbe4e8", - "description": "90.09900616327847 - 0.825", - "type": "color" + "type": "color", + "description": "90.09900616327847 - 0.825" }, "850": { "value": "#e3eaed", - "description": "92.16274465706378 - 0.85", - "type": "color" + "type": "color", + "description": "92.16274465706378 - 0.85" }, "875": { "value": "#e9eff1", - "description": "94.02048246917325 - 0.875", - "type": "color" + "type": "color", + "description": "94.02048246917325 - 0.875" }, "900": { "value": "#eff3f5", - "description": "95.66461936106825 - 0.9", - "type": "color" + "type": "color", + "description": "95.66461936106825 - 0.9" }, "925": { "value": "#f4f7f8", - "description": "97.08887201491288 - 0.925", - "type": "color" + "type": "color", + "description": "97.08887201491288 - 0.925" }, "950": { "value": "#f9fafb", - "description": "98.28831277231767 - 0.95", - "type": "color" + "type": "color", + "description": "98.28831277231767 - 0.95" }, "975": { "value": "#fcfdfd", - "description": "99.25940001499231 - 0.975", - "type": "color" + "type": "color", + "description": "99.25940001499231 - 0.975" }, "1000": { "value": "#ffffff", - "description": "100 - 1", - "type": "color" + "type": "color", + "description": "100 - 1" } }, "Green": { "0": { "value": "#000000", - "description": "0 - 0", - "type": "color" + "type": "color", + "description": "0 - 0" }, "25": { "value": "#000401", - "description": "0.7405999850077014 - 0.025", - "type": "color" + "type": "color", + "description": "0.7405999850077014 - 0.025" }, "50": { "value": "#000803", - "description": "1.7116872276823336 - 0.05", - "type": "color" + "type": "color", + "description": "1.7116872276823336 - 0.05" }, "75": { "value": "#000e04", - "description": "2.911127985087129 - 0.075", - "type": "color" + "type": "color", + "description": "2.911127985087129 - 0.075" }, "100": { "value": "#001306", - "description": "4.335380638931743 - 0.1", - "type": "color" + "type": "color", + "description": "4.335380638931743 - 0.1" }, "125": { "value": "#001809", - "description": "5.979517530826747 - 0.125", - "type": "color" + "type": "color", + "description": "5.979517530826747 - 0.125" }, "150": { "value": "#001c0b", - "description": "7.837255342936205 - 0.15", - "type": "color" + "type": "color", + "description": "7.837255342936205 - 0.15" }, "175": { "value": "#00210e", - "description": "9.90099383672154 - 0.175", - "type": "color" + "type": "color", + "description": "9.90099383672154 - 0.175" }, "200": { "value": "#002611", - "description": "12.16186271093947 - 0.2", - "type": "color" + "type": "color", + "description": "12.16186271093947 - 0.2" }, "225": { "value": "#002c15", - "description": "14.609776289998841 - 0.225", - "type": "color" + "type": "color", + "description": "14.609776289998841 - 0.225" }, "250": { "value": "#003219", - "description": "17.233495705504463 - 0.25", - "type": "color" + "type": "color", + "description": "17.233495705504463 - 0.25" }, "275": { "value": "#00391d", - "description": "20.02069818761812 - 0.275", - "type": "color" + "type": "color", + "description": "20.02069818761812 - 0.275" }, "300": { "value": "#004021", - "description": "22.958053039032254 - 0.3", - "type": "color" + "type": "color", + "description": "22.958053039032254 - 0.3" }, "325": { "value": "#004826", - "description": "26.03130382315192 - 0.325", - "type": "color" + "type": "color", + "description": "26.03130382315192 - 0.325" }, "350": { "value": "#00502b", - "description": "29.22535625976699 - 0.35", - "type": "color" + "type": "color", + "description": "29.22535625976699 - 0.35" }, "375": { "value": "#005930", - "description": "32.524371286309126 - 0.375", - "type": "color" + "type": "color", + "description": "32.524371286309126 - 0.375" }, "400": { "value": "#006236", - "description": "35.91186271093947 - 0.4", - "type": "color" + "type": "color", + "description": "35.91186271093947 - 0.4" }, "425": { "value": "#006b3b", - "description": "39.37079885540354 - 0.425", - "type": "color" + "type": "color", + "description": "39.37079885540354 - 0.425" }, "450": { "value": "#007541", - "description": "42.88370756099135 - 0.45", - "type": "color" + "type": "color", + "description": "42.88370756099135 - 0.45" }, "475": { "value": "#007f47", - "description": "46.43278391020581 - 0.475", - "type": "color" + "type": "color", + "description": "46.43278391020581 - 0.475" }, "500": { "value": "#00894d", - "description": "49.99999999999999 - 0.5", - "type": "color" + "type": "color", + "description": "49.99999999999999 - 0.5" }, "525": { "value": "#009353", - "description": "53.56721608979418 - 0.525", - "type": "color" + "type": "color", + "description": "53.56721608979418 - 0.525" }, "550": { "value": "#009e5a", - "description": "57.11629243900867 - 0.55", - "type": "color" + "type": "color", + "description": "57.11629243900867 - 0.55" }, "575": { "value": "#00a860", - "description": "60.62920114459644 - 0.575", - "type": "color" + "type": "color", + "description": "60.62920114459644 - 0.575" }, "600": { "value": "#00b266", - "description": "64.08813728906053 - 0.6", - "type": "color" + "type": "color", + "description": "64.08813728906053 - 0.6" }, "625": { "value": "#00bc6c", - "description": "67.47562871369087 - 0.625", - "type": "color" + "type": "color", + "description": "67.47562871369087 - 0.625" }, "650": { "value": "#00c772", - "description": "70.774643740233 - 0.65", - "type": "color" + "type": "color", + "description": "70.774643740233 - 0.65" }, "675": { "value": "#00d078", - "description": "73.96869617684807 - 0.675", - "type": "color" + "type": "color", + "description": "73.96869617684807 - 0.675" }, "700": { "value": "#00da7e", - "description": "77.04194696096773 - 0.7", - "type": "color" + "type": "color", + "description": "77.04194696096773 - 0.7" }, "725": { "value": "#00e384", - "description": "79.97930181238189 - 0.725", - "type": "color" + "type": "color", + "description": "79.97930181238189 - 0.725" }, "750": { "value": "#00ec89", - "description": "82.76650429449552 - 0.75", - "type": "color" + "type": "color", + "description": "82.76650429449552 - 0.75" }, "775": { "value": "#00f48e", - "description": "85.39022371000115 - 0.775", - "type": "color" + "type": "color", + "description": "85.39022371000115 - 0.775" }, "800": { "value": "#00fc93", - "description": "87.83813728906054 - 0.8", - "type": "color" + "type": "color", + "description": "87.83813728906054 - 0.8" }, "825": { "value": "#5cffa5", - "description": "90.09900616327847 - 0.825", - "type": "color" + "type": "color", + "description": "90.09900616327847 - 0.825" }, "850": { "value": "#91ffbb", - "description": "92.16274465706378 - 0.85", - "type": "color" + "type": "color", + "description": "92.16274465706378 - 0.85" }, "875": { "value": "#b2ffcd", - "description": "94.02048246917325 - 0.875", - "type": "color" + "type": "color", + "description": "94.02048246917325 - 0.875" }, "900": { "value": "#caffdc", - "description": "95.66461936106825 - 0.9", - "type": "color" + "type": "color", + "description": "95.66461936106825 - 0.9" }, "925": { "value": "#ddffe8", - "description": "97.08887201491288 - 0.925", - "type": "color" + "type": "color", + "description": "97.08887201491288 - 0.925" }, "950": { "value": "#ebfff1", - "description": "98.28831277231767 - 0.95", - "type": "color" + "type": "color", + "description": "98.28831277231767 - 0.95" }, "975": { "value": "#f7fff9", - "description": "99.25940001499231 - 0.975", - "type": "color" + "type": "color", + "description": "99.25940001499231 - 0.975" }, "1000": { "value": "#ffffff", - "description": "100 - 1", - "type": "color" + "type": "color", + "description": "100 - 1" } }, "Red": { "0": { "value": "#000000", - "description": "0 - 0", - "type": "color" + "type": "color", + "description": "0 - 0" }, "25": { "value": "#0c0001", - "description": "0.7405999850077014 - 0.025", - "type": "color" + "type": "color", + "description": "0.7405999850077014 - 0.025" }, "50": { "value": "#170003", - "description": "1.7116872276823336 - 0.05", - "type": "color" + "type": "color", + "description": "1.7116872276823336 - 0.05" }, "75": { "value": "#200005", - "description": "2.911127985087129 - 0.075", - "type": "color" + "type": "color", + "description": "2.911127985087129 - 0.075" }, "100": { "value": "#290007", - "description": "4.335380638931743 - 0.1", - "type": "color" + "type": "color", + "description": "4.335380638931743 - 0.1" }, "125": { "value": "#310009", - "description": "5.979517530826747 - 0.125", - "type": "color" + "type": "color", + "description": "5.979517530826747 - 0.125" }, "150": { "value": "#38000c", - "description": "7.837255342936205 - 0.15", - "type": "color" + "type": "color", + "description": "7.837255342936205 - 0.15" }, "175": { "value": "#40000f", - "description": "9.90099383672154 - 0.175", - "type": "color" + "type": "color", + "description": "9.90099383672154 - 0.175" }, "200": { "value": "#480012", - "description": "12.16186271093947 - 0.2", - "type": "color" + "type": "color", + "description": "12.16186271093947 - 0.2" }, "225": { "value": "#520016", - "description": "14.609776289998841 - 0.225", - "type": "color" + "type": "color", + "description": "14.609776289998841 - 0.225" }, "250": { "value": "#5c001a", - "description": "17.233495705504463 - 0.25", - "type": "color" + "type": "color", + "description": "17.233495705504463 - 0.25" }, "275": { "value": "#67001e", - "description": "20.02069818761812 - 0.275", - "type": "color" + "type": "color", + "description": "20.02069818761812 - 0.275" }, "300": { "value": "#730022", - "description": "22.958053039032254 - 0.3", - "type": "color" + "type": "color", + "description": "22.958053039032254 - 0.3" }, "325": { "value": "#800027", - "description": "26.03130382315192 - 0.325", - "type": "color" + "type": "color", + "description": "26.03130382315192 - 0.325" }, "350": { "value": "#8e002c", - "description": "29.22535625976699 - 0.35", - "type": "color" + "type": "color", + "description": "29.22535625976699 - 0.35" }, "375": { "value": "#9c0031", - "description": "32.524371286309126 - 0.375", - "type": "color" + "type": "color", + "description": "32.524371286309126 - 0.375" }, "400": { "value": "#ab0037", - "description": "35.91186271093947 - 0.4", - "type": "color" + "type": "color", + "description": "35.91186271093947 - 0.4" }, "425": { "value": "#bb003d", - "description": "39.37079885540354 - 0.425", - "type": "color" + "type": "color", + "description": "39.37079885540354 - 0.425" }, "450": { "value": "#cb0043", - "description": "42.88370756099135 - 0.45", - "type": "color" + "type": "color", + "description": "42.88370756099135 - 0.45" }, "475": { "value": "#db0049", - "description": "46.43278391020581 - 0.475", - "type": "color" + "type": "color", + "description": "46.43278391020581 - 0.475" }, "500": { "value": "#ec004f", - "description": "49.99999999999999 - 0.5", - "type": "color" + "type": "color", + "description": "49.99999999999999 - 0.5" }, "525": { "value": "#fd0056", - "description": "53.56721608979418 - 0.525", - "type": "color" + "type": "color", + "description": "53.56721608979418 - 0.525" }, "550": { "value": "#ff3865", - "description": "57.11629243900867 - 0.55", - "type": "color" + "type": "color", + "description": "57.11629243900867 - 0.55" }, "575": { "value": "#ff5474", - "description": "60.62920114459644 - 0.575", - "type": "color" + "type": "color", + "description": "60.62920114459644 - 0.575" }, "600": { "value": "#ff6981", - "description": "64.08813728906053 - 0.6", - "type": "color" + "type": "color", + "description": "64.08813728906053 - 0.6" }, "625": { "value": "#ff7a8e", - "description": "67.47562871369087 - 0.625", - "type": "color" + "type": "color", + "description": "67.47562871369087 - 0.625" }, "650": { "value": "#ff8a9a", - "description": "70.774643740233 - 0.65", - "type": "color" + "type": "color", + "description": "70.774643740233 - 0.65" }, "675": { "value": "#ff99a6", - "description": "73.96869617684807 - 0.675", - "type": "color" + "type": "color", + "description": "73.96869617684807 - 0.675" }, "700": { "value": "#ffa6b1", - "description": "77.04194696096773 - 0.7", - "type": "color" + "type": "color", + "description": "77.04194696096773 - 0.7" }, "725": { "value": "#ffb2bb", - "description": "79.97930181238189 - 0.725", - "type": "color" + "type": "color", + "description": "79.97930181238189 - 0.725" }, "750": { "value": "#ffbdc5", - "description": "82.76650429449552 - 0.75", - "type": "color" + "type": "color", + "description": "82.76650429449552 - 0.75" }, "775": { "value": "#ffc8ce", - "description": "85.39022371000115 - 0.775", - "type": "color" + "type": "color", + "description": "85.39022371000115 - 0.775" }, "800": { "value": "#ffd1d6", - "description": "87.83813728906054 - 0.8", - "type": "color" + "type": "color", + "description": "87.83813728906054 - 0.8" }, "825": { "value": "#ffdade", - "description": "90.09900616327847 - 0.825", - "type": "color" + "type": "color", + "description": "90.09900616327847 - 0.825" }, "850": { "value": "#ffe2e5", - "description": "92.16274465706378 - 0.85", - "type": "color" + "type": "color", + "description": "92.16274465706378 - 0.85" }, "875": { "value": "#ffe9eb", - "description": "94.02048246917325 - 0.875", - "type": "color" + "type": "color", + "description": "94.02048246917325 - 0.875" }, "900": { "value": "#ffeff0", - "description": "95.66461936106825 - 0.9", - "type": "color" + "type": "color", + "description": "95.66461936106825 - 0.9" }, "925": { "value": "#fff4f5", - "description": "97.08887201491288 - 0.925", - "type": "color" + "type": "color", + "description": "97.08887201491288 - 0.925" }, "950": { "value": "#fff9f9", - "description": "98.28831277231767 - 0.95", - "type": "color" + "type": "color", + "description": "98.28831277231767 - 0.95" }, "975": { "value": "#fffcfd", - "description": "99.25940001499231 - 0.975", - "type": "color" + "type": "color", + "description": "99.25940001499231 - 0.975" }, "1000": { "value": "#ffffff", - "description": "100 - 1", - "type": "color" + "type": "color", + "description": "100 - 1" } }, "Blue": { "0": { "value": "#000000", - "description": "0 - 0", - "type": "color" + "type": "color", + "description": "0 - 0" }, "25": { "value": "#00020f", - "description": "0.7405999850077014 - 0.025", - "type": "color" + "type": "color", + "description": "0.7405999850077014 - 0.025" }, "50": { "value": "#00051c", - "description": "1.7116872276823336 - 0.05", - "type": "color" + "type": "color", + "description": "1.7116872276823336 - 0.05" }, "75": { "value": "#000826", - "description": "2.911127985087129 - 0.075", - "type": "color" + "type": "color", + "description": "2.911127985087129 - 0.075" }, "100": { "value": "#000c30", - "description": "4.335380638931743 - 0.1", - "type": "color" + "type": "color", + "description": "4.335380638931743 - 0.1" }, "125": { "value": "#001038", - "description": "5.979517530826747 - 0.125", - "type": "color" + "type": "color", + "description": "5.979517530826747 - 0.125" }, "150": { "value": "#001441", - "description": "7.837255342936205 - 0.15", - "type": "color" + "type": "color", + "description": "7.837255342936205 - 0.15" }, "175": { "value": "#001749", - "description": "9.90099383672154 - 0.175", - "type": "color" + "type": "color", + "description": "9.90099383672154 - 0.175" }, "200": { "value": "#001b53", - "description": "12.16186271093947 - 0.2", - "type": "color" + "type": "color", + "description": "12.16186271093947 - 0.2" }, "225": { "value": "#00205e", - "description": "14.609776289998841 - 0.225", - "type": "color" + "type": "color", + "description": "14.609776289998841 - 0.225" }, "250": { "value": "#002569", - "description": "17.233495705504463 - 0.25", - "type": "color" + "type": "color", + "description": "17.233495705504463 - 0.25" }, "275": { "value": "#002a76", - "description": "20.02069818761812 - 0.275", - "type": "color" + "type": "color", + "description": "20.02069818761812 - 0.275" }, "300": { "value": "#003084", - "description": "22.958053039032254 - 0.3", - "type": "color" + "type": "color", + "description": "22.958053039032254 - 0.3" }, "325": { "value": "#003692", - "description": "26.03130382315192 - 0.325", - "type": "color" + "type": "color", + "description": "26.03130382315192 - 0.325" }, "350": { "value": "#003da1", - "description": "29.22535625976699 - 0.35", - "type": "color" + "type": "color", + "description": "29.22535625976699 - 0.35" }, "375": { "value": "#0044b2", - "description": "32.524371286309126 - 0.375", - "type": "color" + "type": "color", + "description": "32.524371286309126 - 0.375" }, "400": { "value": "#004bc2", - "description": "35.91186271093947 - 0.4", - "type": "color" + "type": "color", + "description": "35.91186271093947 - 0.4" }, "425": { "value": "#0053d4", - "description": "39.37079885540354 - 0.425", - "type": "color" + "type": "color", + "description": "39.37079885540354 - 0.425" }, "450": { "value": "#005ae6", - "description": "42.88370756099135 - 0.45", - "type": "color" + "type": "color", + "description": "42.88370756099135 - 0.45" }, "475": { "value": "#0062f9", - "description": "46.43278391020581 - 0.475", - "type": "color" + "type": "color", + "description": "46.43278391020581 - 0.475" }, "500": { "value": "#2a6cff", - "description": "49.99999999999999 - 0.5", - "type": "color" + "type": "color", + "description": "49.99999999999999 - 0.5" }, "525": { "value": "#4676ff", - "description": "53.56721608979418 - 0.525", - "type": "color" + "type": "color", + "description": "53.56721608979418 - 0.525" }, "550": { "value": "#5a81ff", - "description": "57.11629243900867 - 0.55", - "type": "color" + "type": "color", + "description": "57.11629243900867 - 0.55" }, "575": { "value": "#6b8bff", - "description": "60.62920114459644 - 0.575", - "type": "color" + "type": "color", + "description": "60.62920114459644 - 0.575" }, "600": { "value": "#7a95ff", - "description": "64.08813728906053 - 0.6", - "type": "color" + "type": "color", + "description": "64.08813728906053 - 0.6" }, "625": { "value": "#899fff", - "description": "67.47562871369087 - 0.625", - "type": "color" + "type": "color", + "description": "67.47562871369087 - 0.625" }, "650": { "value": "#96a8ff", - "description": "70.774643740233 - 0.65", - "type": "color" + "type": "color", + "description": "70.774643740233 - 0.65" }, "675": { "value": "#a2b2ff", - "description": "73.96869617684807 - 0.675", - "type": "color" + "type": "color", + "description": "73.96869617684807 - 0.675" }, "700": { "value": "#adbbff", - "description": "77.04194696096773 - 0.7", - "type": "color" + "type": "color", + "description": "77.04194696096773 - 0.7" }, "725": { "value": "#b8c3ff", - "description": "79.97930181238189 - 0.725", - "type": "color" + "type": "color", + "description": "79.97930181238189 - 0.725" }, "750": { "value": "#c2ccff", - "description": "82.76650429449552 - 0.75", - "type": "color" + "type": "color", + "description": "82.76650429449552 - 0.75" }, "775": { "value": "#ccd3ff", - "description": "85.39022371000115 - 0.775", - "type": "color" + "type": "color", + "description": "85.39022371000115 - 0.775" }, "800": { "value": "#d4dbff", - "description": "87.83813728906054 - 0.8", - "type": "color" + "type": "color", + "description": "87.83813728906054 - 0.8" }, "825": { "value": "#dce1ff", - "description": "90.09900616327847 - 0.825", - "type": "color" + "type": "color", + "description": "90.09900616327847 - 0.825" }, "850": { "value": "#e4e7ff", - "description": "92.16274465706378 - 0.85", - "type": "color" + "type": "color", + "description": "92.16274465706378 - 0.85" }, "875": { "value": "#eaedff", - "description": "94.02048246917325 - 0.875", - "type": "color" + "type": "color", + "description": "94.02048246917325 - 0.875" }, "900": { "value": "#f0f2ff", - "description": "95.66461936106825 - 0.9", - "type": "color" + "type": "color", + "description": "95.66461936106825 - 0.9" }, "925": { "value": "#f5f6ff", - "description": "97.08887201491288 - 0.925", - "type": "color" + "type": "color", + "description": "97.08887201491288 - 0.925" }, "950": { "value": "#f9faff", - "description": "98.28831277231767 - 0.95", - "type": "color" + "type": "color", + "description": "98.28831277231767 - 0.95" }, "975": { "value": "#fcfdff", - "description": "99.25940001499231 - 0.975", - "type": "color" + "type": "color", + "description": "99.25940001499231 - 0.975" }, "1000": { "value": "#ffffff", - "description": "100 - 1", - "type": "color" + "type": "color", + "description": "100 - 1" } }, "Purple": { "0": { "value": "#000000", - "description": "0 - 0", - "type": "color" + "type": "color", + "description": "0 - 0" }, "25": { "value": "#060011", - "description": "0.7405999850077014 - 0.025", - "type": "color" + "type": "color", + "description": "0.7405999850077014 - 0.025" }, "50": { "value": "#0e001e", - "description": "1.7116872276823336 - 0.05", - "type": "color" + "type": "color", + "description": "1.7116872276823336 - 0.05" }, "75": { "value": "#150029", - "description": "2.911127985087129 - 0.075", - "type": "color" + "type": "color", + "description": "2.911127985087129 - 0.075" }, "100": { "value": "#1b0033", - "description": "4.335380638931743 - 0.1", - "type": "color" + "type": "color", + "description": "4.335380638931743 - 0.1" }, "125": { "value": "#21003d", - "description": "5.979517530826747 - 0.125", - "type": "color" + "type": "color", + "description": "5.979517530826747 - 0.125" }, "150": { "value": "#270046", - "description": "7.837255342936205 - 0.15", - "type": "color" + "type": "color", + "description": "7.837255342936205 - 0.15" }, "175": { "value": "#2d004f", - "description": "9.90099383672154 - 0.175", - "type": "color" + "type": "color", + "description": "9.90099383672154 - 0.175" }, "200": { "value": "#330059", - "description": "12.16186271093947 - 0.2", - "type": "color" + "type": "color", + "description": "12.16186271093947 - 0.2" }, "225": { "value": "#3a0065", - "description": "14.609776289998841 - 0.225", - "type": "color" + "type": "color", + "description": "14.609776289998841 - 0.225" }, "250": { "value": "#420071", - "description": "17.233495705504463 - 0.25", - "type": "color" + "type": "color", + "description": "17.233495705504463 - 0.25" }, "275": { "value": "#4b007e", - "description": "20.02069818761812 - 0.275", - "type": "color" + "type": "color", + "description": "20.02069818761812 - 0.275" }, "300": { "value": "#54008d", - "description": "22.958053039032254 - 0.3", - "type": "color" + "type": "color", + "description": "22.958053039032254 - 0.3" }, "325": { "value": "#5d009c", - "description": "26.03130382315192 - 0.325", - "type": "color" + "type": "color", + "description": "26.03130382315192 - 0.325" }, "350": { "value": "#6800ad", - "description": "29.22535625976699 - 0.35", - "type": "color" + "type": "color", + "description": "29.22535625976699 - 0.35" }, "375": { "value": "#7200be", - "description": "32.524371286309126 - 0.375", - "type": "color" + "type": "color", + "description": "32.524371286309126 - 0.375" }, "400": { "value": "#7e00d0", - "description": "35.91186271093947 - 0.4", - "type": "color" + "type": "color", + "description": "35.91186271093947 - 0.4" }, "425": { "value": "#8a00e2", - "description": "39.37079885540354 - 0.425", - "type": "color" + "type": "color", + "description": "39.37079885540354 - 0.425" }, "450": { "value": "#9600f6", - "description": "42.88370756099135 - 0.45", - "type": "color" + "type": "color", + "description": "42.88370756099135 - 0.45" }, "475": { "value": "#9e22ff", - "description": "46.43278391020581 - 0.475", - "type": "color" + "type": "color", + "description": "46.43278391020581 - 0.475" }, "500": { "value": "#a23eff", - "description": "49.99999999999999 - 0.5", - "type": "color" + "type": "color", + "description": "49.99999999999999 - 0.5" }, "525": { "value": "#a752ff", - "description": "53.56721608979418 - 0.525", - "type": "color" + "type": "color", + "description": "53.56721608979418 - 0.525" }, "550": { "value": "#ac63ff", - "description": "57.11629243900867 - 0.55", - "type": "color" + "type": "color", + "description": "57.11629243900867 - 0.55" }, "575": { "value": "#b273ff", - "description": "60.62920114459644 - 0.575", - "type": "color" + "type": "color", + "description": "60.62920114459644 - 0.575" }, "600": { "value": "#b780ff", - "description": "64.08813728906053 - 0.6", - "type": "color" + "type": "color", + "description": "64.08813728906053 - 0.6" }, "625": { "value": "#bd8eff", - "description": "67.47562871369087 - 0.625", - "type": "color" + "type": "color", + "description": "67.47562871369087 - 0.625" }, "650": { "value": "#c39aff", - "description": "70.774643740233 - 0.65", - "type": "color" + "type": "color", + "description": "70.774643740233 - 0.65" }, "675": { "value": "#c9a5ff", - "description": "73.96869617684807 - 0.675", - "type": "color" + "type": "color", + "description": "73.96869617684807 - 0.675" }, "700": { "value": "#cfb0ff", - "description": "77.04194696096773 - 0.7", - "type": "color" + "type": "color", + "description": "77.04194696096773 - 0.7" }, "725": { "value": "#d4bbff", - "description": "79.97930181238189 - 0.725", - "type": "color" + "type": "color", + "description": "79.97930181238189 - 0.725" }, "750": { "value": "#dac4ff", - "description": "82.76650429449552 - 0.75", - "type": "color" + "type": "color", + "description": "82.76650429449552 - 0.75" }, "775": { "value": "#dfcdff", - "description": "85.39022371000115 - 0.775", - "type": "color" + "type": "color", + "description": "85.39022371000115 - 0.775" }, "800": { "value": "#e4d6ff", - "description": "87.83813728906054 - 0.8", - "type": "color" + "type": "color", + "description": "87.83813728906054 - 0.8" }, "825": { "value": "#e9ddff", - "description": "90.09900616327847 - 0.825", - "type": "color" + "type": "color", + "description": "90.09900616327847 - 0.825" }, "850": { "value": "#eee4ff", - "description": "92.16274465706378 - 0.85", - "type": "color" + "type": "color", + "description": "92.16274465706378 - 0.85" }, "875": { "value": "#f2ebff", - "description": "94.02048246917325 - 0.875", - "type": "color" + "type": "color", + "description": "94.02048246917325 - 0.875" }, "900": { "value": "#f5f0ff", - "description": "95.66461936106825 - 0.9", - "type": "color" + "type": "color", + "description": "95.66461936106825 - 0.9" }, "925": { "value": "#f8f5ff", - "description": "97.08887201491288 - 0.925", - "type": "color" + "type": "color", + "description": "97.08887201491288 - 0.925" }, "950": { "value": "#fbf9ff", - "description": "98.28831277231767 - 0.95", - "type": "color" + "type": "color", + "description": "98.28831277231767 - 0.95" }, "975": { "value": "#fdfcff", - "description": "99.25940001499231 - 0.975", - "type": "color" + "type": "color", + "description": "99.25940001499231 - 0.975" }, "1000": { "value": "#ffffff", - "description": "100 - 1", - "type": "color" + "type": "color", + "description": "100 - 1" } }, "OpaqueGrey": { "Default": { "value": "#7c7c7c20", - "description": "An opaque grey that picks up some, but not all, of the colors behind it", + "type": "color", + "description": "An opaque grey that picks up some, but not all, of the colors behind it" + } + }, + "Gray": { + "25": { + "value": "#fcfcfd", + "type": "color" + }, + "50": { + "value": "#f9fafb", + "type": "color" + }, + "100": { + "value": "#f2f4f7", + "type": "color" + }, + "200": { + "value": "#eaecf0", + "type": "color" + }, + "300": { + "value": "#d0d5dd", + "type": "color" + }, + "400": { + "value": "#98a2b3", + "type": "color" + }, + "500": { + "value": "#667085", + "type": "color" + }, + "600": { + "value": "#475467", + "type": "color" + }, + "700": { + "value": "#344054", + "type": "color" + }, + "800": { + "value": "#1d2939", + "type": "color" + }, + "900": { + "value": "#101828", + "type": "color" + } + }, + "White": { + "value": "#ffffff", + "type": "color" + }, + "Black": { + "value": "#000000", + "type": "color" + }, + "Primary": { + "25": { + "value": "#f6fcfe", + "type": "color" + }, + "50": { + "value": "#dceffc", + "type": "color" + }, + "100": { + "value": "#c1dcf9", + "type": "color" + }, + "200": { + "value": "#a7c3f6", + "type": "color" + }, + "300": { + "value": "#8da4f4", + "type": "color" + }, + "400": { + "value": "#6178f4", + "type": "color" + }, + "500": { + "value": "#4c4ff1", + "type": "color" + }, + "600": { + "value": "#4e38ed", + "type": "color" + }, + "700": { + "value": "#5724e8", + "type": "color" + }, + "800": { + "value": "#441bb6", + "type": "color" + }, + "900": { + "value": "#301383", + "type": "color" + }, + "Default": { + "value": "#4e38ed", + "type": "color" + }, + "Hover": { + "value": "#eaecf0", + "type": "color" + } + }, + "Warning": { + "25": { + "value": "#fffcf5", + "type": "color" + }, + "50": { + "value": "#fffaeb", + "type": "color" + }, + "100": { + "value": "#fef0c7", + "type": "color" + }, + "200": { + "value": "#fedf89", + "type": "color" + }, + "300": { + "value": "#fec84b", + "type": "color" + }, + "400": { + "value": "#fdb022", + "type": "color" + }, + "500": { + "value": "#f79009", + "type": "color" + }, + "600": { + "value": "#dc6803", + "type": "color" + }, + "700": { + "value": "#b54708", + "type": "color" + } + }, + "Error": { + "25": { + "value": "#fffbfa", + "type": "color" + }, + "50": { + "value": "#fef3f2", + "type": "color" + }, + "100": { + "value": "#fee4ef", + "type": "color" + }, + "200": { + "value": "#fecdca", + "type": "color" + }, + "300": { + "value": "#fda29b", + "type": "color" + }, + "400": { + "value": "#f97066", + "type": "color" + }, + "500": { + "value": "#f04438", + "type": "color" + }, + "600": { + "value": "#d92d20", + "type": "color" + }, + "700": { + "value": "#b42318", + "type": "color" + } + }, + "Success": { + "25": { + "value": "#f6fef9", + "type": "color" + }, + "50": { + "value": "#ecfdf3", + "type": "color" + }, + "100": { + "value": "#d1fadf", + "type": "color" + }, + "200": { + "value": "#a6f4c5", + "type": "color" + }, + "300": { + "value": "#6ce9a6", + "type": "color" + }, + "400": { + "value": "#32d583", + "type": "color" + }, + "500": { + "value": "#12b76a", + "type": "color" + }, + "600": { + "value": "#039855", + "type": "color" + }, + "700": { + "value": "#027a48", + "type": "color" + } + }, + "Pink": { + "700": { + "value": "#c11574", + "type": "color" + } + }, + "Text": { + "Primary": { + "value": "#101828", + "type": "color" + }, + "Secondary": { + "value": "#667085", "type": "color" } } @@ -1175,8 +1418,8 @@ "lineHeight": "12px", "letterSpacing": "-0.12px" }, - "description": "", - "type": "typography" + "type": "typography", + "description": "" }, "200": { "value": { @@ -1186,8 +1429,8 @@ "lineHeight": "16px", "letterSpacing": "-0.15px" }, - "description": "", - "type": "typography" + "type": "typography", + "description": "" } }, "Shadow": { @@ -1199,8 +1442,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "200": { "value": { @@ -1210,8 +1453,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "300": { "value": { @@ -1221,8 +1464,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "400": { "value": { @@ -1232,8 +1475,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "500": { "value": { @@ -1243,8 +1486,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "600": { "value": { @@ -1254,8 +1497,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "700": { "value": { @@ -1265,8 +1508,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "800": { "value": { @@ -1276,8 +1519,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "900": { "value": { @@ -1287,8 +1530,8 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" }, "1000": { "value": { @@ -1298,98 +1541,102 @@ "spread": "0px", "color": "rgba(0, 0, 0, .2)" }, - "description": "", - "type": "boxShadow" + "type": "boxShadow", + "description": "" } }, "Radius": { "0": { "value": "0", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "100": { "value": "2", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "200": { "value": "6", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "300": { "value": "14", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "400": { "value": "30", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "500": { "value": "62", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" } }, "Spacing": { "0": { "value": "0", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "100": { "value": "2", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "200": { "value": "4", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "300": { "value": "8", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "400": { "value": "12", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "500": { "value": "16", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "600": { "value": "32", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "700": { "value": "48", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "800": { "value": "64", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "900": { "value": "96", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" }, "1000": { "value": "128", - "description": "", - "type": "dimension" + "type": "dimension", + "description": "" } } + }, + "$themes": [], + "$metadata": { + "tokenSetOrder": ["Alias", "Global"] } } diff --git a/crates/re_ui/data/icons/gear.png b/crates/re_ui/data/icons/gear.png new file mode 100644 index 0000000000000000000000000000000000000000..24f732003b8d8b425e9d6754b22f22fbc88007c0 GIT binary patch literal 612 zcmV-q0-ODbP)nuv*lAcCD1R)P}0QluAlizKE=JjZvQTpu$}#6`UDV|UNY+`D() z%z4f|fG7d)z-|I+rG+Ta3jTu~upzdkwH2bkn3B~REhBC0w#m5g1DFO4pb{*D94Jc6 z&4aJt5oiQ6T3eS^fXV)Vzu+G@)8|{y+$oLa)Xh2g4OT+QXZ51c2fbi#rD|dW1|mi;fT>*N$WRSJ^1OO zZMOPlbZEhriyo8>KFBqV^Z6X<`>Kl$fDImyO^tWhfnJH(KzB^UqxsQA`#$7J9!IBg zbz70=oEPoH-Tk7m!+qzm@^)3`b!V%*{tGU$gHX^$=G}FBVUE_&0vZ(`(zzzm$Y^L& zp1HBv#fC_Oq){%X{Gm&$rRCCkX~DE&T5>)yuSmt2jFwJopVrz;S^-~-s~dZ|>ZHBC yZ8BfP7l^OPkRh8ligt0000Or?sV@}5Kw4XL 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�Pzt8a|G;J4*r6#%t~*3$&N1^A1geQ|&aER(!^ zclq0~R=YdA}&<1?@|K>_+73bVKZ3R|Y~E0IBNcqfqk*a8%H~ zgaXw^t(Ja0(=>6b*V4vo!D5+T-EuK%Ug5JZ|IdibGt%|qg6XZtdMzEI08pu|Zn;@i z?gvH%?M>(qona&TLTyd!UwbO+rPxqOq7jp5y%+dK(7p@fds(1MB5!G z^MkN`5Mn?cNG4kTYAA2^eGHJQZv7^z_xNr1Q@~b>&dy|~tUHi5`5FMpnwH~@mFIv` zKkXg>-M|OH7GNhzI{=1pD5F)GAi|U&9W21Q_I7*R6U`OvgMCBPr8m7vCR!#ND-RQ% zFTYbnUqV@F)x4_F?l&ilsc8DiE7CR4n-(TEjqSFbGsKu96?-CTPDONvUrra`=+R~7 zP7Yu`#iu)fN^PR`8R<#c3Xo?}!mQaI5I&Ek*? z)7jXvL7nN^6$r)eegh)6+I&f!iv^Y5q$)SoXrz0ws9X+=acOG+c3>m3(oK^$6gts) z04z+rKee0aT41beTmA5c3O8y)tlp30ir$=eUol7B~8jL(B^MHba(V-F5=j#GzHRk`^H%U#;& ziy{w?C^1((_~zsmKkXKxwz~PtqViLpi&S#fjyQ6lpFL+2LCKkGM<5h6Pkn%NAY5Ia zjU~hOQ2we<_KKpm zy7h8Vy#pxm!CW`W7t)RKH~M?g$sORF85<8#_-7Z|{jkoO=$D4emk&_i5W7b#X8_xM zFc(F*et$1I34l>Hf1NtVU(Oib|cV6r6gYdewBhGy2Pe*KaZp#swZ0xsKJ)NG?a+gHJRDyBUS02Pocz18fCYz8^a&+;kP z)Hh9e5^;UqD9IahP2Yo_20(41?Q6JB=N2waXJd^{Z3IBNA$G68fRi@GBEp5qnQKS% zKIUlv2zIfTdi@xyG0y@ZIkRmHh|3hO?5M6? z*E-Y>oX6Ks-YzN^)6+Y^2^Lw@`)|(zKsxQ&I4tN8(X0RKo$0$>>4$~7#@PR&^53rX z1wCu-w9OwcPI(sqR?qiJt1l`%R@X4)m#*}~h825l&tTUHBTJ(lAAeV#1VD0T+ZaU7 z@ItE}tYE+ANIR5dvQsvqdaFAN`tdlxlK_bB=42e*{uOyN-B9tS3+*stb;MotUUt*zd8EO<2ru=-_BbowCh+e~A{ zD=xH$18Smo15OfJB5F?o4+7wfL_;Ye#})6=z9_QPh3;@+&mz}zF0>U+0f-h;Rs^8J zbd-_axb+D-JEL9o4s;8WocZ2FvnyJqM&clyqWZx$+1?OD?_UvpKvhohT36u&07)fs z&L$m-=D1@4$+8 zCTs!z;6mG$mezze0P==k`(dZHDz{jXXAdx0^W0A1rDSF97-x?4M#agsP%AUS8UQ;t zR>c3$wUJbH>pT(eWoWs1M;ODi3lr~8_1dq#h_SzMp{t@{4FKJ+dbK!BkVsW-tU+}t z#U;kZyNRZI@*v!iH*!D~zk^4@`4b>2Z#g$&x^D!^JW!gQs%lMnptE0Qu8+M7c)k!O z=%i2v0HOtJ+KQnkK~m;+b@74-6*z z=$K?xTcT*k213bsuPNaS070GR%ZqaAfF~&L7-Nr3XXD4Ev+;x=7Xfylw?xo^HH${K zc`hj$&HzANr%B6zBX>3O-Q^o9J|2hEWn)W$x`8$IFi%|D4`Pgq{}ka201?^c*u=hjeTNm5?V-E_h+3zu*dqDJ3JRxQM}f}|a!b0` zoENJIaql%XoB^OBPTQ_M)KuO8c$jOTP8(tD$=`D^&k)W4K;8Gh4J=hNBv z!^L~Ff7Z@uJp#N>x>00PI0FEgF=viC9iLANASz z&#g6I0R~%ppo%{x|wD6FFY zW(`|_WfnQ9J{wCe-%#On;%6@x9RBVEML+LC+jMkn5HGTVMde3cIn|3UV{COc)n$DAY*W1+|IX*9Cs&wXQ;2eKsD8rn6Hr#R#2|Xeb@a zA>YUQQEG`=Ndf=qv2HDvVYo0 z)_iy<;Hv8C*WguYm=Pz{E5He<$`&V8N5cVQyRXDiD?c?oQ`eq|tIG3U==6*5PtLSM zOtSK$alqv+v;{UDwW0iv;86gSmgyS6#dxW7iIpSuA`hl}Bv1zO^_I@0;@i*wwPyX$#`n{g&^+C{Y)mG;gxU>-@-E`fJ zn{|JnXY`*h-Sl+tW1a>;{f6?DfYSvMQSNqLgb@I#M9USxLYFpFc`&nf<|kG@J)UF` zMdVIzHTPk>5&KMXTATMNQUNi)y5&re+Z~(fidehP4lbV0GPUo_FO`C6*F{rlqW0O| z9WRF~$(okqRV9t%U0em0KJ<3QyM2#)9so}?SF|hY#{$kVrO}RGUU2xk6MV4cgxads zlZ-V_1EU?A%Bzj^yJG8Pp4pa0ZU+45=RZ3?qI8WPO&Yu=Rh>IaM4tt9AHYIpf4hh} z34qMn@*N`hHG6k-H_61$dc*!+be0j6AF=5k23K0` z1xFY$FQlq--|@-bkT_??#zRtxTphw4__uTWT?3`AIc;rHn`o&OB<<2hKdi7OdS%#l z5?fG}JJkw50jhj7n~PF1V_A06eoM>!*gNnvIgy^CjcR{f9u)Vbv++Ui-sbC|Bw5uw%ZOQngF;7-=mwgBtpYn#*$IF!9vA~m z0Aj(pSCBW_+wBQYG*=8dFT>|iX(+}62P<0;JNrPl*YUkHES-(thvItW>(j<=6?^Vb z_Erj{HZa{7yGG#WLD)VBv4SqDZ>ShjZAL)Fp`!V0>@rjj+HGKcL%F&xJ7vfXDgvtx z&LDZ)(S0GXIw&88I|AQJH^z%fzzMWGJoElxySB0YbkIkF^kW#32f3my8!vjr)%T}> z{z#&cNTT(}d%AUe#=vxna#3Al+>=GNPy|2^Dygcrn-D#ycL*<%ZALk7d1KtOo`fL^ zfF4p+trsEsWBf^$7*4!}l1w+o-|$j2bS+}ijj;!ev9kb2+oTLjmf2EsoY!AIgzyfq zmxYN;Hq>sexEA?p$&k&%&*F&RJfgR&%t|+z)N|{rJ1JqA068X zKXa#T9$#A0@gtPQ3=^M=U<<+$+Yq}egE-kZx;Mk>V$QG3eMJqo08ZMb1&oTw10`0k zUD_CL^ULV43IOP7ocv~0Yc0YL2-iA%5y(@ZKS*cezw_O)-v+&6&K!-Fzt?(+2-o3n zrma^f@IO}V4VlJ@SAue4*apB}mY`-`W$tt_=1S1h2;rk`A9jeyBl*1E>Uw}T7>0cS z^d?!=mN4jLh@KBj589tzSd>*DOYKN`G_$tc@vdlt=b#FJ-po(5eF3%eRG0?@(PgY1 zSR*RGibz-8vh36X`xFJkK^p+QnP2t6VM^pAg%d@XP0v$=*QHt8P<>5=RTy2FFWLNJ z&_yYO;ZqR+eOYvDZgf|>sj;G!V5`JRB~T?I6BHe*$S8r)pyTKnQ*7xeu+UAwCRIKZ sp+QB~W9@nqjjpd-Ke;*BPc=0DAEYgz%6uAR3jhEB07*qoM6N<$g4I@6mjD0& literal 12227 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 diff --git a/crates/re_ui/data/logo_light_mode.png b/crates/re_ui/data/logo_light_mode.png index 4675fec247da1f3ab73d8b0886da2ecc18b333fb..1b951089cf2a6bb35d753d2bba96ef226cf01134 100644 GIT binary patch literal 4402 zcmV-25zX$2P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H15Wh)8 zK~#90?VWpcT-9}lzkOz8*|PC7){KP#TN=rL3oEGuEDT8vX#$j&aT$$`F$)q*C?N#e z6xxQh>ZF8{0*l3?1QHTjjIl<;!-a%bmK_I76CgA|u))?yKmchZWBit_$DG|iWD_CF z8d-DCy;p|sFYC^o-`;a)`_8%N?7ffhDI__wZ4A4+XBd%6kSc*n3snXgr|5WKBuE)O z|Ly>G0Na3VBDzD7Z9pTcZ;6%lNPa_I9? z9-G+#yau|`!m1tY_}x?4>JE=>c+No^0P`!Gzo^FiE$BRungCqbh4O+L{gvjmF4I); zrvQ8?$UzkVsmhHtnC{C!&IgVN+Mixnh1IA&pd%xXWY(7N2-=~&4Eq2`qLE0V^-P6t z0beEjpJ^xPBPMTdUH*3L^`M;z`^DNX$(d_Mn2w2;qVkUj(}MPA2q^G4R)3gon(}f` zP7J#MSb~~$iM9&_{XS3`v_IaU�Pzt8a|G;J4*r6#%t~*3$&N1^A1geQ|&aER(!^ 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 literal 12287 zcmeHsXIN9))^0-YRgq2zU0M=)?@fBIDj@_2y@V<{f*V0rq!hUCC z4-gRn_H*C5UJ3xXt`}rtfi{BsgFL-G98j)E5IWEk2|@;-8~}iTx$;~~Z)T}XwX6EL zJ2)F~DGaWynatDm(>9`8d{9$|jKwl~vcMPp;Svi#J^#y#+TF{TTAB#|wx&B8)rGaP zhp}lAi}$-X?vcy)ygL;O4Why+HnI?|Ao|ZK!_|DBr_k|HlzQ&qC8JX?dHU++gD+y3bAi!!)AP037MtJhU0G;dolADv#H$RA zoCTj;T(wbm-F~4T_q8lpww5nM7qrdOE*h8?m{{p!r@798>f8|Rc2HKlyjJn}ZvJM? zliaI-*B+KXe(YPf82yNwK6PFH?tANuZtd*bDO*y9!DW8NjkCM^`9I%veE<3NgYx;< zmi5uyF9-Xl)j6hPHpIRcRx?Rl-TwI+(hcs5EXL2fzZ%n*bRG2iS3FBJdcSnOI^Rc> zf?iDSrx022T=F1&dCf;wNPKC)3EK5Qu355#Yh8F(X5sy7O&Q-qXW>1m!N)$t2QB@1 z_xyJk0txjJ!+RrkRv(9U+z;yQa>k$Q>Y0hr4@H>NFdHA)ALmb-N-1H$MtJf2IJZ%))0w(r$dm2EBeuh z<^<_T3BjmtJ~x#gxozF_lbD^zpy+j*zPio$!_&8h`%Emnu|x5|ZDn+OieyGgKh{y?)od#yd<{QHB>`4|z;AO0M9&xyn;IvT()kQM0~$NOx76<8y0K zVQgXf+et;0+jMeQLP)!T((%LnPv83Sb_(ykMLu_QUp~u5lF8`R6W|zsXgzMwv$%KT ziG6W1%BLYR&B|gtTA__+>dBLRAGadw!Uss&F1ZC9Tw^{RkM*g2qD+1l`-OI8L<6#- z@zfPTJ-cY$`pFt$`aFLyad$5&X>q^I!aU<1L7Poz!WO^nJGmG1ayK?wc5_k9?TVPR zv+=y7gsCJtToHi}f~hK9MzJ0pV$k7eX9wO@DG$qGlQOkMW|0J4PbZWm@{a zrhmYh%;ZcItA(|V&(Y7*Z`I8D!Rt(_m-?@K7C78tjfY-uo#m!Uu9Imr*>ditM!z4~ zub!EeD6ViE8M%o4G`yM+PSWORT`ymYU!IxYdtb%~B!7QCd-`&7Dq zkFI=7St>h*Paz(x**&q!?JtiupUl&n*X#&#a(L_@`HgyL9O|9;qd0E>ttT90zE5&C zet(p~-N5oP!OQN&Gk>8H_8kh&QAcO~i5wj8aACJ(HygsaI$u4Xd$w&WuI)Cy*P#`s zNlNiG3S|6hf=gD8v_GOZFEnAV#Y-{#gX{RH*m{d6|2BsO)mqogXkw$=)?lo;dpHb4 ze-8<9OB6X|AMi2$xH76oKv2GSqap1RcngP>JQ?hLTaq~Pl`5AfoT71K5hNDW=Vyn0 zvhZwxFKbY1v;gNXYP-9u5|DA!_0`KHvkIQ_jes9kRiK z@GK##H{Ywn1o&yId=sdtpP=9WfL0#5838(Pn0L@rabD`K6v0S`yOK+YvW-Mtj+nT8 zl`QW*2BgjsIa?7o0$0;X&aCEj70R3A>=1VStK=cKCQ7d}CJr&>Ue}|9$v(Ktq+ELbG)<*EiTV(ga)i;* z4Cr_y(E7?UJum{U5m7_2r*yzq>lh_gR6UOr>ac#_8)^#~#5c#oyFmgw?-M%G6oMY^ zSAYPAx$b4bZ-mBP>=CgkJvC@Bu*;$)_TSEA>Lt(ySEu%kIjMeoF;51z##xdsy5)i? z&&}Y?pT}7Y+u2SA(Bcs3buJ83(>x){Y@t?;+_vOXE<0ikn+QAA)}_IR-s7XSr5H)# z=eCH6Qt4cX1KLDteUk#oqlu^b8^>eCP0-ur&io98B}oBe50+7r?)L(0%jZ5Mle?OZ zGuM%lZC1ZBe{!X)?cW2jwt2Tq!VkStdwN*d#F1;Si!w+~ID;sC7`2jVRRh77tYk1b zhGg-Q#RHz!Arh**TG}BTeeD_XT2WRbR8D{#WX2jnSp6$WcczER*RuFB^KNQ+?FxRn zv0ZRSryndeVf*p%P37zbP~)0a*8RNjN5bE{+Q_%6g1@`Tk))qk>rbJoc8GH^{tq&Iwh4Lwyk_kntvD@oI_n8dL=hg$bC}ZE8Pmyp&jM$!|+-(_G zR2B$|w9fMFm@oNL$4Dyg1c9qx$VpUz&qabJ&9cA>()Y zH3nn68@i8e9AFxcLX@%~eX(JDx9>K=YNH$1Z8?b2FI%)f1$Bh0cfk13rvhJ#jve># zI~VgT6cd_+1vv8t%F_kxd%Ti2pv0UYYAsJzg78%@azc|AZpGwhcxw&UB%OQ)77llK z*~=Hji{wvyS+c{D_4IgB=tulA+C*9%Ad#F?s?VCI#Uf71b*u!_sLT{5EP3DbMpPiO z!&xh~&Xv;&9iA!@9cvosfKv+FBabG#o|55|OjOfaXwiLi?Rll+r$0$EAn=DW{Hh$$J63Fsa0Yk6GJpb)#BR`LYUI6+8N@$8e+HA zm$TF^t^@GiYnx4fjhJ)?kQ#aBE+ zBnny&YDKA(@Y+cfqWo#+RwJ~?dcnLTv-43IM8%6`z83c{AbUjXaK0^ar7p(PIgavY zQ2D*m{+|@z;~674(xB#sRT_K>BzJ$#jY;UV$fh zPJYacI*&<{MWy`=rn#!3nRAQQqNwY6Sb>SH!`bQ^x^IF~cwfZfINX{O>(c-!^iIC_ zj%eIg*`nD`?#_h@AEAlaHB>_>t&6!#^@d5GmwU^JBmHX#S3-o?iphd;oJ~MuxMjU2 zKW-V@>5n~Cu!1=A_uIUF1iJ1mvXv@+{^GhPIcvSc3vt^YrH@lNn{}Ntl2vX8lny;G zwB6;Ua8t6$C3<@`YV)lr#$)}GK3}VE4 zd-N!^+uiFPzu{BI6IPcUW>=o`e52b?O3|$Z8$mY+q`!`PeIBCq8Z$f`>dJzCL|(?- z^8U@2D9c+F2SQCnM*dz|R-E+)CSt>1+^Qy>zmwk#dOn!h5>C9Ba!YqkshA&CRU^}% z5n%0_-hbUR?TUd{CUU!&>8aKWSN4Uh@Pc~n&)freU3vBVCebAvz-|}gci%rHCp7du zz_2U#R>ev@q*lA0tRohb0FI{bHgIe`>A>``K8@5F_0rCz2~#rp4w4fAfT`$wV{jM> z95_6`Tg4?vYs`nRCAi}{=TrNca2h=#>(^)FNjh1yiVl)lPqNntTF{4|yxO}FB{(TV z^*%l7O(y*mlguiQktbCw&!a;?+0B?1iiv`=+9{-x;YWM(qy|#JPdLR#DIA{G#N4)7 zT<0x?Ys6hgAAD@@d@ST3h%v8|$!1vzRnN)NHU%m)$$pyrtb90(ueiU;htcUHjmPuM zUdg;F`oY89t>CBp@FSbCsQ+eBcq5?_f2QkHM^+Oe3F<oqZM zt9o>ao6u@~JHhB?0_}oxyD8V)n5+=a=?tWLOq|-k>HW@dh~#HwizAL8wuS`iE8TSF z!3X&L#;bC6QCw?o;oKiZygi5jFNjPda#Qy+a&=e@`GtOy0mx{=!WD7Pr5F z8FzXofWecgNWIYEkl;!9Zj4lJiz#&}tDxuU+d?Fm#QsD__%2S+)hfugW0T#a<^>VK z&KEW3z%pk4u6?)m*+SaIB-95B&4gouDI(QqZH{boiYlzfn+RgUQa;LkoJ-TkD!&Y8>*>>Rs^oYOLEQO)I;U01zhOsP8JCPM zY2~np-6G+wP83)@jm|7y2gnJtmv$*0Ch-I4g}qw|b&iVEJRsB#RgT1un_3E(LI_s1 zw~PcUFV(SrzX}seoC<8GRtAoyi>d0cOjo2cnEdRGZv1K(3c=g<9?|L$Ht;!J)y*VI zhc^*MwCIPv_T$dz{Xu{a5uT!?gYn^5?78I;)ePUeZ#{??(d0BgOD9b-S6+4)hd=ma z+~1q#Ts4V(t>J{x$)DY9cuu~gixW^u=N!4ka0W$S=$axIl$D1fZBN5x&G%SKrB^5v zAG4P0F#1tP5gMh~N7mvuldS5=7*?NrEPwJf(DG%HDW}vB5wYsZP@y=dEzOg;i-oyE z+LJA>i0KD)v_>9LmE@;`s#Bz<^YZA%O%YHhlPz{WhV{N zJIzha7Zl$DevGtE@5uNOQytp--$c&E`%|6OtRHR2`oBec%8K{3hWt{iO*Qq#SB>@uAXgbuoS%2?XVWe zIa(z1Y9~(!Lz6-2Lda3*DBhC^aB0I*gLBgjqK1c?^#STWwR)oF7};g+w!X`M~5`S-H7o8LP`_ z#+OvPX9Jo#CJKEs4OFRCvYtrl&eF$I>RX3*<0GGu-SNby++sO*fwd~wIy?(ynfmg< zd0^qXDW3p{Azm}YBKC>;m%?IlBgrs3wZglQq%d<6lM%bmk>hr1^zvFgLNQ+IU9UP@ z-nHP-8dB?lYf_#G4w_Qh>fUVKAlKK}h_moCBA9* z{u2$g`dAx|CvA@=55Wp?PfKkffMRb~+^yg`z-z%m1pJ0O+Al=>#>SvUY5VlZT2^{L zOfm_2yha-2W5O7#xTiJramS$g&d<=&7U_CA()F2oE8n0CDWbk55GSv@-FjwlCGs4H z=%HgptM(At>$_9puh~TKBoyt!UvX%2FG}d#m+Kq96j-ejD>VJOt9ia z0}A7f2kw7-v^7|_EYd^Z48Nt>J(r)USdS9En$a_C)t{OR+2+*d*C>IsSe54&)NRU)VnlyZWq`=+pIK6 zg||WaQRZSXvCgAGC=T82I12%@s1hNUJ!7Zp1#t7Rn{J#?h6jR_^~3?RCQMHVn9@8_ zLGmnzANL1nU!=G&Kc!i04T8sn-5>sRBa8?1=vagH^hZ6qsMM%g>rjcbcfx^AnO^(%zgvC^YPXFJ3XPwcQ#CGAJ*|6Kvp|nEkT1FuJ@jHD^dk zSw`(QBQN6yQTeK})S&u;)gjo47zLvO+CQ2zQ}a8E)h~OAXpT^p=|#pq3UZKFw=yi? z^-BwwHvVpwf~V3=vAx4(C9x%<+H)hCm%Y?*_*&vtBn^g&w1oZ1jAx0f7Q+z6QZd21 zaIFq}-OGm55y*`mBXFjrti0fUz4qC?h|k%$gSI;W^op`*jlZ)hMaLS%bgS@9uGUMWfO*YPYVZlqshAncvE%@ zngzKR`p$MzpOc#_+?Bq#|8@Q%izoEH==G(ISF;qjWQRSYCzu4t&3s}202qlz)Z{{z7N(moRrNs6br$g(tI z_B`wB%C$t`I7sxgFx2=l?cg=pp?7k@!Cmp+1P*-eKf1%^0mJZ6O~MxIVkKfLBm@h- z$$Ld+a7KO44;U*VrBU#{?e` zW9Oh{^^C=C;d#`@&X$HT-&{k^`dF@5Do|-O>}DNRg(=6NYVHXw>m~b;aH=-9IYR<7 z=~LyXweP)GEB>txaPK}{5}vS{8}pa+K;;g_M)&#Up_WBiAkHKv#xt_rOj)c>u%CO9)|-kw$p93Bm0>?2tkMZl2hkZ~#D7 zA;1%ka6zI$c1TB*yBzqSqX!H^*~@{=#dIOMo+?NulxC1O(l|)Z1QFzdkg^9W$dkwh zNMixqkZ3q4z|Ga&M>;?b{2Nyqd;Y6g7!3Mtf_9MuTj&~qR6M+qAWy=Q34y(m13RJ7p3=g?{{H?#{vtvi-j2dhDJdyo2uv6T6U15w`UJY8 z;Q@l~J{-Rw{=iU0`XIbfo@kVZJLnfC+|I)nEe8f;`$7MT&&^X;_b+&NpFdf^@*x}m z_Y{T-L4@7hg#Yg0gI4pyg8UiK|LEakg8jol*a+$4;p>e+s`(+^(Hwt=ut)sm@9FF9 z`a2zagfP+->4r7+!Hx?3$B-IYx(0uF{Gz}S<>vX@3rqGtEYT>3f0Ol(*nai=PUr7| zVBP=1{fG6xa{q0NwbIp{Oc2GMJK@k|t9wy;{lz@tg{S8XX-3JYKM<9PeVZnt^SR90dl!F}%ju1pb9H4@t zc1U|cJGc}ULPQJ-MPdh#kc9mWLf;#ORVCc@?@|4Nvd2P6z+n7JoAHcurB8|-k%PjntP_Y2NJ+R40t9T>fXb*1_4-Z#4@UNIazdV2I8zlQjRWwmP zSc|}4lK)fm#z?O}uKt(${5Cqgt0wOAckbwMA@W0S~JRH#eaBrlN zBQ{fPHdqDy&IZK$2bX;R494FH`HLtBc6LKV1!0mVP*G_S329Lgeh5q&0s#yE@8^is zAWRBkj})|ri9xV3l7I=qp;$pVh}ubrK_tY)?CcQ#4Ep~$M=@!LgtQp+pE=43|605L zRaRNy|EutS82oLq!*cUS8+H-JuF1lGEy{nA_Dd`Oi?2Tg_rEv-7W%(~{73x$m#+WP z^&c_t9~u8wUH_%)KVslNGXAf+{=d;h^6$qwq&xOj&ma5HW>L5Q9{a#YV5hC23b^|9 zF6ydC!?qB6YFhdL074wU4q!mJf*-b#5Ur)FM!12;Oin1mG#Ow80FY#AsVbQS%^NGN#bHya6rePLoA!G2gl+mP2|JT(F9 z1aA|H`EoN-h=Yk1qP{b39cX7+RPgNrkAi#DwTyY>EVg^ae)LYn+88|01o}{Me4fzy z32Z94Z`_wl91J*mJ&JOS&}$YCWaH7jzp&;FqJ2srizE9p4L936T7tVw*1&}-)||TX zt;_09fR*$Y9pyF9v2q`ftcaD>rMX)_^R@`Tgw)#%euooMEn#3qM~R=qiHp|3IVa%B zG>6MaKGJP@^CMz#ztNB|;g`*d$M*1Y_C#O-j0SX4;_XOpjaRWGS-p9cPS9 zTH2ny{G*E~t5{fnEORjTJSM+i=#>X)E}qS2dFxn|)EDLYg3#8}-N@N@xnuL`k&03G z>Z()yaF1=K;kJujG~9zRLGXLcB;-u zI)lTaE>8Wp-tQn%^M#G{Q}QP{nmyhtI=51v#FoUk7O^E$>L@3)5SLN_oo0s3BGY4L z22cu#`q9Uu7gUN#<#!b|Kcz^wpWX#DkA8UhRgv7khiRCg1{mG8Oyzl_4xH>UlRQJ~ zL@0_s0XV_+z>N>*@;Xc>NQK$O(Z%$COvJ~>4+$&8aA6!VZF)VWWxI%@At_<%^c0L# zc&KW*5Ja{`Ph`Gq*zOsjC!Z(Y`;|GlTILgGocx1*(s22~HP;)rc7QgUt^ujLKOID@ z_XC&GqN1Ykk)yq;(YkC+=b$} z9vQT@{x8YKp2TWgq=dWR7ak<1->`v7bulf>j(+RR3t7GrFEVC&A+Zstt)$c|xzY+$ zR9znlyIhb2?5<%L_B8y8Zr5Mc>l)Qfq}g1~j5PNk81@25Ju^BKjILpxnN>Ug`0)e5 zMZP{{AAGy0rtAb8A;$vVd-~dj)$H>FfM~$jajamh5C*84i+tqf%J2!ik}KaNv&sR& zeHxJsp2oE6g_hyS?n_~sXwX5p+6YX>0Rh^$Nnda7d-qizbS8(6erqIohGwU8w)wd- zr-H(LVrp1RSwUfZnAl+Rv4vAy5OuA6E!e%;kdh^V^GAOTXW4eq#v|9;QLbUf*)O_q z$nR(Vl-t@$q70M9^dLH4<vbq**Jt3bRX=*v@BVNJ?}hkFYXRQk z2^~;HYL|t~N=A0@mZg?Vn6b@t`A5$ex3I;SRyExUT;l6Kpo}k99IxssyMfd8!9l#fFlP3|>qNK8Xqozu=|5Sj)#=(r|Nk7#G$9SGmyZyYJ4>^Nd zH8K<{K~Opyckj<$>&w_uE2XKL?feY2E(+3_ZUVh1p`9I*2HbtQ(S;;W`AP@Wb~89B zyCr_E5WeT7Ik1cu$La|$=)-co2SdyPw%W2DbVRNHb23?pt`V7`4+U9;tS;IdsL zKkNtYm^VFh;Cj!o72e+7E1&5C$058}1E+4L@x#}1h=}*KmrNoq6M>#?{FInm3*V;C zkKS5|eh*PC9Q82+b&R>^iodHgsa5>cc|*lq5lYrvdWWXZP3x?RzEmS^Qz(NdHPc;e zvFldG&0?dVDo31eEv~#v$3-o8CyPM~Yvpbw@gx6&qoUPD{z_?(1)|iQZhv zbhwSUy@l@xRe#PZnGNyz5;&oB?1|6duRIpwVw<@c*gJ*BuY23<>x9!c%oAP$d}If@ zMq#6L4=<;a~pW1x_ zt@b#!iG*3)%%9zC_;AC=i98d2hJk<1oxjbG{|PLr=%bNIg3%(oF-p&OTnEL;s`^v%4ap)UBipYMim zdN{wW3UpfT4k`a?_gLoO3VhY$L#5A6cY}C@6+FnOvl&j;mK<0UST)_(+lm!{TFQf% zx6JP!RTV5kKB;%Ozx1ot)}3| zK1SQx)`6vxIgsf6>VA({W5?;<-nO8WH?(T6mSgiB7>pL!GU5pvfl~Lv zS#vX>Z=x#b=tmd4SEwK(r)~~gnUxxVChltfZtt%Phh}4aj%ByS4-}!ZGkl0|nE>sv z&#odelcyK)W3Gf>QV@fmfJfvb>b62Y@2;ypN<0v2JmuW;g_lbvyUD+l9%|<=3?>nU zHj>Is{w%AjtK(hzvSXw(<5V+CB}w_cJjV^+PxI`|;dAPKovS-oyFf!>-hTY zCQJBvZD+%q+vWSlhV0(JA;0KdvV?8+$rfIZtpi}y=D-kiVrZDVPyKYLiS#RLijNK( zSbBc8an2(CgSFPkG>sL)gS^ALPoc)keOmbEowK^fv`# zO0&JgCzffwz_T^u)&-(JA98r51?P#$2DLa!sO=3sbQ6bc!lvXfp$Qb(OS65wpPB;< z;(T)sQ+)I1@IP5)1w65>dObJ8)6}hhQMRq`85Tm>6r)l7(ut(=T7W4z=4}0vp)_2Z zK!sifBL6lGw*%MWC(5OI@rYYB`}ji9JV9{I^M2-1yD*m&o`MJ8v9p#XM-6+KZzMik zu-`cPc=89?ub+4w%`~FQ>U&q*?8l81#u7N+g)3$u`UoQa#5!5!ITZ_)s_g}?SH<50 cN3H?Q%C-^z2QpwzUH||9 diff --git a/crates/re_ui/src/command.rs b/crates/re_ui/src/command.rs index 55f1aa2a430a..f41bf4fa43b0 100644 --- a/crates/re_ui/src/command.rs +++ b/crates/re_ui/src/command.rs @@ -72,7 +72,7 @@ impl Command { Command::Open => ("Open…", "Open a Rerun Data File (.rrd)"), #[cfg(not(target_arch = "wasm32"))] - Command::Quit => ("Quit", "Close the Rerun Viewer"), + Command::Quit => ("Quit", "Close the Depthai Viewer"), Command::ResetViewer => ( "Reset viewer", @@ -87,7 +87,7 @@ impl Command { Command::ToggleMemoryPanel => ( "Toggle memory panel", - "Investigate what is using up RAM in Rerun Viewer", + "Investigate what is using up RAM in Depthai Viewer", ), Command::ToggleBlueprintPanel => ("Toggle blueprint panel", "Toggle the left panel"), Command::ToggleSelectionPanel => ("Toggle selection panel", "Toggle the right panel"), diff --git a/crates/re_ui/src/design_tokens.rs b/crates/re_ui/src/design_tokens.rs index 4a3642695e87..dbcfdd66af40 100644 --- a/crates/re_ui/src/design_tokens.rs +++ b/crates/re_ui/src/design_tokens.rs @@ -11,6 +11,17 @@ pub struct DesignTokens { pub bottom_bar_stroke: egui::Stroke, pub bottom_bar_rounding: egui::Rounding, pub shadow_gradient_dark_start: egui::Color32, + pub success_bg_color: egui::Color32, + pub success_hover_bg_color: egui::Color32, + pub warning_bg_color: egui::Color32, + pub warning_hover_bg_color: egui::Color32, + pub error_bg_color: egui::Color32, + pub error_hover_bg_color: egui::Color32, + pub primary_bg_color: egui::Color32, + pub primary_hover_bg_color: egui::Color32, + pub gray_50: egui::Color32, + pub gray_900: egui::Color32, + pub primary_700: egui::Color32, } impl DesignTokens { @@ -46,7 +57,7 @@ fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens { } let mut egui_style = egui::Style { - visuals: egui::Visuals::dark(), + visuals: egui::Visuals::light(), ..Default::default() }; @@ -71,26 +82,25 @@ fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens { } let panel_bg_color = get_aliased_color(&json, "{Alias.Color.Surface.Default.value}"); - // let floating_color = get_aliased_color(&json, "{Alias.Color.Surface.Floating.value}"); - let floating_color = Color32::from_gray(38); // TODO(emilk): change the content of the design_tokens.json origin instead + let floating_color = get_aliased_color(&json, "{Alias.Color.Surface.Floating.value}"); // Used as the background of text edits, scroll bars and others things // that needs to look different from other interactive stuff. // We need this very dark, since the theme overall is very, very dark. - egui_style.visuals.extreme_bg_color = egui::Color32::BLACK; + egui_style.visuals.extreme_bg_color = egui::Color32::WHITE; egui_style.visuals.widgets.noninteractive.weak_bg_fill = panel_bg_color; egui_style.visuals.widgets.noninteractive.bg_fill = panel_bg_color; egui_style.visuals.button_frame = true; - egui_style.visuals.widgets.inactive.weak_bg_fill = Default::default(); // Buttons have no background color when inactive - egui_style.visuals.widgets.inactive.bg_fill = Color32::from_gray(40); - // get_aliased_color(&json, "{Alias.Color.Action.Default.value}"); // too dark to see, especially for scroll bars + egui_style.visuals.widgets.inactive.weak_bg_fill = + get_aliased_color(&json, "{Alias.Color.Action.Inactive.value}"); // Buttons have no background color when inactive + egui_style.visuals.widgets.inactive.bg_fill = + get_aliased_color(&json, "{Alias.Color.Action.Default.value}"); { // Background colors for buttons (menu buttons, blueprint buttons, etc) when hovered or clicked: - // let hovered_color = get_aliased_color(&json, "{Alias.Color.Action.Hovered.value}"); - let hovered_color = Color32::from_gray(64); // TODO(emilk): change the content of the design_tokens.json origin instead + let hovered_color = get_aliased_color(&json, "{Alias.Color.Action.Hovered.value}"); egui_style.visuals.widgets.hovered.weak_bg_fill = hovered_color; egui_style.visuals.widgets.hovered.bg_fill = hovered_color; egui_style.visuals.widgets.active.weak_bg_fill = hovered_color; @@ -100,11 +110,11 @@ fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens { } { - // Turn off strokes around buttons: - egui_style.visuals.widgets.inactive.bg_stroke = Default::default(); - egui_style.visuals.widgets.hovered.bg_stroke = Default::default(); - egui_style.visuals.widgets.active.bg_stroke = Default::default(); - egui_style.visuals.widgets.open.bg_stroke = Default::default(); + let border_color = get_global_color(&json, "{Global.Color.Gray.200}"); + egui_style.visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, border_color); + egui_style.visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.0, border_color); + egui_style.visuals.widgets.active.bg_stroke = egui::Stroke::new(1.0, border_color); + egui_style.visuals.widgets.open.bg_stroke = egui::Stroke::new(1.0, border_color); } { @@ -149,13 +159,14 @@ fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens { // Add stripes to grids and tables? egui_style.visuals.striped = false; egui_style.visuals.indent_has_left_vline = false; - egui_style.spacing.button_padding = egui::Vec2::new(1.0, 0.0); // Makes the icons in the blueprint panel align + egui_style.spacing.button_padding = egui::Vec2::new(12.0, 4.0); // Makes the icons in the blueprint panel align egui_style.spacing.indent = 14.0; // From figma egui_style.debug.show_blocking_widget = false; // turn this on to debug interaction problems egui_style.spacing.combo_width = 8.0; // minimum width of ComboBox - keep them small, with the down-arrow close. + egui_style.spacing.icon_width = 18.0; // Checkbox width and height egui_style.spacing.scroll_bar_inner_margin = 2.0; egui_style.spacing.scroll_bar_width = 6.0; egui_style.spacing.scroll_bar_outer_margin = 2.0; @@ -163,26 +174,42 @@ fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens { ctx.set_style(egui_style); DesignTokens { - top_bar_color: Color32::from_gray(20), // copied from figma - bottom_bar_color: get_global_color(&json, "{Global.Color.Grey.150}"), - bottom_bar_stroke: egui::Stroke::new(1.0, egui::Color32::from_gray(47)), // copied from figma + top_bar_color: get_global_color(&json, "{Global.Color.Gray.50}"), // copied from figma + bottom_bar_color: get_global_color(&json, "{Global.Color.Gray.100}"), + bottom_bar_stroke: egui::Stroke::new( + 1.0, + Color32::TRANSPARENT, // Transparent because it doesn't look good in light mode + ), // copied from figma bottom_bar_rounding: egui::Rounding { nw: 6.0, ne: 6.0, sw: 0.0, se: 0.0, }, // copied from figma, should be top only - shadow_gradient_dark_start: egui::Color32::from_black_alpha(77), + shadow_gradient_dark_start: Color32::TRANSPARENT, + success_bg_color: get_global_color(&json, "{Global.Color.Success.200}"), + success_hover_bg_color: get_global_color(&json, "{Global.Color.Success.300}"), + warning_bg_color: get_global_color(&json, "{Global.Color.Warning.200}"), + warning_hover_bg_color: get_global_color(&json, "{Global.Color.Warning.300}"), + error_bg_color: get_global_color(&json, "{Global.Color.Error.200}"), + error_hover_bg_color: get_global_color(&json, "{Global.Color.Error.300}"), + primary_bg_color: get_global_color(&json, "{Global.Color.Primary.Default}"), + primary_hover_bg_color: get_global_color(&json, "{Global.Color.Primary.500}"), + gray_50: get_global_color(&json, "{Global.Color.Gray.50}"), + gray_900: get_global_color(&json, "{Global.Color.Gray.900}"), + primary_700: get_global_color(&json, "{Global.Color.Primary.700}"), } } // ---------------------------------------------------------------------------- fn get_aliased_color(json: &serde_json::Value, alias_path: &str) -> egui::Color32 { + re_log::debug!("Alias path: {alias_path}"); parse_color(get_alias_str(json, alias_path)) } fn get_global_color(json: &serde_json::Value, global_path: &str) -> egui::Color32 { + re_log::debug!("Global path: {global_path}"); parse_color(global_path_value(json, global_path).as_str().unwrap()) } diff --git a/crates/re_ui/src/icons.rs b/crates/re_ui/src/icons.rs index ecd81778f537..b453030bf320 100644 --- a/crates/re_ui/src/icons.rs +++ b/crates/re_ui/src/icons.rs @@ -63,6 +63,8 @@ pub const RESET: Icon = Icon::new("reset", include_bytes!("../data/icons/reset.p pub const CLOSE: Icon = Icon::new("close", include_bytes!("../data/icons/close.png")); +pub const GEAR: Icon = Icon::new("gear", include_bytes!("../data/icons/gear.png")); + pub const SPACE_VIEW_TEXT: Icon = Icon::new( "spaceview_text", include_bytes!("../data/icons/spaceview_text.png"), diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs index d5d9569cf7b2..d8a9d782db95 100644 --- a/crates/re_ui/src/lib.rs +++ b/crates/re_ui/src/lib.rs @@ -14,6 +14,7 @@ pub use command_palette::CommandPalette; pub use design_tokens::DesignTokens; pub use icons::Icon; pub use static_image_cache::StaticImageCache; +use std::ops::RangeInclusive; pub use toggle_switch::toggle_switch; // --------------------------------------------------------------------------- @@ -128,6 +129,99 @@ impl ReUi { 10.0 } + #[inline] + pub const fn box_width() -> f32 { + 139.0 + } + + #[inline] + pub const fn box_height() -> f32 { + 22.0 + } + + pub fn labeled_combo_box( + &self, + ui: &mut egui::Ui, + label: &str, + selected_text: String, + left_to_right: bool, + menu_contents: impl FnOnce(&mut egui::Ui) -> R, + ) { + let align = egui::Align::Center; + let layout = if left_to_right { + egui::Layout::left_to_right(align) + } else { + egui::Layout::right_to_left(align) + }; + + ui.with_layout(layout, |ui| { + if left_to_right { + ui.label(egui::RichText::new(label).color(self.design_tokens.gray_900)); + } + ui.add_sized( + [Self::box_width(), Self::box_height() + 1.0], + |ui: &mut egui::Ui| { + egui::ComboBox::from_id_source(label) + .selected_text(selected_text) + .width(Self::box_width()) + .show_ui(ui, menu_contents) + .response + }, + ); + if !left_to_right { + ui.label(egui::RichText::new(label).color(self.design_tokens.gray_900)); + } + }); + } + + pub fn labeled_checkbox(&self, ui: &mut egui::Ui, label: &str, value: &mut bool) { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add_sized( + [Self::box_width(), Self::box_height()], + |ui: &mut egui::Ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.checkbox(value, ""); + }) + .response + }, + ); + ui.label(egui::RichText::new(label).color(self.design_tokens.gray_900)); + }); + } + + pub fn labeled_dragvalue( + &self, + ui: &mut egui::Ui, + label: &str, + value: &mut Num, + range: RangeInclusive, + ) where + Num: egui::emath::Numeric, + { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add_sized( + [Self::box_width(), Self::box_height()], + egui::DragValue::new(value).clamp_range(range), + ); + ui.label(egui::RichText::new(label).color(self.design_tokens.gray_900)); + }); + } + + pub fn labeled_toggle_switch(&self, ui: &mut egui::Ui, label: &str, value: &mut bool) { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add_sized( + [Self::box_width(), Self::box_height()], + |ui: &mut egui::Ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Min), |ui| { + ui.add(toggle_switch(value)); + }) + .response + }, + ); + ui.label(egui::RichText::new(label).color(self.design_tokens.gray_900)); + }); + } + pub fn top_panel_frame(&self) -> egui::Frame { let mut frame = egui::Frame { inner_margin: Self::top_bar_margin(), @@ -290,6 +384,9 @@ impl ReUi { let texture_id = image.texture_id(ui.ctx()); // TODO(emilk): change color and size on hover let tint = ui.visuals().widgets.inactive.fg_stroke.color; + let mut style = ui.style_mut().clone(); + style.spacing.button_padding = egui::Vec2::new(2.0, 2.0); + ui.set_style(style); ui.add(egui::ImageButton::new(texture_id, size_points).tint(tint)) } diff --git a/crates/re_ui/src/toggle_switch.rs b/crates/re_ui/src/toggle_switch.rs index ba52c4dd3e50..3185f49f3082 100644 --- a/crates/re_ui/src/toggle_switch.rs +++ b/crates/re_ui/src/toggle_switch.rs @@ -1,39 +1,55 @@ //! Adapted from `egui_demo_lib/src/demo/toggle_switch.rs` +pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { + // Widget code can be broken up in four steps: + // 1. Decide a size for the widget + // 2. Allocate space for it + // 3. Handle interactions with the widget (if any) + // 4. Paint the widget -fn toggle_switch_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { - let interactive_size = egui::vec2(12.0, ui.spacing().interact_size.y); - let (interact_rect, mut response) = - ui.allocate_exact_size(interactive_size, egui::Sense::click()); - let visual_size = egui::vec2(12.0, 8.0); // 12x7 in figma, but 12x8 looks _much_ better in epaint - let visual_rect = - egui::Align2::CENTER_CENTER.align_size_within_rect(visual_size, interact_rect); + // 1. Deciding widget size: + // You can query the `ui` how much space is available, + // but in this example we have a fixed size widget based on the height of a standard button: + let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0); + // 2. Allocating space: + // This is where we get a region of the screen assigned. + // We also tell the Ui to sense clicks in the allocated region. + let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); + + // 3. Interact: Time to check for clicks! if response.clicked() { *on = !*on; - response.mark_changed(); + response.mark_changed(); // report back that the value changed } + + // Attach some meta-data to the response which can be used by screen readers: response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, "")); - if ui.is_rect_visible(visual_rect) { + // 4. Paint! + // Make sure we need to paint: + if ui.is_rect_visible(rect) { + // Let's ask for a simple animation from egui. + // egui keeps track of changes in the boolean associated with the id and + // returns an animated value in the 0-1 range for how much "on" we are. let how_on = ui.ctx().animate_bool(response.id, *on); - let visuals = ui.style().interact(&response); - let expanded_rect = visual_rect.expand(visuals.expansion); - let fg_fill = visuals.bg_fill; - let bg_fill = visuals.text_color(); - let rounding = 0.5 * expanded_rect.height(); + // We will follow the current style by asking + // "how should something that is being interacted with be painted?". + // This will, for instance, give us different colors when the widget is hovered or clicked. + let visuals = ui.style().interact_selectable(&response, *on); + // All coordinates are in absolute screen coordinates so we use `rect` to place the elements. + let rect = rect.expand(visuals.expansion); + let radius = 0.5 * rect.height(); ui.painter() - .rect(expanded_rect, rounding, bg_fill, egui::Stroke::NONE); - let circle_x = egui::lerp( - (expanded_rect.left() + rounding)..=(expanded_rect.right() - rounding), - how_on, - ); - - let circle_center = egui::pos2(circle_x, expanded_rect.center().y); - let circle_radius = 2.5 * expanded_rect.height() / visual_size.y; + .rect(rect, radius, visuals.bg_fill, visuals.bg_stroke); + // Paint the circle, animating it from left to right with `how_on`: + let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on); + let center = egui::pos2(circle_x, rect.center().y); ui.painter() - .circle(circle_center, circle_radius, fg_fill, egui::Stroke::NONE); + .circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke); } + // All done! Return the interaction response so the user can check what happened + // (hovered, clicked, ...) and maybe show a tooltip: response } @@ -45,5 +61,5 @@ fn toggle_switch_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { /// ui.add(toggle_switch(&mut my_bool)); /// ``` pub fn toggle_switch(on: &mut bool) -> impl egui::Widget + '_ { - move |ui: &mut egui::Ui| toggle_switch_ui(ui, on) + move |ui: &mut egui::Ui| toggle_ui(ui, on) } diff --git a/crates/re_viewer/Cargo.toml b/crates/re_viewer/Cargo.toml index 8ec7e70ddc1b..645f75e77749 100644 --- a/crates/re_viewer/Cargo.toml +++ b/crates/re_viewer/Cargo.toml @@ -17,6 +17,7 @@ include = [ "Cargo.toml", "data/*", ] +resolver = "2" [package.metadata.docs.rs] all-features = true @@ -28,14 +29,15 @@ crate-type = ["cdylib", "rlib"] [features] -default = ["analytics"] +default = ["analytics", "webgl"] ## Enable telemetry using our analytics SDK. analytics = ["dep:re_analytics"] +## Render using webgl instead of webgpu on wasm builds. +webgl = ["re_renderer/webgl"] -[dependencies] -# Internal: +[dependencies] # Internal: re_arrow_store.workspace = true re_build_info.workspace = true re_data_store = { workspace = true, features = ["serde"] } @@ -46,11 +48,21 @@ re_log_types = { workspace = true, features = ["ecolor", "glam", "image"] } re_log.workspace = true re_memory.workspace = true re_query.workspace = true -re_renderer = { workspace = true, features = ["arrow", "serde"] } +re_renderer = { workspace = true, default-features = false, features = [ + "arrow", + "import-gltf", + "import-obj", + "serde", +] } re_smart_channel.workspace = true re_tensor_ops.workspace = true re_ui.workspace = true re_ws_comms = { workspace = true, features = ["client"] } +serde_json = "1" +tokio = { workspace = true, default-features = false, features = ["rt"] } +ewebsock = { version = "0.2", optional = false } +strum = { version = "0.24", features = ["derive"] } +strum_macros = "0.24" # Internal (optional): re_analytics = { workspace = true, optional = true } @@ -60,15 +72,16 @@ re_analytics = { workspace = true, optional = true } ahash.workspace = true anyhow.workspace = true bytemuck = { version = "1.11", features = ["extern_crate_alloc"] } -eframe = { workspace = true, default-features = false, features = [ +crossbeam-channel = "0.5.7" +eframe = { workspace = true, features = [ "default_fonts", "persistence", "puffin", "wgpu", ] } -egui = { workspace = true, features = ["extra_debug_asserts", "tracing"] } +egui.workspace = true egui_dock = { workspace = true, features = ["serde"] } -egui_extras = { workspace = true, features = ["tracing"] } +egui_extras.workspace = true egui-wgpu.workspace = true enumset.workspace = true glam = { workspace = true, features = [ @@ -93,16 +106,24 @@ serde = { version = "1", features = ["derive"] } slotmap = { version = "1.0.6", features = ["serde"] } smallvec = { workspace = true, features = ["serde"] } thiserror.workspace = true -time = { workspace = true, default-features = false, features = ["formatting"] } +time = { workspace = true, default-features = false, features = [ + "formatting", + "wasm-bindgen", +] } uuid = { version = "1.1", features = ["serde", "v4", "js"] } vec1 = "1.8" wgpu.workspace = true +url = "2.3.1" # native dependencies: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] arboard = { version = "3.2", default-features = false, features = [ "image-data", ] } +pyo3 = { version = "0.18.0", features = ["auto-initialize"] } +pyo3-asyncio = { version = "0.18", features = ["attributes", "tokio-runtime"] } +async-std = "1.9" + puffin_http = "0.11" puffin.workspace = true diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs index c374cc5a1aa3..1702312cc594 100644 --- a/crates/re_viewer/src/app.rs +++ b/crates/re_viewer/src/app.rs @@ -17,6 +17,7 @@ use re_ui::{toasts, Command}; use crate::{ app_icon::setup_app_icon, + depthai::depthai, misc::{AppOptions, Caches, RecordingConfig, ViewerContext}, ui::{data_ui::ComponentUiRegistry, Blueprint}, viewer_analytics::ViewerAnalytics, @@ -99,9 +100,47 @@ pub struct App { analytics: ViewerAnalytics, icon_status: AppIconStatus, + + #[cfg(not(target_arch = "wasm32"))] + backend_handle: Option, } impl App { + #[cfg(not(target_arch = "wasm32"))] + fn spawn_backend() -> Option { + // TODO(filip): Is there some way I can know for sure where depthai_viewer_backend is? + let backend_handle = match std::process::Command::new("python") + .args(["-m", "depthai_viewer._backend.main"]) + .spawn() + { + Ok(child) => { + println!("Backend started successfully."); + Some(child) + } + Err(err) => { + eprintln!("Failed to start depthai viewer: {err}"); + match std::process::Command::new("python3") + .args(["-m", "depthai_viewer._backend.main"]) + .spawn() + { + Ok(child) => { + println!("Backend started successfully."); + Some(child) + } + Err(err) => { + eprintln!("Failed to start depthai_viewer {err}"); + None + } + } + } + }; + // assert!( + // backend_handle.is_some(), + // "Couldn't start backend, exiting..." + // ); + backend_handle + } + /// Create a viewer that receives new log messages over time pub fn from_receiver( build_info: re_build_info::BuildInfo, @@ -157,6 +196,8 @@ impl App { analytics, icon_status: AppIconStatus::NotSetTryAgain, + #[cfg(not(target_arch = "wasm32"))] + backend_handle: App::spawn_backend(), } } @@ -246,8 +287,6 @@ impl App { } fn run_command(&mut self, cmd: Command, _frame: &mut eframe::Frame, egui_ctx: &egui::Context) { - let is_narrow_screen = egui_ctx.screen_rect().width() < 600.0; // responsive ui for mobiles etc - match cmd { #[cfg(not(target_arch = "wasm32"))] Command::Save => { @@ -263,6 +302,10 @@ impl App { } #[cfg(not(target_arch = "wasm32"))] Command::Quit => { + self.state.depthai_state.shutdown(); + if let Some(backend_handle) = &mut self.backend_handle { + backend_handle.kill(); + } _frame.close(); } @@ -281,20 +324,10 @@ impl App { Command::ToggleBlueprintPanel => { let blueprint = self.blueprint_mut(egui_ctx); blueprint.blueprint_panel_expanded ^= true; - - // Only one of blueprint or selection panel can be open at a time on mobile: - if is_narrow_screen && blueprint.blueprint_panel_expanded { - blueprint.selection_panel_expanded = false; - } } Command::ToggleSelectionPanel => { let blueprint = self.blueprint_mut(egui_ctx); blueprint.selection_panel_expanded ^= true; - - // Only one of blueprint or selection panel can be open at a time on mobile: - if is_narrow_screen && blueprint.selection_panel_expanded { - blueprint.blueprint_panel_expanded = false; - } } Command::ToggleTimePanel => { self.blueprint_mut(egui_ctx).time_panel_expanded ^= true; @@ -425,6 +458,15 @@ impl eframe::App for App { [0.0; 4] // transparent so we can get rounded corners when doing [`re_ui::CUSTOM_WINDOW_DECORATIONS`] } + #[cfg(not(target_arch = "wasm32"))] + fn on_close_event(&mut self) -> bool { + self.state.depthai_state.shutdown(); + if let Some(backend_handle) = &mut self.backend_handle { + backend_handle.kill(); + } + true + } + fn save(&mut self, storage: &mut dyn eframe::Storage) { if self.startup_options.persist_state { eframe::set_value(storage, eframe::APP_KEY, &self.state); @@ -433,6 +475,27 @@ impl eframe::App for App { fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame) { let frame_start = Instant::now(); + self.state.depthai_state.update(); // Always update depthai state + #[cfg(not(target_arch = "wasm32"))] + { + match &mut self.backend_handle { + Some(handle) => match handle.try_wait() { + Ok(status) => { + if status.is_some() { + handle.kill(); + re_log::debug!("Backend process has exited, restarting!"); + self.backend_handle = App::spawn_backend(); + } + } + Err(_) => {} + }, + None => self.backend_handle = App::spawn_backend(), + }; + } + + if self.backend_handle.is_none() { + self.backend_handle = App::spawn_backend(); + }; if self.startup_options.memory_limit.limit.is_none() { // we only warn about high memory usage if the user hasn't specified a limit @@ -444,8 +507,14 @@ impl eframe::App for App { } if self.shutdown.load(std::sync::atomic::Ordering::Relaxed) { + self.state.depthai_state.shutdown(); #[cfg(not(target_arch = "wasm32"))] - frame.close(); + { + if let Some(backend_handle) = &mut self.backend_handle { + backend_handle.kill(); + } + frame.close(); + } return; } @@ -548,18 +617,14 @@ impl eframe::App for App { .unwrap(); render_ctx.begin_frame(); - if log_db.is_default() { - wait_screen_ui(ui, &self.rx); - } else { - self.state.show( - ui, - render_ctx, - log_db, - &self.re_ui, - &self.component_ui_registry, - self.rx.source(), - ); - } + self.state.show( + ui, + render_ctx, + log_db, + &self.re_ui, + &self.component_ui_registry, + self.rx.source(), + ); render_ctx.before_submit(); } @@ -583,6 +648,7 @@ impl eframe::App for App { egui_ctx.input(|i| i.time), frame_start.elapsed().as_secs_f32(), ); + egui_ctx.request_repaint(); // Force repaint even when out of focus } } @@ -943,14 +1009,19 @@ struct AppState { /// Configuration for the current recording (found in [`LogDb`]). recording_configs: IntMap, + #[serde(skip)] // Quick fix for subscriptions setting, just don't remembet space views blueprints: HashMap, /// Which view panel is currently being shown panel_selection: PanelSelection, selection_panel: crate::selection_panel::SelectionPanel, + time_panel: crate::time_panel::TimePanel, + selected_device: depthai::DeviceId, + depthai_state: depthai::State, + #[cfg(not(target_arch = "wasm32"))] #[serde(skip)] profiler: crate::Profiler, @@ -976,8 +1047,10 @@ impl AppState { recording_configs, panel_selection, blueprints, - selection_panel, - time_panel, + selection_panel: _, + time_panel: _, + selected_device: _, + depthai_state, #[cfg(not(target_arch = "wasm32"))] profiler: _, } = self; @@ -998,13 +1071,11 @@ impl AppState { rec_cfg, re_ui, render_ctx, + depthai_state, }; - let blueprint = blueprints - .entry(selected_app_id.clone()) - .or_insert_with(|| Blueprint::new(ui.ctx())); - time_panel.show_panel(&mut ctx, blueprint, ui); - selection_panel.show_panel(&mut ctx, ui, blueprint); + // Hide time panel for now, reuse for recordings in the future + // time_panel.show_panel(&mut ctx, blueprint, ui); let central_panel_frame = egui::Frame { fill: ui.style().visuals.panel_fill, @@ -1345,7 +1416,7 @@ fn frame_time_label_ui(ui: &mut egui::Ui, app: &mut App) { // we use monospace so the width doesn't fluctuate as the numbers change. let text = format!("{ms:.1} ms"); ui.label(egui::RichText::new(text).monospace().color(color)) - .on_hover_text("CPU time used by Rerun Viewer each frame. Lower is better."); + .on_hover_text("CPU time used by Depthai Viewer each frame. Lower is better."); } } @@ -1360,7 +1431,7 @@ fn memory_use_label_ui(ui: &mut egui::Ui, gpu_resource_stats: &WgpuResourcePoolS .color(ui.visuals().weak_text_color()), ) .on_hover_text(format!( - "Rerun Viewer is using {} of RAM in {} separate allocations,\n\ + "Depthai Viewer is using {} of RAM in {} separate allocations,\n\ plus {} of GPU memory in {} textures and {} buffers.", bytes_used_text, format_number(count.count), @@ -1395,7 +1466,7 @@ fn input_latency_label_ui(ui: &mut egui::Ui, app: &mut App) { format_number(queue_len), ); let hover_text = - "When more data is arriving over network than the Rerun Viewer can index, a queue starts building up, leading to latency and increased RAM use.\n\ + "When more data is arriving over network than the Depthai Viewer can index, a queue starts building up, leading to latency and increased RAM use.\n\ This latency does NOT include network latency."; if latency_sec < app.state.app_options.warn_latency { diff --git a/crates/re_viewer/src/depthai/api.rs b/crates/re_viewer/src/depthai/api.rs new file mode 100644 index 000000000000..cea17653b1a8 --- /dev/null +++ b/crates/re_viewer/src/depthai/api.rs @@ -0,0 +1,81 @@ +use super::depthai; +use super::ws::{BackWsMessage as WsMessage, WebSocket, WsMessageData, WsMessageType}; + +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct ApiError { + pub detail: String, +} + +impl Default for ApiError { + fn default() -> Self { + Self { + detail: "ApiError".to_string(), + } + } +} + +#[derive(Default)] +pub struct BackendCommChannel { + pub ws: WebSocket, +} + +impl BackendCommChannel { + pub fn shutdown(&mut self) { + self.ws.shutdown(); + } + + pub fn set_subscriptions(&mut self, subscriptions: &Vec) { + self.ws.send( + serde_json::to_string( + &(WsMessage { + kind: WsMessageType::Subscriptions, + data: WsMessageData::Subscriptions(subscriptions.clone()), + }), + ) + .unwrap(), + ); + } + + pub fn set_pipeline(&mut self, config: &depthai::DeviceConfig, runtime_only: bool) { + self.ws.send( + serde_json::to_string( + &(WsMessage { + kind: WsMessageType::Pipeline, + data: WsMessageData::Pipeline((config.clone(), runtime_only)), + }), + ) + .unwrap(), + ); + } + + pub fn receive(&mut self) -> Option { + self.ws.receive() + } + + pub fn get_devices(&mut self) { + self.ws.send( + serde_json::to_string( + &(WsMessage { + kind: WsMessageType::Devices, + data: WsMessageData::Devices(Vec::new()), + }), + ) + .unwrap(), + ); + } + + pub fn set_device(&mut self, device_id: depthai::DeviceId) { + self.ws.send( + serde_json::to_string( + &(WsMessage { + kind: WsMessageType::Device, + data: WsMessageData::Device(depthai::Device { + id: device_id, + ..Default::default() + }), + }), + ) + .unwrap(), + ); + } +} diff --git a/crates/re_viewer/src/depthai/depthai.rs b/crates/re_viewer/src/depthai/depthai.rs new file mode 100644 index 000000000000..8323ffc873e1 --- /dev/null +++ b/crates/re_viewer/src/depthai/depthai.rs @@ -0,0 +1,822 @@ +use itertools::Itertools; +use re_log_types::EntityPath; + +use super::api::BackendCommChannel; +use super::ws::WsMessageData; +use instant::Instant; +use std::fmt; + +use strum::EnumIter; +use strum::IntoEnumIterator; + +#[derive(serde::Deserialize, serde::Serialize, fmt::Debug, PartialEq, Clone, Copy, EnumIter)] +#[allow(non_camel_case_types)] +pub enum ColorCameraResolution { + THE_720_P, + THE_800_P, + THE_1440X1080, + THE_1080_P, + THE_1200_P, + THE_5_MP, + THE_4_K, + THE_12_MP, + THE_4000X3000, + THE_13_MP, + THE_48_MP, +} + +#[derive(serde::Deserialize, serde::Serialize, fmt::Debug, PartialEq, Clone, Copy, EnumIter)] +#[allow(non_camel_case_types)] +pub enum MonoCameraResolution { + THE_400_P, + THE_480_P, + THE_720_P, + THE_800_P, + THE_1200_P, +} + +// fmt::Display is used in UI while fmt::Debug is used with the depthai backend api +impl fmt::Display for ColorCameraResolution { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::THE_1080_P => write!(f, "1080p"), + Self::THE_4_K => write!(f, "4k"), + Self::THE_720_P => write!(f, "720p"), + Self::THE_800_P => write!(f, "800p"), + Self::THE_1200_P => write!(f, "1200p"), + Self::THE_5_MP => write!(f, "5MP"), + Self::THE_12_MP => write!(f, "12MP"), + Self::THE_13_MP => write!(f, "13MP"), + Self::THE_4000X3000 => write!(f, "4000x3000"), + Self::THE_48_MP => write!(f, "48MP"), + Self::THE_1440X1080 => write!(f, "1440x1080"), + } + } +} + +impl fmt::Display for MonoCameraResolution { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::THE_400_P => write!(f, "400p"), + Self::THE_480_P => write!(f, "480p"), + Self::THE_720_P => write!(f, "720p"), + Self::THE_800_P => write!(f, "800p"), + Self::THE_1200_P => write!(f, "1200p"), + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq)] +pub struct ColorCameraConfig { + pub fps: u8, + pub resolution: ColorCameraResolution, + #[serde(rename = "xout_video")] + pub stream_enabled: bool, +} + +impl Default for ColorCameraConfig { + fn default() -> Self { + Self { + fps: 30, + resolution: ColorCameraResolution::THE_1080_P, + stream_enabled: true, + } + } +} + +impl fmt::Debug for ColorCameraConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Color camera config: fps: {}, resolution: {:?}", + self.fps, self.resolution + ) + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, EnumIter, Debug)] +#[allow(non_camel_case_types)] +pub enum CameraBoardSocket { + AUTO, + RGB, + LEFT, + RIGHT, + CENTER, + CAM_A, + CAM_B, + CAM_C, + CAM_D, + CAM_E, + CAM_F, + CAM_G, + CAM_H, +} + +impl Default for CameraBoardSocket { + fn default() -> Self { + Self::AUTO + } +} + +impl CameraBoardSocket { + pub fn depth_align_options() -> Vec { + return vec![Self::RGB, Self::LEFT, Self::RIGHT]; + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq)] +pub struct MonoCameraConfig { + pub fps: u8, + pub resolution: MonoCameraResolution, + pub board_socket: CameraBoardSocket, + #[serde(rename = "xout")] + pub stream_enabled: bool, +} + +impl Default for MonoCameraConfig { + fn default() -> Self { + Self { + fps: 30, + resolution: MonoCameraResolution::THE_800_P, + board_socket: CameraBoardSocket::AUTO, + stream_enabled: false, + } + } +} + +impl fmt::Debug for MonoCameraConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Mono camera config: fps: {}, resolution: {:?}", + self.fps, self.resolution + ) + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq)] +#[allow(non_camel_case_types)] +pub enum DepthProfilePreset { + HIGH_DENSITY, + HIGH_ACCURACY, +} + +impl Default for DepthProfilePreset { + fn default() -> Self { + Self::HIGH_DENSITY + } +} + +impl fmt::Display for DepthProfilePreset { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::HIGH_DENSITY => write!(f, "High Density"), + Self::HIGH_ACCURACY => write!(f, "High Accuracy"), + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Debug, EnumIter)] +#[allow(non_camel_case_types)] +pub enum DepthMedianFilter { + MEDIAN_OFF, + KERNEL_3x3, + KERNEL_5x5, + KERNEL_7x7, +} + +impl Default for DepthMedianFilter { + fn default() -> Self { + Self::KERNEL_7x7 + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Debug)] +pub struct DepthConfig { + pub median: DepthMedianFilter, + pub lr_check: bool, + pub lrc_threshold: u64, + pub extended_disparity: bool, + pub subpixel_disparity: bool, + pub sigma: i64, + pub confidence: i64, + pub align: CameraBoardSocket, +} + +impl Default for DepthConfig { + fn default() -> Self { + Self { + median: DepthMedianFilter::default(), + lr_check: true, + lrc_threshold: 5, + extended_disparity: false, + subpixel_disparity: true, + sigma: 0, + confidence: 230, + align: CameraBoardSocket::RGB, + } + } +} + +impl DepthConfig { + pub fn default_as_option() -> Option { + Some(Self::default()) + } + + pub fn only_runtime_configs_differ(&self, other: &DepthConfig) -> bool { + self.lr_check == other.lr_check + && self.align == other.align + && self.extended_disparity == other.extended_disparity + && self.subpixel_disparity == other.subpixel_disparity + && self != other + } +} + +#[derive(Default, serde::Deserialize, serde::Serialize, Clone)] +pub struct DeviceConfig { + pub color_camera: ColorCameraConfig, + pub left_camera: MonoCameraConfig, + pub right_camera: MonoCameraConfig, + #[serde(default = "bool_true")] + pub depth_enabled: bool, // Much easier to have an explicit bool for checkbox + #[serde(default = "DepthConfig::default_as_option")] + pub depth: Option, + pub ai_model: AiModel, +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[allow(non_camel_case_types)] +pub enum CameraSensorType { + COLOR, + MONO, + THERMAL, + TOF, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct CameraSensorConfig { + height: i64, + #[serde(rename = "maxFps")] + max_fps: i64, + #[serde(rename = "minFps")] + min_fps: i64, + #[serde(rename = "type")] + kind: CameraSensorType, + width: i64, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct CameraFeatures { + configs: Vec, + #[serde(rename = "hasAutofocus")] + has_autofocus: bool, + height: i64, + name: String, + orientation: CameraImageOrientation, + #[serde(rename = "sensorName")] + sensor_name: String, + socket: CameraBoardSocket, + #[serde(rename = "supportedTypes")] + supported_types: Vec, + width: i64, +} + +#[derive(serde::Serialize, serde::Deserialize)] +#[allow(non_camel_case_types)] +pub enum CameraImageOrientation { + AUTO, + HORIZONTAL_MIRROR, + NORMAL, + ROTATE_180_DEG, + VERTICAL_FLIP, +} + +impl PartialEq for DeviceConfig { + fn eq(&self, other: &Self) -> bool { + let depth_eq = match (&self.depth, &other.depth) { + (Some(a), Some(b)) => a == b, + _ => true, // If one is None, it's only different if depth_enabled is different + }; + self.color_camera == other.color_camera + && self.left_camera == other.left_camera + && self.right_camera == other.right_camera + && depth_eq + && self.depth_enabled == other.depth_enabled + && self.ai_model == other.ai_model + } +} + +#[inline] +const fn bool_true() -> bool { + true +} + +#[derive(Default, serde::Deserialize, serde::Serialize)] +pub struct DeviceConfigState { + pub config: DeviceConfig, + #[serde(skip)] + pub update_in_progress: bool, +} + +impl fmt::Debug for DeviceConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Device config: {:?} {:?} {:?}, depth: {:?}, ai_model: {:?}, depth_enabled: {:?}", + self.color_camera, + self.left_camera, + self.right_camera, + self.depth, + self.ai_model, + self.depth_enabled + ) + } +} + +#[derive(serde::Deserialize)] +struct PipelineResponse { + message: String, +} + +impl Default for PipelineResponse { + fn default() -> Self { + Self { + message: "Pipeline not started".to_string(), + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, fmt::Debug)] +pub enum ErrorAction { + None, + FullReset, +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq, fmt::Debug)] +pub struct Error { + pub action: ErrorAction, + pub message: String, +} + +impl Default for Error { + fn default() -> Self { + Self { + action: ErrorAction::None, + message: String::from("Invalid message"), + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, PartialEq, fmt::Debug)] +pub struct Device { + pub id: DeviceId, + pub supported_color_resolutions: Vec, + pub supported_left_mono_resolutions: Vec, + pub supported_right_mono_resolutions: Vec, +} + +// Sensible default for when no device is connected +impl Default for Device { + fn default() -> Self { + Self { + id: DeviceId::default(), + supported_color_resolutions: vec![ColorCameraResolution::THE_1080_P], + supported_left_mono_resolutions: vec![MonoCameraResolution::THE_400_P], + supported_right_mono_resolutions: vec![MonoCameraResolution::THE_400_P], + } + } +} + +#[derive(serde::Deserialize, serde::Serialize, Clone, fmt::Debug)] +pub struct AiModel { + pub path: String, + pub display_name: String, +} + +impl Default for AiModel { + fn default() -> Self { + Self { + path: String::new(), + display_name: String::from("No model selected"), + } + } +} + +impl PartialEq for AiModel { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct State { + #[serde(skip)] + devices_available: Option>, + #[serde(skip)] + pub selected_device: Device, + pub applied_device_config: DeviceConfigState, + pub modified_device_config: DeviceConfigState, + #[serde(skip, default = "all_subscriptions")] + // Want to resubscribe to api when app is reloaded + pub subscriptions: Vec, // Shown in ui + #[serde(skip)] + setting_subscriptions: bool, + #[serde(skip)] + pub backend_comms: BackendCommChannel, + #[serde(skip)] + poll_instant: Option, + #[serde(default = "default_neural_networks")] + pub neural_networks: Vec, +} + +#[inline] +fn all_subscriptions() -> Vec { + ChannelId::iter().collect_vec() +} + +#[inline] +fn default_neural_networks() -> Vec { + vec![ + AiModel::default(), + AiModel { + path: String::from("yolov8n_coco_640x352"), + display_name: String::from("Yolo V8"), + }, + AiModel { + path: String::from("mobilenet-ssd"), + display_name: String::from("MobileNet SSD"), + }, + AiModel { + path: String::from("face-detection-retail-0004"), + display_name: String::from("Face Detection"), + }, + AiModel { + path: String::from("age-gender-recognition-retail-0013"), + display_name: String::from("Age gender recognition"), + }, + ] +} + +impl Default for State { + fn default() -> Self { + Self { + devices_available: None, + selected_device: Device::default(), + applied_device_config: DeviceConfigState::default(), + modified_device_config: DeviceConfigState::default(), + subscriptions: ChannelId::iter().collect(), + setting_subscriptions: false, + backend_comms: BackendCommChannel::default(), + poll_instant: Some(Instant::now()), // No default for Instant + neural_networks: default_neural_networks(), + } + } +} + +#[repr(u8)] +#[derive( + serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq, fmt::Debug, Hash, EnumIter, +)] +pub enum ChannelId { + ColorImage, + LeftMono, + RightMono, + DepthImage, + PinholeCamera, + ImuData, +} + +/// Entity paths for depthai-viewer space views +/// !---- These have to match with EntityPath in rerun_py/rerun_sdk/depthai_viewer_backend/sdk_callbacks.py ----! +pub mod entity_paths { + use lazy_static::lazy_static; + use re_log_types::EntityPath; + + lazy_static! { + pub static ref RGB_PINHOLE_CAMERA: EntityPath = EntityPath::from("color/camera/rgb"); + pub static ref LEFT_PINHOLE_CAMERA: EntityPath = EntityPath::from("mono/camera/left_mono"); + pub static ref LEFT_CAMERA_IMAGE: EntityPath = + EntityPath::from("mono/camera/left_mono/Left mono"); + pub static ref RIGHT_PINHOLE_CAMERA: EntityPath = + EntityPath::from("mono/camera/right_mono"); + pub static ref RIGHT_CAMERA_IMAGE: EntityPath = + EntityPath::from("mono/camera/right_mono/Right mono"); + pub static ref RGB_CAMERA_IMAGE: EntityPath = + EntityPath::from("color/camera/rgb/Color camera"); + pub static ref DETECTIONS: EntityPath = EntityPath::from("color/camera/rgb/Detections"); + pub static ref DETECTION: EntityPath = EntityPath::from("color/camera/rgb/Detection"); + pub static ref RGB_CAMERA_TRANSFORM: EntityPath = EntityPath::from("color/camera"); + pub static ref MONO_CAMERA_TRANSFORM: EntityPath = EntityPath::from("mono/camera"); + + // --- These are extra for the depthai-viewer --- + pub static ref COLOR_CAM_3D: EntityPath = EntityPath::from("color"); + pub static ref MONO_CAM_3D: EntityPath = EntityPath::from("mono"); + pub static ref DEPTH_RGB: EntityPath = EntityPath::from("color/camera/rgb/Depth"); + pub static ref DEPTH_LEFT_MONO: EntityPath = + EntityPath::from("mono/camera/left_mono/Depth"); + pub static ref DEPTH_RIGHT_MONO: EntityPath = + EntityPath::from("mono/camera/right_mono/Depth"); + } +} + +impl State { + pub fn only_runtime_configs_changed( + old_config: &DeviceConfig, + new_config: &DeviceConfig, + ) -> bool { + let any_runtime_conf_changed = old_config.depth.is_some() + && new_config.depth.is_some() + && old_config + .depth + .unwrap() + .only_runtime_configs_differ(&new_config.depth.unwrap()); // || others to be added + any_runtime_conf_changed + && old_config.left_camera == new_config.left_camera + && old_config.right_camera == new_config.right_camera + && old_config.color_camera == new_config.color_camera + && old_config.ai_model == new_config.ai_model + } + + /// Should the space view be added to the UI based on the new subscriptions (a subscription change occurred) + fn create_entity_paths_from_subscriptions( + &mut self, + new_subscriptions: &Vec, + ) -> Vec { + let mut new_entity_paths = Vec::new(); + for channel in new_subscriptions.iter() { + match channel { + ChannelId::ColorImage => { + new_entity_paths.push(EntityPath::from("color/camera/rgb/Color camera")); + } + ChannelId::LeftMono => { + new_entity_paths.push(EntityPath::from("mono/camera/left_mono/Left mono")); + } + ChannelId::RightMono => { + new_entity_paths.push(EntityPath::from("mono/camera/right_mono/Right mono")); + } + ChannelId::DepthImage => { + new_entity_paths.push(EntityPath::from("color/camera/rgb/Depth")); + new_entity_paths.push(EntityPath::from("mono/camera/right_mono/Depth")); + new_entity_paths.push(EntityPath::from("mono/camera/left_mono/Depth")); + } + _ => {} + } + } + new_entity_paths + } + + /// Get the entities that should be removed based on UI (e.g. if depth is disabled, remove the depth image) + pub fn get_entities_to_remove(&mut self) -> Vec { + let mut remove_entities = Vec::new(); + if self.applied_device_config.config.depth.is_none() { + remove_entities.push(entity_paths::DEPTH_LEFT_MONO.clone()); + remove_entities.push(entity_paths::DEPTH_RIGHT_MONO.clone()); + remove_entities.push(entity_paths::DEPTH_RGB.clone()); + } + if !self + .applied_device_config + .config + .right_camera + .stream_enabled + { + remove_entities.push(entity_paths::RIGHT_PINHOLE_CAMERA.clone()); + remove_entities.push(entity_paths::RIGHT_CAMERA_IMAGE.clone()); + // Both cams disabled -> remove 3D view + if !self.applied_device_config.config.left_camera.stream_enabled { + remove_entities.push(entity_paths::MONO_CAM_3D.clone()); + remove_entities.push(entity_paths::MONO_CAMERA_TRANSFORM.clone()); + } + } + if !self.applied_device_config.config.left_camera.stream_enabled { + remove_entities.push(entity_paths::LEFT_PINHOLE_CAMERA.clone()); + remove_entities.push(entity_paths::LEFT_CAMERA_IMAGE.clone()); + } + if !self + .applied_device_config + .config + .color_camera + .stream_enabled + { + remove_entities.push(entity_paths::RGB_PINHOLE_CAMERA.clone()); + remove_entities.push(entity_paths::RGB_CAMERA_IMAGE.clone()); + remove_entities.push(entity_paths::COLOR_CAM_3D.clone()); + remove_entities.push(entity_paths::RGB_CAMERA_TRANSFORM.clone()); + } + if self.applied_device_config.config.ai_model.path.is_empty() { + remove_entities.push(entity_paths::DETECTIONS.clone()); + remove_entities.push(entity_paths::DETECTION.clone()); + } + remove_entities + } + + /// DEPRECATED! Just keep it in the code as a reminder of how to do it + /// Probably won't be needed when we make the move away from log_db in the future, will likely be implemented (much) differently. + /// Until then we just loose a bit of performance if a user isn't viewing all of the channels + /// Set subscriptions based on the visible UI + // pub fn set_subscriptions_from_space_views(&mut self, visible_space_views: Vec<&SpaceView>) { + // // If any bool in the vec is true, the channel is currently visible in the ui somewhere + // let mut visibilities = HashMap::>::from([ + // (ChannelId::ColorImage, Vec::new()), + // (ChannelId::LeftMono, Vec::new()), + // (ChannelId::RightMono, Vec::new()), + // (ChannelId::DepthImage, Vec::new()), + // ]); + // // Fill in visibilities + // for space_view in visible_space_views.iter() { + // let property_map = space_view.data_blueprint.data_blueprints_projected(); + // for entity_path in space_view.data_blueprint.entity_paths().iter() { + // if let Some(channel_id) = DEPTHAI_ENTITY_HASHES.get(&entity_path.hash()) { + // if let Some(visibility) = visibilities.get_mut(channel_id) { + // visibility.push(property_map.get(entity_path).visible); + // } + // } + // } + // } + + // // First add subscriptions that don't have explicit enable disable buttons in the ui + // let mut possible_subscriptions = Vec::::from([ChannelId::ImuData]); + // // Now add subscriptions that should be possible in terms of ui + // if self.applied_device_config.config.depth_enabled { + // possible_subscriptions.push(ChannelId::DepthImage); + // } + // if self + // .applied_device_config + // .config + // .color_camera + // .stream_enabled + // { + // possible_subscriptions.push(ChannelId::ColorImage); + // } + + // if self.applied_device_config.config.left_camera.stream_enabled { + // possible_subscriptions.push(ChannelId::LeftMono); + // } + // if self + // .applied_device_config + // .config + // .right_camera + // .stream_enabled + // { + // possible_subscriptions.push(ChannelId::RightMono); + // } + + // // Filter visibilities, include those that are currently visible and also possible (example pointcloud enabled == pointcloud possible) + // let mut subscriptions = visibilities + // .iter() + // .filter_map(|(channel, vis)| { + // if vis.iter().any(|x| *x) { + // if possible_subscriptions.contains(channel) { + // return Some(*channel); + // } + // } + // None + // }) + // .collect_vec(); + + // // Keep subscriptions that should be visible but have not yet been sent by the backend + // for channel in ChannelId::iter() { + // if !subscriptions.contains(&channel) + // && possible_subscriptions.contains(&channel) + // && self.subscriptions.contains(&channel) + // { + // subscriptions.push(channel); + // } + // } + + // self.set_subscriptions(&subscriptions); + // } + + pub fn set_subscriptions(&mut self, subscriptions: &Vec) { + if self.subscriptions.len() == subscriptions.len() + && self + .subscriptions + .iter() + .all(|channel_id| subscriptions.contains(channel_id)) + { + return; + } + self.backend_comms.set_subscriptions(subscriptions); + self.subscriptions = subscriptions.clone(); + } + + pub fn get_devices(&mut self) -> Vec { + // Return stored available devices or fetch them from the api (they get fetched every 30s via poller) + if let Some(devices) = self.devices_available.clone() { + return devices; + } + Vec::new() + } + + pub fn shutdown(&mut self) { + self.backend_comms.shutdown(); + } + + pub fn update(&mut self) { + if let Some(ws_message) = self.backend_comms.receive() { + re_log::debug!("Received message: {:?}", ws_message); + match ws_message.data { + WsMessageData::Subscriptions(subscriptions) => { + re_log::debug!("Setting subscriptions"); + self.subscriptions = subscriptions; + } + WsMessageData::Devices(devices) => { + re_log::debug!("Setting devices..."); + self.devices_available = Some(devices); + } + WsMessageData::Pipeline((config, _)) => { + let mut subs = self.subscriptions.clone(); + if config.depth.is_some() { + subs.push(ChannelId::DepthImage); + } + if config.color_camera.stream_enabled { + subs.push(ChannelId::ColorImage); + } + if config.left_camera.stream_enabled { + subs.push(ChannelId::LeftMono); + } + if config.right_camera.stream_enabled { + subs.push(ChannelId::RightMono); + } + self.applied_device_config.config = config.clone(); + self.modified_device_config.config = config; + self.applied_device_config.config.depth_enabled = + self.applied_device_config.config.depth.is_some(); + self.modified_device_config.config.depth_enabled = + self.modified_device_config.config.depth.is_some(); + self.set_subscriptions(&subs); + self.applied_device_config.update_in_progress = false; + } + WsMessageData::Device(device) => { + re_log::debug!("Setting device: {device:?}"); + self.selected_device = device; + self.backend_comms.set_subscriptions(&self.subscriptions); + self.backend_comms + .set_pipeline(&self.applied_device_config.config, false); + self.applied_device_config.update_in_progress = true; + } + WsMessageData::Error(error) => { + re_log::error!("Error: {:}", error.message); + self.applied_device_config.update_in_progress = false; + match error.action { + ErrorAction::None => (), + ErrorAction::FullReset => { + self.set_device(String::new()); + } + } + } + } + } + + if let Some(poll_instant) = self.poll_instant { + if poll_instant.elapsed().as_secs() < 2 { + return; + } + if self.selected_device.id.is_empty() { + self.backend_comms.get_devices(); + } + self.poll_instant = Some(Instant::now()); + } else { + self.poll_instant = Some(Instant::now()); + } + } + + pub fn set_device(&mut self, device_id: DeviceId) { + if self.selected_device.id == device_id { + return; + } + re_log::debug!("Setting device: {:?}", device_id); + self.backend_comms.set_device(device_id); + self.applied_device_config.update_in_progress = true; + } + + pub fn set_device_config(&mut self, config: &mut DeviceConfig, runtime_only: bool) { + // Don't try to set pipeline in ws not connected + if !self + .backend_comms + .ws + .connected + .load(std::sync::atomic::Ordering::SeqCst) + { + return; + } + config.left_camera.board_socket = CameraBoardSocket::LEFT; + config.right_camera.board_socket = CameraBoardSocket::RIGHT; + if !config.depth_enabled { + config.depth = None; + } + if self.selected_device.id.is_empty() { + self.applied_device_config.config = config.clone(); + return; + } + self.backend_comms.set_pipeline(config, runtime_only); + if runtime_only { + self.applied_device_config.config = config.clone(); + self.applied_device_config.update_in_progress = false; + } else { + re_log::info!("Creating pipeline..."); + self.applied_device_config.update_in_progress = true; + } + } +} + +pub type DeviceId = String; diff --git a/crates/re_viewer/src/depthai/mod.rs b/crates/re_viewer/src/depthai/mod.rs new file mode 100644 index 000000000000..e20c7a80a1dc --- /dev/null +++ b/crates/re_viewer/src/depthai/mod.rs @@ -0,0 +1,3 @@ +mod api; +pub mod depthai; +mod ws; diff --git a/crates/re_viewer/src/depthai/ws.rs b/crates/re_viewer/src/depthai/ws.rs new file mode 100644 index 000000000000..c6570deba36e --- /dev/null +++ b/crates/re_viewer/src/depthai/ws.rs @@ -0,0 +1,247 @@ +use crossbeam_channel; +use ewebsock::{WsEvent, WsMessage}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::ops::ControlFlow; +use std::process::exit; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use super::depthai; + +async fn spawn_ws_client( + recv_tx: crossbeam_channel::Sender, + send_rx: crossbeam_channel::Receiver, + shutdown: Arc, + connected: Arc, +) { + let (error_tx, error_rx) = crossbeam_channel::unbounded(); + // Retry connection until successful + loop { + let recv_tx = recv_tx.clone(); + let error_tx = error_tx.clone(); + let connected = connected.clone(); + if let Ok(sender) = ewebsock::ws_connect( + String::from("ws://localhost:9001"), + Box::new(move |event| { + match event { + WsEvent::Opened => { + re_log::info!("Websocket opened"); + connected.store(true, std::sync::atomic::Ordering::SeqCst); + ControlFlow::Continue(()) + } + WsEvent::Message(message) => { + // re_log::debug!("Websocket message"); + recv_tx.send(message); + ControlFlow::Continue(()) + } + WsEvent::Error(e) => { + // re_log::info!("Websocket Error: {:?}", e); + connected.store(false, std::sync::atomic::Ordering::SeqCst); + error_tx.send(e); + ControlFlow::Break(()) + } + WsEvent::Closed => { + // re_log::info!("Websocket Closed"); + error_tx.send(String::from("Websocket Closed")); + ControlFlow::Break(()) + } + } + }), + ) + .as_mut() + { + while error_rx.is_empty() { + if shutdown.load(std::sync::atomic::Ordering::SeqCst) { + re_log::debug!("Shutting down websocket client"); + exit(0); + } + if let Ok(message) = send_rx.recv_timeout(std::time::Duration::from_millis(100)) { + re_log::debug!("Sending message: {:?}", message); + sender.send(message); + } + } + for error in error_rx.try_iter() { + re_log::debug!("Websocket error: {:}", error); + } + } else { + re_log::error!("Coudln't create websocket"); + } + if shutdown.load(std::sync::atomic::Ordering::SeqCst) { + re_log::debug!("Shutting down websocket client"); + exit(0); + } + std::thread::sleep(std::time::Duration::from_secs(1)); + } +} + +type RuntimeOnly = bool; + +#[derive(Serialize, Deserialize, fmt::Debug)] +pub enum WsMessageData { + Subscriptions(Vec), + Devices(Vec), + Device(depthai::Device), + Pipeline((depthai::DeviceConfig, RuntimeOnly)), + Error(depthai::Error), +} + +#[derive(Deserialize, Serialize, fmt::Debug)] +pub enum WsMessageType { + Subscriptions, + Devices, + Device, + Pipeline, + Error, +} + +impl Default for WsMessageType { + fn default() -> Self { + Self::Error + } +} + +// TODO(filip): Perhaps add a "message" field to all messages to display toasts +#[derive(Serialize, fmt::Debug)] +pub struct BackWsMessage { + #[serde(rename = "type")] + pub kind: WsMessageType, + pub data: WsMessageData, +} + +impl<'de> Deserialize<'de> for BackWsMessage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + pub struct Message { + #[serde(rename = "type")] + pub kind: WsMessageType, + pub data: serde_json::Value, + } + + let message = Message::deserialize(deserializer)?; + let data = match message.kind { + WsMessageType::Subscriptions => WsMessageData::Subscriptions( + serde_json::from_value(message.data).unwrap_or_default(), + ), + WsMessageType::Devices => { + WsMessageData::Devices(serde_json::from_value(message.data).unwrap_or_default()) + } + WsMessageType::Device => { + WsMessageData::Device(serde_json::from_value(message.data).unwrap_or_default()) + } + WsMessageType::Pipeline => { + WsMessageData::Pipeline(serde_json::from_value(message.data).unwrap()) + // TODO(filip) change to unwrap_or_default when pipeline config api is more stable + } + WsMessageType::Error => { + WsMessageData::Error(serde_json::from_value(message.data).unwrap_or_default()) + } + }; + Ok(Self { + kind: message.kind, + data, + }) + } +} + +impl Default for BackWsMessage { + fn default() -> Self { + Self { + kind: WsMessageType::Error.into(), + data: WsMessageData::Error(depthai::Error::default()), + } + } +} + +pub struct WebSocket { + receiver: crossbeam_channel::Receiver, + sender: crossbeam_channel::Sender, + shutdown: Arc, + task: tokio::task::JoinHandle<()>, + pub connected: Arc, +} + +impl Default for WebSocket { + fn default() -> Self { + Self::new() + } +} + +impl WebSocket { + pub fn new() -> Self { + re_log::debug!("Creating websocket client"); + let (recv_tx, recv_rx) = crossbeam_channel::unbounded(); + let (send_tx, send_rx) = crossbeam_channel::unbounded(); + let shutdown = Arc::new(AtomicBool::new(false)); + let shutdown_clone = shutdown.clone(); + let connected = Arc::new(AtomicBool::new(false)); + let connected_clone = connected.clone(); + let task; + if let Ok(handle) = tokio::runtime::Handle::try_current() { + re_log::debug!("Using current tokio runtime"); + task = handle.spawn(spawn_ws_client( + recv_tx, + send_rx, + shutdown_clone, + connected_clone, + )); + } else { + re_log::debug!("Creating new tokio runtime"); + task = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .spawn(spawn_ws_client( + recv_tx, + send_rx, + shutdown_clone, + connected_clone, + )); + } + Self { + receiver: recv_rx, + sender: send_tx, + shutdown, + task, + connected, + } + } + + pub fn shutdown(&mut self) { + self.shutdown + .store(true, std::sync::atomic::Ordering::SeqCst); + } + + pub fn receive(&self) -> Option { + if let Ok(message) = self.receiver.try_recv() { + match message { + WsMessage::Text(text) => { + re_log::debug!("Received: {:?}", text); + match serde_json::from_str::(&text.as_str()) { + Ok(back_message) => { + return Some(back_message); + } + Err(err) => { + re_log::error!("Error: {:}", err); + return None; + } + } + } + _ => { + return None; + } + } + } + None + } + + pub fn send(&self, message: String) { + self.sender.send(WsMessage::Text(message)); + // TODO(filip): This is a hotfix for the websocket not sending the message + // This doesn't actually send any message, but it makes the websocket actually send the message previous msg + // It has to be something related to tokio::spawn, because it works fine when just running in the current thread + self.sender.send(WsMessage::Text("".to_string())); + } +} diff --git a/crates/re_viewer/src/gpu_bridge/mod.rs b/crates/re_viewer/src/gpu_bridge/mod.rs index 176f28149dee..9cf35e1d4c01 100644 --- a/crates/re_viewer/src/gpu_bridge/mod.rs +++ b/crates/re_viewer/src/gpu_bridge/mod.rs @@ -9,7 +9,9 @@ use egui::mutex::Mutex; use re_renderer::{ renderer::{ColormappedTexture, RectangleOptions}, - resource_managers::{GpuTexture2D, Texture2DCreationDesc}, + resource_managers::{ + GpuTexture2D, Texture2DCreationDesc, TextureCreationError, TextureManager2DError, + }, RenderContext, ViewBuilder, }; @@ -52,12 +54,12 @@ pub fn viewport_resolution_in_pixels(clip_rect: egui::Rect, pixels_from_point: f [resolution.x as u32, resolution.y as u32] } -pub fn try_get_or_create_texture<'a, Err>( +pub fn try_get_or_create_texture<'a, Err: std::fmt::Display>( render_ctx: &mut RenderContext, texture_key: u64, try_create_texture_desc: impl FnOnce() -> Result, Err>, -) -> Result { - render_ctx.texture_manager_2d.get_or_create_with( +) -> Result> { + render_ctx.texture_manager_2d.get_or_try_create_with( texture_key, &mut render_ctx.gpu_resources.textures, try_create_texture_desc, @@ -68,17 +70,12 @@ pub fn get_or_create_texture<'a>( render_ctx: &mut RenderContext, texture_key: u64, create_texture_desc: impl FnOnce() -> Texture2DCreationDesc<'a>, -) -> GpuTexture2D { - enum Never {} - let result: Result = render_ctx.texture_manager_2d.get_or_create_with( +) -> Result { + render_ctx.texture_manager_2d.get_or_create_with( texture_key, &mut render_ctx.gpu_resources.textures, - || Ok(create_texture_desc()), - ); - match result { - Ok(handle) => handle, - Err(never) => match never {}, - } + create_texture_desc, + ) } /// Render a `re_render` view using the given clip rectangle. diff --git a/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs b/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs index adcd47dd0922..7787f6c50f15 100644 --- a/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs +++ b/crates/re_viewer/src/gpu_bridge/tensor_to_gpu.rs @@ -1,5 +1,6 @@ //! Upload [`Tensor`] to [`re_renderer`]. +use anyhow::Context; use std::borrow::Cow; use bytemuck::{allocation::pod_collect_to_vec, cast_slice, Pod}; @@ -94,7 +95,8 @@ fn color_tensor_to_gpu( width, height, }) - })?; + }) + .map_err(|err| anyhow::anyhow!("Failed to create texture for color tensor: {err}"))?; let texture_format = texture_handle.format(); @@ -110,7 +112,7 @@ fn color_tensor_to_gpu( crate::gpu_bridge::range(tensor_stats)? }; - let color_mapper = if texture_format.describe().components == 1 { + let color_mapper = if re_renderer::texture_info::num_texture_components(texture_format) == 1 { // Single-channel images = luminance = grayscale Some(ColorMapper::Function(re_renderer::Colormap::Grayscale)) } else { @@ -151,14 +153,13 @@ fn class_id_tensor_to_gpu( .ok_or_else(|| anyhow::anyhow!("compressed_tensor!?"))?; anyhow::ensure!(0.0 <= min, "Negative class id"); - // create a lookup texture for the colors that's 256 wide, - // and as many rows as needed to fit all the classes. - anyhow::ensure!(max <= 65535.0, "Too many class ids"); + anyhow::ensure!(max <= 65535.0, "Too many class ids"); // we only support u8 and u16 tensors // We pack the colormap into a 2D texture so we don't go over the max texture size. // We only support u8 and u16 class ids, so 256^2 is the biggest texture we need. + let num_colors = (max + 1.0) as usize; let colormap_width = 256; - let colormap_height = (max as usize + colormap_width - 1) / colormap_width; + let colormap_height = (num_colors + colormap_width - 1) / colormap_width; let colormap_texture_handle = get_or_create_texture(render_ctx, hash(annotations.row_id), || { @@ -179,11 +180,13 @@ fn class_id_tensor_to_gpu( width: colormap_width as u32, height: colormap_height as u32, } - }); + }) + .context("Failed to create class_id_colormap.")?; let main_texture_handle = try_get_or_create_texture(render_ctx, hash(tensor.id()), || { general_texture_creation_desc_from_tensor(debug_name, tensor) - })?; + }) + .map_err(|err| anyhow::anyhow!("Failed to create texture for class id tensor: {err}"))?; Ok(ColormappedTexture { texture: main_texture_handle, @@ -212,7 +215,8 @@ fn depth_tensor_to_gpu( let texture = try_get_or_create_texture(render_ctx, hash(tensor.id()), || { general_texture_creation_desc_from_tensor(debug_name, tensor) - })?; + }) + .map_err(|err| anyhow::anyhow!("Failed to create depth tensor texture: {err}"))?; Ok(ColormappedTexture { texture, diff --git a/crates/re_viewer/src/lib.rs b/crates/re_viewer/src/lib.rs index 85a2939b3160..25618fe852be 100644 --- a/crates/re_viewer/src/lib.rs +++ b/crates/re_viewer/src/lib.rs @@ -1,9 +1,10 @@ -//! Rerun Viewer GUI. +//! Depthai Viewer GUI. //! -//! This crate contains all the GUI code for the Rerun Viewer, +//! This crate contains all the GUI code for the Depthai Viewer, //! including all 2D and 3D visualization code. mod app; +pub mod depthai; pub mod env_vars; pub(crate) mod gpu_bridge; pub mod math; @@ -14,7 +15,7 @@ mod viewer_analytics; pub(crate) use misc::{mesh_loader, Item, TimeControl, TimeView, ViewerContext}; use re_log_types::PythonVersion; -pub(crate) use ui::{memory_panel, selection_panel, time_panel, UiVerbosity}; +pub(crate) use ui::{bottom_panel, memory_panel, selection_panel, time_panel, UiVerbosity}; pub use app::{App, StartupOptions}; pub use remote_viewer_app::RemoteViewerApp; @@ -115,11 +116,7 @@ impl AppEnvironment { // --------------------------------------------------------------------------- #[allow(dead_code)] -const APPLICATION_NAME: &str = "Rerun Viewer"; - -pub(crate) fn hardware_tier() -> re_renderer::config::HardwareTier { - re_renderer::config::HardwareTier::default() -} +const APPLICATION_NAME: &str = "Depthai Viewer"; pub(crate) fn wgpu_options() -> egui_wgpu::WgpuConfiguration { egui_wgpu::WgpuConfiguration { @@ -141,10 +138,8 @@ pub(crate) fn wgpu_options() -> egui_wgpu::WgpuConfiguration { egui_wgpu::SurfaceErrorAction::SkipFrame } }), - backends: re_renderer::config::supported_backends(), - device_descriptor: crate::hardware_tier().device_descriptor(), - // TODO(andreas): This should be the default for egui-wgpu. - power_preference: wgpu::util::power_preference_from_env().unwrap_or(wgpu::PowerPreference::HighPerformance), + supported_backends: re_renderer::config::supported_backends(), + device_descriptor: std::sync::Arc::new(|adapter| re_renderer::config::HardwareTier::from_adapter(adapter).device_descriptor()), ..Default::default() } } @@ -157,11 +152,14 @@ pub(crate) fn customize_eframe(cc: &eframe::CreationContext<'_>) -> re_ui::ReUi let paint_callback_resources = &mut render_state.renderer.write().paint_callback_resources; paint_callback_resources.insert(RenderContext::new( + &render_state.adapter, render_state.device.clone(), render_state.queue.clone(), RenderContextConfig { output_format_color: render_state.target_format, - hardware_tier: crate::hardware_tier(), + hardware_tier: re_renderer::config::HardwareTier::from_adapter( + &render_state.adapter, + ), }, )); } @@ -188,6 +186,11 @@ pub fn wake_up_ui_thread_on_each_msg( .name("ui_waker".to_owned()) .spawn(move || { while let Ok((sent_at, msg)) = rx.recv_with_send_time() { + // TODO(filip): Improve this code to be more smart, maybe we have 100 legit messages and ui is in focus? + if tx.len() > 100 { + re_log::trace!("Dropping messages: Most likely the app is not in focus!"); + continue; + } if tx.send_at(sent_at, msg).is_ok() { ctx.request_repaint(); } else { diff --git a/crates/re_viewer/src/misc/selection_state.rs b/crates/re_viewer/src/misc/selection_state.rs index 9372ea91dc0a..9068b00a977f 100644 --- a/crates/re_viewer/src/misc/selection_state.rs +++ b/crates/re_viewer/src/misc/selection_state.rs @@ -7,7 +7,7 @@ use re_data_store::EntityPath; use re_log_types::{component_types::InstanceKey, EntityPathHash}; use re_renderer::OutlineMaskPreference; -use crate::ui::{Blueprint, SelectionHistory, SpaceView, SpaceViewId}; +use crate::ui::{Blueprint, SelectionHistory, SpaceView, SpaceViewId, Viewport}; use super::{Item, ItemCollection}; @@ -288,9 +288,9 @@ impl SelectionState { &mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui, - blueprint: &mut Blueprint, + viewport: &mut Viewport, ) -> Option { - self.history.selection_ui(re_ui, ui, blueprint) + self.history.selection_ui(re_ui, ui, viewport) } pub fn highlight_for_ui_element(&self, test: &Item) -> HoverHighlight { diff --git a/crates/re_viewer/src/misc/viewer_context.rs b/crates/re_viewer/src/misc/viewer_context.rs index 90fb0b40ea25..93584b9c254a 100644 --- a/crates/re_viewer/src/misc/viewer_context.rs +++ b/crates/re_viewer/src/misc/viewer_context.rs @@ -11,6 +11,8 @@ use super::{ HoverHighlight, }; +use crate::depthai::depthai; + /// Common things needed by many parts of the viewer. pub struct ViewerContext<'a> { /// Global options for the whole viewer. @@ -32,6 +34,7 @@ pub struct ViewerContext<'a> { pub re_ui: &'a re_ui::ReUi, pub render_ctx: &'a mut re_renderer::RenderContext, + pub depthai_state: &'a mut depthai::State, } impl<'a> ViewerContext<'a> { diff --git a/crates/re_viewer/src/native.rs b/crates/re_viewer/src/native.rs index c365c5829dc3..37edb5399def 100644 --- a/crates/re_viewer/src/native.rs +++ b/crates/re_viewer/src/native.rs @@ -1,9 +1,9 @@ use re_log_types::LogMsg; - use crate::APPLICATION_NAME; -type AppCreator = - Box, re_ui::ReUi) -> Box>; +type AppCreator = Box< + dyn FnOnce(&eframe::CreationContext<'_>, re_ui::ReUi) -> Box +>; // NOTE: the name of this function is hard-coded in `crates/rerun/src/crash_handler.rs`! pub fn run_native_app(app_creator: AppCreator) -> eframe::Result<()> { @@ -20,7 +20,7 @@ pub fn run_native_app(app_creator: AppCreator) -> eframe::Result<()> { transparent: re_ui::CUSTOM_WINDOW_DECORATIONS, follow_system_theme: false, - default_theme: eframe::Theme::Dark, + default_theme: eframe::Theme::Light, renderer: eframe::Renderer::Wgpu, wgpu_options: crate::wgpu_options(), @@ -36,7 +36,7 @@ pub fn run_native_app(app_creator: AppCreator) -> eframe::Result<()> { Box::new(move |cc| { let re_ui = crate::customize_eframe(cc); app_creator(cc, re_ui) - }), + }) ) } @@ -44,21 +44,25 @@ pub fn run_native_viewer_with_messages( build_info: re_build_info::BuildInfo, app_env: crate::AppEnvironment, startup_options: crate::StartupOptions, - log_messages: Vec, + log_messages: Vec ) -> eframe::Result<()> { let (tx, rx) = re_smart_channel::smart_channel(re_smart_channel::Source::Sdk); for log_msg in log_messages { tx.send(log_msg).ok(); } - run_native_app(Box::new(move |cc, re_ui| { - Box::new(crate::App::from_receiver( - build_info, - &app_env, - startup_options, - re_ui, - cc.storage, - rx, - std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), - )) - })) + run_native_app( + Box::new(move |cc, re_ui| { + Box::new( + crate::App::from_receiver( + build_info, + &app_env, + startup_options, + re_ui, + cc.storage, + rx, + std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)) + ) + ) + }) + ) } diff --git a/crates/re_viewer/src/ui/auto_layout.rs b/crates/re_viewer/src/ui/auto_layout.rs index 36778aa689f3..7af2a61b8ee1 100644 --- a/crates/re_viewer/src/ui/auto_layout.rs +++ b/crates/re_viewer/src/ui/auto_layout.rs @@ -9,33 +9,45 @@ //! * We also want to pick aspect ratios that fit the data pretty well // TODO(emilk): fix O(N^2) execution time (where N = number of spaces) -use std::collections::BTreeMap; +use core::panic; +use std::collections::{BTreeMap, BTreeSet}; use ahash::HashMap; use egui::Vec2; +use egui_dock::NodeIndex; use itertools::Itertools as _; +use lazy_static::lazy_static; use re_data_store::{EntityPath, EntityPathPart}; -use super::{space_view::SpaceView, view_category::ViewCategory, SpaceViewId}; +use crate::depthai::depthai; + +use super::{ + space_view::{SpaceView, SpaceViewKind}, + view_category::ViewCategory, + viewport::Tab, + SpaceViewId, +}; #[derive(Clone, Debug)] pub struct SpaceMakeInfo { pub id: SpaceViewId, /// Some path we use to group the views by - pub path: EntityPath, + pub path: Option, - pub category: ViewCategory, + pub category: Option, /// Desired aspect ratio, if any. pub aspect_ratio: Option, + + pub kind: SpaceViewKind, } enum LayoutSplit { LeftRight(Box, f32, Box), TopBottom(Box, f32, Box), - Leaf(SpaceMakeInfo), + Leaf(Vec), } enum SplitDirection { @@ -43,14 +55,346 @@ enum SplitDirection { TopBottom { top: Vec2, t: f32, bottom: Vec2 }, } -pub(crate) fn tree_from_space_views( +fn right_panel_split() -> LayoutSplit { + LayoutSplit::TopBottom( + LayoutSplit::Leaf(vec![CONFIG_SPACE_VIEW.clone(), STATS_SPACE_VIEW.clone()]).into(), + 0.7, + LayoutSplit::Leaf(vec![SELECTION_SPACE_VIEW.clone()]).into(), + ) +} + +// Creates space make infos for constant space views. +// This is needed to be able to search for these views in the tree later, based on the SpaceViewId +lazy_static! { + static ref CONFIG_SPACE_VIEW: SpaceMakeInfo = SpaceMakeInfo { + id: SpaceViewId::random(), + path: None, + category: None, + aspect_ratio: None, + kind: SpaceViewKind::Config, + }; + static ref STATS_SPACE_VIEW: SpaceMakeInfo = SpaceMakeInfo { + id: SpaceViewId::random(), + path: None, + category: None, + aspect_ratio: None, + kind: SpaceViewKind::Stats, + }; + static ref SELECTION_SPACE_VIEW: SpaceMakeInfo = SpaceMakeInfo { + id: SpaceViewId::random(), + path: None, + category: None, + aspect_ratio: None, + kind: SpaceViewKind::Selection, + }; + static ref CONSTANT_SPACE_VIEWS: Vec = vec![ + CONFIG_SPACE_VIEW.id, + STATS_SPACE_VIEW.id, + SELECTION_SPACE_VIEW.id, + ]; +} + +fn push_space_view_to_leaf( + tree: &mut egui_dock::Tree, + leaf: NodeIndex, + space_view: &SpaceView, +) { + tree.set_focused_node(leaf); + tree.push_to_focused_leaf(space_view.into()); +} + +fn find_space_path_in_tree( + tree: &egui_dock::Tree, + space_view_path: &EntityPath, +) -> Option { + tree.tabs() + .find(|tab| { + let Some(path) = &tab.space_path else { + return false; + }; + path == space_view_path + }) + .cloned() +} + +fn find_top_left_leaf(tree: &egui_dock::Tree) -> NodeIndex { + let mut node = NodeIndex::root(); + loop { + if tree[node].is_leaf() { + println!("Node: {node:?}"); + return node; + } + node = node.right(); + } +} + +/// Is it possible to create a quad of left top 3d color left bottom 2d color +/// right top 3d mono right bottom 2d mono, based on the current tree +fn can_create_color_mono_quad(tree: &egui_dock::Tree, space_views: Vec) -> bool { + let Some(color3d_tab) = find_space_path_in_tree(tree, &depthai::entity_paths::COLOR_CAM_3D) else { + return false; + }; + let Some((color3d_node_index, _)) = tree.find_tab(&color3d_tab) else { + return false; + }; + let Some(mono3d_tab) = find_space_path_in_tree(tree, &depthai::entity_paths::MONO_CAM_3D) else { + return false; + }; + let Some((mono3d_node_index, mono3d_tab_index)) = tree.find_tab(&mono3d_tab) else { + return false; + }; + mono3d_node_index == color3d_node_index.right() +} + +/// Insert new space views and remove space views that aren't available anymore. +/// Tries to layout the viewport as intuitively as possible +/// TODO(filip): Reduce the size of this code. A lot of it is repetitive and can be refactored +/// TODO(filip): Improve code functionally: detect when you can group mono and color 3d + 2d views into a 4 way split +pub(crate) fn update_tree( + tree: &mut egui_dock::Tree, + visible_space_views: &BTreeSet, + space_views: &HashMap, + is_maximized: bool, +) { + // One view is maximized + if is_maximized { + let tab: Tab; + let space_view_id = visible_space_views.first().unwrap(); + if let Some(space_view) = space_views.get(space_view_id) { + tab = space_view.into(); + } else { + tab = if space_view_id == &STATS_SPACE_VIEW.id { + Tab { + space_path: None, + space_view_id: *space_view_id, + space_view_kind: SpaceViewKind::Stats, + } + } else { + re_log::warn_once!("Can't maximize this space view"); + return; + } + } + *tree = egui_dock::Tree::new(vec![tab]); + return; + } + + for tab in tree.clone().tabs().filter(|tab| { + !CONSTANT_SPACE_VIEWS.contains(&tab.space_view_id) + && !visible_space_views + .iter() + .any(|sv_id| sv_id == &tab.space_view_id) + }) { + tree.remove_tab(tree.find_tab(tab).unwrap()); + } + + // If there aren't any "data" space views, we show the config, stats and selection panel on the right. + // With an empty leaf on the left (aka middle if you take into account the blueprint panel) + if visible_space_views.is_empty() { + *tree = egui_dock::Tree::new(vec![]); + + tree_from_split( + tree, + NodeIndex::root(), + &LayoutSplit::LeftRight( + LayoutSplit::Leaf(Vec::new()).into(), + 0.5, + right_panel_split().into(), + ), + ); + let (config_node, config_tab) = tree + .find_tab( + tree.tabs() + .find(|tab| tab.space_view_id == CONFIG_SPACE_VIEW.id) + .unwrap(), // CONFIG_SPACE_VIEW is always present + ) + .unwrap(); + tree.set_active_tab(config_node, config_tab); + + return; + } + + let visible_space_views = visible_space_views + .iter() + .map(|sv| space_views.get(sv).unwrap()); + // Insert new space views + for space_view in visible_space_views { + // println!("Space view: {:?}", space_view.space_path.clone()); + if tree + .find_tab(&Tab { + space_view_id: space_view.id, + space_view_kind: SpaceViewKind::Data, + space_path: Some(space_view.space_path.clone()), + }) + .is_none() + { + // Insert space view into the tree, taking into account the following: + // * If the space view is a 3d view, try to find the corresponding 2d view and place the 3d on top of the 2d view + // * If the space view is a 2d view, try to find the corresponding 3d view and place the 2d view on top of the 3d view + // * If the space view is a duplicate of an existing view (entity path is the same space_view_id differs), place it within the same leaf as the existing view + // * else if none of the above, just place the view in the top left corner as a new tab, (don't insert it into a leaf, create a new leaf) + // println!("Space view getting inserted: {:?}", space_view.space_path); + + match space_view.space_path { + ref space_path + if space_path.hash() == depthai::entity_paths::COLOR_CAM_3D.hash() => + { + if let Some(existing_3d) = + find_space_path_in_tree(tree, &depthai::entity_paths::COLOR_CAM_3D) + { + let (leaf, _) = tree.find_tab(&existing_3d).unwrap(); + push_space_view_to_leaf(tree, leaf, space_view); + } else if let Some(existing_2d) = + find_space_path_in_tree(tree, &depthai::entity_paths::RGB_PINHOLE_CAMERA) + { + let (node_index, _) = tree.find_tab(&existing_2d).unwrap(); + tree.split_above(node_index, 0.5, vec![space_view.into()]); + } else if let Some(existing_mono3d) = + find_space_path_in_tree(tree, &depthai::entity_paths::MONO_CAM_3D) + { + let (leaf, _) = tree.find_tab(&existing_mono3d).unwrap(); + tree.split_left(leaf, 0.5, vec![space_view.into()]); + } else { + let top_left = find_top_left_leaf(tree); + push_space_view_to_leaf(tree, top_left, space_view); + } + } + ref space_path + if space_path.hash() == depthai::entity_paths::RGB_PINHOLE_CAMERA.hash() => + { + if let Some(existing_2d) = + find_space_path_in_tree(tree, &depthai::entity_paths::RGB_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_2d).unwrap(); + push_space_view_to_leaf(tree, leaf, space_view); + } else if let Some(existing_left) = + find_space_path_in_tree(tree, &depthai::entity_paths::LEFT_PINHOLE_CAMERA) + { + let (node_index, _) = tree.find_tab(&existing_left).unwrap(); + tree.split_left(node_index, 0.5, vec![space_view.into()]); + } else if let Some(existing_right) = + find_space_path_in_tree(tree, &depthai::entity_paths::RIGHT_PINHOLE_CAMERA) + { + let (node_index, _) = tree.find_tab(&existing_right).unwrap(); + tree.split_left(node_index, 0.5, vec![space_view.into()]); + } else if let Some(existing_3d) = + find_space_path_in_tree(tree, &depthai::entity_paths::COLOR_CAM_3D) + { + let (node_index, _) = tree.find_tab(&existing_3d).unwrap(); + tree.split_below(node_index, 0.5, vec![space_view.into()]); + } else { + let top_left = find_top_left_leaf(tree); + push_space_view_to_leaf(tree, top_left, space_view); + } + } + ref space_path + if space_path.hash() == depthai::entity_paths::MONO_CAM_3D.hash() => + { + if let Some(existing_3d) = + find_space_path_in_tree(tree, &depthai::entity_paths::MONO_CAM_3D) + { + let (leaf, _) = tree.find_tab(&existing_3d).unwrap(); + push_space_view_to_leaf(tree, leaf, space_view); + } else if let Some(existing_3d_color) = + find_space_path_in_tree(tree, &depthai::entity_paths::COLOR_CAM_3D) + { + let (leaf, _) = tree.find_tab(&existing_3d_color).unwrap(); + tree.split_right(leaf, 0.5, vec![space_view.into()]); + } else if let Some(existing_left) = + find_space_path_in_tree(tree, &depthai::entity_paths::LEFT_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_left).unwrap(); + tree.split_above(leaf, 0.5, vec![space_view.into()]); + } else if let Some(existing_right) = + find_space_path_in_tree(tree, &depthai::entity_paths::RIGHT_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_right).unwrap(); + tree.split_above(leaf, 0.5, vec![space_view.into()]); + } else if let Some(existing_color) = + find_space_path_in_tree(tree, &depthai::entity_paths::RGB_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_color).unwrap(); + tree.split_right(leaf, 0.5, vec![space_view.into()]); + } else { + let top_left = find_top_left_leaf(tree); + push_space_view_to_leaf(tree, top_left, space_view); + } + } + ref space_path + if space_path.hash() == depthai::entity_paths::LEFT_PINHOLE_CAMERA.hash() => + { + if let Some(existing_left) = + find_space_path_in_tree(tree, &depthai::entity_paths::LEFT_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_left).unwrap(); + push_space_view_to_leaf(tree, leaf, space_view); + } else if let Some(existing_right) = + find_space_path_in_tree(tree, &depthai::entity_paths::RIGHT_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_right).unwrap(); + push_space_view_to_leaf(tree, leaf, space_view); + } else if let Some(existing_3d) = + find_space_path_in_tree(tree, &depthai::entity_paths::MONO_CAM_3D) + { + let (node_index, _) = tree.find_tab(&existing_3d).unwrap(); + tree.split_below(node_index, 0.5, vec![space_view.into()]); + } else if let Some(existing_2d_color) = + find_space_path_in_tree(tree, &depthai::entity_paths::RGB_PINHOLE_CAMERA) + { + let (node_index, _) = tree.find_tab(&existing_2d_color).unwrap(); + tree.split_right(node_index, 0.5, vec![space_view.into()]); + } else { + let top_left = find_top_left_leaf(tree); + push_space_view_to_leaf(tree, top_left, space_view); + } + } + ref space_path + if space_path.hash() == depthai::entity_paths::RIGHT_PINHOLE_CAMERA.hash() => + { + if let Some(existing_right) = + find_space_path_in_tree(tree, &depthai::entity_paths::RIGHT_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_right).unwrap(); + push_space_view_to_leaf(tree, leaf, space_view); + } else if let Some(existing_left) = + find_space_path_in_tree(tree, &depthai::entity_paths::LEFT_PINHOLE_CAMERA) + { + let (leaf, _) = tree.find_tab(&existing_left).unwrap(); + push_space_view_to_leaf(tree, leaf, space_view); + } else if let Some(existing_3d) = + find_space_path_in_tree(tree, &depthai::entity_paths::MONO_CAM_3D) + { + let (node_index, _) = tree.find_tab(&existing_3d).unwrap(); + tree.split_below(node_index, 0.5, vec![space_view.into()]); + } else if let Some(existing_2d_color) = + find_space_path_in_tree(tree, &depthai::entity_paths::RGB_PINHOLE_CAMERA) + { + let (node_index, _) = tree.find_tab(&existing_2d_color).unwrap(); + tree.split_right(node_index, 0.5, vec![space_view.into()]); + } else { + let top_left = find_top_left_leaf(tree); + push_space_view_to_leaf(tree, top_left, space_view); + } + } + _ => {} + }; + } + } +} + +/// Default layout of space views tuned for depthai-viewer +pub(crate) fn default_tree_from_space_views( viewport_size: egui::Vec2, visible: &std::collections::BTreeSet, space_views: &HashMap, -) -> egui_dock::Tree { - let mut tree = egui_dock::Tree::new(vec![]); - - let mut space_make_infos = space_views +) -> egui_dock::Tree { + // TODO(filip): Implement sensible auto layout when space views changes. + // Something like: + // - Get the tabs that need to be added or removed + // - Removal is easy, just remove the tab + // - Addition should try to layout like currently 3d, 2d views. New views just appear in the top left corner i guess. + let mut tree = egui_dock::Tree::new(Vec::new()); + + let spaces = space_views .iter() .filter(|(space_view_id, _space_view)| visible.contains(space_view_id)) // Sort for determinism: @@ -76,30 +420,116 @@ pub(crate) fn tree_from_space_views( } } ViewCategory::Tensor | ViewCategory::TimeSeries => Some(1.0), // Not sure if we should do `None` here. - ViewCategory::Text => Some(2.0), // Make text logs wide + ViewCategory::Text | ViewCategory::NodeGraph => Some(2.0), // Make text logs wide ViewCategory::BarChart => None, }; SpaceMakeInfo { id: *space_view_id, - path: space_view.space_path.clone(), - category: space_view.category, + path: Some(space_view.space_path.clone()), + category: Some(space_view.category), aspect_ratio, + kind: SpaceViewKind::Data, } }) .collect_vec(); - if !space_make_infos.is_empty() { - // Users often organize by path prefix, so we start by splitting along that - let layout = layout_by_path_prefix(viewport_size, &mut space_make_infos); - tree_from_split(&mut tree, egui_dock::NodeIndex(0), &layout); + if !spaces.is_empty() { + let layout = LayoutSplit::LeftRight( + { + if spaces.len() == 1 { + LayoutSplit::Leaf(spaces) + } else { + // Split space views: + // - Color stream available: Split top 3d, bottom 2d + // - if mono available split it right from color streams into 3d top and both 2d in a tab group on the bottom + let mut top_left_spaces = Vec::new(); + let mut top_right_spaces = Vec::new(); + let mut bottom_left_spaces = Vec::new(); + let mut bottom_right_spaces = Vec::new(); + spaces.iter().cloned().for_each(|space| { + let Some(space_path) = &space.path else { + return; + }; + if space_path.hash() == depthai::entity_paths::COLOR_CAM_3D.hash() { + top_left_spaces.push(space); + } else if space_path.hash() + == depthai::entity_paths::RGB_PINHOLE_CAMERA.hash() + { + top_right_spaces.push(space); + } else if space_path.hash() == depthai::entity_paths::MONO_CAM_3D.hash() { + bottom_left_spaces.push(space); + } else { + bottom_right_spaces.push(space); + } + }); + + let color_empty = top_left_spaces.is_empty() && top_right_spaces.is_empty(); + let mono_empty = + bottom_left_spaces.is_empty() && bottom_right_spaces.is_empty(); + let mut color_split = LayoutSplit::TopBottom( + LayoutSplit::Leaf(top_left_spaces.clone()).into(), + 0.5, + LayoutSplit::Leaf(top_right_spaces.clone()).into(), + ); + let mut mono_split = LayoutSplit::TopBottom( + LayoutSplit::Leaf(bottom_left_spaces.clone()).into(), + 0.5, + LayoutSplit::Leaf(bottom_right_spaces.clone()).into(), + ); + + if !color_empty && mono_empty { + color_split + } else if !color_empty && !mono_empty { + if top_left_spaces.is_empty() { + color_split = LayoutSplit::Leaf(top_right_spaces); + } else if top_right_spaces.is_empty() { + color_split = LayoutSplit::Leaf(top_left_spaces); + } + if bottom_left_spaces.is_empty() { + mono_split = LayoutSplit::Leaf(bottom_right_spaces); + } else if bottom_right_spaces.is_empty() { + mono_split = LayoutSplit::Leaf(bottom_left_spaces); + } + LayoutSplit::LeftRight(color_split.into(), 0.5, mono_split.into()) + } else if color_empty && !mono_empty { + mono_split + } else { + LayoutSplit::Leaf(spaces) + } + } + } + .into(), + 0.7, + right_panel_split().into(), + ); + tree_from_split(&mut tree, NodeIndex::root(), &layout); + } else { + tree_from_split( + &mut tree, + NodeIndex::root(), + &LayoutSplit::LeftRight( + LayoutSplit::Leaf(vec![]).into(), + 0.7, + right_panel_split().into(), + ), + ); } + // Always set the config tab as the active tab + let (config_node, config_tab) = tree + .find_tab( + tree.tabs() + .find(|tab| tab.space_view_id == CONFIG_SPACE_VIEW.id) + .unwrap(), // CONFIG_SPACE_VIEW is always present + ) + .unwrap(); + tree.set_active_tab(config_node, config_tab); tree } fn tree_from_split( - tree: &mut egui_dock::Tree, + tree: &mut egui_dock::Tree, parent: egui_dock::NodeIndex, split: &LayoutSplit, ) { @@ -114,9 +544,15 @@ fn tree_from_split( tree_from_split(tree, top_ni, top); tree_from_split(tree, bottom_ni, bottom); } - LayoutSplit::Leaf(space_info) => { + LayoutSplit::Leaf(space_infos) => { tree.set_focused_node(parent); - tree.push_to_focused_leaf(space_info.id); + for space_info in space_infos { + tree.push_to_focused_leaf(Tab { + space_view_id: space_info.id, + space_view_kind: space_info.kind, + space_path: space_info.path.clone(), + }); + } } } } @@ -126,7 +562,7 @@ fn layout_by_category(viewport_size: egui::Vec2, spaces: &mut [SpaceMakeInfo]) - assert!(!spaces.is_empty()); if spaces.len() == 1 { - LayoutSplit::Leaf(spaces[0].clone()) + LayoutSplit::Leaf(spaces.to_vec()) } else { let groups = group_by_category(spaces); @@ -145,7 +581,7 @@ fn layout_by_path_prefix(viewport_size: egui::Vec2, spaces: &mut [SpaceMakeInfo] assert!(!spaces.is_empty()); if spaces.len() == 1 { - LayoutSplit::Leaf(spaces[0].clone()) + LayoutSplit::Leaf(spaces.to_vec()) } else { let groups = group_by_path_prefix(spaces); @@ -264,7 +700,10 @@ fn desired_aspect_ratio(spaces: &[SpaceMakeInfo]) -> Option { fn group_by_category(space_infos: &[SpaceMakeInfo]) -> Vec> { let mut groups: BTreeMap> = Default::default(); for info in space_infos { - groups.entry(info.category).or_default().push(info.clone()); + let Some(category) = info.category else { + continue; + }; + groups.entry(category).or_default().push(info.clone()); } groups.into_values().collect() } @@ -277,7 +716,12 @@ fn group_by_path_prefix(space_infos: &[SpaceMakeInfo]) -> Vec let paths = space_infos .iter() - .map(|space_info| space_info.path.as_slice().to_vec()) + .map(|space_info| { + let Some(path) = &space_info.path else { + panic!("Space {:?} has no path", space_info); + }; + path.as_slice().to_vec() + }) .collect_vec(); for i in 0.. { diff --git a/crates/re_viewer/src/ui/blueprint.rs b/crates/re_viewer/src/ui/blueprint.rs index 8a173ac58883..969fb15465f5 100644 --- a/crates/re_viewer/src/ui/blueprint.rs +++ b/crates/re_viewer/src/ui/blueprint.rs @@ -18,8 +18,8 @@ impl Blueprint { pub fn new(egui_ctx: &egui::Context) -> Self { let screen_size = egui_ctx.screen_rect().size(); Self { - blueprint_panel_expanded: screen_size.x > 750.0, - selection_panel_expanded: screen_size.x > 1000.0, + blueprint_panel_expanded: true, + selection_panel_expanded: true, time_panel_expanded: screen_size.y > 600.0, viewport: Default::default(), } @@ -71,7 +71,8 @@ impl Blueprint { ..Default::default() } .show(ui, |ui| { - self.viewport.tree_ui(ctx, ui); + self.viewport + .add_or_remove_space_views_ui(ctx, ui, spaces_info); }); }); } @@ -91,15 +92,13 @@ impl Blueprint { .show_inside(ui, |ui| { ui.horizontal_centered(|ui| { ui.strong("Blueprint").on_hover_text( - "The Blueprint is where you can configure the Rerun Viewer.", + "The Blueprint is where you can configure the Depthai Viewer.", ); ui.allocate_ui_with_layout( ui.available_size_before_wrap(), egui::Layout::right_to_left(egui::Align::Center), |ui| { - self.viewport - .add_new_spaceview_button_ui(ctx, ui, spaces_info); self.reset_button_ui(ctx, ui, spaces_info); }, ); diff --git a/crates/re_viewer/src/ui/bottom_panel/mod.rs b/crates/re_viewer/src/ui/bottom_panel/mod.rs new file mode 100644 index 000000000000..6b3e30e06094 --- /dev/null +++ b/crates/re_viewer/src/ui/bottom_panel/mod.rs @@ -0,0 +1,115 @@ +use super::Blueprint; +use crate::ViewerContext; +use egui::Vec2; +use egui_dock::{DockArea, TabViewer, Tree}; + +struct Tabs<'a, 'b> { + ctx: &'a mut ViewerContext<'b>, +} + +impl<'a, 'b> Tabs<'a, 'b> { + fn xlink_statistics_ui(&mut self, ui: &mut egui::Ui) { + ui.label("Xlink"); + } + + fn imu_ui(&mut self, ui: &mut egui::Ui) { + ui.label("IMU"); + } +} + +impl<'a, 'b> TabViewer for Tabs<'a, 'b> { + type Tab = Tab; + + fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { + match tab { + Tab::XlinkStatistics => self.xlink_statistics_ui(ui), + Tab::Imu => self.imu_ui(ui), + } + } + + fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { + match tab { + Tab::XlinkStatistics => "Xlink Statistics".into(), + Tab::Imu => "IMU".into(), + } + } +} + +enum Tab { + XlinkStatistics, + Imu, +} + +/// The bottom panel of the viewer. +/// This is where XlinkOut statistics and IMU are logged. +/// In the future this panel will also be used to replay recordings. (that will likely look mostly like time_panel) +#[derive(serde::Serialize, serde::Deserialize)] +pub struct BottomPanel { + #[serde(skip)] + dock_tree: Tree, +} + +impl Default for BottomPanel { + fn default() -> Self { + Self { + dock_tree: Tree::new(vec![Tab::XlinkStatistics, Tab::Imu]), + } + } +} + +impl BottomPanel { + pub fn show_panel( + &mut self, + ctx: &mut ViewerContext<'_>, + blueprint: &mut Blueprint, + ui: &mut egui::Ui, + ) { + let top_bar_height = 28.0; + let margin = ctx.re_ui.bottom_panel_margin(); + let mut panel_frame = ctx.re_ui.bottom_panel_frame(); + + let screen_height = ui.ctx().screen_rect().width(); + + let collapsed = egui::TopBottomPanel::bottom("bottom_panel_collapsed") + .resizable(false) + .show_separator_line(false) + .frame(panel_frame) + .default_height(44.0); + + let min_height = 150.0; + let expanded = egui::TopBottomPanel::bottom("bottom_panel_expanded") + .resizable(true) + .show_separator_line(false) + .frame(panel_frame) + .min_height(min_height) + .default_height((0.25 * screen_height).clamp(min_height, 250.0).round()); + + egui::TopBottomPanel::show_animated_between_inside( + ui, + true, + collapsed, + expanded, + |ui: &mut egui::Ui, expansion: f32| { + if expansion < 1.0 { + // Collapsed or animating + ui.horizontal(|ui| { + ui.spacing_mut().interact_size = Vec2::splat(top_bar_height); + ui.visuals_mut().button_frame = true; + // self.collapsed_ui(ctx, ui); + }); + } else { + // Expanded: + // Add extra margin on the left which was intentionally missing on the controls. + let mut top_rop_frame = egui::Frame::default(); + top_rop_frame.inner_margin.left = 8.0; + top_rop_frame.show(ui, |ui| { + DockArea::new(&mut self.dock_tree) + .id(egui::Id::new("bottom_panel_tabs")) + .style(re_ui::egui_dock_style(ui.style())) + .show_inside(ui, &mut Tabs { ctx }); + }); + } + }, + ); + } +} diff --git a/crates/re_viewer/src/ui/data_blueprint.rs b/crates/re_viewer/src/ui/data_blueprint.rs index 97f0fb3cdc7f..e965ebeb2c19 100644 --- a/crates/re_viewer/src/ui/data_blueprint.rs +++ b/crates/re_viewer/src/ui/data_blueprint.rs @@ -210,8 +210,8 @@ impl DataBlueprintTree { ) { crate::profile_function!(); + self.entity_paths.clear(); let mut new_leaf_groups = Vec::new(); - for path in paths { self.entity_paths.insert(path.clone()); diff --git a/crates/re_viewer/src/ui/data_ui/image.rs b/crates/re_viewer/src/ui/data_ui/image.rs index 630c011513b7..b187d16892d9 100644 --- a/crates/re_viewer/src/ui/data_ui/image.rs +++ b/crates/re_viewer/src/ui/data_ui/image.rs @@ -5,13 +5,13 @@ use re_log_types::{ component_types::{ClassId, Tensor, TensorDataMeaning}, DecodedTensor, TensorElement, }; -use re_renderer::renderer::ColormappedTexture; -use re_ui::ReUi; use crate::{ misc::{caches::TensorStats, ViewerContext}, ui::annotations::AnnotationMap, }; +use re_renderer::renderer::ColormappedTexture; +use re_ui::ReUi; use super::{EntityDataUi, UiVerbosity}; diff --git a/crates/re_viewer/src/ui/device_settings_panel.rs b/crates/re_viewer/src/ui/device_settings_panel.rs new file mode 100644 index 000000000000..19cd0b1e953b --- /dev/null +++ b/crates/re_viewer/src/ui/device_settings_panel.rs @@ -0,0 +1,397 @@ +use crate::{depthai::depthai, misc::ViewerContext}; + +use strum::IntoEnumIterator; // Needed for enum::iter() + +/// The "Selection View" side-bar. +#[derive(serde::Deserialize, serde::Serialize, Default)] +#[serde(default)] +pub(crate) struct DeviceSettingsPanel {} + +const CONFIG_UI_WIDTH: f32 = 224.0; + +impl DeviceSettingsPanel { + #[allow(clippy::unused_self)] + pub fn show_panel(&mut self, ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { + let mut available_devices = ctx.depthai_state.get_devices(); + let currently_selected_device = ctx.depthai_state.selected_device.clone(); + let mut combo_device: depthai::DeviceId = currently_selected_device.id; + if !combo_device.is_empty() && available_devices.is_empty() { + available_devices.push(combo_device.clone()); + } + + egui::CentralPanel::default() + .frame(egui::Frame { + inner_margin: egui::Margin::same(re_ui::ReUi::view_padding()), + ..Default::default() + }) + .show_inside(ui, |ui| { + ui.add_sized( + [ui.available_width(), re_ui::ReUi::box_height()], + |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + ctx.re_ui.labeled_combo_box( + ui, + "Device", + if !combo_device.is_empty() { + combo_device.clone() + } else { + "No device selected".to_owned() + }, + true, + |ui: &mut egui::Ui| { + if ui + .selectable_value( + &mut combo_device, + String::new(), + "No device", + ) + .changed() + { + ctx.depthai_state.set_device(combo_device.clone()); + } + for device in available_devices { + if ui + .selectable_value( + &mut combo_device, + device.clone(), + device, + ) + .changed() + { + ctx.depthai_state.set_device(combo_device.clone()); + } + } + }, + ); + }) + .response + }, + ); + + if ctx.depthai_state.applied_device_config.update_in_progress { + ui.add_sized([CONFIG_UI_WIDTH, 10.0], |ui: &mut egui::Ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.add(egui::Spinner::new()) + }) + .response + }); + return; + } + Self::device_configuration_ui(ctx, ui); + }); + } + + fn device_configuration_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { + let mut device_config = ctx.depthai_state.modified_device_config.config.clone(); + let primary_700 = ctx.re_ui.design_tokens.primary_700; + egui::ScrollArea::both() + .auto_shrink([false; 2]) + .show(ui, |ui| { + let mut style = ui.style_mut().clone(); + style.spacing.scroll_bar_inner_margin = 0.0; + ui.set_style(style); + egui::Frame { + fill: ctx.re_ui.design_tokens.gray_50, + inner_margin: egui::Margin::symmetric(30.0, 21.0), + ..Default::default() + } + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.collapsing( + egui::RichText::new("Color Camera").color(primary_700), + |ui| { + ui.vertical(|ui| { + ui.set_width(CONFIG_UI_WIDTH); + ctx.re_ui.labeled_combo_box( + ui, + "Resolution", + format!("{}", device_config.color_camera.resolution), + false, + |ui| { + for res in &ctx + .depthai_state + .selected_device + .supported_color_resolutions + { + ui.selectable_value( + &mut device_config.color_camera.resolution, + *res, + format!("{res}"), + ); + } + }, + ); + ctx.re_ui.labeled_dragvalue( + ui, + "FPS", + &mut device_config.color_camera.fps, + 0..=120, + ); + ctx.re_ui.labeled_checkbox( + ui, + "Stream", + &mut device_config.color_camera.stream_enabled, + ); + }); + }, + ); + ui.collapsing( + egui::RichText::new("Left Mono Camera").color(primary_700), + |ui| { + ui.vertical(|ui| { + ui.set_width(CONFIG_UI_WIDTH); + ctx.re_ui.labeled_combo_box( + ui, + "Resolution", + format!("{}", device_config.left_camera.resolution), + false, + |ui| { + for res in &ctx + .depthai_state + .selected_device + .supported_left_mono_resolutions + { + ui.selectable_value( + &mut device_config.left_camera.resolution, + *res, + format!("{res}"), + ); + } + }, + ); + ctx.re_ui.labeled_dragvalue( + ui, + "FPS", + &mut device_config.left_camera.fps, + 0..=120, + ); + ctx.re_ui.labeled_checkbox( + ui, + "Stream", + &mut device_config.left_camera.stream_enabled, + ); + }) + }, + ); + + ui.collapsing( + egui::RichText::new("Right Mono Camera").color(primary_700), + |ui| { + ui.vertical(|ui| { + ui.set_width(CONFIG_UI_WIDTH); + ctx.re_ui.labeled_combo_box( + ui, + "Resolution", + format!("{}", device_config.right_camera.resolution), + false, + |ui| { + for res in &ctx + .depthai_state + .selected_device + .supported_right_mono_resolutions + { + ui.selectable_value( + &mut device_config.right_camera.resolution, + *res, + format!("{res}"), + ); + } + }, + ); + ctx.re_ui.labeled_dragvalue( + ui, + "FPS", + &mut device_config.right_camera.fps, + 0..=120, + ); + ctx.re_ui.labeled_checkbox( + ui, + "Stream", + &mut device_config.right_camera.stream_enabled, + ); + }) + }, + ); + + // This is a hack, I wanted AI settings at the bottom, but some depth settings names + // are too long and it messes up the width of the ui layout somehow. + ui.collapsing( + egui::RichText::new("AI settings").color(primary_700), + |ui| { + ui.vertical(|ui| { + ui.set_width(CONFIG_UI_WIDTH); + ctx.re_ui.labeled_combo_box( + ui, + "AI Model", + device_config.ai_model.display_name.clone(), + false, + |ui| { + for nn in &ctx.depthai_state.neural_networks { + ui.selectable_value( + &mut device_config.ai_model, + nn.clone(), + &nn.display_name, + ); + } + }, + ); + }); + }, + ); + + let mut depth = device_config.depth.unwrap_or_default(); + if depth.align == depthai::CameraBoardSocket::CENTER && !depth.lr_check + { + depth.align = depthai::CameraBoardSocket::AUTO; + } + + ui.collapsing( + egui::RichText::new("Depth settings").color(primary_700), + |ui| { + ui.vertical(|ui| { + ui.set_width(CONFIG_UI_WIDTH); + ctx.re_ui.labeled_checkbox( + ui, + "LR Check", + &mut depth.lr_check, + ); + ctx.re_ui.labeled_combo_box( + ui, + "Align to", + format!("{:?}", depth.align), + false, + |ui| { + for align in + depthai::CameraBoardSocket::depth_align_options( + ) + { + if align == depthai::CameraBoardSocket::CENTER + && !depth.lr_check + { + continue; + } + ui.selectable_value( + &mut depth.align, + align, + format!("{align:?}"), + ); + } + }, + ); + ctx.re_ui.labeled_combo_box( + ui, + "Median Filter", + format!("{:?}", depth.median), + false, + |ui| { + for filter in depthai::DepthMedianFilter::iter() { + ui.selectable_value( + &mut depth.median, + filter, + format!("{filter:?}"), + ); + } + }, + ); + ctx.re_ui.labeled_dragvalue( + ui, + "LR Threshold", + &mut depth.lrc_threshold, + 0..=10, + ); + ctx.re_ui.labeled_checkbox( + ui, + "Extended Disparity", + &mut depth.extended_disparity, + ); + ctx.re_ui.labeled_checkbox( + ui, + "Subpixel Disparity", + &mut depth.subpixel_disparity, + ); + ctx.re_ui.labeled_dragvalue( + ui, + "Sigma", + &mut depth.sigma, + 0..=65535, + ); + ctx.re_ui.labeled_dragvalue( + ui, + "Confidence", + &mut depth.confidence, + 0..=255, + ); + ctx.re_ui.labeled_toggle_switch( + ui, + "Depth enabled", + &mut device_config.depth_enabled, + ); + }); + }, + ); + + device_config.depth = Some(depth); + ctx.depthai_state.modified_device_config.config = device_config.clone(); + ui.vertical(|ui| { + ui.horizontal(|ui| { + let only_runtime_configs_changed = + depthai::State::only_runtime_configs_changed( + &ctx.depthai_state.applied_device_config.config, + &device_config, + ); + let apply_enabled = !only_runtime_configs_changed + && device_config + != ctx.depthai_state.applied_device_config.config + && !ctx.depthai_state.selected_device.id.is_empty(); + if !apply_enabled && only_runtime_configs_changed { + ctx.depthai_state + .set_device_config(&mut device_config, true); + } + if ctx.depthai_state.selected_device.id.is_empty() { + ctx.depthai_state + .set_device_config(&mut device_config, false); + } + + ui.add_enabled_ui(apply_enabled, |ui| { + ui.scope(|ui| { + let mut style = ui.style_mut().clone(); + if apply_enabled { + let color = + ctx.re_ui.design_tokens.primary_bg_color; + let hover_color = + ctx.re_ui.design_tokens.primary_hover_bg_color; + style.visuals.widgets.hovered.bg_fill = hover_color; + style.visuals.widgets.hovered.weak_bg_fill = + hover_color; + style.visuals.widgets.inactive.bg_fill = color; + style.visuals.widgets.inactive.weak_bg_fill = color; + style.visuals.widgets.inactive.fg_stroke.color = + egui::Color32::WHITE; + style.visuals.widgets.hovered.fg_stroke.color = + egui::Color32::WHITE; + } + style.spacing.button_padding = + egui::Vec2::new(24.0, 4.0); + ui.set_style(style); + if ui + .add_sized( + [CONFIG_UI_WIDTH, re_ui::ReUi::box_height()], + egui::Button::new("Apply"), + ) + .clicked() + { + ctx.depthai_state + .set_device_config(&mut device_config, false); + } + }); + }); + }); + }); + }); + ui.add_space(ui.available_width()); + }); + }); + }); + } +} diff --git a/crates/re_viewer/src/ui/memory_panel.rs b/crates/re_viewer/src/ui/memory_panel.rs index 796c98977a9d..5c61be30e84a 100644 --- a/crates/re_viewer/src/ui/memory_panel.rs +++ b/crates/re_viewer/src/ui/memory_panel.rs @@ -57,7 +57,7 @@ impl MemoryPanel { }); egui::CentralPanel::default().show_inside(ui, |ui| { - ui.label("🗠 Rerun Viewer memory use over time"); + ui.label("🗠 Depthai Viewer memory use over time"); self.plot(ui, limit); }); } @@ -69,7 +69,7 @@ impl MemoryPanel { store_config: &DataStoreConfig, store_stats: &DataStoreStats, ) { - ui.strong("Rerun Viewer resource usage"); + ui.strong("Depthai Viewer resource usage"); ui.separator(); ui.collapsing("CPU Resources", |ui| { diff --git a/crates/re_viewer/src/ui/mod.rs b/crates/re_viewer/src/ui/mod.rs index da730d21b5e7..9bb817412638 100644 --- a/crates/re_viewer/src/ui/mod.rs +++ b/crates/re_viewer/src/ui/mod.rs @@ -10,14 +10,18 @@ mod space_view_entity_picker; mod space_view_heuristics; mod view_bar_chart; mod view_category; +mod view_node_graph; mod view_tensor; mod view_text; mod view_time_series; mod viewport; +pub(crate) mod bottom_panel; pub(crate) mod data_ui; +pub(crate) mod device_settings_panel; pub(crate) mod memory_panel; pub(crate) mod selection_panel; +pub(crate) mod stats_panel; pub(crate) mod time_panel; pub mod view_spatial; @@ -27,7 +31,7 @@ pub mod view_spatial; use self::scene::SceneQuery; pub(crate) use self::blueprint::Blueprint; -pub(crate) use self::space_view::{SpaceView, SpaceViewId}; +pub(crate) use self::space_view::{SpaceView, SpaceViewId, SpaceViewKind}; pub use self::annotations::{Annotations, DefaultColor, MISSING_ANNOTATIONS}; pub use self::data_blueprint::DataBlueprintGroupHandle; diff --git a/crates/re_viewer/src/ui/selection_history_ui.rs b/crates/re_viewer/src/ui/selection_history_ui.rs index e91c53966da7..3b20a9f046ac 100644 --- a/crates/re_viewer/src/ui/selection_history_ui.rs +++ b/crates/re_viewer/src/ui/selection_history_ui.rs @@ -1,8 +1,8 @@ use egui::RichText; use re_ui::Command; -use super::SelectionHistory; -use crate::{misc::ItemCollection, ui::Blueprint, Item}; +use super::{SelectionHistory, Viewport}; +use crate::{misc::ItemCollection, Item}; // --- @@ -11,30 +11,31 @@ impl SelectionHistory { &mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui, - blueprint: &Blueprint, + viewport: &Viewport, ) -> Option { - self.control_bar_ui(re_ui, ui, blueprint) + self.control_bar_ui(re_ui, ui, viewport) } fn control_bar_ui( &mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui, - blueprint: &Blueprint, + viewport: &Viewport, ) -> Option { ui.horizontal_centered(|ui| { - ui.strong("Selection").on_hover_text("The Selection View contains information and options about the currently selected object(s)."); - // TODO(emilk): an egui helper for right-to-left ui.allocate_ui_with_layout( ui.available_size_before_wrap(), egui::Layout::right_to_left(egui::Align::Center), |ui| { - let next = self.next_button_ui(re_ui, ui, blueprint); - let prev = self.prev_button_ui(re_ui, ui, blueprint); + let next = self.next_button_ui(re_ui, ui, viewport); + let prev = self.prev_button_ui(re_ui, ui, viewport); prev.or(next) - }).inner - }).inner + }, + ) + .inner + }) + .inner } #[must_use] @@ -63,7 +64,7 @@ impl SelectionHistory { &mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui, - blueprint: &Blueprint, + viewport: &Viewport, ) -> Option { // undo selection if let Some(previous) = self.previous() { @@ -75,13 +76,13 @@ impl SelectionHistory { \n\ Right-click for more.", Command::SelectionPrevious.format_shortcut_tooltip_suffix(ui.ctx()), - item_collection_to_string(blueprint, &previous.selection), + item_collection_to_string(viewport, &previous.selection), )); let response = response.context_menu(|ui| { // undo: newest on top, oldest on bottom for i in (0..self.current).rev() { - self.history_item_ui(blueprint, ui, i); + self.history_item_ui(viewport, ui, i); } }); @@ -106,7 +107,7 @@ impl SelectionHistory { &mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui, - blueprint: &Blueprint, + viewport: &Viewport, ) -> Option { // redo selection if let Some(next) = self.next() { @@ -118,13 +119,13 @@ impl SelectionHistory { \n\ Right-click for more.", Command::SelectionNext.format_shortcut_tooltip_suffix(ui.ctx()), - item_collection_to_string(blueprint, &next.selection), + item_collection_to_string(viewport, &next.selection), )); let response = response.context_menu(|ui| { // redo: oldest on top, most recent on bottom for i in (self.current + 1)..self.stack.len() { - self.history_item_ui(blueprint, ui, i); + self.history_item_ui(viewport, ui, i); } }); @@ -145,12 +146,12 @@ impl SelectionHistory { None } - fn history_item_ui(&mut self, blueprint: &Blueprint, ui: &mut egui::Ui, index: usize) { + fn history_item_ui(&mut self, viewport: &Viewport, ui: &mut egui::Ui, index: usize) { if let Some(sel) = self.stack.get(index) { ui.horizontal(|ui| { { // borrow checker workaround - let sel = item_collection_to_string(blueprint, sel); + let sel = item_collection_to_string(viewport, sel); if ui.selectable_value(&mut self.current, index, sel).clicked() { ui.close_menu(); } @@ -169,10 +170,10 @@ fn item_kind_ui(ui: &mut egui::Ui, sel: &Item) { ui.weak(RichText::new(format!("({})", sel.kind()))); } -fn item_collection_to_string(blueprint: &Blueprint, items: &ItemCollection) -> String { +fn item_collection_to_string(viewport: &Viewport, items: &ItemCollection) -> String { assert!(!items.is_empty()); // history never contains empty selections. if items.len() == 1 { - item_to_string(blueprint, items.iter().next().unwrap()) + item_to_string(viewport, items.iter().next().unwrap()) } else if let Some(kind) = items.are_all_same_kind() { format!("{}x {}s", items.len(), kind) } else { @@ -180,10 +181,10 @@ fn item_collection_to_string(blueprint: &Blueprint, items: &ItemCollection) -> S } } -fn item_to_string(blueprint: &Blueprint, item: &Item) -> String { +fn item_to_string(viewport: &Viewport, item: &Item) -> String { match item { Item::SpaceView(sid) => { - if let Some(space_view) = blueprint.viewport.space_view(sid) { + if let Some(space_view) = viewport.space_view(sid) { space_view.display_name.clone() } else { "".to_owned() @@ -191,7 +192,7 @@ fn item_to_string(blueprint: &Blueprint, item: &Item) -> String { } Item::InstancePath(_, entity_path) => entity_path.to_string(), Item::DataBlueprintGroup(sid, handle) => { - if let Some(space_view) = blueprint.viewport.space_view(sid) { + if let Some(space_view) = viewport.space_view(sid) { if let Some(group) = space_view.data_blueprint.group(*handle) { group.display_name.clone() } else { diff --git a/crates/re_viewer/src/ui/selection_panel.rs b/crates/re_viewer/src/ui/selection_panel.rs index f84b282b4b6a..1b28d6785db5 100644 --- a/crates/re_viewer/src/ui/selection_panel.rs +++ b/crates/re_viewer/src/ui/selection_panel.rs @@ -1,4 +1,6 @@ -use egui::NumExt as _; +use egui::{ + NumExt as _, +}; use re_data_store::{ query_latest_single, ColorMapper, Colormap, EditableAutoValue, EntityPath, EntityProperties, }; @@ -8,81 +10,34 @@ use re_log_types::{ }; use crate::{ - ui::{view_spatial::SpatialNavigationMode, Blueprint}, - Item, UiVerbosity, ViewerContext, + ui::view_spatial::SpatialNavigationMode, Item, UiVerbosity, ViewerContext, }; -use super::{data_ui::DataUi, space_view::ViewState}; +use super::{data_ui::DataUi, space_view::ViewState, Viewport}; // --- /// The "Selection View" side-bar. -#[derive(Default, serde::Deserialize, serde::Serialize)] +#[derive(serde::Deserialize, serde::Serialize, Default)] #[serde(default)] pub(crate) struct SelectionPanel {} impl SelectionPanel { - #[allow(clippy::unused_self)] - pub fn show_panel( - &mut self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - blueprint: &mut Blueprint, - ) { - let screen_width = ui.ctx().screen_rect().width(); - - let panel = egui::SidePanel::right("selection_view") - .min_width(120.0) - .default_width((0.45 * screen_width).min(250.0).round()) - .max_width((0.65 * screen_width).round()) - .resizable(true) - .frame(egui::Frame { - fill: ui.style().visuals.panel_fill, - ..Default::default() + pub fn show_panel(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, viewport: &mut Viewport) { + egui::ScrollArea::both() + .auto_shrink([true; 2]) + .show(ui, |ui| { + egui::Frame { + inner_margin: egui::Margin::same(re_ui::ReUi::view_padding()), + ..Default::default() + } + .show(ui, |ui| { + Self::contents(ui, ctx, viewport); + }); }); - - panel.show_animated_inside( - ui, - blueprint.selection_panel_expanded, - |ui: &mut egui::Ui| { - egui::TopBottomPanel::top("selection_panel_title_bar") - .exact_height(re_ui::ReUi::title_bar_height()) - .frame(egui::Frame { - inner_margin: egui::Margin::symmetric(re_ui::ReUi::view_padding(), 0.0), - ..Default::default() - }) - .show_inside(ui, |ui| { - if let Some(selection) = ctx - .rec_cfg - .selection_state - .selection_ui(ctx.re_ui, ui, blueprint) - { - ctx.set_multi_selection(selection.iter().cloned()); - } - }); - - egui::ScrollArea::both() - .auto_shrink([false; 2]) - .show(ui, |ui| { - egui::Frame { - inner_margin: egui::Margin::same(re_ui::ReUi::view_padding()), - ..Default::default() - } - .show(ui, |ui| { - self.contents(ui, ctx, blueprint); - }); - }); - }, - ); } - #[allow(clippy::unused_self)] - fn contents( - &mut self, - ui: &mut egui::Ui, - ctx: &mut ViewerContext<'_>, - blueprint: &mut Blueprint, - ) { + fn contents(ui: &mut egui::Ui, ctx: &mut ViewerContext<'_>, viewport: &mut Viewport) { crate::profile_function!(); let query = ctx.current_query(); @@ -95,7 +50,7 @@ impl SelectionPanel { let selection = ctx.selection().to_vec(); for (i, item) in selection.iter().enumerate() { ui.push_id(i, |ui| { - what_is_selected_ui(ui, ctx, blueprint, item); + what_is_selected_ui(ui, ctx, viewport, item); if has_data_section(item) { ctx.re_ui.large_collapsing_header(ui, "Data", true, |ui| { @@ -105,7 +60,7 @@ impl SelectionPanel { ctx.re_ui .large_collapsing_header(ui, "Blueprint", true, |ui| { - blueprint_ui(ui, ctx, blueprint, item); + blueprint_ui(ui, ctx, viewport, item); }); if i + 1 < num_selections { @@ -115,6 +70,27 @@ impl SelectionPanel { }); } } + + pub fn selection_panel_options_ui( + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + viewport: &mut Viewport, + tab_bar_rect: egui::Rect, + ) { + let tab_bar_rect = tab_bar_rect.shrink2(egui::vec2(4.0, 0.0)); // Add some side margin outside the frame + + ui.allocate_ui_at_rect(tab_bar_rect, |ui| { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if let Some(selection) = ctx + .rec_cfg + .selection_state + .selection_ui(ctx.re_ui, ui, viewport) + { + ctx.set_multi_selection(selection.iter().cloned()); + } + }); + }); + } } fn has_data_section(item: &Item) -> bool { @@ -129,7 +105,7 @@ fn has_data_section(item: &Item) -> bool { pub fn what_is_selected_ui( ui: &mut egui::Ui, ctx: &mut ViewerContext<'_>, - blueprint: &mut Blueprint, + viewport: &mut Viewport, item: &Item, ) { match item { @@ -151,7 +127,7 @@ pub fn what_is_selected_ui( }); } Item::SpaceView(space_view_id) => { - if let Some(space_view) = blueprint.viewport.space_view_mut(space_view_id) { + if let Some(space_view) = viewport.space_view_mut(space_view_id) { ui.horizontal(|ui| { ui.label("Space view:"); ui.text_edit_singleline(&mut space_view.display_name); @@ -169,7 +145,7 @@ pub fn what_is_selected_ui( ui.end_row(); if let Some(space_view_id) = space_view_id { - if let Some(space_view) = blueprint.viewport.space_view_mut(space_view_id) { + if let Some(space_view) = viewport.space_view_mut(space_view_id) { ui.label("in Space View:"); ctx.space_view_button(ui, space_view); ui.end_row(); @@ -178,7 +154,7 @@ pub fn what_is_selected_ui( }); } Item::DataBlueprintGroup(space_view_id, data_blueprint_group_handle) => { - if let Some(space_view) = blueprint.viewport.space_view_mut(space_view_id) { + if let Some(space_view) = viewport.space_view_mut(space_view_id) { if let Some(group) = space_view .data_blueprint .group_mut(*data_blueprint_group_handle) @@ -238,12 +214,12 @@ impl DataUi for Item { fn blueprint_ui( ui: &mut egui::Ui, ctx: &mut ViewerContext<'_>, - blueprint: &mut Blueprint, + viewport: &mut Viewport, item: &Item, ) { match item { Item::ComponentPath(component_path) => { - list_existing_data_blueprints(ui, ctx, component_path.entity_path(), blueprint); + list_existing_data_blueprints(ui, ctx, component_path.entity_path(), viewport); } Item::SpaceView(space_view_id) => { @@ -253,8 +229,7 @@ fn blueprint_ui( .on_hover_text("Manually add or remove entities from the Space View.") .clicked() { - blueprint - .viewport + viewport .show_add_remove_entities_window(*space_view_id); } @@ -263,25 +238,25 @@ fn blueprint_ui( .on_hover_text("Create an exact duplicate of this Space View including all blueprint settings") .clicked() { - if let Some(space_view) = blueprint.viewport.space_view(space_view_id) { + if let Some(space_view) = viewport.space_view(space_view_id) { let mut new_space_view = space_view.clone(); new_space_view.id = super::SpaceViewId::random(); - blueprint.viewport.add_space_view(new_space_view); - blueprint.viewport.mark_user_interaction(); + viewport.add_space_view(new_space_view); + viewport.mark_user_interaction(); } } }); ui.add_space(ui.spacing().item_spacing.y); - if let Some(space_view) = blueprint.viewport.space_view_mut(space_view_id) { + if let Some(space_view) = viewport.space_view_mut(space_view_id) { space_view.selection_ui(ctx, ui); } } Item::InstancePath(space_view_id, instance_path) => { - if let Some(space_view) = space_view_id - .and_then(|space_view_id| blueprint.viewport.space_view_mut(&space_view_id)) + if let Some(space_view) = + space_view_id.and_then(|space_view_id| viewport.space_view_mut(&space_view_id)) { if instance_path.instance_key.is_specific() { ui.horizontal(|ui| { @@ -303,12 +278,12 @@ fn blueprint_ui( data_blueprint.set(instance_path.entity_path.clone(), props); } } else { - list_existing_data_blueprints(ui, ctx, &instance_path.entity_path, blueprint); + list_existing_data_blueprints(ui, ctx, &instance_path.entity_path, viewport); } } Item::DataBlueprintGroup(space_view_id, data_blueprint_group_handle) => { - if let Some(space_view) = blueprint.viewport.space_view_mut(space_view_id) { + if let Some(space_view) = viewport.space_view_mut(space_view_id) { if let Some(group) = space_view .data_blueprint .group_mut(*data_blueprint_group_handle) @@ -332,11 +307,9 @@ fn list_existing_data_blueprints( ui: &mut egui::Ui, ctx: &mut ViewerContext<'_>, entity_path: &EntityPath, - blueprint: &Blueprint, + viewport: &Viewport, ) { - let space_views_with_path = blueprint - .viewport - .space_views_containing_entity_path(entity_path); + let space_views_with_path = viewport.space_views_containing_entity_path(entity_path); if space_views_with_path.is_empty() { ui.weak("(Not shown in any Space View)"); @@ -346,7 +319,7 @@ fn list_existing_data_blueprints( ui.indent("list of data blueprints indent", |ui| { for space_view_id in &space_views_with_path { - if let Some(space_view) = blueprint.viewport.space_view(space_view_id) { + if let Some(space_view) = viewport.space_view(space_view_id) { ctx.entity_path_button_to( ui, Some(*space_view_id), @@ -409,35 +382,98 @@ fn entity_props_ui( }); } -fn colormap_props_ui(ui: &mut egui::Ui, entity_props: &mut EntityProperties) { - let current = *entity_props.color_mapper.get(); +fn colormap_props_ui( + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + entity_path: &EntityPath, + entity_props: &mut EntityProperties, +) { + // Color mapping picker + { + let current = *entity_props.color_mapper.get(); + ui.label("Color map"); + egui::ComboBox::from_id_source("depth_color_mapper") + .selected_text(current.to_string()) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + ui.set_min_width(64.0); + + let mut add_label = |proposed| { + if ui + .selectable_label(current == proposed, proposed.to_string()) + .clicked() + { + entity_props.color_mapper = EditableAutoValue::Auto(proposed); + } + }; + + add_label(ColorMapper::Colormap(Colormap::Grayscale)); + add_label(ColorMapper::Colormap(Colormap::Turbo)); + add_label(ColorMapper::Colormap(Colormap::Viridis)); + add_label(ColorMapper::Colormap(Colormap::Plasma)); + add_label(ColorMapper::Colormap(Colormap::Magma)); + add_label(ColorMapper::Colormap(Colormap::Inferno)); + add_label(ColorMapper::AlbedoTexture); + }); + ui.end_row(); + } + + if *entity_props.color_mapper.get() != ColorMapper::AlbedoTexture { + return; + } - ui.label("Color map"); - egui::ComboBox::from_id_source("color_mapper") - .selected_text(current.to_string()) - .show_ui(ui, |ui| { + // Albedo texture picker + if let Some(tree) = entity_path + .parent() + .and_then(|path| ctx.log_db.entity_db.tree.subtree(&path)) + { + let query = ctx.current_query(); + let current = entity_props.albedo_texture.clone(); + + ui.label("Albedo texture"); + + let mut combo = egui::ComboBox::from_id_source("depth_color_texture"); + if let Some(current) = current.as_ref() { + combo = combo.selected_text(current.to_string()); + } else { + // Select the first image-shaped tensor we find + // tree.visit_children_recursively(&mut |ent_path| { + // if entity_props.albedo_texture.is_some() { + // return; + // } + // let Some(tensor) = + // query_latest_single::(&ctx.log_db.entity_db, ent_path, &query) else { + // return; + // }; + // if tensor.is_shaped_like_an_image() { + // entity_props.albedo_texture = Some(ent_path.clone()); + // } + // }); + } + + combo.show_ui(ui, |ui| { ui.style_mut().wrap = Some(false); ui.set_min_width(64.0); - // TODO(cmc): that is not ideal but I don't want to import yet another proc-macro... - let mut add_label = |proposed| { - if ui - .selectable_label(current == proposed, proposed.to_string()) - .clicked() + tree.visit_children_recursively(&mut |ent_path| { + let Some(tensor) = query_latest_single::( + &ctx.log_db.entity_db, + ent_path, + &query, + ) else { + return; + }; + + if tensor.is_shaped_like_an_image() + && ui + .selectable_label(current.as_ref() == Some(ent_path), ent_path.to_string()) + .clicked() { - entity_props.color_mapper = EditableAutoValue::Auto(proposed); + entity_props.albedo_texture = Some(ent_path.clone()); } - }; - - add_label(ColorMapper::Colormap(Colormap::Grayscale)); - add_label(ColorMapper::Colormap(Colormap::Turbo)); - add_label(ColorMapper::Colormap(Colormap::Viridis)); - add_label(ColorMapper::Colormap(Colormap::Plasma)); - add_label(ColorMapper::Colormap(Colormap::Magma)); - add_label(ColorMapper::Colormap(Colormap::Inferno)); + }); }); - - ui.end_row(); + } } fn pinhole_props_ui( @@ -510,9 +546,25 @@ fn depth_props_ui( backproject_radius_scale_ui(ui, &mut entity_props.backproject_radius_scale); + ui.label("Backproject radius scale"); + let mut radius_scale = *entity_props.backproject_radius_scale.get(); + let speed = (radius_scale * 0.001).at_least(0.001); + if ui + .add( + egui::DragValue::new(&mut radius_scale) + .clamp_range(0.0..=1.0e8) + .speed(speed), + ) + .on_hover_text("Scales the radii of the points in the backprojected point cloud") + .changed() + { + entity_props.backproject_radius_scale = EditableAutoValue::UserEdited(radius_scale); + } + ui.end_row(); + // TODO(cmc): This should apply to the depth map entity as a whole, but for that we // need to get the current hardcoded colormapping out of the image cache first. - colormap_props_ui(ui, entity_props); + colormap_props_ui(ctx, ui, entity_path, entity_props); } Some(()) @@ -522,6 +574,7 @@ fn depth_from_world_scale_ui(ui: &mut egui::Ui, property: &mut EditableAutoValue ui.label("Backproject meter"); let mut value = *property.get(); let speed = (value * 0.05).at_least(0.01); + let response = ui .add( egui::DragValue::new(&mut value) @@ -563,4 +616,4 @@ fn backproject_radius_scale_ui(ui: &mut egui::Ui, property: &mut EditableAutoVal *property = EditableAutoValue::UserEdited(value); } ui.end_row(); -} +} \ No newline at end of file diff --git a/crates/re_viewer/src/ui/space_view.rs b/crates/re_viewer/src/ui/space_view.rs index 016491107a31..365c151cfd6b 100644 --- a/crates/re_viewer/src/ui/space_view.rs +++ b/crates/re_viewer/src/ui/space_view.rs @@ -3,14 +3,15 @@ use re_data_store::{EntityPath, EntityPropertyMap, EntityTree, InstancePath, Tim use re_renderer::{GpuReadbackIdentifier, ScreenshotProcessor}; use crate::{ + depthai::depthai, misc::{space_info::SpaceInfoCollection, SpaceViewHighlights, TransformCache, ViewerContext}, ui::view_category::categorize_entity_path, }; use super::{ data_blueprint::DataBlueprintTree, space_view_heuristics::default_queried_entities, - view_bar_chart, view_category::ViewCategory, view_spatial, view_tensor, view_text, - view_time_series, + view_bar_chart, view_category::ViewCategory, view_node_graph, view_spatial, view_tensor, + view_text, view_time_series, }; // ---------------------------------------------------------------------------- @@ -21,6 +22,14 @@ use super::{ )] pub struct SpaceViewId(uuid::Uuid); +#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)] +pub enum SpaceViewKind { + Data, + Stats, + Config, + Selection, +} + impl SpaceViewId { pub fn random() -> Self { Self(uuid::Uuid::new_v4()) @@ -66,6 +75,8 @@ pub struct SpaceView { /// True if the user is expected to add entities themselves. False otherwise. pub entities_determined_by_user: bool, + + pub is_depthai_spaceview: bool, } impl SpaceView { @@ -78,11 +89,31 @@ impl SpaceView { // this led to somewhat confusing and inconsistent behavior. See https://github.com/rerun-io/rerun/issues/1220 // Spaces are now always named after the final element of the space-path (or the root), independent of the // query entities. - let display_name = if let Some(name) = space_path.iter().last() { - name.to_string() - } else { - // Include category name in the display for root paths because they look a tad bit too short otherwise. - format!("/ ({category})") + let mut is_depthai_spaceview = true; + let display_name = match space_path { + ep if ep.hash() == depthai::entity_paths::RGB_PINHOLE_CAMERA.hash() => { + "Color camera (2D)".into() + } + ep if ep.hash() == depthai::entity_paths::COLOR_CAM_3D.hash() => { + "Color camera (3D)".into() + } + ep if ep.hash() == depthai::entity_paths::RIGHT_PINHOLE_CAMERA.hash() => { + "Right mono camera (2D)".into() + } + ep if ep.hash() == depthai::entity_paths::LEFT_PINHOLE_CAMERA.hash() => { + "Left mono camera (2D)".into() + } + ep if ep.hash() == depthai::entity_paths::MONO_CAM_3D.hash() => { + "Mono cameras (3D)".into() + } + _ => { + is_depthai_spaceview = false; + if let Some(entity_path_part) = space_path.iter().last() { + entity_path_part.to_string() + } else { + format!("/ ({category})") + } + } }; let mut data_blueprint_tree = DataBlueprintTree::default(); @@ -97,6 +128,7 @@ impl SpaceView { view_state: ViewState::default(), category, entities_determined_by_user: false, + is_depthai_spaceview, } } @@ -191,6 +223,7 @@ impl SpaceView { } } } + ViewCategory::NodeGraph => self.view_state.state_node_graph.selection_ui(ctx.re_ui, ui), } } @@ -262,6 +295,11 @@ impl SpaceView { scene.load(ctx, &query); self.view_state.ui_tensor(ctx, ui, &scene); } + ViewCategory::NodeGraph => { + let mut scene = view_node_graph::SceneNodeGraph::default(); + scene.load(ctx, &query); + self.view_state.ui_node_graph(ctx, ui, &scene); + } }; } @@ -324,6 +362,7 @@ pub struct ViewState { state_bar_chart: view_bar_chart::BarChartState, pub state_spatial: view_spatial::ViewSpatialState, state_tensors: ahash::HashMap, + state_node_graph: view_node_graph::ViewNodeGraphState, } impl ViewState { @@ -410,6 +449,21 @@ impl ViewState { }); } + fn ui_node_graph( + &mut self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + scene: &view_node_graph::SceneNodeGraph, + ) { + egui::Frame { + inner_margin: re_ui::ReUi::view_padding().into(), + ..egui::Frame::default() + } + .show(ui, |ui| { + view_node_graph::view_node_graph(ctx, ui, &mut self.state_node_graph, scene) + }); + } + fn ui_bar_chart( &mut self, ctx: &mut ViewerContext<'_>, diff --git a/crates/re_viewer/src/ui/space_view_heuristics.rs b/crates/re_viewer/src/ui/space_view_heuristics.rs index df1da9956907..88181e47cd75 100644 --- a/crates/re_viewer/src/ui/space_view_heuristics.rs +++ b/crates/re_viewer/src/ui/space_view_heuristics.rs @@ -4,8 +4,8 @@ use ahash::HashMap; use itertools::Itertools; use nohash_hasher::IntSet; use re_arrow_store::{DataStore, LatestAtQuery, Timeline}; -use re_data_store::{log_db::EntityDb, query_latest_single, ComponentName, EntityPath}; -use re_log_types::{component_types::Tensor, Component}; +use re_data_store::{log_db::EntityDb, query_latest_single, ComponentName, EntityPath, EntityTree}; +use re_log_types::{component_types::Tensor, Component, EntityPathPart}; use crate::{ misc::{space_info::SpaceInfoCollection, ViewerContext}, @@ -22,8 +22,24 @@ pub fn all_possible_space_views( crate::profile_function!(); // Everything with a SpaceInfo is a candidate (that is root + whenever there is a transform), - // as well as all direct descendants of the root. - let root_children = &ctx.log_db.entity_db.tree.children; + // as well as all direct descendants of the root that have some messages. + let root_children = &ctx + .log_db + .entity_db + .tree + .children + .iter() + .filter(|(_k, v)| { + let timelines = v.prefix_times.timelines(); + let mut total_msgs = 0; + for timeline in timelines { + if let Some(hist) = v.prefix_times.get(timeline) { + total_msgs += hist.total_count(); + } + } + total_msgs != 0 + }) + .collect::>(); let candidate_space_paths = spaces_info .iter() .map(|info| &info.path) @@ -111,6 +127,11 @@ fn is_interesting_space_view_not_at_root( false } +/// Function to create the non data space views. Configuration panel and blueprint panel. +fn create_blueprint_and_selection_space_view() { + // let mut config_viewport = +} + /// List out all space views we generate by default for the available data. pub fn default_created_space_views( ctx: &ViewerContext<'_>, @@ -256,6 +277,8 @@ fn is_default_added_to_space_view( re_log_types::component_types::InstanceKey::name(), re_log_types::component_types::KeypointId::name(), DataStore::insert_id_key(), + re_log_types::ImuData::name(), // Separate plotting view for IMU data. + re_log_types::XlinkStats::name(), // Separate plotting view for XLink stats. ]; entity_path.is_descendant_of(space_path) @@ -293,7 +316,6 @@ pub fn default_queried_entities( .cloned(), ); }); - entities } diff --git a/crates/re_viewer/src/ui/stats_panel.rs b/crates/re_viewer/src/ui/stats_panel.rs new file mode 100644 index 000000000000..e05199f5d8bf --- /dev/null +++ b/crates/re_viewer/src/ui/stats_panel.rs @@ -0,0 +1,335 @@ +use egui::{ + emath::History, + plot::{Line, Plot, PlotPoints}, +}; +use egui_dock::{TabViewer, Tree}; +use itertools::Itertools; +use re_arrow_store::{LatestAtQuery, TimeInt, Timeline}; +use re_log_types::{ + component_types::{ImuData, XlinkStats}, + Component, +}; +use strum::{EnumIter, IntoEnumIterator}; + +use crate::{depthai::depthai, misc::ViewerContext}; + +use super::Viewport; + +pub struct StatsPanel {} + +impl StatsPanel { + pub fn show_panel(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, state: &mut StatsPanelState) { + let mut tree = state.tree.clone(); // Have to clone to avoid borrowing issue + state.imu_tab_visible = false; // Check every frame if the IMU tab is visible + egui_dock::DockArea::new(&mut tree) + .id(egui::Id::new("stats_panel")) + .style(re_ui::egui_dock_style(ui.style())) + .show_inside(ui, &mut StatsTabs { ctx, state }); + state.tree = tree; + } + + pub fn stats_panel_options_ui( + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + viewport: &mut Viewport, + tab_bar_rect: egui::Rect, + ) { + } +} + +#[derive(Debug, Copy, Clone, EnumIter, PartialEq, Eq)] +enum ImuTabKind { + Accel, + Gyro, + Mag, +} + +#[derive(Debug, Copy, Clone, EnumIter, PartialEq, Eq)] +enum Xyz { + X, + Y, + Z, +} + +#[derive(Debug, Copy, Clone, EnumIter, PartialEq, Eq)] +pub enum StatTabKind { + Imu, + Xlink, +} + +pub struct StatsPanelState { + tree: Tree, + accel_history: History<[f32; 3]>, + gyro_history: History<[f32; 3]>, + magnetometer_history: History<[f32; 3]>, + start_time: instant::Instant, // Time elapsed from spawning the app + imu_tab_visible: bool, // Used to subscribe and unsubscribe from the IMU data + xlink_stats_history: History<[f64; 4]>, // [MB written in last time frame, MB read in last time frame, MB written total, MB read total] + avg_xlink_stats_plot_history: History<[f64; 2]>, // [Avg MB written, Avg MB read] +} + +impl Default for StatsPanelState { + fn default() -> Self { + Self { + tree: Tree::new(StatTabKind::iter().collect_vec()), + accel_history: History::new(0..1000, 5.0), + gyro_history: History::new(0..1000, 5.0), + magnetometer_history: History::new(0..1000, 5.0), + start_time: instant::Instant::now(), + imu_tab_visible: false, + xlink_stats_history: History::new(0..1000, 1.0), + avg_xlink_stats_plot_history: History::new(0..1000, 5.0), + } + } +} + +impl StatsPanelState { + pub fn update(&mut self, ctx: &mut ViewerContext<'_>) { + self.update_imu(ctx); + self.update_xlink(ctx); + } + + /// Push new data into the history buffers. + fn update_imu(&mut self, ctx: &mut ViewerContext<'_>) { + self.update_imu_subscription(ctx); + let now = self.start_time.elapsed().as_secs_f64(); + let imu_entity_path = &ImuData::entity_path(); + if let Ok(latest) = re_query::query_entity_with_primary::( + &ctx.log_db.entity_db.data_store, + &LatestAtQuery::new(Timeline::log_time(), TimeInt::MAX), + imu_entity_path, + &[ImuData::name()], + ) { + let _ = latest.visit1(|_inst, imu_data| { + self.accel_history + .add(now, [imu_data.accel.x, imu_data.accel.y, imu_data.accel.z]); + self.gyro_history + .add(now, [imu_data.gyro.x, imu_data.gyro.y, imu_data.gyro.z]); + if let Some(mag) = imu_data.mag { + self.magnetometer_history.add(now, [mag.x, mag.y, mag.z]); + } + }); + } + } + + fn update_xlink(&mut self, ctx: &mut ViewerContext<'_>) { + let now = self.start_time.elapsed().as_secs_f64(); + let entity_path = &XlinkStats::entity_path(); + if let Ok(latest) = re_query::query_entity_with_primary::( + &ctx.log_db.entity_db.data_store, + &LatestAtQuery::new(Timeline::log_time(), TimeInt::MAX), + entity_path, + &[XlinkStats::name()], + ) { + let _ = latest.visit1(|_inst, xlink_stats| { + let (mut written, mut read) = ( + (xlink_stats.bytes_written / 1e6 as i64) as f64, + (xlink_stats.bytes_read / 1e6 as i64) as f64, + ); + if let Some((time, [_, _, total_written, total_read])) = + self.xlink_stats_history.iter().last() + { + written = (written - total_written) / (now - time); + read = (read - total_read) / (now - time); + } + + self.xlink_stats_history.add( + now, + [ + written, + read, + (xlink_stats.bytes_written / 1e6 as i64) as f64, + (xlink_stats.bytes_read / 1e6 as i64) as f64, + ], + ); + self.avg_xlink_stats_plot_history.add( + now, + [ + self.xlink_stats_history + .iter() + .map(|(_, [written, _, _, _])| written) + .sum::() + / self.xlink_stats_history.len() as f64, + self.xlink_stats_history + .iter() + .map(|(_, [_, read, _, _])| read) + .sum::() + / self.xlink_stats_history.len() as f64, + ], + ); + }); + } + } + + pub fn update_imu_subscription(&mut self, ctx: &mut ViewerContext<'_>) { + let unsub = !self.imu_tab_visible + && ctx + .depthai_state + .subscriptions + .contains(&depthai::ChannelId::ImuData); + if unsub { + let subs = ctx + .depthai_state + .subscriptions + .iter() + .filter_map(|x| { + if x != &depthai::ChannelId::ImuData { + return Some(x.clone()); + } else { + return None; + } + }) + .collect_vec(); + ctx.depthai_state.set_subscriptions(&subs); + self.accel_history.clear(); + self.gyro_history.clear(); + self.magnetometer_history.clear(); + } else if self.imu_tab_visible + && !ctx + .depthai_state + .subscriptions + .contains(&depthai::ChannelId::ImuData) + { + let mut subs = ctx.depthai_state.subscriptions.clone(); + subs.push(depthai::ChannelId::ImuData); + ctx.depthai_state.set_subscriptions(&subs); + } + } +} + +struct StatsTabs<'a, 'b> { + ctx: &'a mut ViewerContext<'b>, + state: &'a mut StatsPanelState, +} + +impl<'a, 'b> StatsTabs<'a, 'b> { + fn imu_ui(&mut self, ui: &mut egui::Ui) { + let imu_entity_path = &ImuData::entity_path(); + egui::ScrollArea::both().show(ui, |ui| { + egui::Frame { + inner_margin: egui::Margin::same(re_ui::ReUi::view_padding()), + ..Default::default() + } + .show(ui, |ui| { + let max_width = ui.available_width(); + for kind in ImuTabKind::iter() { + self.xyz_plot_ui(ui, kind, max_width); + } + }); + }); + } + + fn xlink_ui(&mut self, ui: &mut egui::Ui) { + ui.vertical(|ui| { + egui::Frame { + inner_margin: egui::Margin::same(re_ui::ReUi::view_padding()), + ..Default::default() + } + .show(ui, |ui| { + let (history, display_name, unit) = ( + &mut self.state.avg_xlink_stats_plot_history, + "XLink throughput", + "", + ); + let Some(latest) = history.latest() else { + ui.label(format!("No {display_name} data yet")); + return; + }; + ui.label(format!( + "{display_name}: avg. Sent from device {:.2} MB/s, avg. Sent to Device: {:.2} MB/s", + latest[0], latest[1] + )); + Plot::new(display_name).show(ui, |plot_ui| { + plot_ui.line( + Line::new(PlotPoints::new( + history + .iter() + .map(|(t, [written, _])| [t, written]) + .collect_vec(), + )) + .color(egui::Color32::BLUE), + ); + plot_ui.line( + Line::new(PlotPoints::new( + history.iter().map(|(t, [_, read])| [t, read]).collect_vec(), + )) + .color(egui::Color32::RED), + ); + }); + }); + }); + } + + fn xyz_plot_ui(&mut self, ui: &mut egui::Ui, kind: ImuTabKind, max_width: f32) { + ui.vertical(|ui| { + let (history, display_name, unit) = match kind { + ImuTabKind::Accel => (&mut self.state.accel_history, "Accelerometer", "(m/s^2)"), + ImuTabKind::Gyro => (&mut self.state.gyro_history, "Gyroscope", "(rad/s)"), + ImuTabKind::Mag => (&mut self.state.magnetometer_history, "Magnetometer", "(uT)"), + }; + let Some(latest) = history.latest() else { + ui.label(format!("No {display_name} data yet")); + return; + }; + ui.label(display_name); + ui.add_sized([max_width, 150.0], |ui: &mut egui::Ui| { + ui.horizontal(|ui| { + ui.add_sized([max_width, 150.0], |ui: &mut egui::Ui| { + Plot::new(format!("{kind:?}")) + .allow_drag(false) + .allow_zoom(false) + .allow_scroll(false) + .show(ui, |plot_ui| { + for axis in Xyz::iter() { + plot_ui.line( + Line::new(PlotPoints::new( + (*history) + .iter() + .map(|(t, v)| [t, v[axis as usize].into()]) + .collect_vec(), + )) + .color(match axis + { + Xyz::X => egui::Color32::RED, + Xyz::Y => egui::Color32::GREEN, + Xyz::Z => egui::Color32::BLUE, + }), + ); + } + }) + .response + }); + }) + .response + }); + + ui.label(format!( + "{display_name}: ({:.2}, {:.2}, {:.2}) {unit}", + latest[0], latest[1], latest[2] + )); + }); + } +} + +impl<'a, 'b> TabViewer for StatsTabs<'a, 'b> { + type Tab = StatTabKind; + + fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { + match tab { + StatTabKind::Imu => { + self.state.imu_tab_visible = true; + self.imu_ui(ui); + } + StatTabKind::Xlink => { + self.xlink_ui(ui); + } + }; + } + + fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { + match tab { + StatTabKind::Imu => "IMU".into(), + StatTabKind::Xlink => "Xlink".into(), + } + } +} diff --git a/crates/re_viewer/src/ui/time_panel/data_density_graph.rs b/crates/re_viewer/src/ui/time_panel/data_density_graph.rs index bdf311b4ccf2..83b48a7ab007 100644 --- a/crates/re_viewer/src/ui/time_panel/data_density_graph.rs +++ b/crates/re_viewer/src/ui/time_panel/data_density_graph.rs @@ -540,7 +540,7 @@ fn show_row_ids_tooltip( } ui.add_space(8.0); - crate::ui::selection_panel::what_is_selected_ui(ui, ctx, blueprint, item); + crate::ui::selection_panel::what_is_selected_ui(ui, ctx, &mut blueprint.viewport, item); ui.add_space(8.0); let timeline = *ctx.rec_cfg.time_ctrl.timeline(); diff --git a/crates/re_viewer/src/ui/time_panel/mod.rs b/crates/re_viewer/src/ui/time_panel/mod.rs index e106cb4ed74a..73e8476ce531 100644 --- a/crates/re_viewer/src/ui/time_panel/mod.rs +++ b/crates/re_viewer/src/ui/time_panel/mod.rs @@ -525,7 +525,7 @@ impl TimePanel { if is_visible { response.on_hover_ui(|ui| { let item = Item::ComponentPath(component_path.clone()); - what_is_selected_ui(ui, ctx, blueprint, &item); + what_is_selected_ui(ui, ctx, &mut blueprint.viewport, &item); ui.add_space(8.0); let query = ctx.current_query(); component_path.data_ui(ctx, ui, super::UiVerbosity::Small, &query); diff --git a/crates/re_viewer/src/ui/view_category.rs b/crates/re_viewer/src/ui/view_category.rs index 9a23512e42af..d0166aa2815e 100644 --- a/crates/re_viewer/src/ui/view_category.rs +++ b/crates/re_viewer/src/ui/view_category.rs @@ -2,7 +2,8 @@ use re_arrow_store::{LatestAtQuery, TimeInt}; use re_data_store::{EntityPath, LogDb, Timeline}; use re_log_types::{ component_types::{ - Box3D, LineStrip2D, LineStrip3D, Point2D, Point3D, Rect2D, Scalar, Tensor, TextEntry, + Box3D, LineStrip2D, LineStrip3D, NodeGraph, Point2D, Point3D, Rect2D, Scalar, Tensor, + TextEntry, }, Arrow3D, Component, Mesh3D, Transform, }; @@ -29,6 +30,7 @@ pub enum ViewCategory { /// High-dimensional tensor view Tensor, + NodeGraph, } impl ViewCategory { @@ -39,6 +41,7 @@ impl ViewCategory { ViewCategory::BarChart => &re_ui::icons::SPACE_VIEW_HISTOGRAM, ViewCategory::Spatial => &re_ui::icons::SPACE_VIEW_3D, ViewCategory::Tensor => &re_ui::icons::SPACE_VIEW_TENSOR, + ViewCategory::NodeGraph => &re_ui::icons::SPACE_VIEW_TENSOR, // TODO(filip): add icon } } } @@ -51,6 +54,7 @@ impl std::fmt::Display for ViewCategory { ViewCategory::BarChart => "Bar Chart", ViewCategory::Spatial => "Spatial", ViewCategory::Tensor => "Tensor", + ViewCategory::NodeGraph => "Node Graph", }) } } @@ -111,6 +115,8 @@ pub fn categorize_entity_path( } } } + } else if component == NodeGraph::name() { + set.insert(ViewCategory::NodeGraph); } } diff --git a/crates/re_viewer/src/ui/view_node_graph/mod.rs b/crates/re_viewer/src/ui/view_node_graph/mod.rs new file mode 100644 index 000000000000..2839c6b28214 --- /dev/null +++ b/crates/re_viewer/src/ui/view_node_graph/mod.rs @@ -0,0 +1,5 @@ +mod scene; +pub(crate) use self::scene::{NodeGraphEntry, SceneNodeGraph}; + +mod ui; +pub(crate) use self::ui::{view_node_graph, ViewNodeGraphState}; diff --git a/crates/re_viewer/src/ui/view_node_graph/scene.rs b/crates/re_viewer/src/ui/view_node_graph/scene.rs new file mode 100644 index 000000000000..0f9b7c0361ea --- /dev/null +++ b/crates/re_viewer/src/ui/view_node_graph/scene.rs @@ -0,0 +1,34 @@ +use crate::{ui::SceneQuery, ViewerContext}; +use re_data_store::EntityPath; +// --- + +#[derive(Debug, Clone)] +pub struct NodeGraphEntry { + pub entity_path: EntityPath, + + /// `None` for timeless data. + pub time: Option, + + pub color: Option<[u8; 4]>, + + pub level: Option, + + pub body: String, +} + +/// A NodeGraph scene, with everything needed to render it. +#[derive(Default)] +pub struct SceneNodeGraph { + pub NodeGraph_entries: Vec, +} + +impl SceneNodeGraph { + /// Loads all NodeGraph components into the scene according to the given query. + pub(crate) fn load(&mut self, ctx: &ViewerContext<'_>, query: &SceneQuery<'_>) { + crate::profile_function!(); + + let store = &ctx.log_db.entity_db.data_store; + + for entity_path in query.entity_paths {} + } +} diff --git a/crates/re_viewer/src/ui/view_node_graph/ui.rs b/crates/re_viewer/src/ui/view_node_graph/ui.rs new file mode 100644 index 000000000000..5b68f0dc0ecc --- /dev/null +++ b/crates/re_viewer/src/ui/view_node_graph/ui.rs @@ -0,0 +1,91 @@ +use std::collections::BTreeMap; +use crate::ViewerContext; +use re_data_store::{EntityPath, Timeline}; + +use super::{NodeGraphEntry, SceneNodeGraph}; +// --- Main view --- + +#[derive(Clone, Default, serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct ViewNodeGraphState { + /// Keeps track of the latest time selection made by the user. + /// + /// We need this because we want the user to be able to manually scroll the + /// NodeGraph entry window however they please when the time cursor isn't moving. + latest_time: i64, + + pub filters: ViewNodeGraphFilters, + + monospace: bool, +} + +impl ViewNodeGraphState { + pub fn selection_ui(&mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui) { + crate::profile_function!(); + re_log::info!("Holda from node graph"); + } +} + +pub(crate) fn view_node_graph( + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + state: &mut ViewNodeGraphState, + scene: &SceneNodeGraph, +) -> egui::Response { + crate::profile_function!(); + + ui.with_layout(egui::Layout::top_down(egui::Align::Center), |ui| { + if ui.button("Button text").clicked() { + re_log::info!("Holda from node graph"); + } + }) + .response +} + +// --- Filters --- + +// TODO(cmc): implement "body contains " filter. +// TODO(cmc): beyond filters, it'd be nice to be able to swap columns at some point. +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct ViewNodeGraphFilters { + // Column filters: which columns should be visible? + // Timelines are special: each one has a dedicated column. + pub col_timelines: BTreeMap, + pub col_entity_path: bool, + pub col_log_level: bool, + + // Row filters: which rows should be visible? + pub row_entity_paths: BTreeMap, + pub row_log_levels: BTreeMap, +} + +impl Default for ViewNodeGraphFilters { + fn default() -> Self { + Self { + col_entity_path: true, + col_log_level: true, + col_timelines: Default::default(), + row_entity_paths: Default::default(), + row_log_levels: Default::default(), + } + } +} + +impl ViewNodeGraphFilters { + pub fn is_entity_path_visible(&self, entity_path: &EntityPath) -> bool { + self.row_entity_paths + .get(entity_path) + .copied() + .unwrap_or(true) + } + + pub fn is_log_level_visible(&self, level: &str) -> bool { + self.row_log_levels.get(level).copied().unwrap_or(true) + } + + // Checks whether new values are available for any of the filters, and updates everything + // accordingly. + fn update(&mut self, ctx: &mut ViewerContext<'_>, nodegraph_entries: &[NodeGraphEntry]) { + crate::profile_function!(); + } +} diff --git a/crates/re_viewer/src/ui/view_spatial/eye.rs b/crates/re_viewer/src/ui/view_spatial/eye.rs index 287481311101..d1909b7cfee5 100644 --- a/crates/re_viewer/src/ui/view_spatial/eye.rs +++ b/crates/re_viewer/src/ui/view_spatial/eye.rs @@ -165,6 +165,7 @@ impl Eye { pub struct OrbitEye { pub orbit_center: Vec3, pub orbit_radius: f32, + pub world_from_view_rot: Quat, pub fov_y: f32, @@ -173,11 +174,38 @@ pub struct OrbitEye { /// For controlling the eye with WSAD in a smooth way. pub velocity: Vec3, + + /// Left over scroll delta that still needs to be applied (smoothed out over several frames) + #[serde(skip)] + unprocessed_scroll_delta: f32, } impl OrbitEye { const MAX_PITCH: f32 = 0.999 * 0.25 * std::f32::consts::TAU; + /// Scroll wheels delta are capped out at this value per second. Anything above is smoothed out over several frames. + /// + /// We generally only want this to only kick in when the user scrolls fast while we maintain very high framerate, + /// so don't go too low! + /// + /// To give a sense of ballpark: + /// * measured 14.0 as the value of a single notch on a logitech mouse wheel connected to a Macbook returns in a single frame (!) + /// (so scrolling 10 notches in a tenth of a second gives a per second scroll delta of 1400) + /// * macbook trackpad is typically at max 1.0 in every given frame + const MAX_SCROLL_DELTA_PER_SECOND: f32 = 1000.0; + + pub fn new(orbit_center: Vec3, orbit_radius: f32, world_from_view_rot: Quat, up: Vec3) -> Self { + OrbitEye { + orbit_center, + orbit_radius, + world_from_view_rot, + fov_y: Eye::DEFAULT_FOV_Y, + up, + velocity: Vec3::ZERO, + unprocessed_scroll_delta: 0.0, + } + } + pub fn position(&self) -> Vec3 { self.orbit_center + self.world_from_view_rot * vec3(0.0, 0.0, self.orbit_radius) } @@ -213,6 +241,10 @@ impl OrbitEye { fov_y: egui::lerp(self.fov_y..=other.fov_y, t), up: self.up.lerp(other.up, t).normalize_or_zero(), velocity: self.velocity.lerp(other.velocity, t), + unprocessed_scroll_delta: lerp( + self.unprocessed_scroll_delta..=other.unprocessed_scroll_delta, + t, + ), } } @@ -261,8 +293,9 @@ impl OrbitEye { } } - /// Returns `true` if any change - pub fn interact(&mut self, response: &egui::Response, drag_threshold: f32) -> bool { + /// Returns `true` if interaction occurred. + /// I.e. the camera changed via user input. + pub fn update(&mut self, response: &egui::Response, drag_threshold: f32) -> bool { let mut did_interact = false; if response.drag_delta().length() > drag_threshold { @@ -283,21 +316,42 @@ impl OrbitEye { } } - if response.hovered() { + let (zoom_delta, raw_scroll_delta) = if response.hovered() { self.keyboard_navigation(&response.ctx); - let factor = response - .ctx - .input(|i| i.zoom_delta() * (i.scroll_delta.y / 200.0).exp()); - if factor != 1.0 { - let new_radius = self.orbit_radius / factor; - - // Don't let radius go too small or too big because this might cause infinity/nan in some calculations. - // Max value is chosen with some generous margin of an observed crash due to infinity. - if f32::MIN_POSITIVE < new_radius && new_radius < 1.0e17 { - self.orbit_radius = new_radius; - } + response.ctx.input(|i| (i.zoom_delta(), i.scroll_delta.y)) + } else { + (1.0, 0.0) + }; + if zoom_delta != 1.0 || raw_scroll_delta != 0.0 { + did_interact = true; + } - did_interact = true; + // Mouse wheels often go very large steps! + // This makes the zoom speed feel clunky, so we smooth it out over several frames. + let frame_delta = response.ctx.input(|i| i.stable_dt).at_most(0.1); + let accumulated_scroll_delta = raw_scroll_delta + self.unprocessed_scroll_delta; + let unsmoothed_scroll_per_second = accumulated_scroll_delta / frame_delta; + let scroll_dir = unsmoothed_scroll_per_second.signum(); + let scroll_delta = scroll_dir + * unsmoothed_scroll_per_second + .abs() + .at_most(Self::MAX_SCROLL_DELTA_PER_SECOND) + * frame_delta; + self.unprocessed_scroll_delta = accumulated_scroll_delta - scroll_delta; + + if self.unprocessed_scroll_delta.abs() > 0.1 { + // We have a lot of unprocessed scroll delta, so we need to keep calling this function. + response.ctx.request_repaint(); + } + + let zoom_factor = zoom_delta * (scroll_delta / 200.0).exp(); + if zoom_factor != 1.0 { + let new_radius = self.orbit_radius / zoom_factor; + + // Don't let radius go too small or too big because this might cause infinity/nan in some calculations. + // Max value is chosen with some generous margin of an observed crash due to infinity. + if f32::MIN_POSITIVE < new_radius && new_radius < 1.0e17 { + self.orbit_radius = new_radius; } } diff --git a/crates/re_viewer/src/ui/view_spatial/scene/mod.rs b/crates/re_viewer/src/ui/view_spatial/scene/mod.rs index 0afad5c77dd3..81c2688a943a 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/mod.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/mod.rs @@ -7,7 +7,7 @@ use re_log_types::{ component_types::{ClassId, InstanceKey, KeypointId}, DecodedTensor, MeshId, }; -use re_renderer::{Color32, OutlineMaskPreference, Size}; +use re_renderer::{renderer::TexturedRect, Color32, OutlineMaskPreference, Size}; use crate::{ misc::{mesh_loader::LoadedMesh, SpaceViewHighlights, TransformCache, ViewerContext}, @@ -63,15 +63,8 @@ pub struct Image { pub tensor: DecodedTensor, - /// If this is a depth map, how long is a meter? - /// - /// For example, with a `u16` dtype one might have - /// `meter == 1000.0` for millimeter precision - /// up to a ~65m range. - pub meter: Option, - - /// A thing that provides additional semantic context for your dtype. - pub annotations: Arc, + /// Textured rectangle for the renderer. + pub textured_rect: TexturedRect, } pub enum UiLabelTarget { @@ -104,9 +97,6 @@ pub struct SceneSpatialUiData { /// Picking any any of these rects cause the referred instance to be hovered. /// Only use this for 2d overlays! pub pickable_ui_rects: Vec<(egui::Rect, InstancePathHash)>, - - /// Images are a special case of rects where we're storing some extra information to allow miniature previews etc. - pub images: Vec, } pub struct SceneSpatial { @@ -231,7 +221,7 @@ impl SceneSpatial { return SpatialNavigationMode::ThreeD; } - if !self.ui.images.is_empty() { + if !self.primitives.images.is_empty() { return SpatialNavigationMode::TwoD; } if self.num_logged_3d_objects == 0 { diff --git a/crates/re_viewer/src/ui/view_spatial/scene/picking.rs b/crates/re_viewer/src/ui/view_spatial/scene/picking.rs index ddf73c2fdfbf..8d8325fdff1a 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/picking.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/picking.rs @@ -2,10 +2,10 @@ use ahash::HashSet; use re_data_store::InstancePathHash; -use re_log_types::{component_types::InstanceKey, EntityPathHash}; +use re_log_types::component_types::InstanceKey; use re_renderer::PickingLayerProcessor; -use super::{SceneSpatialPrimitives, SceneSpatialUiData}; +use super::{Image, SceneSpatialPrimitives, SceneSpatialUiData}; use crate::{ misc::instance_hash_conversions::instance_path_hash_from_picking_layer_id, ui::view_spatial::eye::Eye, @@ -116,11 +116,7 @@ impl PickingContext { self, previous_picking_result, ); - let mut rect_hits = picking_textured_rects( - self, - &primitives.textured_rectangles, - &primitives.textured_rectangles_ids, - ); + let mut rect_hits = picking_textured_rects(self, &primitives.images); rect_hits.sort_by(|a, b| b.depth_offset.cmp(&a.depth_offset)); let ui_rect_hits = picking_ui_rects(self, ui_data); @@ -241,23 +237,13 @@ fn picking_gpu( } } -fn picking_textured_rects( - context: &PickingContext, - textured_rectangles: &[re_renderer::renderer::TexturedRect], - textured_rectangles_ids: &[EntityPathHash], -) -> Vec { +fn picking_textured_rects(context: &PickingContext, images: &[Image]) -> Vec { crate::profile_function!(); let mut hits = Vec::new(); - for (rect, id) in textured_rectangles - .iter() - .zip(textured_rectangles_ids.iter()) - { - if !id.is_some() { - continue; - } - + for image in images { + let rect = &image.textured_rect; let rect_plane = macaw::Plane3::from_normal_point( rect.extent_u.cross(rect.extent_v).normalize(), rect.top_left_corner_position, @@ -277,7 +263,7 @@ fn picking_textured_rects( if (0.0..=1.0).contains(&u) && (0.0..=1.0).contains(&v) { hits.push(PickingRayHit { instance_path_hash: InstancePathHash { - entity_path_hash: *id, + entity_path_hash: image.ent_path.hash(), instance_key: InstanceKey::from_2d_image_coordinate( [ (u * rect.colormapped_texture.texture.width() as f32) as u32, diff --git a/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs b/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs index 392f0bde4fa1..407d1eec47fa 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/primitives.rs @@ -1,6 +1,6 @@ use egui::Color32; use re_data_store::EntityPath; -use re_log_types::{component_types::InstanceKey, EntityPathHash}; +use re_log_types::component_types::InstanceKey; use re_renderer::{ renderer::{DepthClouds, MeshInstance}, LineStripSeriesBuilder, PointCloudBuilder, @@ -20,11 +20,7 @@ pub struct SceneSpatialPrimitives { /// Estimated bounding box of all data in scene coordinates. Accumulated. pub(super) bounding_box: macaw::BoundingBox, - // TODO(andreas): Storing extra data like so is unsafe and not future proof either - // (see also above comment on the need to separate cpu-readable data) - pub textured_rectangles_ids: Vec, - pub textured_rectangles: Vec, - + pub images: Vec, pub line_strips: LineStripSeriesBuilder, pub points: PointCloudBuilder, pub meshes: Vec, @@ -44,8 +40,7 @@ impl SceneSpatialPrimitives { pub fn new(re_ctx: &mut re_renderer::RenderContext) -> Self { Self { bounding_box: macaw::BoundingBox::nothing(), - textured_rectangles_ids: Default::default(), - textured_rectangles: Default::default(), + images: Default::default(), line_strips: LineStripSeriesBuilder::new(re_ctx) .radius_boost_in_ui_points_for_outlines(SIZE_BOOST_IN_POINTS_FOR_LINE_OUTLINES), points: PointCloudBuilder::new(re_ctx) @@ -68,8 +63,7 @@ impl SceneSpatialPrimitives { pub fn num_primitives(&self) -> usize { let Self { bounding_box: _, - textured_rectangles, - textured_rectangles_ids: _, + images, line_strips, points, meshes, @@ -77,7 +71,7 @@ impl SceneSpatialPrimitives { any_outlines: _, } = &self; - textured_rectangles.len() + images.len() + line_strips.vertices.len() + points.vertices.len() + meshes.len() @@ -89,8 +83,7 @@ impl SceneSpatialPrimitives { let Self { bounding_box, - textured_rectangles_ids: _, - textured_rectangles, + images, line_strips, points, meshes, @@ -100,7 +93,8 @@ impl SceneSpatialPrimitives { *bounding_box = macaw::BoundingBox::nothing(); - for rect in textured_rectangles { + for image in images { + let rect = &image.textured_rect; bounding_box.extend(rect.top_left_corner_position); bounding_box.extend(rect.top_left_corner_position + rect.extent_u); bounding_box.extend(rect.top_left_corner_position + rect.extent_v); diff --git a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs index 5e1227feb4ad..574bb254f2db 100644 --- a/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs +++ b/crates/re_viewer/src/ui/view_spatial/scene/scene_part/images.rs @@ -9,7 +9,8 @@ use re_log_types::{ }; use re_query::{query_primary_with_history, EntityView, QueryError}; use re_renderer::{ - renderer::{DepthCloud, DepthCloudDepthData, RectangleOptions}, + renderer::{DepthCloud, DepthCloudAlbedoData, RectangleOptions}, + resource_managers::Texture2DCreationDesc, Colormap, OutlineMaskPreference, }; @@ -24,9 +25,7 @@ use crate::{ use super::ScenePart; -#[allow(clippy::too_many_arguments)] -fn push_tensor_texture( - scene: &mut SceneSpatial, +fn to_textured_rect( ctx: &mut ViewerContext<'_>, annotations: &Annotations, world_from_obj: glam::Mat4, @@ -34,10 +33,10 @@ fn push_tensor_texture( tensor: &DecodedTensor, multiplicative_tint: egui::Rgba, outline_mask: OutlineMaskPreference, -) { +) -> Option { crate::profile_function!(); - let Some([height, width, _]) = tensor.image_height_width_channels() else { return; }; + let Some([height, width, _]) = tensor.image_height_width_channels() else { return None; }; let debug_name = ent_path.to_string(); let tensor_stats = ctx.cache.tensor_stats(tensor); @@ -67,7 +66,7 @@ fn push_tensor_texture( re_renderer::renderer::TextureFilterMin::Linear }; - let textured_rect = re_renderer::renderer::TexturedRect { + Some(re_renderer::renderer::TexturedRect { top_left_corner_position: world_from_obj.transform_point3(glam::Vec3::ZERO), extent_u: world_from_obj.transform_vector3(glam::Vec3::X * width as f32), extent_v: world_from_obj.transform_vector3(glam::Vec3::Y * height as f32), @@ -79,15 +78,11 @@ fn push_tensor_texture( depth_offset: -1, // Push to background. Mostly important for mouse picking order! outline_mask, }, - }; - scene.primitives.textured_rectangles.push(textured_rect); - scene - .primitives - .textured_rectangles_ids - .push(ent_path.hash()); + }) } Err(err) => { re_log::error_once!("Failed to create texture from tensor for {debug_name:?}: {err}"); + None } } } @@ -98,15 +93,17 @@ fn handle_image_layering(scene: &mut SceneSpatial) { // Handle layered rectangles that are on (roughly) the same plane and were logged in sequence. // First, group by similar plane. // TODO(andreas): Need planes later for picking as well! - let rects_grouped_by_plane = { + let images_grouped_by_plane = { let mut cur_plane = macaw::Plane3::from_normal_dist(Vec3::NAN, std::f32::NAN); let mut rectangle_group = Vec::new(); scene .primitives - .textured_rectangles - .iter_mut() + .images + .drain(..) // We rebuild the list as we might reorder as well! .batching(move |it| { - for rect in it.by_ref() { + for image in it { + let rect = &image.textured_rect; + let prev_plane = cur_plane; cur_plane = macaw::Plane3::from_normal_point( rect.extent_u.cross(rect.extent_v).normalize(), @@ -118,10 +115,10 @@ fn handle_image_layering(scene: &mut SceneSpatial) { && prev_plane.normal.dot(cur_plane.normal) < 0.99 && (prev_plane.d - cur_plane.d) < 0.01 { - let previous_group = std::mem::replace(&mut rectangle_group, vec![rect]); + let previous_group = std::mem::replace(&mut rectangle_group, vec![image]); return Some(previous_group); } - rectangle_group.push(rect); + rectangle_group.push(image); } if !rectangle_group.is_empty() { Some(rectangle_group.drain(..).collect()) @@ -129,14 +126,19 @@ fn handle_image_layering(scene: &mut SceneSpatial) { None } }) - }; - // Then, change opacity & transformation for planes within group except the base plane. - for mut grouped_rects in rects_grouped_by_plane { - let total_num_images = grouped_rects.len(); - for (idx, rect) in grouped_rects.iter_mut().enumerate() { + } + .collect_vec(); + + // Then, for each planar group do resorting and change transparency. + for mut grouped_images in images_grouped_by_plane { + // Class id images should generally come last as they typically have large areas being zeroed out (which maps to fully transparent). + grouped_images.sort_by_key(|image| image.tensor.meaning == TensorDataMeaning::ClassId); + + let total_num_images = grouped_images.len(); + for (idx, image) in grouped_images.iter_mut().enumerate() { // Set depth offset for correct order and avoid z fighting when there is a 3d camera. // Keep behind depth offset 0 for correct picking order. - rect.options.depth_offset = + image.textured_rect.options.depth_offset = (idx as isize - total_num_images as isize) as re_renderer::DepthOffset; // make top images transparent @@ -145,8 +147,14 @@ fn handle_image_layering(scene: &mut SceneSpatial) { } else { 1.0 / total_num_images.at_most(20) as f32 }; // avoid precision problems in framebuffer - rect.options.multiplicative_tint = rect.options.multiplicative_tint.multiply(opacity); + image.textured_rect.options.multiplicative_tint = image + .textured_rect + .options + .multiplicative_tint + .multiply(opacity); } + + scene.primitives.images.extend(grouped_images); } } @@ -159,7 +167,7 @@ impl ImagesPart { scene: &mut SceneSpatial, ctx: &mut ViewerContext<'_>, transforms: &TransformCache, - properties: &EntityProperties, + properties: &mut EntityProperties, ent_path: &EntityPath, world_from_obj: glam::Mat4, highlights: &SpaceViewHighlights, @@ -189,16 +197,6 @@ impl ImagesPart { }; let annotations = scene.annotation_map.find(ent_path); - - // TODO(jleibs): Meter should really be its own component - let meter = tensor.meter; - scene.ui.images.push(Image { - ent_path: ent_path.clone(), - tensor: tensor.clone(), - meter, - annotations: annotations.clone(), - }); - let entity_highlight = highlights.entity_outline_mask(ent_path.hash()); if *properties.backproject_depth.get() && tensor.meaning == TensorDataMeaning::Depth { @@ -233,8 +231,7 @@ impl ImagesPart { DefaultColor::OpaqueWhite, ); - push_tensor_texture( - scene, + if let Some(textured_rect) = to_textured_rect( ctx, &annotations, world_from_obj, @@ -242,7 +239,13 @@ impl ImagesPart { &tensor, color.into(), entity_highlight.overall, - ); + ) { + scene.primitives.images.push(Image { + ent_path: ent_path.clone(), + tensor, + textured_rect, + }); + } } Ok(()) @@ -253,7 +256,7 @@ impl ImagesPart { scene: &mut SceneSpatial, ctx: &mut ViewerContext<'_>, transforms: &TransformCache, - properties: &EntityProperties, + properties: &mut EntityProperties, tensor: &DecodedTensor, ent_path: &EntityPath, pinhole_ent_path: &EntityPath, @@ -277,26 +280,55 @@ impl ImagesPart { return Err(format!("Couldn't fetch pinhole extrinsics at {pinhole_ent_path:?}")); }; - // TODO(cmc): automagically convert as needed for non-natively supported datatypes? - let data = match &tensor.data { - // NOTE: Shallow clone if feature `arrow` is enabled, full alloc + memcpy otherwise. - TensorData::U16(data) => DepthCloudDepthData::U16(data.clone()), - TensorData::F32(data) => DepthCloudDepthData::F32(data.clone()), - _ => { - return Err(format!( - "Tensor datatype {} is not supported for backprojection", - tensor.dtype() - )); - } + let Some([height, width, _]) = tensor.image_height_width_channels() else { + return Err(format!("Tensor at {ent_path:?} is not an image")); + }; + let dimensions = glam::UVec2::new(width as _, height as _); + + let depth_texture = { + // Ideally, we'd use the same key as for displaying the texture, but we might make other compromises regarding formats etc.! + // So to not couple this, we use a different key here + let texture_key = egui::util::hash((tensor.id(), "depth_cloud")); + let mut data_f32 = Vec::new(); + ctx.render_ctx + .texture_manager_2d + .get_or_try_create_with( + texture_key, + &mut ctx.render_ctx.gpu_resources.textures, + || { + // TODO(andreas/cmc): Ideally we'd upload the u16 data as-is. + // However, R16Unorm is behind a feature flag and Depth16Unorm doesn't work on WebGL (and is awkward as this is a depth buffer format!). + let data = match &tensor.data { + TensorData::U16(data) => { + data_f32.extend(data.as_slice().iter().map(|d| *d as f32)); + bytemuck::cast_slice(&data_f32).into() + } + TensorData::F32(data) => bytemuck::cast_slice(data).into(), + _ => { + return Err(format!( + "Tensor datatype {} is not supported for back-projection", + tensor.dtype() + )); + } + }; + + Ok(Texture2DCreationDesc { + label: format!("Depth cloud for {ent_path:?}").into(), + data, + format: wgpu::TextureFormat::R32Float, + width: width as _, + height: height as _, + }) + }, + ) + .map_err(|err| format!("Failed to create depth cloud texture: {err}"))? }; let depth_from_world_scale = *properties.depth_from_world_scale.get(); - let world_depth_from_data_depth = 1.0 / depth_from_world_scale; - let (h, w) = (tensor.shape()[0].size, tensor.shape()[1].size); - let dimensions = glam::UVec2::new(w as _, h as _); + let world_depth_from_texture_depth = 1.0 / depth_from_world_scale; - let colormap = match *properties.color_mapper.get() { + let mut colormap = match *properties.color_mapper.get() { re_data_store::ColorMapper::Colormap(colormap) => match colormap { re_data_store::Colormap::Grayscale => Colormap::Grayscale, re_data_store::Colormap::Turbo => Colormap::Turbo, @@ -305,13 +337,58 @@ impl ImagesPart { re_data_store::Colormap::Magma => Colormap::Magma, re_data_store::Colormap::Inferno => Colormap::Inferno, }, + re_data_store::ColorMapper::AlbedoTexture => Colormap::AlbedoTexture, }; + let mut albedo_data = None; + let mut albedo_dimensions = glam::UVec2::ZERO; + + if colormap == Colormap::AlbedoTexture { + let tensor = properties.albedo_texture.as_ref().and_then(|path| { + query_latest_single::(&ctx.log_db.entity_db, path, &ctx.current_query()) + }); + if let Some(tensor) = tensor { + let (h, w) = (tensor.shape()[0].size, tensor.shape()[1].size); + albedo_dimensions = glam::UVec2::new(w as _, h as _); + + // TODO(cmc): How does one know whether the texture is sRGB or not at this point? + // TODO(cmc): We should easily be able to pass almost any datatype here. + + albedo_data = match &tensor.data { + TensorData::U8(data) => { + if let Some([_, _, c]) = tensor.image_height_width_channels() { + match c { + 1 => Some(DepthCloudAlbedoData::Mono8(data.0.to_vec())), + 3 => Some(DepthCloudAlbedoData::Rgb8(data.0.to_vec())), + 4 => Some(DepthCloudAlbedoData::Rgb8Srgb(data.0.to_vec())), + _ => None, + } + } else { + None + } + } + _ => { + re_log::debug_once!( + "Tensor datatype not supported for albedo texture ({:?})", + std::mem::discriminant(&tensor.data), + ); + None + } + }; + } else { + re_log::debug_once!( + "Albedo texture couldn't be fetched ({:?})", + properties.albedo_texture + ); + colormap = Colormap::Grayscale; + } + } + // We want point radius to be defined in a scale where the radius of a point // is a factor (`backproject_radius_scale`) of the diameter of a pixel projected // at that distance. let fov_y = intrinsics.fov_y().unwrap_or(1.0); - let pixel_width_from_depth = (0.5 * fov_y).tan() / (0.5 * h as f32); + let pixel_width_from_depth = (0.5 * fov_y).tan() / (0.5 * height as f32); let radius_scale = *properties.backproject_radius_scale.get(); let point_radius_from_world_depth = radius_scale * pixel_width_from_depth; @@ -321,23 +398,25 @@ impl ImagesPart { // This could only happen for Jpegs, and we should never get here. // TODO(emilk): refactor the code so that we can always calculate a range for the tensor re_log::warn_once!("Couldn't calculate range for a depth tensor!?"); - match data { - DepthCloudDepthData::U16(_) => u16::MAX as f32, - DepthCloudDepthData::F32(_) => 10.0, + match tensor.data { + TensorData::U16(_) => u16::MAX as f32, + _ => 10.0, } }; scene.primitives.depth_clouds.clouds.push(DepthCloud { world_from_obj, depth_camera_intrinsics: intrinsics.image_from_cam.into(), - world_depth_from_data_depth, + world_depth_from_texture_depth, point_radius_from_world_depth, - max_depth_in_world: world_depth_from_data_depth * max_data_value, + max_depth_in_world: max_data_value / depth_from_world_scale, depth_dimensions: dimensions, - depth_data: data, + depth_texture, colormap, outline_mask_id: entity_highlight.overall, picking_object_id: re_renderer::PickingLayerObjectId(ent_path.hash64()), + albedo_data, + albedo_dimensions, }); Ok(()) @@ -355,7 +434,7 @@ impl ScenePart for ImagesPart { ) { crate::profile_scope!("ImagesPart"); - for (ent_path, props) in query.iter_entities() { + for (ent_path, mut props) in query.iter_entities() { let Some(world_from_obj) = transforms.reference_from_entity(ent_path) else { continue; }; @@ -375,7 +454,7 @@ impl ScenePart for ImagesPart { scene, ctx, transforms, - &props, + &mut props, ent_path, world_from_obj, highlights, diff --git a/crates/re_viewer/src/ui/view_spatial/space_camera_3d.rs b/crates/re_viewer/src/ui/view_spatial/space_camera_3d.rs index f18d17a504a2..c429cbcaa5cf 100644 --- a/crates/re_viewer/src/ui/view_spatial/space_camera_3d.rs +++ b/crates/re_viewer/src/ui/view_spatial/space_camera_3d.rs @@ -6,7 +6,7 @@ use re_log_types::{EntityPath, ViewCoordinates}; /// A logged camera that connects spaces. #[derive(Clone)] pub struct SpaceCamera3D { - /// Path to the entity which has the projection (pinhole, ortho or otherwise) transforms. + /// Path to the instance which has the projection (pinhole, ortho or otherwise) transforms. /// /// We expect the camera transform to apply to this instance and every path below it. pub ent_path: EntityPath, diff --git a/crates/re_viewer/src/ui/view_spatial/ui.rs b/crates/re_viewer/src/ui/view_spatial/ui.rs index 49209bb1b32e..bd2daf058b1e 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui.rs @@ -5,7 +5,7 @@ use re_format::format_f32; use egui::{NumExt, WidgetText}; use macaw::BoundingBox; use re_log_types::component_types::{Tensor, TensorDataMeaning}; -use re_renderer::OutlineConfig; +use re_renderer::{Colormap, OutlineConfig}; use crate::{ misc::{ @@ -30,6 +30,7 @@ use super::{ }; /// Describes how the scene is navigated, determining if it is a 2D or 3D experience. + #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)] pub enum SpatialNavigationMode { #[default] @@ -233,6 +234,40 @@ impl ViewSpatialState { properties.backproject_radius_scale = EditableAutoValue::Auto(1.0); } + let colormap = match *properties.color_mapper.get() { + re_data_store::ColorMapper::Colormap(colormap) => match colormap { + re_data_store::Colormap::Grayscale => Colormap::Grayscale, + re_data_store::Colormap::Turbo => Colormap::Turbo, + re_data_store::Colormap::Viridis => Colormap::Viridis, + re_data_store::Colormap::Plasma => Colormap::Plasma, + re_data_store::Colormap::Magma => Colormap::Magma, + re_data_store::Colormap::Inferno => Colormap::Inferno, + }, + re_data_store::ColorMapper::AlbedoTexture => Colormap::AlbedoTexture, + }; + // Set albedo texture if it is not set yet + if colormap == Colormap::AlbedoTexture && properties.albedo_texture.is_none() { + let mut tex_ep = None; + if let Some(tree) = entity_path + .parent() + .and_then(|path| ctx.log_db.entity_db.tree.subtree(&path)) + { + tree.visit_children_recursively(&mut |ent_path| { + if tex_ep.is_some() { + return; + } + let Some(tensor) = + query_latest_single::(&ctx.log_db.entity_db, ent_path, &ctx.current_query()) else { + return; + }; + if tensor.is_shaped_like_an_image() { + tex_ep = Some(ent_path.clone()); + } + }); + properties.albedo_texture = tex_ep; + } + } + data_blueprint .data_blueprints_individual() .set(entity_path.clone(), properties); @@ -421,6 +456,7 @@ impl ViewSpatialState { ); } SpatialNavigationMode::TwoD => { + self.scene_bbox_accum = self.scene_bbox; let scene_rect_accum = egui::Rect::from_min_max( self.scene_bbox_accum.min.truncate().to_array().into(), self.scene_bbox_accum.max.truncate().to_array().into(), @@ -754,28 +790,29 @@ pub fn picking( let picked_image_with_coords = if hit.hit_type == PickingHitType::TexturedRect || *ent_properties.backproject_depth.get() { - scene - .ui - .images - .iter() - .find(|image| image.ent_path == instance_path.entity_path) - .and_then(|image| { - // If we're here because of back-projection, but this wasn't actually a depth image, drop out. - // (the back-projection property may be true despite this not being a depth image!) - if hit.hit_type != PickingHitType::TexturedRect - && *ent_properties.backproject_depth.get() - && image.tensor.meaning != TensorDataMeaning::Depth - { - return None; - } - image.tensor.image_height_width_channels().map(|[_, w, _]| { + query_latest_single::( + &ctx.log_db.entity_db, + &instance_path.entity_path, + &ctx.current_query(), + ) + .and_then(|tensor| { + // If we're here because of back-projection, but this wasn't actually a depth image, drop out. + // (the back-projection property may be true despite this not being a depth image!) + if hit.hit_type != PickingHitType::TexturedRect + && *ent_properties.backproject_depth.get() + && tensor.meaning != TensorDataMeaning::Depth + { + None + } else { + tensor.image_height_width_channels().map(|[_, w, _]| { let coordinates = hit .instance_path_hash .instance_key .to_2d_image_coordinate(w); - (image, coordinates) + (tensor, coordinates) }) - }) + } + }) } else { None }; @@ -789,9 +826,9 @@ pub fn picking( instance_path.clone(), )); - response = if let Some((image, coords)) = picked_image_with_coords { - if let Some(meter) = image.meter { - if let Some(raw_value) = image.tensor.get(&[ + response = if let Some((tensor, coords)) = picked_image_with_coords { + if let Some(meter) = tensor.meter { + if let Some(raw_value) = tensor.get(&[ picking_context.pointer_in_space2d.y.round() as _, picking_context.pointer_in_space2d.x.round() as _, ]) { @@ -815,10 +852,10 @@ pub fn picking( &ctx.current_query(), ); - if let [h, w, ..] = image.tensor.shape() { + if let Some([h, w, ..]) = tensor.image_height_width_channels() { ui.separator(); ui.horizontal(|ui| { - let (w, h) = (w.size as f32, h.size as f32); + let (w, h) = (w as f32, h as f32); if *state.nav_mode.get() == SpatialNavigationMode::TwoD { let rect = egui::Rect::from_min_size( egui::Pos2::ZERO, @@ -826,24 +863,30 @@ pub fn picking( ); data_ui::image::show_zoomed_image_region_area_outline( ui, - &image.tensor, + &tensor, [coords[0] as _, coords[1] as _], space_from_ui.inverse().transform_rect(rect), ); } - let tensor_stats = *ctx.cache.tensor_stats(&image.tensor); - let debug_name = image.ent_path.to_string(); - data_ui::image::show_zoomed_image_region( - ctx.render_ctx, - ui, - &image.tensor, - &tensor_stats, - &image.annotations, - image.meter, - &debug_name, - [coords[0] as _, coords[1] as _], - ); + let tensor_name = instance_path.to_string(); + match ctx.cache.decode.try_decode_tensor_if_necessary(tensor) { + Ok(decoded_tensor) => + data_ui::image::show_zoomed_image_region( + ctx.render_ctx, + ui, + &decoded_tensor, + ctx.cache.tensor_stats(&decoded_tensor), + &scene.annotation_map.find(&instance_path.entity_path), + decoded_tensor.meter, + &tensor_name, + [coords[0] as _, coords[1] as _], + ), + Err(err) => + re_log::warn_once!( + "Encountered problem decoding tensor at path {tensor_name}: {err}" + ), + } }); } }); diff --git a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs index 295a1e05dec3..dfcbeb6b6d40 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs @@ -289,7 +289,7 @@ pub fn view_3d( state .state_3d .update_eye(&response, &state.scene_bbox_accum, &scene.space_cameras); - let did_interact_with_eye = orbit_eye.interact(&response, orbit_eye_drag_threshold); + let did_interact_with_eye = orbit_eye.update(&response, orbit_eye_drag_threshold); let orbit_eye = *orbit_eye; let eye = orbit_eye.to_eye(); @@ -453,6 +453,26 @@ pub fn view_3d( } } + if state.state_3d.show_bbox { + let bbox = scene.primitives.bounding_box(); + if bbox.is_something() && bbox.is_finite() { + let scale = bbox.size(); + let translation = bbox.center(); + let bbox_from_unit_cube = glam::Affine3A::from_scale_rotation_translation( + scale, + Default::default(), + translation, + ); + scene + .primitives + .line_strips + .batch("scene_bbox") + .add_box_outline(bbox_from_unit_cube) + .radius(Size::AUTO) + .color(egui::Color32::WHITE); + } + } + { let orbit_center_alpha = egui::remap_clamp( ui.input(|i| i.time) - state.state_3d.last_eye_interact_time, @@ -633,14 +653,10 @@ fn default_eye(scene_bbox: &macaw::BoundingBox, space_specs: &SpaceSpecs) -> Orb let eye_pos = center - radius * look_dir; - OrbitEye { - orbit_center: center, - orbit_radius: radius, - world_from_view_rot: Quat::from_affine3( - &Affine3A::look_at_rh(eye_pos, center, look_up).inverse(), - ), - fov_y: Eye::DEFAULT_FOV_Y, - up: space_specs.up.unwrap_or(Vec3::ZERO), - velocity: Vec3::ZERO, - } + OrbitEye::new( + center, + radius, + Quat::from_affine3(&Affine3A::look_at_rh(eye_pos, center, look_up).inverse()), + space_specs.up.unwrap_or(Vec3::ZERO), + ) } diff --git a/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs b/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs index 513abdf82430..e02885290f73 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_renderer_bridge.rs @@ -32,7 +32,11 @@ pub fn fill_view_builder( .queue_draw(&primitives.points.to_draw_data(render_ctx)?) .queue_draw(&RectangleDrawData::new( render_ctx, - &primitives.textured_rectangles, + &primitives + .images + .iter() + .map(|image| image.textured_rect.clone()) + .collect::>(), )?); if matches!(background, ScreenBackground::GenericSkybox) { diff --git a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs index 761c46d5da77..887471a9e519 100644 --- a/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs +++ b/crates/re_viewer/src/ui/view_tensor/tensor_slice_to_gpu.rs @@ -1,5 +1,8 @@ use re_log_types::{component_types::TensorCastError, DecodedTensor, TensorDataType}; -use re_renderer::{renderer::ColormappedTexture, resource_managers::Texture2DCreationDesc}; +use re_renderer::{ + renderer::ColormappedTexture, + resource_managers::{GpuTexture2D, Texture2DCreationDesc, TextureManager2DError}, +}; use crate::{ gpu_bridge::{range, RangeError}, @@ -28,10 +31,11 @@ pub fn colormapped_texture( tensor: &DecodedTensor, tensor_stats: &TensorStats, state: &ViewTensorState, -) -> Result { +) -> Result> { crate::profile_function!(); - let range = range(tensor_stats)?; + let range = + range(tensor_stats).map_err(|err| TextureManager2DError::DataCreation(err.into()))?; let texture = upload_texture_slice_to_gpu(render_ctx, tensor, state.slice())?; let color_mapping = state.color_mapping(); @@ -50,7 +54,7 @@ fn upload_texture_slice_to_gpu( render_ctx: &mut re_renderer::RenderContext, tensor: &DecodedTensor, slice_selection: &SliceSelection, -) -> Result { +) -> Result> { let id = egui::util::hash((tensor.id(), slice_selection)); crate::gpu_bridge::try_get_or_create_texture(render_ctx, id, || { diff --git a/crates/re_viewer/src/ui/view_tensor/ui.rs b/crates/re_viewer/src/ui/view_tensor/ui.rs index b26e2ba2f03b..b1c6066df1fa 100644 --- a/crates/re_viewer/src/ui/view_tensor/ui.rs +++ b/crates/re_viewer/src/ui/view_tensor/ui.rs @@ -27,7 +27,7 @@ pub struct SliceSelection { pub selector_values: BTreeMap, } -#[derive(Clone, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct ViewTensorState { /// What slice are we vieiwing? slice: SliceSelection, @@ -321,7 +321,8 @@ fn paint_colormap_gradient( width, height, } - }); + }) + .map_err(|err| anyhow::anyhow!("Failed to create horizontal gradient texture: {err}"))?; let colormapped_texture = re_renderer::renderer::ColormappedTexture { texture: horizontal_gradient, diff --git a/crates/re_viewer/src/ui/view_time_series/ui.rs b/crates/re_viewer/src/ui/view_time_series/ui.rs index c2d0d52861d3..68553847af5b 100644 --- a/crates/re_viewer/src/ui/view_time_series/ui.rs +++ b/crates/re_viewer/src/ui/view_time_series/ui.rs @@ -82,9 +82,10 @@ pub(crate) fn view_time_series( plot = plot.x_grid_spacer(move |spacer| ns_grid_spacer(canvas_size, &spacer)); } - let egui::InnerResponse { + let egui::plot::PlotResponse { inner: time_x, response, + transform: _, } = plot.show(ui, |plot_ui| { if plot_ui.plot_secondary_clicked() { let timeline = ctx.rec_cfg.time_ctrl.timeline(); diff --git a/crates/re_viewer/src/ui/viewport.rs b/crates/re_viewer/src/ui/viewport.rs index 5f4a0073b992..7f2508169b2c 100644 --- a/crates/re_viewer/src/ui/viewport.rs +++ b/crates/re_viewer/src/ui/viewport.rs @@ -9,15 +9,14 @@ use re_data_store::EntityPath; use crate::{ misc::{space_info::SpaceInfoCollection, Item, SpaceViewHighlights, ViewerContext}, - ui::space_view_heuristics::default_created_space_views, + ui::{space_view_heuristics::default_created_space_views, stats_panel::StatsPanel}, }; use super::{ - data_blueprint::{DataBlueprintGroup, DataBlueprintGroupHandle}, + device_settings_panel::DeviceSettingsPanel, selection_panel::SelectionPanel, space_view_entity_picker::SpaceViewEntityPicker, - space_view_heuristics::all_possible_space_views, - view_category::ViewCategory, - SpaceView, SpaceViewId, + space_view_heuristics::all_possible_space_views, stats_panel::StatsPanelState, + view_category::ViewCategory, SpaceView, SpaceViewId, SpaceViewKind, }; // ---------------------------------------------------------------------------- @@ -40,18 +39,23 @@ pub struct Viewport { /// One for each combination of what views are visible. /// So if a user toggles the visibility of one SpaceView, we /// switch which layout we are using. This is somewhat hacky. - trees: HashMap>, + trees: HashMap>, /// Show one tab as maximized? maximized: Option, - /// Set to `true` the first time the user messes around with the viewport blueprint. - /// Before this is set we automatically add new spaces to the viewport - /// when they show up in the data. - has_been_user_edited: bool, + /// Store for each space view if the user has edited it (eg. removed). + /// Is reset when a space view get's automatically removed. + has_been_user_edited: HashMap, #[serde(skip)] space_view_entity_window: Option, + device_settings_panel: DeviceSettingsPanel, + + #[serde(skip)] + stats_panel_state: StatsPanelState, + + previous_frame_tree: Option>, } impl Viewport { @@ -80,8 +84,11 @@ impl Viewport { visible, trees, maximized, - has_been_user_edited, + has_been_user_edited: _, space_view_entity_window, + device_settings_panel: _, + stats_panel_state: _, + previous_frame_tree: _, } = self; if let Some(window) = space_view_entity_window { @@ -90,8 +97,6 @@ impl Viewport { } } - *has_been_user_edited = true; - trees.retain(|vis_set, _| !vis_set.contains(space_view_id)); if *maximized == Some(*space_view_id) { @@ -102,229 +107,97 @@ impl Viewport { space_views.remove(space_view_id) } - /// Show the blueprint panel tree view. - pub fn tree_ui(&mut self, ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { + pub fn add_or_remove_space_views_ui( + &mut self, + ctx: &mut ViewerContext<'_>, + ui: &mut egui::Ui, + spaces_info: &SpaceInfoCollection, + ) { crate::profile_function!(); + let entities_to_remove = ctx.depthai_state.get_entities_to_remove(); + egui::ScrollArea::vertical() .auto_shrink([false; 2]) .show(ui, |ui| { - let space_view_ids = self - .space_views - .keys() - .sorted_by_key(|space_view_id| &self.space_views[space_view_id].space_path) - .copied() - .collect_vec(); - - for space_view_id in &space_view_ids { - self.space_view_entry_ui(ctx, ui, space_view_id); + ui.style_mut().wrap = Some(false); + + // Only show "logical" space views like Color camera, Mono Camera etc, don't go into + // details like rerun does in tree_ui, depthai-viewer users don't care about that + // as they didn't create the blueprint by logging the data + for space_view in all_possible_space_views(ctx, spaces_info) + .into_iter() + .filter(|sv| { + sv.is_depthai_spaceview && !entities_to_remove.contains(&sv.space_path) + }) + { + self.available_space_view_row_ui(ctx, ui, space_view); } }); } - /// If a group or spaceview has a total of this number of elements, show its subtree by default? - fn default_open_for_group(group: &DataBlueprintGroup) -> bool { - let num_children = group.children.len() + group.entities.len(); - 2 <= num_children && num_children <= 3 - } - - fn space_view_entry_ui( + fn available_space_view_row_ui( &mut self, ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, - space_view_id: &SpaceViewId, + space_view: SpaceView, ) { - let Some(space_view) = self.space_views.get_mut(space_view_id) else { - re_log::warn_once!("Bug: asked to show a ui for a Space View that doesn't exist"); - return; - }; - debug_assert_eq!(space_view.id, *space_view_id); - - let mut visibility_changed = false; - let mut removed_space_view = false; - let mut is_space_view_visible = self.visible.contains(space_view_id); - - let root_group = space_view.data_blueprint.root_group(); - let default_open = Self::default_open_for_group(root_group); - let collapsing_header_id = ui.id().with(space_view.id); + let space_path = space_view.space_path.clone(); // to avoid borrowing issue in .body() of collapsing state + let collapsing_header_id = ui.id().with(space_view.display_name.clone()); egui::collapsing_header::CollapsingState::load_with_default_open( ui.ctx(), collapsing_header_id, - default_open, + true, ) .show_header(ui, |ui| { - blueprint_row_with_buttons( - ctx.re_ui, - ui, - true, - is_space_view_visible, - |ui| { - let response = ctx.space_view_button(ui, space_view); - if response.clicked() { - if let Some(tree) = self.trees.get_mut(&self.visible) { - focus_tab(tree, space_view_id); - } - } - response - }, - |re_ui, ui| { - visibility_changed = - visibility_button_ui(re_ui, ui, true, &mut is_space_view_visible).changed(); - removed_space_view = re_ui - .small_icon_button(ui, &re_ui::icons::REMOVE) - .on_hover_text("Remove Space View from the viewport.") - .clicked(); - }, + ui.label(space_view.display_name.clone()); + let mut ui = ui.child_ui( + ui.max_rect(), + egui::Layout::right_to_left(egui::Align::Center), ); + if ctx + .re_ui + .small_icon_button(&mut ui, &re_ui::icons::ADD) + .clicked() + { + self.add_space_view(space_view); + } }) .body(|ui| { - Self::data_blueprint_tree_ui( - ctx, - ui, - space_view.data_blueprint.root_handle(), - space_view, - self.visible.contains(space_view_id), - ); - }); - - if removed_space_view { - self.remove(space_view_id); - } - - if visibility_changed { - self.has_been_user_edited = true; - if is_space_view_visible { - self.visible.insert(*space_view_id); - } else { - self.visible.remove(space_view_id); - } - } - } - - fn data_blueprint_tree_ui( - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - group_handle: DataBlueprintGroupHandle, - space_view: &mut SpaceView, - space_view_visible: bool, - ) { - let Some(group) = space_view.data_blueprint.group(group_handle) else { - debug_assert!(false, "Invalid group handle in blueprint group tree"); - return; - }; - - // TODO(andreas): These clones are workarounds against borrowing multiple times from data_blueprint_tree. - let children = group.children.clone(); - let entities = group.entities.clone(); - let group_name = group.display_name.clone(); - let group_is_visible = group.properties_projected.visible && space_view_visible; - - for entity_path in &entities { - ui.horizontal(|ui| { - let mut properties = space_view - .data_blueprint - .data_blueprints_individual() - .get(entity_path); - blueprint_row_with_buttons( - ctx.re_ui, - ui, - group_is_visible, - properties.visible, - |ui| { - let name = entity_path.iter().last().unwrap().to_string(); - let label = format!("🔹 {name}"); - ctx.data_blueprint_button_to(ui, label, space_view.id, entity_path) - }, - |re_ui, ui| { - if visibility_button_ui( - re_ui, - ui, - group_is_visible, - &mut properties.visible, - ) - .changed() - { - space_view - .data_blueprint - .data_blueprints_individual() - .set(entity_path.clone(), properties); - } - if re_ui - .small_icon_button(ui, &re_ui::icons::REMOVE) - .on_hover_text("Remove Entity from the space view.") - .clicked() - { - space_view.data_blueprint.remove_entity(entity_path); - space_view.entities_determined_by_user = true; - } - }, - ); - }); - } - - for child_group_handle in &children { - let Some(child_group) = space_view.data_blueprint.group_mut(*child_group_handle) else { - debug_assert!(false, "Data blueprint group {group_name} has an invalid child"); - continue; - }; - - let mut remove_group = false; - let default_open = Self::default_open_for_group(child_group); - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - ui.id().with(child_group_handle), - default_open, - ) - .show_header(ui, |ui| { - blueprint_row_with_buttons( - ctx.re_ui, - ui, - group_is_visible, - child_group.properties_individual.visible, - |ui| { - ctx.data_blueprint_group_button_to( - ui, - child_group.display_name.clone(), - space_view.id, - *child_group_handle, - ) - }, - |re_ui, ui| { - visibility_button_ui( - re_ui, - ui, - group_is_visible, - &mut child_group.properties_individual.visible, - ); - if re_ui - .small_icon_button(ui, &re_ui::icons::REMOVE) - .on_hover_text("Remove group and all its children from the space view.") - .clicked() - { - remove_group = true; - } - }, - ); - }) - .body(|ui| { - Self::data_blueprint_tree_ui( - ctx, - ui, - *child_group_handle, - space_view, - space_view_visible, - ); + let instances_of_space_view = self.visible.iter().filter(|id| { + if let Some(sv) = self.space_views.get(*id) { + sv.space_path == space_path + } else { + false + } }); - if remove_group { - space_view.data_blueprint.remove_group(*child_group_handle); - space_view.entities_determined_by_user = true; + let mut space_views_to_remove = Vec::new(); + for sv_id in instances_of_space_view { + ui.horizontal(|ui| { + let label = format!("🔹 {}", self.space_views[sv_id].display_name); + ui.label(label); + let mut ui = ui.child_ui( + ui.max_rect(), + egui::Layout::right_to_left(egui::Align::Center), + ); + if ctx + .re_ui + .small_icon_button(&mut ui, &re_ui::icons::REMOVE) + .clicked() + { + space_views_to_remove.push(*sv_id); + self.has_been_user_edited + .insert(self.space_views[sv_id].space_path.clone(), true); + } + }); } - } + for sv_id in &space_views_to_remove { + self.remove(sv_id); + } + }); } - pub(crate) fn mark_user_interaction(&mut self) { - self.has_been_user_edited = true; - } + pub(crate) fn mark_user_interaction(&mut self) {} pub(crate) fn add_space_view(&mut self, mut space_view: SpaceView) -> SpaceViewId { let id = space_view.id; @@ -362,17 +235,52 @@ impl Viewport { spaces_info: &SpaceInfoCollection, ) { crate::profile_function!(); + let mut space_views_to_remove = Vec::new(); + + // Get all the entity paths that aren't logged anymore + let entities_to_remove = ctx.depthai_state.get_entities_to_remove(); + // First clear the has_been_user_edited entry, so if the entity path is a space path and it reappeaars later, + // it will get added back into the viewport + for ep in &entities_to_remove { + self.has_been_user_edited.insert(ep.clone(), false); + } + self.stats_panel_state.update(ctx); + // Remove all entities that are marked for removal from the space view. + // Remove the space view if it has no entities left for space_view in self.space_views.values_mut() { - space_view.on_frame_start(ctx, spaces_info); - } + if let Some(group) = space_view + .data_blueprint + .group(space_view.data_blueprint.root_handle()) + { + for ep in entities_to_remove.iter() { + space_view.data_blueprint.remove_entity(ep); + } - if !self.has_been_user_edited { - for space_view_candidate in default_created_space_views(ctx, spaces_info) { - if self.should_auto_add_space_view(&space_view_candidate) { - self.add_space_view(space_view_candidate); + if space_view.data_blueprint.entity_paths().is_empty() { + space_views_to_remove.push(space_view.id); + self.has_been_user_edited + .insert(space_view.space_path.clone(), false); + continue; } } + space_view.on_frame_start(ctx, spaces_info); + } + for id in &space_views_to_remove { + if self.space_views.get(id).is_some() { + self.remove(id); + } + } + for space_view_candidate in default_created_space_views(ctx, spaces_info) { + if !self + .has_been_user_edited + .get(&space_view_candidate.space_path) + .unwrap_or(&false) + && !entities_to_remove.contains(&space_view_candidate.space_path) + && self.should_auto_add_space_view(&space_view_candidate) + { + self.add_space_view(space_view_candidate); + } } } @@ -418,12 +326,6 @@ impl Viewport { self.trees.retain(|_, tree| is_tree_valid(tree)); - if let Some(space_view_id) = self.maximized { - if !self.space_views.contains_key(&space_view_id) { - self.maximized = None; // protect against bad deserialized data - } - } - let visible_space_views = if let Some(space_view_id) = self.maximized { std::iter::once(space_view_id).collect() } else { @@ -431,16 +333,30 @@ impl Viewport { }; // Lazily create a layout tree based on which SpaceViews should be visible: - let tree = self + let mut tree = self .trees .entry(visible_space_views.clone()) .or_insert_with(|| { - super::auto_layout::tree_from_space_views( - ui.available_size(), - &visible_space_views, - &self.space_views, - ) - }); + // TODO(filip): Continue working on this smart layout updater + if let Some(previous_frame_tree) = &self.previous_frame_tree { + let mut tree = previous_frame_tree.clone(); + super::auto_layout::update_tree( + &mut tree, + &visible_space_views, + &self.space_views, + self.maximized.is_some(), + ); + tree + } else { + super::auto_layout::default_tree_from_space_views( + ui.available_size(), + &visible_space_views, + &self.space_views, + ) + } + }) + .clone(); + self.previous_frame_tree = Some(tree.clone()); let num_space_views = tree.num_tabs(); if num_space_views == 0 { @@ -449,7 +365,7 @@ impl Viewport { let mut tab_viewer = TabViewer { ctx, - space_views: &mut self.space_views, + viewport: self, }; ui.scope(|ui| { @@ -457,7 +373,8 @@ impl Viewport { ui.spacing_mut().item_spacing.x = re_ui::ReUi::view_padding(); - egui_dock::DockArea::new(tree) + egui_dock::DockArea::new(&mut tree) + .id(egui::Id::new("space_view_dock")) .style(re_ui::egui_dock_style(ui.style())) .show_inside(ui, &mut tab_viewer); }); @@ -466,16 +383,11 @@ impl Viewport { let tab_bars = tree .iter() .filter_map(|node| { - let egui_dock::Node::Leaf { - rect, - viewport, - tabs, - active, - } = node else { - return None; - }; + let egui_dock::Node::Leaf { rect, viewport, tabs, active } = node else { + return None; + }; - let space_view_id = tabs.get(active.0)?; + let space_view_tab = tabs.get(active.0)?; // `rect` includes the tab area, while `viewport` is just the tab body. // so the tab bar rect is: @@ -485,55 +397,37 @@ impl Viewport { // rect/viewport can be invalid for the first frame tab_bar_rect .is_finite() - .then_some((*space_view_id, tab_bar_rect)) + .then_some((space_view_tab.clone(), tab_bar_rect)) }) .collect_vec(); - - for (space_view_id, tab_bar_rect) in tab_bars { - // rect/viewport can be invalid for the first frame - space_view_options_ui(ctx, ui, self, tab_bar_rect, space_view_id, num_space_views); - } - } - - pub fn add_new_spaceview_button_ui( - &mut self, - ctx: &mut ViewerContext<'_>, - ui: &mut egui::Ui, - spaces_info: &SpaceInfoCollection, - ) { - #![allow(clippy::collapsible_if)] - - let icon_image = ctx.re_ui.icon_image(&re_ui::icons::ADD); - let texture_id = icon_image.texture_id(ui.ctx()); - ui.menu_image_button(texture_id, re_ui::ReUi::small_icon_size(), |ui| { - ui.style_mut().wrap = Some(false); - - for space_view in all_possible_space_views(ctx, spaces_info) - .into_iter() - .sorted_by_key(|space_view| space_view.space_path.to_string()) - { - if ctx - .re_ui - .selectable_label_with_icon( + self.trees.insert(visible_space_views, tree); + + for ( + Tab { + space_view_id, + space_view_kind, + .. + }, + tab_bar_rect, + ) in tab_bars + { + match space_view_kind { + SpaceViewKind::Data | SpaceViewKind::Stats => { + space_view_options_ui( + ctx, ui, - space_view.category.icon(), - if space_view.space_path.is_root() { - space_view.display_name.clone() - } else { - space_view.space_path.to_string() - }, - false, - ) - .clicked() - { - ui.close_menu(); - let new_space_view_id = self.add_space_view(space_view); - ctx.set_single_selection(Item::SpaceView(new_space_view_id)); + self, + tab_bar_rect, + space_view_id, + num_space_views, + ); + } + SpaceViewKind::Selection => { + SelectionPanel::selection_panel_options_ui(ctx, ui, self, tab_bar_rect); } + _ => {} } - }) - .response - .on_hover_text("Add new space view."); + } } pub fn space_views_containing_entity_path(&self, path: &EntityPath) -> Vec { @@ -651,51 +545,111 @@ fn visibility_button_ui( // ---------------------------------------------------------------------------- +struct ViewportView {} + struct TabViewer<'a, 'b> { ctx: &'a mut ViewerContext<'b>, - space_views: &'a mut HashMap, + viewport: &'a mut Viewport, } -impl<'a, 'b> egui_dock::TabViewer for TabViewer<'a, 'b> { - type Tab = SpaceViewId; +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct Tab { + pub space_view_id: SpaceViewId, + + /// Rerun only displayed logged data in the viewport. + /// Depthai Viewer also has fixed ui panels that are part of the tab system. + /// This is used to distinguish between the two. + pub space_view_kind: SpaceViewKind, + + pub space_path: Option, +} + +impl From<&SpaceView> for Tab { + fn from(space_view: &SpaceView) -> Self { + Self { + space_view_id: space_view.id, + space_view_kind: SpaceViewKind::Data, + space_path: Some(space_view.space_path.clone()), + } + } +} + +impl From for Tab { + fn from(space_view: SpaceView) -> Self { + Self::from(&space_view) + } +} - fn ui(&mut self, ui: &mut egui::Ui, space_view_id: &mut Self::Tab) { +impl<'a, 'b> egui_dock::TabViewer for TabViewer<'a, 'b> { + type Tab = Tab; + + fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { + let Tab { + space_view_id, + space_view_kind, + space_path, + } = tab; crate::profile_function!(); - let highlights = self - .ctx - .selection_state() - .highlights_for_space_view(*space_view_id, self.space_views); - let space_view = self - .space_views - .get_mut(space_view_id) - .expect("Should have been populated beforehand"); + match space_view_kind { + SpaceViewKind::Data => { + let highlights = self + .ctx + .selection_state() + .highlights_for_space_view(*space_view_id, &self.viewport.space_views); + let space_view = self + .viewport + .space_views + .get_mut(space_view_id) + .expect("Should have been populated beforehand"); - space_view_ui(self.ctx, ui, space_view, &highlights); + space_view_ui(self.ctx, ui, space_view, &highlights); + } + SpaceViewKind::Selection => SelectionPanel::show_panel(self.ctx, ui, self.viewport), + SpaceViewKind::Config => self.viewport.device_settings_panel.show_panel(self.ctx, ui), + SpaceViewKind::Stats => { + StatsPanel::show_panel(self.ctx, ui, &mut self.viewport.stats_panel_state); + } + _ => {} + } } fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { - let space_view = self - .space_views - .get_mut(tab) - .expect("Should have been populated beforehand"); - - let mut text = - egui::WidgetText::RichText(egui::RichText::new(space_view.display_name.clone())); - - if self.ctx.selection().contains(&Item::SpaceView(*tab)) { - // Show that it is selected: - let egui_ctx = &self.ctx.re_ui.egui_ctx; - let selection_bg_color = egui_ctx.style().visuals.selection.bg_fill; - text = text.background_color(selection_bg_color); - } + match tab.space_view_kind { + SpaceViewKind::Data => { + let space_view = self + .viewport + .space_views + .get_mut(&tab.space_view_id) + .expect("Should have been populated beforehand"); + + let mut text = egui::WidgetText::RichText(egui::RichText::new( + space_view.display_name.clone(), + )); - text + if self + .ctx + .selection() + .contains(&Item::SpaceView(tab.space_view_id)) + { + // Show that it is selected: + let egui_ctx = &self.ctx.re_ui.egui_ctx; + let selection_bg_color = egui_ctx.style().visuals.selection.bg_fill; + text = text.background_color(selection_bg_color); + } + + text + } + SpaceViewKind::Stats => "Stats".into(), + SpaceViewKind::Config => "Device Settings".into(), + SpaceViewKind::Selection => "Selection".into(), + } } fn on_tab_button(&mut self, tab: &mut Self::Tab, response: &egui::Response) { if response.clicked() { - self.ctx.set_single_selection(Item::SpaceView(*tab)); + self.ctx + .set_single_selection(Item::SpaceView(tab.space_view_id)); } } } @@ -706,6 +660,7 @@ fn help_text_ui(ui: &mut egui::Ui, space_view: &SpaceView) { ViewCategory::BarChart => Some(crate::ui::view_bar_chart::HELP_TEXT), ViewCategory::Spatial => Some(space_view.view_state.state_spatial.help_text()), ViewCategory::Text | ViewCategory::Tensor => None, + ViewCategory::NodeGraph => None, }; if let Some(help_text) = help_text { @@ -722,8 +677,6 @@ fn space_view_options_ui( space_view_id: SpaceViewId, num_space_views: usize, ) { - let Some(space_view) = viewport.space_views.get(&space_view_id) else { return; }; - let tab_bar_rect = tab_bar_rect.shrink2(egui::vec2(4.0, 0.0)); // Add some side margin outside the frame ui.allocate_ui_at_rect(tab_bar_rect, |ui| { @@ -754,6 +707,56 @@ fn space_view_options_ui( ctx.set_single_selection(Item::SpaceView(space_view_id)); } } + let Some(space_view) = viewport.space_views.get_mut(&space_view_id) else { + return; + }; + + let icon_image = ctx.re_ui.icon_image(&re_ui::icons::GEAR); + let texture_id = icon_image.texture_id(ui.ctx()); + ui.menu_image_button(texture_id, re_ui::ReUi::small_icon_size(), |ui| { + ui.style_mut().wrap = Some(false); + let entities = space_view.data_blueprint.entity_paths().clone(); + let entities = entities.iter().filter(|ep| { + let eps_to_skip = vec![ + EntityPath::from("color/camera/rgb"), + EntityPath::from("color/camera"), + EntityPath::from("mono/camera"), + EntityPath::from("mono/camera/left_mono"), + EntityPath::from("mono/camera/right_mono"), + ]; + !eps_to_skip.contains(ep) + }); + for entity_path in entities { + // if matches!(entity_path, EntityPath::from("color")) + ui.horizontal(|ui| { + let mut properties = space_view + .data_blueprint + .data_blueprints_individual() + .get(entity_path); + blueprint_row_with_buttons( + ctx.re_ui, + ui, + true, + properties.visible, + |ui| { + let name = entity_path.iter().last().unwrap().to_string(); + let label = format!("🔹 {name}"); + ctx.data_blueprint_button_to(ui, label, space_view.id, entity_path) + }, + |re_ui, ui| { + if visibility_button_ui(re_ui, ui, true, &mut properties.visible) + .changed() + { + space_view + .data_blueprint + .data_blueprints_individual() + .set(entity_path.clone(), properties); + } + }, + ); + }); + } + }); // Show help last, since not all space views have help text help_text_ui(ui, space_view); @@ -782,7 +785,7 @@ fn space_view_ui( ui.centered_and_justified(|ui| { ui.label(ctx.re_ui.warning_text("No time selected")); }); - return + return; }; space_view.scene_ui(ctx, ui, latest_at, space_view_highlights); @@ -790,14 +793,14 @@ fn space_view_ui( // ---------------------------------------------------------------------------- -fn focus_tab(tree: &mut egui_dock::Tree, tab: &SpaceViewId) { +fn focus_tab(tree: &mut egui_dock::Tree, tab: &Tab) { if let Some((node_index, tab_index)) = tree.find_tab(tab) { tree.set_focused_node(node_index); tree.set_active_tab(node_index, tab_index); } } -fn is_tree_valid(tree: &egui_dock::Tree) -> bool { +fn is_tree_valid(tree: &egui_dock::Tree) -> bool { tree.iter().all(|node| match node { egui_dock::Node::Vertical { rect: _, fraction } | egui_dock::Node::Horizontal { rect: _, fraction } => fraction.is_finite(), diff --git a/crates/re_viewer/src/viewer_analytics.rs b/crates/re_viewer/src/viewer_analytics.rs index 8269f3c19cc4..02727b590127 100644 --- a/crates/re_viewer/src/viewer_analytics.rs +++ b/crates/re_viewer/src/viewer_analytics.rs @@ -1,4 +1,4 @@ -//! All telemetry analytics collected by the Rerun Viewer are defined in this file for easy auditing. +//! All telemetry analytics collected by the Depthai Viewer are defined in this file for easy auditing. //! //! There are two exceptions: //! * `crates/rerun/src/crash_handler.rs` sends anonymized callstacks on crashes @@ -67,7 +67,7 @@ impl ViewerAnalytics { // ---------------------------------------------------------------------------- -/// Here follows all the analytics collected by the Rerun Viewer. +/// Here follows all the analytics collected by the Depthai Viewer. #[cfg(all(not(target_arch = "wasm32"), feature = "analytics"))] impl ViewerAnalytics { /// When the viewer is first started diff --git a/crates/re_viewer/src/web.rs b/crates/re_viewer/src/web.rs index 58dcec080ce2..65cf3312ce8c 100644 --- a/crates/re_viewer/src/web.rs +++ b/crates/re_viewer/src/web.rs @@ -24,8 +24,9 @@ pub async fn start( let web_options = eframe::WebOptions { follow_system_theme: false, - default_theme: eframe::Theme::Dark, + default_theme: eframe::Theme::Light, wgpu_options: crate::wgpu_options(), + depth_buffer: 0, }; eframe::start_web( @@ -38,7 +39,7 @@ pub async fn start( let startup_options = crate::StartupOptions { memory_limit: re_memory::MemoryLimit { // On wasm32 we only have 4GB of memory to play around with. - limit: Some(3_500_000_000), + limit: Some(2_500_000_000), }, persist_state, }; diff --git a/crates/re_web_viewer_server/build.rs b/crates/re_web_viewer_server/build.rs index b072925e41e5..79b8fe048136 100644 --- a/crates/re_web_viewer_server/build.rs +++ b/crates/re_web_viewer_server/build.rs @@ -89,19 +89,29 @@ impl<'a> Packages<'a> { } } +fn get_and_track_env_var(env_var_name: &str) -> Result { + println!("cargo:rerun-if-env-changed={env_var_name}"); + std::env::var(env_var_name) +} + +fn is_tracked_env_var_set(env_var_name: &str) -> bool { + let var = get_and_track_env_var(env_var_name).map(|v| v.to_lowercase()); + var == Ok("1".to_owned()) || var == Ok("yes".to_owned()) || var == Ok("true".to_owned()) +} + fn main() { - if std::env::var("IS_IN_RERUN_WORKSPACE") != Ok("yes".to_owned()) { + if !is_tracked_env_var_set("IS_IN_RERUN_WORKSPACE") { // Only run if we are in the rerun workspace, not on users machines. return; } - if std::env::var("RERUN_IS_PUBLISHING") == Ok("yes".to_owned()) { + if is_tracked_env_var_set("RERUN_IS_PUBLISHING") { // We don't need to rebuild - we should have done so beforehand! // See `RELEASES.md` return; } // Rebuild the web-viewer Wasm, - // because the web_server library bundles it with `include_bytes!` + // because the web_server library bundles it with `include_bytes!`. let metadata = MetadataCommand::new() .features(CargoOpt::AllFeatures) @@ -118,12 +128,12 @@ fn main() { // or patched!). pkgs.track_implicit_dep("re_viewer"); - if std::env::var("CARGO_FEATURE___CI").is_ok() { + if get_and_track_env_var("CARGO_FEATURE___CI").is_ok() { // If the `__ci` feature is set we skip building the web viewer wasm, saving a lot of time. // This feature is set on CI (hence the name), but also with `--all-features`, which is set by rust analyzer, bacon, etc. eprintln!("__ci feature detected: Skipping building of web viewer wasm."); } else { let release = std::env::var("PROFILE").unwrap() == "release"; - re_build_web_viewer::build(release); + re_build_web_viewer::build(release, is_tracked_env_var_set("RERUN_BUILD_WEBGPU")); } } diff --git a/crates/re_web_viewer_server/src/lib.rs b/crates/re_web_viewer_server/src/lib.rs index 4d8f29172588..e6964931c490 100644 --- a/crates/re_web_viewer_server/src/lib.rs +++ b/crates/re_web_viewer_server/src/lib.rs @@ -211,7 +211,7 @@ impl WebViewerServer { pub fn new(port: WebViewerServerPort) -> Result { let bind_addr = format!("0.0.0.0:{port}").parse()?; let server = hyper::Server::try_bind(&bind_addr) - .map_err(|e| WebViewerServerError::BindFailed(port, e))? + .map_err(|err| WebViewerServerError::BindFailed(port, err))? .serve(MakeSvc); Ok(Self { server }) } diff --git a/crates/re_ws_comms/src/server.rs b/crates/re_ws_comms/src/server.rs index d6fde09d2851..d8b3e4acc900 100644 --- a/crates/re_ws_comms/src/server.rs +++ b/crates/re_ws_comms/src/server.rs @@ -34,7 +34,7 @@ impl RerunServer { let listener = TcpListener::bind(&bind_addr) .await - .map_err(|e| RerunServerError::BindFailed(port, e))?; + .map_err(|err| RerunServerError::BindFailed(port, err))?; let port = RerunServerPort(listener.local_addr()?.port()); diff --git a/crates/rerun/Cargo.toml b/crates/rerun/Cargo.toml index 15e31264d47f..1ab2ddad2591 100644 --- a/crates/rerun/Cargo.toml +++ b/crates/rerun/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "rerun" +name = "depthai-viewer" authors.workspace = true -default-run = "rerun" +default-run = "depthai-viewer" description = "Log images, point clouds, etc, and visualize them effortlessly" edition.workspace = true homepage.workspace = true @@ -17,6 +17,9 @@ version.workspace = true all-features = true targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] +[[bin]] +name = "depthai-viewer" +path = "src/main.rs" [features] default = ["analytics", "glam", "image", "native_viewer", "server", "sdk"] diff --git a/crates/rerun/README.md b/crates/rerun/README.md index ce107685a9fc..14fe6a694ce6 100644 --- a/crates/rerun/README.md +++ b/crates/rerun/README.md @@ -13,21 +13,22 @@

# Rerun Rust logging SDK + Rerun is an SDK for logging computer vision and robotics data paired with a visualizer for exploring that data over time. It lets you debug and understand the internal state and data of your systems with minimal code. ```shell cargo add rerun -```` +``` -``` rust -rerun::MsgSender::new("points") +```rust +depthai_viewer::MsgSender::new("points") .with_component(&points)? .with_component(&colors)? - .send(&mut rerun::global_session())?; + .send(&mut depthai_viewer::global_session())?; -rerun::MsgSender::new("image") - .with_component(&[rerun::components::Tensor::from_image(image)?])? - .send(&mut rerun::global_session())?; +depthai_viewer::MsgSender::new("image") + .with_component(&[depthai_viewer::components::Tensor::from_image(image)?])? + .send(&mut depthai_viewer::global_session())?; ```

@@ -35,17 +36,20 @@ rerun::MsgSender::new("image")

## Getting started + - [Examples](https://github.com/rerun-io/rerun/tree/latest/examples/rust) - [High-level docs](http://rerun.io/docs) - [Rust API docs](https://docs.rs/rerun/) - [Troubleshooting](https://www.rerun.io/docs/getting-started/troubleshooting) ## Library + You can add the `rerun` crate to your project with `cargo add rerun`. To get started, see [the examples](https://github.com/rerun-io/rerun/tree/latest/examples/rust). ## Binary + You can install the binary with `cargo install rerun` This can act either as a server, a viewer, or both, depending on which options you use when you start it. @@ -54,8 +58,8 @@ Running `rerun` with no arguments will start the viewer, waiting for an SDK to c Run `rerun --help` for more. - ### Running a web viewer + The web viewer is an experimental feature, but you can try it out with: ```sh diff --git a/crates/rerun/src/clap.rs b/crates/rerun/src/clap.rs index fddb1de26aa6..024f779b314c 100644 --- a/crates/rerun/src/clap.rs +++ b/crates/rerun/src/clap.rs @@ -34,7 +34,7 @@ enum RerunBehavior { /// #[clap(author, version, about)] /// struct MyArgs { /// #[command(flatten)] -/// rerun: rerun::clap::RerunArgs, +/// rerun: depthai_viewer::clap::RerunArgs, /// /// #[clap(long)] /// my_arg: bool, diff --git a/crates/rerun/src/crash_handler.rs b/crates/rerun/src/crash_handler.rs index ba1174daac72..a11e6834440d 100644 --- a/crates/rerun/src/crash_handler.rs +++ b/crates/rerun/src/crash_handler.rs @@ -254,7 +254,7 @@ pub fn callstack_from(start_patterns: &[&str]) -> String { "std::sys_common::backtrace::__rust_begin_short_backtrace", // Trim the bottom even more to exclude any user code that potentially used `rerun` // as a library to show a viewer. In these cases there may be sensitive user code - // that called `rerun::run`, and we do not want to include it: + // that called `depthai_viewer::run`, and we do not want to include it: "run_native_app", ]; diff --git a/crates/rerun/src/lib.rs b/crates/rerun/src/lib.rs index ae017c1c4fd9..f68297ba3763 100644 --- a/crates/rerun/src/lib.rs +++ b/crates/rerun/src/lib.rs @@ -13,7 +13,7 @@ //! - [Rust API docs](https://docs.rs/rerun/) //! - [Troubleshooting](https://www.rerun.io/docs/getting-started/troubleshooting) //! -//! There are many different ways of sending data to the Rerun Viewer depending on what you're +//! There are many different ways of sending data to the Depthai Viewer depending on what you're //! trying to achieve and whether the viewer is running in the same process as your code, in //! another process, or even as a separate web application. //! @@ -27,23 +27,23 @@ //! #### Logging //! //! ``` -//! # use rerun::external::image; +//! # use depthai_viewer::external::image; //! # fn capture_image() -> image::DynamicImage { Default::default() } -//! # fn positions() -> Vec { Default::default() } -//! # fn colors() -> Vec { Default::default() } -//! let mut rr_session = rerun::SessionBuilder::new("my_app").buffered(); +//! # fn positions() -> Vec { Default::default() } +//! # fn colors() -> Vec { Default::default() } +//! let mut rr_session = depthai_viewer::SessionBuilder::new("my_app").buffered(); //! -//! let points: Vec = positions(); -//! let colors: Vec = colors(); +//! let points: Vec = positions(); +//! let colors: Vec = colors(); //! let image: image::DynamicImage = capture_image(); //! -//! rerun::MsgSender::new("points") +//! depthai_viewer::MsgSender::new("points") //! .with_component(&points)? //! .with_component(&colors)? //! .send(&mut rr_session)?; //! -//! rerun::MsgSender::new("image") -//! .with_component(&[rerun::components::Tensor::from_image(image)?])? +//! depthai_viewer::MsgSender::new("image") +//! .with_component(&[depthai_viewer::components::Tensor::from_image(image)?])? //! .send(&mut rr_session)?; //! //! # Ok::<(), Box>(()) @@ -58,17 +58,17 @@ //! Then do this: //! //! ``` no_run -//! let mut rr_session = rerun::SessionBuilder::new("my_app").connect(rerun::default_server_addr()); +//! let mut rr_session = depthai_viewer::SessionBuilder::new("my_app").connect(depthai_viewer::default_server_addr()); //! ``` //! //! #### Buffering //! //! ``` no_run -//! # fn log_using(rr_session: &rerun::Session) {} +//! # fn log_using(rr_session: &depthai_viewer::Session) {} //! -//! let mut rr_session = rerun::SessionBuilder::new("my_app").buffered(); +//! let mut rr_session = depthai_viewer::SessionBuilder::new("my_app").buffered(); //! log_using(&mut rr_session); -//! rerun::native_viewer::show(&mut rr_session); +//! depthai_viewer::native_viewer::show(&mut rr_session); //! ``` //! //! ## Binary diff --git a/crates/rerun/src/main.rs b/crates/rerun/src/main.rs index 2c1a2bc9c5ba..ad27f207dd56 100644 --- a/crates/rerun/src/main.rs +++ b/crates/rerun/src/main.rs @@ -8,7 +8,7 @@ static GLOBAL: AccountingAllocator = async fn main() -> anyhow::Result { re_log::setup_native_logging(); let build_info = re_build_info::build_info!(); - rerun::run(build_info, rerun::CallSource::Cli, std::env::args()) + depthai_viewer::run(build_info, depthai_viewer::CallSource::Cli, std::env::args()) .await .map(std::process::ExitCode::from) } diff --git a/crates/rerun/src/native_viewer.rs b/crates/rerun/src/native_viewer.rs index e1d5fc1de4e0..478548501ef7 100644 --- a/crates/rerun/src/native_viewer.rs +++ b/crates/rerun/src/native_viewer.rs @@ -31,6 +31,7 @@ where .spawn(move || run(session)) .expect("Failed to spawn thread"); + // NOTE: Some platforms still mandate that the UI must run on the main thread, so make sure // to spawn the viewer in place and migrate the user callback to a new thread. re_viewer::run_native_app(Box::new(move |cc, re_ui| { diff --git a/crates/rerun/src/run.rs b/crates/rerun/src/run.rs index 979c05705ff7..a43d371ca96e 100644 --- a/crates/rerun/src/run.rs +++ b/crates/rerun/src/run.rs @@ -15,7 +15,7 @@ use crate::web_viewer::host_web_viewer; // Note the extra blank lines between the point-lists below: it is required by `clap`. -/// The Rerun Viewer and Server +/// The Depthai Viewer and Server /// /// Features: /// @@ -54,14 +54,13 @@ struct Args { #[clap(long)] drop_at_latency: Option, - /// An upper limit on how much memory the Rerun Viewer should use. - /// - /// When this limit is used, Rerun will purge the oldest data. - /// - /// Example: `16GB` - #[clap(long)] - memory_limit: Option, - + // /// An upper limit on how much memory the Rerun Viewer should use. + // /// + // /// When this limit is used, Rerun will purge the oldest data. + // /// + // /// Example: `16GB` + // #[clap(long)] + // memory_limit: Option, /// Whether the Rerun Viewer should persist the state of the viewer to disk. /// /// When persisted, the state will be stored at the following locations: @@ -294,11 +293,8 @@ async fn run_impl( #[cfg(feature = "native_viewer")] let startup_options = re_viewer::StartupOptions { - memory_limit: args.memory_limit.as_ref().map_or(Default::default(), |l| { - re_memory::MemoryLimit::parse(l) - .unwrap_or_else(|err| panic!("Bad --memory-limit: {err}")) - }), persist_state: args.persist_state, + ..Default::default() }; let (shutdown_rx, shutdown_bool) = setup_ctrl_c_handler(); @@ -441,7 +437,7 @@ async fn run_impl( app.set_profiler(profiler); Box::new(app) })) - .map_err(|e| e.into()); + .map_err(|err| err.into()); #[cfg(not(feature = "native_viewer"))] { diff --git a/design/blueprint_store.md b/design/blueprint_store.md index 18e95fc450c6..e4a0738d3633 100644 --- a/design/blueprint_store.md +++ b/design/blueprint_store.md @@ -36,11 +36,11 @@ The user save the data store and blueprint store to separate files. Something like this is very low-level, and not very ergonomic: ```py -rr.ui_log("top_bar", [("visible", false)]) -space_view_bpid = rr.ui_new_space_view("My 3D view") -data_bpid = rr.new_data_blueprint("world/points") -rr.ui_log(space_view_bpid, [("category", "3d"), ("children", [data_bpid])]) -rr.ui_log("viewport", [("children", [space_view_bpid])]) +viewer.ui_log("top_bar", [("visible", false)]) +space_view_bpid = viewer.ui_new_space_view("My 3D view") +data_bpid = viewer.new_data_blueprint("world/points") +viewer.ui_log(space_view_bpid, [("category", "3d"), ("children", [data_bpid])]) +viewer.ui_log("viewport", [("children", [space_view_bpid])]) ``` Adding high-level helpers on top of this is very desirable, but a lot of work. diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 000000000000..76e49aa15754 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +out.rrd diff --git a/examples/python/README.md b/examples/python/README.md index 7cfc35518eea..4f41a9caf564 100644 --- a/examples/python/README.md +++ b/examples/python/README.md @@ -1,16 +1,19 @@ # Rerun Python Examples + The simplest example is [`minimal`](minimal/main.py). You may want to start there! Read more about our examples at . ## Setup -First install the Rerun Python SDK with `pip install rerun-sdk` + +First install the Rerun Python SDK with `pip install depthai-viewer` > Note: Make sure your SDK version matches the code in the examples. -For example, if your SDK version is `0.4.0`, check out the matching tag -for this repository by running `git checkout v0.4.0`. +> For example, if your SDK version is `0.4.0`, check out the matching tag +> for this repository by running `git checkout v0.4.0`. ## Dependencies + Each example comes with its own set of dependencies listed in a `requirements.txt` file. For example, to install dependencies and run the toy `car` example (which doesn't need to download any data) run: ```sh @@ -25,6 +28,7 @@ pip install -r examples/python/requirements.txt ``` ## Running the examples + By default, the examples spawn a Rerun Viewer and stream log data to it. You can instead save the log data to an `.rrd` file using `examples/python/car/main.py --save data.rrd`. You can then open that `.rrd` file with `rerun data.rrd`. @@ -34,9 +38,11 @@ You can instead save the log data to an `.rrd` file using `examples/python/car/m NOTE: `.rrd` files do not yet guarantee any backwards or forwards compatibility. One version of Rerun will likely not be able to open an `.rrd` file generated by another Rerun version. ## Datasets + Some examples will download a small datasets before they run. They will do so the first time you run the example. The datasets will be added to a subdir called `dataset`, which is in the repo-wide `.gitignore`. ## Contributions welcome + Feel free to open a PR to add a new example! See [`CONTRIBUTING.md`](../CONTRIBUTING.md) for details on how to contribute. diff --git a/examples/python/api_demo/main.py b/examples/python/api_demo/main.py index aff55a99bab8..bb6fc66137d4 100755 --- a/examples/python/api_demo/main.py +++ b/examples/python/api_demo/main.py @@ -15,63 +15,71 @@ import os import cv2 +import depthai_viewer as viewer import numpy as np -import rerun as rr from scipy.spatial.transform import Rotation def run_segmentation() -> None: - rr.set_time_seconds("sim_time", 1) + viewer.set_time_seconds("sim_time", 1) # Log an image before we have set up our labels segmentation_img = np.zeros([128, 128], dtype="uint8") segmentation_img[10:20, 30:50] = 13 segmentation_img[80:100, 60:80] = 42 segmentation_img[20:50, 90:110] = 99 - rr.log_segmentation_image("seg_demo/img", segmentation_img) + viewer.log_segmentation_image("seg_demo/img", segmentation_img) # Log a bunch of classified 2D points - rr.log_point("seg_demo/single_point", np.array([64, 64]), class_id=13) - rr.log_point("seg_demo/single_point_labeled", np.array([90, 50]), class_id=13, label="labeled point") - rr.log_points("seg_demo/several_points0", np.array([[20, 50], [100, 70], [60, 30]]), class_ids=42) - rr.log_points( + viewer.log_point("seg_demo/single_point", np.array([64, 64]), class_id=13) + viewer.log_point("seg_demo/single_point_labeled", np.array([90, 50]), class_id=13, label="labeled point") + viewer.log_points("seg_demo/several_points0", np.array([[20, 50], [100, 70], [60, 30]]), class_ids=42) + viewer.log_points( "seg_demo/several_points1", np.array([[40, 50], [120, 70], [80, 30]]), class_ids=np.array([13, 42, 99], dtype=np.uint8), ) - rr.log_points( + viewer.log_points( "seg_demo/many points", np.array([[100 + (int(i / 5)) * 2, 100 + (i % 5) * 2] for i in range(25)]), class_ids=np.array([42], dtype=np.uint8), ) - rr.log_text_entry("logs/seg_demo_log", "default colored rects, default colored points, a single point has a label") + viewer.log_text_entry( + "logs/seg_demo_log", "default colored rects, default colored points, a single point has a label" + ) # Log an initial segmentation map with arbitrary colors - rr.set_time_seconds("sim_time", 2) - rr.log_annotation_context("seg_demo", [(13, "label1"), (42, "label2"), (99, "label3")], timeless=False) - rr.log_text_entry( + viewer.set_time_seconds("sim_time", 2) + viewer.log_annotation_context("seg_demo", [(13, "label1"), (42, "label2"), (99, "label3")], timeless=False) + viewer.log_text_entry( "logs/seg_demo_log", "default colored rects, default colored points, " "all points except the bottom right clusters have labels", ) # Log an updated segmentation map with specific colors - rr.set_time_seconds("sim_time", 3) - rr.log_annotation_context( + viewer.set_time_seconds("sim_time", 3) + viewer.log_annotation_context( "seg_demo", [(13, "label1", (255, 0, 0)), (42, "label2", (0, 255, 0)), (99, "label3", (0, 0, 255))], timeless=False, ) - rr.log_text_entry("logs/seg_demo_log", "points/rects with user specified colors") + viewer.log_text_entry("logs/seg_demo_log", "points/rects with user specified colors") # Log with a mixture of set and unset colors / labels - rr.set_time_seconds("sim_time", 4) - rr.log_annotation_context( + viewer.set_time_seconds("sim_time", 4) + viewer.log_annotation_context( "seg_demo", - [rr.AnnotationInfo(13, color=(255, 0, 0)), (42, "label2", (0, 255, 0)), rr.AnnotationInfo(99, label="label3")], + [ + viewer.AnnotationInfo(13, color=(255, 0, 0)), + (42, "label2", (0, 255, 0)), + viewer.AnnotationInfo(99, label="label3"), + ], timeless=False, ) - rr.log_text_entry("logs/seg_demo_log", "label1 disappears and everything with label3 is now default colored again") + viewer.log_text_entry( + "logs/seg_demo_log", "label1 disappears and everything with label3 is now default colored again" + ) def run_2d_lines() -> None: @@ -79,27 +87,27 @@ def run_2d_lines() -> None: T = np.linspace(0, 5, 100) for n in range(2, len(T)): - rr.set_time_seconds("sim_time", T[n]) + viewer.set_time_seconds("sim_time", T[n]) t = T[:n] x = np.cos(t * 5) * t y = np.sin(t * 5) * t pts = np.vstack([x, y]).T - rr.log_line_strip("2d_lines/spiral", positions=pts) + viewer.log_line_strip("2d_lines/spiral", positions=pts) def run_3d_points() -> None: import random - rr.set_time_seconds("sim_time", 1) - rr.log_point("3d_points/single_point_unlabeled", np.array([10.0, 0.0, 0.0])) - rr.log_point("3d_points/single_point_labeled", np.array([0.0, 0.0, 0.0]), label="labeled point") - rr.log_points( + viewer.set_time_seconds("sim_time", 1) + viewer.log_point("3d_points/single_point_unlabeled", np.array([10.0, 0.0, 0.0])) + viewer.log_point("3d_points/single_point_labeled", np.array([0.0, 0.0, 0.0]), label="labeled point") + viewer.log_points( "3d_points/spiral_small", np.array([[math.sin(i * 0.2) * 5, math.cos(i * 0.2) * 5 + 10.0, i * 4.0 - 5.0] for i in range(9)]), labels=[str(i) for i in range(9)], radii=np.linspace(0.1, 2.0, num=9), ) - rr.log_points( + viewer.log_points( "3d_points/spiral_big", np.array([[math.sin(i * 0.2) * 5, math.cos(i * 0.2) * 5 - 10.0, i * 0.4 - 5.0] for i in range(100)]), labels=[str(i) for i in range(100)], @@ -108,7 +116,7 @@ def run_3d_points() -> None: def raw_mesh() -> None: - rr.log_mesh( + viewer.log_mesh( "mesh_demo/triangle", positions=[[0, 0, 0], [0, 0.7, 0], [1.0, 0.0, 0]], vertex_colors=[[255, 0, 0], [0, 255, 0], [0, 0, 255]], @@ -118,51 +126,51 @@ def raw_mesh() -> None: def run_rects() -> None: import random - rr.set_time_seconds("sim_time", 1) + viewer.set_time_seconds("sim_time", 1) # Add an image img = np.zeros([1024, 1024, 3], dtype="uint8") img[:, :] = (128, 128, 128) - rr.log_image("rects_demo/img", img) + viewer.log_image("rects_demo/img", img) # 20 random rectangles - rr.set_time_seconds("sim_time", 2) + viewer.set_time_seconds("sim_time", 2) rects_xy = np.random.rand(20, 2) * 1024 rects_wh = np.random.rand(20, 2) * (1024 - rects_xy + 1) rects = np.hstack((rects_xy, rects_wh)) colors = np.array([[random.randrange(255) for _ in range(3)] for _ in range(20)]) - rr.log_rects("rects_demo/rects", rects, colors=colors, rect_format=rr.RectFormat.XYWH) + viewer.log_rects("rects_demo/rects", rects, colors=colors, rect_format=viewer.RectFormat.XYWH) # Clear the rectangles by logging an empty set - rr.set_time_seconds("sim_time", 3) - rr.log_rects("rects_demo/rects", []) + viewer.set_time_seconds("sim_time", 3) + viewer.log_rects("rects_demo/rects", []) def run_text_logs() -> None: - rr.log_text_entry("logs", "Text with explicitly set color", color=[255, 215, 0], timeless=True) - rr.log_text_entry("logs", "this entry has loglevel TRACE", level=rr.LogLevel.TRACE) + viewer.log_text_entry("logs", "Text with explicitly set color", color=[255, 215, 0], timeless=True) + viewer.log_text_entry("logs", "this entry has loglevel TRACE", level=viewer.LogLevel.TRACE) - logging.getLogger().addHandler(rr.LoggingHandler("logs/handler")) + logging.getLogger().addHandler(viewer.LoggingHandler("logs/handler")) logging.getLogger().setLevel(-1) logging.info("This log got added through a `LoggingHandler`") def run_log_cleared() -> None: - rr.set_time_seconds("sim_time", 1) - rr.log_rect("null_demo/rect/0", [5, 5, 4, 4], label="Rect1", color=(255, 0, 0)) - rr.log_rect("null_demo/rect/1", [10, 5, 4, 4], label="Rect2", color=(0, 255, 0)) - rr.set_time_seconds("sim_time", 2) - rr.log_cleared("null_demo/rect/0") - rr.set_time_seconds("sim_time", 3) - rr.log_cleared("null_demo/rect", recursive=True) - rr.set_time_seconds("sim_time", 4) - rr.log_rect("null_demo/rect/0", [5, 5, 4, 4]) - rr.set_time_seconds("sim_time", 5) - rr.log_rect("null_demo/rect/1", [10, 5, 4, 4]) + viewer.set_time_seconds("sim_time", 1) + viewer.log_rect("null_demo/rect/0", [5, 5, 4, 4], label="Rect1", color=(255, 0, 0)) + viewer.log_rect("null_demo/rect/1", [10, 5, 4, 4], label="Rect2", color=(0, 255, 0)) + viewer.set_time_seconds("sim_time", 2) + viewer.log_cleared("null_demo/rect/0") + viewer.set_time_seconds("sim_time", 3) + viewer.log_cleared("null_demo/rect", recursive=True) + viewer.set_time_seconds("sim_time", 4) + viewer.log_rect("null_demo/rect/0", [5, 5, 4, 4]) + viewer.set_time_seconds("sim_time", 5) + viewer.log_rect("null_demo/rect/1", [10, 5, 4, 4]) def transforms_rigid_3d() -> None: - rr.set_time_seconds("sim_time", 0) + viewer.set_time_seconds("sim_time", 0) sun_to_planet_distance = 6.0 planet_to_moon_distance = 3.0 @@ -170,22 +178,22 @@ def transforms_rigid_3d() -> None: rotation_speed_moon = 5.0 # Planetary motion is typically in the XY plane. - rr.log_view_coordinates("transforms3d", up="+Z", timeless=True) - rr.log_view_coordinates("transforms3d/sun", up="+Z", timeless=True) - rr.log_view_coordinates("transforms3d/sun/planet", up="+Z", timeless=True) - rr.log_view_coordinates("transforms3d/sun/planet/moon", up="+Z", timeless=True) + viewer.log_view_coordinates("transforms3d", up="+Z", timeless=True) + viewer.log_view_coordinates("transforms3d/sun", up="+Z", timeless=True) + viewer.log_view_coordinates("transforms3d/sun/planet", up="+Z", timeless=True) + viewer.log_view_coordinates("transforms3d/sun/planet/moon", up="+Z", timeless=True) # All are in the center of their own space: - rr.log_point("transforms3d/sun", [0.0, 0.0, 0.0], radius=1.0, color=[255, 200, 10]) - rr.log_point("transforms3d/sun/planet", [0.0, 0.0, 0.0], radius=0.4, color=[40, 80, 200]) - rr.log_point("transforms3d/sun/planet/moon", [0.0, 0.0, 0.0], radius=0.15, color=[180, 180, 180]) + viewer.log_point("transforms3d/sun", [0.0, 0.0, 0.0], radius=1.0, color=[255, 200, 10]) + viewer.log_point("transforms3d/sun/planet", [0.0, 0.0, 0.0], radius=0.4, color=[40, 80, 200]) + viewer.log_point("transforms3d/sun/planet/moon", [0.0, 0.0, 0.0], radius=0.15, color=[180, 180, 180]) # "dust" around the "planet" (and inside, don't care) # distribution is quadratically higher in the middle radii = np.random.rand(200) * planet_to_moon_distance * 0.5 angles = np.random.rand(200) * math.tau height = np.power(np.random.rand(200), 0.2) * 0.5 - 0.5 - rr.log_points( + viewer.log_points( "transforms3d/sun/planet/dust", np.array([np.sin(angles) * radii, np.cos(angles) * radii, height]).transpose(), colors=[80, 80, 80], @@ -195,11 +203,11 @@ def transforms_rigid_3d() -> None: # paths where the planet & moon move angles = np.arange(0.0, 1.01, 0.01) * math.tau circle = np.array([np.sin(angles), np.cos(angles), angles * 0.0]).transpose() - rr.log_line_strip( + viewer.log_line_strip( "transforms3d/sun/planet_path", circle * sun_to_planet_distance, ) - rr.log_line_strip( + viewer.log_line_strip( "transforms3d/sun/planet/moon_path", circle * planet_to_moon_distance, ) @@ -207,10 +215,10 @@ def transforms_rigid_3d() -> None: # movement via transforms for i in range(0, 6 * 120): time = i / 120.0 - rr.set_time_seconds("sim_time", time) + viewer.set_time_seconds("sim_time", time) rotation_q = [0, 0, 0, 1] - rr.log_rigid3( + viewer.log_rigid3( "transforms3d/sun/planet", parent_from_child=( [ @@ -221,7 +229,7 @@ def transforms_rigid_3d() -> None: Rotation.from_euler("x", 20, degrees=True).as_quat(), ), ) - rr.log_rigid3( + viewer.log_rigid3( "transforms3d/sun/planet/moon", child_from_parent=( [ @@ -235,8 +243,8 @@ def transforms_rigid_3d() -> None: def run_bounding_box() -> None: - rr.set_time_seconds("sim_time", 0) - rr.log_obb( + viewer.set_time_seconds("sim_time", 0) + viewer.log_obb( "bbox_demo/bbox", half_size=[1.0, 0.5, 0.25], position=np.array([0.0, 0.0, 0.0]), @@ -246,8 +254,8 @@ def run_bounding_box() -> None: label="box/t0", ) - rr.set_time_seconds("sim_time", 1) - rr.log_obb( + viewer.set_time_seconds("sim_time", 1) + viewer.log_obb( "bbox_demo/bbox", half_size=[1.0, 0.5, 0.25], position=np.array([1.0, 0.0, 0.0]), @@ -259,19 +267,19 @@ def run_bounding_box() -> None: def run_extension_component() -> None: - rr.set_time_seconds("sim_time", 0) + viewer.set_time_seconds("sim_time", 0) # Hack to establish 2d view bounds - rr.log_rect("extension_components", [0, 0, 128, 128]) + viewer.log_rect("extension_components", [0, 0, 128, 128]) # Single point - rr.log_point("extension_components/point", np.array([64, 64]), color=(255, 0, 0)) + viewer.log_point("extension_components/point", np.array([64, 64]), color=(255, 0, 0)) # Separate extension component - rr.log_extension_components("extension_components/point", {"confidence": 0.9}) + viewer.log_extension_components("extension_components/point", {"confidence": 0.9}) # Batch points with extension # Note: each extension component must either be length 1 (a splat) or the same length as the batch - rr.set_time_seconds("sim_time", 1) - rr.log_points( + viewer.set_time_seconds("sim_time", 1) + viewer.log_points( "extension_components/points", np.array([[32, 32], [32, 96], [96, 32], [96, 96]]), colors=(0, 255, 0), @@ -286,11 +294,11 @@ def run_image_tensors() -> None: img_bgra = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) img_rgba = cv2.cvtColor(img_bgra, cv2.COLOR_BGRA2RGBA) - rr.log_image("img_rgba", img_rgba) + viewer.log_image("img_rgba", img_rgba) img_rgb = cv2.cvtColor(img_rgba, cv2.COLOR_RGBA2RGB) - rr.log_image("img_rgb", img_rgb) + viewer.log_image("img_rgb", img_rgb) img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY) - rr.log_image("img_gray", img_gray) + viewer.log_image("img_gray", img_gray) dtypes = [ "uint8", @@ -307,9 +315,9 @@ def run_image_tensors() -> None: ] for dtype in dtypes: - rr.log_image(f"img_rgba_{dtype}", img_rgba.astype(dtype)) - rr.log_image(f"img_rgb_{dtype}", img_rgb.astype(dtype)) - rr.log_image(f"img_gray_{dtype}", img_gray.astype(dtype)) + viewer.log_image(f"img_rgba_{dtype}", img_rgba.astype(dtype)) + viewer.log_image(f"img_rgb_{dtype}", img_rgb.astype(dtype)) + viewer.log_image(f"img_gray_{dtype}", img_gray.astype(dtype)) def main() -> None: @@ -332,10 +340,11 @@ def main() -> None: "--demo", type=str, default="most", help="What demo to run", choices=["most", "all"] + list(demos.keys()) ) - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "api_demo") + viewer.script_setup(args, "api_demo") if args.demo in ["most", "all"]: print(f"Running {args.demo} demos…") @@ -350,7 +359,7 @@ def main() -> None: demo = demos[args.demo] demo() - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/api_demo/requirements.txt b/examples/python/api_demo/requirements.txt index 49dd44f9f951..1d84a2910cbd 100644 --- a/examples/python/api_demo/requirements.txt +++ b/examples/python/api_demo/requirements.txt @@ -1,4 +1,4 @@ numpy opencv-python -rerun-sdk +depthai-viewer scipy diff --git a/examples/python/arkitscenes/download_dataset.py b/examples/python/arkitscenes/download_dataset.py index d2f5e2cbe62a..de0e07634e31 100644 --- a/examples/python/arkitscenes/download_dataset.py +++ b/examples/python/arkitscenes/download_dataset.py @@ -3,6 +3,7 @@ import math import os import subprocess +import zipfile from pathlib import Path from typing import Final, List, Optional @@ -121,9 +122,9 @@ def download_file(url: str, file_name: str, dst: Path) -> bool: def unzip_file(file_name: str, dst: Path, keep_zip: bool = True) -> bool: filepath = os.path.join(dst, file_name) print(f"Unzipping zip file {filepath}") - command = f"unzip -oq {filepath} -d {dst}" try: - subprocess.check_call(command, shell=True) + with zipfile.ZipFile(filepath, "r") as zip_ref: + zip_ref.extractall(dst) except Exception as error: print(f"Error unzipping {filepath}, error: {error}") return False @@ -306,6 +307,7 @@ def ensure_recording_available(video_id: str, include_highres: bool) -> Path: Returns the path to the recording for a given video_id. Args: + ---- video_id (str): Identifier for the recording. Returns diff --git a/examples/python/arkitscenes/main.py b/examples/python/arkitscenes/main.py index e05277eff918..ada653949dcd 100755 --- a/examples/python/arkitscenes/main.py +++ b/examples/python/arkitscenes/main.py @@ -2,14 +2,14 @@ import argparse import json import os -from pathlib import Path +from pathlib import Path, PosixPath from typing import Any, Dict, List, Tuple import cv2 +import depthai_viewer as viewer import matplotlib.pyplot as plt import numpy as np import numpy.typing as npt -import rerun as rr import trimesh from download_dataset import AVAILABLE_RECORDINGS, ensure_recording_available from scipy.spatial.transform import Rotation as R @@ -68,7 +68,7 @@ def log_annotated_bboxes(annotation: Dict[str, Any]) -> Tuple[npt.NDArray[np.flo rot = R.from_matrix(rotation).inv() - rr.log_obb( + viewer.log_obb( f"world/annotations/box-{uid}-{label}", half_size=half_size, position=centroid, @@ -135,7 +135,7 @@ def log_line_segments(entity_path: str, bboxes_2d_filtered: npt.NDArray[np.float # log centroid and add label so that object label is visible in the 2d view if valid_points.size > 0: centroid = valid_points.mean(axis=0) - rr.log_point(f"{entity_path}/centroid", centroid, color=color, label=label) + viewer.log_point(f"{entity_path}/centroid", centroid, color=color, label=label) else: pass @@ -160,7 +160,7 @@ def log_line_segments(entity_path: str, bboxes_2d_filtered: npt.NDArray[np.float bboxes_2d_filtered[3], bboxes_2d_filtered[7] ], dtype=np.float32) - rr.log_line_segments(entity_path, segments, color=color) + viewer.log_line_segments(entity_path, segments, color=color) def project_3d_bboxes_to_2d_keypoints( @@ -244,18 +244,18 @@ def log_camera( # Project 3D bounding boxes into 2D image bboxes_2d = project_3d_bboxes_to_2d_keypoints(bboxes, camera_from_world, intrinsic, img_width=w, img_height=h) # clear previous centroid labels - rr.log_cleared(f"{entity_id}/bbox-2d-segments", recursive=True) + viewer.log_cleared(f"{entity_id}/bbox-2d-segments", recursive=True) # Log line segments for each bounding box in the image for i, (label, bbox_2d) in enumerate(zip(bbox_labels, bboxes_2d)): log_line_segments(f"{entity_id}/bbox-2d-segments/{label}", bbox_2d.reshape(-1, 2), colors[i], label) - rr.log_rigid3( + viewer.log_rigid3( # pathlib makes it easy to get the parent, but log_rigid requires a string - str(Path(entity_id).parent), + str(PosixPath(entity_id).parent), child_from_parent=camera_from_world, xyz="RDF", # X=Right, Y=Down, Z=Forward ) - rr.log_pinhole(f"{entity_id}", child_from_parent=intrinsic, width=w, height=h) + viewer.log_pinhole(f"{entity_id}", child_from_parent=intrinsic, width=w, height=h) def read_camera_from_world(traj_string: str) -> Tuple[str, Tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]]: @@ -263,6 +263,7 @@ def read_camera_from_world(traj_string: str) -> Tuple[str, Tuple[npt.NDArray[np. Reads out camera_from_world transform from trajectory string. Args: + ---- traj_string: A space-delimited file where each line represents a camera position at a particular timestamp. The file has seven columns: * Column 1: timestamp @@ -309,6 +310,7 @@ def log_arkit(recording_path: Path, include_highres: bool) -> None: Logs ARKit recording data using Rerun. Args: + ---- recording_path (Path): The path to the ARKit recording. Returns @@ -342,13 +344,13 @@ def log_arkit(recording_path: Path, include_highres: bool) -> None: timestamp = f"{round(float(timestamp), 3):.3f}" camera_from_world_dict[timestamp] = camera_from_world - rr.log_view_coordinates("world", up="+Z", right_handed=True, timeless=True) + viewer.log_view_coordinates("world", up="+Z", right_handed=True, timeless=True) ply_path = recording_path / f"{recording_path.stem}_3dod_mesh.ply" print(f"Loading {ply_path}…") assert os.path.isfile(ply_path), f"Failed to find {ply_path}" mesh_ply = trimesh.load(str(ply_path)) - rr.log_mesh( + viewer.log_mesh( "world/mesh", positions=mesh_ply.vertices, indices=mesh_ply.faces, @@ -367,7 +369,7 @@ def log_arkit(recording_path: Path, include_highres: bool) -> None: print("Processing frames…") for frame_timestamp in tqdm(lowres_frame_ids): # frame_id is equivalent to timestamp - rr.set_time_seconds("time", float(frame_timestamp)) + viewer.set_time_seconds("time", float(frame_timestamp)) # load the lowres image and depth bgr = cv2.imread(f"{lowres_image_dir}/{video_id}_{frame_timestamp}.png") rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB) @@ -388,12 +390,12 @@ def log_arkit(recording_path: Path, include_highres: bool) -> None: colors_list, ) - rr.log_image(f"{lowres_posed_entity_id}/rgb", rgb) - rr.log_depth_image(f"{lowres_posed_entity_id}/depth", depth, meter=1000) + viewer.log_image(f"{lowres_posed_entity_id}/rgb", rgb) + viewer.log_depth_image(f"{lowres_posed_entity_id}/depth", depth, meter=1000) # log the high res camera if high_res_exists: - rr.set_time_seconds("time high resolution", float(frame_timestamp)) + viewer.set_time_seconds("time high resolution", float(frame_timestamp)) # only low res camera has a trajectory, high res does not so need to find the closest low res frame id closest_lowres_frame_id = find_closest_frame_id(frame_timestamp, camera_from_world_dict) highres_intri_path = intrinsics_dir / f"{video_id}_{frame_timestamp}.pincam" @@ -412,8 +414,8 @@ def log_arkit(recording_path: Path, include_highres: bool) -> None: highres_depth = cv2.imread(f"{depth_dir}/{video_id}_{frame_timestamp}.png", cv2.IMREAD_ANYDEPTH) highres_rgb = cv2.cvtColor(highres_bgr, cv2.COLOR_BGR2RGB) - rr.log_image(f"{highres_entity_id}/rgb", highres_rgb) - rr.log_depth_image(f"{highres_entity_id}/depth", highres_depth, meter=1000) + viewer.log_image(f"{highres_entity_id}/rgb", highres_rgb) + viewer.log_depth_image(f"{highres_entity_id}/depth", highres_depth, meter=1000) def main() -> None: @@ -430,14 +432,15 @@ def main() -> None: action="store_true", help="Include the high resolution camera and depth images", ) - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "arkitscenes") + viewer.script_setup(args, "arkitscenes") recording_path = ensure_recording_available(args.video_id, args.include_highres) log_arkit(recording_path, args.include_highres) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/arkitscenes/requirements.txt b/examples/python/arkitscenes/requirements.txt index 0629c41c76b3..bf77809429fe 100644 --- a/examples/python/arkitscenes/requirements.txt +++ b/examples/python/arkitscenes/requirements.txt @@ -1,7 +1,7 @@ numpy opencv-python pandas -rerun-sdk +depthai-viewer scipy tqdm trimesh diff --git a/examples/python/car/main.py b/examples/python/car/main.py index 8745f064a026..c448ae368765 100755 --- a/examples/python/car/main.py +++ b/examples/python/car/main.py @@ -6,9 +6,9 @@ from typing import Iterator, Tuple import cv2 +import depthai_viewer as viewer import numpy as np import numpy.typing as npt -import rerun as rr def log_car_data() -> None: @@ -16,22 +16,22 @@ def log_car_data() -> None: NUM_FRAMES = 40 # Set our preferred up-axis on the space that we will log the points to: - rr.log_view_coordinates("world", up="-Y", timeless=True) + viewer.log_view_coordinates("world", up="-Y", timeless=True) for sample in generate_car_data(num_frames=NUM_FRAMES): # This will assign logged entities a timeline called `frame_nr`. # In the viewer you can select how to view entities - by frame_nr or the built-in `log_time`. - rr.set_time_sequence("frame_nr", sample.frame_idx) + viewer.set_time_sequence("frame_nr", sample.frame_idx) # Log the camera pose: - rr.log_rigid3( + viewer.log_rigid3( "world/camera", parent_from_child=(sample.camera.position, sample.camera.rotation_q), xyz="RDF", # X=Right, Y=Down, Z=Forward ) # Log the camera projection matrix: - rr.log_pinhole( + viewer.log_pinhole( "world/camera/image", child_from_parent=sample.camera.intrinsics, width=sample.camera.resolution[0], @@ -39,14 +39,14 @@ def log_car_data() -> None: ) # We log the rgb image to the image-space of the camera: - rr.log_image("world/camera/image/rgb", sample.rgb_image) + viewer.log_image("world/camera/image/rgb", sample.rgb_image) # Same with the bounding box: ((car_x, car_y), (car_w, car_h)) = sample.car_bbox - rr.log_rect("world/camera/image/bbox", [car_x, car_y, car_w, car_h], label="A car", color=(0, 128, 255)) + viewer.log_rect("world/camera/image/bbox", [car_x, car_y, car_w, car_h], label="A car", color=(0, 128, 255)) # The depth image is in millimeters, so we set meter=1000 - rr.log_depth_image("world/camera/image/depth", sample.depth_image_mm, meter=1000) + viewer.log_depth_image("world/camera/image/depth", sample.depth_image_mm, meter=1000) class DummyCar: @@ -254,12 +254,13 @@ def generate_car_data(num_frames: int) -> Iterator[SampleFrame]: def main() -> None: parser = argparse.ArgumentParser(description="Logs rich data using the Rerun SDK.") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "car") + viewer.script_setup(args, "car") log_car_data() - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/car/requirements.txt b/examples/python/car/requirements.txt index 13a25792d4ee..554e9b09870d 100644 --- a/examples/python/car/requirements.txt +++ b/examples/python/car/requirements.txt @@ -1,3 +1,3 @@ numpy opencv-python<4.6 # Avoid opencv-4.6 since it rotates images incorrectly (https://github.com/opencv/opencv/issues/22088) -rerun-sdk +depthai-viewer diff --git a/examples/python/clock/main.py b/examples/python/clock/main.py index 1eff8821373c..4b0722795b9f 100755 --- a/examples/python/clock/main.py +++ b/examples/python/clock/main.py @@ -10,8 +10,8 @@ import math from typing import Final, Tuple +import depthai_viewer as viewer import numpy as np -import rerun as rr LENGTH_S: Final = 20.0 LENGTH_M: Final = 10.0 @@ -30,9 +30,9 @@ def rotate(angle: float, len: float) -> Tuple[float, float, float]: 0.0, ) - rr.log_view_coordinates("world", up="+Y", timeless=True) + viewer.log_view_coordinates("world", up="+Y", timeless=True) - rr.log_obb( + viewer.log_obb( "world/frame", half_size=[LENGTH_S, LENGTH_S, 1.0], position=[0.0, 0.0, 0.0], @@ -43,25 +43,29 @@ def rotate(angle: float, len: float) -> Tuple[float, float, float]: for step in range(steps): t_secs = step - rr.set_time_seconds("sim_time", t_secs) + viewer.set_time_seconds("sim_time", t_secs) scaled_s = (t_secs % 60) / 60.0 point_s = np.array(rotate(math.tau * scaled_s, LENGTH_S)) color_s = (int(255 - (scaled_s * 255)), int(scaled_s * 255), 0, 128) - rr.log_point("world/seconds_pt", position=point_s, color=color_s) - rr.log_arrow("world/seconds_hand", origin=[0.0, 0.0, 0.0], vector=point_s, color=color_s, width_scale=WIDTH_S) + viewer.log_point("world/seconds_pt", position=point_s, color=color_s) + viewer.log_arrow( + "world/seconds_hand", origin=[0.0, 0.0, 0.0], vector=point_s, color=color_s, width_scale=WIDTH_S + ) scaled_m = (t_secs % 3600) / 3600.0 point_m = np.array(rotate(math.tau * scaled_m, LENGTH_M)) color_m = (int(255 - (scaled_m * 255)), int(scaled_m * 255), 128, 128) - rr.log_point("world/minutes_pt", position=point_m, color=color_m) - rr.log_arrow("world/minutes_hand", origin=[0.0, 0.0, 0.0], vector=point_m, color=color_m, width_scale=WIDTH_M) + viewer.log_point("world/minutes_pt", position=point_m, color=color_m) + viewer.log_arrow( + "world/minutes_hand", origin=[0.0, 0.0, 0.0], vector=point_m, color=color_m, width_scale=WIDTH_M + ) scaled_h = (t_secs % 43200) / 43200.0 point_h = np.array(rotate(math.tau * scaled_h, LENGTH_H)) color_h = (int(255 - (scaled_h * 255)), int(scaled_h * 255), 255, 255) - rr.log_point("world/hours_pt", position=point_h, color=color_h) - rr.log_arrow("world/hours_hand", origin=[0.0, 0.0, 0.0], vector=point_h, color=color_h, width_scale=WIDTH_M) + viewer.log_point("world/hours_pt", position=point_h, color=color_h) + viewer.log_arrow("world/hours_hand", origin=[0.0, 0.0, 0.0], vector=point_h, color=color_h, width_scale=WIDTH_M) if __name__ == "__main__": @@ -69,9 +73,10 @@ def rotate(angle: float, len: float) -> Tuple[float, float, float]: description="An example visualizing an analog clock is built with Rerun Arrow3D primitives." ) parser.add_argument("--steps", type=int, default=10_000, help="The number of time steps to log") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "clock") + viewer.script_setup(args, "clock") log_clock(args.steps) - rr.script_teardown(args) + viewer.script_teardown(args) diff --git a/examples/python/clock/requirements.txt b/examples/python/clock/requirements.txt index fa4ff5da669a..24975fd48cc3 100644 --- a/examples/python/clock/requirements.txt +++ b/examples/python/clock/requirements.txt @@ -1,2 +1,2 @@ numpy -rerun-sdk +depthai-viewer diff --git a/examples/python/colmap/main.py b/examples/python/colmap/main.py index cb83a0e800ea..d9512afe5681 100755 --- a/examples/python/colmap/main.py +++ b/examples/python/colmap/main.py @@ -9,10 +9,10 @@ from typing import Any, Final, Optional, Tuple import cv2 +import depthai_viewer as viewer import numpy as np import numpy.typing as npt import requests -import rerun as rr from read_write_model import Camera, read_model from tqdm import tqdm @@ -99,7 +99,7 @@ def read_and_log_sparse_reconstruction( # Filter out noisy points points3D = {id: point for id, point in points3D.items() if point.rgb.any() and len(point.image_ids) > 4} - rr.log_view_coordinates("/", up="-Y", timeless=True) + viewer.log_view_coordinates("/", up="-Y", timeless=True) # Iterate through images (video frames) logging data related to each frame. for image in sorted(images.values(), key=lambda im: im.name): # type: ignore[no-any-return] @@ -134,24 +134,24 @@ def read_and_log_sparse_reconstruction( if resize: visible_xys *= scale_factor - rr.set_time_sequence("frame", frame_idx) + viewer.set_time_sequence("frame", frame_idx) points = [point.xyz for point in visible_xyzs] point_colors = [point.rgb for point in visible_xyzs] point_errors = [point.error for point in visible_xyzs] - rr.log_scalar("plot/avg_reproj_err", np.mean(point_errors), color=[240, 45, 58]) + viewer.log_scalar("plot/avg_reproj_err", np.mean(point_errors), color=[240, 45, 58]) - rr.log_points("points", points, colors=point_colors, ext={"error": point_errors}) + viewer.log_points("points", points, colors=point_colors, ext={"error": point_errors}) - rr.log_rigid3( + viewer.log_rigid3( "camera", child_from_parent=camera_from_world, xyz="RDF", # X=Right, Y=Down, Z=Forward ) # Log camera intrinsics - rr.log_pinhole( + viewer.log_pinhole( "camera/image", child_from_parent=intrinsics, width=camera.width, @@ -163,11 +163,11 @@ def read_and_log_sparse_reconstruction( img = cv2.resize(img, resize) jpeg_quality = [int(cv2.IMWRITE_JPEG_QUALITY), 75] _, encimg = cv2.imencode(".jpg", img, jpeg_quality) - rr.log_image_file("camera/image", img_bytes=encimg) + viewer.log_image_file("camera/image", img_bytes=encimg) else: - rr.log_image_file("camera/image", img_path=dataset_path / "images" / image.name) + viewer.log_image_file("camera/image", img_path=dataset_path / "images" / image.name) - rr.log_points("camera/image/keypoints", visible_xys, colors=[34, 138, 167]) + viewer.log_points("camera/image/keypoints", visible_xys, colors=[34, 138, 167]) def main() -> None: @@ -181,16 +181,17 @@ def main() -> None: help="Which dataset to download", ) parser.add_argument("--resize", action="store", help="Target resolution to resize images") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] if args.resize: args.resize = tuple(int(x) for x in args.resize.split("x")) - rr.script_setup(args, "colmap") + viewer.script_setup(args, "colmap") dataset_path = get_downloaded_dataset_path(args.dataset) read_and_log_sparse_reconstruction(dataset_path, filter_output=not args.unfiltered, resize=args.resize) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/colmap/requirements.txt b/examples/python/colmap/requirements.txt index 4f65e12c4d02..f7d4d3ff824f 100644 --- a/examples/python/colmap/requirements.txt +++ b/examples/python/colmap/requirements.txt @@ -1,5 +1,5 @@ opencv-python<4.6 # Avoid opencv-4.6 since it rotates images incorrectly (https://github.com/opencv/opencv/issues/22088) numpy requests==2.28.1 -rerun-sdk +depthai-viewer tqdm diff --git a/examples/python/deep_sdf/main.py b/examples/python/deep_sdf/main.py index 7bc91a827242..8b0c7630c8ac 100755 --- a/examples/python/deep_sdf/main.py +++ b/examples/python/deep_sdf/main.py @@ -34,10 +34,10 @@ from timeit import default_timer as timer from typing import Tuple, cast +import depthai_viewer as viewer import mesh_to_sdf import numpy as np import numpy.typing as npt -import rerun as rr import trimesh from download_dataset import AVAILABLE_MESHES, ensure_mesh_downloaded from trimesh import Trimesh @@ -53,7 +53,7 @@ def wrapper(*args, **kwargs): # type: ignore[no-untyped-def] now = timer() result = func(*args, **kwargs) elapsed_ms = (timer() - now) * 1_000.0 - rr.log_text_entry(objpath, f"execution took {elapsed_ms:.1f}ms", level=level) + viewer.log_text_entry(objpath, f"execution took {elapsed_ms:.1f}ms", level=level) return result return wrapper @@ -62,13 +62,13 @@ def wrapper(*args, **kwargs): # type: ignore[no-untyped-def] # TODO(cmc): This really should be the job of the SDK. -def get_mesh_format(mesh: Trimesh) -> rr.MeshFormat: +def get_mesh_format(mesh: Trimesh) -> viewer.MeshFormat: ext = Path(mesh.metadata["file_name"]).suffix.lower() try: return { - ".glb": rr.MeshFormat.GLB, + ".glb": viewer.MeshFormat.GLB, # ".gltf": MeshFormat.GLTF, - ".obj": rr.MeshFormat.OBJ, + ".obj": viewer.MeshFormat.OBJ, }[ext] except Exception: raise ValueError(f"unknown file extension: {ext}") @@ -80,21 +80,21 @@ def read_mesh(path: Path) -> Trimesh: return cast(Trimesh, mesh) -@log_timing_decorator("global/voxel_sdf", rr.LogLevel.DEBUG) # type: ignore[misc] +@log_timing_decorator("global/voxel_sdf", viewer.LogLevel.DEBUG) # type: ignore[misc] def compute_voxel_sdf(mesh: Trimesh, resolution: int) -> npt.NDArray[np.float32]: print("computing voxel-based SDF") voxvol = np.array(mesh_to_sdf.mesh_to_voxels(mesh, voxel_resolution=resolution), dtype=np.float32) return voxvol -@log_timing_decorator("global/sample_sdf", rr.LogLevel.DEBUG) # type: ignore[misc] +@log_timing_decorator("global/sample_sdf", viewer.LogLevel.DEBUG) # type: ignore[misc] def compute_sample_sdf(mesh: Trimesh, num_points: int) -> Tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]: print("computing sample-based SDF") points, sdf, _ = mesh_to_sdf.sample_sdf_near_surface(mesh, number_of_points=num_points, return_gradients=True) return (points, sdf) -@log_timing_decorator("global/log_mesh", rr.LogLevel.DEBUG) # type: ignore[misc] +@log_timing_decorator("global/log_mesh", viewer.LogLevel.DEBUG) # type: ignore[misc] def log_mesh(path: Path, mesh: Trimesh) -> None: # Internally, `mesh_to_sdf` will normalize everything to a unit sphere centered around the # center of mass. @@ -107,7 +107,7 @@ def log_mesh(path: Path, mesh: Trimesh) -> None: with open(path, mode="rb") as file: scale = bs2.scale / bs1.scale center = bs2.center - bs1.center * scale - rr.log_mesh_file( + viewer.log_mesh_file( "world/mesh", mesh_format, file.read(), @@ -116,27 +116,29 @@ def log_mesh(path: Path, mesh: Trimesh) -> None: def log_sampled_sdf(points: npt.NDArray[np.float32], sdf: npt.NDArray[np.float32]) -> None: - # rr.log_view_coordinates("world", up="+Y", timeless=True # TODO(cmc): depends on the mesh really - rr.log_annotation_context("world/sdf", [(0, "inside", (255, 0, 0)), (1, "outside", (0, 255, 0))], timeless=False) - rr.log_points("world/sdf/points", points, class_ids=np.array(sdf > 0, dtype=np.uint8)) + # viewer.log_view_coordinates("world", up="+Y", timeless=True # TODO(cmc): depends on the mesh really + viewer.log_annotation_context( + "world/sdf", [(0, "inside", (255, 0, 0)), (1, "outside", (0, 255, 0))], timeless=False + ) + viewer.log_points("world/sdf/points", points, class_ids=np.array(sdf > 0, dtype=np.uint8)) outside = points[sdf > 0] - rr.log_text_entry( + viewer.log_text_entry( "world/sdf/inside/logs", f"{len(points) - len(outside)} points inside ({len(points)} total)", - level=rr.LogLevel.TRACE, + level=viewer.LogLevel.TRACE, ) - rr.log_text_entry( - "world/sdf/outside/logs", f"{len(outside)} points outside ({len(points)} total)", level=rr.LogLevel.TRACE + viewer.log_text_entry( + "world/sdf/outside/logs", f"{len(outside)} points outside ({len(points)} total)", level=viewer.LogLevel.TRACE ) def log_volumetric_sdf(voxvol: npt.NDArray[np.float32]) -> None: names = ["width", "height", "depth"] - rr.log_tensor("tensor", voxvol, names=names) + viewer.log_tensor("tensor", voxvol, names=names) -@log_timing_decorator("global/log_mesh", rr.LogLevel.DEBUG) # type: ignore[misc] +@log_timing_decorator("global/log_mesh", viewer.LogLevel.DEBUG) # type: ignore[misc] def compute_and_log_volumetric_sdf(mesh_path: Path, mesh: Trimesh, resolution: int) -> None: os.makedirs(CACHE_DIR, exist_ok=True) basename = os.path.basename(mesh_path) @@ -144,7 +146,7 @@ def compute_and_log_volumetric_sdf(mesh_path: Path, mesh: Trimesh, resolution: i try: with open(voxvol_path, "rb") as f: voxvol = np.load(voxvol_path) - rr.log_text_entry("global", "loading volumetric SDF from cache") + viewer.log_text_entry("global", "loading volumetric SDF from cache") except Exception: voxvol = compute_voxel_sdf(mesh, resolution) @@ -152,10 +154,10 @@ def compute_and_log_volumetric_sdf(mesh_path: Path, mesh: Trimesh, resolution: i with open(voxvol_path, "wb+") as f: np.save(f, voxvol) - rr.log_text_entry("global", "writing volumetric SDF to cache", level=rr.LogLevel.DEBUG) + viewer.log_text_entry("global", "writing volumetric SDF to cache", level=viewer.LogLevel.DEBUG) -@log_timing_decorator("global/log_mesh", rr.LogLevel.DEBUG) # type: ignore[misc] +@log_timing_decorator("global/log_mesh", viewer.LogLevel.DEBUG) # type: ignore[misc] def compute_and_log_sample_sdf(mesh_path: Path, mesh: Trimesh, num_points: int) -> None: basename = os.path.basename(mesh_path) points_path = f"{CACHE_DIR}/{basename}.points.{num_points}.npy" @@ -165,10 +167,10 @@ def compute_and_log_sample_sdf(mesh_path: Path, mesh: Trimesh, num_points: int) try: with open(sdf_path, "rb") as f: sdf = np.load(sdf_path) - rr.log_text_entry("global", "loading sampled SDF from cache") + viewer.log_text_entry("global", "loading sampled SDF from cache") with open(points_path, "rb") as f: points = np.load(points_path) - rr.log_text_entry("global", "loading point cloud from cache") + viewer.log_text_entry("global", "loading point cloud from cache") except Exception: (points, sdf) = compute_sample_sdf(mesh, num_points) @@ -177,10 +179,10 @@ def compute_and_log_sample_sdf(mesh_path: Path, mesh: Trimesh, num_points: int) with open(points_path, "wb+") as f: np.save(f, points) - rr.log_text_entry("global", "writing sampled SDF to cache", level=rr.LogLevel.DEBUG) + viewer.log_text_entry("global", "writing sampled SDF to cache", level=viewer.LogLevel.DEBUG) with open(sdf_path, "wb+") as f: np.save(f, sdf) - rr.log_text_entry("global", "writing point cloud to cache", level=rr.LogLevel.DEBUG) + viewer.log_text_entry("global", "writing point cloud to cache", level=viewer.LogLevel.DEBUG) def main() -> None: @@ -203,10 +205,11 @@ def main() -> None: type=Path, help="Path to a mesh to analyze. If set, overrides the `--mesh` argument.", ) - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "deep_sdf") + viewer.script_setup(args, "deep_sdf") mesh_path = args.mesh_path if mesh_path is None: @@ -217,7 +220,7 @@ def main() -> None: compute_and_log_volumetric_sdf(mesh_path, mesh, args.resolution) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/deep_sdf/requirements.txt b/examples/python/deep_sdf/requirements.txt index bc5299798aa4..c13df7f10912 100644 --- a/examples/python/deep_sdf/requirements.txt +++ b/examples/python/deep_sdf/requirements.txt @@ -1,6 +1,6 @@ mesh_to_sdf===0.0.14 numpy requests==2.28.1 -rerun-sdk +depthai-viewer scikit-learn==1.1.3 trimesh==3.15.2 diff --git a/examples/python/dicom/main.py b/examples/python/dicom/main.py index 7b35e94baf85..8cc05edfd19a 100755 --- a/examples/python/dicom/main.py +++ b/examples/python/dicom/main.py @@ -15,12 +15,12 @@ from pathlib import Path from typing import Final, Iterable, Tuple +import depthai_viewer as viewer import dicom_numpy import numpy as np import numpy.typing as npt import pydicom as dicom import requests -import rerun as rr DATASET_DIR: Final = Path(os.path.dirname(__file__)) / "dataset" DATASET_URL: Final = "https://storage.googleapis.com/rerun-example-datasets/dicom.zip" @@ -51,7 +51,7 @@ def read_and_log_dicom_dataset(dicom_files: Iterable[Path]) -> None: # the data is i16, but in range [0, 536]. voxels_volume_u16: npt.NDArray[np.uint16] = np.require(voxels_volume, np.uint16) - rr.log_tensor( + viewer.log_tensor( "tensor", voxels_volume_u16, names=["right", "back", "up"], @@ -73,9 +73,10 @@ def ensure_dataset_downloaded() -> Iterable[Path]: if __name__ == "__main__": parser = argparse.ArgumentParser(description="Logs rich data using the Rerun SDK.") - rr.script_add_args(parser) - args = parser.parse_args() - rr.script_setup(args, "dicom") + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] + viewer.script_setup(args, "dicom") dicom_files = ensure_dataset_downloaded() read_and_log_dicom_dataset(dicom_files) - rr.script_teardown(args) + viewer.script_teardown(args) diff --git a/examples/python/dicom/requirements.txt b/examples/python/dicom/requirements.txt index e6a8f0befd54..84e50f295110 100644 --- a/examples/python/dicom/requirements.txt +++ b/examples/python/dicom/requirements.txt @@ -2,5 +2,5 @@ dicom_numpy==0.6.2 numpy pydicom==2.3.0 requests==2.28.1 -rerun-sdk +depthai-viewer types-requests==2.28.11 diff --git a/examples/python/dna/main.py b/examples/python/dna/main.py index 3294ad7980b9..3e87b59fafd0 100755 --- a/examples/python/dna/main.py +++ b/examples/python/dna/main.py @@ -6,45 +6,44 @@ `examples/python/dna/main.py` """ -import sys from math import tau +import depthai_viewer as viewer import numpy as np -import rerun as rr from rerun_demo.data import build_color_spiral from rerun_demo.util import bounce_lerp, interleave from scipy.spatial.transform import Rotation -# sanity-check since all other example scripts take arguments: -assert len(sys.argv) == 1, f"{sys.argv[0]} does not take any arguments" +_, unknown = __import__("argparse").ArgumentParser().parse_known_args() +[__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] -rr.init("DNA Abacus") +viewer.init("DNA Abacus") -rr.spawn() -rr.set_time_seconds("stable_time", 0) +viewer.spawn() +viewer.set_time_seconds("stable_time", 0) NUM_POINTS = 100 # points and colors are both np.array((NUM_POINTS, 3)) points1, colors1 = build_color_spiral(NUM_POINTS) points2, colors2 = build_color_spiral(NUM_POINTS, angular_offset=tau * 0.5) -rr.log_points("dna/structure/left", points1, colors=colors1, radii=0.08) -rr.log_points("dna/structure/right", points2, colors=colors2, radii=0.08) +viewer.log_points("dna/structure/left", points1, colors=colors1, radii=0.08) +viewer.log_points("dna/structure/right", points2, colors=colors2, radii=0.08) points = interleave(points1, points2) -rr.log_line_segments("dna/structure/scaffolding", points, color=[128, 128, 128]) +viewer.log_line_segments("dna/structure/scaffolding", points, color=[128, 128, 128]) time_offsets = np.random.rand(NUM_POINTS) for i in range(400): time = i * 0.01 - rr.set_time_seconds("stable_time", time) + viewer.set_time_seconds("stable_time", time) times = np.repeat(time, NUM_POINTS) + time_offsets beads = [bounce_lerp(points1[n], points2[n], times[n]) for n in range(NUM_POINTS)] colors = [[int(bounce_lerp(80, 230, times[n] * 2))] for n in range(NUM_POINTS)] - rr.log_points("dna/structure/scaffolding/beads", beads, radii=0.06, colors=np.repeat(colors, 3, axis=-1)) + viewer.log_points("dna/structure/scaffolding/beads", beads, radii=0.06, colors=np.repeat(colors, 3, axis=-1)) - rr.log_rigid3( + viewer.log_rigid3( "dna/structure", parent_from_child=( [0, 0, 0], diff --git a/examples/python/dna/requirements.txt b/examples/python/dna/requirements.txt index 8349c316dcbb..02605412d748 100644 --- a/examples/python/dna/requirements.txt +++ b/examples/python/dna/requirements.txt @@ -1,3 +1,3 @@ numpy -rerun-sdk +depthai-viewer scipy diff --git a/examples/python/minimal/main.py b/examples/python/minimal/main.py index 2beff431ed9f..a14d9200580f 100755 --- a/examples/python/minimal/main.py +++ b/examples/python/minimal/main.py @@ -2,18 +2,16 @@ """Demonstrates the most barebone usage of the Rerun SDK.""" -import sys +import depthai_viewer as viewer import numpy as np -import rerun as rr -rr.spawn() +_, unknown = __import__("argparse").ArgumentParser().parse_known_args() +[__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] + +viewer.spawn() positions = np.vstack([xyz.ravel() for xyz in np.mgrid[3 * [slice(-5, 5, 10j)]]]).T colors = np.vstack([rgb.ravel() for rgb in np.mgrid[3 * [slice(0, 255, 10j)]]]).astype(np.uint8).T -rr.log_points("my_points", positions=positions, colors=colors) - - -# sanity-check since all other example scripts take arguments: -assert len(sys.argv) == 1, f"{sys.argv[0]} does not take any arguments" +viewer.log_points("my_points", positions=positions, colors=colors) diff --git a/examples/python/minimal/requirements.txt b/examples/python/minimal/requirements.txt index fa4ff5da669a..24975fd48cc3 100644 --- a/examples/python/minimal/requirements.txt +++ b/examples/python/minimal/requirements.txt @@ -1,2 +1,2 @@ numpy -rerun-sdk +depthai-viewer diff --git a/examples/python/mp_pose/main.py b/examples/python/mp_pose/main.py index c2d91117c631..7076bb655c8b 100755 --- a/examples/python/mp_pose/main.py +++ b/examples/python/mp_pose/main.py @@ -9,11 +9,11 @@ from typing import Any, Final, Iterator, Optional import cv2 as cv +import depthai_viewer as viewer import mediapipe as mp import numpy as np import numpy.typing as npt import requests -import rerun as rr EXAMPLE_DIR: Final = Path(os.path.dirname(__file__)) DATASET_DIR: Final = EXAMPLE_DIR / "dataset" / "pose_movement" @@ -23,39 +23,39 @@ def track_pose(video_path: str, segment: bool) -> None: mp_pose = mp.solutions.pose - rr.log_annotation_context( + viewer.log_annotation_context( "/", - rr.ClassDescription( - info=rr.AnnotationInfo(label="Person"), - keypoint_annotations=[rr.AnnotationInfo(id=lm.value, label=lm.name) for lm in mp_pose.PoseLandmark], + viewer.ClassDescription( + info=viewer.AnnotationInfo(label="Person"), + keypoint_annotations=[viewer.AnnotationInfo(id=lm.value, label=lm.name) for lm in mp_pose.PoseLandmark], keypoint_connections=mp_pose.POSE_CONNECTIONS, ), ) # Use a separate annotation context for the segmentation mask. - rr.log_annotation_context( + viewer.log_annotation_context( "video/mask", - [rr.AnnotationInfo(id=0, label="Background"), rr.AnnotationInfo(id=1, label="Person", color=(0, 0, 0))], + [viewer.AnnotationInfo(id=0, label="Background"), viewer.AnnotationInfo(id=1, label="Person", color=(0, 0, 0))], ) - rr.log_view_coordinates("person", up="-Y", timeless=True) + viewer.log_view_coordinates("person", up="-Y", timeless=True) with closing(VideoSource(video_path)) as video_source, mp_pose.Pose(enable_segmentation=segment) as pose: for bgr_frame in video_source.stream_bgr(): rgb = cv.cvtColor(bgr_frame.data, cv.COLOR_BGR2RGB) - rr.set_time_seconds("time", bgr_frame.time) - rr.set_time_sequence("frame_idx", bgr_frame.idx) - rr.log_image("video/rgb", rgb) + viewer.set_time_seconds("time", bgr_frame.time) + viewer.set_time_sequence("frame_idx", bgr_frame.idx) + viewer.log_image("video/rgb", rgb) results = pose.process(rgb) h, w, _ = rgb.shape landmark_positions_2d = read_landmark_positions_2d(results, w, h) - rr.log_points("video/pose/points", landmark_positions_2d, keypoint_ids=mp_pose.PoseLandmark) + viewer.log_points("video/pose/points", landmark_positions_2d, keypoint_ids=mp_pose.PoseLandmark) landmark_positions_3d = read_landmark_positions_3d(results) - rr.log_points("person/pose/points", landmark_positions_3d, keypoint_ids=mp_pose.PoseLandmark) + viewer.log_points("person/pose/points", landmark_positions_3d, keypoint_ids=mp_pose.PoseLandmark) segmentation_mask = results.segmentation_mask if segmentation_mask is not None: - rr.log_segmentation_image("video/mask", segmentation_mask) + viewer.log_segmentation_image("video/mask", segmentation_mask) def read_landmark_positions_2d( @@ -148,10 +148,11 @@ def main() -> None: parser.add_argument("--dataset_dir", type=Path, default=DATASET_DIR, help="Directory to save example videos to.") parser.add_argument("--video_path", type=str, default="", help="Full path to video to run on. Overrides `--video`.") parser.add_argument("--no-segment", action="store_true", help="Don't run person segmentation.") - rr.script_add_args(parser) + viewer.script_add_args(parser) - args = parser.parse_args() - rr.script_setup(args, "mp_pose") + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] + viewer.script_setup(args, "mp_pose") video_path = args.video_path # type: str if not video_path: @@ -159,7 +160,7 @@ def main() -> None: track_pose(video_path, segment=not args.no_segment) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/mp_pose/requirements.txt b/examples/python/mp_pose/requirements.txt index f13236af560e..26207c819626 100644 --- a/examples/python/mp_pose/requirements.txt +++ b/examples/python/mp_pose/requirements.txt @@ -3,4 +3,4 @@ mediapipe>=0.8.11; platform_system != "Darwin" and platform.machine != 'arm64' numpy opencv-python<4.6 # Avoid opencv-4.6 since it rotates images incorrectly (https://github.com/opencv/opencv/issues/22088) requests -rerun-sdk +depthai-viewer diff --git a/examples/python/multiprocessing/main.py b/examples/python/multiprocessing/main.py index 7261981cbd25..f007627e0baf 100755 --- a/examples/python/multiprocessing/main.py +++ b/examples/python/multiprocessing/main.py @@ -7,31 +7,37 @@ import os import threading -import rerun as rr +import depthai_viewer as viewer def task(title: str) -> None: # All processes spawned with `multiprocessing` will automatically # be assigned the same default recording_id. # We just need to connect each process to the the rerun viewer: - rr.connect() + viewer.connect() - rr.log_text_entry( + viewer.log_text_entry( "log", - text=f"Logging from pid={os.getpid()}, thread={threading.get_ident()} using the rerun recording id {rr.get_recording_id()}", # noqa: E501 line too long + text=f"Logging from pid={os.getpid()}, thread={threading.get_ident()} using the rerun recording id {viewer.get_recording_id()}", # noqa: E501 line too long ) - rr.log_rect(title, [10, 20, 30, 40], label=title) + viewer.log_rect(title, [10, 20, 30, 40], label=title) def main() -> None: parser = argparse.ArgumentParser(description="Test multi-process logging to the same Rerun server") - parser.parse_args() + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.init("multiprocessing") - rr.spawn(connect=False) # this is the viewer that each process will connect to + viewer.init("multiprocessing") + viewer.spawn(connect=False) # this is the viewer that each process will connect to task("main_task") + # Using multiprocessing with "fork" results in a hang on shutdown so + # always use "spawn" + # TODO(https://github.com/rerun-io/rerun/issues/1921) + multiprocessing.set_start_method("spawn") + p = multiprocessing.Process(target=task, args=("child_task",)) p.start() p.join() diff --git a/examples/python/multiprocessing/requirements.txt b/examples/python/multiprocessing/requirements.txt index ebb847ff0d2d..a6bfff6cfe23 100644 --- a/examples/python/multiprocessing/requirements.txt +++ b/examples/python/multiprocessing/requirements.txt @@ -1 +1 @@ -rerun-sdk +depthai-viewer diff --git a/examples/python/multithreading/main.py b/examples/python/multithreading/main.py index eeb6f2ed9f49..703afa7b8733 100755 --- a/examples/python/multithreading/main.py +++ b/examples/python/multithreading/main.py @@ -6,9 +6,9 @@ import random import threading +import depthai_viewer as viewer import numpy as np import numpy.typing as npt -import rerun as rr def rect_logger(path: str, color: npt.NDArray[np.float32]) -> None: @@ -16,23 +16,29 @@ def rect_logger(path: str, color: npt.NDArray[np.float32]) -> None: rects_xy = np.random.rand(5, 2) * 1024 rects_wh = np.random.rand(5, 2) * (1024 - rects_xy + 1) rects = np.hstack((rects_xy, rects_wh)) - rr.log_rects(path, rects, colors=color, rect_format=rr.RectFormat.XYWH) + viewer.log_rects(path, rects, colors=color, rect_format=viewer.RectFormat.XYWH) def main() -> None: parser = argparse.ArgumentParser(description="Logs rich data using the Rerun SDK.") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "multithreading") + viewer.script_setup(args, "multithreading") + threads = [] for i in range(10): t = threading.Thread( target=rect_logger, args=("thread/{}".format(i), [random.randrange(255) for _ in range(3)]) ) t.start() + threads.append(t) - rr.script_teardown(args) + for t in threads: + t.join() + + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/multithreading/requirements.txt b/examples/python/multithreading/requirements.txt index fa4ff5da669a..24975fd48cc3 100644 --- a/examples/python/multithreading/requirements.txt +++ b/examples/python/multithreading/requirements.txt @@ -1,2 +1,2 @@ numpy -rerun-sdk +depthai-viewer diff --git a/examples/python/notebook/README.md b/examples/python/notebook/README.md index c06eb6293921..a7b7ac56e514 100644 --- a/examples/python/notebook/README.md +++ b/examples/python/notebook/README.md @@ -7,7 +7,7 @@ or [VSCode](https://code.visualstudio.com/blogs/2021/08/05/notebooks). In order to show a rerun viewer inline within the notebook you need to use a special in-memory recording: ``` -rec = rr.memory_recording() +rec = viewer.memory_recording() ``` After creating this recording all the normal rerun commands will work as expected and log diff --git a/examples/python/notebook/cube.ipynb b/examples/python/notebook/cube.ipynb index d04f1be19908..b9d47a7ddf57 100644 --- a/examples/python/notebook/cube.ipynb +++ b/examples/python/notebook/cube.ipynb @@ -1,273 +1,272 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "b31c0a84", - "metadata": {}, - "source": [ - "## Rerun imports and initialization" - ] + "cells": [ + { + "cell_type": "markdown", + "id": "b31c0a84", + "metadata": {}, + "source": [ + "## Rerun imports and initialization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1076c3a0", + "metadata": {}, + "outputs": [], + "source": [ + "from collections import namedtuple\n", + "from math import cos, sin, tau\n", + "import math\n", + "\n", + "import numpy as np\n", + "\n", + "import rerun as rr\n", + "\n", + "viewer.init(\"cube\")" + ] + }, + { + "cell_type": "markdown", + "id": "f3c194db", + "metadata": {}, + "source": [ + "## Optional: start a local web-viewer server\n", + "\n", + "By default, Rerun will use a copy of the viewer hosted at [https://app.rerun.io](https://app.rerun.io).\n", + "This is generally preferable as it will work more seamlessly even if you\n", + "are connected to a notebook instance on a remote machine. However there\n", + "are some cases where this won't work such as running from source, or\n", + "using your notebook in an offline environment.\n", + "\n", + "In these cases you can start a local viewer server by uncommenting the following\n", + "line:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63f80605", + "metadata": {}, + "outputs": [], + "source": [ + "# viewer.start_web_viewer_server()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "bf894a1f", + "metadata": {}, + "source": [ + "## Helper to create the colored cube\n", + "\n", + "This is the same as the color cube demo from `rerun -m rerun_demo`, but the code\n", + "is repeated here for context." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f709925e", + "metadata": {}, + "outputs": [], + "source": [ + "ColorGrid = namedtuple(\"ColorGrid\", [\"positions\", \"colors\"])\n", + "\n", + "\n", + "def build_color_grid(x_count=10, y_count=10, z_count=10, twist=0):\n", + " \"\"\"\n", + " Create a cube of points with colors.\n", + "\n", + " The total point cloud will have x_count * y_count * z_count points.\n", + "\n", + " Parameters\n", + " ----------\n", + " x_count, y_count, z_count:\n", + " Number of points in each dimension.\n", + " twist:\n", + " Angle to twist from bottom to top of the cube\n", + "\n", + " \"\"\"\n", + "\n", + " grid = np.mgrid[\n", + " slice(-10, 10, x_count * 1j),\n", + " slice(-10, 10, y_count * 1j),\n", + " slice(-10, 10, z_count * 1j),\n", + " ]\n", + "\n", + " angle = np.linspace(-float(twist) / 2, float(twist) / 2, z_count)\n", + " for z in range(z_count):\n", + " xv, yv, zv = grid[:, :, :, z]\n", + " rot_xv = xv * cos(angle[z]) - yv * sin(angle[z])\n", + " rot_yv = xv * sin(angle[z]) + yv * cos(angle[z])\n", + " grid[:, :, :, z] = [rot_xv, rot_yv, zv]\n", + "\n", + " positions = np.vstack([xyz.ravel() for xyz in grid])\n", + "\n", + " colors = np.vstack(\n", + " [\n", + " xyz.ravel()\n", + " for xyz in np.mgrid[\n", + " slice(0, 255, x_count * 1j),\n", + " slice(0, 255, y_count * 1j),\n", + " slice(0, 255, z_count * 1j),\n", + " ]\n", + " ]\n", + " )\n", + "\n", + " return ColorGrid(positions.T, colors.T.astype(np.uint8))" + ] + }, + { + "cell_type": "markdown", + "id": "b9a75269", + "metadata": {}, + "source": [ + "## Start a new recording\n", + "\n", + "To start a new recording all you need to do is call viewer.memory_recording()." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4e1caf9", + "metadata": {}, + "outputs": [], + "source": [ + "rec = viewer.memory_recording()" + ] + }, + { + "cell_type": "markdown", + "id": "6e4f945b", + "metadata": {}, + "source": [ + "## Showing the recording\n", + "\n", + "At any point you can show this recording by returning it as the last item in the cell.\n", + "In this case the recording simply does not have any data in it yet." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d586d222", + "metadata": {}, + "outputs": [], + "source": [ + "rec" + ] + }, + { + "cell_type": "markdown", + "id": "04c095ef", + "metadata": {}, + "source": [ + "## Logging some data\n", + "\n", + "Now we can create some data and add it to the recording before we show it again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92871ea1", + "metadata": {}, + "outputs": [], + "source": [ + "STEPS = 100\n", + "twists = math.pi * np.sin(np.linspace(0, math.tau, STEPS)) / 4\n", + "for t in range(STEPS):\n", + " viewer.set_time_sequence(\"step\", t)\n", + " cube = build_color_grid(10, 10, 10, twist=twists[t])\n", + " viewer.log_points(\"cube\", positions=cube.positions, colors=cube.colors, radii=0.5)\n", + "\n", + "rec" + ] + }, + { + "cell_type": "markdown", + "id": "31d392a8", + "metadata": {}, + "source": [ + "## Adjusting the view\n", + "\n", + "The recording also as a `show` method that lets you adjust properties such as width and height.\n", + "In the future this will support additional blueprint and layout options." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1a1b0f66-4287-4705-8be5-ae837ffe3f90", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "rec.show(width=400, height=400)" + ] + }, + { + "cell_type": "markdown", + "id": "36f9f61b", + "metadata": {}, + "source": [ + "## Stating a new recording\n", + "\n", + "You can always start another recording by calling `viewer.memory_recording()` again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4cc33fd", + "metadata": {}, + "outputs": [], + "source": [ + "rec2 = viewer.memory_recording()\n", + "\n", + "STEPS = 1\n", + "twists = math.pi * np.sin(np.linspace(0, math.tau, STEPS)) / 4\n", + "for t in range(STEPS):\n", + " viewer.set_time_sequence(\"step\", t)\n", + " cube = build_color_grid(50, 50, 50, twist=twists[t])\n", + " viewer.log_points(\"cube\", positions=cube.positions, colors=cube.colors, radii=0.5)\n", + "\n", + "rec2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb8f7701", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.7" + } }, - { - "cell_type": "code", - "execution_count": null, - "id": "1076c3a0", - "metadata": {}, - "outputs": [], - "source": [ - "from collections import namedtuple\n", - "from math import cos, sin, tau\n", - "import math\n", - "\n", - "import numpy as np\n", - "\n", - "import rerun as rr\n", - "\n", - "rr.init(\"cube\")" - ] - }, - { - "cell_type": "markdown", - "id": "f3c194db", - "metadata": {}, - "source": [ - "## Optional: start a local web-viewer server\n", - "\n", - "By default, Rerun will use a copy of the viewer hosted at [https://app.rerun.io](https://app.rerun.io).\n", - "This is generally preferable as it will work more seamlessly even if you\n", - "are connected to a notebook instance on a remote machine. However there\n", - "are some cases where this won't work such as running from source, or\n", - "using your notebook in an offline environment.\n", - "\n", - "In these cases you can start a local viewer server by uncommenting the following\n", - "line:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "63f80605", - "metadata": {}, - "outputs": [], - "source": [ - "# rr.start_web_viewer_server()" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "bf894a1f", - "metadata": {}, - "source": [ - "## Helper to create the colored cube\n", - "\n", - "This is the same as the color cube demo from `rerun -m rerun_demo`, but the code\n", - "is repeated here for context." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f709925e", - "metadata": {}, - "outputs": [], - "source": [ - "ColorGrid = namedtuple(\"ColorGrid\", [\"positions\", \"colors\"])\n", - "\n", - "\n", - "def build_color_grid(x_count=10, y_count=10, z_count=10, twist=0):\n", - " \"\"\"\n", - " Create a cube of points with colors.\n", - "\n", - " The total point cloud will have x_count * y_count * z_count points.\n", - "\n", - " Parameters\n", - " ----------\n", - " x_count, y_count, z_count:\n", - " Number of points in each dimension.\n", - " twist:\n", - " Angle to twist from bottom to top of the cube\n", - "\n", - " \"\"\"\n", - "\n", - " grid = np.mgrid[\n", - " slice(-10, 10, x_count * 1j),\n", - " slice(-10, 10, y_count * 1j),\n", - " slice(-10, 10, z_count * 1j),\n", - " ]\n", - "\n", - " angle = np.linspace(-float(twist) / 2, float(twist) / 2, z_count)\n", - " for z in range(z_count):\n", - " xv, yv, zv = grid[:, :, :, z]\n", - " rot_xv = xv * cos(angle[z]) - yv * sin(angle[z])\n", - " rot_yv = xv * sin(angle[z]) + yv * cos(angle[z])\n", - " grid[:, :, :, z] = [rot_xv, rot_yv, zv]\n", - "\n", - " positions = np.vstack([xyz.ravel() for xyz in grid])\n", - "\n", - " colors = np.vstack(\n", - " [\n", - " xyz.ravel()\n", - " for xyz in np.mgrid[\n", - " slice(0, 255, x_count * 1j),\n", - " slice(0, 255, y_count * 1j),\n", - " slice(0, 255, z_count * 1j),\n", - " ]\n", - " ]\n", - " )\n", - "\n", - " return ColorGrid(positions.T, colors.T.astype(np.uint8))" - ] - }, - { - "cell_type": "markdown", - "id": "b9a75269", - "metadata": {}, - "source": [ - "## Start a new recording\n", - "\n", - "To start a new recording all you need to do is call rr.memory_recording()." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f4e1caf9", - "metadata": {}, - "outputs": [], - "source": [ - "rec = rr.memory_recording()" - ] - }, - { - "cell_type": "markdown", - "id": "6e4f945b", - "metadata": {}, - "source": [ - "## Showing the recording\n", - "\n", - "At any point you can show this recording by returning it as the last item in the cell.\n", - "In this case the recording simply does not have any data in it yet." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d586d222", - "metadata": {}, - "outputs": [], - "source": [ - "rec" - ] - }, - { - "cell_type": "markdown", - "id": "04c095ef", - "metadata": {}, - "source": [ - "## Logging some data\n", - "\n", - "Now we can create some data and add it to the recording before we show it again." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "92871ea1", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "STEPS = 100\n", - "twists = math.pi * np.sin(np.linspace(0, math.tau, STEPS)) / 4\n", - "for t in range(STEPS):\n", - " rr.set_time_sequence(\"step\", t)\n", - " cube = build_color_grid(10, 10, 10, twist=twists[t])\n", - " rr.log_points(\"cube\", positions=cube.positions, colors=cube.colors, radii=0.5)\n", - "\n", - "rec" - ] - }, - { - "cell_type": "markdown", - "id": "31d392a8", - "metadata": {}, - "source": [ - "## Adjusting the view\n", - "\n", - "The recording also as a `show` method that lets you adjust properties such as width and height.\n", - "In the future this will support additional blueprint and layout options." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1a1b0f66-4287-4705-8be5-ae837ffe3f90", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "rec.show(width=400, height=400)" - ] - }, - { - "cell_type": "markdown", - "id": "36f9f61b", - "metadata": {}, - "source": [ - "## Stating a new recording\n", - "\n", - "You can always start another recording by calling `rr.memory_recording()` again." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c4cc33fd", - "metadata": {}, - "outputs": [], - "source": [ - "rec2 = rr.memory_recording()\n", - "\n", - "STEPS = 1\n", - "twists = math.pi * np.sin(np.linspace(0, math.tau, STEPS)) / 4\n", - "for t in range(STEPS):\n", - " rr.set_time_sequence(\"step\", t)\n", - " cube = build_color_grid(50, 50, 50, twist=twists[t])\n", - " rr.log_points(\"cube\", positions=cube.positions, colors=cube.colors, radii=0.5)\n", - "\n", - "rec2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eb8f7701", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/examples/python/notebook/requirements.txt b/examples/python/notebook/requirements.txt index 1e5f56badec1..c6823954fce9 100644 --- a/examples/python/notebook/requirements.txt +++ b/examples/python/notebook/requirements.txt @@ -1,2 +1,2 @@ jupyter -rerun-sdk +depthai-viewer diff --git a/examples/python/nyud/main.py b/examples/python/nyud/main.py index da577c687d07..051ccf670b9e 100755 --- a/examples/python/nyud/main.py +++ b/examples/python/nyud/main.py @@ -13,10 +13,10 @@ from typing import Final, Tuple import cv2 +import depthai_viewer as viewer import numpy as np import numpy.typing as npt import requests -import rerun as rr from tqdm import tqdm DEPTH_IMAGE_SCALING: Final = 1e4 @@ -61,7 +61,7 @@ def read_image(buf: bytes) -> npt.NDArray[np.uint8]: def log_nyud_data(recording_path: Path, subset_idx: int = 0) -> None: - rr.log_view_coordinates("world", up="-Y", timeless=True) + viewer.log_view_coordinates("world", up="-Y", timeless=True) with zipfile.ZipFile(recording_path, "r") as archive: archive_dirs = [f.filename for f in archive.filelist if f.is_dir()] @@ -78,12 +78,12 @@ def log_nyud_data(recording_path: Path, subset_idx: int = 0) -> None: files_with_timestamps.sort(key=lambda t: t[0]) for time, f in files_with_timestamps: - rr.set_time_seconds("time", time.timestamp()) + viewer.set_time_seconds("time", time.timestamp()) if f.filename.endswith(".ppm"): buf = archive.read(f) img_rgb = read_image_rgb(buf) - rr.log_image("world/camera/image/rgb", img_rgb) + viewer.log_image("world/camera/image/rgb", img_rgb) elif f.filename.endswith(".pgm"): buf = archive.read(f) @@ -92,12 +92,12 @@ def log_nyud_data(recording_path: Path, subset_idx: int = 0) -> None: # Log the camera transforms: translation = [0, 0, 0] rotation_q = [0, 0, 0, 1] - rr.log_rigid3( + viewer.log_rigid3( "world/camera", parent_from_child=(translation, rotation_q), xyz="RDF", # X=Right, Y=Down, Z=Forward ) - rr.log_pinhole( + viewer.log_pinhole( "world/camera/image", child_from_parent=camera_intrinsics(img_depth), width=img_depth.shape[1], @@ -105,7 +105,7 @@ def log_nyud_data(recording_path: Path, subset_idx: int = 0) -> None: ) # Log the depth image to the cameras image-space: - rr.log_depth_image("world/camera/image/depth", img_depth, meter=DEPTH_IMAGE_SCALING) + viewer.log_depth_image("world/camera/image/depth", img_depth, meter=DEPTH_IMAGE_SCALING) def ensure_recording_downloaded(name: str) -> Path: @@ -159,10 +159,11 @@ def download_progress(url: str, dst: Path) -> None: help="Name of the NYU Depth Dataset V2 recording", ) parser.add_argument("--subset-idx", type=int, default=0, help="The index of the subset of the recording to use.") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "nyud") + viewer.script_setup(args, "nyud") recording_path = ensure_recording_downloaded(args.recording) log_nyud_data( @@ -170,4 +171,4 @@ def download_progress(url: str, dst: Path) -> None: subset_idx=args.subset_idx, ) - rr.script_teardown(args) + viewer.script_teardown(args) diff --git a/examples/python/nyud/requirements.txt b/examples/python/nyud/requirements.txt index 1732988759cd..e9c0ebefe4c2 100644 --- a/examples/python/nyud/requirements.txt +++ b/examples/python/nyud/requirements.txt @@ -1,5 +1,5 @@ numpy opencv-python<4.6 # Avoid opencv-4.6 since it rotates images incorrectly (https://github.com/opencv/opencv/issues/22088) requests -rerun-sdk +depthai-viewer tqdm diff --git a/examples/python/objectron/main.py b/examples/python/objectron/main.py index f4a8efc60a45..8c0bc842f9f3 100755 --- a/examples/python/objectron/main.py +++ b/examples/python/objectron/main.py @@ -16,9 +16,9 @@ from pathlib import Path from typing import Iterable, Iterator, List +import depthai_viewer as viewer import numpy as np import numpy.typing as npt -import rerun as rr from download_dataset import ( ANNOTATIONS_FILENAME, AVAILABLE_RECORDINGS, @@ -114,17 +114,17 @@ def read_annotations(dirpath: Path) -> Sequence: def log_ar_frames(samples: Iterable[SampleARFrame], seq: Sequence) -> None: """Logs a stream of `ARFrame` samples and their annotations with the Rerun SDK.""" - rr.log_view_coordinates("world", up="+Y", timeless=True) + viewer.log_view_coordinates("world", up="+Y", timeless=True) log_annotated_bboxes(seq.objects) frame_times = [] for sample in samples: - rr.set_time_sequence("frame", sample.index) - rr.set_time_seconds("time", sample.timestamp) + viewer.set_time_sequence("frame", sample.index) + viewer.set_time_seconds("time", sample.timestamp) frame_times.append(sample.timestamp) - rr.log_image_file("world/camera/video", img_path=sample.image_path, img_format=rr.ImageFormat.JPEG) + viewer.log_image_file("world/camera/video", img_path=sample.image_path, img_format=viewer.ImageFormat.JPEG) log_camera(sample.frame.camera) log_point_cloud(sample.frame.raw_feature_points) @@ -151,10 +151,10 @@ def log_camera(cam: ARCamera) -> None: rot = rot * R.from_rotvec((math.tau / 2.0) * X) # TODO(emilk): figure out why this is needed - rr.log_rigid3( + viewer.log_rigid3( "world/camera", parent_from_child=(translation, rot.as_quat()), xyz="RDF" # X=Right, Y=Down, Z=Forward ) - rr.log_pinhole( + viewer.log_pinhole( "world/camera/video", child_from_parent=intrinsics, width=w, @@ -167,7 +167,7 @@ def log_point_cloud(point_cloud: ARPointCloud) -> None: positions = np.array([[p.x, p.y, p.z] for p in point_cloud.point]).astype(np.float32) identifiers = point_cloud.identifier - rr.log_points("world/points", positions=positions, identifiers=identifiers, colors=[255, 255, 255, 255]) + viewer.log_points("world/points", positions=positions, identifiers=identifiers, colors=[255, 255, 255, 255]) def log_annotated_bboxes(bboxes: Iterable[Object]) -> None: @@ -179,7 +179,7 @@ def log_annotated_bboxes(bboxes: Iterable[Object]) -> None: continue rot = R.from_matrix(np.asarray(bbox.rotation).reshape((3, 3))) - rr.log_obb( + viewer.log_obb( f"world/annotations/box-{bbox.id}", half_size=0.5 * np.array(bbox.scale), position=bbox.translation, @@ -199,8 +199,8 @@ def log_frame_annotations(frame_times: List[float], frame_annotations: List[Fram continue time = frame_times[frame_idx] - rr.set_time_sequence("frame", frame_idx) - rr.set_time_seconds("time", time) + viewer.set_time_sequence("frame", frame_idx) + viewer.set_time_seconds("time", time) for obj_ann in frame_ann.annotations: keypoint_ids = [kp.id for kp in obj_ann.keypoints] @@ -212,7 +212,7 @@ def log_frame_annotations(frame_times: List[float], frame_annotations: List[Fram log_projected_bbox(f"world/camera/video/estimates/box-{obj_ann.object_id}", keypoint_pos2s) else: for id, pos2 in zip(keypoint_ids, keypoint_pos2s): - rr.log_point( + viewer.log_point( f"world/camera/video/estimates/box-{obj_ann.object_id}/{id}", pos2, color=[130, 160, 250, 255], @@ -250,7 +250,7 @@ def log_projected_bbox(path: str, keypoints: npt.NDArray[np.float32]) -> None: keypoints[4], keypoints[8]], dtype=np.float32) # fmt: on - rr.log_line_segments(path, segments, color=[130, 160, 250, 255]) + viewer.log_line_segments(path, segments, color=[130, 160, 250, 255]) def main() -> None: @@ -282,10 +282,11 @@ def main() -> None: "--dataset_dir", type=Path, default=LOCAL_DATASET_DIR, help="Directory to save example videos to." ) - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "objectron") + viewer.script_setup(args, "objectron") dir = ensure_recording_available(args.recording, args.dataset_dir, args.force_reprocess_video) @@ -293,7 +294,7 @@ def main() -> None: seq = read_annotations(dir) log_ar_frames(samples, seq) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/objectron/requirements.txt b/examples/python/objectron/requirements.txt index a8d14bd1af00..154defde3963 100644 --- a/examples/python/objectron/requirements.txt +++ b/examples/python/objectron/requirements.txt @@ -2,5 +2,5 @@ betterproto[compiler] numpy opencv-python<4.6 # Avoid opencv-4.6 since it rotates images incorrectly (https://github.com/opencv/opencv/issues/22088) requests -rerun-sdk +depthai-viewer scipy diff --git a/examples/python/opencv_canny/main.py b/examples/python/opencv_canny/main.py index 6220d7ef1c9f..08fae001c65e 100755 --- a/examples/python/opencv_canny/main.py +++ b/examples/python/opencv_canny/main.py @@ -22,18 +22,22 @@ """ import argparse +from typing import Optional import cv2 -import rerun as rr +import depthai_viewer as viewer -def run_canny() -> None: +def run_canny(num_frames: Optional[int]) -> None: # Create a new video capture cap = cv2.VideoCapture(0) frame_nr = 0 while cap.isOpened(): + if num_frames and frame_nr >= num_frames: + break + # Read the frame ret, img = cap.read() if not ret: @@ -43,22 +47,22 @@ def run_canny() -> None: # Get the current frame time. On some platforms it always returns zero. frame_time_ms = cap.get(cv2.CAP_PROP_POS_MSEC) if frame_time_ms != 0: - rr.set_time_nanos("frame_time", int(frame_time_ms * 1_000_000)) + viewer.set_time_nanos("frame_time", int(frame_time_ms * 1_000_000)) - rr.set_time_sequence("frame_nr", frame_nr) + viewer.set_time_sequence("frame_nr", frame_nr) frame_nr += 1 # Log the original image rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - rr.log_image("image/rgb", rgb) + viewer.log_image("image/rgb", rgb) # Convert to grayscale gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - rr.log_image("image/gray", gray) + viewer.log_image("image/gray", gray) # Run the canny edge detector canny = cv2.Canny(gray, 50, 200) - rr.log_image("image/canny", canny) + viewer.log_image("image/canny", canny) def main() -> None: @@ -66,11 +70,13 @@ def main() -> None: parser.add_argument( "--device", type=int, default=0, help="Which camera device to use. (Passed to `cv2.VideoCapture()`)" ) + parser.add_argument("--num-frames", type=int, default=None, help="The number of frames to log") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "opencv_canny") + viewer.script_setup(args, "opencv_canny") print(args.connect) @@ -93,9 +99,9 @@ def main() -> None: """ ) - run_canny() + run_canny(args.num_frames) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/opencv_canny/requirements.txt b/examples/python/opencv_canny/requirements.txt index 51d3a8cf0abf..2cabd331384f 100644 --- a/examples/python/opencv_canny/requirements.txt +++ b/examples/python/opencv_canny/requirements.txt @@ -1,2 +1,2 @@ opencv-python -rerun-sdk +depthai-viewer diff --git a/examples/python/plots/main.py b/examples/python/plots/main.py index 5f4d327fd746..08bae38d77f4 100755 --- a/examples/python/plots/main.py +++ b/examples/python/plots/main.py @@ -14,8 +14,8 @@ import random from math import cos, sin, tau +import depthai_viewer as viewer import numpy as np -import rerun as rr def clamp(n, smallest, largest): # type: ignore[no-untyped-def] @@ -29,13 +29,13 @@ def log_bar_chart() -> None: variance = np.square(std) x = np.arange(-5, 5, 0.01) y = np.exp(-np.square(x - mean) / 2 * variance) / (np.sqrt(2 * np.pi * variance)) - rr.log_tensor("bar_chart", y) + viewer.log_tensor("bar_chart", y) def log_parabola() -> None: # Log a parabola as a time series for t in range(0, 1000, 10): - rr.set_time_sequence("frame_nr", t) + viewer.set_time_sequence("frame_nr", t) f_of_t = (t * 0.01 - 5) ** 3 + 1 radius = clamp(abs(f_of_t) * 0.1, 0.5, 10.0) # type: ignore[no-untyped-call] @@ -45,29 +45,29 @@ def log_parabola() -> None: elif f_of_t > 10.0: color = [0, 255, 0] - rr.log_scalar("curves/parabola", f_of_t, label="f(t) = (0.01t - 3)³ + 1", radius=radius, color=color) + viewer.log_scalar("curves/parabola", f_of_t, label="f(t) = (0.01t - 3)³ + 1", radius=radius, color=color) def log_trig() -> None: # Log a time series for t in range(0, int(tau * 2 * 100.0)): - rr.set_time_sequence("frame_nr", t) + viewer.set_time_sequence("frame_nr", t) sin_of_t = sin(float(t) / 100.0) - rr.log_scalar("trig/sin", sin_of_t, label="sin(0.01t)", color=[255, 0, 0]) + viewer.log_scalar("trig/sin", sin_of_t, label="sin(0.01t)", color=[255, 0, 0]) cos_of_t = cos(float(t) / 100.0) - rr.log_scalar("trig/cos", cos_of_t, label="cos(0.01t)", color=[0, 255, 0]) + viewer.log_scalar("trig/cos", cos_of_t, label="cos(0.01t)", color=[0, 255, 0]) def log_segmentation() -> None: # Log a time series for t in range(0, 1000, 2): - rr.set_time_sequence("frame_nr", t) + viewer.set_time_sequence("frame_nr", t) f_of_t = (2 * 0.01 * t) + 2 color = [255, 255, 0] - rr.log_scalar("segmentation/line", f_of_t, color=color, radius=3.0) + viewer.log_scalar("segmentation/line", f_of_t, color=color, radius=3.0) g_of_t = f_of_t + random.uniform(-5.0, 5.0) if g_of_t < f_of_t - 1.5: @@ -77,24 +77,25 @@ def log_segmentation() -> None: else: color = [255, 255, 255] radius = abs(g_of_t - f_of_t) - rr.log_scalar("segmentation/samples", g_of_t, color=color, scattered=True, radius=radius) + viewer.log_scalar("segmentation/samples", g_of_t, color=color, scattered=True, radius=radius) def main() -> None: parser = argparse.ArgumentParser( description="demonstrates how to integrate python's native `logging` with the Rerun SDK" ) - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "plot") + viewer.script_setup(args, "plot") log_parabola() log_trig() log_segmentation() log_bar_chart() - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/plots/requirements.txt b/examples/python/plots/requirements.txt index fa4ff5da669a..24975fd48cc3 100644 --- a/examples/python/plots/requirements.txt +++ b/examples/python/plots/requirements.txt @@ -1,2 +1,2 @@ numpy -rerun-sdk +depthai-viewer diff --git a/examples/python/raw_mesh/main.py b/examples/python/raw_mesh/main.py index 5978060e0902..14f63469141d 100755 --- a/examples/python/raw_mesh/main.py +++ b/examples/python/raw_mesh/main.py @@ -14,8 +14,8 @@ from pathlib import Path from typing import Optional, cast +import depthai_viewer as viewer import numpy as np -import rerun as rr import trimesh from download_dataset import AVAILABLE_MESHES, ensure_mesh_downloaded @@ -44,7 +44,7 @@ def log_scene(scene: trimesh.Scene, node: str, path: Optional[str] = None) -> No q = trimesh.transformations.quaternion_from_matrix(world_from_mesh) # `trimesh` stores quaternions in `wxyz` format, rerun needs `xyzw` q = np.array([q[1], q[2], q[3], q[0]]) - rr.log_rigid3(path, parent_from_child=(t, q)) + viewer.log_rigid3(path, parent_from_child=(t, q)) # Log this node's mesh, if it has one. mesh = cast(trimesh.Trimesh, scene.geometry.get(node_data[1])) @@ -58,7 +58,7 @@ def log_scene(scene: trimesh.Scene, node: str, path: Optional[str] = None) -> No albedo_factor = np.array(colors) / 255.0 except Exception: pass - rr.log_mesh( + viewer.log_mesh( path, mesh.vertices, indices=mesh.faces, normals=mesh.vertex_normals, albedo_factor=albedo_factor ) @@ -83,10 +83,11 @@ def main() -> None: type=Path, help="Path to a scene to analyze. If set, overrides the `--scene` argument.", ) - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "raw_mesh") + viewer.script_setup(args, "raw_mesh") scene_path = args.scene_path if scene_path is None: @@ -96,10 +97,10 @@ def main() -> None: root = next(iter(scene.graph.nodes)) # glTF always uses a right-handed coordinate system when +Y is up and meshes face +Z. - rr.log_view_coordinates(root, xyz="RUB", timeless=True) + viewer.log_view_coordinates(root, xyz="RUB", timeless=True) log_scene(scene, root) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/raw_mesh/requirements.txt b/examples/python/raw_mesh/requirements.txt index 746082ef66e9..d8b9987e3364 100644 --- a/examples/python/raw_mesh/requirements.txt +++ b/examples/python/raw_mesh/requirements.txt @@ -1,5 +1,5 @@ numpy requests==2.28.1 -rerun-sdk +depthai-viewer trimesh==3.15.2 pillow diff --git a/examples/python/ros/main.py b/examples/python/ros/main.py index 0b83bc88d391..132714022a79 100644 --- a/examples/python/ros/main.py +++ b/examples/python/ros/main.py @@ -13,8 +13,8 @@ import argparse import sys +import depthai_viewer as viewer import numpy as np -import rerun as rr try: import cv_bridge @@ -81,7 +81,7 @@ def __init__(self) -> None: # Log a bounding box as a visual placeholder for the map # # TODO(jleibs): Log the real map once [#1531](https://github.com/rerun-io/rerun/issues/1531) is merged - rr.log_obb( + viewer.log_obb( "map/box", half_size=[3, 3, 1], position=[0, 0, 1], @@ -158,18 +158,18 @@ def log_tf_as_rigid3(self, path: str, time: Time) -> None: tf = self.tf_buffer.lookup_transform(parent_frame, child_frame, time, timeout=Duration(seconds=0.1)) t = tf.transform.translation q = tf.transform.rotation - rr.log_rigid3(path, parent_from_child=([t.x, t.y, t.z], [q.x, q.y, q.z, q.w])) + viewer.log_rigid3(path, parent_from_child=([t.x, t.y, t.z], [q.x, q.y, q.z, q.w])) except TransformException as ex: print("Failed to get transform: {}".format(ex)) def cam_info_callback(self, info: CameraInfo) -> None: """Log a `CameraInfo` with `log_pinhole`.""" time = Time.from_msg(info.header.stamp) - rr.set_time_nanos("ros_time", time.nanoseconds) + viewer.set_time_nanos("ros_time", time.nanoseconds) self.model.fromCameraInfo(info) - rr.log_pinhole( + viewer.log_pinhole( "map/robot/camera/img", child_from_parent=self.model.intrinsicMatrix(), width=self.model.width, @@ -179,11 +179,11 @@ def cam_info_callback(self, info: CameraInfo) -> None: def odom_callback(self, odom: Odometry) -> None: """Update transforms when odom is updated.""" time = Time.from_msg(odom.header.stamp) - rr.set_time_nanos("ros_time", time.nanoseconds) + viewer.set_time_nanos("ros_time", time.nanoseconds) # Capture time-series data for the linear and angular velocities - rr.log_scalar("odometry/vel", odom.twist.twist.linear.x) - rr.log_scalar("odometry/ang_vel", odom.twist.twist.angular.z) + viewer.log_scalar("odometry/vel", odom.twist.twist.linear.x) + viewer.log_scalar("odometry/ang_vel", odom.twist.twist.angular.z) # Update the robot pose itself via TF self.log_tf_as_rigid3("map/robot", time) @@ -191,15 +191,15 @@ def odom_callback(self, odom: Odometry) -> None: def image_callback(self, img: Image) -> None: """Log an `Image` with `log_image` using `cv_bridge`.""" time = Time.from_msg(img.header.stamp) - rr.set_time_nanos("ros_time", time.nanoseconds) + viewer.set_time_nanos("ros_time", time.nanoseconds) - rr.log_image("map/robot/camera/img", self.cv_bridge.imgmsg_to_cv2(img)) + viewer.log_image("map/robot/camera/img", self.cv_bridge.imgmsg_to_cv2(img)) self.log_tf_as_rigid3("map/robot/camera", time) def points_callback(self, points: PointCloud2) -> None: """Log a `PointCloud2` with `log_points`.""" time = Time.from_msg(points.header.stamp) - rr.set_time_nanos("ros_time", time.nanoseconds) + viewer.set_time_nanos("ros_time", time.nanoseconds) pts = point_cloud2.read_points(points, field_names=["x", "y", "z"], skip_nans=True) @@ -219,7 +219,7 @@ def points_callback(self, points: PointCloud2) -> None: # Log points once rigidly under robot/camera/points. This is a robot-centric # view of the world. - rr.log_points("map/robot/camera/points", positions=pts, colors=colors) + viewer.log_points("map/robot/camera/points", positions=pts, colors=colors) self.log_tf_as_rigid3("map/robot/camera/points", time) # Log points a second time after transforming to the map frame. This is a map-centric @@ -227,7 +227,7 @@ def points_callback(self, points: PointCloud2) -> None: # # Once Rerun supports fixed-frame aware transforms [#1522](https://github.com/rerun-io/rerun/issues/1522) # this will no longer be necessary. - rr.log_points("map/points", positions=pts, colors=colors) + viewer.log_points("map/points", positions=pts, colors=colors) self.log_tf_as_rigid3("map/points", time) def scan_callback(self, scan: LaserScan) -> None: @@ -239,7 +239,7 @@ def scan_callback(self, scan: LaserScan) -> None: [#1534](https://github.com/rerun-io/rerun/issues/1534) """ time = Time.from_msg(scan.header.stamp) - rr.set_time_nanos("ros_time", time.nanoseconds) + viewer.set_time_nanos("ros_time", time.nanoseconds) # Project the laser scan to a collection of points points = self.laser_proj.projectLaser(scan) @@ -250,7 +250,7 @@ def scan_callback(self, scan: LaserScan) -> None: origin = (pts / np.linalg.norm(pts, axis=1).reshape(-1, 1)) * 0.3 segs = np.hstack([origin, pts]).reshape(pts.shape[0] * 2, 3) - rr.log_line_segments("map/robot/scan", segs, stroke_width=0.005) + viewer.log_line_segments("map/robot/scan", segs, stroke_width=0.005) self.log_tf_as_rigid3("map/robot/scan", time) def urdf_callback(self, urdf_msg: String) -> None: @@ -269,9 +269,9 @@ def urdf_callback(self, urdf_msg: String) -> None: def main() -> None: parser = argparse.ArgumentParser(description="Simple example of a ROS node that republishes to Rerun.") - rr.script_add_args(parser) + viewer.script_add_args(parser) args, unknownargs = parser.parse_known_args() - rr.script_setup(args, "turtlebot_viz") + viewer.script_setup(args, "turtlebot_viz") # Any remaining args go to rclpy rclpy.init(args=unknownargs) diff --git a/examples/python/ros/requirements.txt b/examples/python/ros/requirements.txt index 5c08038be119..c3abe45c4b0d 100644 --- a/examples/python/ros/requirements.txt +++ b/examples/python/ros/requirements.txt @@ -1,4 +1,4 @@ numpy opencv-python -rerun-sdk +depthai-viewer yourdfpy diff --git a/examples/python/ros/rerun_urdf.py b/examples/python/ros/rerun_urdf.py index 64449a7e4e77..5c2a1efd96fa 100644 --- a/examples/python/ros/rerun_urdf.py +++ b/examples/python/ros/rerun_urdf.py @@ -3,8 +3,8 @@ from typing import Optional, cast from urllib.parse import urlparse +import depthai_viewer as viewer import numpy as np -import rerun as rr import trimesh from ament_index_python.packages import get_package_share_directory from std_msgs.msg import String @@ -43,7 +43,7 @@ def log_scene(scene: trimesh.Scene, node: str, path: Optional[str] = None, timel # `trimesh` stores quaternions in `wxyz` format, rerun needs `xyzw` # TODO(jleibs): Remove conversion once [#883](https://github.com/rerun-io/rerun/issues/883) is closed q = np.array([q[1], q[2], q[3], q[0]]) - rr.log_rigid3(path, parent_from_child=(t, q), timeless=timeless) + viewer.log_rigid3(path, parent_from_child=(t, q), timeless=timeless) # Log this node's mesh, if it has one. mesh = cast(trimesh.Trimesh, scene.geometry.get(node_data[1])) @@ -70,7 +70,7 @@ def log_scene(scene: trimesh.Scene, node: str, path: Optional[str] = None, timel albedo_factor = vertex_colors if vertex_colors is not None else visual_color - rr.log_mesh( + viewer.log_mesh( path, mesh.vertices, indices=mesh.faces, diff --git a/examples/python/segment_anything/main.py b/examples/python/segment_anything/main.py index ad2069840f54..dfc33b9a315e 100755 --- a/examples/python/segment_anything/main.py +++ b/examples/python/segment_anything/main.py @@ -26,9 +26,9 @@ from urllib.parse import urlparse import cv2 +import depthai_viewer as viewer import numpy as np import requests -import rerun as rr import torch import torchvision from cv2 import Mat @@ -85,7 +85,7 @@ def create_sam(model: str, device: str) -> Sam: def run_segmentation(mask_generator: SamAutomaticMaskGenerator, image: Mat) -> None: """Run segmentation on a single image.""" - rr.log_image("image", image) + viewer.log_image("image", image) logging.info("Finding masks") masks = mask_generator.generate(image) @@ -98,7 +98,7 @@ def run_segmentation(mask_generator: SamAutomaticMaskGenerator, image: Mat) -> N np.dstack([np.zeros((image.shape[0], image.shape[1]))] + [m["segmentation"] for m in masks]).astype("uint8") * 128 ) - rr.log_tensor("mask_tensor", mask_tensor) + viewer.log_tensor("mask_tensor", mask_tensor) # Note: for stacking, it is important to sort these masks by area from largest to smallest # this is because the masks are overlapping and we want smaller masks to @@ -111,9 +111,9 @@ def run_segmentation(mask_generator: SamAutomaticMaskGenerator, image: Mat) -> N # Work-around for https://github.com/rerun-io/rerun/issues/1782 # Make sure we have an AnnotationInfo present for every class-id used in this image # TODO(jleibs): Remove when fix is released - rr.log_annotation_context( + viewer.log_annotation_context( "image", - [rr.AnnotationInfo(id) for id, _ in masks_with_ids], + [viewer.AnnotationInfo(id) for id, _ in masks_with_ids], timeless=False, ) @@ -122,10 +122,10 @@ def run_segmentation(mask_generator: SamAutomaticMaskGenerator, image: Mat) -> N for id, m in masks_with_ids: segmentation_img[m["segmentation"]] = id - rr.log_segmentation_image("image/masks", segmentation_img) + viewer.log_segmentation_image("image/masks", segmentation_img) mask_bbox = np.array([m["bbox"] for _, m in masks_with_ids]) - rr.log_rects("image/boxes", rects=mask_bbox, class_ids=[id for id, _ in masks_with_ids]) + viewer.log_rects("image/boxes", rects=mask_bbox, class_ids=[id for id, _ in masks_with_ids]) def is_url(path: str) -> bool: @@ -180,11 +180,12 @@ def main() -> None: ) parser.add_argument("images", metavar="N", type=str, nargs="*", help="A list of images to process.") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "segment_anything") - logging.getLogger().addHandler(rr.LoggingHandler("logs")) + viewer.script_setup(args, "segment_anything") + logging.getLogger().addHandler(viewer.LoggingHandler("logs")) logging.getLogger().setLevel(logging.INFO) sam = create_sam(args.model, args.device) @@ -199,11 +200,11 @@ def main() -> None: ] for n, image_uri in enumerate(args.images): - rr.set_time_sequence("image", n) + viewer.set_time_sequence("image", n) image = load_image(image_uri) run_segmentation(mask_generator, image) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/segment_anything/requirements.txt b/examples/python/segment_anything/requirements.txt index 9c0dfad84fa8..23bf59b9dec8 100644 --- a/examples/python/segment_anything/requirements.txt +++ b/examples/python/segment_anything/requirements.txt @@ -2,7 +2,7 @@ numpy opencv-python requests -rerun-sdk +depthai-viewer torch torchvision tqdm diff --git a/examples/python/stable_diffusion/huggingface_pipeline.py b/examples/python/stable_diffusion/huggingface_pipeline.py index 3306c4c77757..e098911aebd9 100644 --- a/examples/python/stable_diffusion/huggingface_pipeline.py +++ b/examples/python/stable_diffusion/huggingface_pipeline.py @@ -56,10 +56,10 @@ DPTForDepthEstimation, ) -import rerun as rr +import depthai_viewer as viewer logger = logging.get_logger(__name__) # pylint: disable=invalid-name -logger.addHandler(rr.log.text.LoggingHandler("logs")) +logger.addHandler(viewer.log.text.LoggingHandler("logs")) # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.preprocess @@ -210,8 +210,8 @@ def _encode_prompt(self, prompt, device, num_images_per_prompt, do_classifier_fr if `guidance_scale` is less than `1`). """ batch_size = len(prompt) if isinstance(prompt, list) else 1 - rr.log_text_entry("prompt/text", prompt) - rr.log_text_entry("prompt/text_negative", negative_prompt) + viewer.log_text_entry("prompt/text", prompt) + viewer.log_text_entry("prompt/text_negative", negative_prompt) text_inputs = self.tokenizer( prompt, padding="max_length", @@ -220,7 +220,7 @@ def _encode_prompt(self, prompt, device, num_images_per_prompt, do_classifier_fr return_tensors="pt", ) text_input_ids = text_inputs.input_ids - rr.log_tensor("prompt/text_input/ids", text_input_ids) + viewer.log_tensor("prompt/text_input/ids", text_input_ids) untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids): @@ -231,7 +231,7 @@ def _encode_prompt(self, prompt, device, num_images_per_prompt, do_classifier_fr ) if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: - rr.log_tensor("prompt/text_input/attention_mask", text_inputs.attention_mask) + viewer.log_tensor("prompt/text_input/attention_mask", text_inputs.attention_mask) attention_mask = text_inputs.attention_mask.to(device) else: attention_mask = None @@ -276,7 +276,7 @@ def _encode_prompt(self, prompt, device, num_images_per_prompt, do_classifier_fr truncation=True, return_tensors="pt", ) - rr.log_tensor("prompt/uncond_input/ids", uncond_input.input_ids) + viewer.log_tensor("prompt/uncond_input/ids", uncond_input.input_ids) if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask: attention_mask = uncond_input.attention_mask.to(device) @@ -297,8 +297,8 @@ def _encode_prompt(self, prompt, device, num_images_per_prompt, do_classifier_fr # For classifier free guidance, we need to do two forward passes. # Here we concatenate the unconditional and text embeddings into a single batch # to avoid doing two forward passes - rr.log_tensor("prompt/text_embeddings", text_embeddings) - rr.log_tensor("prompt/uncond_embeddings", uncond_embeddings) + viewer.log_tensor("prompt/text_embeddings", text_embeddings) + viewer.log_tensor("prompt/uncond_embeddings", uncond_embeddings) text_embeddings = torch.cat([uncond_embeddings, text_embeddings]) return text_embeddings @@ -375,14 +375,14 @@ def prepare_latents(self, image, timestep, batch_size, num_images_per_prompt, dt image = image.to(device=device, dtype=dtype) init_latent_dist = self.vae.encode(image).latent_dist init_latents = init_latent_dist.sample(generator=generator) - rr.log_tensor("encoded_input_image", init_latents, names=["b", "c", "h", "w"]) + viewer.log_tensor("encoded_input_image", init_latents, names=["b", "c", "h", "w"]) # Decode the initial latents so we can inspect the image->latent->image loop decoded = self.vae.decode(init_latents).sample decoded = (decoded / 2 + 0.5).clamp(0, 1) decoded = decoded.cpu().permute(0, 2, 3, 1).float().numpy() decoded = (decoded * 255).round().astype("uint8") - rr.log_image("decoded_init_latents", decoded) + viewer.log_image("decoded_init_latents", decoded) init_latents = 0.18215 * init_latents @@ -426,7 +426,7 @@ def prepare_depth_map(self, image, depth_map, batch_size, do_classifier_free_gui if depth_map is None: pixel_values = self.feature_extractor(images=image, return_tensors="pt").pixel_values - rr.log_tensor("depth/input_preprocessed", pixel_values) + viewer.log_tensor("depth/input_preprocessed", pixel_values) pixel_values = pixel_values.to(device=device) # The DPT-Hybrid model uses batch-norm layers which are not compatible with fp16. # So we use `torch.autocast` here for half precision inference. @@ -436,19 +436,19 @@ def prepare_depth_map(self, image, depth_map, batch_size, do_classifier_free_gui else: depth_map = depth_map.to(device=device, dtype=dtype) - rr.log_depth_image("depth/estimated", depth_map) + viewer.log_depth_image("depth/estimated", depth_map) depth_map = torch.nn.functional.interpolate( depth_map.unsqueeze(1), size=(height // self.vae_scale_factor, width // self.vae_scale_factor), mode="bicubic", align_corners=False, ) - rr.log_depth_image("depth/interpolated", depth_map) + viewer.log_depth_image("depth/interpolated", depth_map) depth_min = torch.amin(depth_map, dim=[1, 2, 3], keepdim=True) depth_max = torch.amax(depth_map, dim=[1, 2, 3], keepdim=True) depth_map = 2.0 * (depth_map - depth_min) / (depth_max - depth_min) - 1.0 - rr.log_depth_image("depth/normalized", depth_map) + viewer.log_depth_image("depth/normalized", depth_map) depth_map = depth_map.to(dtype) # duplicate mask and masked_image_latents for each generation per prompt, using mps friendly method @@ -529,7 +529,7 @@ def __call__( list of `bool`s denoting whether the corresponding generated image likely represents "not-safe-for-work" (nsfw) content, according to the `safety_checker`. """ - rr.set_time_sequence("step", -1) + viewer.set_time_sequence("step", -1) # 1. Check inputs self.check_inputs(prompt, strength, callback_steps) @@ -557,9 +557,9 @@ def __call__( ) # 5. Preprocess image - rr.log_image("image/original", image) + viewer.log_image("image/original", image) image = preprocess(image) - rr.log_tensor("input_image/preprocessed", image) + viewer.log_tensor("input_image/preprocessed", image) # 6. set timesteps self.scheduler.set_timesteps(num_inference_steps, device=device) @@ -570,7 +570,7 @@ def __call__( latents = self.prepare_latents( image, latent_timestep, batch_size, num_images_per_prompt, text_embeddings.dtype, device, generator ) - rr.log_tensor("diffusion/latents", latents, names=["b", "c", "h", "w"]) + viewer.log_tensor("diffusion/latents", latents, names=["b", "c", "h", "w"]) # 8. Prepare extra step kwargs. TODO(original author): Logic should ideally just be moved out of the pipeline extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) @@ -579,18 +579,18 @@ def __call__( num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order with self.progress_bar(total=num_inference_steps) as progress_bar: for i, t in enumerate(timesteps): - rr.set_time_sequence("step", i) - rr.set_time_sequence("timestep", t) + viewer.set_time_sequence("step", i) + viewer.set_time_sequence("timestep", t) # expand the latents if we are doing classifier free guidance latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) latent_model_input = torch.cat([latent_model_input, depth_mask], dim=1) - rr.log_tensor("diffusion/latent_model_input", latent_model_input) + viewer.log_tensor("diffusion/latent_model_input", latent_model_input) # predict the noise residual noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=text_embeddings).sample - rr.log_tensor("diffusion/noise_pred", noise_pred, names=["b", "c", "h", "w"]) + viewer.log_tensor("diffusion/noise_pred", noise_pred, names=["b", "c", "h", "w"]) # perform guidance if do_classifier_free_guidance: @@ -599,12 +599,12 @@ def __call__( # compute the previous noisy sample x_t -> x_t-1 latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample - rr.log_tensor("diffusion/latents", latents, names=["b", "c", "h", "w"]) + viewer.log_tensor("diffusion/latents", latents, names=["b", "c", "h", "w"]) # Decode the latents for visualization purposes image = self.decode_latents(latents) image = (image * 255).round().astype("uint8") - rr.log_image("image/diffused", image) + viewer.log_image("image/diffused", image) # call the callback, if provided if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): @@ -615,7 +615,7 @@ def __call__( # 10. Post-processing image = self.decode_latents(latents) image_8 = (image * 255).round().astype("uint8") - rr.log_image("image/diffused", image_8) + viewer.log_image("image/diffused", image_8) # 11. Convert to PIL if output_type == "pil": diff --git a/examples/python/stable_diffusion/main.py b/examples/python/stable_diffusion/main.py index 9452ae3bd837..a4e7429cb689 100755 --- a/examples/python/stable_diffusion/main.py +++ b/examples/python/stable_diffusion/main.py @@ -13,8 +13,8 @@ if platform.system() == "Darwin": os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" +import depthai_viewer as viewer import requests -import rerun as rr import torch from huggingface_pipeline import StableDiffusionDepth2ImgPipeline from PIL import Image @@ -107,10 +107,11 @@ def main() -> None: """, ) - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "Depth Guided Stable Diffusion") + viewer.script_setup(args, "Depth Guided Stable Diffusion") image_path = args.image_path # type: str if not image_path: @@ -140,7 +141,7 @@ def main() -> None: image=image, ) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/stable_diffusion/requirements.txt b/examples/python/stable_diffusion/requirements.txt index abc65b445d76..4582e9d86375 100644 --- a/examples/python/stable_diffusion/requirements.txt +++ b/examples/python/stable_diffusion/requirements.txt @@ -5,7 +5,7 @@ numpy packaging pillow requests==2.28.1 -rerun-sdk +depthai-viewer scipy torch>=1.13.0 transformers>=4.26.0 diff --git a/examples/python/text_logging/main.py b/examples/python/text_logging/main.py index c02a7ae3d6df..b1b4e2d699cb 100755 --- a/examples/python/text_logging/main.py +++ b/examples/python/text_logging/main.py @@ -13,7 +13,7 @@ import argparse import logging -import rerun as rr +import depthai_viewer as viewer def setup_logging() -> None: @@ -25,7 +25,7 @@ def setup_logging() -> None: # default). # # For more info: https://docs.python.org/3/howto/logging.html#handlers - logging.getLogger().addHandler(rr.log.text.LoggingHandler()) + logging.getLogger().addHandler(viewer.log.text.LoggingHandler()) logging.getLogger().setLevel(-1) @@ -51,7 +51,7 @@ def log_stuff(frame_offset: int) -> None: # Test that we can log multiple times to the same sequence timeline and still # have the log messages show up in the correct chronological order in the viewer: for frame_nr in range(2): - rr.set_time_sequence("frame_nr", 2 * frame_offset + frame_nr) + viewer.set_time_sequence("frame_nr", 2 * frame_offset + frame_nr) logging.info(f"Log one thing during frame {frame_nr}") logging.info(f"Log second thing during the same frame {frame_nr}") logging.info(f"Log third thing during the same frame {frame_nr}") @@ -63,7 +63,7 @@ def log_stuff(frame_offset: int) -> None: # Use spaces to create distinct logging streams other_logger = logging.getLogger("totally.unrelated") other_logger.propagate = False # don't want root logger to catch those - other_logger.addHandler(rr.log.text.LoggingHandler("3rd_party_logs")) + other_logger.addHandler(viewer.log.text.LoggingHandler("3rd_party_logs")) for _ in range(10): other_logger.debug("look ma, got my very own view!") @@ -73,16 +73,17 @@ def main() -> None: description="demonstrates how to integrate python's native `logging` with the Rerun SDK" ) parser.add_argument("--repeat", type=int, default=1, help="How many times do we want to run the log function?") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "text_logging") + viewer.script_setup(args, "text_logging") setup_logging() for frame_offset in range(args.repeat): log_stuff(frame_offset) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/text_logging/requirements.txt b/examples/python/text_logging/requirements.txt index ebb847ff0d2d..a6bfff6cfe23 100644 --- a/examples/python/text_logging/requirements.txt +++ b/examples/python/text_logging/requirements.txt @@ -1 +1 @@ -rerun-sdk +depthai-viewer diff --git a/examples/python/tracking_hf_opencv/main.py b/examples/python/tracking_hf_opencv/main.py index 2c783d0ba341..df2be51e9dbd 100755 --- a/examples/python/tracking_hf_opencv/main.py +++ b/examples/python/tracking_hf_opencv/main.py @@ -9,10 +9,10 @@ from typing import Any, Dict, Final, List, Sequence import cv2 as cv +import depthai_viewer as viewer import numpy as np import numpy.typing as npt import requests -import rerun as rr from PIL import Image EXAMPLE_DIR: Final = Path(os.path.dirname(__file__)) @@ -83,8 +83,8 @@ def detect_objects_to_track(self, rgb: npt.NDArray[np.uint8], frame_idx: int) -> _, _, scaled_height, scaled_width = inputs["pixel_values"].shape scaled_size = (scaled_width, scaled_height) rgb_scaled = cv.resize(rgb, scaled_size) - rr.log_image("image/scaled/rgb", rgb_scaled) - rr.log_unknown_transform("image/scaled") # Note: Haven't implemented 2D transforms yet. + viewer.log_image("image/scaled/rgb", rgb_scaled) + viewer.log_unknown_transform("image/scaled") # Note: Haven't implemented 2D transforms yet. logging.debug("Pass image to detection network") outputs = self.model(**inputs) @@ -97,7 +97,7 @@ def detect_objects_to_track(self, rgb: npt.NDArray[np.uint8], frame_idx: int) -> )[0] mask = segmentation_mask.detach().cpu().numpy().astype(np.uint8) - rr.log_segmentation_image("image/scaled/segmentation", mask) + viewer.log_segmentation_image("image/scaled/segmentation", mask) boxes = detections["boxes"].detach().cpu().numpy() class_ids = detections["labels"].detach().cpu().numpy() @@ -127,19 +127,19 @@ def log_detections(self, boxes: npt.NDArray[np.float32], class_ids: List[int], t thing_boxes = boxes[things_np, :] thing_class_ids = class_ids_np[things_np] - rr.log_rects( + viewer.log_rects( "image/scaled/detections/things", thing_boxes, - rect_format=rr.log.rects.RectFormat.XYXY, + rect_format=viewer.log.rects.RectFormat.XYXY, class_ids=thing_class_ids, ) background_boxes = boxes[~things_np, :] background_class_ids = class_ids[~things_np] - rr.log_rects( + viewer.log_rects( "image/scaled/detections/background", background_boxes, - rect_format=rr.log.rects.RectFormat.XYXY, + rect_format=viewer.log.rects.RectFormat.XYXY, class_ids=background_class_ids, ) @@ -187,14 +187,14 @@ def update(self, bgr: npt.NDArray[np.uint8]) -> None: def log_tracked(self) -> None: if self.is_tracking: - rr.log_rect( + viewer.log_rect( f"image/tracked/{self.tracking_id}", self.tracked.bbox_xywh, - rect_format=rr.log.rects.RectFormat.XYWH, + rect_format=viewer.log.rects.RectFormat.XYWH, class_id=self.tracked.class_id, ) else: - rr.log_rect(f"image/tracked/{self.tracking_id}", None) + viewer.log_rect(f"image/tracked/{self.tracking_id}", None) def update_with_detection(self, detection: Detection, bgr: npt.NDArray[np.uint8]) -> None: self.num_recent_undetected_frames = 0 @@ -311,9 +311,10 @@ def track_objects(video_path: str) -> None: with open(COCO_CATEGORIES_PATH) as f: coco_categories = json.load(f) class_descriptions = [ - rr.log.annotation.AnnotationInfo(id=cat["id"], color=cat["color"], label=cat["name"]) for cat in coco_categories + viewer.log.annotation.AnnotationInfo(id=cat["id"], color=cat["color"], label=cat["name"]) + for cat in coco_categories ] - rr.log_annotation_context("/", class_descriptions, timeless=True) + viewer.log_annotation_context("/", class_descriptions, timeless=True) detector = Detector(coco_categories=coco_categories) @@ -325,14 +326,14 @@ def track_objects(video_path: str) -> None: trackers = [] # type: List[Tracker] while cap.isOpened(): ret, bgr = cap.read() - rr.set_time_sequence("frame", frame_idx) + viewer.set_time_sequence("frame", frame_idx) if not ret: logging.info("End of video") break rgb = cv.cvtColor(bgr, cv.COLOR_BGR2RGB) - rr.log_image("image/rgb", rgb) + viewer.log_image("image/rgb", rgb) if not trackers or frame_idx % 40 == 0: detections = detector.detect_objects_to_track(rgb=rgb, frame_idx=frame_idx) @@ -368,7 +369,7 @@ def get_downloaded_path(dataset_dir: Path, video_name: str) -> str: def setup_looging() -> None: logger = logging.getLogger() - rerun_handler = rr.log.text.LoggingHandler("logs") + rerun_handler = viewer.log.text.LoggingHandler("logs") rerun_handler.setLevel(-1) logger.addHandler(rerun_handler) stream_handler = logging.StreamHandler() @@ -392,10 +393,11 @@ def main() -> None: ) parser.add_argument("--dataset_dir", type=Path, default=DATASET_DIR, help="Directory to save example videos to.") parser.add_argument("--video_path", type=str, default="", help="Full path to video to run on. Overrides `--video`.") - rr.script_add_args(parser) - args = parser.parse_args() + viewer.script_add_args(parser) + args, unknown = parser.parse_known_args() + [__import__("logging").warning(f"unknown arg: {arg}") for arg in unknown] - rr.script_setup(args, "tracking_hf_opencv") + viewer.script_setup(args, "tracking_hf_opencv") setup_looging() @@ -405,7 +407,7 @@ def main() -> None: track_objects(video_path) - rr.script_teardown(args) + viewer.script_teardown(args) if __name__ == "__main__": diff --git a/examples/python/tracking_hf_opencv/requirements.txt b/examples/python/tracking_hf_opencv/requirements.txt index 6a9d1bb22a33..c4dfeec99773 100644 --- a/examples/python/tracking_hf_opencv/requirements.txt +++ b/examples/python/tracking_hf_opencv/requirements.txt @@ -3,7 +3,7 @@ opencv-contrib-python<4.6 # Avoid opencv-4.6 since it rotates images incorrectly opencv-python<4.6 # Avoid opencv-4.6 since it rotates images incorrectly (https://github.com/opencv/opencv/issues/22088) pillow requests==2.28.1 -rerun-sdk +depthai-viewer timm==0.6.11 torch>=1.13.0 transformers diff --git a/examples/rust/api_demo/Cargo.toml b/examples/rust/api_demo/Cargo.toml index 508fa149957a..71110ce2b8f5 100644 --- a/examples/rust/api_demo/Cargo.toml +++ b/examples/rust/api_demo/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true publish = false [dependencies] -rerun = { workspace = true, features = ["web_viewer"] } +depthai-viewer = { workspace = true, features = ["web_viewer"] } anyhow.workspace = true clap = { workspace = true, features = ["derive"] } diff --git a/examples/rust/api_demo/src/main.rs b/examples/rust/api_demo/src/main.rs index abc6ff2e873e..16bcbbe4402b 100644 --- a/examples/rust/api_demo/src/main.rs +++ b/examples/rust/api_demo/src/main.rs @@ -17,7 +17,7 @@ use std::{ f32::consts::{PI, TAU}, }; -use rerun::{ +use depthai_viewer::{ components::{ AnnotationContext, AnnotationInfo, Box3D, ClassDescription, ClassId, ColorRGBA, Label, LineStrip3D, Point2D, Point3D, Quaternion, Radius, Rect2D, Rigid3, Tensor, @@ -149,7 +149,7 @@ fn demo_log_cleared(session: &Session) -> anyhow::Result<()> { ent_path: impl Into, recursive: bool, ) { - use rerun::external::re_log_types::PathOp; + use depthai_viewer::external::re_log_types::PathOp; let tp = timepoint.iter().collect::>(); let timepoint = [ (Timeline::log_time(), Time::now().into()), @@ -474,7 +474,7 @@ fn demo_transforms_3d(session: &Session) -> anyhow::Result<()> { ) -> anyhow::Result<()> { let view_coords = ViewCoordinates::from_up_and_handedness( SignedAxis3::POSITIVE_Z, - rerun::coordinates::Handedness::Right, + depthai_viewer::coordinates::Handedness::Right, ); MsgSender::new(ent_path.into()) .with_timeless(true) @@ -622,7 +622,7 @@ enum Demo { #[clap(author, version, about)] struct Args { #[command(flatten)] - rerun: rerun::clap::RerunArgs, + rerun: depthai_viewer::clap::RerunArgs, /// Which demo should we run? All of them by default. #[clap(long, value_enum)] diff --git a/examples/rust/dna/Cargo.toml b/examples/rust/dna/Cargo.toml index d4f94b92e839..473645b8fb00 100644 --- a/examples/rust/dna/Cargo.toml +++ b/examples/rust/dna/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true publish = false [dependencies] -rerun.workspace = true +depthai-viewer.workspace = true itertools = { workspace = true } rand = "0.8" diff --git a/examples/rust/dna/src/main.rs b/examples/rust/dna/src/main.rs index 488808886119..3484fd7bbfe9 100644 --- a/examples/rust/dna/src/main.rs +++ b/examples/rust/dna/src/main.rs @@ -4,7 +4,7 @@ use std::f32::consts::TAU; use itertools::Itertools as _; -use rerun::{ +use depthai_viewer::{ components::{ColorRGBA, LineStrip3D, Point3D, Quaternion, Radius, Rigid3, Transform, Vec3D}, demo_util::{bounce_lerp, color_spiral}, external::glam, @@ -15,8 +15,8 @@ use rerun::{ const NUM_POINTS: usize = 100; fn main() -> Result<(), Box> { - let recording_info = rerun::new_recording_info("DNA Abacus"); - rerun::native_viewer::spawn(recording_info, |session| { + let recording_info = depthai_viewer::new_recording_info("DNA Abacus"); + depthai_viewer::native_viewer::spawn(recording_info, |session| { run(&session).unwrap(); })?; Ok(()) diff --git a/examples/rust/minimal/Cargo.toml b/examples/rust/minimal/Cargo.toml index cb3e27d8ef68..24777a6df22e 100644 --- a/examples/rust/minimal/Cargo.toml +++ b/examples/rust/minimal/Cargo.toml @@ -7,4 +7,4 @@ license.workspace = true publish = false [dependencies] -rerun.workspace = true +depthai-viewer.workspace = true diff --git a/examples/rust/minimal/src/main.rs b/examples/rust/minimal/src/main.rs index 6fdce0ef1a48..6a82aa078abc 100644 --- a/examples/rust/minimal/src/main.rs +++ b/examples/rust/minimal/src/main.rs @@ -1,6 +1,6 @@ //! Demonstrates the most barebone usage of the Rerun SDK. -use rerun::{ +use depthai_viewer::{ components::{ColorRGBA, Point3D}, demo_util::grid, external::glam, @@ -22,7 +22,7 @@ fn main() -> Result<(), Box> { .with_component(&colors)? .send(&session)?; - rerun::native_viewer::show(&session)?; + depthai_viewer::native_viewer::show(&session)?; Ok(()) } diff --git a/examples/rust/minimal_options/Cargo.toml b/examples/rust/minimal_options/Cargo.toml index 9f5888dd8d69..e65ba047139d 100644 --- a/examples/rust/minimal_options/Cargo.toml +++ b/examples/rust/minimal_options/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true publish = false [dependencies] -rerun = { workspace = true, features = ["web_viewer"] } +depthai-viewer = { workspace = true, features = ["web_viewer"] } anyhow.workspace = true clap = { workspace = true, features = ["derive"] } diff --git a/examples/rust/minimal_options/src/main.rs b/examples/rust/minimal_options/src/main.rs index c6045998e498..288dfdad0b98 100644 --- a/examples/rust/minimal_options/src/main.rs +++ b/examples/rust/minimal_options/src/main.rs @@ -5,17 +5,17 @@ //! cargo run -p minimal_options -- --help //! ``` -use rerun::components::{ColorRGBA, Point3D}; -use rerun::time::{TimeType, Timeline}; -use rerun::{external::re_log, MsgSender, Session}; +use depthai_viewer::components::{ColorRGBA, Point3D}; +use depthai_viewer::time::{TimeType, Timeline}; +use depthai_viewer::{external::re_log, MsgSender, Session}; -use rerun::demo_util::grid; +use depthai_viewer::demo_util::grid; #[derive(Debug, clap::Parser)] #[clap(author, version, about)] struct Args { #[command(flatten)] - rerun: rerun::clap::RerunArgs, + rerun: depthai_viewer::clap::RerunArgs, #[clap(long, default_value = "10")] num_points_per_axis: usize, diff --git a/examples/rust/objectron/Cargo.toml b/examples/rust/objectron/Cargo.toml index e96c1b651a73..11d3046c9f74 100644 --- a/examples/rust/objectron/Cargo.toml +++ b/examples/rust/objectron/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] -rerun = { workspace = true, features = ["web_viewer"] } +depthai-viewer = { workspace = true, features = ["web_viewer"] } anyhow.workspace = true clap = { workspace = true, features = ["derive"] } diff --git a/examples/rust/objectron/src/main.rs b/examples/rust/objectron/src/main.rs index 203b581a0ec4..737a300186f2 100644 --- a/examples/rust/objectron/src/main.rs +++ b/examples/rust/objectron/src/main.rs @@ -18,7 +18,7 @@ use std::{ use anyhow::{anyhow, Context as _}; -use rerun::{ +use depthai_viewer::{ external::re_log, time::{Time, TimePoint, TimeType, Timeline}, MsgSender, Session, @@ -74,10 +74,10 @@ impl<'a> From<&'a [objectron::FrameAnnotation]> for AnnotationsPerFrame<'a> { fn log_coordinate_space( session: &Session, - ent_path: impl Into, + ent_path: impl Into, axes: &str, ) -> anyhow::Result<()> { - let view_coords: rerun::components::ViewCoordinates = axes + let view_coords: depthai_viewer::components::ViewCoordinates = axes .parse() .map_err(|err| anyhow!("couldn't parse {axes:?} as ViewCoordinates: {err}"))?; MsgSender::new(ent_path) @@ -110,7 +110,7 @@ fn log_ar_frame( } fn log_baseline_objects(session: &Session, objects: &[objectron::Object]) -> anyhow::Result<()> { - use rerun::components::{Box3D, ColorRGBA, Label, Rigid3, Transform}; + use depthai_viewer::components::{Box3D, ColorRGBA, Label, Rigid3, Transform}; let boxes = objects.iter().filter_map(|object| { Some({ @@ -151,7 +151,7 @@ fn log_baseline_objects(session: &Session, objects: &[objectron::Object]) -> any fn log_video_frame(session: &Session, ar_frame: &ArFrame) -> anyhow::Result<()> { let image_path = ar_frame.dir.join(format!("video/{}.jpg", ar_frame.index)); - let tensor = rerun::components::Tensor::tensor_from_jpeg_file(image_path)?; + let tensor = depthai_viewer::components::Tensor::tensor_from_jpeg_file(image_path)?; MsgSender::new("world/camera/video") .with_timepoint(ar_frame.timepoint.clone()) @@ -189,7 +189,7 @@ fn log_ar_camera( // TODO(cmc): I can't figure out why I need to do this let rot = rot * glam::Quat::from_axis_angle(glam::Vec3::X, std::f32::consts::TAU / 2.0); - use rerun::components::{Pinhole, Rigid3, Transform}; + use depthai_viewer::components::{Pinhole, Rigid3, Transform}; MsgSender::new("world/camera") .with_timepoint(timepoint.clone()) .with_component(&[Transform::Rigid3(Rigid3 { @@ -213,7 +213,7 @@ fn log_feature_points( timepoint: TimePoint, points: &objectron::ArPointCloud, ) -> anyhow::Result<()> { - use rerun::components::{ColorRGBA, InstanceKey, Point3D}; + use depthai_viewer::components::{ColorRGBA, InstanceKey, Point3D}; let ids = points.identifier.iter(); let points = points.point.iter(); @@ -246,7 +246,7 @@ fn log_frame_annotations( timepoint: &TimePoint, annotations: &objectron::FrameAnnotation, ) -> anyhow::Result<()> { - use rerun::components::{ColorRGBA, InstanceKey, LineStrip2D, Point2D}; + use depthai_viewer::components::{ColorRGBA, InstanceKey, LineStrip2D, Point2D}; for ann in &annotations.annotations { // TODO(cmc): we shouldn't be using those preprojected 2D points to begin with, Rerun is @@ -332,7 +332,7 @@ enum Recording { #[clap(author, version, about)] struct Args { #[command(flatten)] - rerun: rerun::clap::RerunArgs, + rerun: depthai_viewer::clap::RerunArgs, /// Specifies the recording to replay. #[clap(long, value_enum, default_value = "book")] diff --git a/examples/rust/raw_mesh/Cargo.toml b/examples/rust/raw_mesh/Cargo.toml index 7f50ee7d1868..cbd7c076b9c2 100644 --- a/examples/rust/raw_mesh/Cargo.toml +++ b/examples/rust/raw_mesh/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true publish = false [dependencies] -rerun = { workspace = true, features = ["web_viewer"] } +depthai-viewer = { workspace = true, features = ["web_viewer"] } anyhow.workspace = true bytes = "1.3" diff --git a/examples/rust/raw_mesh/src/main.rs b/examples/rust/raw_mesh/src/main.rs index fd1f1e0419fc..d357e04ed35e 100644 --- a/examples/rust/raw_mesh/src/main.rs +++ b/examples/rust/raw_mesh/src/main.rs @@ -12,9 +12,11 @@ use std::path::PathBuf; use anyhow::anyhow; use bytes::Bytes; -use rerun::components::{ColorRGBA, Mesh3D, MeshId, RawMesh3D, Transform, Vec4D, ViewCoordinates}; -use rerun::time::{TimeType, Timeline}; -use rerun::{ +use depthai_viewer::components::{ + ColorRGBA, Mesh3D, MeshId, RawMesh3D, Transform, Vec4D, ViewCoordinates, +}; +use depthai_viewer::time::{TimeType, Timeline}; +use depthai_viewer::{ external::{re_log, re_memory::AccountingAllocator}, EntityPath, MsgSender, Session, }; @@ -54,14 +56,14 @@ impl From for Mesh3D { // Declare how to turn a glTF transform into a Rerun component (`Transform`). impl From for Transform { fn from(transform: GltfTransform) -> Self { - Transform::Rigid3(rerun::components::Rigid3 { - rotation: rerun::components::Quaternion { + Transform::Rigid3(depthai_viewer::components::Rigid3 { + rotation: depthai_viewer::components::Quaternion { x: transform.r[0], y: transform.r[1], z: transform.r[2], w: transform.r[3], }, - translation: rerun::components::Vec3D(transform.t), + translation: depthai_viewer::components::Vec3D(transform.t), }) } } @@ -131,7 +133,7 @@ enum Scene { #[clap(author, version, about)] struct Args { #[command(flatten)] - rerun: rerun::clap::RerunArgs, + rerun: depthai_viewer::clap::RerunArgs, /// Specifies the glTF scene to load. #[clap(long, value_enum, default_value = "buggy")] diff --git a/justfile b/justfile index 90a6b0deecff..c7301a1f1db7 100644 --- a/justfile +++ b/justfile @@ -31,11 +31,35 @@ py-dev-env: venv/bin/pip install -r rerun_py/requirements-lint.txt echo "Do 'source venv/bin/activate' to use the virtual environment!" -# Run all examples -py-run-all: py-build +# Run all examples with the specified args +py-run-all *ARGS: py-build #!/usr/bin/env bash set -euo pipefail - fd main.py | xargs -I _ sh -c "echo _ && python3 _" + find examples/python/ -name main.py | xargs -I _ sh -c 'cd $(dirname _) && echo $(pwd) && python3 main.py {{ARGS}} --num-frames=30 --steps=200' + +# Run all examples in the native viewer +py-run-all-native: py-run-all + +# Run all examples in the web viewer +py-run-all-web: + #!/usr/bin/env bash + set -euo pipefail + + function cleanup { + kill $(jobs -p) + } + trap cleanup EXIT + + cargo r -p rerun --all-features -- --web-viewer & + just py-run-all --connect + +# Run all examples, save them to disk as rrd, then view them natively +py-run-all-rrd *ARGS: + #!/usr/bin/env bash + set -euo pipefail + just py-run-all --save out.rrd + cargo r -p rerun --all-features -- + find examples/python/ -name main.py | xargs -I _ sh -c 'cd $(dirname _) && echo $(pwd) && cargo r -p rerun --all-features -- out.rrd' # Build and install the package into the venv py-build *ARGS: @@ -100,6 +124,12 @@ rs-lint: cargo doc --quiet --document-private-items --no-deps --all-features cargo test --quiet --doc --all-features # runs all doc-tests +# Run all examples with the specified args +rs-run-all *ARGS: + #!/usr/bin/env bash + set -euo pipefail + find examples/rust/ -name main.rs | xargs -I _ sh -c 'cd $(dirname _) && echo $(pwd) && cargo r' + ### TOML diff --git a/rerun_py/Cargo.toml b/rerun_py/Cargo.toml index a538ad7fba83..0493512b242f 100644 --- a/rerun_py/Cargo.toml +++ b/rerun_py/Cargo.toml @@ -14,8 +14,8 @@ name = "rerun_bindings" # name of the .so library that the Python module will im [features] default = ["extension-module", "native_viewer"] -## The features we turn on when building the `rerun-sdk` PyPi package -## for . +## The features we turn on when building the `depthai-viewer` PyPi package +## for . pypi = ["extension-module", "native_viewer", "web_viewer"] ## We need to enable the `pyo3/extension-module` when building the SDK, @@ -26,7 +26,7 @@ pypi = ["extension-module", "native_viewer", "web_viewer"] extension-module = ["pyo3/extension-module"] ## Support spawning a native Rerun viewer with `spawn()`. -native_viewer = ["rerun/native_viewer"] +native_viewer = ["depthai-viewer/native_viewer"] ## Support serving a web viewer over HTTP with `serve()`. ## @@ -35,7 +35,11 @@ native_viewer = ["rerun/native_viewer"] ## ## You also need to install some additional tools, which you can do by running ## [`scripts/setup_web.sh`](https://github.com/rerun-io/rerun/blob/main/scripts/setup_web.sh). -web_viewer = ["rerun/web_viewer", "dep:re_web_viewer_server", "dep:re_ws_comms"] +web_viewer = [ + "depthai-viewer/web_viewer", + "dep:re_web_viewer_server", + "dep:re_ws_comms", +] [dependencies] @@ -44,7 +48,7 @@ re_error.workspace = true re_log.workspace = true re_log_types.workspace = true re_memory.workspace = true -rerun = { workspace = true, default-features = false, features = [ +depthai-viewer = { workspace = true, default-features = false, features = [ "analytics", "server", "sdk", diff --git a/rerun_py/README.md b/rerun_py/README.md index 9286cfa58153..1a0a09166208 100644 --- a/rerun_py/README.md +++ b/rerun_py/README.md @@ -3,7 +3,6 @@ Rerun is an SDK for logging computer vision and robotics data paired with a visualizer for exploring that data over time. It lets you debug and understand the internal state and data of your systems with minimal code. -

Rerun Viewer

@@ -11,60 +10,65 @@ It lets you debug and understand the internal state and data of your systems wit ## Install ```sh -pip3 install rerun-sdk +pip3 install depthai-viewer ``` ℹ️ Note: -The Python module is called `rerun`, while the package published on PyPI is `rerun-sdk`. +The Python module is called `rerun`, while the package published on PyPI is `depthai-viewer`. ## Example + ```py import rerun as rr import numpy as np -rr.spawn() +viewer.spawn() positions = np.vstack([xyz.ravel() for xyz in np.mgrid[3 * [slice(-5, 5, 10j)]]]).T colors = np.vstack([rgb.ravel() for rgb in np.mgrid[3 * [slice(0, 255, 10j)]]]).astype(np.uint8).T -rr.log_points("my_points", positions=positions, colors=colors) +viewer.log_points("my_points", positions=positions, colors=colors) ``` ## Resources -* [Quick start](https://www.rerun.io/docs/getting-started/python) -* [Python API docs](https://ref.rerun.io/docs/python) -* [Tutorial](https://www.rerun.io/docs/getting-started/logging-python) -* [Examples on GitHub](https://github.com/rerun-io/rerun/tree/latest/examples/python) -* [Troubleshooting](https://www.rerun.io/docs/getting-started/troubleshooting) -* [Discord Server](https://discord.com/invite/Gcm8BbTaAj) + +- [Quick start](https://www.rerun.io/docs/getting-started/python) +- [Python API docs](https://ref.rerun.io/docs/python) +- [Tutorial](https://www.rerun.io/docs/getting-started/logging-python) +- [Examples on GitHub](https://github.com/rerun-io/rerun/tree/latest/examples/python) +- [Troubleshooting](https://www.rerun.io/docs/getting-started/troubleshooting) +- [Discord Server](https://discord.com/invite/Gcm8BbTaAj) ## Logging and viewing in different processes You can run the viewer and logger in different processes. In one terminal, start up a viewer with a server that the SDK can connect to: + ```sh python3 -m rerun ``` In a second terminal, run the example with the `--connect` option: + ```sh python3 examples/python/car/main.py --connect ``` -------------------------- +--- # From Source Setup: -* Install the Rust toolchain: -* `git clone git@github.com:rerun-io/rerun.git && cd rerun` -* Run `./scripts/setup_dev.sh`. -* Make sure `cargo --version` prints `1.67.1` once you are done +- Install the Rust toolchain: +- `git clone git@github.com:rerun-io/rerun.git && cd rerun` +- Run `./scripts/setup_dev.sh`. +- Make sure `cargo --version` prints `1.67.1` once you are done ## Building -To build from source and install Rerun into your *current* Python environment run: + +To build from source and install Rerun into your _current_ Python environment run: ```sh python3 -m pip install --upgrade pip @@ -98,6 +102,7 @@ just py-build ``` ### Test + ```sh # Run the unit tests just py-test @@ -110,9 +115,11 @@ python examples/python/car/main.py ``` ## Building an installable Python Wheel + The Python bindings to the core Rust library are built using https://github.com/PyO3/pyo3. To build an installable Python wheel run: + ``` pip install -r rerun_py/requirements-build.txt maturin build -m rerun_py/Cargo.toml --release @@ -127,35 +134,41 @@ pip3 install target/wheels/*.whl ``` ## Viewing the docs locally + The rerun python docs are generated using `mkdocs` Install the doc requirements: + ``` pip install -r rerun_py/requirements-doc.txt ``` Serve the docs: + ```sh mkdocs serve -f rerun_py/mkdocs.yml -w rerun_py ``` + or + ```sh just py-docs-serve ``` For information on how the docs system works, see: [docs/docs.md](docs/docs.md) - ## Troubleshooting + You can run with `RUST_LOG=debug` to get more output out of the rerun SDK. If you are using an Apple-silicon Mac, make sure `rustc -vV` outputs `host: aarch64-apple-darwin`. If not, this should fix it: -``` sh +```sh rustup set default-host aarch64-apple-darwin && rustup install 1.67 ``` If you want to switch back, this is how: -``` sh + +```sh rustup set default-host x86_64-apple-darwin && rustup install 1.67 ``` diff --git a/rerun_py/rerun/__init__.py b/rerun_py/depthai_viewer/__init__.py similarity index 89% rename from rerun_py/rerun/__init__.py rename to rerun_py/depthai_viewer/__init__.py index 4e72d42e2cc4..af379c212ee7 100644 --- a/rerun_py/rerun/__init__.py +++ b/rerun_py/depthai_viewer/__init__.py @@ -20,5 +20,5 @@ sys.path.insert(0, str(real_path)) -del sys.modules["rerun"] -sys.modules["rerun"] = __import__("rerun") +del sys.modules["depthai_viewer"] +sys.modules["depthai_viewer"] = __import__("depthai_viewer") diff --git a/rerun_py/docs/gen_common_index.py b/rerun_py/docs/gen_common_index.py index b159a49858bd..7b9326a9a834 100644 --- a/rerun_py/docs/gen_common_index.py +++ b/rerun_py/docs/gen_common_index.py @@ -6,10 +6,10 @@ ## Initialization Function | Description -------- | ----------- -[rerun.init()](initialization/#rerun.init) | Initialize the Rerun SDK ... -[rerun.set_recording_id()](initialization/#rerun.set_recording_id) | Set the recording ID ... -[rerun.connect()](initialization/#rerun.connect) | Connect to a remote Rerun Viewer on the ... -[rerun.spawn()](initialization/#rerun.spawn) | Spawn a Rerun Viewer ... +[depthai_viewer.init()](initialization/#depthai_viewer.init) | Initialize the Rerun SDK ... +[depthai_viewer.set_recording_id()](initialization/#depthai_viewer.set_recording_id) | Set the recording ID ... +[depthai_viewer.connect()](initialization/#depthai_viewer.connect) | Connect to a remote Depthai Viewer on the ... +[depthai_viewer.spawn()](initialization/#depthai_viewer.spawn) | Spawn a Depthai Viewer ... ... The Summary should look like: @@ -131,7 +131,7 @@ class Section: # This is what mkdocstrings uses under the hood search_paths = [path for path in sys.path if path] # eliminate empty path search_paths.insert(0, root.as_posix()) -rerun_pkg = griffe.load("rerun", search_paths=search_paths) +rerun_pkg = griffe.load("depthai_viewer", search_paths=search_paths) # Create the nav for this section nav = mkdocs_gen_files.Nav() @@ -157,16 +157,16 @@ def make_slug(s: str) -> str: - toc --- # Getting Started -* [Quick start](https://www.rerun.io/docs/getting-started/python) -* [Tutorial](https://www.rerun.io/docs/getting-started/logging-python) +* [Quick start](https://www.depthai_viewer.io/docs/getting-started/python) +* [Tutorial](https://www.depthai_viewer.io/docs/getting-started/logging-python) * [Examples on GitHub](https://github.com/rerun-io/rerun/tree/latest/examples/python) -* [Troubleshooting](https://www.rerun.io/docs/getting-started/troubleshooting) +* [Troubleshooting](https://www.depthai_viewer.io/docs/getting-started/troubleshooting) -There are many different ways of sending data to the Rerun Viewer depending on what you're trying +There are many different ways of sending data to the Depthai Viewer depending on what you're trying to achieve and whether the viewer is running in the same process as your code, in another process, or even as a separate web application. -Checkout [SDK Operating Modes](https://www.rerun.io/docs/reference/sdk-operating-modes) for an +Checkout [SDK Operating Modes](https://www.depthai_viewer.io/docs/reference/sdk-operating-modes) for an overview of what's possible and how. # APIs @@ -183,13 +183,13 @@ def make_slug(s: str) -> str: write_path = common_dir.joinpath(md_file) with mkdocs_gen_files.open(write_path, "w") as fd: if section.module_summary is not None: - fd.write(f"::: rerun.{section.module_summary}\n") + fd.write(f"::: depthai_viewer.{section.module_summary}\n") fd.write(" options:\n") fd.write(" show_root_heading: False\n") fd.write(" members: []\n") fd.write("----\n") for func_name in section.func_list: - fd.write(f"::: rerun.{func_name}\n") + fd.write(f"::: depthai_viewer.{func_name}\n") # Write out a table for the section in the index_file index_file.write(f"## {section.title}\n") @@ -197,7 +197,9 @@ def make_slug(s: str) -> str: index_file.write("-------- | -----------\n") for func_name in section.func_list: func = rerun_pkg[func_name] - index_file.write(f"[`rerun.{func_name}()`]({md_name}#rerun.{func_name}) | {func.docstring.lines[0]}\n") + index_file.write( + f"[`depthai_viewer.{func_name}()`]({md_name}#depthai_viewer.{func_name}) | {func.docstring.lines[0]}\n" + ) index_file.write("\n") diff --git a/rerun_py/docs/gen_package_index.py b/rerun_py/docs/gen_package_index.py index 79bc3d0b1fd7..8afb01fc8538 100644 --- a/rerun_py/docs/gen_package_index.py +++ b/rerun_py/docs/gen_package_index.py @@ -41,7 +41,7 @@ nav = mkdocs_gen_files.Nav() nav["index"] = "index.md" -for package in ["rerun", "rerun_demo"]: +for package in ["depthai_viewer", "rerun_demo"]: for path in sorted(root.joinpath(package).rglob("*.py")): rel_path = path.relative_to(root) diff --git a/rerun_py/pyproject.toml b/rerun_py/pyproject.toml index d60ea1b9f109..bcf8d7114432 100644 --- a/rerun_py/pyproject.toml +++ b/rerun_py/pyproject.toml @@ -3,6 +3,17 @@ build-backend = "maturin" requires = ["maturin>=0.14.0,<0.15"] [project] +dependencies = [ + "deprecated", + "numpy>=1.23", + "pyarrow==10.0.1", + "setuptools", + "ahrs", + "depthai", # Atm python3 -m pip install --extra-index-url https://artifacts.luxonis.com/artifactory/luxonis-python-snapshot-local/ depthai==2.21.2.0.dev0+5004cc71950e6786feb36147b7919e146f4ef8da --force-reinstall # is required + "depthai-sdk>=1.10.0", + "websockets", + "pydantic", +] classifiers = [ "Programming Language :: Rust", "Programming Language :: Python :: Implementation :: CPython", @@ -11,15 +22,14 @@ classifiers = [ "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Scientific/Engineering :: Visualization", ] -dependencies = ["deprecated", "numpy>=1.23", "pyarrow==10.0.1"] description = "The Rerun Logging SDK" keywords = ["computer-vision", "logging", "rerun"] -name = "rerun-sdk" +name = "depthai-viewer" requires-python = ">=3.8" [[project.authors]] -email = "opensource@rerun.io" -name = "Rerun.io" +email = "filip.jeretina@luxonis.com" +name = "Luxonis" [project.license] text = "MIT OR Apache-2.0" @@ -30,10 +40,10 @@ tests = ["pytest==7.1.2"] [project.urls] documentation = "https://www.rerun.io/docs" homepage = "https://www.rerun.io" -repository = "https://github.com/rerun-io/rerun" +repository = "https://github.com/luxonis/depthai-viewer" [project.scripts] -rerun = "rerun.__main__:main" +depthai-viewer = "depthai_viewer.__main__:main" [tool.black] line-length = 120 @@ -93,4 +103,4 @@ ban-relative-imports = "all" # See https://github.com/rerun-io/rerun/pull/1085 for more details include = ["rerun_sdk.pth", "rerun_sdk/rerun_demo/colmap_fiat.rrd"] locked = true -python-packages = ["rerun_sdk/rerun", "rerun_sdk/rerun_demo"] +python-packages = ["rerun_sdk/depthai_viewer", "rerun_sdk/rerun_demo"] diff --git a/rerun_py/requirements-lint.txt b/rerun_py/requirements-lint.txt index 1811d3ef6afa..6d1240634459 100644 --- a/rerun_py/requirements-lint.txt +++ b/rerun_py/requirements-lint.txt @@ -7,3 +7,4 @@ pyupgrade==2.37.3 ruff==0.0.251 types-Deprecated==1.2.9 types-requests==2.28.10 +types-setuptools==67.7.0.2 diff --git a/rerun_py/rerun_sdk/rerun/__init__.py b/rerun_py/rerun_sdk/depthai_viewer/__init__.py similarity index 88% rename from rerun_py/rerun_sdk/rerun/__init__.py rename to rerun_py/rerun_sdk/depthai_viewer/__init__.py index 799ec2ccfd50..1c1d30efd015 100644 --- a/rerun_py/rerun_sdk/rerun/__init__.py +++ b/rerun_py/rerun_sdk/depthai_viewer/__init__.py @@ -5,24 +5,28 @@ import rerun_bindings as bindings # type: ignore[attr-defined] -from rerun.log import log_cleared -from rerun.log.annotation import AnnotationInfo, ClassDescription, log_annotation_context -from rerun.log.arrow import log_arrow -from rerun.log.bounding_box import log_obb -from rerun.log.camera import log_pinhole -from rerun.log.extension_components import log_extension_components -from rerun.log.file import ImageFormat, MeshFormat, log_image_file, log_mesh_file -from rerun.log.image import log_depth_image, log_image, log_segmentation_image -from rerun.log.lines import log_line_segments, log_line_strip, log_path -from rerun.log.mesh import log_mesh, log_meshes -from rerun.log.points import log_point, log_points -from rerun.log.rects import RectFormat, log_rect, log_rects -from rerun.log.scalar import log_scalar -from rerun.log.tensor import log_tensor -from rerun.log.text import LoggingHandler, LogLevel, log_text_entry -from rerun.log.transform import log_rigid3, log_unknown_transform, log_view_coordinates -from rerun.recording import MemoryRecording -from rerun.script_helpers import script_add_args, script_setup, script_teardown +from depthai_viewer import _backend +from depthai_viewer.log import log_cleared +from depthai_viewer.log.annotation import AnnotationInfo, ClassDescription, log_annotation_context +from depthai_viewer.log.arrow import log_arrow +from depthai_viewer.log.bounding_box import log_obb +from depthai_viewer.log.camera import log_pinhole +from depthai_viewer.log.extension_components import log_extension_components +from depthai_viewer.log.file import ImageFormat, MeshFormat, log_image_file, log_mesh_file +from depthai_viewer.log.image import log_depth_image, log_image, log_segmentation_image +from depthai_viewer.log.imu import log_imu +from depthai_viewer.log.lines import log_line_segments, log_line_strip, log_path +from depthai_viewer.log.mesh import log_mesh, log_meshes +from depthai_viewer.log.pipeline_graph import log_pipeline_graph +from depthai_viewer.log.points import log_point, log_points +from depthai_viewer.log.rects import RectFormat, log_rect, log_rects +from depthai_viewer.log.scalar import log_scalar +from depthai_viewer.log.tensor import log_tensor +from depthai_viewer.log.text import LoggingHandler, LogLevel, log_text_entry +from depthai_viewer.log.transform import log_rigid3, log_unknown_transform, log_view_coordinates +from depthai_viewer.log.xlink_stats import log_xlink_stats +from depthai_viewer.recording import MemoryRecording +from depthai_viewer.script_helpers import script_add_args, script_setup, script_teardown __all__ = [ "AnnotationInfo", @@ -39,6 +43,7 @@ "log_extension_components", "log_image_file", "log_image", + "log_pipeline_graph", "log_line_segments", "log_line_strip", "log_mesh_file", @@ -65,6 +70,9 @@ "script_add_args", "script_setup", "script_teardown", + "log_imu", + "log_xlink_stats", + "_backend", ] @@ -145,7 +153,7 @@ def init(application_id: str, spawn: bool = False, default_enabled: bool = True, and another doing camera calibration, you could have `rerun.init("object_detector")` and `rerun.init("calibrator")`. spawn : bool - Spawn a Rerun Viewer and stream logging data to it. + Spawn a Depthai Viewer and stream logging data to it. Short for calling `spawn` separately. If you don't call this, log events will be buffered indefinitely until you call either `connect`, `show`, or `save` @@ -181,7 +189,8 @@ def init(application_id: str, spawn: bool = False, default_enabled: bool = True, stack = inspect.stack() for frame in stack[:MAX_FRAMES]: filename = frame[FRAME_FILENAME_INDEX] - path = pathlib.Path(str(filename)).resolve() # normalize before comparison! + # normalize before comparison! + path = pathlib.Path(str(filename)).resolve() if "rerun/examples" in str(path): application_path = path except Exception: @@ -264,9 +273,9 @@ def set_strict_mode(strict_mode: bool) -> None: def connect(addr: Optional[str] = None) -> None: """ - Connect to a remote Rerun Viewer on the given ip:port. + Connect to a remote Depthai Viewer on the given ip:port. - Requires that you first start a Rerun Viewer, e.g. with 'python -m rerun' + Requires that you first start a Depthai Viewer, e.g. with 'python -m rerun' This function returns immediately. @@ -289,7 +298,7 @@ def connect(addr: Optional[str] = None) -> None: def spawn(port: int = 9876, connect: bool = True) -> None: """ - Spawn a Rerun Viewer, listening on the given port. + Spawn a Depthai Viewer, listening on the given port. This is often the easiest and best way to use Rerun. Just call this once at the start of your program. @@ -325,7 +334,9 @@ def spawn(port: int = 9876, connect: bool = True) -> None: # start_new_session=True ensures the spawned process does NOT die when # we hit ctrl-c in the terminal running the parent Python process. - subprocess.Popen([python_executable, "-m", "rerun", "--port", str(port)], env=new_env, start_new_session=True) + subprocess.Popen( + [python_executable, "-m", "depthai_viewer", "--port", str(port)], env=new_env, start_new_session=True + ) # TODO(emilk): figure out a way to postpone connecting until the rerun viewer is listening. # For example, wait until it prints "Hosting a SDK server over TCP at …" diff --git a/rerun_py/rerun_sdk/rerun/__main__.py b/rerun_py/rerun_sdk/depthai_viewer/__main__.py similarity index 74% rename from rerun_py/rerun_sdk/rerun/__main__.py rename to rerun_py/rerun_sdk/depthai_viewer/__main__.py index 8213af58ec28..32f4965ff05e 100644 --- a/rerun_py/rerun_sdk/rerun/__main__.py +++ b/rerun_py/rerun_sdk/depthai_viewer/__main__.py @@ -2,7 +2,7 @@ import sys -from rerun import bindings, unregister_shutdown # type: ignore[attr-defined] +from depthai_viewer import bindings, unregister_shutdown # type: ignore[attr-defined] def main() -> None: diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/.gitignore b/rerun_py/rerun_sdk/depthai_viewer/_backend/.gitignore new file mode 100644 index 000000000000..32b40b2abc94 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/.gitignore @@ -0,0 +1,5 @@ +.venv +.vscode +__pycache__ +rs_try +dev_experiments diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/README.md b/rerun_py/rerun_sdk/depthai_viewer/_backend/README.md new file mode 100644 index 000000000000..50b0c2f6ca03 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/README.md @@ -0,0 +1,31 @@ +# Depthai Viewer backend + +Features: +- [x] color camera video + left/right camera video +- [x] color stereo depth stream +- [x] 3D point cloud +- [x] IMU values + charts so that users can check if it works properly +- [x] Discovery of available OAK cameras + switching between cameras (only 1 can be used at a time) +- [ ] Settings for cameras - various filters, IR, Laser projector - so that user can quickly check the performance of stereo + cameras +- [x] Dropdown list of few most interesting neural models - yolo, hand detection, ... + - [x] YOLO + - [x] Face detection + - [x] Age gender detection + - [ ] Human Pose +- [ ] Bandwidth statistics +Extra: +- [ ] recording/replay +- [ ] camera calibration - similar to Lukasz' calibration app +- [ ] detailed information about cameras and firmware upgrade +- [ ] visualization for VIO/SLAM when available - similar to Zedfu app + + +## Develop + +```sh +python3 install_requirements.py +``` + +```sh +source .venv/bin/activate +``` diff --git a/rerun_py/rerun_sdk/rerun/py.typed b/rerun_py/rerun_sdk/depthai_viewer/_backend/__init__.py similarity index 100% rename from rerun_py/rerun_sdk/rerun/py.typed rename to rerun_py/rerun_sdk/depthai_viewer/_backend/__init__.py diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/classification_labels.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/classification_labels.py new file mode 100644 index 000000000000..7d8f6b7e1958 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/classification_labels.py @@ -0,0 +1,107 @@ +MOBILENET_LABELS = [ + "background", + "aeroplane", + "bicycle", + "bird", + "boat", + "bottle", + "bus", + "car", + "cat", + "chair", + "cow", + "diningtable", + "dog", + "horse", + "motorbike", + "person", + "pottedplant", + "sheep", + "sofa", + "train", + "tvmonitor", +] + + +YOLO_TINY_LABELS = [ + "person", + "bicycle", + "car", + "motorcycle", + "airplane", + "bus", + "train", + "truck", + "boat", + "trafficlight", + "firehydrant", + "stopsign", + "parkingmeter", + "bench", + "bird", + "cat", + "dog", + "horse", + "sheep", + "cow", + "elephant", + "bear", + "zebra", + "giraffe", + "backpack", + "umbrella", + "handbag", + "tie", + "suitcase", + "frisbee", + "skis", + "snowboard", + "sportsball", + "kite", + "baseballbat", + "baseballglove", + "skateboard", + "surfboard", + "tennisracket", + "bottle", + "wineglass", + "cup", + "fork", + "knife", + "spoon", + "bowl", + "banana", + "apple", + "sandwich", + "orange", + "broccoli", + "carrot", + "hotdog", + "pizza", + "donut", + "cake", + "chair", + "couch", + "pottedplant", + "bed", + "diningtable", + "toilet", + "tv", + "laptop", + "mouse", + "remote", + "keyboard", + "cellphone", + "microwave", + "oven", + "toaster", + "sink", + "refrigerator", + "book", + "clock", + "vase", + "scissors", + "teddybear", + "hairdrier", + "toothbrush", +] diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/config_api.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/config_api.py new file mode 100644 index 000000000000..105affef27ba --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/config_api.py @@ -0,0 +1,186 @@ +import asyncio +import json +from enum import Enum +from multiprocessing import Queue +from queue import Empty as QueueEmptyException +from signal import SIGINT, signal +from typing import Any, Dict, Optional, Tuple + +import depthai as dai +import websockets +from websockets.server import WebSocketServerProtocol + +from depthai_viewer._backend.device_configuration import PipelineConfiguration +from depthai_viewer._backend.store import Action +from depthai_viewer._backend.topic import Topic + +signal(SIGINT, lambda *args, **kwargs: exit(0)) + +# Definitions for linting +# send actions to back +dispatch_action_queue: Queue # type: ignore[type-arg] + +# bool indicating action success +result_queue: Queue # type: ignore[type-arg] + +# Send messages from backend to frontend without the frontend sending a message first +send_message_queue: Queue # type: ignore[type-arg] + + +def dispatch_action(action: Action, **kwargs) -> Tuple[bool, Dict[str, Any]]: # type: ignore[no-untyped-def] + """ + Dispatches an action that will be executed by store.py. + + Returns: (success: bool, result: Dict[str, Any]). + """ + dispatch_action_queue.put((action, kwargs)) + return result_queue.get() # type: ignore[no-any-return] + + +class MessageType: + SUBSCRIPTIONS = "Subscriptions" # Get or set subscriptions + PIPELINE = "Pipeline" # Get or Set pipeline + DEVICES = "Devices" # Get device list + DEVICE = "Device" # Get or set device + ERROR = "Error" # Error message + + +class ErrorAction(Enum): + NONE = None + FULL_RESET = "FullReset" + + +def error(message: str, action: ErrorAction) -> str: + """Create an error message to send via ws.""" + return json.dumps({"type": MessageType.ERROR, "data": {"action": action, "message": message}}) + + +async def ws_api(websocket: WebSocketServerProtocol) -> None: + while True: + raw_message = None + try: + raw_message = await asyncio.wait_for(websocket.recv(), 1) + except asyncio.TimeoutError: + pass + except websockets.exceptions.ConnectionClosed: + success, _ = dispatch_action(Action.RESET) # type: ignore[assignment] + if success: + return + raise Exception("Couldn't reset backend after websocket disconnect!") + + if raw_message: + try: + message: Dict[str, Any] = json.loads(raw_message) + except json.JSONDecodeError: + print("Failed to parse message: ", message) + continue + message_type = message.get("type", None) + if not message_type: + print("Missing message type") + continue + print("Got message: ", message) + if message_type == MessageType.SUBSCRIPTIONS: + data = message.get("data", {}) + subscriptions = [Topic.create(topic_name) for topic_name in data.get(MessageType.SUBSCRIPTIONS, [])] + dispatch_action(Action.SET_SUBSCRIPTIONS, subscriptions=subscriptions) + print("Subscriptions: ", subscriptions) + active_subscriptions = [ + topic.name # type: ignore[attr-defined] + for topic in dispatch_action(Action.GET_SUBSCRIPTIONS) + if topic + ] + await websocket.send(json.dumps({"type": MessageType.SUBSCRIPTIONS, "data": active_subscriptions})) + elif message_type == MessageType.PIPELINE: + data = message.get("data", {}) + pipeline_config_json, runtime_only = data.get("Pipeline", ({}, False)) + pipeline_config = PipelineConfiguration(**pipeline_config_json) + print("Pipeline config: ", pipeline_config) + + success, result = dispatch_action( + Action.UPDATE_PIPELINE, pipeline_config=pipeline_config, runtime_only=runtime_only + ) + if runtime_only: + # Send a full reset if setting a runtime config fails. + # Don't send pipeline config to save bandwidth. + if not success: + await websocket.send(error("Failed to set runtime config", ErrorAction.FULL_RESET)) + continue + if success: + active_config: Optional[PipelineConfiguration] = dispatch_action( + Action.GET_PIPELINE + ) # type: ignore[assignment] + print("Active config: ", active_config) + await websocket.send( + json.dumps( + { + "type": MessageType.PIPELINE, + "data": (active_config.to_json(), False) if active_config is not None else None, + } + ) + ) + else: + await websocket.send(error("Unknown error", ErrorAction.FULL_RESET)) + elif message_type == MessageType.DEVICES: + await websocket.send( + json.dumps( + { + "type": MessageType.DEVICES, + "data": [ + d.getMxId() for d in dai.Device.getAllAvailableDevices() # type: ignore[call-arg] + ], + } + ) + ) + + elif message_type == MessageType.DEVICE: + data = message.get("data", {}) + device_repr = data.get("Device", {}) + device_id = device_repr.get("id", None) + if device_id is None: + print("Missing device id") + continue + success, result = dispatch_action(Action.SELECT_DEVICE, device_id=device_id) + if success: + print("Selected device properties: ", result.get("device_properties", None)) + await websocket.send( + json.dumps({"type": MessageType.DEVICE, "data": result.get("device_properties", {})}) + ) + else: + await websocket.send(error(result.get("message", "Unknown error"), ErrorAction.FULL_RESET)) + + else: + print("Unknown message type: ", message_type) + continue + send_message = None + try: + send_message = send_message_queue.get(timeout=0.01) + except QueueEmptyException: + pass + if send_message: + print("Sending message: ", send_message) + await websocket.send(send_message) + + +async def main() -> None: + async with websockets.serve(ws_api, "localhost", 9001): # type: ignore[attr-defined] + await asyncio.Future() # run forever + + +def start_api( + _dispatch_action_queue: Queue, _result_queue: Queue, _send_message_queue: Queue # type: ignore[type-arg] +) -> None: + """ + Starts the websocket API. + + _dispatch_action_queue: Queue to send actions to store.py + _result_queue: Queue to get results from store.py + _send_message_queue: Queue to send messages to frontend. + """ + global dispatch_action_queue + dispatch_action_queue = _dispatch_action_queue + global result_queue + result_queue = _result_queue + global send_message_queue + send_message_queue = _send_message_queue + + asyncio.run(main()) diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/depth.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/depth.py new file mode 100644 index 000000000000..c10d202ed20d --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/depth.py @@ -0,0 +1,71 @@ +ctrl = { + "algorithm_control": { + "align": "RECTIFIED_RIGHT", # | 'RECTIFIED_LEFT' | 'CENTER' + "unit": "METER", # | 'CENTIMETER' | 'MILLIMETER' | 'INCH' | 'FOOT' | 'CUSTOM' + "unit_multiplier": 1000, # Only if 'unit' is 'CUSTOM' + "lr_check": True, # Enable left-right check + "extended": True, # Enable extended disparity + "subpixel": True, # Enable subpixel disparity + "lr_check_threshold": 10, # Left-right check threshold + "subpixel_bits": 3, # 3 | 4 | 5 + "disparity_shift": 0, # Disparity shift + "invalidate_edge_pixels": 0, # Number of pixels to invalidate at the edge of the image + }, + "postprocessing": { + "median": 5, # 0 | 3 | 5 | 7 + "bilateral_sigma": 0, # Sigma value for bilateral filter + "spatial": { + "enable": True, # Enable spatial denoise + "hole_filling": 2, # Hole filling radius + "alpha": 0.5, # Alpha factor in an exponential moving average + "delta": 0, # Step-size boundary + "iterations": 1, # Number of iterations + }, + "temporal": { + "enable": False, # Enable or disable temporal denoise + "persistency_mode": 3, # Persistency mode (use corresponding integer value for the enum member) + "alpha": 0.4, # Alpha factor in an exponential moving average + "delta": 0, # Step-size boundary + }, + "threshold": { + "min_range": 0, # Minimum range in depth units + "max_range": 65535, # Maximum range in depth units + }, + "brightness": { + "min": 0, # Minimum pixel brightness + "max": 256, # Maximum pixel brightness + }, + "speckle": { + "enable": False, # Enable or disable the speckle filter + "range": 50, # Speckle search range + }, + "decimation": { + "factor": 1, # Decimation factor (1, 2, 3, or 4) + "mode": 0, # Decimation algorithm type (use corresponding integer value for the enum member) + }, + }, + "census_transform": { + "kernel_size": "AUTO", # | 'KERNEL_5x5' | 'KERNEL_7x7' | 'KERNEL_7x9' + "kernel_mask": 0, # Census transform mask + "enable_mean_mode": True, # Enable mean mode + "threshold": 0, # Census transform comparison threshold value + }, + "cost_matching": { + "disparity_width": "DISPARITY_64", # or 'DISPARITY_96' + "enable_companding": False, # Enable disparity companding using sparse matching + "confidence_threshold": 245, # Confidence threshold for accepted disparities + "linear_equation_parameters": { + "alpha": 0, + "beta": 2, + "threshold": 127, + }, + }, + "cost_aggregation": { + "division_factor": 1, # Division factor for cost calculation linear equation parameters + "horizontal_penalty_cost_p1": 250, # Horizontal P1 penalty cost parameter + "horizontal_penalty_cost_p2": 500, # Horizontal P2 penalty cost parameter + "vertical_penalty_cost_p1": 250, # Vertical P1 penalty cost parameter + "vertical_penalty_cost_p2": 500, # Vertical P2 penalty cost parameter + }, + "reset": False, # Reset all controls to default +} diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/device_configuration.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/device_configuration.py new file mode 100644 index 000000000000..0d2e9614335f --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/device_configuration.py @@ -0,0 +1,190 @@ +from typing import Any, Dict, Optional + +import depthai as dai +from depthai_sdk import Previews as QueueNames +from pydantic import BaseModel + + +class ColorCameraConfiguration(BaseModel): # type: ignore[misc] + fps: Optional[int] = 30 + resolution: Optional[ + dai.ColorCameraProperties.SensorResolution + ] = dai.ColorCameraProperties.SensorResolution.THE_1080_P + board_socket: Optional[dai.CameraBoardSocket] = dai.CameraBoardSocket.RGB + out_preview: bool = False + xout_still: bool = False + xout_video: bool = True + input_control: bool = False + + class Config: + arbitrary_types_allowed = True + # Doesnt work atm + json_encoders = { + Optional[dai.MonoCameraProperties.SensorResolution]: lambda v: v.name, + dai.CameraBoardSocket: lambda v: v.name, + } + + def __init__(self, **v) -> None: # type: ignore[no-untyped-def] + if v.get("resolution"): + v["resolution"] = getattr(dai.ColorCameraProperties.SensorResolution, v["resolution"]) + if v.get("board_socket"): + v["board_socket"] = getattr(dai.CameraBoardSocket, v["board_socket"]) + return super().__init__(**v) # type: ignore[no-any-return] + + @property + # Make this select the queue based on ui, also probably not just one queue + def out_queue_name(self) -> Optional[str]: + prefix: str = QueueNames.color.name + if self.out_preview: + return prefix + "_preview" + if self.xout_still: + return prefix + "_still" + if self.xout_video: + return prefix + "_video" + return None + + +class MonoCameraConfiguration(BaseModel): # type: ignore[misc] + fps: Optional[int] = 30 + resolution: Optional[ + dai.MonoCameraProperties.SensorResolution + ] = dai.MonoCameraProperties.SensorResolution.THE_400_P + board_socket: Optional[dai.CameraBoardSocket] = dai.CameraBoardSocket.LEFT + xout: bool = False # Depth queue fails if I create this queue! + input_control: bool = False + + class Config: + arbitrary_types_allowed = True + # Doesnt work atm + json_encoders = { + Optional[dai.MonoCameraProperties.SensorResolution]: lambda v: v.name, + dai.CameraBoardSocket: lambda v: v.name, + } + + def __init__(self, **v) -> None: # type: ignore[no-untyped-def] + if v.get("resolution"): + v["resolution"] = getattr(dai.MonoCameraProperties.SensorResolution, v["resolution"]) + if v.get("board_socket"): + v["board_socket"] = getattr(dai.CameraBoardSocket, v["board_socket"]) + return super().__init__(**v) # type: ignore[no-any-return] + + @property + def out_queue_name(self) -> str: + return "left" if self.board_socket == dai.CameraBoardSocket.LEFT else "right" + + @classmethod + def create_left(cls, **kwargs) -> "MonoCameraConfiguration": # type: ignore[no-untyped-def] + return cls(board_socket="LEFT", **kwargs) + + @classmethod + def create_right(cls, **kwargs) -> "MonoCameraConfiguration": # type: ignore[no-untyped-def] + return cls(board_socket="RIGHT", **kwargs) + + +# class PointcloudConfiguration(BaseModel): +# enabled: bool = True + + +class DepthConfiguration(BaseModel): # type: ignore[misc] + median: Optional[dai.MedianFilter] = dai.MedianFilter.KERNEL_7x7 + lr_check: Optional[bool] = True + lrc_threshold: int = 5 # 0..10 + extended_disparity: Optional[bool] = False + subpixel_disparity: Optional[bool] = True + align: Optional[dai.CameraBoardSocket] = dai.CameraBoardSocket.CENTER + sigma: int = 0 # 0..65535 + # pointcloud: PointcloudConfiguration | None = None + confidence: int = 230 + + class Config: + arbitrary_types_allowed = True + + def __init__(self, **v) -> None: # type: ignore[no-untyped-def] + if v.get("median"): + v["median"] = getattr(dai.MedianFilter, v["median"]) + if v.get("align"): + v["align"] = getattr(dai.CameraBoardSocket, v["align"]) + + return super().__init__(**v) # type: ignore[no-any-return] + + def to_runtime_controls(self) -> Dict[str, Any]: + return { + "algorithm_control": { + "align": "RECTIFIED_LEFT" + if self.align == dai.CameraBoardSocket.LEFT + else "RECTIFIED_RIGHT" + if self.align == dai.CameraBoardSocket.RIGHT + else "CENTER", + "lr_check": self.lr_check, + "lrc_check_threshold": self.lrc_threshold, + "extended": self.extended_disparity, + "subpixel": self.subpixel_disparity, + }, + "postprocessing": { + "median": { + dai.MedianFilter.MEDIAN_OFF: 0, + dai.MedianFilter.KERNEL_3x3: 3, + dai.MedianFilter.KERNEL_5x5: 5, + dai.MedianFilter.KERNEL_7x7: 7, + }[self.median] + if self.median + else 0, + "bilateral_sigma": self.sigma, + }, + "cost_matching": { + "confidence_threshold": self.confidence, + }, + } + + @property + def out_queue_name(self) -> str: + return str(QueueNames.depthRaw.name) + + +class AiModelConfiguration(BaseModel): # type: ignore[misc] + display_name: str = "Yolo V8" + path: str = "yolov8n_coco_640x352" + + +class ImuConfiguration(BaseModel): # type: ignore[misc] + report_rate: int = 100 + batch_report_threshold: int = 5 + + +class PipelineConfiguration(BaseModel): # type: ignore[misc] + color_camera: ColorCameraConfiguration = ColorCameraConfiguration() + left_camera: MonoCameraConfiguration = MonoCameraConfiguration.create_left() + right_camera: MonoCameraConfiguration = MonoCameraConfiguration.create_right() + depth: Optional[DepthConfiguration] = DepthConfiguration() + ai_model: Optional[AiModelConfiguration] = AiModelConfiguration() + imu: ImuConfiguration = ImuConfiguration() + + def to_json(self) -> Dict[str, Any]: + as_dict = self.dict() + return self._fix_depthai_types(as_dict) + + def _fix_depthai_types(self, as_dict: Dict[str, Any]) -> Dict[str, Any]: + """ATM Config.json_encoders doesn't work, so we manually fix convert the depthai types to strings here.""" + if as_dict.get("color_camera"): + as_dict["color_camera"] = self._fix_camera(as_dict["color_camera"]) + if as_dict.get("left_camera"): + as_dict["left_camera"] = self._fix_camera(as_dict["left_camera"]) + if as_dict.get("right_camera"): + as_dict["right_camera"] = self._fix_camera(as_dict["right_camera"]) + if as_dict.get("depth"): + as_dict["depth"] = self._fix_depth(as_dict["depth"]) + return as_dict + + def _fix_depth(self, as_dict: Dict[str, Any]) -> Dict[str, Any]: + if as_dict.get("align"): + as_dict["align"] = as_dict["align"].name + if as_dict.get("median"): + as_dict["median"] = as_dict["median"].name + return as_dict + + def _fix_camera(self, as_dict: Dict[str, Any]) -> Dict[str, Any]: + if as_dict.get("resolution"): + as_dict["resolution"] = as_dict["resolution"].name + if as_dict.get("board_socket"): + as_dict["board_socket"] = as_dict["board_socket"].name + return as_dict diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/main.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/main.py new file mode 100644 index 000000000000..727c4af5b10c --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/main.py @@ -0,0 +1,360 @@ +import json +import threading +import time +from queue import Empty as QueueEmptyException +from queue import Queue +from typing import Any, Dict, Optional, Tuple, Union + +import depthai as dai +import depthai_sdk +import numpy as np +import pkg_resources +from depthai_sdk import OakCamera +from depthai_sdk.components import CameraComponent, NNComponent, StereoComponent +from numpy.typing import NDArray + +import depthai_viewer as viewer +from depthai_viewer._backend.config_api import start_api +from depthai_viewer._backend.device_configuration import PipelineConfiguration +from depthai_viewer._backend.sdk_callbacks import SdkCallbacks +from depthai_viewer._backend.store import Store + +viewer.init("Depthai Viewer") +viewer.connect() + +color_wh_to_enum = { + (1280, 720): dai.ColorCameraProperties.SensorResolution.THE_720_P, + (1280, 800): dai.ColorCameraProperties.SensorResolution.THE_800_P, + (1920, 1080): dai.ColorCameraProperties.SensorResolution.THE_1080_P, + (3840, 2160): dai.ColorCameraProperties.SensorResolution.THE_4_K, + (4056, 3040): dai.ColorCameraProperties.SensorResolution.THE_12_MP, + (1440, 1080): dai.ColorCameraProperties.SensorResolution.THE_1440X1080, + (5312, 6000): dai.ColorCameraProperties.SensorResolution.THE_5312X6000, + # TODO(filip): Add other resolutions +} + +mono_wh_to_enum = { + (640, 400): dai.MonoCameraProperties.SensorResolution.THE_400_P, + (640, 480): dai.MonoCameraProperties.SensorResolution.THE_480_P, + (1280, 720): dai.MonoCameraProperties.SensorResolution.THE_720_P, + (1280, 800): dai.MonoCameraProperties.SensorResolution.THE_800_P, + (1920, 1200): dai.MonoCameraProperties.SensorResolution.THE_1200_P, +} + + +class SelectedDevice: + id: str + intrinsic_matrix: Dict[Tuple[int, int], NDArray[np.float32]] = {} + calibration_data: Optional[dai.CalibrationHandler] = None + use_encoding: bool = False + _time_of_last_xlink_update: int = 0 + + _color: CameraComponent = None + _left: CameraComponent = None + _right: CameraComponent = None + _stereo: StereoComponent = None + _nnet: NNComponent = None + # _pc: PointcloudComponent = None + + oak_cam: OakCamera = None + + def __init__(self, device_id: str): + self.id = device_id + self.oak_cam = OakCamera(self.id) + print("Oak cam: ", self.oak_cam) + + def get_intrinsic_matrix(self, width: int, height: int) -> NDArray[np.float32]: + if self.intrinsic_matrix.get((width, height)) is not None: + return self.intrinsic_matrix.get((width, height)) # type: ignore[return-value] + if self.calibration_data is None: + raise Exception("Missing calibration data!") + M_right = self.calibration_data.getCameraIntrinsics( # type: ignore[union-attr] + dai.CameraBoardSocket.RIGHT, dai.Size2f(width, height) + ) + self.intrinsic_matrix[(width, height)] = np.array(M_right).reshape(3, 3) + return self.intrinsic_matrix[(width, height)] + + def get_device_properties(self) -> Dict[str, Any]: + dai_props = self.oak_cam.device.getConnectedCameraFeatures() + device_properties = { + "id": self.id, + "supported_color_resolutions": [], + "supported_left_mono_resolutions": [], + "supported_right_mono_resolutions": [], + } + for cam in dai_props: + resolutions_key = "supported_left_mono_resolutions" + if cam.socket == dai.CameraBoardSocket.RGB: + resolutions_key = "supported_color_resolutions" + elif cam.socket == dai.CameraBoardSocket.RIGHT: + resolutions_key = "supported_right_mono_resolutions" + for config in cam.configs: + wh = (config.width, config.height) + if wh not in device_properties[resolutions_key]: # type: ignore[comparison-overlap] + device_properties[resolutions_key].append( # type: ignore[attr-defined] + (config.width, config.height) + ) + device_properties["supported_color_resolutions"] = list( + map( + lambda x: color_wh_to_enum[x].name, # type: ignore[index, no-any-return] + sorted(device_properties["supported_color_resolutions"], key=lambda x: int(x[0]) * int(x[1])), + ) + ) + device_properties["supported_left_mono_resolutions"] = list( + map( + lambda x: color_wh_to_enum[x].name, # type: ignore[index, no-any-return] + sorted(device_properties["supported_left_mono_resolutions"], key=lambda x: int(x[0]) * int(x[1])), + ) + ) + device_properties["supported_right_mono_resolutions"] = list( + map( + lambda x: color_wh_to_enum[x].name, # type: ignore[index, no-any-return] + sorted(device_properties["supported_right_mono_resolutions"], key=lambda x: int(x[0]) * int(x[1])), + ) + ) + return device_properties + + def update_pipeline( + self, config: PipelineConfiguration, runtime_only: bool, callbacks: "SdkCallbacks" + ) -> Tuple[bool, Dict[str, str]]: + if self.oak_cam.running(): + if runtime_only: + if config.depth is not None: + return True, self._stereo.control.send_controls(config.depth.to_runtime_controls()) + return False, {"message": "Depth is not enabled, can't send runtime controls!"} + print("Cam running, closing...") + self.oak_cam.device.close() + self.oak_cam = None + # Check if the device is available, timeout after 10 seconds + timeout_start = time.time() + while time.time() - timeout_start < 10: + available_devices = [ + device.getMxId() for device in dai.Device.getAllAvailableDevices() # type: ignore[call-arg] + ] + if self.id in available_devices: + break + try: + self.oak_cam = OakCamera(self.id) + except RuntimeError as e: + print("Failed to create oak camera") + print(e) + self.oak_cam = None + return False, {"message": "Failed to create oak camera"} + + self.use_encoding = self.oak_cam.device.getDeviceInfo().protocol == dai.XLinkProtocol.X_LINK_TCP_IP + if self.use_encoding: + print("Connected device is PoE: Using encoding...") + else: + print("Connected device is USB: Not using encoding...") + if config.color_camera is not None: + print("Creating color camera") + self._color = self.oak_cam.create_camera( + "color", config.color_camera.resolution, config.color_camera.fps, name="color", encode=self.use_encoding + ) + if config.color_camera.xout_video: + self.oak_cam.callback(self._color, callbacks.on_color_frame, enable_visualizer=self.use_encoding) + if config.left_camera is not None: + print("Creating left camera") + self._left = self.oak_cam.create_camera( + "left", config.left_camera.resolution, config.left_camera.fps, name="left", encode=self.use_encoding + ) + if config.left_camera.xout: + self.oak_cam.callback(self._left, callbacks.on_left_frame, enable_visualizer=self.use_encoding) + if config.right_camera is not None: + print("Creating right camera") + self._right = self.oak_cam.create_camera( + "right", config.right_camera.resolution, config.right_camera.fps, name="right", encode=self.use_encoding + ) + if config.right_camera.xout: + self.oak_cam.callback(self._right, callbacks.on_right_frame, enable_visualizer=self.use_encoding) + if config.depth: + print("Creating depth") + self._stereo = self.oak_cam.create_stereo(left=self._left, right=self._right, name="depth") + + # We used to be able to pass in the board socket to align to, but this was removed in depthai 1.10.0 + align = config.depth.align + if pkg_resources.parse_version(depthai_sdk.__version__) >= pkg_resources.parse_version("1.10.0"): + align = ( + self._left + if config.depth.align == dai.CameraBoardSocket.LEFT + else self._right + if config.depth.align == dai.CameraBoardSocket.RIGHT + else self._color + ) + self._stereo.config_stereo( + lr_check=config.depth.lr_check, + subpixel=config.depth.subpixel_disparity, + confidence=config.depth.confidence, + align=align, + lr_check_threshold=config.depth.lrc_threshold, + median=config.depth.median, + ) + self.oak_cam.callback(self._stereo, callbacks.on_stereo_frame) + # if config.depth.pointcloud and config.depth.pointcloud.enabled: + # self._pc = self.oak_cam.create_pointcloud(stereo=self._stereo, colorize=self._color) + # self.oak_cam.callback(self._pc, callbacks.on_pointcloud) + + if config.imu is not None: + print("Creating IMU") + imu = self.oak_cam.create_imu() + sensors = [ + dai.IMUSensor.ACCELEROMETER_RAW, + dai.IMUSensor.GYROSCOPE_RAW, + ] + if "BNO" in self.oak_cam.device.getConnectedIMU(): + sensors.append(dai.IMUSensor.MAGNETOMETER_CALIBRATED) + imu.config_imu( + sensors, report_rate=config.imu.report_rate, batch_report_threshold=config.imu.batch_report_threshold + ) + self.oak_cam.callback(imu, callbacks.on_imu) + + if config.ai_model and config.ai_model.path: + if config.ai_model.path == "age-gender-recognition-retail-0013": + face_detection = self.oak_cam.create_nn("face-detection-retail-0004", self._color) + self._nnet = self.oak_cam.create_nn("age-gender-recognition-retail-0013", input=face_detection) + self.oak_cam.callback(self._nnet, callbacks.on_age_gender_packet) + elif config.ai_model.path == "mobilenet-ssd": + self._nnet = self.oak_cam.create_nn( + config.ai_model.path, + self._color, + ) + self.oak_cam.callback(self._nnet, callbacks.on_mobilenet_ssd_packet) + else: + self._nnet = self.oak_cam.create_nn(config.ai_model.path, self._color) + callback = callbacks.on_detections + if config.ai_model.path == "yolov8n_coco_640x352": + callback = callbacks.on_yolo_packet + self.oak_cam.callback( + self._nnet, callback, True + ) # in depthai-sdk=1.10.0 nnet callbacks don't work without visualizer enabled + try: + self.oak_cam.start(blocking=False) + except RuntimeError as e: + print("Couldn't start pipeline: ", e) + return False, {"message": "Couldn't start pipeline"} + running = self.oak_cam.running() + if running: + self.oak_cam.poll() + self.calibration_data = self.oak_cam.device.readCalibration() + self.intrinsic_matrix = {} + return running, {"message": "Pipeline started" if running else "Couldn't start pipeline"} + + def update(self) -> None: + self.oak_cam.poll() + if time.time_ns() - self._time_of_last_xlink_update >= 16e6: + self._time_of_last_xlink_update = time.time_ns() + if hasattr(self.oak_cam.device, "getProfilingData"): # Only on latest develop + xlink_stats = self.oak_cam.device.getProfilingData() + viewer.log_xlink_stats(xlink_stats.numBytesWritten, xlink_stats.numBytesRead) + + +class DepthaiViewerBack: + _device: Optional[SelectedDevice] = None + + # Queues for communicating with the API process + action_queue: Queue[Any] + result_queue: Queue[Any] + send_message_queue: Queue[Any] + + # Sdk callbacks for handling data from the device and sending it to the frontend + sdk_callbacks: SdkCallbacks + + def __init__(self, compression: bool = False) -> None: + self.action_queue = Queue() + self.result_queue = Queue() + self.send_message_queue = Queue() + + self.store = Store() + self.store.on_update_pipeline = self.update_pipeline + self.store.on_select_device = self.select_device + self.store.on_reset = self.on_reset + + self.api_process = threading.Thread( + target=start_api, args=(self.action_queue, self.result_queue, self.send_message_queue) + ) + self.api_process.start() + + self.sdk_callbacks = SdkCallbacks(self.store) + self.run() + + def set_device(self, device: Optional[SelectedDevice] = None) -> None: + self._device = device + if device: + self.sdk_callbacks.set_camera_intrinsics_getter(device.get_intrinsic_matrix) + + def on_reset(self) -> Tuple[bool, Dict[str, str]]: + print("Resetting...") + if self._device: + print("Closing device...") + self._device.oak_cam.device.close() + self._device.oak_cam.__exit__(None, None, None) + self._device.oak_cam = None + self.set_device(None) + print("Done") + return True, {"message": "Reset successful"} + + def select_device(self, device_id: str) -> Tuple[bool, Dict[str, Union[str, Any]]]: + print("Selecting device: ", device_id) + if self._device: + self.on_reset() + if device_id == "": + return True, {"message": "Successfully unselected device", "device_properties": {}} + try: + self.set_device(SelectedDevice(device_id)) + except RuntimeError as e: + print("Failed to select device:", e) + return False, { + "message": str(e) + ", Try plugging in the device on a different port.", + "device_properties": {}, + } + try: + if self._device is not None: + device_properties = self._device.get_device_properties() + return True, {"message:": "Device selected successfully", "device_properties": device_properties} + return False, {"message": "CCouldn't select device", "device_properties": {}} + except RuntimeError as e: + print("Failed to get device properties:", e) + self.on_reset() + print("Restarting backend...") + # For now exit the backend, the frontend will restart it + # (TODO(filip): Why does "Device already closed or disconnected: Input/output error happen") + exit(-1) + # return False, {"message": "Failed to get device properties", "device_properties": {}} + + def update_pipeline(self, runtime_only: bool) -> Tuple[bool, Dict[str, str]]: + if not self._device: + print("No device selected, can't update pipeline!") + return False, {"message": "No device selected, can't update pipeline!"} + print("Updating pipeline...") + started, message = False, {"message": "Couldn't start pipeline"} + if self.store.pipeline_config is not None: + started, message = self._device.update_pipeline( + self.store.pipeline_config, runtime_only, callbacks=self.sdk_callbacks + ) + if not started: + self.set_device(None) + return started, message + + def run(self) -> None: + """Handles ws messages and polls OakCam.""" + while True: + try: + action, kwargs = self.action_queue.get(timeout=0.0001) + print("Handling action: ", action) + self.result_queue.put(self.store.handle_action(action, **kwargs)) + except QueueEmptyException: + pass + + if self._device and self._device.oak_cam: + self._device.update() + if self._device.oak_cam.device.isClosed(): + # TODO(filip): Typehint the messages properly + self.on_reset() + self.send_message_queue.put( + json.dumps({"type": "Error", "data": {"action": "FullReset", "message": "Device disconnected"}}) + ) + + +if __name__ == "__main__": + viewer.spawn(connect=True) + DepthaiViewerBack() diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/py.typed b/rerun_py/rerun_sdk/depthai_viewer/_backend/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/pyproject.toml b/rerun_py/rerun_sdk/depthai_viewer/_backend/pyproject.toml new file mode 100644 index 000000000000..49ad3a667c76 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "depthai_viewer_backend" +version = "0.0.1" +authors = [{ name = "Filip Jeretina", email = "filip.jeretina@luxonis.com" }] +description = "DepthAi Viewer Backend" + +[build-system] +requires = ["flit"] +build-backend = "flit.buildapi" + + +dependencies = ["depthai-sdk", "depthai", "numpy==1.24.*", "rerun_sdk", "ahrs", "websockets"] diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/sdk_callbacks.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/sdk_callbacks.py new file mode 100644 index 000000000000..d5364301f76a --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/sdk_callbacks.py @@ -0,0 +1,163 @@ +from typing import Callable, List, Optional, Tuple + +import cv2 +import depthai as dai +import numpy as np +from ahrs.filters import Mahony +from depthai_sdk.classes.packets import ( + DepthPacket, + DetectionPacket, + FramePacket, + IMUPacket, + # PointcloudPacket, + TwoStagePacket, + _Detection, +) +from numpy.typing import NDArray + +import depthai_viewer as viewer +from depthai_viewer._backend import classification_labels +from depthai_viewer._backend.store import Store +from depthai_viewer._backend.topic import Topic +from depthai_viewer.components.rect2d import RectFormat + + +class EntityPath: + LEFT_PINHOLE_CAMERA = "mono/camera/left_mono" + LEFT_CAMERA_IMAGE = "mono/camera/left_mono/Left mono" + RIGHT_PINHOLE_CAMERA = "mono/camera/right_mono" + RIGHT_CAMERA_IMAGE = "mono/camera/right_mono/Right mono" + RGB_PINHOLE_CAMERA = "color/camera/rgb" + RGB_CAMERA_IMAGE = "color/camera/rgb/Color camera" + + DETECTIONS = "color/camera/rgb/Detections" + DETECTION = "color/camera/rgb/Detection" + + RGB_CAMERA_TRANSFORM = "color/camera" + MONO_CAMERA_TRANSFORM = "mono/camera" + + +class SdkCallbacks: + store: Store + ahrs: Mahony + _get_camera_intrinsics: Callable[[int, int], NDArray[np.float32]] + + def __init__(self, store: Store): + viewer.init("Depthai Viewer") + viewer.connect() + self.store = store + self.ahrs = Mahony(frequency=100) + self.ahrs.Q = np.array([1, 0, 0, 0], dtype=np.float64) + + def set_camera_intrinsics_getter(self, camera_intrinsics_getter: Callable[[int, int], NDArray[np.float32]]) -> None: + self._get_camera_intrinsics = camera_intrinsics_getter + + def on_imu(self, packet: IMUPacket) -> None: + for data in packet.data: + gyro: dai.IMUReportGyroscope = data.gyroscope + accel: dai.IMUReportAccelerometer = data.acceleroMeter + mag: dai.IMUReportMagneticField = data.magneticField + # TODO(filip): Move coordinate mapping to sdk + self.ahrs.Q = self.ahrs.updateIMU( + self.ahrs.Q, np.array([gyro.z, gyro.x, gyro.y]), np.array([accel.z, accel.x, accel.y]) + ) + if Topic.ImuData not in self.store.subscriptions: + return + viewer.log_imu([accel.z, accel.x, accel.y], [gyro.z, gyro.x, gyro.y], self.ahrs.Q, [mag.x, mag.y, mag.z]) + + def on_color_frame(self, frame: FramePacket) -> None: + # Always log pinhole cam and pose (TODO(filip): move somewhere else or not) + if Topic.ColorImage not in self.store.subscriptions: + return + viewer.log_rigid3(EntityPath.RGB_CAMERA_TRANSFORM, child_from_parent=([0, 0, 0], self.ahrs.Q), xyz="RDF") + h, w, _ = frame.frame.shape + viewer.log_pinhole( + EntityPath.RGB_PINHOLE_CAMERA, child_from_parent=self._get_camera_intrinsics(w, h), width=w, height=h + ) + viewer.log_image(EntityPath.RGB_CAMERA_IMAGE, cv2.cvtColor(frame.frame, cv2.COLOR_BGR2RGB)) + + def on_left_frame(self, frame: FramePacket) -> None: + if Topic.LeftMono not in self.store.subscriptions: + return + h, w = frame.frame.shape + viewer.log_rigid3(EntityPath.MONO_CAMERA_TRANSFORM, child_from_parent=([0, 0, 0], self.ahrs.Q), xyz="RDF") + viewer.log_pinhole( + EntityPath.LEFT_PINHOLE_CAMERA, child_from_parent=self._get_camera_intrinsics(w, h), width=w, height=h + ) + viewer.log_image(EntityPath.LEFT_CAMERA_IMAGE, frame.frame) + + def on_right_frame(self, frame: FramePacket) -> None: + if Topic.RightMono not in self.store.subscriptions: + return + h, w = frame.frame.shape + viewer.log_rigid3(EntityPath.MONO_CAMERA_TRANSFORM, child_from_parent=([0, 0, 0], self.ahrs.Q), xyz="RDF") + viewer.log_pinhole( + EntityPath.RIGHT_PINHOLE_CAMERA, child_from_parent=self._get_camera_intrinsics(w, h), width=w, height=h + ) + viewer.log_image(EntityPath.RIGHT_CAMERA_IMAGE, frame.frame) + + def on_stereo_frame(self, frame: DepthPacket) -> None: + if Topic.DepthImage not in self.store.subscriptions: + return + depth_frame = frame.frame + path = EntityPath.RGB_PINHOLE_CAMERA + "/Depth" + if not self.store.pipeline_config or not self.store.pipeline_config.depth: + # Essentially impossible to get here + return + depth = self.store.pipeline_config.depth + if depth.align == dai.CameraBoardSocket.LEFT: + path = EntityPath.LEFT_PINHOLE_CAMERA + "/Depth" + elif depth.align == dai.CameraBoardSocket.RIGHT: + path = EntityPath.RIGHT_PINHOLE_CAMERA + "/Depth" + viewer.log_depth_image(path, depth_frame, meter=1e3) + + def on_detections(self, packet: DetectionPacket) -> None: + rects, colors, labels = self._detections_to_rects_colors_labels(packet) + viewer.log_rects(EntityPath.DETECTIONS, rects, rect_format=RectFormat.XYXY, colors=colors, labels=labels) + + def _detections_to_rects_colors_labels( + self, packet: DetectionPacket, omz_labels: Optional[List[str]] = None + ) -> Tuple[List[List[int]], List[List[int]], List[str]]: + rects = [] + colors = [] + labels = [] + for detection in packet.detections: + rects.append(self._rect_from_detection(detection)) + colors.append([0, 255, 0]) + label: str = detection.label + # Open model zoo models output label index + if omz_labels is not None and isinstance(label, int): + label += omz_labels[label] + label += ", " + str(int(detection.img_detection.confidence * 100)) + "%" + labels.append(label) + return rects, colors, labels + + def on_yolo_packet(self, packet: DetectionPacket) -> None: + rects, colors, labels = self._detections_to_rects_colors_labels(packet) + viewer.log_rects(EntityPath.DETECTIONS, rects=rects, colors=colors, labels=labels, rect_format=RectFormat.XYXY) + + def on_age_gender_packet(self, packet: TwoStagePacket) -> None: + for det, rec in zip(packet.detections, packet.nnData): + age = int(float(np.squeeze(np.array(rec.getLayerFp16("age_conv3")))) * 100) + gender = np.squeeze(np.array(rec.getLayerFp16("prob"))) + gender_str = "Woman" if gender[0] > gender[1] else "Man" + label = f"{gender_str}, {age}" + color = [255, 0, 0] if gender[0] > gender[1] else [0, 0, 255] + # TODO(filip): maybe use viewer.log_annotation_context to log class colors for detections + viewer.log_rect( + EntityPath.DETECTION, + self._rect_from_detection(det), + rect_format=RectFormat.XYXY, + color=color, + label=label, + ) + + def _rect_from_detection(self, detection: _Detection) -> List[int]: + return [ + *detection.bottom_right, + *detection.top_left, + ] + + def on_mobilenet_ssd_packet(self, packet: DetectionPacket) -> None: + rects, colors, labels = self._detections_to_rects_colors_labels(packet, classification_labels.MOBILENET_LABELS) + viewer.log_rects(EntityPath.DETECTIONS, rects=rects, colors=colors, labels=labels, rect_format=RectFormat.XYXY) diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/store.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/store.py new file mode 100644 index 000000000000..c810a2eb8d78 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/store.py @@ -0,0 +1,53 @@ +from enum import Enum +from typing import Callable, Dict, List, Optional, Tuple + +from depthai_viewer._backend.device_configuration import PipelineConfiguration +from depthai_viewer._backend.topic import Topic + + +class Action(Enum): + UPDATE_PIPELINE = 0 + SELECT_DEVICE = 1 + GET_SUBSCRIPTIONS = 2 + SET_SUBSCRIPTIONS = 3 + GET_PIPELINE = 4 + RESET = 5 # When anything bad happens, a reset occurs (like closing ws connection) + GET_AVAILABLE_DEVICES = 6 + + +class Store: + pipeline_config: Optional[PipelineConfiguration] = PipelineConfiguration() + subscriptions: List[Topic] = [] + on_update_pipeline: Optional[Callable[[bool], Tuple[bool, Dict[str, str]]]] = None + on_select_device: Optional[Callable[[str], Tuple[bool, Dict[str, str]]]] = None + on_reset: Optional[Callable[[], Tuple[bool, Dict[str, str]]]] = None + + def handle_action(self, action: Action, **kwargs) -> Tuple[bool, Dict[str, str]]: # type: ignore[no-untyped-def] + if action == Action.UPDATE_PIPELINE: + if kwargs.get("pipeline_config", None): + if self.on_update_pipeline: + old_pipeline_config = self.pipeline_config + self.pipeline_config = kwargs.get("pipeline_config") + success, message = self.on_update_pipeline(kwargs.get("runtime_only")) # type: ignore[arg-type] + if success: + return success, message + self.pipeline_config = old_pipeline_config + return success, message + elif action == Action.SELECT_DEVICE: + device_id = kwargs.get("device_id", None) + if device_id is not None: + self.device_id = device_id + if self.on_select_device: + return self.on_select_device(device_id) + elif action == Action.GET_SUBSCRIPTIONS: + return self.subscriptions # type: ignore[return-value] + elif action == Action.SET_SUBSCRIPTIONS: + self.subscriptions = kwargs.get("subscriptions", []) + elif action == Action.GET_PIPELINE: + return self.pipeline_config # type: ignore[return-value] + elif action == Action.RESET: + if self.on_reset: + self.pipeline_config = None + self.subscriptions = [] + return self.on_reset() + return False, {"message": f"Action: {action} didn't succeed!"} diff --git a/rerun_py/rerun_sdk/depthai_viewer/_backend/topic.py b/rerun_py/rerun_sdk/depthai_viewer/_backend/topic.py new file mode 100644 index 000000000000..3d7562ce6db2 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/_backend/topic.py @@ -0,0 +1,24 @@ +from enum import Enum +from typing import Union + + +class Topic(Enum): + """All topics that can be subscribed to.""" + + ColorImage = 0 + LeftMono = 1 + RightMono = 2 + DepthImage = 3 + PinholeCamera = 4 + Rectangle = 5 + Rectangles = 6 + ImuData = 7 + + @classmethod + def create(cls, name_or_id: Union[str, int]) -> "Topic": + if type(name_or_id) == str: + return Topic[name_or_id] + elif type(name_or_id) == int: + return Topic(name_or_id) + else: + raise ValueError("Invalid topic name or id: ", name_or_id) diff --git a/rerun_py/rerun_sdk/rerun/color_conversion.py b/rerun_py/rerun_sdk/depthai_viewer/color_conversion.py similarity index 100% rename from rerun_py/rerun_sdk/rerun/color_conversion.py rename to rerun_py/rerun_sdk/depthai_viewer/color_conversion.py diff --git a/rerun_py/rerun_sdk/rerun/components/__init__.py b/rerun_py/rerun_sdk/depthai_viewer/components/__init__.py similarity index 97% rename from rerun_py/rerun_sdk/rerun/components/__init__.py rename to rerun_py/rerun_sdk/depthai_viewer/components/__init__.py index 23b809b27338..9d7b5d33f231 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/__init__.py @@ -6,7 +6,7 @@ import pyarrow as pa -from rerun import bindings +from depthai_viewer import bindings all = [ "annotation", @@ -23,6 +23,8 @@ "tensor", "text_entry", "vec", + "imu", + "xlink_stats", ] # Component names that are recognized by Rerun. diff --git a/rerun_py/rerun_sdk/rerun/components/annotation.py b/rerun_py/rerun_sdk/depthai_viewer/components/annotation.py similarity index 94% rename from rerun_py/rerun_sdk/rerun/components/annotation.py rename to rerun_py/rerun_sdk/depthai_viewer/components/annotation.py index c622fb5489f0..bf0f780f08b6 100644 --- a/rerun_py/rerun_sdk/rerun/components/annotation.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/annotation.py @@ -4,7 +4,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "ClassIdArray", diff --git a/rerun_py/rerun_sdk/rerun/components/arrow.py b/rerun_py/rerun_sdk/depthai_viewer/components/arrow.py similarity index 88% rename from rerun_py/rerun_sdk/rerun/components/arrow.py rename to rerun_py/rerun_sdk/depthai_viewer/components/arrow.py index 1e0577c2a63c..acbfb54f16cc 100644 --- a/rerun_py/rerun_sdk/rerun/components/arrow.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/arrow.py @@ -4,8 +4,8 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory -from rerun.components.vec import Vec3DArray +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components.vec import Vec3DArray __all__ = [ "Arrow3DArray", diff --git a/rerun_py/rerun_sdk/rerun/components/box.py b/rerun_py/rerun_sdk/depthai_viewer/components/box.py similarity index 90% rename from rerun_py/rerun_sdk/rerun/components/box.py rename to rerun_py/rerun_sdk/depthai_viewer/components/box.py index 252d391e70e9..fe4ae15f622c 100644 --- a/rerun_py/rerun_sdk/rerun/components/box.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/box.py @@ -4,7 +4,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "Box3DArray", diff --git a/rerun_py/rerun_sdk/rerun/components/color.py b/rerun_py/rerun_sdk/depthai_viewer/components/color.py similarity index 84% rename from rerun_py/rerun_sdk/rerun/components/color.py rename to rerun_py/rerun_sdk/depthai_viewer/components/color.py index d2ff97cd9965..098817e00078 100644 --- a/rerun_py/rerun_sdk/rerun/components/color.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/color.py @@ -4,8 +4,8 @@ import numpy.typing as npt import pyarrow as pa -from rerun.color_conversion import u8_array_to_rgba -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.color_conversion import u8_array_to_rgba +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "ColorRGBAArray", diff --git a/rerun_py/rerun_sdk/depthai_viewer/components/imu.py b/rerun_py/rerun_sdk/depthai_viewer/components/imu.py new file mode 100644 index 000000000000..18db59992bd5 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/components/imu.py @@ -0,0 +1,38 @@ +from typing import Union + +import numpy as np +import numpy.typing as npt +import pyarrow as pa + +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components.point import Point3DArray, Point3DType +from depthai_viewer.components.quaternion import QuaternionArray + +__all__ = ["ImuType", "Imu"] + + +class Imu(pa.ExtensionArray): # type: ignore[misc] + def create( + accel: npt.NDArray[np.float32], + gyro: npt.NDArray[np.float32], + orientation: npt.NDArray[np.float32], + mag: Union[npt.NDArray[np.float32], None] = None, + ) -> "Imu": + """Build Imu data from acceleration and gyroscope data.""" + assert accel.shape[0] == 3 + assert gyro.shape[0] == 3 + accel_point = Point3DArray.from_numpy(accel.reshape(1, 3)) + gyro_point = Point3DArray.from_numpy(gyro.reshape(1, 3)) + quat = QuaternionArray.from_numpy(np.array(orientation, dtype=np.float32).reshape(1, 4)) + mag_point = pa.nulls(1, type=Point3DType.storage_type) + if mag is not None: + mag_point = Point3DArray.from_numpy(np.array(mag, dtype=np.float32).reshape(1, 3)) + return pa.StructArray.from_arrays( # type: ignore[no-any-return] + fields=ImuType.storage_type, + arrays=[accel_point, gyro_point, mag_point, quat], + mask=pa.array([False, False, mag is None, False], type=pa.bool_()), + ) + + +ImuType = ComponentTypeFactory("ImuType", Imu, REGISTERED_COMPONENT_NAMES["rerun.imu"]) +pa.register_extension_type(ImuType()) diff --git a/rerun_py/rerun_sdk/rerun/components/instance.py b/rerun_py/rerun_sdk/depthai_viewer/components/instance.py similarity index 91% rename from rerun_py/rerun_sdk/rerun/components/instance.py rename to rerun_py/rerun_sdk/depthai_viewer/components/instance.py index 428d339a72cd..aa8ed7db7849 100644 --- a/rerun_py/rerun_sdk/rerun/components/instance.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/instance.py @@ -4,7 +4,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "InstanceArray", diff --git a/rerun_py/rerun_sdk/rerun/components/label.py b/rerun_py/rerun_sdk/depthai_viewer/components/label.py similarity index 89% rename from rerun_py/rerun_sdk/rerun/components/label.py rename to rerun_py/rerun_sdk/depthai_viewer/components/label.py index dbafbfee770a..09161ef14f89 100644 --- a/rerun_py/rerun_sdk/rerun/components/label.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/label.py @@ -4,7 +4,7 @@ import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "LabelArray", diff --git a/rerun_py/rerun_sdk/rerun/components/linestrip.py b/rerun_py/rerun_sdk/depthai_viewer/components/linestrip.py similarity index 96% rename from rerun_py/rerun_sdk/rerun/components/linestrip.py rename to rerun_py/rerun_sdk/depthai_viewer/components/linestrip.py index ee845346d39b..123efc8b6e61 100644 --- a/rerun_py/rerun_sdk/rerun/components/linestrip.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/linestrip.py @@ -7,7 +7,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "LineStrip2DArray", diff --git a/rerun_py/rerun_sdk/rerun/components/point.py b/rerun_py/rerun_sdk/depthai_viewer/components/point.py similarity index 95% rename from rerun_py/rerun_sdk/rerun/components/point.py rename to rerun_py/rerun_sdk/depthai_viewer/components/point.py index 120c8506ba1f..dc61e0b838fe 100644 --- a/rerun_py/rerun_sdk/rerun/components/point.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/point.py @@ -4,7 +4,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "Point2DArray", diff --git a/rerun_py/rerun_sdk/rerun/components/quaternion.py b/rerun_py/rerun_sdk/depthai_viewer/components/quaternion.py similarity index 90% rename from rerun_py/rerun_sdk/rerun/components/quaternion.py rename to rerun_py/rerun_sdk/depthai_viewer/components/quaternion.py index f04091139447..005708451380 100644 --- a/rerun_py/rerun_sdk/rerun/components/quaternion.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/quaternion.py @@ -4,7 +4,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "QuaternionArray", diff --git a/rerun_py/rerun_sdk/rerun/components/radius.py b/rerun_py/rerun_sdk/depthai_viewer/components/radius.py similarity index 89% rename from rerun_py/rerun_sdk/rerun/components/radius.py rename to rerun_py/rerun_sdk/depthai_viewer/components/radius.py index 47203588871a..f6330ff9fc9a 100644 --- a/rerun_py/rerun_sdk/rerun/components/radius.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/radius.py @@ -4,7 +4,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "RadiusArray", diff --git a/rerun_py/rerun_sdk/rerun/components/rect2d.py b/rerun_py/rerun_sdk/depthai_viewer/components/rect2d.py similarity index 97% rename from rerun_py/rerun_sdk/rerun/components/rect2d.py rename to rerun_py/rerun_sdk/depthai_viewer/components/rect2d.py index a0d521e5f6ca..6e5c9f215319 100644 --- a/rerun_py/rerun_sdk/rerun/components/rect2d.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/rect2d.py @@ -6,7 +6,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import ( +from depthai_viewer.components import ( REGISTERED_COMPONENT_NAMES, ComponentTypeFactory, build_dense_union, diff --git a/rerun_py/rerun_sdk/rerun/components/scalar.py b/rerun_py/rerun_sdk/depthai_viewer/components/scalar.py similarity index 94% rename from rerun_py/rerun_sdk/rerun/components/scalar.py rename to rerun_py/rerun_sdk/depthai_viewer/components/scalar.py index 4f14f37e4e63..bc8407014d96 100644 --- a/rerun_py/rerun_sdk/rerun/components/scalar.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/scalar.py @@ -6,7 +6,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "ScalarArray", diff --git a/rerun_py/rerun_sdk/rerun/components/tensor.py b/rerun_py/rerun_sdk/depthai_viewer/components/tensor.py similarity index 97% rename from rerun_py/rerun_sdk/rerun/components/tensor.py rename to rerun_py/rerun_sdk/depthai_viewer/components/tensor.py index b5d50cd2933e..284bc7eb6b78 100644 --- a/rerun_py/rerun_sdk/rerun/components/tensor.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/tensor.py @@ -7,8 +7,8 @@ import numpy.typing as npt import pyarrow as pa -from rerun import bindings -from rerun.components import ( +from depthai_viewer import bindings +from depthai_viewer.components import ( REGISTERED_COMPONENT_NAMES, ComponentTypeFactory, build_dense_union, diff --git a/rerun_py/rerun_sdk/rerun/components/text_entry.py b/rerun_py/rerun_sdk/depthai_viewer/components/text_entry.py similarity index 92% rename from rerun_py/rerun_sdk/rerun/components/text_entry.py rename to rerun_py/rerun_sdk/depthai_viewer/components/text_entry.py index 7d266617f1c2..e43a8dd207c2 100644 --- a/rerun_py/rerun_sdk/rerun/components/text_entry.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/text_entry.py @@ -4,7 +4,7 @@ import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "TextEntryArray", diff --git a/rerun_py/rerun_sdk/rerun/components/vec.py b/rerun_py/rerun_sdk/depthai_viewer/components/vec.py similarity index 94% rename from rerun_py/rerun_sdk/rerun/components/vec.py rename to rerun_py/rerun_sdk/depthai_viewer/components/vec.py index 5c222159cc5c..0db9522c2110 100644 --- a/rerun_py/rerun_sdk/rerun/components/vec.py +++ b/rerun_py/rerun_sdk/depthai_viewer/components/vec.py @@ -4,7 +4,7 @@ import numpy.typing as npt import pyarrow as pa -from rerun.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory __all__ = [ "Vec2DArray", diff --git a/rerun_py/rerun_sdk/depthai_viewer/components/xlink_stats.py b/rerun_py/rerun_sdk/depthai_viewer/components/xlink_stats.py new file mode 100644 index 000000000000..65070f9cfe02 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/components/xlink_stats.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +import pyarrow as pa + +from depthai_viewer.components import REGISTERED_COMPONENT_NAMES, ComponentTypeFactory + +__all__ = ["XLinkStats"] + +# --- + + +class XLinkStats(pa.ExtensionArray): # type: ignore[misc] + def create( + total_bytes_written: int, + total_bytes_read: int, + ) -> "XLinkStats": + """Build XLinkStats data from total bytes written and read.""" + return pa.StructArray.from_arrays( # type: ignore[no-any-return] + fields=XLinkStatsType.storage_type, + arrays=[[total_bytes_written], [total_bytes_read]], + mask=pa.array([False, False], type=pa.bool_()), + ) + + +XLinkStatsType = ComponentTypeFactory("XLinkStatsType", XLinkStats, REGISTERED_COMPONENT_NAMES["rerun.xlink_stats"]) + +pa.register_extension_type(XLinkStatsType()) diff --git a/rerun_py/rerun_sdk/rerun/log/__init__.py b/rerun_py/rerun_sdk/depthai_viewer/log/__init__.py similarity index 98% rename from rerun_py/rerun_sdk/rerun/log/__init__.py rename to rerun_py/rerun_sdk/depthai_viewer/log/__init__.py index fd7a74d1cf76..bf73a06df7f8 100644 --- a/rerun_py/rerun_sdk/rerun/log/__init__.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/__init__.py @@ -3,7 +3,7 @@ import numpy as np import numpy.typing as npt -from rerun import bindings +from depthai_viewer import bindings __all__ = [ "annotation", @@ -23,6 +23,7 @@ "text_internal", "transform", "ext", + "imu", ] diff --git a/rerun_py/rerun_sdk/rerun/log/annotation.py b/rerun_py/rerun_sdk/depthai_viewer/log/annotation.py similarity index 97% rename from rerun_py/rerun_sdk/rerun/log/annotation.py rename to rerun_py/rerun_sdk/depthai_viewer/log/annotation.py index c941fc22ccb0..ebedafd6455b 100644 --- a/rerun_py/rerun_sdk/rerun/log/annotation.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/annotation.py @@ -1,9 +1,9 @@ from dataclasses import dataclass from typing import Iterable, Optional, Sequence, Tuple, Union -from rerun import bindings -from rerun.log import Color, _normalize_colors -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.log import Color, _normalize_colors +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "AnnotationInfo", diff --git a/rerun_py/rerun_sdk/rerun/log/arrow.py b/rerun_py/rerun_sdk/depthai_viewer/log/arrow.py similarity index 83% rename from rerun_py/rerun_sdk/rerun/log/arrow.py rename to rerun_py/rerun_sdk/depthai_viewer/log/arrow.py index 7ad72d06fb80..d6d93a6889ea 100644 --- a/rerun_py/rerun_sdk/rerun/log/arrow.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/arrow.py @@ -3,15 +3,15 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.components.arrow import Arrow3DArray -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.label import LabelArray -from rerun.components.radius import RadiusArray -from rerun.log import Color, _normalize_colors, _normalize_radii -from rerun.log.extension_components import _add_extension_components -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.components.arrow import Arrow3DArray +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.label import LabelArray +from depthai_viewer.components.radius import RadiusArray +from depthai_viewer.log import Color, _normalize_colors, _normalize_radii +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_arrow", diff --git a/rerun_py/rerun_sdk/rerun/log/bounding_box.py b/rerun_py/rerun_sdk/depthai_viewer/log/bounding_box.py similarity index 81% rename from rerun_py/rerun_sdk/rerun/log/bounding_box.py rename to rerun_py/rerun_sdk/depthai_viewer/log/bounding_box.py index 869eaf7a6553..8d575449b40b 100644 --- a/rerun_py/rerun_sdk/rerun/log/bounding_box.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/bounding_box.py @@ -3,18 +3,18 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.components.annotation import ClassIdArray -from rerun.components.box import Box3DArray -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.label import LabelArray -from rerun.components.quaternion import QuaternionArray -from rerun.components.radius import RadiusArray -from rerun.components.vec import Vec3DArray -from rerun.log import Color, _normalize_colors, _normalize_ids, _normalize_radii -from rerun.log.extension_components import _add_extension_components -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.components.annotation import ClassIdArray +from depthai_viewer.components.box import Box3DArray +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.label import LabelArray +from depthai_viewer.components.quaternion import QuaternionArray +from depthai_viewer.components.radius import RadiusArray +from depthai_viewer.components.vec import Vec3DArray +from depthai_viewer.log import Color, _normalize_colors, _normalize_ids, _normalize_radii +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_obb", @@ -41,7 +41,7 @@ def log_obb( Example: -------- ``` - rr.log_obb("my_obb", half_size=[1.0, 2.0, 3.0], position=[0, 0, 0], rotation_q=[0, 0, 0, 1]) + viewer.log_obb("my_obb", half_size=[1.0, 2.0, 3.0], position=[0, 0, 0], rotation_q=[0, 0, 0, 1]) ``` Parameters diff --git a/rerun_py/rerun_sdk/rerun/log/camera.py b/rerun_py/rerun_sdk/depthai_viewer/log/camera.py similarity index 94% rename from rerun_py/rerun_sdk/rerun/log/camera.py rename to rerun_py/rerun_sdk/depthai_viewer/log/camera.py index 8bbb33cc9382..720635c285db 100644 --- a/rerun_py/rerun_sdk/rerun/log/camera.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/camera.py @@ -1,8 +1,8 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_pinhole", diff --git a/rerun_py/rerun_sdk/rerun/log/error_utils.py b/rerun_py/rerun_sdk/depthai_viewer/log/error_utils.py similarity index 87% rename from rerun_py/rerun_sdk/rerun/log/error_utils.py rename to rerun_py/rerun_sdk/depthai_viewer/log/error_utils.py index c8eade22833c..bab3c99854a8 100644 --- a/rerun_py/rerun_sdk/rerun/log/error_utils.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/error_utils.py @@ -1,8 +1,8 @@ import inspect import logging -import rerun -from rerun.log.text_internal import LogLevel, log_text_entry_internal +import depthai_viewer +from depthai_viewer.log.text_internal import LogLevel, log_text_entry_internal __all__ = [ "_send_warning", @@ -24,7 +24,7 @@ def _send_warning(message: str, depth_to_user_code: int) -> None: or raise an exception and let the @log_decorator handle it instead. """ - if rerun.strict_mode(): + if depthai_viewer.strict_mode(): raise TypeError(message) context_descriptor = _build_warning_context_string(skip_first=depth_to_user_code + 2) diff --git a/rerun_py/rerun_sdk/rerun/log/extension_components.py b/rerun_py/rerun_sdk/depthai_viewer/log/extension_components.py similarity index 92% rename from rerun_py/rerun_sdk/rerun/log/extension_components.py rename to rerun_py/rerun_sdk/depthai_viewer/log/extension_components.py index 2307b6ece484..c1bd450f8938 100644 --- a/rerun_py/rerun_sdk/rerun/log/extension_components.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/extension_components.py @@ -5,10 +5,10 @@ import pyarrow as pa # Fully qualified to avoid circular import -import rerun.log.error_utils -from rerun import bindings -from rerun.components.instance import InstanceArray -from rerun.log.log_decorator import log_decorator +import depthai_viewer.log.error_utils +from depthai_viewer import bindings +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "_add_extension_components", @@ -46,7 +46,7 @@ def _add_extension_components( pa_value = pa.array(np_value) EXT_COMPONENT_TYPES[name] = (np_value.dtype, pa_value.type) except Exception as ex: - rerun.log.error_utils._send_warning( + depthai_viewer.log.error_utils._send_warning( "Error converting extension data to arrow for component {}. Dropping.\n{}: {}".format( name, type(ex).__name__, ex ), @@ -117,7 +117,7 @@ def log_extension_components( identifiers = [int(id) for id in identifiers] identifiers_np = np.array(identifiers, dtype="uint64") except ValueError: - rerun.log.error_utils._send_warning("Only integer identifiers supported", 1) + depthai_viewer.log.error_utils._send_warning("Only integer identifiers supported", 1) instanced: Dict[str, Any] = {} splats: Dict[str, Any] = {} diff --git a/rerun_py/rerun_sdk/rerun/log/file.py b/rerun_py/rerun_sdk/depthai_viewer/log/file.py similarity index 96% rename from rerun_py/rerun_sdk/rerun/log/file.py rename to rerun_py/rerun_sdk/depthai_viewer/log/file.py index a05218b9d857..9be5a59d2ac3 100644 --- a/rerun_py/rerun_sdk/rerun/log/file.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/file.py @@ -6,8 +6,8 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "MeshFormat", diff --git a/rerun_py/rerun_sdk/rerun/log/image.py b/rerun_py/rerun_sdk/depthai_viewer/log/image.py similarity index 96% rename from rerun_py/rerun_sdk/rerun/log/image.py rename to rerun_py/rerun_sdk/depthai_viewer/log/image.py index cbf34bcd1816..9e435678cd42 100644 --- a/rerun_py/rerun_sdk/rerun/log/image.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/image.py @@ -3,10 +3,10 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.log.error_utils import _send_warning -from rerun.log.log_decorator import log_decorator -from rerun.log.tensor import Tensor, _log_tensor, _to_numpy +from depthai_viewer import bindings +from depthai_viewer.log.error_utils import _send_warning +from depthai_viewer.log.log_decorator import log_decorator +from depthai_viewer.log.tensor import Tensor, _log_tensor, _to_numpy __all__ = [ "log_image", diff --git a/rerun_py/rerun_sdk/depthai_viewer/log/imu.py b/rerun_py/rerun_sdk/depthai_viewer/log/imu.py new file mode 100644 index 000000000000..3e9c114fa3d4 --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/log/imu.py @@ -0,0 +1,56 @@ +from typing import Any, Dict, Union + +import numpy as np +import numpy.typing as npt + +from depthai_viewer import bindings +from depthai_viewer.components.imu import Imu +from depthai_viewer.log.log_decorator import log_decorator + + +@log_decorator +def log_imu( + accel: npt.ArrayLike, gyro: npt.ArrayLike, orientation: npt.ArrayLike, mag: Union[npt.ArrayLike, None] = None +) -> None: + """ + Log an IMU sensor reading. + + Parameters + ---------- + entity_path: + Path to the IMU sensor in the space hierarchy. + accel: + Acceleration vector in m/s^2. + gyro: + Angular velocity vector in rad/s. + orientation: + Orientation quaternion. + mag: + Magnetometer vector in uT. + """ + + if accel is not None: + accel = np.require(accel, dtype=np.float32) + else: + raise ValueError("Acceleration vector cannot be None") + if gyro is not None: + gyro = np.require(gyro, dtype=np.float32) + else: + raise ValueError("angular velocity vector cannot be None") + if orientation is not None: + orientation = np.require(orientation, dtype=np.float32) + else: + raise ValueError("orientation vector cannot be None") + + instanced: Dict[str, Any] = {} + if accel.size != 3: + raise ValueError(f"Acceleration vector must have a length of 3, got: {accel.size}") + if gyro.size != 3: + raise ValueError(f"Angular velocity vector must have a length of 3, got: {gyro.size}") + + if orientation.size != 4: + raise ValueError(f"Orientation quaternion must have a length of 4, got: {orientation.size}") + + instanced["rerun.imu"] = Imu.create(accel, gyro, orientation, mag) # type: ignore[arg-type] + # Fixed imu entity path + bindings.log_arrow_msg("imu_data", components=instanced, timeless=False) diff --git a/rerun_py/rerun_sdk/rerun/log/lines.py b/rerun_py/rerun_sdk/depthai_viewer/log/lines.py similarity index 92% rename from rerun_py/rerun_sdk/rerun/log/lines.py rename to rerun_py/rerun_sdk/depthai_viewer/log/lines.py index 01055647398a..87c5b733cd54 100644 --- a/rerun_py/rerun_sdk/rerun/log/lines.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/lines.py @@ -4,14 +4,14 @@ import numpy.typing as npt from deprecated import deprecated -from rerun import bindings -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.linestrip import LineStrip2DArray, LineStrip3DArray -from rerun.components.radius import RadiusArray -from rerun.log import Color, _normalize_colors, _normalize_radii -from rerun.log.extension_components import _add_extension_components -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.linestrip import LineStrip2DArray, LineStrip3DArray +from depthai_viewer.components.radius import RadiusArray +from depthai_viewer.log import Color, _normalize_colors, _normalize_radii +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_path", diff --git a/rerun_py/rerun_sdk/rerun/log/log_decorator.py b/rerun_py/rerun_sdk/depthai_viewer/log/log_decorator.py similarity index 86% rename from rerun_py/rerun_sdk/rerun/log/log_decorator.py rename to rerun_py/rerun_sdk/depthai_viewer/log/log_decorator.py index d7be628412cc..bd199f87caea 100644 --- a/rerun_py/rerun_sdk/rerun/log/log_decorator.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/log_decorator.py @@ -3,9 +3,9 @@ import traceback from typing import Any, Callable, TypeVar, cast -import rerun -from rerun import bindings -from rerun.log.text_internal import LogLevel, log_text_entry_internal +import depthai_viewer +from depthai_viewer import bindings +from depthai_viewer.log.text_internal import LogLevel, log_text_entry_internal _TFunc = TypeVar("_TFunc", bound=Callable[..., Any]) @@ -27,7 +27,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: if not bindings.is_enabled(): return - if rerun.strict_mode(): + if depthai_viewer.strict_mode(): # Pass on any exceptions to the caller return func(*args, **kwargs) else: diff --git a/rerun_py/rerun_sdk/rerun/log/mesh.py b/rerun_py/rerun_sdk/depthai_viewer/log/mesh.py similarity index 97% rename from rerun_py/rerun_sdk/rerun/log/mesh.py rename to rerun_py/rerun_sdk/depthai_viewer/log/mesh.py index 8d122b6bc3f5..e182472b8e39 100644 --- a/rerun_py/rerun_sdk/rerun/log/mesh.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/mesh.py @@ -3,12 +3,12 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.log import ( +from depthai_viewer import bindings +from depthai_viewer.log import ( Colors, _normalize_colors, ) -from rerun.log.log_decorator import log_decorator +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_mesh", diff --git a/rerun_py/rerun_sdk/depthai_viewer/log/pipeline_graph.py b/rerun_py/rerun_sdk/depthai_viewer/log/pipeline_graph.py new file mode 100644 index 000000000000..f24a70e4cc4d --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/log/pipeline_graph.py @@ -0,0 +1,60 @@ +# rerun.pipeline_graph + +from typing import Any, Dict, Optional, Sequence + +import numpy as np + +from depthai_viewer import bindings +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.label import LabelArray +from depthai_viewer.components.radius import RadiusArray +from depthai_viewer.components.scalar import ScalarArray, ScalarPlotPropsArray +from depthai_viewer.log import _normalize_colors +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator + +__all__ = [ + "log_pipeline_graph", +] + + +@log_decorator +def log_pipeline_graph( + entity_path: str, + scalar: float, + label: Optional[str] = None, + color: Optional[Sequence[int]] = None, + radius: Optional[float] = None, + scattered: Optional[bool] = None, + ext: Optional[Dict[str, Any]] = None, +) -> None: + instanced: Dict[str, Any] = {} + splats: Dict[str, Any] = {} + + instanced["rerun.pipeline_graph"] = ScalarArray.from_numpy(np.array([scalar])) + + if label: + instanced["rerun.label"] = LabelArray.new([label]) + + if color: + colors = _normalize_colors(np.array([color])) + instanced["rerun.colorrgba"] = ColorRGBAArray.from_numpy(colors) + + if radius: + instanced["rerun.radius"] = RadiusArray.from_numpy(np.array([radius])) + + if scattered: + props = [{"scattered": scattered}] + instanced["rerun.scalar_plot_props"] = ScalarPlotPropsArray.from_props(props) + + if ext: + _add_extension_components(instanced, splats, ext, None) + + if splats: + splats["rerun.instance_key"] = InstanceArray.splat() + bindings.log_arrow_msg(entity_path, components=splats, timeless=False) + + # Always the primary component last so range-based queries will include the other data. See(#1215) + if instanced: + bindings.log_arrow_msg(entity_path, components=instanced, timeless=False) diff --git a/rerun_py/rerun_sdk/rerun/log/points.py b/rerun_py/rerun_sdk/depthai_viewer/log/points.py similarity index 93% rename from rerun_py/rerun_sdk/rerun/log/points.py rename to rerun_py/rerun_sdk/depthai_viewer/log/points.py index 2da62475f1d3..41ec02c22b43 100644 --- a/rerun_py/rerun_sdk/rerun/log/points.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/points.py @@ -3,14 +3,14 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.components.annotation import ClassIdArray -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.label import LabelArray -from rerun.components.point import Point2DArray, Point3DArray -from rerun.components.radius import RadiusArray -from rerun.log import ( +from depthai_viewer import bindings +from depthai_viewer.components.annotation import ClassIdArray +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.label import LabelArray +from depthai_viewer.components.point import Point2DArray, Point3DArray +from depthai_viewer.components.radius import RadiusArray +from depthai_viewer.log import ( Color, Colors, OptionalClassIds, @@ -20,9 +20,9 @@ _normalize_labels, _normalize_radii, ) -from rerun.log.error_utils import _send_warning -from rerun.log.extension_components import _add_extension_components -from rerun.log.log_decorator import log_decorator +from depthai_viewer.log.error_utils import _send_warning +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_point", diff --git a/rerun_py/rerun_sdk/rerun/log/rects.py b/rerun_py/rerun_sdk/depthai_viewer/log/rects.py similarity index 91% rename from rerun_py/rerun_sdk/rerun/log/rects.py rename to rerun_py/rerun_sdk/depthai_viewer/log/rects.py index 36006fe4a43c..8f4a9f383b75 100644 --- a/rerun_py/rerun_sdk/rerun/log/rects.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/rects.py @@ -3,13 +3,13 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.components.annotation import ClassIdArray -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.label import LabelArray -from rerun.components.rect2d import Rect2DArray, RectFormat -from rerun.log import ( +from depthai_viewer import bindings +from depthai_viewer.components.annotation import ClassIdArray +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.label import LabelArray +from depthai_viewer.components.rect2d import Rect2DArray, RectFormat +from depthai_viewer.log import ( Color, Colors, OptionalClassIds, @@ -17,9 +17,9 @@ _normalize_ids, _normalize_labels, ) -from rerun.log.error_utils import _send_warning -from rerun.log.extension_components import _add_extension_components -from rerun.log.log_decorator import log_decorator +from depthai_viewer.log.error_utils import _send_warning +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "RectFormat", diff --git a/rerun_py/rerun_sdk/rerun/log/scalar.py b/rerun_py/rerun_sdk/depthai_viewer/log/scalar.py similarity index 90% rename from rerun_py/rerun_sdk/rerun/log/scalar.py rename to rerun_py/rerun_sdk/depthai_viewer/log/scalar.py index b80d4c7906c8..bb9c07eefcac 100644 --- a/rerun_py/rerun_sdk/rerun/log/scalar.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/scalar.py @@ -2,15 +2,15 @@ import numpy as np -from rerun import bindings -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.label import LabelArray -from rerun.components.radius import RadiusArray -from rerun.components.scalar import ScalarArray, ScalarPlotPropsArray -from rerun.log import Color, _normalize_colors -from rerun.log.extension_components import _add_extension_components -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.label import LabelArray +from depthai_viewer.components.radius import RadiusArray +from depthai_viewer.components.scalar import ScalarArray, ScalarPlotPropsArray +from depthai_viewer.log import Color, _normalize_colors +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_scalar", diff --git a/rerun_py/rerun_sdk/rerun/log/tensor.py b/rerun_py/rerun_sdk/depthai_viewer/log/tensor.py similarity index 91% rename from rerun_py/rerun_sdk/rerun/log/tensor.py rename to rerun_py/rerun_sdk/depthai_viewer/log/tensor.py index 8f3e38f41e91..e79351f517d5 100644 --- a/rerun_py/rerun_sdk/rerun/log/tensor.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/tensor.py @@ -3,12 +3,12 @@ import numpy as np import numpy.typing as npt -from rerun import bindings -from rerun.components.instance import InstanceArray -from rerun.components.tensor import TensorArray -from rerun.log.error_utils import _send_warning -from rerun.log.extension_components import _add_extension_components -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.tensor import TensorArray +from depthai_viewer.log.error_utils import _send_warning +from depthai_viewer.log.extension_components import _add_extension_components +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_tensor", diff --git a/rerun_py/rerun_sdk/rerun/log/text.py b/rerun_py/rerun_sdk/depthai_viewer/log/text.py similarity index 87% rename from rerun_py/rerun_sdk/rerun/log/text.py rename to rerun_py/rerun_sdk/depthai_viewer/log/text.py index fddb04f37fb8..4c89154b83d7 100644 --- a/rerun_py/rerun_sdk/rerun/log/text.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/text.py @@ -2,14 +2,14 @@ from typing import Any, Dict, Final, Optional # Fully qualified to avoid circular import -import rerun.log.extension_components -from rerun import bindings -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.text_entry import TextEntryArray -from rerun.log import Color, _normalize_colors -from rerun.log.log_decorator import log_decorator -from rerun.log.text_internal import LogLevel +import depthai_viewer.log.extension_components +from depthai_viewer import bindings +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.text_entry import TextEntryArray +from depthai_viewer.log import Color, _normalize_colors +from depthai_viewer.log.log_decorator import log_decorator +from depthai_viewer.log.text_internal import LogLevel __all__ = [ "LogLevel", @@ -110,7 +110,7 @@ def log_text_entry( instanced["rerun.colorrgba"] = ColorRGBAArray.from_numpy(colors) if ext: - rerun.log.extension_components._add_extension_components(instanced, splats, ext, None) + depthai_viewer.log.extension_components._add_extension_components(instanced, splats, ext, None) if splats: splats["rerun.instance_key"] = InstanceArray.splat() diff --git a/rerun_py/rerun_sdk/rerun/log/text_internal.py b/rerun_py/rerun_sdk/depthai_viewer/log/text_internal.py similarity index 90% rename from rerun_py/rerun_sdk/rerun/log/text_internal.py rename to rerun_py/rerun_sdk/depthai_viewer/log/text_internal.py index 41898e4dfa5f..4a9cfd378d3b 100644 --- a/rerun_py/rerun_sdk/rerun/log/text_internal.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/text_internal.py @@ -3,11 +3,11 @@ from typing import Any, Dict, Final, Optional # Fully qualified to avoid circular import -from rerun import bindings -from rerun.components.color import ColorRGBAArray -from rerun.components.instance import InstanceArray -from rerun.components.text_entry import TextEntryArray -from rerun.log import Color, _normalize_colors +from depthai_viewer import bindings +from depthai_viewer.components.color import ColorRGBAArray +from depthai_viewer.components.instance import InstanceArray +from depthai_viewer.components.text_entry import TextEntryArray +from depthai_viewer.log import Color, _normalize_colors __all__ = [ "LogLevel", diff --git a/rerun_py/rerun_sdk/rerun/log/transform.py b/rerun_py/rerun_sdk/depthai_viewer/log/transform.py similarity index 96% rename from rerun_py/rerun_sdk/rerun/log/transform.py rename to rerun_py/rerun_sdk/depthai_viewer/log/transform.py index 52dca28654d0..ad9e1ea8de45 100644 --- a/rerun_py/rerun_sdk/rerun/log/transform.py +++ b/rerun_py/rerun_sdk/depthai_viewer/log/transform.py @@ -7,10 +7,10 @@ import numpy.typing as npt -from rerun import bindings -from rerun.log import _to_sequence -from rerun.log.error_utils import _send_warning -from rerun.log.log_decorator import log_decorator +from depthai_viewer import bindings +from depthai_viewer.log import _to_sequence +from depthai_viewer.log.error_utils import _send_warning +from depthai_viewer.log.log_decorator import log_decorator __all__ = [ "log_view_coordinates", diff --git a/rerun_py/rerun_sdk/depthai_viewer/log/xlink_stats.py b/rerun_py/rerun_sdk/depthai_viewer/log/xlink_stats.py new file mode 100644 index 000000000000..7b6f2cae04fb --- /dev/null +++ b/rerun_py/rerun_sdk/depthai_viewer/log/xlink_stats.py @@ -0,0 +1,22 @@ +from typing import Any, Dict + +from depthai_viewer import bindings +from depthai_viewer.components.xlink_stats import XLinkStats +from depthai_viewer.log.log_decorator import log_decorator + + +@log_decorator +def log_xlink_stats(total_bytes_written: int, total_bytes_read: int) -> None: + """ + Log an XLink throughput statistic. + + Parameters + ---------- + total_bytes_written: + Total bytes written to the XLink by the host. + total_bytes_read: + Total bytes read from the XLink by the host. + """ + instanced: Dict[str, Any] = {} + instanced["rerun.xlink_stats"] = XLinkStats.create(total_bytes_written, total_bytes_read) # type: ignore[arg-type] + bindings.log_arrow_msg("xlink_stats", components=instanced, timeless=False) diff --git a/rerun_py/rerun_sdk/depthai_viewer/py.typed b/rerun_py/rerun_sdk/depthai_viewer/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/rerun_py/rerun_sdk/rerun/recording.py b/rerun_py/rerun_sdk/depthai_viewer/recording.py similarity index 97% rename from rerun_py/rerun_sdk/rerun/recording.py rename to rerun_py/rerun_sdk/depthai_viewer/recording.py index efe9b7b25bea..d89b2efc5815 100644 --- a/rerun_py/rerun_sdk/rerun/recording.py +++ b/rerun_py/rerun_sdk/depthai_viewer/recording.py @@ -6,7 +6,7 @@ import string from typing import Any, Optional -from rerun import bindings +from depthai_viewer import bindings DEFAULT_WIDTH = 950 DEFAULT_HEIGHT = 712 @@ -53,7 +53,7 @@ def as_html( html_template = f""" +

Consider using viewer.start_web_viewer_server()