Skip to content

Commit

Permalink
Show meshes and images with rerun foo.obj bar.png (#2060)
Browse files Browse the repository at this point in the history
* Add re_smart_channel::Source::Files

* Remove unused error enum variant

* Add some profile scopes

* Add MsgSender::from_file_path

* Add MsgSender::into_log_msg

* Fix missing feature flag to re_sdk

* Add support for loading many images, meshes, or rrds at once with rerun

* Silence clippy

* fix compilation

* fix typo

Co-authored-by: Clement Rey <[email protected]>

* typo

* explain the .ok(0

* Improve error message when trying to load multiple urls

* Compilation fix

* Refactor so that MsgSender is less central

* Add DataCell::from_file_path

* Fix docstring

* Make MsgSender::with_cell public

* from_file_path -> from_file_path_as_single_string

* Fix merge conflict

* Hide from_file_path_as_single_string in wasm

* Wasm build fix

* Improve file categorization heuristics

---------

Co-authored-by: Clement Rey <[email protected]>
  • Loading branch information
emilk and teh-cmc authored May 16, 2023
1 parent c21fba8 commit c8323ac
Show file tree
Hide file tree
Showing 14 changed files with 472 additions and 102 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ polars-core = "0.29"
polars-lazy = "0.29"
polars-ops = "0.29"
puffin = "0.14"
rayon = "1.7"
rfd = { version = "0.11.3", default_features = false, features = [
"xdg-portal",
] }
Expand Down
7 changes: 0 additions & 7 deletions crates/re_log_types/src/component_types/tensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,13 +776,6 @@ pub enum TensorImageLoadError {
expected: Vec<TensorDimension>,
found: Vec<TensorDimension>,
},

#[cfg(not(target_arch = "wasm32"))]
#[error("Unsupported file extension '{extension}' for file {path:?}")]
UnknownExtension {
extension: String,
path: std::path::PathBuf,
},
}

#[cfg(feature = "image")]
Expand Down
89 changes: 89 additions & 0 deletions crates/re_log_types/src/data_cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,95 @@ impl DataCellInner {
}
}

// ----------------------------------------------------------------------------

