Introduction
PyOpenMS offers Python bindings to a large part of the OpenMS API.
Build Instructions
In order to configure and build pyOpenMS successfully, you will need to install the following :
-
Install python (preferably, 2.7 but it may run also run with Python 2.6)
-
On windows: you need the 64 bit C++ compiler from Visual Studio 2008. This is important, else you get a different clib than python 2.7 is built with, and pyopenms will crash on import.
-
Install setuptools, see: https://pypi.python.org/pypi/setuptools
-
Install pip and using it, install other required Python modules
$ easy_install pip
$ pip install autowrap
$ pip install nose
If Cython doesn't get installed, install it with
Note that when using pip without root permissions, you have to add a path prefix: –install-option="--prefix=/path/to/local/python/"
-
Install numpy:
-
Configure OpenMS with pyOpenMS: execute cmake as usual, but with parameters "-D PYOPENMS=ON".
On windows add: "-D CMAKE_BUILD_TYPE=Release" as the standard python27.dll is built in release mode.
-
Build pyOpenMS (now there should be pyopenms specific build targets):
on linux: ensure that the libOpenMS.so is in your $LD_LIBRARY_PATH (it needs to be accessible for Python)
-
Run the Python specific tests to make sure that everything went well
$ cd pyOpenMS
$ run_nose.py
run mem leak test:
-
Optionally: if you want to install locally:
$ python setup.py install
If you want to build Python installers:
$ make pyopenms_bdist_egg
or
you find the built installer files in pyOpenMS/dist
Wrapping Workflow and wrapping new Classes
How pyOpenMS wraps Python classes
General concept of how the wrapping is done:
- Step 1: The author declares which classes and which functions of these classes s/he wants to wrap (expose to Python). This is done by writing the function declaration in a file in the pxds/ folder.
- Step 2: The Python tool "autowrap" (developed for this project) creates the wrapping code automatically from the function declaration - see https://github.com/uweschmitt/autowrap for an explanation of the autowrap tool. Since not all code can be wrapped automatically, also manual code can be written in the addons/ folder. Autowrap will create an output file at pyopenms/pyopenms.pyx
- Step 3: Cython translates the pyopenms/pyopenms.pyx to Cpp code at pyopenms/pyopenms.cpp
- Step 4: A compiler compiles the Cpp code to a Python module which is then importable in Python with "import pyopenms"
Maintaining existing wrappers: If the C++ API is changed, then pyOpenMS will not build any more. Thus, find the corresponding file in the pyOpenMS/pxds/ folder and adjust the function declaration accordingly.
How to wrap new classes
To wrap a new OpenMS class: Create a new "pxd" file in the folder "./pxds". As a small example, look at the CVTerm.pxd to get you started. Start with the following structure:
from xxx cimport *
cdef
extern from
"<OpenMS/path/to/header.h>" namespace "
OpenMS":
cdef cppclass ClassName(DefaultParamHandler):
# wrap-inherits:
# DefaultParamHandler
ClassName() nogil except +
ClassName(ClassName) nogil except +
- make sure to use "ClassName:" instead of "ClassName(DefaultParamHandler)" to wrap a class that does not inherit from another class and also remove the two comments below that line.
- always use "cimport" and not Python "import"
- always add default constructor AND copy constructor to the code (note that the C++ compiler will add a default copy constructur to any class, so there is always one if it is not declared, see http://www.cplusplus.com/articles/y8hv0pDG/ "The
implicit copy constructor does a member-wise copy of the source object.")
- to expose a function to Python, copy the signature to your pxd file, e.g. "DataValue getValue()" and make sure you "cimport" all corresponding classes. Replace std::vector with vector from libcpp.vector (see for example PepXMLFile.pxd)
- Remember to include a copy constructor (even if none was declared in the C++ header file) since Cython will need it for certain oprations. Otherwise you might see error messages like "item0.inst = shared_ptr[_ClassName](new _ClassNAme(deref(it_terms))) Call with wrong number of arguments"
Further considerations and limitations:
- Inheritance: there are some limitations, see for example Precursor.pxd
- Reference: arguments by reference may be copied under some circumstances. For example, if they are in an array then not the original argument is handed back, so comparisons might fail. Also, simple Python types like int, float etc cannot be passed by reference.
- operator+=: see for example AASequence.iadd in AASequence.pxd
- operator==, !=, <=, <, >=, > are wrapped automatically
- Iterators: some limitations apply, see MSExperiment.pxd for an example
- copy-constructor becomes __copy__ in Python
- shared pointers: is handled automatically, check DataAccessHelper using shared_ptr[Spectrum]. Use "from smart_ptr cimport shared_ptr" as import statement
These hints can be given to autowrap (also check the autowrap documentation):
- wrap-ignore: is a hint for autowrap to not wrap the function (but the declaration might still be important for Cython to know about)
- wrap-as - see for example AASequence ":"
- wrap-iter-begin, wrap-iter-end (see ConsensusMap.pxd)
- wrap-instances - for templated classes (see MSSpectrum.pxd)
- wrap-attach: enums, static methods (see for example VersionInfo.pxd)
- wrap-upper-limit:size or size() (see MSSpectrum.pxd)
Wrapping code yourself in ./addons
Not all code can be wrapped automatically (yet). Place a file with the same (!) name in the addons filder (e.g. myClass.px in pxds/ and myClass.pyx in addons) and leave two lines empty on the top (this is important). Start with 4 spaces of indent and write your additional wrapper functions, adding a wrap-ignore comment to the pxd file. For some examples, look into the addons folder:
- IDRipper.pyx
- for a reference for both input and output of a complex STL construct (map< String, pair<vector<>, vector<> > )
- MSQuantifications.pyx
- for a vector< vector< pair <String,double > > > as input in registerExperiment
- for a map< String, Ratio> in getRatios to get returned
- QcMLFile.pyx
- for a map< String, map< String,String> > as input
- SequestInfile.pyx
- for a map< String, vector<String> > to get returned
- Attachment.pyx
- for a vector< vector<String> > to get returned
Make sure that you _always_ declare your objects (all C++ and all Cython objects need to be declared) using cdef Type name. Otherwise you get "Cannot
convert ... to Python object" errors.