Skip to content

c2py

Abstract

c2py() calls any Python functions with built-in, std::vector<T>, sstd::mat_c<T> and sstd::mat_r<T> (T is limited by built-in types.) types of arguments from 2 lines of C++ codes.
Data is sharing by the tmp directory.

c2py() は built-in 型,std::vector<T> 型, sstd::mat_c<T> 型,および sstd::mat_r<T> 型 (ただし,T は built-in 型に制限される.) を引数に持つ Python 関数を 2 行の C++ コードで呼び出します. データは tmp ディレクトリにより共有されます.

Header file

namespace sstd{
    template<typename T> class c2py;
}

template<typename T>
class sstd::c2py{
public:
    c2py(const char* temporarilyDir,
         const char* importFile,
         const char* functionName,
         const char* format);
    ~c2py();

    template<typename... ARGS>
    T operator()(ARGS... args);
};
  • c2py interface

    sstd::c2py<ATypeOfReturnValue> FunctionName(
        "Tempolary directory",                               // Argument 1
        "A name of calling .py file (without extension)",    // Argument 2
        "A function name calling from .py file",             // Argument 3
        "Arguments types specification of Python function"); // Argument 4
    

  • c2py インターフェースの設計

    sstd::c2py<戻り値の型> 関数名(
        "一時ディレクトリ",                            // 第 1 引数
        "呼び出し先の .py ファイル (ただし拡張子を除く)", // 第 2 引数
        ".py ファイル中から呼び出す関数名",             // 第 3 引数
        "Python 関数の型指定");                      // 第 4 引数
    

Requirements

c2py() requires the importlib, inspect, itertools, numpy, os and sys Python packages. See sstd/src/c2py.py for details.

c2py() は,importlib, inspect, itertools, numpy, os, sys の Python パッケージを必要とします.詳細は sstd/src/c2py.py を参照すること.

List of corresponding types. (対応する型の一覧)

Table 1. shows the correspondence between the types on the C ++ side and the types on the Python side. Table 2. shows a list of type names given when specifying a type on the C ++ side. If not specifying type on the Python side, numpy will be selected. And selecting the conversion symbol ~, the numpy type will be converted to the built-in type. The c2py is basically processed by numpy, because of the binary compatibility between C++ and Python, type conversion of numpy to built-in type will be overhead.

表 1. に C++ 側の型と,Python 側の型との対応を示す. 表 2. に C++ 側の型を指定する際に与える型名の一覧を示す. Python 側の型について,何も指定しない場合は,numpy 型が選択され,変換記号 ~ を指定することで,built-in 型に変換される. c2py 上の処理は,C++ とのバイナリ互換性の問題から,基本的に numpy 型で行われるため,built-in 型への変換は,オーバーヘッドとなる.

Table 1. Correspondence between types implemented in c2py and Python
表 1. c2py で実装されている型と Python 型との対応
Types of C++ side Types of Python side
  Entity / Pointer
/ Pointer array
Entity / Pointer
Entity / Pointer
Entity / Pointer
Entity / Pointer
   bool std::vector<bool> sstd::mat_c<bool> sstd::mat_r<bool> numpy / built-in
   char std::vector<char> sstd::mat_c<char> sstd::mat_r<char> built-in
   uchar std::vector<uchar> sstd::mat_c<uchar> sstd::mat_r<uchar> built-in
   int8 std::vector<int8> sstd::mat_c<int8> sstd::mat_r<int8> numpy / built-in
   int16 std::vector<int16> sstd::mat_c<int16> sstd::mat_r<int16> numpy / built-in
   int32 std::vector<int32> sstd::mat_c<int32> sstd::mat_r<int32> numpy / built-in
   int64 std::vector<int64> sstd::mat_c<int64> sstd::mat_r<int64> numpy / built-in
   uint8 std::vector<uint8> sstd::mat_c<uint8> sstd::mat_r<uint8> numpy / built-in
   uint16 std::vector<uint16> sstd::mat_c<uint16> sstd::mat_r<uint16> numpy / built-in
   uint32 std::vector<uint32> sstd::mat_c<uint32> sstd::mat_r<uint32> numpy / built-in
   uint64 std::vector<uint64> sstd::mat_c<uint64> sstd::mat_r<uint64> numpy / built-in
   float std::vector<float> sstd::mat_c<float> sstd::mat_r<float> numpy / built-in
   double std::vector<double> sstd::mat_c<double> sstd::mat_r<double> numpy / built-in
   std::string std::vector<std::string> sstd::mat_c<std::string> sstd::mat_r<std::string> built-in


