Skip to content

Commit

Permalink
Merge pull request #4 from paireks/develop/allow_different_las_formats
Browse files Browse the repository at this point in the history
Allow different las versions
  • Loading branch information
pnodet authored Sep 2, 2024
2 parents 0915fab + 5400678 commit 75dec22
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 35 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ name: Rust

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main" ]
branches: ["main"]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
- uses: actions/checkout@v4
with:
lfs: true
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
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" }
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ e57-to-las = "0.6.1"
You can then use it in your code as follows:

```rust
use e57_to_las::convert_file;
use e57_to_las::{e57_to_las, LasVersion};

fn main() {
let input_path = String::from("path/to/input.e57");
let output_path = String::from("path/to/output/directory");
let number_of_threads = 0; // 0 = max possible
let as_stations = true;
convert_file(input_path, output_path, number_of_threads, as_stations);
let las_version = LasVersion::new(1, 4).unwrap(); // 1.0 to 1.4
convert_file(input_path, output_path, number_of_threads, as_stations, las_version);
}
```

Expand All @@ -50,7 +51,8 @@ fn main() {
- `-p, --path <path>`: The path to the input E57 file.
- `-o, --output <output>`: The output directory for the converted LAS files (default: `./`).
- `-T, --threads <threads>`: Number of threads for parallel processing (default: 0 = max possible).
- `-S, --stations <stations>`: Whether to convert e57 file in distinct stations (default: false)
- `-S, --stations <stations>`: Whether to convert e57 file in distinct stations (default: false).
- `-L, --las_version <las_version>`: Version of LAS format used for output file. Default one is (1, 4). Currently possible: (1, 0) to (1, 4).

## Contribution

Expand Down
54 changes: 44 additions & 10 deletions src/convert_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,45 @@ use anyhow::{Context, Result};
use crate::convert_pointcloud::{convert_pointcloud, convert_pointclouds};

use crate::stations::save_stations;
use crate::LasVersion;

/// Converts a given e57 file into LAS format and, optionnally, as stations.
/// Converts a given e57 file into LAS format and, optionally, as stations.
///
/// This function reads an e57 file, extracts the point clouds, and saves them in single or multiples las files.
/// It can also creates stations record file (useful if you use potree).
/// It can also create stations record file (useful if you use potree).
///
/// # Parameters
/// - `input_path`: The path to the e57 file that needs to be converted.
/// - `output_path`: The destination (output dir) where the files will be saved.
/// - `number_of_threads`: The number of threads to be used for parallel processing.
/// - `as_stations`: Whether to convert e57 file in distinct stations
/// - `las_version`: Version of LAS format used for output file. Latest one is (1, 4). Currently possible: (1, 3) and (1, 4).
/// or in single LAS file
///
/// # Example
/// ```
/// use e57_to_las::convert_file;
/// use e57_to_las::{convert_file, LasVersion};
///
/// let input_path = String::from("path/to/input.e57");
/// let output_path = String::from("path/to/output");
/// let number_of_threads = 4;
/// let as_stations = true;
/// convert_file(input_path, output_path, number_of_threads, as_stations);
/// let las_version = LasVersion::new(1, 4).unwrap();
/// let _ = convert_file(input_path, output_path, number_of_threads, as_stations, las_version);
/// ```
pub fn convert_file(
input_path: String,
output_path: String,
number_of_threads: usize,
as_stations: bool,
las_version: LasVersion,
) -> Result<()> {
rayon::ThreadPoolBuilder::new()
.num_threads(number_of_threads)
.build_global()
.context("Failed to initialize the global thread pool")?;
if rayon::current_num_threads() != number_of_threads {
rayon::ThreadPoolBuilder::new()
.num_threads(number_of_threads)
.build_global()
.context("Failed to initialize the global thread pool")?;
}

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

Expand All @@ -54,7 +61,7 @@ pub fn convert_file(
.try_for_each(|(index, pointcloud)| -> Result<()> {
println!("Saving pointcloud {}...", index);

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

Ok(())
Expand All @@ -63,8 +70,35 @@ pub fn convert_file(

save_stations(output_path, pointclouds)?;
} else {
convert_pointclouds(e57_reader, &output_path)
convert_pointclouds(e57_reader, &output_path, &las_version)
.context("Error during the parallel processing of pointclouds")?;
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use rayon::ThreadPoolBuilder;

#[test]
fn test_convert_bunny() {
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 = LasVersion::new(1, 3).unwrap();
let result = convert_file(
input_path,
output_path,
number_of_threads,
as_stations,
las_version,
);

assert!(result.is_ok());
});
}
}
7 changes: 6 additions & 1 deletion src/convert_pointcloud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use std::path::Path;
use std::sync::Mutex;

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

use anyhow::{Context, Result};
use e57::{E57Reader, PointCloud};
Expand Down Expand Up @@ -34,6 +35,7 @@ pub fn convert_pointcloud(
pointcloud: &PointCloud,
input_path: &String,
output_path: &String,
las_version: &LasVersion,
) -> Result<()> {
let mut e57_reader = E57Reader::from_file(input_path).context("Failed to open e57 file: ")?;

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

Expand Down Expand Up @@ -110,6 +113,7 @@ pub fn convert_pointcloud(
pub fn convert_pointclouds(
e57_reader: E57Reader<BufReader<File>>,
output_path: &String,
las_version: &LasVersion,
) -> Result<()> {
let pointclouds = e57_reader.pointclouds();
let guid = &e57_reader.guid().to_owned();
Expand Down Expand Up @@ -172,6 +176,7 @@ pub fn convert_pointclouds(
path,
max_cartesian_mutex.lock().unwrap().to_owned(),
has_color_mutex.lock().unwrap().to_owned(),
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>;
5 changes: 4 additions & 1 deletion src/get_las_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use anyhow::{Context, Result};
use las::Vector;
use uuid::Uuid;

use crate::LasVersion;

fn find_smallest_scale(x: f64) -> f64 {
let mut scale = 0.001;
let min_i32 = f64::from(i32::MIN);
Expand All @@ -21,8 +23,9 @@ pub(crate) fn get_las_writer(
output_path: PathBuf,
max_cartesian: f64,
has_color: bool,
las_version: &LasVersion,
) -> Result<las::Writer<BufWriter<File>>> {
let mut builder = las::Builder::from((1, 4));
let mut builder = las::Builder::from(las_version);
builder.point_format.has_color = has_color;
builder.generating_software = String::from("e57_to_las");
let offset = 0.0;
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 LasVersion {
major: u8,
minor: u8,
}

impl LasVersion {
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(LasVersion { major, minor })
}
}

impl TryFrom<&str> for LasVersion {
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<&LasVersion> for las::Version {
fn from(value: &LasVersion) -> 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::LasVersion::new(2, 3);

assert!(las_version.is_err())
}

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

assert!(las_version.is_err())
}

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

assert!(las_version.is_err())
}
}
7 changes: 5 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@
#![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 las_version;
mod spatial_point;
mod stations;
mod utils;

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};
pub use las_version::LasVersion;
Loading

0 comments on commit 75dec22

Please sign in to comment.