From d0912efe76a3d4e46f7f8940b0094a193ec67a58 Mon Sep 17 00:00:00 2001 From: dancergraham Date: Tue, 9 Jan 2024 21:31:56 +0100 Subject: [PATCH 1/2] =?UTF-8?q?feature:=20Now=20in=20technicolor!=20?= =?UTF-8?q?=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=F0=9F=8C=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- README.md | 1 + src/lib.rs | 40 ++++++++++++++++++++++++++++++++-------- tests/test_e57.py | 28 ++++++++++++++++++++++------ 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d4ab542..a9092b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "e57-python" -version = "0.1.0-a4" +version = "0.1.0-a5" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index 7893c36..3b0dfb6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This python library wraps the [rust e57 library](https://github.com/cry-inc/e57) - [x] Proof of concept xml reading - [x] Read e57 point coordinates to numpy array - see `read_points` method. +- [x] Read color field to numpy array. - [ ] Read intensity and other fields to numpy array. - [ ] Write to e57 (format ?) diff --git a/src/lib.rs b/src/lib.rs index 927107b..ac4f4fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,10 +2,18 @@ use std::fs::File; use std::io::BufReader; use ::e57::{CartesianCoordinate, E57Reader}; -use ndarray::Ix2; -use numpy::{PyArray}; +use ndarray::{Ix2}; +use numpy::PyArray; use pyo3::prelude::*; +#[pyclass] +pub struct E57 { + #[pyo3(get)] + pub points: Py>, + #[pyo3(get)] + pub color: Py>, +} + /// Extracts the xml contents from an e57 file. #[pyfunction] fn raw_xml(filepath: &str) -> PyResult { @@ -26,7 +34,7 @@ fn raw_xml(filepath: &str) -> PyResult { /// Extracts the point data from an e57 file. #[pyfunction] -fn read_points<'py>(py: Python<'py>, filepath: &str) -> PyResult<&'py PyArray> { +unsafe fn read_points<'py>(py: Python<'py>, filepath: &str) -> PyResult { let file = E57Reader::from_file(filepath); let mut file = match file { Ok(file) => file, @@ -38,8 +46,8 @@ fn read_points<'py>(py: Python<'py>, filepath: &str) -> PyResult<&'py PyArray(py: Python<'py>, filepath: &str) -> PyResult<&'py PyArray PyResult<()> { +fn e57(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_class::()?; m.add_function(wrap_pyfunction!(raw_xml, m)?)?; m.add_function(wrap_pyfunction!(read_points, m)?)?; Ok(()) diff --git a/tests/test_e57.py b/tests/test_e57.py index 7e7e4fa..dd5a064 100644 --- a/tests/test_e57.py +++ b/tests/test_e57.py @@ -10,14 +10,30 @@ def test_raw_xml(): def test_read_points(): pointcloud = e57.read_points(r"testdata/bunnyFloat.e57") - assert isinstance(pointcloud, np.ndarray) - assert len(pointcloud) == 30_571 + points = pointcloud.points + assert isinstance(points, np.ndarray) + assert len(points) == 30_571 + + +def test_read_spherical(): + pointcloud = e57.read_points(r"testdata/pipeSpherical.e57") + points = pointcloud.points + assert isinstance(points, np.ndarray) + assert len(points) == 1_220 + + +def test_read_color(): + pointcloud = e57.read_points(r"testdata/pipeSpherical.e57") + color = pointcloud.color + assert isinstance(color, np.ndarray) + assert len(color) == 1_220 def test_box_dimensions(): pointcloud: np.ndarray = e57.read_points(r"testdata/bunnyFloat.e57") - max_coords = pointcloud.max(0, None, False, -np.inf) - min_coords = pointcloud.min(0, None, False, np.inf) + points = pointcloud.points + max_coords = points.max(0, None, False, -np.inf) + min_coords = points.min(0, None, False, np.inf) X, Y, Z = max_coords - min_coords assert X == pytest.approx(0.155698) assert Y == pytest.approx(0.14731) @@ -26,8 +42,8 @@ def test_box_dimensions(): def test_global_box_center(): pointcloud: np.ndarray = e57.read_points(r"testdata/bunnyFloat.e57") - max_coords = pointcloud.max(0, None, False, -np.inf) - min_coords = pointcloud.min(0, None, False, np.inf) + max_coords = pointcloud.points.max(0, None, False, -np.inf) + min_coords = pointcloud.points.min(0, None, False, np.inf) X, Y, Z = (max_coords + min_coords) / 2 assert X == pytest.approx(-0.016840) assert Y == pytest.approx(0.113666) From 63421accfbe5c2d542439bc35bf910d679d858dc Mon Sep 17 00:00:00 2001 From: dancergraham Date: Tue, 9 Jan 2024 21:36:14 +0100 Subject: [PATCH 2/2] =?UTF-8?q?feature:=20Now=20in=20technicolor!=20?= =?UTF-8?q?=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=F0=9F=8C=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- testdata/pipeSpherical.e57 | Bin 0 -> 26624 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 testdata/pipeSpherical.e57 diff --git a/testdata/pipeSpherical.e57 b/testdata/pipeSpherical.e57 new file mode 100644 index 0000000000000000000000000000000000000000..6f9a04057947d09c16ca101bc39f5b366676ccb3 GIT binary patch literal 26624 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 B{Zjw{ literal 0 HcmV?d00001