How to implement rust-cpython as_object() and into_object()? - python

I am trying to implement the trait PythonObject for a custom type.
impl PythonObject for PyTerm {
#[inline]
fn as_object(&self) -> &PyObject {
//???
}
#[inline]
fn into_object(self) -> PyObject {
//???
}
#[inline]
unsafe fn unchecked_downcast_from(o: PyObject) -> PyTerm {
std::mem::transmute(o)
}
#[inline]
unsafe fn unchecked_downcast_borrow_from(o: &PyObject) -> &PyTerm {
std::mem::transmute(o)
}
}
PyTerm is defined as a custom struct:
struct PyTerm {
// not important fields
}

You may be interested in cpython::pyobject_newtype! (sources).
It looks like the macros is a blessed way to define PythonObjects.

Related

How do I return rust iterator from a python module function using pyo3

Its not like I am not able to return any rust iterators from a python module function using pyo3. The problem is when lifetime doesn't live long enough!
Allow me to explain.
First attempt:
#[pyclass]
struct ItemIterator {
iter: Box<dyn Iterator<Item = u64> + Send>,
}
#[pymethods]
impl ItemIterator {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<u64> {
slf.iter.next()
}
}
#[pyfunction]
fn get_numbers() -> ItemIterator {
let i = vec![1u64, 2, 3, 4, 5].into_iter();
ItemIterator { iter: Box::new(i) }
}
In the contrived example above I have written a python iterator wrapper for our rust iterator as per pyo3 guide and it works seemlessly.
Second attempt:
The problem is when lifetimes are involved.
Say now I have a Warehouse struct that I would want make available as python class alongside pertaining associated functions.
struct Warehouse {
items: Vec<u64>,
}
impl Warehouse {
fn new() -> Warehouse {
Warehouse {
items: vec![1u64, 2, 3, 4, 5],
}
}
fn get_items(&self) -> Box<dyn Iterator<Item = u64> + '_> {
Box::new(self.items.iter().map(|f| *f))
}
}
Implementing them as python class and methods
#[pyclass]
struct ItemIterator {
iter: Box<dyn Iterator<Item = u64> + Send>,
}
#[pymethods]
impl ItemIterator {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<u64> {
slf.iter.next()
}
}
#[pyclass]
struct Warehouse {
items: Vec<u64>,
}
#[pymethods]
impl Warehouse {
#[new]
fn new() -> Warehouse {
Warehouse {
items: vec![1u64, 2, 3, 4, 5],
}
}
fn get_items(&self) -> ItemIterator {
ItemIterator {
iter: Box::new(self.items.iter().map(|f| *f)),
}
}
}
This throws compiler error in getItems function saying:
error: lifetime may not live long enough
--> src/lib.rs:54:19
|
52 | fn get_items(&self) -> ItemIterator {
| - let's call the lifetime of this reference `'1`
53 | ItemIterator {
54 | iter: Box::new(self.items.iter().map(|f| *f)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`
error: could not compile `pyo3-example` due to previous error
I am not really sure how to fix this. Can someone explain what's really going on here. How does this compare to my first attempt implementing iterators and how to fix this?
If we remove the python stuff:
struct ItemIterator {
iter: Box<dyn Iterator<Item = u64> + Send>,
}
impl ItemIterator {
fn __iter__(&self) -> &'_ ItemIterator {
self
}
fn __next__(&mut self) -> Option<u64> {
self.iter.next()
}
}
We see the same error:
error: lifetime may not live long enough
--> src/lib.rs:21:19
|
19 | fn get_items(&self) -> ItemIterator {
| - let's call the lifetime of this reference `'1`
20 | ItemIterator {
21 | iter: Box::new(self.items.iter().map(|f| *f)),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`
The problem is that the iterator holds a reference to underlying data, but there is nothing in the type to indicate so. When you then try to construct an instance that does hold references Rust is going to let you know about it.
Without the Python FFI it can be easily fixed with an extra lifetime on the iterator:
struct ItemIterator<'a> {
iter: Box<dyn Iterator<Item = u64> + Send + 'a>,
}
Unfortunately, this won't work with the Python bindings because lifetimes and generics are not supported by pyo3. This is going to be annoying because it means that your iterator must own all of the items.
One quick fix would be to clone the vector so that the iterator owns its items. That way, no lifetimes are needed. This should work, but will be very inefficient if there is a lot of data.
Another approach is with shared ownership, using a reference-counting smart pointer; Rc or Arc, and interior mutability; RefCell, RwLock or Mutex. However, this change will have a knock-on effect - all usages of this vector will need to be changed to have to deal with the smart pointer.
use std::{rc::Rc, cell::RefCell};
#[pyclass]
struct ItemIterator {
items: Rc<RefCell<Vec<u64>>>,
index: usize,
}
#[pymethods]
impl ItemIterator {
fn __iter__(&self) -> &'_ ItemIterator {
self
}
fn __next__(&mut self) -> Option<u64> {
let item = self.items.borrow().get(self.index).copied();
self.index += 1;
item
}
}
#[pyclass]
struct Warehouse {
items: Rc<RefCell<Vec<u64>>>,
}
#[pymethods]
impl Warehouse {
fn get_items(&self) -> ItemIterator {
ItemIterator {
items: Rc::clone(&self.items),
index: 0,
}
}
}
This should now work because the exposed types and functions do not use lifetimes.
Based on #peter-hall suggestion I managed to implement a working solution (though inefficient):
#[pyclass]
struct ItemIterator {
iter: std::vec::IntoIter<u64>,
}
#[pymethods]
impl ItemIterator {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<u64> {
slf.iter.next()
}
}
#[pyclass]
struct Warehouse {
items: Vec<u64>,
}
#[pymethods]
impl Warehouse {
#[new]
fn new() -> Warehouse {
Warehouse {
items: vec![1u64, 2, 3, 4, 5],
}
}
fn get_items(&self) -> ItemIterator {
ItemIterator {
iter: self.items.collect::<Vec<_>>().into_iter(),
}
}
}

