From f9ca3ecc0f896ee7d1895afb2c2778c09d03efb3 Mon Sep 17 00:00:00 2001
From: David GEISSBUHLER <dgeissbuhler@italix29.idiap.ch>
Date: Tue, 27 Jun 2017 16:08:48 +0200
Subject: [PATCH] Added original C++ code by Robby Tan for specular highlights
 removal with corresponding python bindings.'

---
 bob/ip/qualitymeasure/main.cpp                | 129 +++++
 .../tan_specular_highlights_orig.cpp          | 445 ++++++++++++++++++
 bob/ip/qualitymeasure/version.cpp             |  72 +++
 setup.py                                      |  47 +-
 4 files changed, 691 insertions(+), 2 deletions(-)
 create mode 100644 bob/ip/qualitymeasure/main.cpp
 create mode 100644 bob/ip/qualitymeasure/tan_specular_highlights_orig.cpp
 create mode 100644 bob/ip/qualitymeasure/version.cpp

diff --git a/bob/ip/qualitymeasure/main.cpp b/bob/ip/qualitymeasure/main.cpp
new file mode 100644
index 0000000..fbe072a
--- /dev/null
+++ b/bob/ip/qualitymeasure/main.cpp
@@ -0,0 +1,129 @@
+// include directly and indirectly dependent libraries
+#ifdef NO_IMPORT_ARRAY
+#undef NO_IMPORT_ARRAY
+#endif
+
+
+#include <bob.blitz/cppapi.h>
+#include <bob.blitz/cleanup.h>
+#include <bob.extension/documentation.h>
+
+
+// declare C++ functions
+void remove_highlights_orig(  blitz::Array<float ,3> &img,
+                              blitz::Array<float ,3> &diff,
+                              blitz::Array<float ,3> &sfi,
+                              blitz::Array<float ,3> &residue,
+                              float  epsilon);
+
+
+// declare the function
+static PyObject* PyRemoveHighlights(PyObject*, PyObject* args, PyObject* kwargs) {
+
+  BOB_TRY
+
+  static const char* const_kwlist[] = {"array", "startEps", 0};
+  static char** kwlist = const_cast<char**>(const_kwlist);
+
+  PyBlitzArrayObject* array;
+  double epsilon = 0.5f;
+
+  if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|d", kwlist,
+                                  &PyBlitzArray_Converter, &array,
+                                  &epsilon)) return 0;
+
+  // check that the array has the expected properties
+  if (array->type_num != NPY_FLOAT32|| array->ndim != 3){
+    PyErr_Format(PyExc_TypeError,
+                "remove_highlights : Only 3D arrays of type float32 are allowed");
+    return 0;
+  }
+
+  // extract the actual blitz array from the Python type
+  blitz::Array<float ,3> img = *PyBlitzArrayCxx_AsBlitz<float , 3>(array);
+
+  // results
+  int dim_x = img.shape()[2];
+  int dim_y = img.shape()[1];
+
+  blitz::Array<float ,3> diffuse_img(3, dim_y, dim_x);
+  blitz::Array<float ,3> speckle_free_img(3, dim_y, dim_x);
+  blitz::Array<float ,3> speckle_img(3, dim_y, dim_x);
+
+  diffuse_img       = 0;
+  speckle_free_img  = 0;
+  speckle_img       = 0;
+
+  // call the C++ function
+  remove_highlights_orig(img, diffuse_img, speckle_free_img, speckle_img, (float)epsilon);
+
+  // convert the blitz array back to numpy and return it
+  PyObject *ret_tuple = PyTuple_New(3);
+  PyTuple_SetItem(ret_tuple, 0, PyBlitzArrayCxx_AsNumpy(speckle_free_img));
+  PyTuple_SetItem(ret_tuple, 1, PyBlitzArrayCxx_AsNumpy(diffuse_img));
+  PyTuple_SetItem(ret_tuple, 2, PyBlitzArrayCxx_AsNumpy(speckle_img));
+
+  return ret_tuple;
+
+  // handle exceptions that occurred in this function
+  BOB_CATCH_FUNCTION("remove_highlights", 0)
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/////// Python module declaration ////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+// module-wide methods
+static PyMethodDef module_methods[] = {
+  {
+    "remove_highlights",
+    (PyCFunction)PyRemoveHighlights,
+    METH_VARARGS|METH_KEYWORDS,
+    "remove_highlights [doc]"
+  },
+  {NULL}  // Sentinel
+};
+
+// module documentation
+PyDoc_STRVAR(module_docstr, "Exemplary Python Bindings");
+
+// module definition
+#if PY_VERSION_HEX >= 0x03000000
+static PyModuleDef module_definition = {
+  PyModuleDef_HEAD_INIT,
+  BOB_EXT_MODULE_NAME,
+  module_docstr,
+  -1,
+  module_methods,
+  0, 0, 0, 0
+};
+#endif
+
+// create the module
+static PyObject* create_module (void) {
+
+# if PY_VERSION_HEX >= 0x03000000
+  PyObject* module = PyModule_Create(&module_definition);
+  auto module_ = make_xsafe(module);
+  const char* ret = "O";
+# else
+  PyObject* module = Py_InitModule3(BOB_EXT_MODULE_NAME, module_methods, module_docstr);
+  const char* ret = "N";
+# endif
+  if (!module) return 0;
+
+  if (PyModule_AddStringConstant(module, "__version__", BOB_EXT_MODULE_VERSION) < 0) return 0;
+
+  /* imports bob.blitz C-API + dependencies */
+  if (import_bob_blitz() < 0) return 0;
+
+  return Py_BuildValue(ret, module);
+}
+
+PyMODINIT_FUNC BOB_EXT_ENTRY_NAME (void) {
+# if PY_VERSION_HEX >= 0x03000000
+  return
+# endif
+    create_module();
+}
diff --git a/bob/ip/qualitymeasure/tan_specular_highlights_orig.cpp b/bob/ip/qualitymeasure/tan_specular_highlights_orig.cpp
new file mode 100644
index 0000000..b817b1d
--- /dev/null
+++ b/bob/ip/qualitymeasure/tan_specular_highlights_orig.cpp
@@ -0,0 +1,445 @@
+/**
+ * @author David Geissbuhler <andre.anjos@idiap.ch>
+ * @date Tue 27 Jun 15:54:00 2016
+ *
+ *  Original version of the specular highlights removal code by Robby T. Tan
+ *  reference:
+ *  "separating reflection components of textured surfaces using a single image"
+ *  by Robby T. Tan, Katsushi Ikeuchi,
+ *  IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI),
+ *  27(2), pp.179-193, February, 2005
+ *
+ *  This is the original implementation based on the C++ code provided by Prof.
+ *  Robby Tan but using Blitz++ arrays:
+ *       http://tanrobby.github.io/code.html#
+ *       http://tanrobby.github.io/code/highlight.zip
+ *
+ */
+
+#include <blitz/array.h>
+
+#define SPECULARX		10
+#define SPECULARY		11
+#define DIFFUSE			12
+#define BOUNDARY		13
+#define NOISE			14
+#define CAMERA_DARK		15
+
+void specular_free_image( blitz::Array<float ,3> &src,
+                          blitz::Array<int,2>   &src_i,
+					                blitz::Array<float ,3> &sfi);
+
+void iteration(           blitz::Array<float ,3> &src,
+                          blitz::Array<int,2>   &src_i,
+                          blitz::Array<float ,3> &sfi,
+				                  float  epsilon);
+
+int init(                 blitz::Array<float ,3> &src,
+                          blitz::Array<int,2>   &src_i,
+                          blitz::Array<float ,3> &sfi,
+				                  float  epsilon);
+
+int reset_labels(         blitz::Array<int,2>   &src_i);
+
+// the main function to remove highlights from a single image
+void remove_highlights_orig(  blitz::Array<float ,3> &img,
+                              blitz::Array<float ,3> &diff,
+                              blitz::Array<float ,3> &sfi,
+                              blitz::Array<float ,3> &residue,
+                              float  epsilon)
+{
+  // flags
+  int dim_x = img.shape()[2];
+  int dim_y = img.shape()[1];
+
+  blitz::Array<int,2> img_i(dim_y, dim_x);
+
+  //SPECULAR-FREE IMAGE
+
+  specular_free_image(img, img_i, sfi);
+
+  //ITERATIVE PART
+  float  step =0.01f;
+
+  // copy source
+  diff = img;
+
+  while( epsilon >= 0.0 )
+  {
+    // run the main iteration
+    //printf("*");
+    iteration(diff, img_i, sfi, epsilon);
+    epsilon -= step;
+    //printf(": %f\n",epsilon);
+  }
+
+  // compute residue
+  residue = img - diff;
+}
+
+// utilities
+
+inline float  tot(float  r, float  g, float  b)
+{
+  return r + g + b;
+}
+
+inline float  max(float  r, float  g, float  b)
+{
+  float  max_ = r;
+  if(g>max_) max_ = g;
+  if(b>max_) max_ = b;
+  return max_;
+}
+
+inline float  r_chroma(float  r, float  g, float  b)
+{
+  float  tot_ = tot(r,g,b);
+  if (tot_!=0)  return r/tot_;
+  else          return 0;
+}
+
+inline float  g_chroma(float  r, float  g, float  b)
+{
+  float  tot_ = tot(r,g,b);
+  if (tot_!=0)  return g/tot_;
+  else          return 0;
+}
+
+inline float  b_chroma(float  r, float  g, float  b)
+{
+  float  tot_ = tot(r,g,b);
+  if (tot_!=0)  return b/tot_;
+  else          return 0;
+}
+
+inline float  max_chroma(float  r, float  g, float  b)
+{
+  float  tot_ = tot(r,g,b);
+  if (tot_!=0)  return max(r,g,b)/tot_;
+  else          return 0;
+}
+
+// remove the specular component from source
+
+void specular_free_image( blitz::Array<float ,3> &src,
+                          blitz::Array<int,2>   &src_i,
+					                blitz::Array<float ,3> &sfi)
+{
+  float  Lambda=0.6f;
+  float  camDark=10.0f;	// for pixels that are too dark
+  float  lambdaConst = 3.0f * Lambda - 1.0f;
+
+  //SPECULAR FREE IMAGE
+  int dim_x = src.shape()[2];
+  int dim_y = src.shape()[1];
+
+  int y,x;
+  for(y = 0; y < dim_y; y++){
+    for(x = 0; x < dim_x; x++){
+      //get the rgb values
+      float  r = src(0,y,x);
+      float  g = src(1,y,x);
+      float  b = src(2,y,x);
+
+      //copy the rgb to the sfi
+      sfi(0,y,x) = r;
+      sfi(1,y,x) = g;
+      sfi(2,y,x) = b;
+
+      // init
+      src_i(y,x) = 0;
+
+      //check for camera dark and achromatic pixels
+      if(((r < camDark) &&
+          (g < camDark) &&
+          (b < camDark)))
+      {
+	       src_i(y,x) = CAMERA_DARK;
+	       continue;
+      }
+
+      //perform the specular-to-diffuse mechanism
+
+      float  c    = max_chroma(r,g,b);
+      float  numr = max(r,g,b) * (3.0f * c - 1.0f);
+      float  denm = c * lambdaConst;
+      float  dI   = numr / denm;
+
+      float  sI = (tot(r,g,b) - dI)/3.0f;
+
+      float  dr,dg,db;
+      dr = (r - sI);
+      dg = (g - sI);
+      db = (b - sI);
+
+      if(dr<0) dr=0;
+      if(dg<0) dg=0;
+      if(db<0) db=0;
+
+      if(dr>255) dr=255;
+      if(dg>255) dg=255;
+      if(db>255) db=255;
+
+      sfi(0,y,x) = dr;
+      sfi(1,y,x) = dg;
+      sfi(2,y,x) = db;
+
+    }
+  }
+
+}
+
+// to apply specular to diffuse equation or mechanism
+
+inline int specular_2_diffuse(int y, int x, blitz::Array<float ,3> &iro,
+                              blitz::Array<int,2> &iro_i, float  maxChroma)
+{
+  float  c = max_chroma(iro(0,y,x), iro(1,y,x), iro(2,y,x));
+  float  m = max(iro(0,y,x), iro(1,y,x), iro(2,y,x));
+  float  t = tot(iro(0,y,x), iro(1,y,x), iro(2,y,x));
+  float  numr = (m*(3.0f*c - 1.0f));
+  float  denm = (c*(3.0f*maxChroma - 1.0f));
+
+  float  dI = numr / denm;
+
+  float  sI = (t - dI)/3.0f;
+
+  float  nr = (iro(0,y,x) - sI);
+  float  ng = (iro(1,y,x) - sI);
+  float  nb = (iro(2,y,x) - sI);
+
+  if(nr<=0 || ng<=0 || nb<=0)
+  {
+    iro_i(y,x)=NOISE;
+    return 1;
+  }
+  else
+  {
+    iro(0,y,x) = nr;
+    iro(1,y,x) = ng;
+    iro(2,y,x) = nb;
+
+    return 0;
+  }
+}
+
+// specular reduction mechanism
+
+void iteration( blitz::Array<float ,3> &src,
+                blitz::Array<int,2>   &src_i,
+                blitz::Array<float ,3> &sfi,
+				        float  epsilon)
+{
+  int x,y;
+  int dim_x = src.shape()[2];
+  int dim_y = src.shape()[1];
+
+  float  thR = 0.1f, thG = 0.1f;
+
+  // to have the initial labels
+  int count = init(src,src_i,sfi,epsilon);
+  int pcount;
+
+  while(1)
+  {
+    for(y=0;y<dim_y-1;y++)
+    {
+	    for(x=0;x<dim_x-1;x++)
+      {
+
+        if(src_i(y,x)==CAMERA_DARK) continue;
+
+        //get the rgb values
+        float  r = src(0,y,x);
+        float  g = src(1,y,x);
+        float  b = src(2,y,x);
+
+        float  cr = r_chroma(r,g,b);		// red chroma
+        float  cg = g_chroma(r,g,b);		// green chroma
+
+        //get the rgb values
+        float  rx = src(0,y,x+1);
+        float  gx = src(1,y,x+1);
+        float  bx = src(2,y,x+1);
+
+        float  cr_next_x = r_chroma(rx,gx,bx); // red chroma
+        float  cg_next_x = g_chroma(rx,gx,bx); // green chroma
+
+        //get the rgb values
+        float  ry = src(0,y+1,x);
+        float  gy = src(1,y+1,x);
+        float  by = src(2,y+1,x);
+
+        float  cr_next_y = r_chroma(ry,gy,by); // red chroma
+        float  cg_next_y = g_chroma(ry,gy,by); // green chroma
+
+        // derivatives
+        float  drx = cr_next_x-cr;//pixel right
+        float  dgx = cg_next_x-cg;
+        float  dry = cr_next_y-cr;//pixel below
+        float  dgy = cg_next_y-cg;
+
+        if(src_i(y,x) == SPECULARX)
+        {
+          //if it is  a boundary in the x direction
+          if(fabs(drx) > thR && fabs(dgx) > thG)
+          {
+            //pixel right
+            src_i(y,x)=BOUNDARY;
+            continue;
+          }
+
+          //if it is a noise
+          if(fabs(max_chroma(r,g,b) - max_chroma(rx,gx,bx)) < 0.01)
+          {
+            src_i(y,x)=NOISE;
+            continue;
+          }
+
+          //reduce the specularity at x direction
+          if(max_chroma(r,g,b) < max_chroma(rx,gx,bx))
+          {
+            specular_2_diffuse(y,x,src,src_i,max_chroma(rx,gx,bx));
+            src_i(y,x)=DIFFUSE;
+            src_i(y,x+1)=DIFFUSE;
+          }
+          else
+          {
+            specular_2_diffuse(y,x+1,src,src_i,max_chroma(r,g,b));
+            src_i(y,x)=DIFFUSE;
+            src_i(y,x+1)=DIFFUSE;
+          }
+        }
+
+	      if(src_i(y,x) == SPECULARY)
+	      {
+	        //if it is a boundary in the y direction
+	        if(fabs(dry) > thR && fabs(dgy) > thG)
+          {
+            //pixel right
+		        src_i(y,x)=BOUNDARY;
+		        continue;
+	        }
+
+	        //if it is a noise
+	        if(fabs(max_chroma(r,g,b) - max_chroma(ry,gy,by))<0.01)
+          {
+		        src_i(y,x)=NOISE;
+		        continue;
+	        }
+
+	        //reduce the specularity in y direction
+	        if(max_chroma(r,g,b) < max_chroma(ry,gy,by))
+          {
+            specular_2_diffuse(y,x,src,src_i,max_chroma(ry,gy,by));
+            src_i(y,x)=DIFFUSE;
+            src_i(y+1,x)=DIFFUSE;
+          }
+          else
+          {
+            specular_2_diffuse(y+1,x,src,src_i,max_chroma(r,g,b));
+            src_i(y,x)=DIFFUSE;
+            src_i(y+1,x)=DIFFUSE;
+          }
+	      }
+	    }
+    }
+
+    pcount=count;
+    count = init(src,src_i,sfi,epsilon);
+
+    if(count==0)
+	    break;
+    if(pcount<=count)
+	    break;
+  }
+
+  reset_labels(src_i);
+}
+
+// to have initial labels
+
+int init(   blitz::Array<float ,3> &src,
+            blitz::Array<int,2>   &src_i,
+            blitz::Array<float ,3> &sfi,
+				    float  epsilon)
+{
+  int dim_x = src.shape()[2];
+  int dim_y = src.shape()[1];
+  int x,y;	// pixel iterators
+
+  int count=0;
+
+  for(y = 1; y < dim_y - 1; y++){
+    for(x = 1; x < dim_x - 1; x++){
+      switch(src_i(y,x))
+      {
+        case BOUNDARY:
+        case NOISE:
+        case CAMERA_DARK:
+	        continue;
+	      break;
+      }
+
+      float  src_tot_0 = tot(src(0,y,x), src(1,y,x), src(2,y,x));
+      float  src_tot_x = tot(src(0,y,x+1), src(1,y,x+1), src(2,y,x+1));
+      float  src_tot_y = tot(src(0,y+1,x), src(1,y+1,x), src(2,y+1,x));
+
+      float  sfi_tot_0 = tot(sfi(0,y,x), sfi(1,y,x), sfi(2,y,x));
+      float  sfi_tot_x = tot(sfi(0,y,x+1), sfi(1,y,x+1), sfi(2,y,x+1));
+      float  sfi_tot_y = tot(sfi(0,y+1,x), sfi(1,y+1,x), sfi(2,y+1,x));
+
+      float  dlog_src_x = log( fabs( src_tot_x - src_tot_0 ) );
+      float  dlog_src_y = log( fabs( src_tot_y - src_tot_0 ) );
+
+      float  dlog_sfi_x = log( fabs( sfi_tot_x - sfi_tot_0 ) );
+      float  dlog_sfi_y = log( fabs( sfi_tot_y - sfi_tot_0 ) );
+
+      float  dlogx = (dlog_src_x - dlog_sfi_x);
+      float  dlogy = (dlog_src_y - dlog_sfi_y);
+
+      dlogx=fabs(dlogx);
+      dlogy=fabs(dlogy);
+
+      // specular in the x direction
+      if(dlogx > epsilon)
+      {
+  	     src_i(y,x) = SPECULARX;
+	       count++;
+	       continue;  // go to the next pixel
+      }
+
+      //specular in the y direction
+      if(dlogy > epsilon)
+      {
+	       src_i(y,x)= SPECULARY;
+	       count++;
+	       continue;
+      }
+
+      src_i(y,x) = DIFFUSE;
+
+    }
+  }
+
+  return count;	// return the number of specular pixels
+}
+
+// to reset the label of the pixels
+
+int reset_labels(blitz::Array<int,2> &src_i)
+{
+  int dim_x = src_i.shape()[1];
+  int dim_y = src_i.shape()[0];
+
+  for(int y=0;y<dim_y;y++)
+  {
+    for(int x=0;x<dim_x;x++)
+    {
+      if(src_i(y,x)==CAMERA_DARK) continue;
+      src_i(y,x)=0;
+    }
+  }
+  return 0;
+}
diff --git a/bob/ip/qualitymeasure/version.cpp b/bob/ip/qualitymeasure/version.cpp
new file mode 100644
index 0000000..1cf79b2
--- /dev/null
+++ b/bob/ip/qualitymeasure/version.cpp
@@ -0,0 +1,72 @@
+/**
+ * @author Andre Anjos <andre.anjos@idiap.ch>
+ * @date Thu  7 Nov 13:50:16 2013
+ *
+ * @brief Binds configuration information available from bob
+ */
+
+#define BOB_IMPORT_VERSION
+#include <bob.blitz/config.h>
+#include <bob.blitz/cleanup.h>
+
+// builds the dictionary of versions
+static PyObject* build_version_dictionary() {
+
+  PyObject* retval = PyDict_New();
+  if (!retval) return 0;
+  auto retval_ = make_safe(retval);
+
+  if (!dict_steal(retval, "Blitz++", blitz_version())) return 0;
+  if (!dict_steal(retval, "Boost", boost_version())) return 0;
+  if (!dict_steal(retval, "Compiler", compiler_version())) return 0;
+  if (!dict_steal(retval, "Python", python_version())) return 0;
+  if (!dict_steal(retval, "NumPy", numpy_version())) return 0;
+  if (!dict_steal(retval, "bob.blitz", bob_blitz_version())) return 0;
+
+  return Py_BuildValue("O", retval);
+}
+
+static PyMethodDef module_methods[] = {
+    {0}  /* Sentinel */
+};
+
+PyDoc_STRVAR(module_docstr,
+"Information about software used to compile the C++ Bob API"
+);
+
+#if PY_VERSION_HEX >= 0x03000000
+static PyModuleDef module_definition = {
+  PyModuleDef_HEAD_INIT,
+  BOB_EXT_MODULE_NAME,
+  module_docstr,
+  -1,
+  module_methods,
+  0, 0, 0, 0
+};
+#endif
+
+static PyObject* create_module (void) {
+
+# if PY_VERSION_HEX >= 0x03000000
+  PyObject* module = PyModule_Create(&module_definition);
+  auto module_ = make_xsafe(module);
+  const char* ret = "O";
+# else
+  PyObject* module = Py_InitModule3(BOB_EXT_MODULE_NAME, module_methods, module_docstr);
+  const char* ret = "N";
+# endif
+  if (!module) return 0;
+
+  /* register version numbers and constants */
+  if (PyModule_AddStringConstant(module, "module", BOB_EXT_MODULE_VERSION) < 0) return 0;
+  if (PyModule_AddObject(module, "externals", build_version_dictionary()) < 0) return 0;
+
+  return Py_BuildValue(ret, module);
+}
+
+PyMODINIT_FUNC BOB_EXT_ENTRY_NAME (void) {
+# if PY_VERSION_HEX >= 0x03000000
+  return
+# endif
+    create_module();
+}
diff --git a/setup.py b/setup.py
index e015150..8371b84 100644
--- a/setup.py
+++ b/setup.py
@@ -3,11 +3,18 @@
 # Sushil Bhattacharjee <sushil.bhattacharjee@idiap.ch>
 # Tue 7 Mar 2017 11:26:26 CET
 
