From 2f3b1bf943b8e2df2339ac3bee247a558ed57e1f Mon Sep 17 00:00:00 2001 From: Benedikt Schwab Date: Tue, 9 Jul 2024 13:30:15 +0200 Subject: [PATCH] refactoring --- .github/workflows/ci.yml | 35 +++ .gitignore | 50 ++++- Cargo.toml | 29 +-- README.md | 9 + crates/evoxel-cli/Cargo.toml | 4 +- crates/evoxel-cli/README.md | 10 + crates/evoxel-cli/src/arguments.rs | 22 ++ crates/evoxel-cli/src/commands/mod.rs | 1 + crates/evoxel-cli/src/commands/test.rs | 44 ++++ crates/evoxel-cli/src/main.rs | 37 ++-- crates/evoxel-core/Cargo.toml | 5 +- crates/evoxel-core/README.md | 10 + crates/evoxel-core/src/data_frame_utils.rs | 39 ++++ crates/evoxel-core/src/error.rs | 19 ++ crates/evoxel-core/src/info.rs | 57 +++++ crates/evoxel-core/src/lib.rs | 13 +- crates/evoxel-core/src/voxel_grid.rs | 241 ++++++++++++--------- crates/evoxel-io/Cargo.toml | 6 +- crates/evoxel-io/README.md | 10 + crates/evoxel-io/src/document.rs | 26 ++- crates/evoxel-io/src/error.rs | 4 +- crates/evoxel-io/src/lib.rs | 14 +- crates/evoxel-io/src/read.rs | 29 +-- crates/evoxel-io/src/write.rs | 62 ++++++ crates/evoxel-io/src/write_impl.rs | 39 ++++ crates/evoxel-transform/Cargo.toml | 20 ++ crates/evoxel-transform/README.md | 10 + crates/evoxel-transform/src/error.rs | 14 ++ crates/evoxel-transform/src/filter.rs | 127 +++++++++++ crates/evoxel-transform/src/lib.rs | 21 ++ crates/evoxel-transform/src/translate.rs | 20 ++ crates/evoxel/Cargo.toml | 7 +- crates/evoxel/README.md | 10 + crates/evoxel/src/lib.rs | 40 ++-- 34 files changed, 894 insertions(+), 190 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 crates/evoxel-cli/README.md create mode 100644 crates/evoxel-cli/src/arguments.rs create mode 100644 crates/evoxel-cli/src/commands/mod.rs create mode 100644 crates/evoxel-cli/src/commands/test.rs create mode 100644 crates/evoxel-core/README.md create mode 100644 crates/evoxel-core/src/data_frame_utils.rs create mode 100644 crates/evoxel-core/src/error.rs create mode 100644 crates/evoxel-core/src/info.rs create mode 100644 crates/evoxel-io/README.md create mode 100644 crates/evoxel-io/src/write_impl.rs create mode 100644 crates/evoxel-transform/Cargo.toml create mode 100644 crates/evoxel-transform/README.md create mode 100644 crates/evoxel-transform/src/error.rs create mode 100644 crates/evoxel-transform/src/filter.rs create mode 100644 crates/evoxel-transform/src/lib.rs create mode 100644 crates/evoxel-transform/src/translate.rs create mode 100644 crates/evoxel/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9c18a84 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: + - main + - develop + pull_request: {} + + +jobs: + + build: + name: cargo build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Build + run: cargo build + + test: + name: cargo test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt clippy + - name: rustfmt + run: cargo fmt --all -- --check + - name: clippy + run: cargo clippy + - name: test + run: cargo test --verbose diff --git a/.gitignore b/.gitignore index 373271c..5ee7f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ -### JetBrains template +# Created by https://www.toptal.com/developers/gitignore/api/rust,macos,jetbrains+all +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,macos,jetbrains+all + +### JetBrains+all ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 @@ -77,7 +80,49 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser -### Rust template +### JetBrains+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Rust ### # Generated by Cargo # will have compiled files and executables debug/ @@ -93,3 +138,4 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb +# End of https://www.toptal.com/developers/gitignore/api/rust,macos,jetbrains+all diff --git a/Cargo.toml b/Cargo.toml index ca48e53..f73c19e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,23 +3,26 @@ members = [ "crates/*" ] +resolver = "2" + [workspace.package] -version = "0.0.1-alpha.1" -authors = ["Benedikt Schwab"] +version = "0.0.1-alpha.3" +authors = ["Benedikt Schwab "] edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/tum-gis/evoxel" [workspace.dependencies] -ecoord = { version = "0.0.1-alpha", registry = "custom" } +ecoord = { version = "0.0.1-alpha.3" } -thiserror = "1.0.34" -tracing = "0.1.30" -tracing-subscriber = "0.3.8" -polars = "0.26.1" -ndarray = "0.15" -serde = "1.0" -serde_json = "1.0" -rayon = "1.5" -nalgebra = "0.31.1" -chrono = "0.4.22" +clap = "4.5.9" +thiserror = "1.0.61" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +polars = "0.41.3" +ndarray = "0.15.6" +serde = "1.0.204" +serde_json = "1.0.120" +rayon = "1.10.0" +nalgebra = "0.33.0" +chrono = "0.4.38" diff --git a/README.md b/README.md index 6936cdb..f846fb3 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ # evoxel + +A Rust library for processing 3D voxel grids. + +> [!WARNING] +> The library is at an early stage of development. + +## Contributing + +The library is developed at the [TUM Chair of Geoinformatics](https://github.com/tum-gis) and contributions are highly welcome. diff --git a/crates/evoxel-cli/Cargo.toml b/crates/evoxel-cli/Cargo.toml index eb16b3a..ceb77c1 100644 --- a/crates/evoxel-cli/Cargo.toml +++ b/crates/evoxel-cli/Cargo.toml @@ -9,7 +9,9 @@ description = "CLI tool for evoxel operations" [dependencies] -evoxel = { version = "0.0.1-alpha.1", path = "../evoxel", registry = "custom" } +evoxel = { version = "0.0.1-alpha.3", path = "../evoxel" } +clap = { workspace = true, features = ["derive"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } +nalgebra = { workspace = true } diff --git a/crates/evoxel-cli/README.md b/crates/evoxel-cli/README.md new file mode 100644 index 0000000..f4b6ba2 --- /dev/null +++ b/crates/evoxel-cli/README.md @@ -0,0 +1,10 @@ +# evoxel-cli + +CLI tool for processing 3D voxel grids. + +> [!WARNING] +> The library is at an early stage of development. + +## Contributing + +The library is developed at the [TUM Chair of Geoinformatics](https://github.com/tum-gis) and contributions are highly welcome. diff --git a/crates/evoxel-cli/src/arguments.rs b/crates/evoxel-cli/src/arguments.rs new file mode 100644 index 0000000..007bdc9 --- /dev/null +++ b/crates/evoxel-cli/src/arguments.rs @@ -0,0 +1,22 @@ +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[clap(author, version, about, long_about = None, propagate_version = true)] +pub struct Arguments { + #[clap(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Run some tests + Test { + /// Input directory + #[clap(long)] + input_directory_path: String, + + /// Output directory + #[clap(long)] + output_directory_path: String, + }, +} diff --git a/crates/evoxel-cli/src/commands/mod.rs b/crates/evoxel-cli/src/commands/mod.rs new file mode 100644 index 0000000..bcc92d7 --- /dev/null +++ b/crates/evoxel-cli/src/commands/mod.rs @@ -0,0 +1 @@ +pub(crate) mod test; diff --git a/crates/evoxel-cli/src/commands/test.rs b/crates/evoxel-cli/src/commands/test.rs new file mode 100644 index 0000000..9544115 --- /dev/null +++ b/crates/evoxel-cli/src/commands/test.rs @@ -0,0 +1,44 @@ +use evoxel::io::{EvoxelReader, EvoxelWriter}; +use nalgebra::Point3; +use std::path::Path; +use std::time::Instant; +use tracing::info; + +pub fn run(input_directory_path: impl AsRef, output_directory_path: impl AsRef) { + // let path = PathBuf::from("/submap_0"); + let start = Instant::now(); + let voxel_grid = EvoxelReader::new(input_directory_path).finish().unwrap(); + let duration = start.elapsed(); + info!( + "Read voxel grid with {} cells in {:?}.", + voxel_grid.size(), + duration + ); + + let voxel_grid = evoxel::transform::aggregate_by_index(&voxel_grid).unwrap(); + let voxel_grid = evoxel::transform::filter_by_count(&voxel_grid, 3).unwrap(); + let voxel_grid = evoxel::transform::explode(&voxel_grid).unwrap(); + let voxel_grid = evoxel::transform::filter_by_index_bounds( + &voxel_grid, + Point3::new(676, 95, 0), + Point3::new(1271, 135, 86), + ) + .unwrap(); + + info!("Start"); + let start = Instant::now(); + let c = voxel_grid.get_all_cell_indices_in_local_frame(); + let duration = start.elapsed(); + info!("Calculated {} points in {:?}.", c.len(), duration); + //let all = voxel_grid.get_all_center_points(); + + info!( + "Start writing voxel grid with {} cells to {}", + voxel_grid.size(), + output_directory_path.as_ref().display() + ); + EvoxelWriter::new(output_directory_path) + .with_compressed(false) + .finish(&voxel_grid) + .expect("should work"); +} diff --git a/crates/evoxel-cli/src/main.rs b/crates/evoxel-cli/src/main.rs index c453b83..85f3612 100644 --- a/crates/evoxel-cli/src/main.rs +++ b/crates/evoxel-cli/src/main.rs @@ -1,26 +1,23 @@ -use evoxel::io::EvoxelReader; -use std::path::PathBuf; -use std::time::Instant; -use tracing::info; +mod arguments; +mod commands; + +use crate::arguments::{Arguments, Commands}; +use clap::Parser; +use std::path::{Path, PathBuf}; fn main() { tracing_subscriber::fmt::init(); - info!("Hello, world!"); + let arguments = Arguments::parse(); - let path = PathBuf::from("/submap_0"); - let start = Instant::now(); - let voxel_grid = EvoxelReader::new(path).finish().unwrap(); - let duration = start.elapsed(); - info!( - "Read voxel grid with {} cells in {:?}.", - voxel_grid.size(), - duration - ); + match &arguments.command { + Commands::Test { + input_directory_path, + output_directory_path, + } => { + let input_directory_path = Path::new(input_directory_path).canonicalize().unwrap(); + let output_directory_path = PathBuf::from(output_directory_path); - info!("Start"); - let start = Instant::now(); - let c = voxel_grid.get_all_cell_indices_in_local_frame(); - let duration = start.elapsed(); - info!("Calculated {} points in {:?}.", c.len(), duration); - //let all = voxel_grid.get_all_center_points(); + commands::test::run(input_directory_path, output_directory_path); + } + }; } diff --git a/crates/evoxel-core/Cargo.toml b/crates/evoxel-core/Cargo.toml index 9f644de..93fc038 100644 --- a/crates/evoxel-core/Cargo.toml +++ b/crates/evoxel-core/Cargo.toml @@ -5,13 +5,14 @@ authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -description = "Core of the evoxel library" +description = "Core primitives and operations for processing 3D voxel grids." [dependencies] ecoord = { workspace = true } -polars = { workspace = true, features = ["lazy", "csv-file", "parquet", "ndarray", "rows"] } +thiserror = { workspace = true } +polars = { workspace = true, features = ["lazy", "parquet", "ndarray", "rows"] } ndarray = { workspace = true } rayon = { workspace = true } nalgebra = { workspace = true } diff --git a/crates/evoxel-core/README.md b/crates/evoxel-core/README.md new file mode 100644 index 0000000..1ae543f --- /dev/null +++ b/crates/evoxel-core/README.md @@ -0,0 +1,10 @@ +# evoxel-core + +Core primitives and operations for processing 3D voxel grids. + +> [!WARNING] +> The library is at an early stage of development. + +## Contributing + +The library is developed at the [TUM Chair of Geoinformatics](https://github.com/tum-gis) and contributions are highly welcome. diff --git a/crates/evoxel-core/src/data_frame_utils.rs b/crates/evoxel-core/src/data_frame_utils.rs new file mode 100644 index 0000000..b21c892 --- /dev/null +++ b/crates/evoxel-core/src/data_frame_utils.rs @@ -0,0 +1,39 @@ +use crate::Error::{ColumnNameMisMatch, NoData, TypeMisMatch}; +use crate::{Error, VoxelDataColumnNames, VoxelGridInfo}; +use ecoord::ReferenceFrames; +use polars::datatypes::DataType; +use polars::frame::DataFrame; + +pub fn check_data_integrity( + voxel_data: &DataFrame, + _info: &VoxelGridInfo, + _reference_frames: &ReferenceFrames, +) -> Result<(), Error> { + if voxel_data.is_empty() { + return Err(NoData("voxel_data")); + } + + let column_names = voxel_data.get_column_names(); + if column_names[0] != VoxelDataColumnNames::X.as_str() { + return Err(ColumnNameMisMatch); + } + if column_names[1] != VoxelDataColumnNames::Y.as_str() { + return Err(ColumnNameMisMatch); + } + if column_names[2] != VoxelDataColumnNames::Z.as_str() { + return Err(ColumnNameMisMatch); + } + + let data_types = voxel_data.dtypes(); + if data_types[0] != DataType::Int64 { + return Err(TypeMisMatch("x")); + } + if data_types[1] != DataType::Int64 { + return Err(TypeMisMatch("y")); + } + if data_types[2] != DataType::Int64 { + return Err(TypeMisMatch("z")); + } + + Ok(()) +} diff --git a/crates/evoxel-core/src/error.rs b/crates/evoxel-core/src/error.rs new file mode 100644 index 0000000..2e53866 --- /dev/null +++ b/crates/evoxel-core/src/error.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + EcoordError(#[from] ecoord::Error), + #[error(transparent)] + Polars(#[from] polars::error::PolarsError), + + #[error("No data: {0}")] + NoData(&'static str), + #[error("Lengths don't match: {0}")] + ShapeMisMatch(&'static str), + + #[error("Field {0} does not match type")] + TypeMisMatch(&'static str), + #[error("unknown data store error")] + ColumnNameMisMatch, +} diff --git a/crates/evoxel-core/src/info.rs b/crates/evoxel-core/src/info.rs new file mode 100644 index 0000000..59eaeb5 --- /dev/null +++ b/crates/evoxel-core/src/info.rs @@ -0,0 +1,57 @@ +use chrono::{DateTime, Duration, Utc}; +use ecoord::FrameId; + +#[derive(Debug, Clone, PartialEq)] +pub struct VoxelGridInfo { + pub(crate) frame_id: FrameId, + pub(crate) resolution: f64, + pub(crate) start_time: Option>, + pub(crate) stop_time: Option>, + pub(crate) submap_index: Option, +} + +impl VoxelGridInfo { + pub fn new( + frame_id: FrameId, + resolution: f64, + start_time: Option>, + stop_time: Option>, + submap_index: Option, + ) -> Self { + Self { + resolution, + frame_id, + start_time, + stop_time, + submap_index, + } + } + + pub fn frame_id(&self) -> &FrameId { + &self.frame_id + } + + pub fn resolution(&self) -> f64 { + self.resolution + } + + pub fn start_time(&self) -> &Option> { + &self.start_time + } + + pub fn stop_time(&self) -> &Option> { + &self.stop_time + } + + pub fn submap_index(&self) -> Option { + self.submap_index + } + + pub fn duration(&self) -> Option { + if self.start_time.is_some() && self.stop_time.is_some() { + Some(self.stop_time.unwrap() - self.start_time.unwrap()) + } else { + None + } + } +} diff --git a/crates/evoxel-core/src/lib.rs b/crates/evoxel-core/src/lib.rs index e227777..b1e2098 100644 --- a/crates/evoxel-core/src/lib.rs +++ b/crates/evoxel-core/src/lib.rs @@ -1,7 +1,16 @@ +mod data_frame_utils; +mod error; +mod info; pub mod voxel_grid; #[doc(inline)] -pub use voxel_grid::VoxelGrid; +pub use crate::error::Error; #[doc(inline)] -pub use voxel_grid::VoxelGridInfo; +pub use crate::voxel_grid::VoxelGrid; + +#[doc(inline)] +pub use crate::info::VoxelGridInfo; + +#[doc(inline)] +pub use crate::voxel_grid::VoxelDataColumnNames; diff --git a/crates/evoxel-core/src/voxel_grid.rs b/crates/evoxel-core/src/voxel_grid.rs index 5d8c94c..ce97e10 100644 --- a/crates/evoxel-core/src/voxel_grid.rs +++ b/crates/evoxel-core/src/voxel_grid.rs @@ -1,114 +1,178 @@ +use crate::error::Error; +use crate::info::VoxelGridInfo; use chrono::{DateTime, Utc}; use ecoord::{FrameId, ReferenceFrames, TransformId}; use nalgebra::Point3; -use polars::datatypes::DataType; -use polars::prelude::{DataFrame, TakeRandom}; + +use crate::data_frame_utils; +use polars::prelude::DataFrame; use rayon::prelude::*; #[derive(Debug, Clone)] pub struct VoxelGrid { - pub data: DataFrame, - pub info: VoxelGridInfo, - pub frames: ReferenceFrames, + pub(crate) voxel_data: DataFrame, + pub(crate) info: VoxelGridInfo, + pub(crate) reference_frames: ReferenceFrames, } impl VoxelGrid { - pub fn new(data: DataFrame, info: VoxelGridInfo, frames: ReferenceFrames) -> Self { - let column_names = data.get_column_names(); - assert_eq!(column_names[0], "x"); - assert_eq!(column_names[1], "y"); - assert_eq!(column_names[2], "z"); - - let data_types = data.dtypes(); - assert_eq!(data_types[0], DataType::Int64); - assert_eq!(data_types[1], DataType::Int64); - assert_eq!(data_types[2], DataType::Int64); - - Self { data, info, frames } + pub fn new( + voxel_data: DataFrame, + info: VoxelGridInfo, + reference_frames: ReferenceFrames, + ) -> Result { + data_frame_utils::check_data_integrity(&voxel_data, &info, &reference_frames)?; + + Ok(Self { + voxel_data, + info, + reference_frames, + }) + } + + pub fn from_data_frame( + voxel_data: DataFrame, + info: VoxelGridInfo, + reference_frames: ReferenceFrames, + ) -> Result { + /*assert!( + frames.contains_frame(&info.frame_id), + "Reference frames must contain frame id '{}' of point cloud data.", + info.frame_id + );*/ + + data_frame_utils::check_data_integrity(&voxel_data, &info, &reference_frames)?; + Ok(Self { + voxel_data, + info, + reference_frames, + }) + } + + pub fn voxel_data(&self) -> &DataFrame { + &self.voxel_data + } + + pub fn info(&self) -> &VoxelGridInfo { + &self.info + } + pub fn reference_frames(&self) -> &ReferenceFrames { + &self.reference_frames + } + pub fn set_reference_frames(&mut self, reference_frames: ReferenceFrames) { + self.reference_frames = reference_frames; } pub fn size(&self) -> usize { - self.data.height() + self.voxel_data.height() } pub fn min_index(&self) -> Point3 { - let selected_df_row = self.data.min(); - let index_x: i64 = selected_df_row - .column(DataFrameColumnNames::X.as_str()) + let index_x: i64 = self + .voxel_data + .column(VoxelDataColumnNames::X.as_str()) .unwrap() - .i64() + .min() .unwrap() - .get(0) .unwrap(); - let index_y: i64 = selected_df_row - .column(DataFrameColumnNames::Y.as_str()) + let index_y: i64 = self + .voxel_data + .column(VoxelDataColumnNames::Y.as_str()) .unwrap() - .i64() + .min() .unwrap() - .get(0) .unwrap(); - let index_z: i64 = selected_df_row - .column(DataFrameColumnNames::Z.as_str()) + let index_z: i64 = self + .voxel_data + .column(VoxelDataColumnNames::Z.as_str()) .unwrap() - .i64() + .min() .unwrap() - .get(0) .unwrap(); Point3::new(index_x, index_y, index_z) } + pub fn min_local_center_point(&self) -> Point3 { + let min_index = self.min_index(); + Point3::new(min_index.x as f64, min_index.y as f64, min_index.z as f64) + * self.info.resolution + } + + pub fn min_center_point(&self, frame_id: FrameId) -> Result, Error> { + let min_point = self.min_local_center_point(); + let isometry_graph = self.reference_frames.derive_transform_graph(&None, &None)?; + let transform_id = TransformId::new(frame_id, self.info.frame_id.clone()); + let isometry = isometry_graph.get_isometry(&transform_id)?; + + Ok(isometry * min_point) + } + pub fn max_index(&self) -> Point3 { - let selected_df_row = self.data.max(); - let index_x: i64 = selected_df_row - .column(DataFrameColumnNames::X.as_str()) + let index_x: i64 = self + .voxel_data + .column(VoxelDataColumnNames::X.as_str()) .unwrap() - .i64() + .max() .unwrap() - .get(0) .unwrap(); - let index_y: i64 = selected_df_row - .column(DataFrameColumnNames::Y.as_str()) + let index_y: i64 = self + .voxel_data + .column(VoxelDataColumnNames::Y.as_str()) .unwrap() - .i64() + .max() .unwrap() - .get(0) .unwrap(); - let index_z: i64 = selected_df_row - .column(DataFrameColumnNames::Z.as_str()) + let index_z: i64 = self + .voxel_data + .column(VoxelDataColumnNames::Z.as_str()) .unwrap() - .i64() + .max() .unwrap() - .get(0) .unwrap(); Point3::new(index_x, index_y, index_z) } + pub fn max_local_center_point(&self) -> Point3 { + let max_index = self.max_index(); + Point3::new(max_index.x as f64, max_index.y as f64, max_index.z as f64) + * self.info.resolution + } + + pub fn max_center_point(&self, frame_id: FrameId) -> Result, Error> { + let max_point = self.max_local_center_point(); + let isometry_graph = self.reference_frames.derive_transform_graph(&None, &None)?; + let transform_id = TransformId::new(frame_id, self.info.frame_id.clone()); + let isometry = isometry_graph.get_isometry(&transform_id)?; + + Ok(isometry * max_point) + } + /// Returns all cell indices as a vector in the local coordinate frame. /// /// pub fn get_all_cell_indices_in_local_frame(&self) -> Vec> { let x_series = self - .data - .column(DataFrameColumnNames::X.as_str()) + .voxel_data + .column(VoxelDataColumnNames::X.as_str()) .unwrap() .i64() .unwrap(); let y_series = self - .data - .column(DataFrameColumnNames::Y.as_str()) + .voxel_data + .column(VoxelDataColumnNames::Y.as_str()) .unwrap() .i64() .unwrap(); let z_series = self - .data - .column(DataFrameColumnNames::Z.as_str()) + .voxel_data + .column(VoxelDataColumnNames::Z.as_str()) .unwrap() .i64() .unwrap(); - let all_indices: Vec> = (0..self.size() as usize) + let all_indices: Vec> = (0..self.size()) .into_par_iter() .map(|i: usize| { Point3::new( @@ -140,24 +204,24 @@ impl VoxelGrid { pub fn get_cell_index(&self, row_index: usize) -> Point3 { let index_x: i64 = self - .data - .column(DataFrameColumnNames::X.as_str()) + .voxel_data + .column(VoxelDataColumnNames::X.as_str()) .unwrap() .i64() .unwrap() .get(row_index) .unwrap(); let index_y: i64 = self - .data - .column(DataFrameColumnNames::Y.as_str()) + .voxel_data + .column(VoxelDataColumnNames::Y.as_str()) .unwrap() .i64() .unwrap() .get(row_index) .unwrap(); let index_z: i64 = self - .data - .column(DataFrameColumnNames::Z.as_str()) + .voxel_data + .column(VoxelDataColumnNames::Z.as_str()) .unwrap() .i64() .unwrap() @@ -171,7 +235,7 @@ impl VoxelGrid { /// pub fn get_local_frame_id(&self) -> FrameId { //let t: &Transformation = self.frames.transforms().get(); - FrameId::from(self.info.frame_id.clone()) + self.info.frame_id.clone() } pub fn get_local_center_point(&self, row_idx: usize) -> Point3 { @@ -188,72 +252,47 @@ impl VoxelGrid { row_idx: usize, frame_id: &FrameId, timestamp: DateTime, - ) -> Point3 { + ) -> Result, Error> { let local_center_point = self.get_local_center_point(row_idx); let transform_id = TransformId::new(frame_id.clone(), self.get_local_frame_id()); let isometry = self - .frames - .derive_transform_graph(&None, &Some(timestamp)) - .get_isometry(&transform_id); + .reference_frames + .derive_transform_graph(&None, &Some(timestamp))? + .get_isometry(&transform_id)?; // let isometry = self.pose.isometry(); - isometry * local_center_point + Ok(isometry * local_center_point) } pub fn get_all_center_points( &self, frame_id: &FrameId, timestamp: DateTime, - ) -> Vec> { - let a: Vec> = (0..self.size() as i64) - .into_iter() + ) -> Result>, Error> { + let center_points: Vec> = (0..self.size() as i64) .map(|i: i64| self.get_center_point(i as usize, frame_id, timestamp)) - .collect(); - a - } -} - -#[derive(Debug, Clone)] -pub struct VoxelGridInfo { - pub frame_id: String, - pub resolution: f64, - pub start_time: DateTime, - pub stop_time: DateTime, - pub submap_index: i32, -} - -impl VoxelGridInfo { - pub fn new( - frame_id: String, - resolution: f64, - start_time: DateTime, - stop_time: DateTime, - submap_index: i32, - ) -> Self { - Self { - frame_id, - resolution, - start_time, - stop_time, - submap_index, - } + .collect::, Error>>()?; + Ok(center_points) } } -enum DataFrameColumnNames { +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum VoxelDataColumnNames { X, Y, Z, + Count, } -impl DataFrameColumnNames { - fn as_str(&self) -> &'static str { +impl VoxelDataColumnNames { + pub fn as_str(&self) -> &'static str { match self { - DataFrameColumnNames::X => "x", - DataFrameColumnNames::Y => "y", - DataFrameColumnNames::Z => "z", + VoxelDataColumnNames::X => "x", + VoxelDataColumnNames::Y => "y", + VoxelDataColumnNames::Z => "z", + VoxelDataColumnNames::Count => "count", } } } diff --git a/crates/evoxel-io/Cargo.toml b/crates/evoxel-io/Cargo.toml index e3e5f33..2e9183d 100644 --- a/crates/evoxel-io/Cargo.toml +++ b/crates/evoxel-io/Cargo.toml @@ -5,17 +5,17 @@ authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -description = "IO related logic for the evoxel library" +description = "IO operations for processing 3D voxel grids." [dependencies] -evoxel-core = { version = "0.0.1-alpha.1", path = "../evoxel-core", registry = "custom" } +evoxel-core = { version = "0.0.1-alpha.3", path = "../evoxel-core" } ecoord = { workspace = true } thiserror = { workspace = true } chrono = { workspace = true } -polars = { workspace = true, features = ["lazy", "csv-file", "parquet", "ndarray"] } +polars = { workspace = true, features = ["lazy", "parquet", "ndarray"] } ndarray = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/crates/evoxel-io/README.md b/crates/evoxel-io/README.md new file mode 100644 index 0000000..c79c2fc --- /dev/null +++ b/crates/evoxel-io/README.md @@ -0,0 +1,10 @@ +# evoxel-io + +IO operations for processing 3D voxel grids. + +> [!WARNING] +> The library is at an early stage of development. + +## Contributing + +The library is developed at the [TUM Chair of Geoinformatics](https://github.com/tum-gis) and contributions are highly welcome. diff --git a/crates/evoxel-io/src/document.rs b/crates/evoxel-io/src/document.rs index 094da73..22e0263 100644 --- a/crates/evoxel-io/src/document.rs +++ b/crates/evoxel-io/src/document.rs @@ -1,16 +1,28 @@ use chrono::{DateTime, TimeZone, Timelike, Utc}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] -pub struct VoxelGridInfoDocument { - pub frame_id: String, +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct EvoxelInfoDocument { pub resolution: f64, - pub start_timestamp: TimeElement, - pub stop_timestamp: TimeElement, - pub submap_index: i32, + pub frame_id: String, + pub start_timestamp: Option, + pub stop_timestamp: Option, + pub submap_index: Option, +} + +impl EvoxelInfoDocument { + pub fn new(frame_id: String, resolution: f64) -> Self { + Self { + frame_id, + resolution, + start_timestamp: None, + stop_timestamp: None, + submap_index: None, + } + } } -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub struct TimeElement { sec: i64, nanosec: u32, diff --git a/crates/evoxel-io/src/error.rs b/crates/evoxel-io/src/error.rs index 216d034..897709b 100644 --- a/crates/evoxel-io/src/error.rs +++ b/crates/evoxel-io/src/error.rs @@ -3,6 +3,8 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum Error { + #[error(transparent)] + EvoxelError(#[from] evoxel_core::Error), #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] @@ -10,5 +12,5 @@ pub enum Error { #[error(transparent)] Polars(#[from] PolarsError), #[error(transparent)] - Ecoord(#[from] ecoord::io::error::Error), + Ecoord(#[from] ecoord::io::Error), } diff --git a/crates/evoxel-io/src/lib.rs b/crates/evoxel-io/src/lib.rs index 50f2b01..c96c22d 100644 --- a/crates/evoxel-io/src/lib.rs +++ b/crates/evoxel-io/src/lib.rs @@ -2,6 +2,18 @@ mod document; mod error; mod read; mod write; +mod write_impl; #[doc(inline)] -pub use read::EvoxelReader; +pub use crate::error::Error; + +#[doc(inline)] +pub use crate::read::EvoxelReader; + +#[doc(inline)] +pub use crate::write::EvoxelWriter; + +pub const FILE_NAME_VOXEL_DATA_COMPRESSED: &str = "voxel_data.parquet"; +pub const FILE_NAME_VOXEL_DATA_UNCOMPRESSED: &str = "voxel_data.xyz"; +pub const FILE_NAME_INFO: &str = "info.json"; +pub const FILE_NAME_ECOORD: &str = "ecoord.json"; diff --git a/crates/evoxel-io/src/read.rs b/crates/evoxel-io/src/read.rs index 0fb095b..c9ae0d8 100644 --- a/crates/evoxel-io/src/read.rs +++ b/crates/evoxel-io/src/read.rs @@ -1,13 +1,16 @@ -use crate::document::VoxelGridInfoDocument; +use crate::document::EvoxelInfoDocument; use crate::error::Error; -use evoxel_core::voxel_grid::{VoxelGrid, VoxelGridInfo}; +use crate::{FILE_NAME_ECOORD, FILE_NAME_INFO, FILE_NAME_VOXEL_DATA_UNCOMPRESSED}; +use evoxel_core::voxel_grid::VoxelGrid; +use evoxel_core::VoxelGridInfo; +use polars::prelude::LazyFileListReader; use polars::prelude::{all, LazyCsvReader}; use std::fs; use std::path::{Path, PathBuf}; /// `EvoxelReader` sets up a reader for the custom reader data structure. /// -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EvoxelReader { path: PathBuf, } @@ -23,31 +26,31 @@ impl EvoxelReader { // assert!(self.path.is_dir()); // assert!(self.path.exists(), "Path must exist."); - let voxel_grid_data_path = self.path.join("voxel_grid.xyz"); + let voxel_grid_data_path = self.path.join(FILE_NAME_VOXEL_DATA_UNCOMPRESSED); let df = LazyCsvReader::new(voxel_grid_data_path) - .with_delimiter(b' ') + .with_separator(b' ') .finish()? .select([all()]) .collect()?; // let vg = VoxelGrid - let voxel_grid_info_path = self.path.join("info.json"); + let voxel_grid_info_path = self.path.join(FILE_NAME_INFO); let voxel_grid_info_string = fs::read_to_string(voxel_grid_info_path).expect("Unable to read file"); - let voxel_grid_document: VoxelGridInfoDocument = + let voxel_grid_document: EvoxelInfoDocument = serde_json::from_str(&voxel_grid_info_string).expect("Unable to parse file"); let voxel_grid_info = VoxelGridInfo::new( - voxel_grid_document.frame_id, + voxel_grid_document.frame_id.into(), voxel_grid_document.resolution, - voxel_grid_document.start_timestamp.into(), - voxel_grid_document.stop_timestamp.into(), + voxel_grid_document.start_timestamp.map(|t| t.into()), + voxel_grid_document.stop_timestamp.map(|t| t.into()), voxel_grid_document.submap_index, ); - let frames_path = self.path.join("frames.json"); - let frames = ecoord::io::EcoordReader::new(frames_path).finish()?; + let ecoord_path = self.path.join(FILE_NAME_ECOORD); + let reference_frames = ecoord::io::EcoordReader::from_path(ecoord_path)?.finish()?; - let voxel_grid = VoxelGrid::new(df, voxel_grid_info, frames); + let voxel_grid = VoxelGrid::new(df, voxel_grid_info, reference_frames)?; Ok(voxel_grid) } } diff --git a/crates/evoxel-io/src/write.rs b/crates/evoxel-io/src/write.rs index 8b13789..ae6afc7 100644 --- a/crates/evoxel-io/src/write.rs +++ b/crates/evoxel-io/src/write.rs @@ -1 +1,63 @@ +use crate::document::EvoxelInfoDocument; +use crate::error::Error; +use crate::write_impl::{write_to_parquet, write_to_xyz}; +use crate::{ + FILE_NAME_ECOORD, FILE_NAME_INFO, FILE_NAME_VOXEL_DATA_COMPRESSED, + FILE_NAME_VOXEL_DATA_UNCOMPRESSED, +}; +use evoxel_core::VoxelGrid; +use std::fs; +use std::fs::OpenOptions; +use std::path::{Path, PathBuf}; +/// `EvoxelReader` sets up a reader for the custom reader data structure. +/// +#[derive(Debug, Clone)] +pub struct EvoxelWriter { + path: PathBuf, + compressed: bool, +} + +impl EvoxelWriter { + pub fn new(path: impl AsRef) -> Self { + Self { + path: path.as_ref().to_owned(), + compressed: true, + } + } + + pub fn with_compressed(mut self, compressed: bool) -> Self { + self.compressed = compressed; + self + } + + pub fn finish(&self, voxel_grid: &VoxelGrid) -> Result<(), Error> { + fs::create_dir_all(self.path.clone())?; + + if self.compressed { + let parquet_file_path = self.path.join(FILE_NAME_VOXEL_DATA_COMPRESSED); + write_to_parquet(voxel_grid, parquet_file_path)?; + } else { + let xyz_file_path = self.path.join(FILE_NAME_VOXEL_DATA_UNCOMPRESSED); + write_to_xyz(voxel_grid, xyz_file_path)?; + } + + let info_document_path = self.path.join(FILE_NAME_INFO); + let info_document = EvoxelInfoDocument::new( + voxel_grid.info().frame_id().clone().into(), + voxel_grid.info().resolution(), + ); + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(info_document_path)?; + serde_json::to_writer_pretty(file, &info_document)?; + + let ecoord_document_path = self.path.join(FILE_NAME_ECOORD); + ecoord::io::EcoordWriter::from_path(ecoord_document_path)? + .finish(voxel_grid.reference_frames())?; + + Ok(()) + } +} diff --git a/crates/evoxel-io/src/write_impl.rs b/crates/evoxel-io/src/write_impl.rs new file mode 100644 index 0000000..7000140 --- /dev/null +++ b/crates/evoxel-io/src/write_impl.rs @@ -0,0 +1,39 @@ +use crate::error::Error; +use evoxel_core::VoxelGrid; +use polars::io::SerWriter; +use polars::prelude::{CsvWriter, ParquetWriter, StatisticsOptions}; +use std::fs::OpenOptions; +use std::path::Path; + +pub fn write_to_xyz(voxel_grid: &VoxelGrid, file_path: impl AsRef) -> Result<(), Error> { + // fs::remove_file(file_path).expect("File delete failed"); + + let mut voxel_data = voxel_grid.voxel_data().clone(); + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(file_path)?; + CsvWriter::new(file) + .with_separator(b' ') + .finish(&mut voxel_data)?; + + Ok(()) +} + +pub fn write_to_parquet(voxel_grid: &VoxelGrid, file_path: impl AsRef) -> Result<(), Error> { + let mut voxel_grid = voxel_grid.voxel_data().clone(); + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(file_path)?; + + ParquetWriter::new(file) + .with_statistics(StatisticsOptions::default()) + .finish(&mut voxel_grid)?; + + Ok(()) +} diff --git a/crates/evoxel-transform/Cargo.toml b/crates/evoxel-transform/Cargo.toml new file mode 100644 index 0000000..663842f --- /dev/null +++ b/crates/evoxel-transform/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "evoxel-transform" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Supplementary operations for processing 3D voxel grids." + + +[dependencies] +evoxel-core = { version = "0.0.1-alpha.3", path = "../evoxel-core" } + +ecoord = { workspace = true } + +thiserror = { workspace = true } +polars = { workspace = true, features = ["lazy", "parquet", "ndarray", "partition_by"] } +chrono = { workspace = true } +rayon = { workspace = true } +nalgebra = { workspace = true } diff --git a/crates/evoxel-transform/README.md b/crates/evoxel-transform/README.md new file mode 100644 index 0000000..9834695 --- /dev/null +++ b/crates/evoxel-transform/README.md @@ -0,0 +1,10 @@ +# evoxel-transform + +Supplementary operations for processing 3D voxel grids. + +> [!WARNING] +> The library is at an early stage of development. + +## Contributing + +The library is developed at the [TUM Chair of Geoinformatics](https://github.com/tum-gis) and contributions are highly welcome. diff --git a/crates/evoxel-transform/src/error.rs b/crates/evoxel-transform/src/error.rs new file mode 100644 index 0000000..3632f99 --- /dev/null +++ b/crates/evoxel-transform/src/error.rs @@ -0,0 +1,14 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + EcoordError(#[from] ecoord::Error), + #[error(transparent)] + EvoxelError(#[from] evoxel_core::Error), + #[error(transparent)] + Polars(#[from] polars::error::PolarsError), + + #[error("the data for key `{0}` is not available")] + LowerCornerMustBeBelowUpperCorner(&'static str), +} diff --git a/crates/evoxel-transform/src/filter.rs b/crates/evoxel-transform/src/filter.rs new file mode 100644 index 0000000..1d8a94f --- /dev/null +++ b/crates/evoxel-transform/src/filter.rs @@ -0,0 +1,127 @@ +use crate::Error; +use evoxel_core::{VoxelDataColumnNames, VoxelGrid}; +use nalgebra::Point3; + +use polars::frame::DataFrame; + +use crate::Error::LowerCornerMustBeBelowUpperCorner; +use polars::prelude::{all, col, len, ChunkCompare, IntoLazy}; + +pub fn aggregate_by_index(voxel_grid: &VoxelGrid) -> Result { + let voxel_data = voxel_grid.voxel_data(); + let partition_columns = vec![ + VoxelDataColumnNames::X.as_str(), + VoxelDataColumnNames::Y.as_str(), + VoxelDataColumnNames::Z.as_str(), + ]; + + let partitioned: DataFrame = voxel_data + .clone() + .lazy() + .group_by(partition_columns) + .agg([all(), len()]) + //.limit(5) + .collect()?; + //dbg!("{:?}", &partitioned); + + /*println!("partitions: {}", partitioned.len()); + let parit_filtered: Vec = partitioned + .into_iter() + .filter(|d| d.height() >= minimum) + .collect(); + + let partitioned_lazy: Vec = + parit_filtered.iter().map(|d| d.clone().lazy()).collect(); + + let merged_again = concat(partitioned_lazy, true, true) + .unwrap() + .collect() + .unwrap();*/ + + let filtered_voxel_grid = VoxelGrid::new( + partitioned, + voxel_grid.info().clone(), + voxel_grid.reference_frames().clone(), + )?; + Ok(filtered_voxel_grid) +} + +pub fn explode(voxel_grid: &VoxelGrid) -> Result { + let voxel_data = voxel_grid.voxel_data(); + + let column_names: Vec<&str> = voxel_data + .get_columns() + .iter() + .filter(|s| s.dtype().inner_dtype().is_some()) // if contains inner, it's a list + .map(|s| s.name()) + .collect(); + + let df: DataFrame = voxel_data + .clone() + .lazy() + .explode(column_names.into_iter().map(col).collect::>()) + //.limit(5) + .collect()?; + + let filtered_voxel_grid = VoxelGrid::new( + df, + voxel_grid.info().clone(), + voxel_grid.reference_frames().clone(), + )?; + Ok(filtered_voxel_grid) +} + +pub fn filter_by_count(voxel_grid: &VoxelGrid, minimum: usize) -> Result { + let voxel_data = voxel_grid.voxel_data().clone(); + + let mask = voxel_data + .column(VoxelDataColumnNames::Count.as_str())? + .gt_eq(minimum as i32)?; + + let filtered_voxel_data = voxel_data.filter(&mask)?; + let filtered_voxel_grid = VoxelGrid::new( + filtered_voxel_data, + voxel_grid.info().clone(), + voxel_grid.reference_frames().clone(), + )?; + + Ok(filtered_voxel_grid) +} + +pub fn filter_by_index_bounds( + voxel_grid: &VoxelGrid, + lower_corner: Point3, + upper_corner: Point3, +) -> Result { + if lower_corner >= upper_corner { + return Err(LowerCornerMustBeBelowUpperCorner("")); + } + + let voxel_data = voxel_grid.voxel_data().clone(); + + let filtered_voxel_data = voxel_data + .lazy() + .filter( + col(VoxelDataColumnNames::X.as_str()) + .gt_eq(lower_corner.x) + .and(col(VoxelDataColumnNames::X.as_str()).lt_eq(upper_corner.x)), + ) + .filter( + col(VoxelDataColumnNames::Y.as_str()) + .gt_eq(lower_corner.y) + .and(col(VoxelDataColumnNames::Y.as_str()).lt_eq(upper_corner.y)), + ) + .filter( + col(VoxelDataColumnNames::Z.as_str()) + .gt_eq(lower_corner.z) + .and(col(VoxelDataColumnNames::Z.as_str()).lt_eq(upper_corner.z)), + ) + .collect()?; + let filtered_voxel_grid = VoxelGrid::new( + filtered_voxel_data, + voxel_grid.info().clone(), + voxel_grid.reference_frames().clone(), + )?; + + Ok(filtered_voxel_grid) +} diff --git a/crates/evoxel-transform/src/lib.rs b/crates/evoxel-transform/src/lib.rs new file mode 100644 index 0000000..163c862 --- /dev/null +++ b/crates/evoxel-transform/src/lib.rs @@ -0,0 +1,21 @@ +mod error; +mod filter; +pub mod translate; + +#[doc(inline)] +pub use crate::error::Error; + +#[doc(inline)] +pub use crate::translate::translate; + +#[doc(inline)] +pub use crate::filter::aggregate_by_index; + +#[doc(inline)] +pub use crate::filter::filter_by_count; + +#[doc(inline)] +pub use crate::filter::filter_by_index_bounds; + +#[doc(inline)] +pub use crate::filter::explode; diff --git a/crates/evoxel-transform/src/translate.rs b/crates/evoxel-transform/src/translate.rs new file mode 100644 index 0000000..bb96e01 --- /dev/null +++ b/crates/evoxel-transform/src/translate.rs @@ -0,0 +1,20 @@ +use evoxel_core::{VoxelDataColumnNames, VoxelGrid}; +use nalgebra::Vector3; + +pub fn translate(voxel_grid: &VoxelGrid, translation: Vector3) -> VoxelGrid { + let mut translated_data = voxel_grid.voxel_data().clone(); + translated_data + .apply(VoxelDataColumnNames::X.as_str(), |x| x + translation.x) + .expect("TODO: panic message"); + translated_data + .apply(VoxelDataColumnNames::Y.as_str(), |y| y + translation.y) + .expect("TODO: panic message"); + translated_data + .apply(VoxelDataColumnNames::Z.as_str(), |z| z + translation.z) + .expect("TODO: panic message"); + + let info = voxel_grid.info().clone(); + let frames = voxel_grid.reference_frames().clone(); + + VoxelGrid::from_data_frame(translated_data, info, frames).unwrap() +} diff --git a/crates/evoxel/Cargo.toml b/crates/evoxel/Cargo.toml index 3b94551..061b1bb 100644 --- a/crates/evoxel/Cargo.toml +++ b/crates/evoxel/Cargo.toml @@ -5,9 +5,10 @@ authors.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -description = "Library for processing 3D voxel grids" +description = "Library for processing 3D point clouds." [dependencies] -evoxel-core = { version = "0.0.1-alpha.1", path = "../evoxel-core", registry = "custom" } -evoxel-io = { version = "0.0.1-alpha.1", path = "../evoxel-io", registry = "custom" } +evoxel-core = { version = "0.0.1-alpha.3", path = "../evoxel-core" } +evoxel-io = { version = "0.0.1-alpha.3", path = "../evoxel-io" } +evoxel-transform = { version = "0.0.1-alpha.3", path = "../evoxel-transform" } diff --git a/crates/evoxel/README.md b/crates/evoxel/README.md new file mode 100644 index 0000000..fc45d35 --- /dev/null +++ b/crates/evoxel/README.md @@ -0,0 +1,10 @@ +# evoxel + +Library for processing 3D point clouds. + +> [!WARNING] +> The library is at an early stage of development. + +## Contributing + +The library is developed at the [TUM Chair of Geoinformatics](https://github.com/tum-gis) and contributions are highly welcome. diff --git a/crates/evoxel/src/lib.rs b/crates/evoxel/src/lib.rs index 79c2efe..38430fd 100644 --- a/crates/evoxel/src/lib.rs +++ b/crates/evoxel/src/lib.rs @@ -1,9 +1,5 @@ -//! Library for representing voxel grids with the extension `.evox`. +//! `evoxel` is a library for processing 3D voxel grids. //! -//! References: -//! -//! - [parry3d_f64::transformation::voxelization](https://docs.rs/parry3d-f64/latest/parry3d_f64/transformation/voxelization/index.html) -//! - [octomap](https://github.com/OctoMap/octomap) //! //! # Overview //! @@ -12,30 +8,32 @@ //! //! For serializing a voxel grid, this data structure is used: //! -//! - `voxel_grid_name` (directory) or `voxel_grid_name.evox` (single file as [tarball](https://en.wikipedia.org/wiki/Tar_(computing))) -//! - `voxel_grid.xyz` (uncompressed) or `voxel_grid.parquet` (compressed) +//! - `voxel_grid_name` (directory) or `voxel_grid_name.evoxel` (single file as [tarball](https://en.wikipedia.org/wiki/Tar_(computing))) +//! - `voxel_data.xyz` (uncompressed) or `voxel_data.parquet` (compressed) //! - mandatory fields: -//! - `x`: [i64](i64) -//! - `y`: [i64](i64) -//! - `z`: [i64](i64) +//! - `x` [i64]: X index +//! - `y` [i64]: Y index +//! - `z` [i64]: Z index //! - `info.json` //! - mandatory fields: -//! - `frame_id`: [String](String) -//! - `resolution`: [f64](f64) -//! - `start_time`: [i128](i128) -//! - `stop_time`: [i128](i128) -//! - `submap_index`: [i32](i32) -//! - `frames.json` +//! - `frame_id` [String] +//! - `resolution` [f64] +//! - optional fields: +//! - `start_time` [i128] +//! - `stop_time` [i128] +//! - `submap_index` [i32] +//! - `ecoord.json` //! - contains a transformation tree with validity durations -//! - kind of similar to a geopose //! - information: srid //! - purpose: translate and rotate the voxel grid without reading/writing the point data //! -//! - `preview.geojson` -//! - bounding box -//! - purpose: fast visualization +//! # Other projects //! +//! - [parry3d_f64::transformation::voxelization](https://docs.rs/parry3d-f64/latest/parry3d_f64/transformation/voxelization/index.html) +//! - [octomap](https://github.com/OctoMap/octomap) -pub use evoxel_core::{VoxelGrid, VoxelGridInfo}; +pub use evoxel_core::{Error, VoxelDataColumnNames, VoxelGrid, VoxelGridInfo}; pub use evoxel_io as io; + +pub use evoxel_transform as transform;