Implementing pymemcache from python to rust

I am migrating a project from python to rust, more specifically right now a file which uses the pymemchace library to call the set and get methods.
In order to set and get the cache a class was created, something like this:
class Example:
def __init__(self):
# Create connection with to memcached server
def set_cache(self, ... ):
# Set cache
def get_cache(self, ... ):
# Get cache
Instance = Example()
I know the OOP concepts like classes are implemented a differently on Rust compared to python.
How do you guys think I should approach this ? Build a struct and implement a method using the memcache crate or build a trait and call it as I needed ?
Edit[7/11/2022]:
After reading the comments this is the first model I have for the implementation of the memecache main methods:
rust
extern crate memcache;
use memcache::{Client, MemcacheError};
pub struct CacheManager {
client: memcache::Client,
}
impl CacheManager {
pub fn __ini__() -> Self {
CacheManager {
client: memcache::Client::connect("memcache://localhost:11211").unwrap(),
}
}
pub fn set_cache(&self, key: &str, val: &str, expire: u32) -> bool {
self.client.set(key, val, expire).unwrap();
self.client.replace(" ", "", 100000000).unwrap();
true
}
pub fn get_cache(&self, key: &str) -> Result<Option<String>, MemcacheError> {
self.client.replace(" ", "", 100000000).unwrap();
self.client.get(key)
}
pub fn get_or_set(self, key: &str, val: &str) -> bool {
//If status cache already exist and value is the same then do nothing else set it
let cache_val = self.get_cache(key);
match cache_val.unwrap() {
Some(i) => {
if (i.parse::<i32>().unwrap() - val.parse::<i32>().unwrap()) < 1 {
true
} else {
false
}
}
None => {
self.set_cache(key, val, 86400);
false
}
}
}
}
What do you guys think ?
I will build some testing mod to try it out now.

Using pyo3 pyclass on a rust struct that contains types from a different crate