Table 2. Type names giving to the 4th argument of c2py.
表 2. c2py の第 4 引数に与える型名.
Types of C++ side
          Entity / Pointer
/ Pointer array
Entity / Pointer
Entity / Pointer
Entity / Pointer
   bool vec<bool> mat_c<bool> mat_r<bool>
   char vec<char> mat_c<char> mat_r<char>
   uchar vec<uchar> mat_c<uchar> mat_r<uchar>
   int8 vec<int8> mat_c<int8> mat_r<int8>
   int16 vec<int16> mat_c<int16> mat_r<int16>
   int32 vec<int32> mat_c<int32> mat_r<int32>
   int64 vec<int64> mat_c<int64> mat_r<int64>
   uint8 vec<uint8> mat_c<uint8> mat_r<uint8>
   uint16 vec<uint16> mat_c<uint16> mat_r<uint16>
   uint32 vec<uint32> mat_c<uint32> mat_r<uint32>
   uint64 vec<uint64> mat_c<uint64> mat_r<uint64>
   float vec<float> mat_c<float> mat_r<float>
   double vec<double> mat_c<double> mat_r<double>
   str / string vec<str> / vec<string> mat_c<str> / mat_c<string> mat_r<str> / mat_r<string>

Usage

Sample1: (Return: "int" / Input: "int" and "const int*")

  • pyFunction.py
    def plus_a_b(a, b): return a+b
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        sstd::mkdir("./tmp");
        sstd::system("echo 'def plus_a_b(a, b): return a+b' > ./tmp/pyFunction.py");
    
        sstd::c2py<int> plus_a_b("./tmpDir", "./tmp/pyFunction", "plus_a_b", "int, int, const int*");
        int a=1, b=2;
        int c=plus_a_b(a, &b); // Running Python here
    
        sstd::printn(c);
        sstd::rm("./tmp"); return 0;
    }
    
  • Execution result
    c = 3
    

Sample2: (Return: "vec<int>" / Input: "const int*", "len" and "const vec<int>*")

  • pyFunction.py
    def plus_vecA_vecB(vecA, vecB): return vecA+vecB
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        sstd::mkdir("./tmp");
        sstd::system("echo 'def plus_vecA_vecB(vecA, vecB): return vecA+vecB' > ./tmp/pyFunction.py");
    
        sstd::c2py<std::vector<int>> plus_vecA_vecB("./tmpDir", "./tmp/pyFunction", "plus_vecA_vecB", "vec<int>, const int*, len, const vec<int>*");
        int arrA[]={1,2,3};
        std::vector<int> vecB={4,5,6};
        std::vector<int> vecC=plus_vecA_vecB(arrA, 3, &vecB); // Running Python here
    
        sstd::printn(vecC);
        sstd::rm("./tmp"); return 0;
    }
    
  • Execution result
    vecC = [5 7 9]
    

Sample3: (Return: "void" / Input: "int*", "int*", "len" and "vec<int>*")

