From 5f16fde64a9f48f291a53095e78932dfbff1106b Mon Sep 17 00:00:00 2001
From: David Geissbuhler <david.geissbuhler@idiap.ch>
Date: Wed, 28 Jun 2017 16:22:25 +0200
Subject: [PATCH] added modified c++ algorithm

---
 bob/ip/qualitymeasure/__init__.py             |   1 +
 bob/ip/qualitymeasure/main.cpp                |  17 +-
 bob/ip/qualitymeasure/main_orig.cpp           | 128 +++++
 .../script/remove_highlights.py               |   4 +-
 .../tan_specular_highlights.cpp               | 457 ++++++++++++++++++
 setup.py                                      |  16 +-
 6 files changed, 611 insertions(+), 12 deletions(-)
 create mode 100644 bob/ip/qualitymeasure/main_orig.cpp
 create mode 100644 bob/ip/qualitymeasure/tan_specular_highlights.cpp

diff --git a/bob/ip/qualitymeasure/__init__.py b/bob/ip/qualitymeasure/__init__.py
index f9dbfca..3b35a74 100644
--- a/bob/ip/qualitymeasure/__init__.py
+++ b/bob/ip/qualitymeasure/__init__.py
@@ -5,6 +5,7 @@ from .galbally_iqm_features import compute_quality_features
 from .msu_iqa_features import compute_msu_iqa_features
 
 from ._library import remove_highlights
+from ._library_orig import remove_highlights_orig
 
 
 def get_config():
diff --git a/bob/ip/qualitymeasure/main.cpp b/bob/ip/qualitymeasure/main.cpp
index fbe072a..6dd3b12 100644
--- a/bob/ip/qualitymeasure/main.cpp
+++ b/bob/ip/qualitymeasure/main.cpp
@@ -9,13 +9,12 @@
 #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 C++ function
+void remove_highlights(   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) {
@@ -26,7 +25,7 @@ static PyObject* PyRemoveHighlights(PyObject*, PyObject* args, PyObject* kwargs)
   static char** kwlist = const_cast<char**>(const_kwlist);
 
   PyBlitzArrayObject* array;
-  double epsilon = 0.5f;
+  double epsilon  = 0.5f;
 
   if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O&|d", kwlist,
                                   &PyBlitzArray_Converter, &array,
@@ -55,7 +54,7 @@ static PyObject* PyRemoveHighlights(PyObject*, PyObject* args, PyObject* kwargs)
   speckle_img       = 0;
 
   // call the C++ function
-  remove_highlights_orig(img, diffuse_img, speckle_free_img, speckle_img, (float)epsilon);
+  remove_highlights(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);
diff --git a/bob/ip/qualitymeasure/main_orig.cpp b/bob/ip/qualitymeasure/main_orig.cpp
new file mode 100644
index 0000000..f964ccd
--- /dev/null
+++ b/bob/ip/qualitymeasure/main_orig.cpp
@@ -0,0 +1,128 @@
+// 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++ function
+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* PyRemoveHighlightsOrig(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_orig", 0)
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+/////// Python module declaration ////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////
+
+// module-wide methods
+static PyMethodDef module_methods[] = {
+  {
+    "remove_highlights_orig",
+    (PyCFunction)PyRemoveHighlightsOrig,
+    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/script/remove_highlights.py b/bob/ip/qualitymeasure/script/remove_highlights.py
index a314e07..17610af 100644
--- a/bob/ip/qualitymeasure/script/remove_highlights.py
+++ b/bob/ip/qualitymeasure/script/remove_highlights.py
@@ -13,7 +13,7 @@ import argparse
 import bob.io.base
 import numpy as np
 
-from bob.ip.qualitymeasure import remove_highlights
+from bob.ip.qualitymeasure import remove_highlights_orig
 
 def main(command_line_parameters=None):
     """Remove the specular component of the input image and write result to
@@ -51,7 +51,7 @@ def main(command_line_parameters=None):
     # 2. compute
     print("Extracting diffuse component...")
 
-    sfi, diff, residue = remove_highlights(img.astype(np.float32), 0.5)
+    sfi, diff, residue = remove_highlights_orig(img.astype(np.float32), 0.5)
 
     # 1. save output image
     print("Saving output file: %s" % args.outImg)
diff --git a/bob/ip/qualitymeasure/tan_specular_highlights.cpp b/bob/ip/qualitymeasure/tan_specular_highlights.cpp
new file mode 100644
index 0000000..8054f7a
--- /dev/null
+++ b/bob/ip/qualitymeasure/tan_specular_highlights.cpp
@@ -0,0 +1,457 @@
+/**
+ * @author David Geissbuhler <andre.anjos@idiap.ch>
+ * @date Tue 27 Jun 15:54:00 2016
+ *
+ *  Modified 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
+ *       http://tanrobby.github.io/code.html#
+ *       http://tanrobby.github.io/code/highlight.zip
+ *
+ *  This is a modified implementation based on the C++ code provided by Prof.
+ *  Robby Tan but using Blitz++ arrays and with a modification that also
+ *  ignores pixels marked G_DIFFUSE. This leads to a smaller number of
+ *  iterations per epsilon value, while producing the similar results.
+ *
+ */
+
+#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(   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;
+
+      if(denm == 0) dI = 0;
+      else 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));
+
+  if(fabs(denm) > 0.000000001)
+  {
+    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;
+    }
+  }
+  else
+  {
+    iro_i(y,x)=NOISE;
+    return 1;
+  }
+}
+
+// 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:
+        case DIFFUSE:
+	        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 || isinf(dlog_src_x) || isinf(dlog_sfi_x))
+      {
+  	     src_i(y,x) = SPECULARX;
+	       count++;
+	       continue;  // go to the next pixel
+      }
+
+      //specular in the y direction
+      if(dlogy > epsilon || isinf(dlog_src_y) || isinf(dlog_sfi_y))
+      {
+	       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/setup.py b/setup.py
index 8371b84..e2a2b74 100644
--- a/setup.py
+++ b/setup.py
@@ -58,7 +58,7 @@ setup(
         # list of files compiled into this extension
         [
           # the pure C++ code
-          "bob/ip/qualitymeasure/tan_specular_highlights_orig.cpp",
+          "bob/ip/qualitymeasure/tan_specular_highlights.cpp",
           # the Python bindings
           "bob/ip/qualitymeasure/main.cpp",
         ],
@@ -66,6 +66,20 @@ setup(
         version = version,
         bob_packages = bob_packages,
       ),
+
+        # The second extension contains the actual C++ code and the Python bindings
+        Extension("bob.ip.qualitymeasure._library_orig",
+          # 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_orig.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
-- 
GitLab