I have a rust struct that uses the pyo3 pyclass macro to allow using in python. This works fine for simple structs but if I have a struct that contains a type from a different library it becomes more difficult.
Example:
use geo::Point;
#[pyclass]
#[derive(Clone, Copy)]
pub struct CellState {
pub id: u32,
pub position: Point<f64>,
pub population: u32,
}
Above we use the Point type from the rust geo library. The compiler provides the following error:
the trait `pyo3::PyClass` is not implemented for `geo::Point<f64>
If I then try to implement PyClass on the Point:
impl PyClass for Point<f64> {}
It gives me the following compiler error:
impl doesn't use only types from inside the current crate
Any ideas on a clean and simple way to resolve this?
Until a better answer comes up my solution was to nest the class inside a python class but I then had to manually write the getters and setters.
#[pyclass]
#[derive(Clone)]
pub struct CellStatePy {
pub inner: CellState,
}
#[pymethods]
impl CellStatePy {
#[new]
pub fn new(id: u32, pos: (f64, f64), population: u32) -> Self {
CellStatePy {
inner: CellState {
id: CellIndex(id),
position: point!(x: pos.0, y: pos.1),
population,
},
}
}
}
Then implement PyObjectProtocol:
#[pyproto]
impl PyObjectProtocol for CellStatePy {
fn __str__(&self) -> PyResult<&'static str> {
Ok("CellStatePy")
}
fn __repr__<'a>(&'a self) -> PyResult<String> {
Ok(format!("CellStateObj id: {:?}", self.inner.id))
}
fn __getattr__(&'a self, name: &str) -> PyResult<String> {
let out: String = match name {
"id" => self.inner.id.into(),
"position" => format!("{},{}", self.inner.position.x(), self.inner.position.y()),
"population" => format!("{}", self.inner.population),
&_ => "INVALID FIELD".to_owned(),
};
Ok(out)
}
}

How can I access a Rust Iterator from Python using PyO3?

I'm quite new with Rust, and my first 'serious' project has involved writing a Python wrapper for a small Rust library using PyO3. This has mostly been quite painless, but I'm struggling to work out how to expose lazy iterators over Rust Vecs to Python code.
So far, I have been collecting the values produced by the iterator and returning a list, which obviously isn't the best solution. Here's some code which illustrates my problem:
use pyo3::prelude::*;
// The Rust Iterator, from the library I'm wrapping.
pub struct RustIterator<'a> {
position: usize,
view: &'a Vec<isize>
}
impl<'a> Iterator for RustIterator<'a> {
type Item = &'a isize;
fn next(&mut self) -> Option<Self::Item> {
let result = self.view.get(self.position);
if let Some(_) = result { self.position += 1 };
result
}
}
// The Rust struct, from the library I'm wrapping.
struct RustStruct {
v: Vec<isize>
}
impl RustStruct {
fn iter(&self) -> RustIterator {
RustIterator{ position: 0, view: &self.v }
}
}
// The Python wrapper class, which exposes the
// functions of RustStruct in a Python-friendly way.
#[pyclass]
struct PyClass {
rust_struct: RustStruct,
}
#[pymethods]
impl PyClass {
#[new]
fn new(v: Vec<isize>) -> Self {
let rust_struct = RustStruct { v };
Self{ rust_struct }
}
// This is what I'm doing so far, which works
// but doesn't iterate lazily.
fn iter(&self) -> Vec<isize> {
let mut output_v = Vec::new();
for item in self.rust_struct.iter() {
output_v.push(*item);
}
output_v
}
}
I've tried to wrap the RustIterator class with a Python wrapper, but I can't use PyO3's #[pyclass] proc. macro with lifetime parameters. I looked into pyo3::types::PyIterator but this looks like a way to access a Python iterator from Rust rather than the other way around.
How can I access a lazy iterator over RustStruct.v in Python? It's safe to assume that the type contained in the Vec always derives Copy and Clone, and answers which require some code on the Python end are okay (but less ideal).
You can make your RustIterator a pyclass and then implement the proper trait (PyIterProtocol) using the rust iter itself.
Not tested, but something like:
#[pyclass]
pub struct RustIterator<'a> {
position: usize,
view: &'a Vec<isize>
}
impl<'a> Iterator for RustIterator<'a> {
type Item = &'a isize;
fn next(&mut self) -> Option<Self::Item> {
let result = self.view.get(self.position);
if let Some(_) = result { self.position += 1 };
result
}
}
#[pyproto]
impl PyIterProtocol for Iter {
fn __next__(mut slf: PyRefMut<Self>) -> IterNextOutput<usize, &'static str> {
match self.next() {
Some(value) => IterNextOutput::Yield(value),
None => IterNextOutput::Return("Ended")
}
}
}

Use Tensorflow model in android

I have a Tensorflow model and I have converted it to ".tflite" but I don't know the way how to implement it on android. I followed the TensorFlow guidelines to implement it in android but since there is no XML code given the TensorFlow website I am struggling to connect it with the front end (XML). I need a clear explanation of how to use my model in android studio using java.
I followed the official instructions given in the TensorFlow website to implement the model in android.
A sample code of how to implement object detection based on tflite model from Tensorflow. I suppose these kinds of answers are not the best answers, but I happened to have a simple example of your exact problem.
Note: it does detect objects and outputs their labels into standard output using Log.d. No boxes or labels will be drawn around detected images.
Download started models and labels from here. Put them into the assets folder of your project.
Java
import android.content.pm.PackageManager;
import android.media.Image;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ExperimentalGetImage;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.mlkit.common.model.LocalModel;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.objects.DetectedObject;
import com.google.mlkit.vision.objects.ObjectDetection;
import com.google.mlkit.vision.objects.ObjectDetector;
import com.google.mlkit.vision.objects.custom.CustomObjectDetectorOptions;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutionException;
public class ActivityExample extends AppCompatActivity {
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private ObjectDetector objectDetector;
private PreviewView prevView;
private List<String> labels;
private int REQUEST_CODE_PERMISSIONS = 101;
private String[] REQUIRED_PERMISSIONS =
new String[]{"android.permission.CAMERA"};
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fullscreen);
prevView = findViewById(R.id.viewFinder);
prepareObjectDetector();
prepareLabels();
if (allPermissionsGranted()) {
startCamera();
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
}
private void prepareLabels() {
try {
InputStreamReader reader = new InputStreamReader(getAssets().open("labels_mobilenet_quant_v1_224.txt"));
labels = readLines(reader);
} catch (IOException e) {
e.printStackTrace();
}
}
private List<String> readLines(InputStreamReader reader) {
BufferedReader bufferedReader = new BufferedReader(reader, 8 * 1024);
Iterator<String> iterator = new LinesSequence(bufferedReader);
ArrayList<String> list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next());
}
return list;
}
private void prepareObjectDetector() {
CustomObjectDetectorOptions options = new CustomObjectDetectorOptions.Builder(loadModel("mobilenet_v1_1.0_224_quant.tflite"))
.setDetectorMode(CustomObjectDetectorOptions.SINGLE_IMAGE_MODE)
.enableMultipleObjects()
.enableClassification()
.setClassificationConfidenceThreshold(0.5f)
.setMaxPerObjectLabelCount(3)
.build();
objectDetector = ObjectDetection.getClient(options);
}
private LocalModel loadModel(String assetFileName) {
return new LocalModel.Builder()
.setAssetFilePath(assetFileName)
.build();
}
private void startCamera() {
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
bindPreview(cameraProvider);
} catch (ExecutionException e) {
// No errors need to be handled for this Future.
// This should never be reached.
} catch (InterruptedException e) {
}
}, ContextCompat.getMainExecutor(this));
}
private void bindPreview(ProcessCameraProvider cameraProvider) {
Preview preview = new Preview.Builder().build();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
YourAnalyzer yourAnalyzer = new YourAnalyzer();
yourAnalyzer.setObjectDetector(objectDetector, labels);
imageAnalysis.setAnalyzer(
ContextCompat.getMainExecutor(this),
yourAnalyzer);
Camera camera =
cameraProvider.bindToLifecycle(
this,
cameraSelector,
preview,
imageAnalysis
);
preview.setSurfaceProvider(prevView.createSurfaceProvider(camera.getCameraInfo()));
}
private Boolean allPermissionsGranted() {
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED
) {
return false;
}
}
return true;
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera();
} else {
Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT)
.show();
finish();
}
}
}
private static class YourAnalyzer implements ImageAnalysis.Analyzer {
private ObjectDetector objectDetector;
private List<String> labels;
public void setObjectDetector(ObjectDetector objectDetector, List<String> labels) {
this.objectDetector = objectDetector;
this.labels = labels;
}
#Override
#ExperimentalGetImage
public void analyze(#NonNull ImageProxy imageProxy) {
Image mediaImage = imageProxy.getImage();
if (mediaImage != null) {
InputImage image = InputImage.fromMediaImage(
mediaImage,
imageProxy.getImageInfo().getRotationDegrees()
);
objectDetector
.process(image)
.addOnFailureListener(e -> imageProxy.close())
.addOnSuccessListener(detectedObjects -> {
// list of detectedObjects has all the information you need
StringBuilder builder = new StringBuilder();
for (DetectedObject detectedObject : detectedObjects) {
for (DetectedObject.Label label : detectedObject.getLabels()) {
builder.append(labels.get(label.getIndex()));
builder.append("\n");
}
}
Log.d("OBJECTS DETECTED", builder.toString().trim());
imageProxy.close();
});
}
}
}
static class LinesSequence implements Iterator<String> {
private BufferedReader reader;
private String nextValue;
private Boolean done = false;
public LinesSequence(BufferedReader reader) {
this.reader = reader;
}
#Override
public boolean hasNext() {
if (nextValue == null && !done) {
try {
nextValue = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
nextValue = null;
}
if (nextValue == null) done = true;
}
return nextValue != null;
}
#Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String answer = nextValue;
nextValue = null;
return answer;
}
}
}
XML layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.camera.view.PreviewView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Gradle file configuration
android {
...
aaptOptions {
noCompress "tflite" // Your model\'s file extension: "tflite", "lite", etc.
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
...
implementation 'com.google.mlkit:object-detection-custom:16.0.0'
def camerax_version = "1.0.0-beta03"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha10"
}

Categories

Resources