Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: improve discoverability of image compression #5675

Merged
merged 9 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion crates/re_types/definitions/rerun/archetypes/image.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
4 changes: 2 additions & 2 deletions crates/re_types/definitions/rerun/archetypes/points2d.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---
Expand Down
4 changes: 2 additions & 2 deletions crates/re_types/definitions/rerun/archetypes/points3d.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 4 additions & 0 deletions crates/re_types/src/archetypes/image.rs

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

2 changes: 1 addition & 1 deletion crates/re_types/src/datatypes/tensor_buffer.rs

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

190 changes: 76 additions & 114 deletions crates/re_types_builder/src/codegen/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,6 @@ fn is_blank<T: AsRef<str>>(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<String> {
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.
Expand All @@ -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<str>) -> 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,
}
}
}

Expand Down Expand Up @@ -314,46 +281,41 @@ pub fn collect_snippets_for_api_docs<'a>(
extension: &str,
required: bool,
) -> anyhow::Result<Vec<Example<'a>>> {
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<Example<'a>> = 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)
Expand Down
2 changes: 1 addition & 1 deletion crates/re_types_builder/src/codegen/cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2265,7 +2265,7 @@ fn quote_field_docs(field: &ObjectField) -> TokenStream {
}

fn lines_from_docs(docs: &Docs) -> Vec<String> {
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();
Expand Down
13 changes: 4 additions & 9 deletions crates/re_types_builder/src/codegen/docs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ use crate::{

type ObjectMap = std::collections::BTreeMap<String, Object>;

use super::common::get_documentation;

macro_rules! putln {
($o:ident) => ( writeln!($o).ok() );
($o:ident, $($tt:tt)*) => ( writeln!($o, $($tt)*).ok() );
Expand Down Expand Up @@ -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::<Vec<_>>();

let mut page = String::new();
Expand Down
2 changes: 1 addition & 1 deletion crates/re_types_builder/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 5 additions & 6 deletions crates/re_types_builder/src/codegen/python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ fn quote_obj_docs(obj: &Object) -> String {
}

fn lines_from_docs(docs: &Docs) -> Vec<String> {
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() {
Expand Down Expand Up @@ -1208,7 +1208,7 @@ fn quote_doc_from_fields(objects: &Objects, fields: &Vec<ObjectField>) -> 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);
Expand Down Expand Up @@ -1250,7 +1250,7 @@ fn quote_union_kind_from_fields(fields: &Vec<ObjectField>) -> 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);
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down