#[cfg(not(target_arch = "wasm32"))]
/// Errors from [`DataCell::from_file_path`]
#[derive(thiserror::Error, Debug)]
pub enum FromFileError {
#[error(transparent)]
FileRead(#[from] std::io::Error),

#[error(transparent)]
DataCellError(#[from] crate::DataCellError),

#[cfg(feature = "image")]
#[error(transparent)]
TensorImageLoad(#[from] crate::component_types::TensorImageLoadError),

#[error("Unsupported file extension '{extension}' for file {path:?}. To load image files, make sure you compile with the 'image' feature")]
UnknownExtension {
extension: String,
path: std::path::PathBuf,
},
}

#[cfg(not(target_arch = "wasm32"))]
impl DataCell {
/// Read the file at the given path.
///
/// Supported file extensions are:
/// * `glb`, `gltf`, `obj`: encoded meshes, leaving it to the viewer to decode
/// * `jpg`, `jpeg`: encoded JPEG, leaving it to the viewer to decode. Requires the `image` feature.
/// * `png` and other image formats: decoded here. Requires the `image` feature.
///
/// All other extensions will return an error.
pub fn from_file_path(file_path: &std::path::Path) -> Result<Self, FromFileError> {
let extension = file_path
.extension()
.unwrap_or_default()
.to_ascii_lowercase()
.to_string_lossy()
.to_string();

match extension.as_str() {
"glb" => Self::from_mesh_file_path(file_path, crate::MeshFormat::Glb),
"glft" => Self::from_mesh_file_path(file_path, crate::MeshFormat::Gltf),
"obj" => Self::from_mesh_file_path(file_path, crate::MeshFormat::Obj),

#[cfg(feature = "image")]
_ => {
// Assume and image (there are so many image extensions):
let tensor = crate::Tensor::from_image_file(file_path)?;
Ok(Self::try_from_native(std::iter::once(&tensor))?)
}

#[cfg(not(feature = "image"))]
_ => Err(FromFileError::UnknownExtension {
extension,
path: file_path.to_owned(),
}),
}
}

/// Read the mesh file at the given path.
///
/// Supported file extensions are:
/// * `glb`, `gltf`, `obj`: encoded meshes, leaving it to the viewer to decode
///
/// All other extensions will return an error.
pub fn from_mesh_file_path(
file_path: &std::path::Path,
format: crate::MeshFormat,
) -> Result<Self, FromFileError> {
let mesh = crate::EncodedMesh3D {
mesh_id: crate::MeshId::random(),
format,
bytes: std::fs::read(file_path)?.into(),
transform: [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0],
],
};
let mesh = crate::Mesh3D::Encoded(mesh);
Ok(Self::try_from_native(std::iter::once(&mesh))?)
}
}

// ----------------------------------------------------------------------------

#[test]
fn data_cell_sizes() {
use crate::{component_types::InstanceKey, Component as _};
Expand Down
3 changes: 3 additions & 0 deletions crates/re_log_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ pub use self::time_point::{TimeInt, TimePoint, TimeType, Timeline, TimelineName}
pub use self::time_range::{TimeRange, TimeRangeF};
pub use self::time_real::TimeReal;

#[cfg(not(target_arch = "wasm32"))]
pub use self::data_cell::FromFileError;

#[cfg(not(target_arch = "wasm32"))]
pub use self::data_table_batcher::{
DataTableBatcher, DataTableBatcherConfig, DataTableBatcherError,
Expand Down
11 changes: 11 additions & 0 deletions crates/re_log_types/src/path/entity_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ impl EntityPath {
Self::from(parts)
}

/// Treat the file path as one opaque string.
///
/// The file path separators will NOT become splits in the new path.
/// The returned path will only have one part.
#[cfg(not(target_arch = "wasm32"))]
pub fn from_file_path_as_single_string(file_path: &std::path::Path) -> Self {
Self::new(vec![EntityPathPart::Index(crate::Index::String(
file_path.to_string_lossy().to_string(),
))])
}

#[inline]
pub fn iter(&self) -> impl Iterator<Item = &EntityPathPart> {
self.path.iter()
Expand Down
2 changes: 2 additions & 0 deletions crates/re_renderer/src/wgpu_resources/buffer_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ impl GpuBufferPool {
/// For more efficient allocation (faster, less fragmentation) you should sub-allocate buffers whenever possible
/// either manually or using a higher level allocator.
pub fn alloc(&self, device: &wgpu::Device, desc: &BufferDesc) -> GpuBuffer {
crate::profile_function!();
self.pool.alloc(desc, |desc| {
crate::profile_scope!("create_buffer");
device.create_buffer(&wgpu::BufferDescriptor {
label: desc.label.get(),
size: desc.size,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ where

// Otherwise create a new resource
re_log::trace!(?desc, "Allocated new resource");
let inner_resource = creation_func(desc);
let inner_resource = {
crate::profile_scope!("creation_func");
creation_func(desc)
};
self.total_resource_size_in_bytes.fetch_add(
desc.resource_size_in_bytes(),
std::sync::atomic::Ordering::Relaxed,
Expand Down
50 changes: 48 additions & 2 deletions crates/re_sdk/src/msg_sender.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use re_log_types::{component_types::InstanceKey, DataRow, DataTableError, RowId};
use re_log_types::{component_types::InstanceKey, DataRow, DataTableError, RecordingId, RowId};

use crate::{
log::DataCell,
Expand Down Expand Up @@ -102,6 +102,26 @@ impl MsgSender {
}
}

/// Read the file at the given path and log it.
///
/// Supported file extensions are:
/// * `glb`, `gltf`, `obj`: encoded meshes, leaving it to the viewer to decode
/// * `jpg`, `jpeg`: encoded JPEG, leaving it to the viewer to decode. Requires the `image` feature.
/// * `png` and other image formats: decoded here. Requires the `image` feature.
///
/// All other extensions will return an error.
pub fn from_file_path(
file_path: &std::path::Path,
) -> Result<Self, re_log_types::FromFileError> {
let ent_path = re_log_types::EntityPath::from_file_path_as_single_string(file_path);
let cell = DataCell::from_file_path(file_path)?;
Ok(Self {
num_instances: Some(cell.num_instances()),
instanced: vec![cell],
..Self::new(ent_path)
})
}

// --- Time ---

/// Appends a given `timepoint` to the current message.
Expand Down Expand Up @@ -161,11 +181,24 @@ impl MsgSender {
/// the same component type multiple times in a single message.
/// Doing so will return an error when trying to `send()` the message.
pub fn with_component<'a, C: SerializableComponent>(
mut self,
self,
data: impl IntoIterator<Item = &'a C>,
) -> Result<Self, MsgSenderError> {
let cell = DataCell::try_from_native(data).map_err(DataTableError::from)?;
self.with_cell(cell)
}

/// Appends a component collection to the current message.
///
/// All component collections stored in the message must have the same row-length (i.e. number
/// of instances)!
/// The row-length of the first appended collection is used as ground truth.
///
/// ⚠ This can only be called once per type of component!
/// The SDK does not yet support batch insertions, which are semantically identical to adding
/// the same component type multiple times in a single message.
/// Doing so will return an error when trying to `send()` the message.
pub fn with_cell(mut self, cell: DataCell) -> Result<MsgSender, MsgSenderError> {
let num_instances = cell.num_instances();

if let Some(cur_num_instances) = self.num_instances {
Expand Down Expand Up @@ -250,6 +283,19 @@ impl MsgSender {
Ok(())
}

/// Turns the current message into a single [`re_log_types::LogMsg`]
pub fn into_log_msg(
self,
recording_id: RecordingId,
) -> Result<re_log_types::LogMsg, DataTableError> {
let data_table = re_log_types::DataTable::from_rows(
re_log_types::TableId::random(),
self.into_rows().into_iter().flatten(),
);
let arrow_msg = data_table.to_arrow_msg()?;
Ok(re_log_types::LogMsg::ArrowMsg(recording_id, arrow_msg))
}

fn into_rows(self) -> [Option<DataRow>; 2] {
let Self {
entity_path,
Expand Down
8 changes: 5 additions & 3 deletions crates/re_smart_channel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ pub use crossbeam::channel::{RecvError, RecvTimeoutError, SendError, TryRecvErro
/// Where is the messages coming from?
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Source {
/// The source if a file on disk
File { path: std::path::PathBuf },
/// The source is one or more files on disk.
/// This could be `.rrd` files, or `.glb`, `.png`, …
// TODO(#2121): Remove this
Files { paths: Vec<std::path::PathBuf> },

/// Streaming an `.rrd` file over http.
RrdHttpStream { url: String },
Expand Down Expand Up @@ -41,7 +43,7 @@ pub enum Source {
impl Source {
pub fn is_network(&self) -> bool {
match self {
Self::File { .. } | Self::Sdk | Self::RrdWebEventListener => false,
Self::Files { .. } | Self::Sdk | Self::RrdWebEventListener => false,
Self::RrdHttpStream { .. } | Self::WsClient { .. } | Self::TcpServer { .. } => true,
}
}
Expand Down
19 changes: 14 additions & 5 deletions crates/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,13 @@ fn wait_screen_ui(ui: &mut egui::Ui, rx: &Receiver<LogMsg>) {
}

match rx.source() {
re_smart_channel::Source::File { path } => {
ui.strong(format!("Loading {}…", path.display()));
re_smart_channel::Source::Files { paths } => {
ui.strong(format!(
"Loading {}…",
paths
.iter()
.format_with(", ", |path, f| f(&format_args!("{}", path.display())))
));
}
re_smart_channel::Source::RrdHttpStream { url } => {
ui.strong(format!("Loading {url}…"));
Expand Down Expand Up @@ -1956,7 +1961,9 @@ fn load_file_path(path: &std::path::Path) -> Option<LogDb> {
match load_file_path_impl(path) {
Ok(mut new_log_db) => {
re_log::info!("Loaded {path:?}");
new_log_db.data_source = Some(re_smart_channel::Source::File { path: path.into() });
new_log_db.data_source = Some(re_smart_channel::Source::Files {
paths: vec![path.into()],
});
Some(new_log_db)
}
Err(err) => {
Expand All @@ -1976,7 +1983,9 @@ fn load_file_contents(name: &str, read: impl std::io::Read) -> Option<LogDb> {
match load_rrd_to_log_db(read) {
Ok(mut log_db) => {
re_log::info!("Loaded {name:?}");
log_db.data_source = Some(re_smart_channel::Source::File { path: name.into() });
log_db.data_source = Some(re_smart_channel::Source::Files {
paths: vec![name.into()],
});
Some(log_db)
}
Err(err) => {
Expand Down Expand Up @@ -2009,7 +2018,7 @@ fn new_recording_confg(
let play_state = match data_source {
// Play files from the start by default - it feels nice and alive./
// RrdHttpStream downloads the whole file before decoding it, so we treat it the same as a file.
re_smart_channel::Source::File { .. }
re_smart_channel::Source::Files { .. }
| re_smart_channel::Source::RrdHttpStream { .. }
| re_smart_channel::Source::RrdWebEventListener => PlayState::Playing,

Expand Down
2 changes: 1 addition & 1 deletion crates/re_viewer/src/viewer_analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ impl ViewerAnalytics {

if let Some(data_source) = &log_db.data_source {
let data_source = match data_source {
re_smart_channel::Source::File { .. } => "file", // .rrd
re_smart_channel::Source::Files { .. } => "file", // .rrd, .png, .glb, …
re_smart_channel::Source::RrdHttpStream { .. } => "http",
re_smart_channel::Source::RrdWebEventListener { .. } => "web_event",
re_smart_channel::Source::Sdk => "sdk", // show()
Expand Down
6 changes: 4 additions & 2 deletions crates/rerun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ demo = ["re_sdk?/demo"]
glam = ["re_sdk?/glam"]

## Integration with the [`image`](https://crates.io/crates/image/) crate.
image = ["re_log_types/image"]
image = ["re_log_types/image", "re_sdk?/image"]

## Support spawning a native viewer.
## This adds a lot of extra dependencies, so only enable this feature if you need it!
Expand Down Expand Up @@ -101,8 +101,10 @@ webbrowser = { version = "0.8", optional = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
backtrace = "0.3"
clap = { workspace = true, features = ["derive"] }
mimalloc.workspace = true
ctrlc.workspace = true
mimalloc.workspace = true
puffin.workspace = true
rayon.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }

# Native unix dependencies:
Expand Down
Loading

0 comments on commit c8323ac

Please sign in to comment.