Updated package to use most recent maturin version

Significant rewrite to allow this module to work with the most recetn
version of maturin. Required dependency removal for ndarray-csv and
rewrite of tests. Should future-proof for migratiosn to python3.13 in
software using this library.
This commit is contained in:
2024-10-25 09:27:12 +01:00
parent d0e353bfab
commit a1150e1ef6
5 changed files with 223 additions and 180 deletions

View File

@@ -1,7 +1,7 @@
use numpy::{IntoPyArray, PyArray2};
use ndarray::{ArrayBase, Ix2, OwnedRepr};
use numpy::{PyArray2, ToPyArray};
use pyo3::exceptions;
use pyo3::prelude::{pymodule, PyErr, PyModule, PyResult, Python};
use pyo3::types::{PyList, PyString};
use pyo3::prelude::*;
use std::path::Path;
pub mod rust_fn;
@@ -16,9 +16,7 @@ impl From<rust_fn::ReadError> for PyErr {
PyErr::new::<exceptions::PyRuntimeError, _>(format!("{}", e))
}
rust_fn::ReadError::Io(e) => PyErr::new::<exceptions::PyIOError, _>(format!("{}", e)),
rust_fn::ReadError::NdarrayCSV(e) => {
PyErr::new::<exceptions::PyIOError, _>(format!("{}", e))
}
rust_fn::ReadError::CSV(e) => PyErr::new::<exceptions::PyIOError, _>(format!("{}", e)),
rust_fn::ReadError::ParseFloatError(e) => {
PyErr::new::<exceptions::PyRuntimeError, _>(format!("{}", e))
}
@@ -27,31 +25,41 @@ impl From<rust_fn::ReadError> for PyErr {
}
}
#[pyfunction]
fn read_layers<'py>(_py: Python<'py>, folder: &'py str) -> PyResult<Bound<'py, PyArray2<f64>>>
where
ArrayBase<OwnedRepr<f64>, Ix2>: ToPyArray<Item = f64, Dim = Ix2>,
{
let rs_result = rust_fn::read_layers(folder)?;
let py_result = rs_result.to_pyarray_bound(_py);
Ok(py_result)
}
#[pyfunction]
fn read_selected_layers<'py>(
_py: Python<'py>,
file_list: Vec<String>,
) -> PyResult<Bound<'py, PyArray2<f64>>> {
let path_list = file_list
.iter()
.map(|x| Path::new(x).to_path_buf())
.collect();
let rs_result = rust_fn::read_selected_layers(path_list)?;
let py_result = rs_result.to_pyarray_bound(_py);
Ok(py_result)
}
#[pyfunction]
fn read_layer<'py>(_py: Python<'py>, file: String) -> PyResult<Bound<'py, PyArray2<f64>>> {
let rs_result = rust_fn::read_layer(&file)?;
let py_result = rs_result.to_pyarray_bound(_py);
Ok(py_result)
}
#[pymodule]
fn read_aconity_layers(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
#[pyfn(m)]
fn read_layers<'py>(py: Python<'py>, folder: &PyString) -> PyResult<&'py PyArray2<f64>> {
Ok(rust_fn::read_layers(folder.to_str().unwrap())?.into_pyarray(py))
}
#[pyfn(m)]
fn read_selected_layers<'py>(
py: Python<'py>,
file_list: &PyList,
) -> PyResult<&'py PyArray2<f64>> {
Ok(rust_fn::read_selected_layers(
file_list
.iter()
.map(|x| Path::new(&(*x).str().unwrap().to_string()).to_path_buf())
.collect(),
)?
.into_pyarray(py))
}
#[pyfn(m)]
fn read_layer<'py>(py: Python<'py>, file: &PyString) -> PyResult<&'py PyArray2<f64>> {
Ok(rust_fn::read_layer(file.to_str().unwrap())?.into_pyarray(py))
}
fn read_aconity_layers(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(read_layers, m)?)?;
m.add_function(wrap_pyfunction!(read_selected_layers, m)?)?;
m.add_function(wrap_pyfunction!(read_layer, m)?)?;
Ok(())
}