Writing back self multiplied value.
自己乗算値を書き戻す.

  • pyFunction.py
    def selfMult(a, vecB, vecC):
        a[0]=a[0]*a[0]
        for i in range(len(vecB)): vecB[i]=vecB[i]*vecB[i]
        for i in range(len(vecC)): vecC[i]=vecC[i]*vecC[i]
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        sstd::mkdir("./tmp");
        sstd::system("echo 'def selfMult(a, vecB, vecC):\n    a[0]=a[0]*a[0]\n    for i in range(len(vecB)): vecB[i]=vecB[i]*vecB[i]\n    for i in range(len(vecC)): vecC[i]=vecC[i]*vecC[i]' > ./tmp/pyFunction.py");
    
        sstd::c2py<void> selfMult("./tmpDir", "./tmp/pyFunction", "selfMult", "void, int*, int*, len, vec<int>*");
        int a=2;
        int arrB[]={3,4,5};
        std::vector<int> vecC={6,7,8};
        selfMult(&a, arrB, 3, &vecC);
    
        sstd::printn(a);
        printf("arrB[3] = [ "); for(uint i=0; i<3; i++){ printf("%d ", arrB[i]); } printf("]\n");
        sstd::printn(vecC);
        sstd::rm("./tmp"); return 0;
    }
    
  • Execution result
    a = 4
    arrB[3] = [ 9 16 25 ]
    vecC = [36 49 64]
    

Sample4: Types conversion of numpy to built-in

Conversion types in Python side. Symbols on the right side of "|" (which is a separator symbol between C++ and Python) mean the symbols have effect on the Python side. On the right side of "|" enable to take "" or "~" and these order have no meaning. (There is no difference between "|~" and "|~*", so it will work same.)

  • *: A symbol have a meaning to convert input value on Python side to a pseudo pointer type (self inclusion list).
  • ~: A symbol have a meaning to convert input value on Python side to a built-in type (instead of numpy type).

Python 側で型変換を行う場合.セパレータ記号 "|" の左右は,それぞれ,C++ 側と Python 側を表している.分割記号 "|" の右側の型は "" または "~" を取ることができ,これは Python 側における変換記号である.このとき,変換記号 "","~" の順序は意味をなさない.(したがって, "|~" と "|~" の間に差はなく,同じように動作する.)

  • *: Python 側の入力値を擬似ポインタ型 (自己包含リスト) へ変換する.
  • ~: Python 側の入力値を(numpy 型の代わりに)組み込み型に変換する.

  • pyFunction.py

    def checkTypes(Numpy, builtIn, pNumpy, pBuiltIn):
        print(type(Numpy), Numpy)
        print(type(builtIn), builtIn)
        print(type(pNumpy), pNumpy)
        print(type(pBuiltIn), pBuiltIn)
    

  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        sstd::mkdir("./tmp");
        sstd::system("echo 'def checkTypes(Numpy, builtIn, pNumpy, pBuiltIn):\n    print(type(Numpy), Numpy)\n    print(type(builtIn), builtIn)\n    print(type(pNumpy), pNumpy)\n    print(type(pBuiltIn), pBuiltIn)' > ./tmp/pyFunction.py");
    
        sstd::c2py<void> checkTypes("./tmpDir", "./tmp/pyFunction", "checkTypes", "void, int, int|~, int|*, int|*~");
        checkTypes(0, 0, 0, 0);
    
        sstd::rm("./tmp"); return 0;
    }
    
  • Execution result
    <class 'numpy.ndarray'> [0]
    <class 'list'> [0]
    <class 'list'> [array([0], dtype=int32)]
    <class 'list'> [[0]]
    

Sample5: Writing back with changing the length of std::vector<T>

