diff --git a/crates/re_types/definitions/rerun/archetypes/image.fbs b/crates/re_types/definitions/rerun/archetypes/image.fbs index d7f4bccf7b6b..3d9ab91b2126 100644 --- a/crates/re_types/definitions/rerun/archetypes/image.fbs +++ b/crates/re_types/definitions/rerun/archetypes/image.fbs @@ -16,10 +16,16 @@ namespace rerun.archetypes; /// Leading and trailing unit-dimensions are ignored, so that /// `1x640x480x3x1` is treated as a `640x480x3` RGB image. /// +/// Rerun also supports compressed image encoded as JPEG, N12, and YUY2. +/// Using these formats can save a lot of bandwidth and memory. +/// \py To compress an image, use [`rerun.Image.compress`][]. +/// \py To pass in an already encoded image, use [`rerun.ImageEncoded`][]. +/// \rs See [`crate::components::TensorData`] for more. +/// \cpp See [`rerun::datatypes::TensorBuffer`] for more. +/// /// \cpp Since the underlying `rerun::datatypes::TensorData` uses `rerun::Collection` internally, /// \cpp data can be passed in without a copy from raw pointers or by reference from `std::vector`/`std::array`/c-arrays. /// \cpp If needed, this "borrow-behavior" can be extended by defining your own `rerun::CollectionAdapter`. -/// \python For an easy way to pass in image formats or encoded images, see [`rerun.ImageEncoded`][]. /// /// \example image_simple image="https://static.rerun.io/image_simple/06ba7f8582acc1ffb42a7fd0006fad7816f3e4e4/1200w.png" table Image ( diff --git a/crates/re_types/definitions/rerun/archetypes/points2d.fbs b/crates/re_types/definitions/rerun/archetypes/points2d.fbs index 7e4f70d4fb94..fdc48f20edf9 100644 --- a/crates/re_types/definitions/rerun/archetypes/points2d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/points2d.fbs @@ -28,8 +28,8 @@ table Points2D ( /// Optional colors for the points. /// - /// \python The colors are interpreted as RGB or RGBA in sRGB gamma-space, - /// \python As either 0-1 floats or 0-255 integers, with separate alpha. + /// \py The colors are interpreted as RGB or RGBA in sRGB gamma-space, + /// \py As either 0-1 floats or 0-255 integers, with separate alpha. colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2100); // --- Optional --- diff --git a/crates/re_types/definitions/rerun/archetypes/points3d.fbs b/crates/re_types/definitions/rerun/archetypes/points3d.fbs index aa109118fe3d..f569f0caabec 100644 --- a/crates/re_types/definitions/rerun/archetypes/points3d.fbs +++ b/crates/re_types/definitions/rerun/archetypes/points3d.fbs @@ -26,8 +26,8 @@ table Points3D ( /// Optional colors for the points. /// - /// \python The colors are interpreted as RGB or RGBA in sRGB gamma-space, - /// \python As either 0-1 floats or 0-255 integers, with separate alpha. + /// \py The colors are interpreted as RGB or RGBA in sRGB gamma-space, + /// \py As either 0-1 floats or 0-255 integers, with separate alpha. colors: [rerun.components.Color] ("attr.rerun.component_recommended", nullable, order: 2100); // --- Optional --- diff --git a/crates/re_types/definitions/rerun/datatypes/tensor_buffer.fbs b/crates/re_types/definitions/rerun/datatypes/tensor_buffer.fbs index a84830e4c89c..52b25cf07c5e 100644 --- a/crates/re_types/definitions/rerun/datatypes/tensor_buffer.fbs +++ b/crates/re_types/definitions/rerun/datatypes/tensor_buffer.fbs @@ -110,7 +110,7 @@ union TensorBuffer ( /// First comes entire image in Y, followed by interleaved lines ordered as U0, V0, U1, V1, etc. NV12: NV12Buffer (transparent), - /// YUY2, also known as YUYV is a YUV 4:2:2 chrome downsampled format with 8 bits per channel. + /// YUY2, also known as YUYV is a YUV 4:2:2 chroma downsampled format with 8 bits per channel. /// /// The order of the channels is Y0, U0, Y1, V0. YUY2: YUY2Buffer (transparent), diff --git a/crates/re_types/src/archetypes/image.rs b/crates/re_types/src/archetypes/image.rs index 524ce24a934f..8b8ce27d2bda 100644 --- a/crates/re_types/src/archetypes/image.rs +++ b/crates/re_types/src/archetypes/image.rs @@ -31,6 +31,10 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; /// Leading and trailing unit-dimensions are ignored, so that /// `1x640x480x3x1` is treated as a `640x480x3` RGB image. /// +/// Rerun also supports compressed image encoded as JPEG, N12, and YUY2. +/// Using these formats can save a lot of bandwidth and memory. +/// See [`crate::components::TensorData`] for more. +/// /// ## Example /// /// ### `image_simple`: diff --git a/crates/re_types/src/datatypes/tensor_buffer.rs b/crates/re_types/src/datatypes/tensor_buffer.rs index d52e2d0fdfa8..9a2f31994eca 100644 --- a/crates/re_types/src/datatypes/tensor_buffer.rs +++ b/crates/re_types/src/datatypes/tensor_buffer.rs @@ -67,7 +67,7 @@ pub enum TensorBuffer { /// First comes entire image in Y, followed by interleaved lines ordered as U0, V0, U1, V1, etc. Nv12(::re_types_core::ArrowBuffer), - /// YUY2, also known as YUYV is a YUV 4:2:2 chrome downsampled format with 8 bits per channel. + /// YUY2, also known as YUYV is a YUV 4:2:2 chroma downsampled format with 8 bits per channel. /// /// The order of the channels is Y0, U0, Y1, V0. Yuy2(::re_types_core::ArrowBuffer), diff --git a/crates/re_types_builder/src/codegen/common.rs b/crates/re_types_builder/src/codegen/common.rs index 32da398c557b..e3ee8423e00e 100644 --- a/crates/re_types_builder/src/codegen/common.rs +++ b/crates/re_types_builder/src/codegen/common.rs @@ -12,35 +12,6 @@ fn is_blank>(line: T) -> bool { line.as_ref().chars().all(char::is_whitespace) } -/// Retrieves the global and tagged documentation from a [`Docs`] object. -pub fn get_documentation(docs: &Docs, tags: &[&str]) -> Vec { - let mut lines = docs.doc.clone(); - - for tag in tags { - lines.extend( - docs.tagged_docs - .get(*tag) - .unwrap_or(&Vec::new()) - .iter() - .cloned(), - ); - } - - // NOTE: remove duplicated blank lines. - lines.dedup(); - - // NOTE: remove trailing blank lines. - while let Some(line) = lines.last() { - if line.is_empty() { - lines.pop(); - } else { - break; - } - } - - lines -} - #[derive(Clone)] pub struct ExampleInfo<'a> { /// The snake_case name of the example. @@ -60,66 +31,62 @@ pub struct ExampleInfo<'a> { impl<'a> ExampleInfo<'a> { /// Parses e.g. `// \example example_name title="Example Title" image="https://www.example.com/img.png"` - pub fn parse(tag_content: &'a impl AsRef) -> Self { - fn mono(tag_content: &str) -> ExampleInfo<'_> { - fn find_keyed<'a>(tag: &str, args: &'a str) -> Option<&'a str> { - let mut prev_end = 0; - loop { - if prev_end + tag.len() + "=\"\"".len() >= args.len() { - return None; - } - let key_start = prev_end + args[prev_end..].find(tag)?; - let key_end = key_start + tag.len(); - if !args[key_end..].starts_with("=\"") { - prev_end = key_end; - continue; - }; - let value_start = key_end + "=\"".len(); - let Some(mut value_end) = args[value_start..].find('"') else { - prev_end = value_start; - continue; - }; - value_end += value_start; - return Some(&args[value_start..value_end]); + pub fn parse(tag_content: &'a str) -> Self { + fn find_keyed<'a>(tag: &str, args: &'a str) -> Option<&'a str> { + let mut prev_end = 0; + loop { + if prev_end + tag.len() + "=\"\"".len() >= args.len() { + return None; } + let key_start = prev_end + args[prev_end..].find(tag)?; + let key_end = key_start + tag.len(); + if !args[key_end..].starts_with("=\"") { + prev_end = key_end; + continue; + }; + let value_start = key_end + "=\"".len(); + let Some(mut value_end) = args[value_start..].find('"') else { + prev_end = value_start; + continue; + }; + value_end += value_start; + return Some(&args[value_start..value_end]); } + } - let tag_content = tag_content.trim(); - let (name, args) = tag_content - .split_once(' ') - .map_or((tag_content, None), |(a, b)| (a, Some(b))); - - let (mut title, mut image, mut exclude_from_api_docs) = (None, None, false); + let tag_content = tag_content.trim(); + let (name, args) = tag_content + .split_once(' ') + .map_or((tag_content, None), |(a, b)| (a, Some(b))); - if let Some(args) = args { - let args = args.trim(); + let (mut title, mut image, mut exclude_from_api_docs) = (None, None, false); - exclude_from_api_docs = args.contains("!api"); - let args = if let Some(args_without_api_prefix) = args.strip_prefix("!api") { - args_without_api_prefix.trim() - } else { - args - }; + if let Some(args) = args { + let args = args.trim(); - if args.starts_with('"') { - // \example example_name "Example Title" - title = args.strip_prefix('"').and_then(|v| v.strip_suffix('"')); - } else { - // \example example_name title="Example Title" image="https://static.rerun.io/annotation_context_rects/9b446c36011ed30fce7dc6ed03d5fd9557460f70/1200w.png" - title = find_keyed("title", args); - image = find_keyed("image", args).map(ImageUrl::parse); - } - } + exclude_from_api_docs = args.contains("!api"); + let args = if let Some(args_without_api_prefix) = args.strip_prefix("!api") { + args_without_api_prefix.trim() + } else { + args + }; - ExampleInfo { - name, - title, - image, - exclude_from_api_docs, + if args.starts_with('"') { + // \example example_name "Example Title" + title = args.strip_prefix('"').and_then(|v| v.strip_suffix('"')); + } else { + // \example example_name title="Example Title" image="https://static.rerun.io/annotation_context_rects/9b446c36011ed30fce7dc6ed03d5fd9557460f70/1200w.png" + title = find_keyed("title", args); + image = find_keyed("image", args).map(ImageUrl::parse); } } - mono(tag_content.as_ref()) + ExampleInfo { + name, + title, + image, + exclude_from_api_docs, + } } } @@ -314,46 +281,41 @@ pub fn collect_snippets_for_api_docs<'a>( extension: &str, required: bool, ) -> anyhow::Result>> { - let mut out = Vec::new(); + let base_path = crate::rerun_workspace_path().join("docs/snippets/all"); - if let Some(examples) = docs.tagged_docs.get("example") { - let base_path = crate::rerun_workspace_path().join("docs/snippets/all"); + let examples: Vec<&'a str> = docs.doc_lines_tagged("example"); - for base @ ExampleInfo { - name, - exclude_from_api_docs, - .. - } in examples.iter().map(ExampleInfo::parse) - { - if exclude_from_api_docs { - continue; - } + let mut out: Vec> = Vec::new(); - let path = base_path.join(format!("{name}.{extension}")); - let content = match std::fs::read_to_string(&path) { - Ok(content) => content, - Err(_) if !required => continue, - Err(err) => { - return Err(err).with_context(|| format!("couldn't open snippet {path:?}")) - } - }; - let mut content = content - .split('\n') - .map(String::from) - .skip_while(|line| line.starts_with("//") || line.starts_with(r#"""""#)) // Skip leading comments. - .skip_while(|line| line.trim().is_empty()) // Strip leading empty lines. - .collect_vec(); - - // trim trailing blank lines - while content.last().is_some_and(is_blank) { - content.pop(); - } + for example in &examples { + let base: ExampleInfo<'a> = ExampleInfo::parse(example); + let name = &base.name; + if base.exclude_from_api_docs { + continue; + } - out.push(Example { - base, - lines: content, - }); + let path = base_path.join(format!("{name}.{extension}")); + let content = match std::fs::read_to_string(&path) { + Ok(content) => content, + Err(_) if !required => continue, + Err(err) => return Err(err).with_context(|| format!("couldn't open snippet {path:?}")), + }; + let mut content = content + .split('\n') + .map(String::from) + .skip_while(|line| line.starts_with("//") || line.starts_with(r#"""""#)) // Skip leading comments. + .skip_while(|line| line.trim().is_empty()) // Strip leading empty lines. + .collect_vec(); + + // trim trailing blank lines + while content.last().is_some_and(is_blank) { + content.pop(); } + + out.push(Example { + base, + lines: content, + }); } Ok(out) diff --git a/crates/re_types_builder/src/codegen/cpp/mod.rs b/crates/re_types_builder/src/codegen/cpp/mod.rs index 697546ab5146..1c48d66edfb3 100644 --- a/crates/re_types_builder/src/codegen/cpp/mod.rs +++ b/crates/re_types_builder/src/codegen/cpp/mod.rs @@ -2265,7 +2265,7 @@ fn quote_field_docs(field: &ObjectField) -> TokenStream { } fn lines_from_docs(docs: &Docs) -> Vec { - let mut lines = crate::codegen::get_documentation(docs, &["cpp", "c++"]); + let mut lines = docs.doc_lines_for_untagged_and("cpp"); let required = true; let examples = collect_snippets_for_api_docs(docs, "cpp", required).unwrap_or_default(); diff --git a/crates/re_types_builder/src/codegen/docs/mod.rs b/crates/re_types_builder/src/codegen/docs/mod.rs index 1acf6e21fce7..119c48fb76c7 100644 --- a/crates/re_types_builder/src/codegen/docs/mod.rs +++ b/crates/re_types_builder/src/codegen/docs/mod.rs @@ -9,8 +9,6 @@ use crate::{ type ObjectMap = std::collections::BTreeMap; -use super::common::get_documentation; - macro_rules! putln { ($o:ident) => ( writeln!($o).ok() ); ($o:ident, $($tt:tt)*) => ( writeln!($o, $($tt)*).ok() ); @@ -143,19 +141,16 @@ fn index_page(kind: ObjectKind, order: u64, prelude: &str, objects: &[&Object]) fn object_page(reporter: &Reporter, object: &Object, object_map: &ObjectMap) -> String { let is_unreleased = object.is_attr_set(crate::ATTR_DOCS_UNRELEASED); - let top_level_docs = get_documentation(&object.docs, &[]); + let top_level_docs = object.docs.untagged(); if top_level_docs.is_empty() { reporter.error(&object.virtpath, &object.fqname, "Undocumented object"); } - let examples = object - .docs - .tagged_docs - .get("example") + let examples = &object.docs.doc_lines_tagged("example"); + let examples = examples .iter() - .flat_map(|v| v.iter()) - .map(ExampleInfo::parse) + .map(|line| ExampleInfo::parse(line)) .collect::>(); let mut page = String::new(); diff --git a/crates/re_types_builder/src/codegen/mod.rs b/crates/re_types_builder/src/codegen/mod.rs index 32428d554476..2f5aaa3fee35 100644 --- a/crates/re_types_builder/src/codegen/mod.rs +++ b/crates/re_types_builder/src/codegen/mod.rs @@ -32,7 +32,7 @@ pub(crate) use macros::autogen_warning; // Hack for declaring macros as `pub(cra // --- pub(crate) mod common; -use self::common::{get_documentation, StringExt}; +use self::common::StringExt; mod cpp; mod docs; diff --git a/crates/re_types_builder/src/codegen/python/mod.rs b/crates/re_types_builder/src/codegen/python/mod.rs index 08d806e0c3e0..f41c57804000 100644 --- a/crates/re_types_builder/src/codegen/python/mod.rs +++ b/crates/re_types_builder/src/codegen/python/mod.rs @@ -1157,7 +1157,7 @@ fn quote_obj_docs(obj: &Object) -> String { } fn lines_from_docs(docs: &Docs) -> Vec { - let mut lines = crate::codegen::get_documentation(docs, &["py", "python"]); + let mut lines = docs.doc_lines_for_untagged_and("py"); let examples = collect_snippets_for_api_docs(docs, "py", true).unwrap(); if !examples.is_empty() { @@ -1208,7 +1208,7 @@ fn quote_doc_from_fields(objects: &Objects, fields: &Vec) -> String let mut lines = vec!["Must be one of:".to_owned(), String::new()]; for field in fields { - let mut content = crate::codegen::get_documentation(&field.docs, &["py", "python"]); + let mut content = field.docs.doc_lines_for_untagged_and("py"); for line in &mut content { if line.starts_with(char::is_whitespace) { line.remove(0); @@ -1250,7 +1250,7 @@ fn quote_union_kind_from_fields(fields: &Vec) -> String { let mut lines = vec!["Possible values:".to_owned(), String::new()]; for field in fields { - let mut content = crate::codegen::get_documentation(&field.docs, &["py", "python"]); + let mut content = field.docs.doc_lines_for_untagged_and("py"); for line in &mut content { if line.starts_with(char::is_whitespace) { line.remove(0); @@ -1950,7 +1950,8 @@ fn quote_init_method( obj.fields .iter() .filter_map(|field| { - if field.docs.doc.is_empty() { + let doc_content = field.docs.doc_lines_for_untagged_and("py"); + if doc_content.is_empty() { if !field.is_testing() && obj.fields.len() > 1 { reporter.error( &field.virtpath, @@ -1960,8 +1961,6 @@ fn quote_init_method( } None } else { - let doc_content = - crate::codegen::get_documentation(&field.docs, &["py", "python"]); Some(format!( "{}:\n {}", field.name, diff --git a/crates/re_types_builder/src/codegen/rust/api.rs b/crates/re_types_builder/src/codegen/rust/api.rs index fd7966e76afa..a89613137681 100644 --- a/crates/re_types_builder/src/codegen/rust/api.rs +++ b/crates/re_types_builder/src/codegen/rust/api.rs @@ -714,7 +714,7 @@ fn quote_obj_docs(reporter: &Reporter, obj: &Object) -> TokenStream { } fn doc_as_lines(reporter: &Reporter, virtpath: &str, fqname: &str, docs: &Docs) -> Vec { - let mut lines = crate::codegen::get_documentation(docs, &["rs", "rust"]); + let mut lines = docs.doc_lines_for_untagged_and("rs"); let examples = collect_snippets_for_api_docs(docs, "rs", true) .map_err(|err| reporter.error(virtpath, fqname, err)) diff --git a/crates/re_types_builder/src/docs.rs b/crates/re_types_builder/src/docs.rs new file mode 100644 index 000000000000..3bfe9b2cf339 --- /dev/null +++ b/crates/re_types_builder/src/docs.rs @@ -0,0 +1,110 @@ +/// A high-level representation of a flatbuffers object's documentation. +#[derive(Debug, Clone)] +pub struct Docs { + /// All docmentation lines, including the leading tag, if any. + /// + /// If the tag is the empty string, it means the line is untagged. + /// + /// Each line excludes the leading space and trailing newline. + /// * `/// COMMENT\n` => `("", "COMMENT")` + /// * `/// \py COMMENT\n` => `("py", "COMMENT")`. + lines: Vec<(String, String)>, +} + +impl Docs { + pub fn from_raw_docs( + docs: Option>>, + ) -> Self { + let parse_line = |line: &str| { + if let Some(line) = line.strip_prefix(" \\") { + // \tagged comment + let tag = line.split_whitespace().next().unwrap().to_owned(); + let line = &line[tag.len()..]; + if let Some(line) = line.strip_prefix(' ') { + // Removed space between tag and comment. + (tag, line.to_owned()) + } else { + assert!(line.is_empty()); + (tag, String::new()) + } + } else if let Some(line) = line.strip_prefix(' ') { + // Removed space between `///` and comment. + (String::new(), line.to_owned()) + } else { + assert!( + line.is_empty(), + "Comments should start with a single space; found {line:?}" + ); + (String::new(), String::new()) + } + }; + + let lines: Vec<(String, String)> = docs + .into_iter() + .flat_map(|doc| doc.into_iter()) + .map(parse_line) + .collect(); + + for (tag, comment) in &lines { + assert!( + matches!(tag.as_str(), "" | "example" | "cpp" | "py" | "rs"), + "Unsupported tag: '\\{tag} {comment}'" + ); + } + + Self { lines } + } + + /// Get all doc lines that start with the given tag. + /// + /// For instance, pass `"example"` to get all lines that start with `"\example"`. + pub fn doc_lines_tagged(&self, tag: &str) -> Vec<&str> { + self.lines_with_tag_matching(|t| t == tag) + } + + /// Get all doc lines that are untagged. + pub fn untagged(&self) -> Vec { + self.lines_with_tag_matching(|t| t.is_empty()) + .iter() + .map(|&s| s.to_owned()) + .collect() + } + + /// Get all doc lines that are untagged, or match the given tag. + /// + /// For instance, pass `"py"` to get all lines that are untagged or starta with `"\py"`. + pub fn doc_lines_for_untagged_and(&self, tag: &str) -> Vec { + self.lines_with_tag_matching(|t| t.is_empty() || t == tag) + .iter() + .map(|&s| s.to_owned()) + .collect() + } + + pub fn lines_with_tag_matching(&self, include_tag: impl Fn(&str) -> bool) -> Vec<&str> { + let mut lines: Vec<&str> = self + .lines + .iter() + .filter_map(|(tag, line)| { + if include_tag(tag) { + Some(line.as_str()) + } else { + None + } + }) + .collect(); + + // NOTE: remove duplicated blank lines. + lines.dedup(); + + // NOTE: remove trailing blank lines. + while let Some(line) = lines.last() { + if line.is_empty() { + lines.pop(); + } else { + break; + } + } + + lines + } +} diff --git a/crates/re_types_builder/src/lib.rs b/crates/re_types_builder/src/lib.rs index 17d6b1615fb7..80b0ca697ab9 100644 --- a/crates/re_types_builder/src/lib.rs +++ b/crates/re_types_builder/src/lib.rs @@ -139,6 +139,8 @@ mod format; #[allow(clippy::unimplemented)] mod objects; +mod docs; + pub mod report; /// In-memory generated files. @@ -147,15 +149,18 @@ pub mod report; /// etc), and finally written to disk by the I/O pass. pub type GeneratedFiles = std::collections::BTreeMap; -pub use self::arrow_registry::{ArrowRegistry, LazyDatatype, LazyField}; -pub use self::codegen::{ - CodeGenerator, CppCodeGenerator, DocsCodeGenerator, PythonCodeGenerator, RustCodeGenerator, -}; -pub use self::format::{CodeFormatter, CppCodeFormatter, PythonCodeFormatter, RustCodeFormatter}; -pub use self::objects::{ - Attributes, Docs, ElementType, Object, ObjectClass, ObjectField, ObjectKind, Objects, Type, +pub use self::{ + arrow_registry::{ArrowRegistry, LazyDatatype, LazyField}, + codegen::{ + CodeGenerator, CppCodeGenerator, DocsCodeGenerator, PythonCodeGenerator, RustCodeGenerator, + }, + docs::Docs, + format::{CodeFormatter, CppCodeFormatter, PythonCodeFormatter, RustCodeFormatter}, + objects::{ + Attributes, ElementType, Object, ObjectClass, ObjectField, ObjectKind, Objects, Type, + }, + report::{Report, Reporter}, }; -pub use self::report::{Report, Reporter}; // --- Attributes --- diff --git a/crates/re_types_builder/src/objects.rs b/crates/re_types_builder/src/objects.rs index e332fb9e89ca..d368f7a4eab9 100644 --- a/crates/re_types_builder/src/objects.rs +++ b/crates/re_types_builder/src/objects.rs @@ -3,15 +3,15 @@ //! The semantic pass transforms the low-level raw reflection data into higher level types that //! are much easier to inspect and manipulate / friendler to work with. -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; use anyhow::Context as _; use camino::{Utf8Path, Utf8PathBuf}; use itertools::Itertools; use crate::{ - root_as_schema, FbsBaseType, FbsEnum, FbsEnumVal, FbsField, FbsKeyValue, FbsObject, FbsSchema, - FbsType, Reporter, ATTR_RERUN_OVERRIDE_TYPE, + root_as_schema, Docs, FbsBaseType, FbsEnum, FbsEnumVal, FbsField, FbsKeyValue, FbsObject, + FbsSchema, FbsType, Reporter, ATTR_RERUN_OVERRIDE_TYPE, }; // --- @@ -253,150 +253,6 @@ impl ObjectKind { } } -/// A high-level representation of a flatbuffers object's documentation. -#[derive(Debug, Clone)] -pub struct Docs { - /// General documentation for the object. - /// - /// Each entry in the vector is a line of comment, - /// excluding the leading space end trailing newline, - /// i.e. the `COMMENT` from `/// COMMENT\n` - /// - /// See also [`Docs::tagged_docs`]. - pub doc: Vec, - - /// Tagged documentation for the object. - /// - /// Each entry in the vector is a line of comment, - /// excluding the leading space end trailing newline, - /// i.e. the `COMMENT` from `/// \py COMMENT\n` - /// - /// E.g. the following will be associated with the `py` tag: - /// ```flatbuffers - /// /// \py Something something about how this fields behave in python. - /// my_field: uint32, - /// ``` - /// - /// See also [`Docs::doc`]. - pub tagged_docs: BTreeMap>, - - /// Contents of all the files included using `\include:`. - pub included_files: BTreeMap, -} - -impl Docs { - fn from_raw_docs( - filepath: &Utf8Path, - docs: Option>>, - ) -> Self { - let mut included_files = BTreeMap::default(); - - let include_file = |included_files: &mut BTreeMap<_, _>, raw_path: &str| { - let path: Utf8PathBuf = raw_path - .parse() - .with_context(|| format!("couldn't parse included path: {raw_path:?}")) - .unwrap(); - - let path = filepath.parent().unwrap().join(path); - - included_files - .entry(path.clone()) - .or_insert_with(|| { - std::fs::read_to_string(&path) - .with_context(|| { - format!("couldn't parse read file at included path: {path:?}") - }) - .unwrap() - }) - .clone() - }; - - // language-agnostic docs - let doc = docs - .into_iter() - .flat_map(|doc| doc.into_iter()) - // NOTE: discard tagged lines! - .filter(|line| !line.trim().starts_with('\\')) - .flat_map(|line| { - assert!(!line.ends_with('\n')); - assert!(!line.ends_with('\r')); - - if let Some((_, path)) = line.split_once("\\include:") { - include_file(&mut included_files, path) - .lines() - .map(|line| line.to_owned()) - .collect_vec() - } else if let Some(line) = line.strip_prefix(' ') { - // Removed space between `///` and comment. - vec![line.to_owned()] - } else { - assert!( - line.is_empty(), - "{filepath}: Comments should start with a single space; found {line:?}" - ); - vec![line.to_owned()] - } - }) - .collect::>(); - - // tagged docs, e.g. `\py this only applies to python!` - let tagged_docs = { - let tagged_lines = docs - .into_iter() - .flat_map(|doc| doc.into_iter()) - // NOTE: discard _un_tagged lines! - .filter_map(|line| { - let trimmed = line.trim(); - trimmed.starts_with('\\').then(|| { - let tag = trimmed.split_whitespace().next().unwrap(); - let line = &trimmed[tag.len()..]; - let tag = tag[1..].to_owned(); - if let Some(line) = line.strip_prefix(' ') { - // Removed space between tag and comment. - (tag, line.to_owned()) - } else { - assert!(line.is_empty()); - (tag, String::default()) - } - }) - }) - .flat_map(|(tag, line)| { - if let Some((_, path)) = line.split_once("\\include:") { - include_file(&mut included_files, path) - .lines() - .map(|line| (tag.clone(), line.to_owned())) - .collect_vec() - } else { - vec![(tag, line)] - } - }) - .collect::>(); - - let all_tags: HashSet<_> = tagged_lines.iter().map(|(tag, _)| tag).collect(); - let mut tagged_docs = BTreeMap::new(); - - for cur_tag in all_tags { - tagged_docs.insert( - cur_tag.clone(), - tagged_lines - .iter() - .filter(|(tag, _)| cur_tag == tag) - .map(|(_, line)| line.clone()) - .collect(), - ); - } - - tagged_docs - }; - - Self { - doc, - tagged_docs, - included_files, - } - } -} - /// A high-level representation of a flatbuffers object, which can be either a struct, a union or /// an enum. #[derive(Debug, Clone)] @@ -471,7 +327,7 @@ impl Object { "Bad filepath: {filepath:?}" ); - let docs = Docs::from_raw_docs(&filepath, obj.documentation()); + let docs = Docs::from_raw_docs(obj.documentation()); let attrs = Attributes::from_raw_attrs(obj.attributes()); let kind = ObjectKind::from_pkg_name(&pkg_name, &attrs); @@ -551,7 +407,7 @@ impl Object { .unwrap(); let filepath = filepath_from_declaration_file(include_dir_path, &virtpath); - let docs = Docs::from_raw_docs(&filepath, enm.documentation()); + let docs = Docs::from_raw_docs(enm.documentation()); let attrs = Attributes::from_raw_attrs(enm.attributes()); let kind = ObjectKind::from_pkg_name(&pkg_name, &attrs); @@ -791,7 +647,7 @@ impl ObjectField { .unwrap(); let filepath = filepath_from_declaration_file(include_dir_path, &virtpath); - let docs = Docs::from_raw_docs(&filepath, field.documentation()); + let docs = Docs::from_raw_docs(field.documentation()); let attrs = Attributes::from_raw_attrs(field.attributes()); @@ -839,7 +695,7 @@ impl ObjectField { .unwrap(); let filepath = filepath_from_declaration_file(include_dir_path, &virtpath); - let docs = Docs::from_raw_docs(&filepath, val.documentation()); + let docs = Docs::from_raw_docs(val.documentation()); let attrs = Attributes::from_raw_attrs(val.attributes()); diff --git a/docs/content/reference/types/archetypes/image.md b/docs/content/reference/types/archetypes/image.md index e67efab56d48..04aac2a6cdc0 100644 --- a/docs/content/reference/types/archetypes/image.md +++ b/docs/content/reference/types/archetypes/image.md @@ -12,6 +12,9 @@ The shape of the `TensorData` must be mappable to: Leading and trailing unit-dimensions are ignored, so that `1x640x480x3x1` is treated as a `640x480x3` RGB image. +Rerun also supports compressed image encoded as JPEG, N12, and YUY2. +Using these formats can save a lot of bandwidth and memory. + ## Components **Required**: [`TensorData`](../components/tensor_data.md) diff --git a/rerun_cpp/src/rerun/archetypes/image.hpp b/rerun_cpp/src/rerun/archetypes/image.hpp index 1b98de979f72..95c7ba3a25a1 100644 --- a/rerun_cpp/src/rerun/archetypes/image.hpp +++ b/rerun_cpp/src/rerun/archetypes/image.hpp @@ -27,6 +27,10 @@ namespace rerun::archetypes { /// Leading and trailing unit-dimensions are ignored, so that /// `1x640x480x3x1` is treated as a `640x480x3` RGB image. /// + /// Rerun also supports compressed image encoded as JPEG, N12, and YUY2. + /// Using these formats can save a lot of bandwidth and memory. + /// See [`rerun::datatypes::TensorBuffer`] for more. + /// /// Since the underlying `rerun::datatypes::TensorData` uses `rerun::Collection` internally, /// data can be passed in without a copy from raw pointers or by reference from `std::vector`/`std::array`/c-arrays. /// If needed, this "borrow-behavior" can be extended by defining your own `rerun::CollectionAdapter`. diff --git a/rerun_cpp/src/rerun/datatypes/tensor_buffer.hpp b/rerun_cpp/src/rerun/datatypes/tensor_buffer.hpp index 9d73a88eac21..f1a40ad5fc0b 100644 --- a/rerun_cpp/src/rerun/datatypes/tensor_buffer.hpp +++ b/rerun_cpp/src/rerun/datatypes/tensor_buffer.hpp @@ -85,7 +85,7 @@ namespace rerun::datatypes { /// First comes entire image in Y, followed by interleaved lines ordered as U0, V0, U1, V1, etc. rerun::Collection nv12; - /// YUY2, also known as YUYV is a YUV 4:2:2 chrome downsampled format with 8 bits per channel. + /// YUY2, also known as YUYV is a YUV 4:2:2 chroma downsampled format with 8 bits per channel. /// /// The order of the channels is Y0, U0, Y1, V0. rerun::Collection yuy2; @@ -420,7 +420,7 @@ namespace rerun::datatypes { return self; } - /// YUY2, also known as YUYV is a YUV 4:2:2 chrome downsampled format with 8 bits per channel. + /// YUY2, also known as YUYV is a YUV 4:2:2 chroma downsampled format with 8 bits per channel. /// /// The order of the channels is Y0, U0, Y1, V0. static TensorBuffer yuy2(rerun::Collection yuy2) { diff --git a/rerun_py/rerun_sdk/rerun/archetypes/image.py b/rerun_py/rerun_sdk/rerun/archetypes/image.py index f92faa9add66..0f3bb50cdb27 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/image.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/image.py @@ -30,7 +30,10 @@ class Image(ImageExt, Archetype): Leading and trailing unit-dimensions are ignored, so that `1x640x480x3x1` is treated as a `640x480x3` RGB image. - For an easy way to pass in image formats or encoded images, see [`rerun.ImageEncoded`][]. + Rerun also supports compressed image encoded as JPEG, N12, and YUY2. + Using these formats can save a lot of bandwidth and memory. + To compress an image, use [`rerun.Image.compress`][]. + To pass in an already encoded image, use [`rerun.ImageEncoded`][]. Example ------- diff --git a/rerun_py/rerun_sdk/rerun/datatypes/tensor_buffer.py b/rerun_py/rerun_sdk/rerun/datatypes/tensor_buffer.py index b608bf32c8cd..34cd5165a0be 100644 --- a/rerun_py/rerun_sdk/rerun/datatypes/tensor_buffer.py +++ b/rerun_py/rerun_sdk/rerun/datatypes/tensor_buffer.py @@ -88,7 +88,7 @@ class TensorBuffer(TensorBufferExt): First comes entire image in Y, followed by interleaved lines ordered as U0, V0, U1, V1, etc. * YUY2 (npt.NDArray[np.uint8]): - YUY2, also known as YUYV is a YUV 4:2:2 chrome downsampled format with 8 bits per channel. + YUY2, also known as YUYV is a YUV 4:2:2 chroma downsampled format with 8 bits per channel. The order of the channels is Y0, U0, Y1, V0. """ @@ -141,7 +141,7 @@ class TensorBuffer(TensorBufferExt): First comes entire image in Y, followed by interleaved lines ordered as U0, V0, U1, V1, etc. * "YUY2": - YUY2, also known as YUYV is a YUV 4:2:2 chrome downsampled format with 8 bits per channel. + YUY2, also known as YUYV is a YUV 4:2:2 chroma downsampled format with 8 bits per channel. The order of the channels is Y0, U0, Y1, V0. """