+setup_packages = ['bob.extension', 'bob.blitz']
+bob_packages = []
+
 from setuptools import setup, find_packages, dist
-dist.Distribution(dict(setup_requires=['bob.extension']))
+dist.Distribution(dict(setup_requires = setup_packages + bob_packages))
+
+# import the Extension class and the build_ext function from bob.blitz
+from bob.blitz.extension import Extension, build_ext
 
+# load the requirements.txt for additional requirements
 from bob.extension.utils import load_requirements
-requirements = load_requirements()
+requirements = setup_packages + bob_packages + load_requirements()
 
 version = open("version.txt").read().rstrip()
 
@@ -31,6 +38,42 @@ setup(
 
     install_requires = requirements,
 
+    # We are defining two extensions here. Each of them will be compiled
+    # independently into a separate .so file.
+    ext_modules = [
+
+      # The first extension defines the version of this package and all C++-dependencies.
+      Extension("bob.ip.qualitymeasure.version",
+        # list of files compiled into this extension
+        [
+          "bob/ip/qualitymeasure/version.cpp",
+        ],
+        # additional parameters, see Extension documentation
+        version = version,
+        bob_packages = bob_packages,
+      ),
+
+      # The second extension contains the actual C++ code and the Python bindings
+      Extension("bob.ip.qualitymeasure._library",
+        # list of files compiled into this extension
+        [
+          # the pure C++ code
+          "bob/ip/qualitymeasure/tan_specular_highlights_orig.cpp",
+          # the Python bindings
+          "bob/ip/qualitymeasure/main.cpp",
+        ],
+        # additional parameters, see Extension documentation
+        version = version,
+        bob_packages = bob_packages,
+      ),
+    ],
+
+    # Important! We need to tell setuptools that we want the extension to be
+    # compiled with our build_ext function!
+    cmdclass = {
+      'build_ext': build_ext,
+    },
+
     entry_points={
       # scripts should be declared using this entry:
       'console_scripts': [
-- 
GitLab