Writing back with changing the length of std::vector<T>. (In order to get value from function, sending address is needed.)
配列長の変化を含む std::vector<T> の書き戻し.(関数から値を受け取るため,アドレスを受け渡している)

  • pyFunction.py
    import numpy as np
    def changeLen(pVec1, vec2):
        pVec1[0]=np.append(pVec1[0], 4) # numpy    # numpy is not able to add values without changing address of variables. so we need to treat as a pointer like objects (self inclusion list).
        vec2.append(4)                  # built-in
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        sstd::mkdir("./tmp");
        sstd::system("echo 'import numpy as np\ndef changeLen(pVec1, vec2):\n    pVec1[0]=np.append(pVec1[0], 4)\n    vec2.append(4)' > ./tmp/pyFunction.py");
    
        sstd::c2py<void> changeLen("./tmpDir", "./tmp/pyFunction", "changeLen", "void, vec<int>*|*, vec<int>*|~");
        std::vector<int> vec1={1,2,3}, vec2={1,2,3};
        changeLen(&vec1, &vec2);
    
        sstd::printn(vec1);
        sstd::printn(vec2);
    
        sstd::rm("./tmp"); return 0;
    }
    
  • Execution result
    vec1 = [1 2 3 4]
    vec2 = [1 2 3 4]
    

Sample6

Receiving multiple return values from python side. (※ "ret" which is a symbol of return value, must be continuous in arg 4. Interrupted ret occurs error.)
Python 側から複数の戻り値を受け取る.(※ 戻り値記号 "ret" は,第 4 引数中で連続している必要がある.不連続な ret はエラーを引き起こす.)

  • pyFunction.py
    def multiRet(): return (9, 9, [1,2,3], [4,5,6])
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        sstd::mkdir("./tmp");
        sstd::system("echo 'def multiRet(): return (9, 9, [1,2,3], [4,5,6])' > ./tmp/pyFunction.py");
    
        sstd::c2py<int> multiRet("./tmpDir", "./tmp/pyFunction", "multiRet", "int, ret int*, ret int*, len, ret vec<int>*");
        int ret0=0;
        int ret1=0;
        int ret2[]={0,0,0};
        std::vector<int> ret3;
        ret0 = multiRet(&ret1, &ret2, 3, &ret3);
    
        sstd::printn(ret0);
        sstd::printn(ret1);
        printf("ret2[3] = [ "); for(uint i=0; i<3; i++){ printf("%d ", ret2[i]); } printf("]\n");
        sstd::printn(ret3);
    
        sstd::rm("./tmp"); return 0;
    }
    
  • Execution result
    ret0 = 9
    ret1 = 9
    ret2[3] = [ 1 2 3 ]
    ret3 = [4 5 6]
    

Appendix

Application sample 1

As one of the most convenient application, c2py enable to call matplotlib which is a famous graph plot library in python from C++. In the code below, generate sin wave on C++ and write graph by matplotlib in Python.

