dgrunwald / rust-cpython

Rust <-> Python bindings
MIT License
1.81k stars 136 forks source link

Calling module that does not contain functions #140

Open hasanOryx opened 6 years ago

hasanOryx commented 6 years ago

I tried to replicate the solution of issue# 121, with a module that does not have a function but stuck, I understood if there is a function with no argument, we can use cpython::NoArgs, but what if there is no function at all, shall I add one, where there is a way to call it.

The below code open a window reflecting the camera, and do face and eye detection:

camera.py:

import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('./haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('./haarcascade_eye.xml')

cap = cv2.VideoCapture(0)

while True:
    # Capture frame-by-frame
    ret, img = cap.read()

    # Our operations on the frame come here
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    faces = face_cascade.detectMultiScale(gray, 1.3, 5)

    for (x,y,w,h) in faces:
        cv2.rectangle(img, (x,y), (x+w, y+h), (255, 0, 0), 2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = img[y:y+h, x:x+w]
        eyes = eye_cascade.detectMultiScale(roi_gray)
        for (ex,ey,ew,eh) in eyes:
            cv2.rectangle(roi_color, (ex,ey), (ex+ew, ey+eh), (0, 255, 0), 2)

    cv2.imshow('img',img)
    #cv2.imshow('gray',gray)

    # Display the resulting frame

    if cv2.waitKey(20) & 0xFF == ord('q'):
        break

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()

And I tried to call it using the below main.rs:

extern crate cpython;

use cpython::{Python, PyResult, PyModule, ObjectProtocol};

const MODULE_STR: &'static str = include_str!("camera.py");

fn main() {
    let gil = Python::acquire_gil();
    let py = gil.python();

    example(py).unwrap();
}

fn example(py: Python<'_>) -> PyResult<()> {

    let module = module_from_str(py, "camera", MODULE_STR)?;

    // How to re-write this?!
    module.call(py, ???? , cpython::NoArgs, None)?.extract(py)?;

    println!("successfully found the python module at compiletime.:");

    Ok(())
}

fn module_from_str(py: Python<'_>, name: &str, source: &str) -> PyResult<PyModule> {
    let module = cpython::PyModule::new(py, name)?;
    let sys = py.import("sys")?;

    let builtins = cpython::PyModule::import(py, "builtins")?;
    module.dict(py).set_item(py, "__builtins__", &builtins)?; 

    sys.get(py, "modules")?.set_item(py, name, &module)?; 
    py.run(source, Some(&module.dict(py)), None)?;

    Ok(module)
}

I stuck with this line: module.call(py, ???? , cpython::NoArgs, None)?.extract(py)?;

ssokolow commented 6 years ago

The proper answer is "Don't do that". When importing functions or otherwise loading modules, Python code outside of functions is intended for one-time initialization.

Beyond that, even if it is single-use, it's good practice in the Python world to habitually structure it like this, so things like your test suite can import it without automatically touching off the main loop, and so the "entry points" feature in setup.py can be used to generate a launcher on setup.py install.

(The "entry points" feature allows you to install the package into /usr/lib/python3.5/site-packages or equivalent, but then auto-generate one or more launchers in /usr/bin or equivalent which import the module and then call functions of your choosing. In this case, main())

import numpy as np
import cv2

face_cascade = cv2.CascadeClassifier('./haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('./haarcascade_eye.xml')

def main():
    cap = cv2.VideoCapture(0)

    while True:
        # Capture frame-by-frame
        ret, img = cap.read()

        # Our operations on the frame come here
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        faces = face_cascade.detectMultiScale(gray, 1.3, 5)

        for (x,y,w,h) in faces:
        cv2.rectangle(img, (x,y), (x+w, y+h), (255, 0, 0), 2)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = img[y:y+h, x:x+w]
        eyes = eye_cascade.detectMultiScale(roi_gray)
        for (ex,ey,ew,eh) in eyes:
            cv2.rectangle(roi_color, (ex,ey), (ex+ew, ey+eh), (0, 255, 0), 2)

        cv2.imshow('img',img)
        #cv2.imshow('gray',gray)

        # Display the resulting frame

        if cv2.waitKey(20) & 0xFF == ord('q'):
            break

    # When everything done, release the capture
    cap.release()
    cv2.destroyAllWindows()

if __name__ = '__main__':  # This doesn't run if we `import` this file
    main()

That said, if you're dead-set on it, just get rid of the module.call line. The code will run when your module_from_str function calls py.run(source, Some(&module.dict(py)), None)?;

You have to understand that Python function and class definitions are not declarative like Rust. You can put def and class inside functions and then return the results or you can write code without wrapping it up inside functions and classes.

The act of importing a Python module runs the code inside the module you're importing. The difference is that you don't have to return any functions you define for them to stick around because import retains the scope the code ran in and gives it a name, rather than destroying it when the code finishes running like it would with a function call.