Skip to content

Commit

Permalink
feat: add custom las Version struct
Browse files Browse the repository at this point in the history
  • Loading branch information
omelette-watin committed Sep 2, 2024
1 parent dee1b21 commit 196a581
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 56 deletions.
9 changes: 5 additions & 4 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 @@ -23,3 +23,4 @@ serde = { version = "1.0.203", features = ["derive"] }
las = "0.9.1"
rayon = "1.10.0"
uuid = { version = "1.8.0", features = ["v4"] }
thiserror = { version = "1.0.63" }
40 changes: 6 additions & 34 deletions src/convert_file.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
extern crate rayon;
use rayon::prelude::*;

use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};

use crate::convert_pointcloud::{convert_pointcloud, convert_pointclouds};

use crate::las_version;
use crate::stations::save_stations;

/// Converts a given e57 file into LAS format and, optionally, as stations.
Expand Down Expand Up @@ -35,7 +36,7 @@ pub fn convert_file(
output_path: String,
number_of_threads: usize,
as_stations: bool,
las_version: (u8, u8),
las_version: las_version::Version,
) -> Result<()> {
if rayon::current_num_threads() != number_of_threads {
rayon::ThreadPoolBuilder::new()
Expand All @@ -44,12 +45,6 @@ pub fn convert_file(
.context("Failed to initialize the global thread pool")?;
}

let allowed_versions = [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4)];

if !allowed_versions.contains(&las_version) {
return Err(anyhow!("LAS version must be between 1.0 and 1.4"));
}

let e57_reader = e57::E57Reader::from_file(&input_path).context("Failed to open e57 file")?;

if e57_reader.format_name() != "ASTM E57 3D Imaging Data File" {
Expand All @@ -65,7 +60,7 @@ pub fn convert_file(
.try_for_each(|(index, pointcloud)| -> Result<()> {
println!("Saving pointcloud {}...", index);

convert_pointcloud(index, pointcloud, &input_path, &output_path, las_version)
convert_pointcloud(index, pointcloud, &input_path, &output_path, &las_version)
.context(format!("Error while converting pointcloud {}", index))?;

Ok(())
Expand All @@ -74,7 +69,7 @@ pub fn convert_file(

save_stations(output_path, pointclouds)?;
} else {
convert_pointclouds(e57_reader, &output_path, las_version)
convert_pointclouds(e57_reader, &output_path, &las_version)
.context("Error during the parallel processing of pointclouds")?;
}
Ok(())
Expand All @@ -93,7 +88,7 @@ mod tests {
let output_path = String::from("examples");
let number_of_threads = 4;
let as_stations = true;
let las_version = (1, 3);
let las_version = las_version::Version::new(1, 3).unwrap();
let result = convert_file(
input_path,
output_path,
Expand All @@ -105,27 +100,4 @@ mod tests {
assert!(result.is_ok());
});
}

#[test]
fn test_wrong_las_version() {
let pool = ThreadPoolBuilder::new().num_threads(4).build().unwrap();
pool.install(|| {
let input_path = String::from("examples/bunnyDouble.e57");
let output_path = String::from("examples");
let number_of_threads = 4;
let as_stations = true;
let las_version = (0, 3);
let result = convert_file(
input_path,
output_path,
number_of_threads,
as_stations,
las_version,
);
assert_eq!(
result.err().unwrap().to_string(),
"LAS version must be between 1.0 and 1.4"
);
});
}
}
10 changes: 5 additions & 5 deletions src/convert_pointcloud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use std::path::Path;
use std::sync::Mutex;

use crate::get_las_writer::get_las_writer;
use crate::las_version;
use crate::{convert_point::convert_point, utils::create_path};

use anyhow::{Context, Result};
use e57::{E57Reader, PointCloud};
use las::Version;
use rayon::prelude::*;

/// Converts a point cloud to a LAS file.
Expand All @@ -35,7 +35,7 @@ pub fn convert_pointcloud(
pointcloud: &PointCloud,
input_path: &String,
output_path: &String,
las_version: (u8, u8),
las_version: &las_version::Version,
) -> Result<()> {
let mut e57_reader = E57Reader::from_file(input_path).context("Failed to open e57 file: ")?;

Expand Down Expand Up @@ -81,7 +81,7 @@ pub fn convert_pointcloud(
path,
max_cartesian,
has_color_mutex.lock().unwrap().to_owned(),
Version::new(las_version.0, las_version.1),
las_version,
)
.context("Unable to create writer: ")?;

Expand Down Expand Up @@ -113,7 +113,7 @@ pub fn convert_pointcloud(
pub fn convert_pointclouds(
e57_reader: E57Reader<BufReader<File>>,
output_path: &String,
las_version: (u8, u8),
las_version: &las_version::Version,
) -> Result<()> {
let pointclouds = e57_reader.pointclouds();
let guid = &e57_reader.guid().to_owned();
Expand Down Expand Up @@ -176,7 +176,7 @@ pub fn convert_pointclouds(
path,
max_cartesian_mutex.lock().unwrap().to_owned(),
has_color_mutex.lock().unwrap().to_owned(),
Version::new(las_version.0, las_version.1),
las_version,
)
.context("Unable to create writer: ")?;

Expand Down
9 changes: 9 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Invalid LAS version {0}")]
InvalidLasVersion(String),
#[error(transparent)]
UnexpectedError(#[from] anyhow::Error),
}

pub type Result<T> = core::result::Result<T, Error>;
6 changes: 4 additions & 2 deletions src/get_las_writer.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::{fs::File, io::BufWriter, path::PathBuf};

use anyhow::{Context, Result};
use las::{Vector, Version};
use las::Vector;
use uuid::Uuid;

use crate::las_version;

fn find_smallest_scale(x: f64) -> f64 {
let mut scale = 0.001;
let min_i32 = f64::from(i32::MIN);
Expand All @@ -21,7 +23,7 @@ pub(crate) fn get_las_writer(
output_path: PathBuf,
max_cartesian: f64,
has_color: bool,
las_version: Version,
las_version: &las_version::Version,
) -> Result<las::Writer<BufWriter<File>>> {
let mut builder = las::Builder::from(las_version);
builder.point_format.has_color = has_color;
Expand Down
84 changes: 84 additions & 0 deletions src/las_version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use crate::{Error, Result};

const ALLOWED_VERSIONS: [(u8, u8); 5] = [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4)];

pub struct Version {
major: u8,
minor: u8,
}

impl Version {
pub fn new(major: u8, minor: u8) -> Result<Self> {
if !ALLOWED_VERSIONS.contains(&(major, minor)) {
return Err(Error::InvalidLasVersion(format!(
"must be between {} and {}",
format_version(ALLOWED_VERSIONS[0]),
format_version(ALLOWED_VERSIONS[4]),
)));
}

Ok(Version { major, minor })
}
}

impl TryFrom<&str> for Version {
type Error = Error;

fn try_from(value: &str) -> std::prelude::v1::Result<Self, Self::Error> {
let parts: Vec<&str> = value.split('.').collect();

if parts.len() != 2 {
return Err(Error::InvalidLasVersion(
"expected format `major.minor` (e.g. 1.4)".into(),
));
}

let major = parts[0]
.parse::<u8>()
.map_err(|_| Error::InvalidLasVersion("invalid major number".into()))?;
let minor = parts[1]
.parse::<u8>()
.map_err(|_| Error::InvalidLasVersion("invalid minor number".into()))?;

Self::new(major, minor)
}
}

impl From<&Version> for las::Version {
fn from(value: &Version) -> Self {
Self {
major: value.major,
minor: value.minor,
}
}
}

fn format_version((major, minor): (u8, u8)) -> String {
format!("{}.{}", major, minor)
}

#[cfg(test)]
mod test {
use crate::las_version;

#[test]
fn test_unsupported_las_version() {
let las_version = las_version::Version::new(2, 3);

assert!(las_version.is_err())
}

#[test]
fn test_invalid_las_version_major() {
let las_version = las_version::Version::try_from("b.4");

assert!(las_version.is_err())
}

#[test]
fn test_invalid_las_version_minor() {
let las_version = las_version::Version::try_from("2.c");

assert!(las_version.is_err())
}
}
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@

#![forbid(unsafe_code)]
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::large_stack_arrays,
clippy::large_types_passed_by_value
)]
#![warn(clippy::panic, clippy::unwrap_used)]

mod convert_file;
mod convert_point;
mod convert_pointcloud;
mod error;
mod get_las_writer;
mod spatial_point;
mod stations;
mod utils;

pub mod las_version;
pub use self::convert_file::convert_file;
pub use self::convert_point::convert_point;
pub use self::convert_pointcloud::convert_pointcloud;
pub use error::{Error, Result};
14 changes: 5 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
extern crate rayon;
use anyhow::{Context, Result};
use anyhow::Context;
use clap::Parser;

use e57_to_las::convert_file;
use e57_to_las::las_version::Version;
use e57_to_las::Result;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
Expand All @@ -26,12 +26,7 @@ struct Args {
fn main() -> Result<()> {
let args = Args::parse();

let las_version = args.las_version.split('.').collect::<Vec<&str>>();

let las_version = match las_version.as_slice() {
[major, minor] => (major.parse::<u8>().unwrap(), minor.parse::<u8>().unwrap()),
_ => (1, 4),
};
let las_version = Version::try_from(args.las_version.as_str())?;

convert_file(
args.path,
Expand All @@ -41,5 +36,6 @@ fn main() -> Result<()> {
las_version,
)
.context("Failed to convert file")?;

Ok(())
}

0 comments on commit 196a581

Please sign in to comment.