Monday, March 7, 2016

Parsing OpenCV C++ Code/Headers

I wanted to create a wrapper of OpenCV for Node js, I've looked at it from different angles as I didn't want to write it by hand, since I had an idea about making it multithreaded and easy to use with typescript bindings.

There's an already existing project called node-opencv but a lot of its functions are not suitable for multithreading or async javascript programming, which is what I aimed for.


hdr_parser.py   

OpenCV's way of exposing the OpenCV api for python is using hdr_parser.py, which goes over the headers and exports a list of functions then needed to be made into python code with gen2.py

The exported list looks something like this:

cv.groupRectangles void
    vector_Rect rectList /IO
    vector_int weights /O
    int groupThreshold
    double eps 0.2
const cv.CASCADE_DO_CANNY_PRUNING 1
const cv.CASCADE_SCALE_IMAGE 2
const cv.CASCADE_FIND_BIGGEST_OBJECT 4
const cv.CASCADE_DO_ROUGH_SEARCH 8
class cv.BaseCascadeClassifier : cv::Algorithm
class cv.CascadeClassifier
cv.CascadeClassifier.CascadeClassifier
cv.CascadeClassifier.CascadeClassifier
    String filename
cv.CascadeClassifier.empty bool
cv.CascadeClassifier.load bool
    String filename
cv.CascadeClassifier.read bool
    FileNode node
cv.CascadeClassifier.detectMultiScale void
    Mat image
    vector_Rect objects /O
    double scaleFactor 1.1
    int minNeighbors 3
    int flags 0
    Size minSize Size()
    Size maxSize Size()

Which isn't so much useful if I'd like to do something more complex such as creating all the functions with their enums as parameters or creating setters and getters instead of get/set functions, but it could be the way OpenCV is written.


PEG.JS

So I've decided to try and parse the headers myself, I've found a PEG.js C++ parser written by Pau Fernández and decided to give it a go, the first execution was terrible, most of the header files couldn't be parsed and I've spent a few days fixing about 80% of it to finish parsing, with some days getting so frustrated that I've even tried to rebuild it myself.

But then I've read somewhere that the problem with C++ parsing expression grammar is that they lack context, so eventually I had to move on and try a different approach.

But before I moved on, I ended up with a nice tool to help me work on that grammar


antlr4

In search of a C++ parser, I've met antlr4, it looked nice and it had many grammars, one of them is C++, while working on that direction, I did not find it suitable, as the learning curve was stiffer than I wanted to invest at the moment.

antlr4 c++ grammer can be found here and the office javascript antlr4 documentation is here.

You can compile the grammer to javascript:

antlr4 -Dlanguage=JavaScript CPP14.g4

or if you would like a visitor:

antlr4 -Dlanguage=JavaScript -visitor CPP14.g4


LLVM

LLVM is a compiler, you can dump the AST. but since it's also directed at full compilation, figuring what you need to take from it is out of the scope of my needs.

You can dump the ast with this command:

clang -cc1 -ast-dump file.c


castxml

castxml is a tool that parses C++ files and generates XML AST, before castxml there was GCC-XML, both were written by Brad King from Kitware who are responsible for some major open source projects such as VTK, CMake and ITK.

castxml showed the most promise, I got to know it after I saw this brief presentation by Matthew MaCormick and looked at the output.

Eventually I decided its time to try it, so I've downloaded it.

It has llvm includes directory and gave it a shot with this command arguments:

castxml.exe --castxml-gccxml -o output.xml "header.hpp" -I includefolder ...

and the entire AST was generated and human readable.
After a bit of manipulation with xml2js, I got the following result:

{ id: '_3119',
       name: 'Cv_iplAllocateImageData',
       type: { id: '_8779', type: [Object], elementtype: 'PointerType' },
       context: [Circular],
       location: 'f66:1938',
       file: 'f66',
       line: '1938',
       elementtype: 'Typedef' },
  { id: '_3120',
       name: 'Cv_iplDeallocate',
       type: { id: '_8780', type: [Object], elementtype: 'PointerType' },
       context: [Circular],
       location: 'f66:1939',
       file: 'f66',
       line: '1939',
       elementtype: 'Typedef' },

from which it was easy to derive:

void filter2D ( InputArray src,  OutputArray dst,  int ddepth,  InputArray kernel,  Point anchor,  double delta,  int borderType );
void sepFilter2D ( InputArray src,  OutputArray dst,  int ddepth,  InputArray kernelX,  InputArray kernelY,  Point anchor,  double delta,  int borderType );
void Sobel ( InputArray src,  OutputArray dst,  int ddepth,  int dx,  int dy,  int ksize,  double scale,  double delta,  int borderType );

Which was pretty neat!

The best part about it, since it went through the llvm compiler and preprocessor, the only exported functions, structures and classes were those that are actually compiled in the real project without any extra work.

please note a few things about castxml:

- it works only with --castxml-gccxml flag, without it, it will compile but not work.
- llvm relies on file extension to distinguish between c and c++ headers, to force it to use c++ compiler for h extension use the "-c c++" argument.
- compiling on windows might throw some errors, it could be because ms vc++ headers rely on a version passed to the compiler, use "-fms-compatibility-version=19.00".
- if you're using exceptions in your code make sure you tell it to llvm as it has exceptions disabled by default "-fexceptions"

bonus: ITK has a module that converts the castxml output to swig 

extra bonus: I've created a parser tool to help me out with the xml file, you can find it here.


swig

Swig actually looks like a perfect tool for this job, it has both V8 and Node js targets, but I wanted a node js with nan target with async workers and multithreaded queuing in libuv and safety locks (mutexes), which I'll probably have to implement into the default swig templates.

At the moment it looks like the best tool for the job but I'm not sure how easy it's going to be to create nan template over regular macros.


For now I'm exploring a combination of castxml and macros, if you think there's a better way, I'll be glad to know about it.

No comments:

Post a Comment