最も便利な応用の 1 つとして,c2py では,Python で有名なグラフプロットライブラリである matplotlib を C++ から呼び出すことができる.下記のサンプルコードでは,C++ 側で生成した sin 波を,Python ライブラリである matplotlib で描画している.

  • pyFunction.py
    import matplotlib as mpl        # "QXcbConnection: Could not connect to display" への対策
    mpl.use('Agg')                  # "QXcbConnection: Could not connect to display" への対策
    import matplotlib.pyplot as plt # "QXcbConnection: Could not connect to display" への対策
    import matplotlib.ticker as tick
    
    def vec2graph(writeName, vecX, vecY):
        plt.clf()
        fig = plt.figure(figsize=(9, 3)) # アスペクト比の設定
        ax1 = fig.add_subplot(111)
        ax1.plot(vecX, vecY, color='k', linewidth=0.5)
    
        title  = "An example of Plotting a figure of sin wave data generated on C++,\n"
        title += "using matplotlib which is a famous graph plotting library of python. \n"
        title += "\"sstd::c2py()\" convertes a type of std::vector<double> on C++ to  \n"
        title += "numpy array type on Python, and calling a Python function from      \n"
        title += "only 2 lines of C++ code.                                                                    "
        ax1.set_title(title)
    
        ax1.grid(which='minor', linewidth=0.5, linestyle=':',  color='gainsboro')
        ax1.grid(which='major', linewidth=0.5, linestyle='-',  color='silver'    )
    
        ax1.tick_params(pad=5, which='major', direction='in', bottom=True, top=True, left=True, right=True, length=4) # 軸の余白 # which: major tick と minor tick に対して変更を適用 # tick を内側方向に # tick を bottom, top, left, right に付加 # tick width # tick length
        ax1.tick_params(pad=5, which='minor', direction='in', bottom=True, top=True, left=True, right=True, length=2) # 軸の余白 # which: major tick と minor tick に対して変更を適用 # tick を内側方向に # tick を bottom, top, left, right に付加 # tick width # tick length
    
        ax1.set_xlabel("Time [sec]\nFig 1.  0.1 Hz sin wave sampled by 10 Hz, 0-60 sec.")
        ax1.set_xlim(0-1, 60+1)
        ax1.xaxis.set_major_locator(tick.MultipleLocator(5))
        ax1.xaxis.set_minor_locator(tick.MultipleLocator(1))
    
        ax1.set_ylabel("Amplitude")
        ax1.set_ylim(-1.1, 1.1)
        ax1.yaxis.set_major_locator(tick.MultipleLocator(0.5))
        ax1.yaxis.set_minor_locator(tick.MultipleLocator(0.1))
    
        plt.savefig(writeName, bbox_inches="tight")
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        double freq2generate = 0.1; // 0.1 Hz sin wave
        double freq2sample = 10;    // 10 Hz sampling
        uint len=60*10 + 1;         // 60 sec
        std::vector<double> vecY = sstd::sinWave(freq2generate, freq2sample, len);
        std::vector<double> vecX(len); for(uint i=0; i<vecX.size(); i++){ vecX[i]=(double)i*(1/freq2sample); }
    
        sstd::c2py<void> vec2graph("./tmpDir", "pyFunctions", "vec2graph", "void, const char*, vec<double>*, vec<double>*");
        vec2graph("./sin.png", &vecX, &vecY);
    
        return 0;
    }
    
  • Execution result
    sin.png

Application sample 2

An example of the additional implementation of vvec<T>. Currently, only vvec is available.

