use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyTuple};
use super::array::value_of_array;
use super::ops::Value;
use crate::collator::ImageBatch;
use crate::image::{Chw, Hwc, Layout, Sample};
struct ArrayView<'a> {
address: usize,
shape: &'a [usize],
typestr: &'static str,
readonly: bool,
}
fn typestr<T: Sample>() -> &'static str {
match (T::NAME, cfg!(target_endian = "little")) {
("uint8", _) => "|u1",
(_, true) => "<f4",
(_, false) => ">f4",
}
}
impl ArrayView<'_> {
fn of<'a, T: Sample>(data: &[T], shape: &'a [usize], readonly: bool) -> ArrayView<'a> {
ArrayView {
address: data.as_ptr() as usize,
shape,
typestr: typestr::<T>(),
readonly,
}
}
fn interface<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
let dict = PyDict::new(py);
dict.set_item(intern!(py, "shape"), PyTuple::new(py, self.shape)?)?;
dict.set_item(intern!(py, "typestr"), self.typestr)?;
dict.set_item(intern!(py, "data"), (self.address, self.readonly))?;
dict.set_item(intern!(py, "strides"), py.None())?;
dict.set_item(intern!(py, "version"), 3)?;
Ok(dict)
}
}
fn dlpack<'py>(
obj: &Bound<'py, PyAny>,
kwargs: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
let py = obj.py();
let array = py
.import(intern!(py, "numpy"))?
.call_method1(intern!(py, "asarray"), (obj,))?;
array.call_method(intern!(py, "__dlpack__"), (), kwargs)
}
const CPU: (i32, i32) = (1, 0);
#[pyclass(frozen, name = "Image", module = "dataloader_rs")]
pub struct PyImage {
value: Value,
shape: [usize; 3],
}
impl PyImage {
pub(crate) fn new(value: Value) -> Self {
let shape = match &value {
Value::U8(i) => i.shape(),
Value::U8Chw(i) => i.shape(),
Value::F32(i) => i.shape(),
Value::F32Chw(i) => i.shape(),
Value::Bytes(_) => unreachable!("PyImage of bytes"),
};
Self { value, shape }
}
pub(crate) fn value(&self) -> &Value {
&self.value
}
fn view(&self) -> ArrayView<'_> {
match &self.value {
Value::U8(i) => ArrayView::of(i.data(), &self.shape, true),
Value::U8Chw(i) => ArrayView::of(i.data(), &self.shape, true),
Value::F32(i) => ArrayView::of(i.data(), &self.shape, true),
Value::F32Chw(i) => ArrayView::of(i.data(), &self.shape, true),
Value::Bytes(_) => unreachable!("PyImage of bytes"),
}
}
fn layout(&self) -> &'static str {
match &self.value {
Value::U8(_) | Value::F32(_) => Hwc::NAME,
_ => Chw::NAME,
}
}
}
#[pymethods]
impl PyImage {
#[staticmethod]
#[pyo3(signature = (obj, layout = "hwc"))]
fn from_array(obj: &Bound<'_, PyAny>, layout: &str) -> PyResult<Self> {
value_of_array(obj, layout).map(Self::new)
}
#[getter]
fn shape(&self) -> (usize, usize, usize) {
let [a, b, c] = self.shape;
(a, b, c)
}
#[getter]
fn dtype(&self) -> &'static str {
match &self.value {
Value::U8(_) | Value::U8Chw(_) => u8::NAME,
_ => f32::NAME,
}
}
#[getter(layout)]
fn layout_name(&self) -> &'static str {
self.layout()
}
#[getter]
fn __array_interface__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
self.view().interface(py)
}
#[pyo3(signature = (**kwargs))]
fn __dlpack__<'py>(
slf: &Bound<'py, Self>,
kwargs: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
dlpack(slf.as_any(), kwargs)
}
fn __dlpack_device__(&self) -> (i32, i32) {
CPU
}
fn __repr__(&self) -> String {
let [a, b, c] = self.shape;
format!("Image({a}x{b}x{c}, {}, {})", self.dtype(), self.layout())
}
}
pub(crate) enum Batch {
U8(ImageBatch<u8>),
F32(ImageBatch<f32>),
}
#[pyclass(frozen, name = "ImageBatch", module = "dataloader_rs")]
pub struct PyImageBatch {
batch: Batch,
shape: [usize; 4],
}
impl PyImageBatch {
pub(crate) fn new(batch: Batch) -> Self {
let shape = match &batch {
Batch::U8(b) => b.shape(),
Batch::F32(b) => b.shape(),
};
Self { batch, shape }
}
}
#[pymethods]
impl PyImageBatch {
#[getter]
fn shape(&self) -> (usize, usize, usize, usize) {
let [n, a, b, c] = self.shape;
(n, a, b, c)
}
#[getter]
fn dtype(&self) -> &'static str {
match &self.batch {
Batch::U8(_) => u8::NAME,
Batch::F32(_) => f32::NAME,
}
}
#[getter]
fn layout(&self) -> &'static str {
match &self.batch {
Batch::U8(b) => b.layout().name(),
Batch::F32(b) => b.layout().name(),
}
}
fn __len__(&self) -> usize {
self.shape[0]
}
#[getter]
fn __array_interface__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
match &self.batch {
Batch::U8(b) => ArrayView::of(b.data(), &self.shape, false).interface(py),
Batch::F32(b) => ArrayView::of(b.data(), &self.shape, false).interface(py),
}
}
#[pyo3(signature = (**kwargs))]
fn __dlpack__<'py>(
slf: &Bound<'py, Self>,
kwargs: Option<&Bound<'py, PyDict>>,
) -> PyResult<Bound<'py, PyAny>> {
dlpack(slf.as_any(), kwargs)
}
fn __dlpack_device__(&self) -> (i32, i32) {
CPU
}
fn __repr__(&self) -> String {
let [n, a, b, c] = self.shape;
format!(
"ImageBatch({n}x{a}x{b}x{c}, {}, {})",
self.dtype(),
self.layout()
)
}
}