View File

@@ -2,7 +2,6 @@ use csv::ReaderBuilder;
use glob::glob;
use indicatif::ProgressBar;
use ndarray::{concatenate, Array2, ArrayView2, Axis, Slice};
use ndarray_csv::Array2Reader;
use rayon::prelude::*;
use std::fs::File;
use std::path::{Path, PathBuf};
@@ -19,8 +18,8 @@ pub enum ReadError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("NdarrayCSV error: {0}")]
NdarrayCSV(#[from] ndarray_csv::ReadError),
#[error("CSV error: {0}")]
CSV(#[from] csv::Error),
#[error("Float parse error: {0}")]
ParseFloatError(#[from] std::num::ParseFloatError),
@@ -158,14 +157,28 @@ pub fn read_layer(file: &str) -> Result<Array2<f64>> {
pub fn read_file(filepath: PathBuf) -> Result<(Array2<f64>, f64, usize)> {
let z: f64 = get_z(&filepath)?;
let file = File::open(filepath)?;
let mut rdr = ReaderBuilder::new()
.has_headers(false)
.delimiter(b' ')
.from_reader(file);
let array_read: Array2<f64> = rdr.deserialize_array2_dynamic()?;
let z_len: usize = array_read.shape()[0];
let mut rdr = ReaderBuilder::new().has_headers(false).from_reader(file);
let data = rdr
.records()
.into_iter()
.collect::<std::result::Result<Vec<csv::StringRecord>, _>>()?
.iter()
.map(|x| {
x.iter()
.map(|y| y.parse::<f64>().map_err(|e| ReadError::ParseFloatError(e)))
.collect::<Result<Vec<f64>>>()
})
.collect::<Result<Vec<_>>>()?;
let length = data.len();
let width = data[0].len(); // WARNING: assumes fixed width columns!
let mut arr: Array2<f64> = Array2::zeros((length, width));
for (data_row, mut arr_row) in data.iter().zip(arr.axis_iter_mut(Axis(0))) {
for (data_i, arr_i) in data_row.iter().zip(arr_row.iter_mut()) {
*arr_i = *data_i
}
}
Ok((array_read, z, z_len))
Ok((arr, z, length))
}
pub fn get_z(filepath: &Path) -> Result<f64> {
@@ -220,13 +233,16 @@ mod tests {
}
impl<'a> Arbitrary<'a> for ExampleData {
fn arbitrary(u: &mut Unstructured<'a>) -> ArbResult<Self> {
// NOTE: We need to add 1 here, it len == 0 the test will fail
// NOTE: We need to add 1 here, if len == 0 the test will fail
let len = 1 + u
.arbitrary_len::<(isize, isize, isize, isize)>()?
.min(usize::MAX - 1);
let ar =
Array2::from_shape_simple_fn((4, len), || isize::arbitrary(u).unwrap_or_default());
let z = f64::arbitrary(u)?.abs() / Z_SCALING;
// This is a clunky way to account for truncation of floats when writing filepath
let z = format!("{:.32}", f64::arbitrary(u)?.abs() / Z_SCALING)
.parse()
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
Ok(ExampleData { z, ar })
}
@@ -291,8 +307,10 @@ mod tests {
fn proptest_read_file() {
fn prop(u: &mut Unstructured<'_>) -> ArbResult<()> {
let data = ExampleData::arbitrary(u)?;
let (_tmpdir, tmpfpath) = create_test_pcd(data.z, &data.ar).unwrap();
let (ar_out, z_out, _) = rust_fn::read_file(tmpfpath).unwrap();
let (_tmpdir, tmpfpath) = create_test_pcd(data.z, &data.ar)
.map_err(|_e| arbitrary::Error::IncorrectFormat)?;
let (ar_out, z_out, _) =
rust_fn::read_file(tmpfpath).map_err(|_e| arbitrary::Error::IncorrectFormat)?;
let actual_result = ReadResult {
z: z_out,
ar: ar_out,