Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a python .pyd for Windows from c/c++ source code? (update: brisk now in Python in case that's what you want)

Tags:

c++

python

c

opencv

How to get from C/C++ extension source code to a pyd file for windows (or other item that I could import to Python)?

edit: The specific library that I wanted to use (BRISK) was included in OpenCV 2.4.3 so my need for this skill went away for the time being. In case you came here looking for BRISK, here is a simple BRISK in Python demo that I posted.


I have the Brisk source code (download) that I would like to build and use in my python application. I got as far as generating a brisk.pyd file... but it was 0 bytes. If there is a better / alternative way to aiming for a brisk.pyd file, then of course I am open to that as well.

edit: Please ignore all the attempts in my original question below and see my answer which was made possible by obmarg's detailed walkthrough

Where am I going wrong?

  1. Distutils without library path: First I tried to build the source as is with distutils and the following setup.py (I have just started learning distutils so this is a shot in the dark). The structure of the BRISK source code is at the bottom of this question for reference.

    from distutils.core import setup, Extension
    module1 = Extension('brisk',
        include_dirs = ['include', 'C:/opencv2.4/build/include', 'C:/brisk/thirdparty/agast/include'],
        #libraries = ['agast_static', 'brisk_static'],
        #library_dirs = ['win32/lib'],
        sources = ['src/brisk.cpp'])
    setup (name = 'BriskPackage',
        ext_modules = [module1])
    

    That instantly gave me the following lines and a 0 byte brisk.pyd somewhere in the build folder. So close?

    running build
    running build_ext
    
  2. Distutils with library path: Scratch that attempt. So I added the two library lines that are commented out in the above setup.py. That seemed to go ok until I got this linking error:

    creating build\lib.win32-2.7
    C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\BIN\link.exe /DLL /nologo /INCREMENTAL:NO /LIBPATH:win32/lib /LIB
    PATH:C:\Python27_32bit\libs /LIBPATH:C:\Python27_32bit\PCbuild agast_static.lib brisk_static.lib /EXPORT:initbrisk build
    \temp.win32-2.7\Release\src/brisk.obj /OUT:build\lib.win32-2.7\brisk.pyd /IMPLIB:build\temp.win32-2.7\Release\src\brisk.
    lib /MANIFESTFILE:build\temp.win32-2.7\Release\src\brisk.pyd.manifest
    LINK : error LNK2001: unresolved external symbol initbrisk
    build\temp.win32-2.7\Release\src\brisk.lib : fatal error LNK1120: 1 unresolved externals
    error: command '"C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\BIN\link.exe"' failed with exit status 1120
    
  3. Uncontrolled flailing: I thought maybe the libraries needed to be built, so I did a crash course (lots of crashing) with cmake + mingw - mingw + vc++ express 2010 as follows:

    • cmake gui: source: c:/brisk, build: c:/brisk/build
    • cmake gui: configure for Visual Studio 10
    • cmake gui: use default options and generate (CMAKE_BACKWARDS_COMPATIBILITY, CMAKE_INSTALL_PREFIX, EXECUTABLE_OUTPUT_PATH, LIBRARY_OUTPUT_PATH)
    • VC++ Express 10: Change to Release and build the solution generated by cmake and get about 20 pages of what look like non-critical warnings followed by all succeeded. Note - no dlls are generated by this. It does generate the following libraries of similar size to the ones included with the download:

      win32/lib/Release/
          agast_static.lib
          brisk_static.lib
      
  4. Further flailing.

Relevant BRISK source file structure for reference:

build/ (empty)
include/brisk/
    brisk.h
    hammingsse.hpp
src
    brisk.cpp
    demo.cpp
thirdparty/agast/
    include/agast/
        agast5_8.h ....
        cvWrapper.h
    src/
        agast5_8.cc ...
    CMakeLists.txt
win32/
    bin/
        brisk.mexw32
        opencv_calib3d220.dll ...
    lib/
        agast_static.lib
        brisk_static.lib
CMakeLists.txt
FindOpenCV.cmake
Makefile
like image 575
KobeJohn Avatar asked May 19 '12 11:05

KobeJohn


People also ask

How do I run a .PYD file in Python?

If you have a DLL named foo. pyd, then it must have a function PyInit_foo(). You can then write Python “import foo”, and Python will search for foo. pyd (as well as foo.py, foo.

How do I create a .PYD file?

A PYD file can be created by creating a module with a name e.g. exmaple. pyd.


2 Answers

Are you sure that this brisk library even exports python bindings? I can't see any reference to it in the source code - it doesn't even seem to import python header files. This would certainly explain why you've not had much success so far - you can't just compile plain C++ code and expect python to interface with it.

I think your second distutils example is closest to correct - it's obviously compiling things and getting to the linker stage, but then you encounter this error. That error just means it can't find a function named initbrisk which I'm guessing would be the top level init function for the module. Again this suggests that you're trying to compile a python module from code that isn't meant for it.

If you want to wrap the C++ code in a python wrapper yourself you could have a look at the official documentation on writing c/c++ extensions. Alternatively you could have a look into boost::python, SIP or shiboken which try to somewhat (or completely) automate the process of making python extensions from C++ code.

EDIT: Since you seem to have made a decent amount of effort to solve the problem yourself and have posted a good question, I've decided to give a more detailed response on how to go about doing this.

Quick Tutorial On Wrapping C++ Libraries Using boost::python

Personally I've only ever used boost::python for stuff like this, so I'll try and give you a good summary of how to go about doing that. I'm going to assume that you're using Visual C++ 2010. I'm also going to assume that you've got a 32bit version of python installed, as I believe the boost pro libraries only provide 32bit binaries.

Installing boost

First you'll need to grab a copy of the boost library. The easiest way to do this is to download an installer from the boost pro website. These should install all the header files and binary files that are required for using the boost c++ library on windows. Take note of where you install these files to, as you'll need them later on - it might be best to install to a path without a space in it. For easyness I'm going to assume you put these files in C:\boost but you can substitute that for the path you actually used.

Alternatively, you can follow these instructions to build boost from source. I'm not 100% sure, but it might be the case that you need to do this in order to get a version of boost::python that is compatible with the version of python you have installed.

Setting up a visual studio project

Next, you'll want to setup a visual studio project for brisk.pyd. If you open visual studio, go to New -> Project then find the option for Win32 Project. Set up your location etc. and click ok. In the wizard that appears select a DLL project type, and then tick the empty project checkbox.

Now that you've created your project, you'll need to set up the include & library paths to allow you to use python, boost::python and the brisk.lib file.

In Visual Studios solution explorer, right click on your project, and select properties from the menu that appears. This should open up the property pages for your project. Go to the Linker -> General section and look for the Additional Library Directories section. You'll need to fill this in with the paths to the .lib files for boost, python and your brisk_static.lib. Generally these can be found in lib (or libs) subdirectories of wherever you've installed the libraries. Paths are seperated with semicolons. I've attached a screenshot of my settings below:

Additional library directories settings

Next, you'll need to get visual studio to link to the .lib files. These sections can be found in the Additional Dependencies field of the Linker -> Input section of the properties. Again it's a semicolon delimited list. You should need to add in libraries for python (in my case this is python27.lib but this will vary by version) and brisk_static.lib. These do not require the full path as you added that in the previous stage. Again, here's a screenshot:

additional dependencies settings

You may also need to add the boost_python library file but I think boost uses some header file magic to save you the trouble. If I'm incorrect then have a look in you boost library path for a file named similar to boost_python-vc100-mt.lib and add that in.

Finally, you'll need to setup the include paths to allow your project to include the relevant C++ header files. To get the relevant settings to appear in project properties, you'll need to add a .cpp file to your project. Right click the source files folder in your solution explorer, and then go to add new item. Select a C++ File (.cpp) and name it main.cpp (or whatever else you want).

Next, go back to your project properties and go to C/C++ -> General. Under the additional libraries directory you need to add the include paths for brisk, python and boost. Again, semicolons for seperators, and again here's a screenshot:

enter image description here

I suspect that you might need to update these settings to include the opencv2 & agast libraries as well but I'll leave that as a task for you to figure out - it should be much the same process.

Wrapping existing c++ classes with boost::python.

Now comes the slightly trickier bit - actually writing C++ to wrap your brisk library in boost python. You can find a tutorial for this here but i'll try and go over it a bit as well.

This will be taking place in the main.cpp file you created earlier. First, add the relevant include statements you'll need at the top of the file:

#include <brisk/brisk.h>
#include <Python.h>
#include <boost/python.hpp>

Next, you'll need to declare your python module. I'm assuming you'd want this to be called brisk, so you do something like this:

BOOST_PYTHON_MODULE(brisk)
{
}

This should tell boost::python to create a python module named brisk.

Next it's just a case of going through all the classes & structs that you want to wrap and declaring boost python classes with them. The declerations of the classes should all be contained in brisk.h. You should only wrap the public members of a class, not any protected or private members. As a quick example, I've done a couple of the structs here:

BOOST_PYTHON_MODULE(brisk)
{
    using namespace boost::python;

    class_< cv::BriskPatternPoint >( "BriskPatternPoint" )
         .def_readwrite("x", &cv::BriskPatternPoint::x)
         .def_readwrite("y", &cv::BriskPatternPoint::y)
         .def_readwrite("sigma", &cv::BriskPatternPoint::sigma);

    class< cv::BriskScaleSpace >( "BriskScaleSpace", init< uint8_t >() )
         .def( "constructPyramid", &cv::BriskScaleSpace::constructPyramid );
}

Here I have wrapped the cv::BriskPatternPoint structure and the cv::BriskScaleSpace class. Some quick explanations:

class_< cv::BriskPatternPoint >( "BriskPatternPoint" ) tells boost::python to declare a class, using the cv::BriskPatternPoint C++ class, and expose it as BriskPatternPoint in python.

.def_readwrite("y", &cv::BriskPatternPoint::y) adds a readable & writeable property to the BriskPatternPoint class. The property is named y, and will map to the BriskPatternPoint::y c++ field.

class< cv::BriskScaleSpace >( "BriskScaleSpace", init< uint8_t >() ) declares another class, this time BriskScaleSpace but also provides a constructor that accepts a uint8_t (an unsigned byte - which should just map to an integer in python, but I'd be careful to not pass in one greater than 255 bytes - I don't know what would happen in that situation)

The following .def line just declares a function - boost::python should (I think) be able to determine the argument types of functions automatically, so you don't need to provide them.

It's probably worth noting that I haven't actually compiled any of these examples - they might well not work at all.

Anyway, to get this fully working in python it should just be a case of doing similar for every structure, class, property & function that you want accessible from python - which is potentially quite a time consuming task!

If you want to see another example of this in action, I did this here to wrap up this class

Building & using the extension

Visual studio should take care of building the extension - then using it is just a case of taking the .DLL and renaming it to .pyd (you can get VS to do this for you, but I'll leave that up to you).

Then you just need to copy your python file to somewhere on your python path (site-packages for example), import it and use it!

import brisk

patternPoint = brisk.BriskPatternPoint()
....    

Anyway, I have spent a good hour or so writing this out - so I'm going to stop here. Apologies if I've left anything out or if anything isn't clear, but I'm doing this mostly from memory. Hopefully it's been of some help to you. If you need anything clarified please just leave a comment, or ask another question.

like image 76
obmarg Avatar answered Oct 18 '22 00:10

obmarg


In case someone needs it, this what I have so far. Basically a BriskFeatureDetector that can be created in Python and then have detect called. Most of this is just confirming/copying what obmarg showed me, but I have added the details that get all the way to the pyd library.

The detect method is still incomplete for me though since it does not convert data types. Anyone who knows a good way to improve this, please do! I did find, for example, this library which seems to convert a numpy ndarray to a cv::Mat, but I don't have the time to figure out how to integrate it now. There are also other data types that need to be converted.

Install OpenCV 2.2

  • for the setup below, I installed to C:\opencv2.2
  • Something about the API or implementation has changed by version 2.4 that gave me problems (maybe the new Algorithm object?) so I stuck with 2.2 which BRISK was developed with.

Install Boost with Boost Python

  • for the setup below, I installed to C:\boost\boost_1_47

Create a Visual Studio 10 Project:

  • new project --> win32
  • for the setup below, I named it brisk
  • next --> DLL application type; empty project --> finished
  • at the top, change from Debug Win32 to Release Win32

Create main.cpp in Source Files

Do this before the project settings so the C++ options become available in the project settings

#include <boost/python.hpp>
#include <opencv2/opencv.hpp>
#include <brisk/brisk.h>

BOOST_PYTHON_MODULE(brisk)
{
    using namespace boost::python;

    //this long mess is the only way I could get the overloaded signatures to be accepted
    void (cv::BriskFeatureDetector::*detect_1)(const cv::Mat&,
                std::vector<cv::KeyPoint, std::allocator<cv::KeyPoint>>&,
                const cv::Mat&) const
                = &cv::BriskFeatureDetector::detect;
    void (cv::BriskFeatureDetector::*detect_vector)(const std::vector<cv::Mat, std::allocator<cv::Mat>>&,
                std::vector< std::vector< cv::KeyPoint, std::allocator<cv::KeyPoint>>, std::allocator< std::vector<cv::KeyPoint, std::allocator<cv::KeyPoint>>>>&,
                const std::vector<cv::Mat, std::allocator<cv::Mat>>&) const
                = &cv::BriskFeatureDetector::detect;

    class_< cv::BriskFeatureDetector >( "BriskFeatureDetector", init<int, int>())
        .def( "detect", detect_1)
    ;
}

Project Settings (right-click on the project --> properties):

  1. Includes / Headers

    • Configuration Properties --> C/C++ --> General
    • add to Additional Include Directories (adjust to your own python / brisk / etc. base paths):

      C:\opencv2.2\include;

      C:\boost\boost_1_47;

      C:\brisk\include;C:\brisk\thirdparty\agast\include;

      C:\python27\include;

  2. Libraries (linker)

    • Configuration Properties --> Linker --> General
    • add to Additional Library Directories (adjust to your own python / brisk / etc. base paths):

      C:\opencv2.2\lib;

      C:\boost\boost_1_47\lib;

      C:\brisk\win32\lib;

      C:\python27\Libs;

    • Configuration Properties --> Linker --> Input

    • add to Additional Dependencies (adjust to your own python / brisk / etc. base paths):

      opencv_imgproc220.lib;opencv_core220.lib;opencv_features2d220.lib;

      agast_static.lib; brisk_static.lib;

      python27.lib;

  3. .pyd output instead of .dll

    • Configuration Properties --> General
    • change Target Extension to .pyd

Build and rename if necessary

  • Right-click on the solution and build/rebuild
  • you may need to rename the output from "Brisk.pyd" to "brisk.pyd" or else python will give you errors about not being able to load the DLL
  • Make brisk.pyd available to python by putting it in site packages or by putting a .pth file that links to its path

Update Path environment variable

  • In windows settings, make sure the following are included in your path (again, adjust to your paths):

    `C:\boost\boost_1_47\lib;C:\brisk\win32\bin`
    
like image 44
KobeJohn Avatar answered Oct 18 '22 00:10

KobeJohn