追加実装された vvec<T> の使用例.現状では,vvec<double> のみ利用可能.

  • Python
    def vvec2graph(writeName, vLabel, vvecX, vvecY):
        plt.clf()
        fig = plt.figure(figsize=(8.5, 3)) # アスペクト比の設定
        ax1 = fig.add_subplot(111)
        #cmap = plt.get_cmap("tab10")
        vColor=['black', 'blue', 'red']
        vLineStyle = ['solid', 'solid', 'solid'] # solid, dashed, dashdot, dotted
        for i in range(len(vvecX)):
            #ax1.plot(vvecX[i], vvecY[i], linewidth=0.5, color=cmap(i), linestyle=vLineStyle[i], label=vLabel[i])
            ax1.plot(vvecX[i], vvecY[i], linewidth=0.5, color=vColor[i], linestyle=vLineStyle[i], label=vLabel[i])
        ax1.legend(loc='upper right')
    
        ax1.grid(which='minor', linewidth=0.5, linestyle=':',  color='gainsboro')
        ax1.grid(which='major', linewidth=0.5, linestyle='-',  color='silver'    )
    
        ax1.tick_params(pad=5, which='major', direction='in', bottom=True, top=True, left=True, right=True, length=4) # 軸の余白 # which: major tick と minor tick に対して変更を適用 # tick を内側方向に # tick を bottom, top, left, right に付加 # tick width # tick length
        ax1.tick_params(pad=5, which='minor', direction='in', bottom=True, top=True, left=True, right=True, length=2) # 軸の余白 # which: major tick と minor tick に対して変更を適用 # tick を内側方向に # tick を bottom, top, left, right に付加 # tick width # tick length
    
        ax1.set_xlabel("Time [sec]\nFig 2.  0.1 Hz sin, cos and -cos wave sampled by 10 Hz, 0-60 sec.")
        ax1.set_xlim(0-1, 60+1)
        ax1.xaxis.set_major_locator(tick.MultipleLocator(5))
        ax1.xaxis.set_minor_locator(tick.MultipleLocator(1))
    
        ax1.set_ylabel("Amplitude")
        ax1.set_ylim(-1.1, 1.1)
        ax1.yaxis.set_major_locator(tick.MultipleLocator(0.5))
        ax1.yaxis.set_minor_locator(tick.MultipleLocator(0.1))
    
        plt.legend(loc='best')
        plt.savefig(writeName, bbox_inches="tight") # , dpi=100
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        double freq_generate = 0.1; // 0.1 Hz sin wave
        double freq_sample = 10;    // 10 Hz sampling
        uint len=60*10 + 1;         // 60 sec
        std::vector<double> sinY = sstd::sinWave(freq_generate, freq_sample, len);
        std::vector<double> sinX(len); for(uint i=0; i<sinX.size(); i++){ sinX[i]=(double)i*(1/freq_sample); }
    
        std::vector<double> cosY = sstd::cosWave(freq_generate, freq_sample, len);
        std::vector<double> cosX(len); for(uint i=0; i<cosX.size(); i++){ cosX[i]=(double)i*(1/freq_sample); }
    
        std::vector<std::string> vLabel={"sin", "cos", "-cos"};
        std::vector<std::vector<double>> vvecX={sinX, cosX,    cosX};
        std::vector<std::vector<double>> vvecY={sinY, cosY, -1*cosY};
    
        sstd::c2py<void> vvec2graph(tmpDir, fileName, "vvec2graph", "void, const char*, const vec<str>*, const vvec<double>*, const vvec<double>*");
        vvec2graph("./sin_cos.png", &vLabel, &vvecX, &vvecY);
    
        return 0;
    }
    
  • Execution result
    sin_cos.png

Application sample 3

In the code below, reading png image from Python, editing on C++ and writing to png file by Python again.

Python 関数から png ファイルを読み込み,C++ で色を編集した後,再度 Python 関数で png ファイルへ書き出すサンプルコードを示す.

  • Python
    import numpy as np
    from PIL import Image
    
    def imgPath2mat_rRGB(path):
        imgRaw = Image.open(path)
        imgRGB = imgRaw.split()
        imgR = imgRGB[0]
        imgG = imgRGB[1]
        imgB = imgRGB[2]
        return (imgR, imgG, imgB)
    
    def mat_rRGB2img(path, imgR, imgG, imgB):
        imgCombined = np.dstack((np.dstack((imgR, imgG)), imgB))
        imgPIL      = Image.fromarray(imgCombined)
        imgPIL.save(path)
    
  • main.cpp
    #include <sstd/sstd.hpp>
    
    int main(){
        sstd::c2py<void> imgPath2mat_rRGB("./tmpDir", "pyFunctions", "imgPath2mat_rRGB", "void, ret mat_r<uint8>*, ret mat_r<uint8>*, ret mat_r<uint8>*, const char*");
        sstd::mat_r<uint8> imgR, imgG, imgB;
        imgPath2mat_rRGB(&imgR, &imgG, &imgB, "./sample.png");
    
        for(uint p=0; p<imgG.rows(); p++){
            for(uint q=0; q<imgG.cols(); q++){
                imgG(p, q) = sstd::round2even(0.5*((double)imgG(p, q)));
            }
        }
    
        sstd::c2py<void> mat_rRGB2img("./tmpDir", "pyFunctions", "mat_rRGB2img", "void, const char*, mat_r<uint8>*, mat_r<uint8>*, mat_r<uint8>*");
        mat_rRGB2img("./sample_reCombined.png", &imgR, &imgG, &imgB);
    
        return 0;
    }
    
  • Execution result
    sample.png markerRight.png sample_reCombined.png
    Input image (sample.png)                Output image (sample_reCombined.png)

Implementation