pymachine.cpp 39 KB
Newer Older
1 2
/**
 * @author Andre Anjos <andre.anjos@idiap.ch>
André Anjos's avatar
André Anjos committed
3
 * @Thu 27 Mar 2014 15:44:46 CET
4
 *
5
 * @brief Bindings for a Bob compatible LIBSVM-based Machine for SVMs
6
 *
André Anjos's avatar
André Anjos committed
7
 * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
8 9
 */

André Anjos's avatar
André Anjos committed
10 11 12 13 14
#define BOB_LEARN_LIBSVM_MODULE
#include <bob.blitz/cppapi.h>
#include <bob.blitz/cleanup.h>
#include <bob.io.base/api.h>
#include <bob.learn.libsvm/api.h>
15 16 17 18 19 20
#include <structmember.h>

/*******************************************************
 * Implementation of Support Vector Machine base class *
 *******************************************************/

André Anjos's avatar
André Anjos committed
21
PyDoc_STRVAR(s_svm_str, BOB_EXT_MODULE_PREFIX ".Machine");
22 23

PyDoc_STRVAR(s_svm_doc,
24 25 26
"Machine(path)\n\
\n\
Machine(hdf5file)\n\
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
\n\
This class can load and run an SVM generated by libsvm.\n\
Libsvm is a simple, easy-to-use, and efficient software\n\
for SVM classification and regression. It solves C-SVM\n\
classification, nu-SVM classification, one-class-SVM,\n\
epsilon-SVM regression, and nu-SVM regression. It also provides\n\
an automatic model selection tool for C-SVM classification.\n\
More information about libsvm can be found on its `website\n\
<http://www.csie.ntu.edu.tw/~cjlin/libsvm/>`_. In particular,\n\
this class covers most of the functionality provided by the\n\
command-line utility svm-predict.\n\
\n\
Input and output is always performed on 1D or 2D arrays with\n\
64-bit floating point numbers.\n\
\n\
This machine can be initialized in two ways: the first is using\n\
an original SVM text file as produced by ``libsvm``. The\n\
second option is to pass a pre-opened HDF5 file pointing to the\n\
machine information to be loaded in memory.\n\
\n\
Using the first constructor, we build a new machine from a\n\
libsvm model file. When you load using the libsvm model loader,\n\
note that the scaling parameters will be set to defaults\n\
(subtraction of 0.0 and division by 1.0). If you need scaling\n\
51
to be applied, set it individually using the appropriate methods\n\
52 53 54 55 56 57 58 59 60
on the returned object.\n\
\n\
Using the second constructor, we build a new machine from an\n\
HDF5 file containing not only the machine support vectors, but\n\
also the scaling factors. Using this constructor assures a 100%\n\
state recovery from previous sessions.\n\
\n\
");

61
/***********************************************
André Anjos's avatar
André Anjos committed
62
 * Implementation of bob.learn.libsvm.Machine *
63 64 65 66
 ***********************************************/

static int PyBobLearnLibsvmMachine_init_svmfile
(PyBobLearnLibsvmMachineObject* self, PyObject* args, PyObject* kwds) {
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

  /* Parses input arguments in a single shot */
  static const char* const_kwlist[] = {"filename", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);

  PyObject* filename = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&", kwlist,
        &PyBobIo_FilenameConverter, &filename))
    return -1;

  auto filename_ = make_safe(filename);

#if PY_VERSION_HEX >= 0x03000000
  const char* c_filename = PyBytes_AS_STRING(filename);
#else
  const char* c_filename = PyString_AS_STRING(filename);
#endif

  try {
87
    self->cxx = new bob::learn::libsvm::Machine(c_filename);
88 89 90 91 92 93 94 95 96 97 98 99 100 101
  }
  catch (std::exception& ex) {
    PyErr_SetString(PyExc_RuntimeError, ex.what());
    return -1;
  }
  catch (...) {
    PyErr_Format(PyExc_RuntimeError, "cannot create new object of type `%s' - unknown exception thrown", Py_TYPE(self)->tp_name);
    return -1;
  }

  return 0;

}

102 103
static int PyBobLearnLibsvmMachine_init_hdf5
(PyBobLearnLibsvmMachineObject* self, PyObject* args, PyObject* kwds) {
104 105 106 107 108 109 110 111 112 113 114 115 116

  /* Parses input arguments in a single shot */
  static const char* const_kwlist[] = {"config", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);

  PyObject* config = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!", kwlist,
        &PyBobIoHDF5File_Type, &config)) return -1;

  auto h5f = reinterpret_cast<PyBobIoHDF5FileObject*>(config);

  try {
117
    self->cxx = new bob::learn::libsvm::Machine(*(h5f->f));
118 119 120 121 122 123 124 125 126 127 128 129 130 131
  }
  catch (std::exception& ex) {
    PyErr_SetString(PyExc_RuntimeError, ex.what());
    return -1;
  }
  catch (...) {
    PyErr_Format(PyExc_RuntimeError, "cannot create new object of type `%s' - unknown exception thrown", Py_TYPE(self)->tp_name);
    return -1;
  }

  return 0;

}

132
static int PyBobLearnLibsvmMachine_init(PyBobLearnLibsvmMachineObject* self,
133 134 135 136
    PyObject* args, PyObject* kwds) {

  Py_ssize_t nargs = (args?PyTuple_Size(args):0) + (kwds?PyDict_Size(kwds):0);

137 138
  if (nargs != 1) {
    PyErr_Format(PyExc_RuntimeError, "number of arguments mismatch - %s requires 1 arguments, but you provided %" PY_FORMAT_SIZE_T "d (see help)", Py_TYPE(self)->tp_name, nargs);
139 140 141
    return -1;
  }

142 143 144 145 146 147
  PyObject* arg = 0; ///< borrowed (don't delete)
  if (PyTuple_Size(args)) arg = PyTuple_GET_ITEM(args, 0);
  else {
    PyObject* tmp = PyDict_Values(kwds);
    auto tmp_ = make_safe(tmp);
    arg = PyList_GET_ITEM(tmp, 0);
148
  }
149 150 151

  if (PyBobIoHDF5File_Check(arg)) {
    return PyBobLearnLibsvmMachine_init_hdf5(self, args, kwds);
152
  }
153 154
  else {
    return PyBobLearnLibsvmMachine_init_svmfile(self, args, kwds);
155 156 157 158
  }

}

159 160
static void PyBobLearnLibsvmMachine_delete
(PyBobLearnLibsvmMachineObject* self) {
161

162 163
  delete self->cxx;
  Py_TYPE(self)->tp_free((PyObject*)self);
164

165
}
166

167 168
int PyBobLearnLibsvmMachine_Check(PyObject* o) {
  return PyObject_IsInstance(o, reinterpret_cast<PyObject*>(&PyBobLearnLibsvmMachine_Type));
169 170 171 172 173 174 175 176 177 178
}

PyDoc_STRVAR(s_input_subtract_str, "input_subtract");
PyDoc_STRVAR(s_input_subtract_doc,
"Input subtraction factor, before feeding data through the\n\
weight matrix W. The subtraction is the first applied\n\
operation in the processing chain - by default, it is set to\n\
0.0.\n\
");

179 180
static PyObject* PyBobLearnLibsvmMachine_getInputSubtraction
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
181 182 183
  return PyBlitzArray_NUMPY_WRAP(PyBlitzArrayCxx_NewFromConstArray(self->cxx->getInputSubtraction()));
}

184 185
static int PyBobLearnLibsvmMachine_setInputSubtraction
(PyBobLearnLibsvmMachineObject* self, PyObject* o, void* /*closure*/) {
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218

  PyBlitzArrayObject* input_subtract = 0;
  if (!PyBlitzArray_Converter(o, &input_subtract)) return -1;
  auto input_subtract_ = make_safe(input_subtract);

  if (input_subtract->type_num != NPY_FLOAT64 || input_subtract->ndim != 1) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit floats 1D arrays for property array `input_subtract'", Py_TYPE(self)->tp_name);
    return -1;
  }

  try {
    self->cxx->setInputSubtraction(*PyBlitzArrayCxx_AsBlitz<double,1>(input_subtract));
  }
  catch (std::exception& ex) {
    PyErr_SetString(PyExc_RuntimeError, ex.what());
    return -1;
  }
  catch (...) {
    PyErr_Format(PyExc_RuntimeError, "cannot reset `input_subtract' of %s: unknown exception caught", Py_TYPE(self)->tp_name);
    return -1;
  }

  return 0;

}

PyDoc_STRVAR(s_input_divide_str, "input_divide");
PyDoc_STRVAR(s_input_divide_doc,
"Input division factor, before feeding data through the\n\
weight matrix W. The division is applied just after\n\
subtraction - by default, it is set to 1.0.\n\
");

219 220
static PyObject* PyBobLearnLibsvmMachine_getInputDivision
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
221 222 223
  return PyBlitzArray_NUMPY_WRAP(PyBlitzArrayCxx_NewFromConstArray(self->cxx->getInputDivision()));
}

224 225
static int PyBobLearnLibsvmMachine_setInputDivision
(PyBobLearnLibsvmMachineObject* self, PyObject* o, void* /*closure*/) {
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258

  PyBlitzArrayObject* input_divide = 0;
  if (!PyBlitzArray_Converter(o, &input_divide)) return -1;
  auto input_divide_ = make_safe(input_divide);

  if (input_divide->type_num != NPY_FLOAT64 || input_divide->ndim != 1) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit floats 1D arrays for property array `input_divide'", Py_TYPE(self)->tp_name);
    return -1;
  }

  try {
    self->cxx->setInputDivision(*PyBlitzArrayCxx_AsBlitz<double,1>(input_divide));
  }
  catch (std::exception& ex) {
    PyErr_SetString(PyExc_RuntimeError, ex.what());
    return -1;
  }
  catch (...) {
    PyErr_Format(PyExc_RuntimeError, "cannot reset `input_divide' of %s: unknown exception caught", Py_TYPE(self)->tp_name);
    return -1;
  }

  return 0;

}

PyDoc_STRVAR(s_shape_str, "shape");
PyDoc_STRVAR(s_shape_doc,
"A tuple that represents the size of the input vector\n\
followed by the size of the output vector in the format\n\
``(input, output)``.\n\
");

259 260
static PyObject* PyBobLearnLibsvmMachine_getShape
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
261 262 263 264
  return Py_BuildValue("(nn)", self->cxx->inputSize(),
      self->cxx->outputSize());
}

265 266 267 268 269 270 271 272 273 274 275 276
PyDoc_STRVAR(s_labels_str, "labels");
PyDoc_STRVAR(s_labels_doc, "The class labels this machine will output");

static PyObject* PyBobLearnLibsvmMachine_getLabels
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
  PyObject* retval = PyList_New(self->cxx->numberOfClasses());
  for (size_t k=0; k<self->cxx->numberOfClasses(); ++k) {
    PyList_SET_ITEM(retval, k, Py_BuildValue("i", self->cxx->classLabel(k)));
  }
  return retval;
}

277 278
PyDoc_STRVAR(s_machine_type_str, "machine_type");
PyDoc_STRVAR(s_machine_type_doc, "The type of SVM machine contained");
279

280
static PyObject* PyBobLearnLibsvmMachine_getMachineType
281
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
282
  return PyBobLearnLibsvm_MachineTypeAsString(self->cxx->machineType());
283 284
}

André Anjos's avatar
André Anjos committed
285 286
PyDoc_STRVAR(s_svm_kernel_type_str, "kernel_type");
PyDoc_STRVAR(s_svm_kernel_type_doc,
287 288
"The type of kernel used by the support vectors in this machine");

289
static PyObject* PyBobLearnLibsvmMachine_getKernelType
290
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
291
  return PyBobLearnLibsvm_KernelTypeAsString(self->cxx->kernelType());
292 293
}

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
PyDoc_STRVAR(s_degree_str, "degree");
PyDoc_STRVAR(s_degree_doc,
"The polinomial degree, only valid if the kernel is ``'POLY'``\n\
(polynomial)");

static PyObject* PyBobLearnLibsvmMachine_getPolynomialDegree
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
  return Py_BuildValue("i", self->cxx->polynomialDegree());
}

PyDoc_STRVAR(s_gamma_str, "gamma");
PyDoc_STRVAR(s_gamma_doc,
"The :math:`\\gamma` parameter for ``'POLY'`` (polynomial),\n\
``'RBF'`` (gaussian) or ``'SIGMOID'`` (sigmoidal) kernels");

static PyObject* PyBobLearnLibsvmMachine_getGamma
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
  return Py_BuildValue("d", self->cxx->gamma());
}

PyDoc_STRVAR(s_coef0_str, "coef0");
PyDoc_STRVAR(s_coef0_doc,
"The coefficient 0 for ``'POLY'`` (polynomial) or\n\
``'SIGMOIDAL'`` (sigmoidal) kernels");

static PyObject* PyBobLearnLibsvmMachine_getCoefficient0
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
  return Py_BuildValue("d", self->cxx->coefficient0());
}

PyDoc_STRVAR(s_probability_str, "probability");
PyDoc_STRVAR(s_probability_doc,
"Set to ``True`` if this machine supports probability outputs");

static PyObject* PyBobLearnLibsvmMachine_getSupportsProbability
(PyBobLearnLibsvmMachineObject* self, void* /*closure*/) {
  if (self->cxx->supportsProbability()) Py_RETURN_TRUE;
  Py_RETURN_FALSE;
}

334
static PyGetSetDef PyBobLearnLibsvmMachine_getseters[] = {
335 336
    {
      s_input_subtract_str,
337 338
      (getter)PyBobLearnLibsvmMachine_getInputSubtraction,
      (setter)PyBobLearnLibsvmMachine_setInputSubtraction,
339 340 341 342 343
      s_input_subtract_doc,
      0
    },
    {
      s_input_divide_str,
344 345
      (getter)PyBobLearnLibsvmMachine_getInputDivision,
      (setter)PyBobLearnLibsvmMachine_setInputDivision,
346 347 348 349 350
      s_input_divide_doc,
      0
    },
    {
      s_shape_str,
351 352
      (getter)PyBobLearnLibsvmMachine_getShape,
      0,
353 354 355
      s_shape_doc,
      0
    },
356 357 358 359 360 361 362 363
    {
      s_labels_str,
      (getter)PyBobLearnLibsvmMachine_getLabels,
      0,
      s_labels_doc,
      0
    },
    {
364 365
      s_machine_type_str,
      (getter)PyBobLearnLibsvmMachine_getMachineType,
366
      0,
367
      s_machine_type_doc,
368 369 370
      0
    },
    {
André Anjos's avatar
André Anjos committed
371
      s_svm_kernel_type_str,
372
      (getter)PyBobLearnLibsvmMachine_getKernelType,
373
      0,
André Anjos's avatar
André Anjos committed
374
      s_svm_kernel_type_doc,
375 376
      0
    },
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
    {
      s_degree_str,
      (getter)PyBobLearnLibsvmMachine_getPolynomialDegree,
      0,
      s_degree_doc,
      0
    },
    {
      s_gamma_str,
      (getter)PyBobLearnLibsvmMachine_getGamma,
      0,
      s_gamma_doc,
      0
    },
    {
      s_coef0_str,
      (getter)PyBobLearnLibsvmMachine_getCoefficient0,
      0,
      s_coef0_doc,
      0
    },
    {
      s_probability_str,
      (getter)PyBobLearnLibsvmMachine_getSupportsProbability,
      0,
      s_probability_doc,
      0
    },
405 406 407 408 409 410 411 412 413
    {0}  /* Sentinel */
};

#if PY_VERSION_HEX >= 0x03000000
#  define PYOBJECT_STR PyObject_Str
#else
#  define PYOBJECT_STR PyObject_Unicode
#endif

414
PyObject* PyBobLearnLibsvmMachine_Repr(PyBobLearnLibsvmMachineObject* self) {
415 416 417 418

  /**
   * Expected output:
   *
André Anjos's avatar
André Anjos committed
419
   * <bob.learn.libsvm.Machine float64@(3, 2)>
420 421
   */

422
  auto shape = make_safe(PyObject_GetAttrString((PyObject*)self, "shape"));
423 424
  auto shape_str = make_safe(PyObject_Str(shape.get()));

425 426
  PyObject* retval = PyUnicode_FromFormat("<%s float64@%U>",
      Py_TYPE(self)->tp_name, shape_str.get());
427 428 429 430 431 432 433 434 435 436 437 438

#if PYTHON_VERSION_HEX < 0x03000000
  if (!retval) return 0;
  PyObject* tmp = PyObject_Str(retval);
  Py_DECREF(retval);
  retval = tmp;
#endif

  return retval;

}

439
PyObject* PyBobLearnLibsvmMachine_Str(PyBobLearnLibsvmMachineObject* self) {
440 441 442 443

  /**
   * Expected output:
   *
André Anjos's avatar
André Anjos committed
444
   * bob.learn.libsvm.Machine (float64) 3 inputs, 2 outputs
445 446 447
   *   properties...
   *
   * TODO: Not fully implemented yet
448 449
   */

450
  return PyBobLearnLibsvmMachine_Repr(self);
451 452 453 454 455

}

PyDoc_STRVAR(s_forward_str, "forward");
PyDoc_STRVAR(s_forward_doc,
456 457 458 459 460
"o.forward(input, [output]) -> array\n\
\n\
o.predict_class(input, [output]) -> array\n\
\n\
o(input, [output]) -> array\n\
461
\n\
462 463
Calculates the **predicted class** using this Machine, given\n\
one single feature vector or multiple ones.\n\
464
\n\
465 466 467 468
The ``input`` array can be either 1D or 2D 64-bit float arrays.\n\
The ``output`` array, if provided, must be of type ``int64``,\n\
always uni-dimensional. The output corresponds to the predicted\n\
classes for each of the input rows.\n\
469 470 471
\n\
.. note::\n\
\n\
472 473
   This method only accepts 64-bit float arrays as input and\n\
   64-bit integers as output.\n\
474 475
\n");

476 477
static PyObject* PyBobLearnLibsvmMachine_forward
(PyBobLearnLibsvmMachineObject* self, PyObject* args, PyObject* kwds) {
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498

  static const char* const_kwlist[] = {"input", "output", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);

  PyBlitzArrayObject* input = 0;
  PyBlitzArrayObject* output = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&", kwlist,
        &PyBlitzArray_Converter, &input,
        &PyBlitzArray_OutputConverter, &output
        )) return 0;

  //protects acquired resources through this scope
  auto input_ = make_safe(input);
  auto output_ = make_xsafe(output);

  if (input->type_num != NPY_FLOAT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `input'", Py_TYPE(self)->tp_name);
    return 0;
  }

499 500
  if (output && output->type_num != NPY_INT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit integer arrays for output array `output'", Py_TYPE(self)->tp_name);
501 502 503 504 505 506 507 508
    return 0;
  }

  if (input->ndim < 1 || input->ndim > 2) {
    PyErr_Format(PyExc_TypeError, "`%s' only accepts 1 or 2-dimensional arrays (not %" PY_FORMAT_SIZE_T "dD arrays)", Py_TYPE(self)->tp_name, input->ndim);
    return 0;
  }

509 510
  if (output && output->ndim != 1) {
    PyErr_Format(PyExc_RuntimeError, "Output arrays should always be 1D but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions", output->ndim);
511 512 513 514 515 516 517 518
    return 0;
  }

  if (input->ndim == 1) {
    if (input->shape[0] != (Py_ssize_t)self->cxx->inputSize()) {
      PyErr_Format(PyExc_RuntimeError, "1D `input' array should have %" PY_FORMAT_SIZE_T "d elements matching `%s' input size, not %" PY_FORMAT_SIZE_T "d elements", self->cxx->inputSize(), Py_TYPE(self)->tp_name, input->shape[0]);
      return 0;
    }
519 520
    if (output && output->shape[0] != 1) {
      PyErr_Format(PyExc_RuntimeError, "1D `output' array should have 1 element, not %" PY_FORMAT_SIZE_T "d elements", output->shape[0]);
521 522 523 524 525
      return 0;
    }
  }
  else {
    if (input->shape[1] != (Py_ssize_t)self->cxx->inputSize()) {
526
      PyErr_Format(PyExc_RuntimeError, "2D `input' array should have %" PY_FORMAT_SIZE_T "d columns, matching `%s' input size, not %" PY_FORMAT_SIZE_T "d", self->cxx->inputSize(), Py_TYPE(self)->tp_name, input->shape[1]);
527 528 529
      return 0;
    }
    if (output && input->shape[0] != output->shape[0]) {
530
      PyErr_Format(PyExc_RuntimeError, "1D `output' array should have %" PY_FORMAT_SIZE_T "d elements matching the number of rows on `input', not %" PY_FORMAT_SIZE_T "d rows", input->shape[0], output->shape[0]);
531 532 533 534 535 536
      return 0;
    }
  }

  /** if ``output`` was not pre-allocated, do it now **/
  if (!output) {
537 538 539
    Py_ssize_t osize = 1;
    if (input->ndim == 2) osize = input->shape[0];
    output = (PyBlitzArrayObject*)PyBlitzArray_SimpleNew(NPY_INT64, 1, &osize);
540 541 542 543 544 545
    output_ = make_safe(output);
  }

  /** all basic checks are done, can call the machine now **/
  try {
    if (input->ndim == 1) {
546 547
      auto bzout = PyBlitzArrayCxx_AsBlitz<int64_t,1>(output);
      (*bzout)(0) = self->cxx->predictClass_(*PyBlitzArrayCxx_AsBlitz<double,1>(input));
548 549 550
    }
    else {
      auto bzin = PyBlitzArrayCxx_AsBlitz<double,2>(input);
551
      auto bzout = PyBlitzArrayCxx_AsBlitz<int64_t,1>(output);
552 553 554
      blitz::Range all = blitz::Range::all();
      for (int k=0; k<bzin->extent(0); ++k) {
        blitz::Array<double,1> i_ = (*bzin)(k, all);
555
        (*bzout)(k) = self->cxx->predictClass_(i_); ///< no need to re-check
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
      }
    }
  }
  catch (std::exception& e) {
    PyErr_SetString(PyExc_RuntimeError, e.what());
    return 0;
  }
  catch (...) {
    PyErr_Format(PyExc_RuntimeError, "%s cannot forward data: unknown exception caught", Py_TYPE(self)->tp_name);
    return 0;
  }

  Py_INCREF(output);
  return PyBlitzArray_NUMPY_WRAP(reinterpret_cast<PyObject*>(output));

}

573 574 575 576
PyDoc_STRVAR(s_scores_str, "predict_class_and_scores");
PyDoc_STRVAR(s_scores_doc,
"o.predict_class_and_scores(input, [cls, [score]]) -> (array, array)\n\
\n\
577 578 579
Calculates the **predicted class** and output scores for the SVM\n\
using the this Machine, given one single feature vector or multiple\n\
ones.\n\
580 581 582 583 584 585
\n\
The ``input`` array can be either 1D or 2D 64-bit float arrays.\n\
The ``cls`` array, if provided, must be of type ``int64``,\n\
always uni-dimensional. The ``cls`` output corresponds to the\n\
predicted classes for each of the input rows. The ``score`` array,\n\
if provided, must be of type ``float64`` (like ``input``) and have\n\
586 587 588 589 590 591 592 593 594
as many rows as ``input`` and ``C`` columns, matching the \n\
number of combinations of the outputs 2-by-2. To score, LIBSVM\n\
will compare the SV outputs for each set two classes in the machine\n\
and output 1 score. If there is only 1 output, then the problem is\n\
binary and only 1 score is produced (``C = 1``). If the SVM is\n\
multi-class, then the number of combinations ``C`` is the total\n\
amount of output combinations which is possible. If ``N`` is\n\
the number of classes in this SVM, then :math:`C = N\\cdot(N-1)/2`.\n\
If ``N = 3``, then ``C = 3``. If ``N = 5``, then ``C = 10``.\n\
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
\n\
This method always returns a tuple composed of the predicted classes\n\
for each row in the ``input`` array, with data type ``int64`` and\n\
of scores for each output of the SVM in a 1D or 2D ``float64`` array.\n\
If you don't provide the arrays upon calling this method, we will\n\
allocate new ones internally and return them. If you are calling\n\
this method on a tight loop, it is recommended you pass the ``cls``\n\
and ``score`` arrays to avoid constant re-allocation.\n\
");

static PyObject* PyBobLearnLibsvmMachine_predictClassAndScores
(PyBobLearnLibsvmMachineObject* self, PyObject* args, PyObject* kwds) {

  static const char* const_kwlist[] = {"input", "cls", "score", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);

  PyBlitzArrayObject* input = 0;
  PyBlitzArrayObject* cls = 0;
  PyBlitzArrayObject* score = 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&O&", kwlist,
        &PyBlitzArray_Converter, &input,
        &PyBlitzArray_OutputConverter, &cls,
        &PyBlitzArray_OutputConverter, &score
        )) return 0;

  //protects acquired resources through this scope
  auto input_ = make_safe(input);
  auto cls_ = make_xsafe(cls);
  auto score_ = make_xsafe(score);

626 627 628 629 630
  //calculates the number of scores expected: combinatorics between
  //all class outputs
  Py_ssize_t N = self->cxx->outputSize();
  Py_ssize_t number_of_scores = N < 2 ? 1 : (N*(N-1))/2;

631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
  if (input->type_num != NPY_FLOAT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `input'", Py_TYPE(self)->tp_name);
    return 0;
  }

  if (cls && cls->type_num != NPY_INT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit integer arrays for output array `cls'", Py_TYPE(self)->tp_name);
    return 0;
  }

  if (score && score->type_num != NPY_FLOAT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for score array `score'", Py_TYPE(self)->tp_name);
    return 0;
  }

  if (input->ndim < 1 || input->ndim > 2) {
    PyErr_Format(PyExc_TypeError, "`%s' only accepts 1 or 2-dimensional arrays (not %" PY_FORMAT_SIZE_T "dD arrays)", Py_TYPE(self)->tp_name, input->ndim);
    return 0;
  }

  if (cls && cls->ndim != 1) {
    PyErr_Format(PyExc_RuntimeError, "the `cls' array should always be 1D but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions", cls->ndim);
    return 0;
  }

  if (score && input->ndim != score->ndim) {
    PyErr_Format(PyExc_RuntimeError, "Input and score arrays should have matching number of dimensions, but input array `input' has %" PY_FORMAT_SIZE_T "d dimensions while output array `score' has %" PY_FORMAT_SIZE_T "d dimensions", input->ndim, score->ndim);
    return 0;
  }

  if (input->ndim == 1) {
    if (input->shape[0] != (Py_ssize_t)self->cxx->inputSize()) {
      PyErr_Format(PyExc_RuntimeError, "1D `input' array should have %" PY_FORMAT_SIZE_T "d elements matching `%s' input size, not %" PY_FORMAT_SIZE_T "d elements", self->cxx->inputSize(), Py_TYPE(self)->tp_name, input->shape[0]);
      return 0;
    }
    if (cls && cls->shape[0] != 1) {
      PyErr_Format(PyExc_RuntimeError, "1D `cls' array should have 1 element, not %" PY_FORMAT_SIZE_T "d elements", cls->shape[0]);
      return 0;
    }
670 671
    if (score && score->shape[0] != number_of_scores) {
      PyErr_Format(PyExc_RuntimeError, "1D `score' array should have %" PY_FORMAT_SIZE_T "d elements matching the expected number of scores for `%s', not %" PY_FORMAT_SIZE_T "d elements", number_of_scores, Py_TYPE(self)->tp_name, score->shape[0]);
672 673 674 675 676 677 678 679 680 681 682 683
      return 0;
    }
  }
  else {
    if (input->shape[1] != (Py_ssize_t)self->cxx->inputSize()) {
      PyErr_Format(PyExc_RuntimeError, "2D `input' array should have %" PY_FORMAT_SIZE_T "d columns, matching `%s' input size, not %" PY_FORMAT_SIZE_T "d elements", self->cxx->inputSize(), Py_TYPE(self)->tp_name, input->shape[1]);
      return 0;
    }
    if (cls && input->shape[0] != cls->shape[0]) {
      PyErr_Format(PyExc_RuntimeError, "1D `cls' array should have %" PY_FORMAT_SIZE_T "d elements matching the number of rows on `input', not %" PY_FORMAT_SIZE_T "d rows", input->shape[0], cls->shape[0]);
      return 0;
    }
684 685
    if (score && score->shape[1] != number_of_scores) {
      PyErr_Format(PyExc_RuntimeError, "2D `score' array should have %" PY_FORMAT_SIZE_T "d columns matching the output size of `%s', not %" PY_FORMAT_SIZE_T "d elements", number_of_scores, Py_TYPE(self)->tp_name, score->shape[1]);
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
      return 0;
    }
    if (score && input->shape[0] != score->shape[0]) {
      PyErr_Format(PyExc_RuntimeError, "2D `score' array should have %" PY_FORMAT_SIZE_T "d rows matching `input' size, not %" PY_FORMAT_SIZE_T "d rows", input->shape[0], score->shape[0]);
      return 0;
    }
  }

  /** if ``cls`` was not pre-allocated, do it now **/
  if (!cls) {
    Py_ssize_t osize = 1;
    if (input->ndim == 2) osize = input->shape[0];
    cls = (PyBlitzArrayObject*)PyBlitzArray_SimpleNew(NPY_INT64, 1, &osize);
    cls_ = make_safe(cls);
  }

  /** if ``score`` was not pre-allocated, do it now **/
  if (!score) {
    Py_ssize_t osize[2];
    if (input->ndim == 1) {
706
      osize[0] = number_of_scores;
707 708 709
    }
    else {
      osize[0] = input->shape[0];
710
      osize[1] = number_of_scores;
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
    }
    score = (PyBlitzArrayObject*)PyBlitzArray_SimpleNew(NPY_FLOAT64, input->ndim, osize);
    score_ = make_safe(score);
  }

  /** all basic checks are done, can call the machine now **/
  try {
    if (input->ndim == 1) {
      auto bzin = PyBlitzArrayCxx_AsBlitz<double,1>(input);
      auto bzcls = PyBlitzArrayCxx_AsBlitz<int64_t,1>(cls);
      auto bzscore = PyBlitzArrayCxx_AsBlitz<double,1>(score);
      (*bzcls)(0) = self->cxx->predictClassAndScores_(*bzin, *bzscore);
    }
    else {
      auto bzin = PyBlitzArrayCxx_AsBlitz<double,2>(input);
      auto bzcls = PyBlitzArrayCxx_AsBlitz<int64_t,1>(cls);
      auto bzscore = PyBlitzArrayCxx_AsBlitz<double,2>(score);
      blitz::Range all = blitz::Range::all();
      for (int k=0; k<bzin->extent(0); ++k) {
        blitz::Array<double,1> i_ = (*bzin)(k, all);
        blitz::Array<double,1> s_ = (*bzscore)(k, all);
        (*bzcls)(k) = self->cxx->predictClassAndScores_(i_, s_);
      }
    }
  }
  catch (std::exception& e) {
    PyErr_SetString(PyExc_RuntimeError, e.what());
    return 0;
  }
  catch (...) {
    PyErr_Format(PyExc_RuntimeError, "%s cannot forward data: unknown exception caught", Py_TYPE(self)->tp_name);
    return 0;
  }

  Py_INCREF(cls);
  Py_INCREF(score);
  return Py_BuildValue("OO",
      PyBlitzArray_NUMPY_WRAP(reinterpret_cast<PyObject*>(cls)),
      PyBlitzArray_NUMPY_WRAP(reinterpret_cast<PyObject*>(score))
      );

}

PyDoc_STRVAR(s_probabilities_str, "predict_class_and_probabilities");
PyDoc_STRVAR(s_probabilities_doc,
"o.predict_class_and_probabilities(input, [cls, [prob]]) -> (array, array)\n\
\n\
Calculates the **predicted class** and output probabilities for the\n\
SVM using the this Machine, given one single feature vector or\n\
multiple ones.\n\
\n\
The ``input`` array can be either 1D or 2D 64-bit float arrays.\n\
The ``cls`` array, if provided, must be of type ``int64``,\n\
always uni-dimensional. The ``cls`` output corresponds to the\n\
predicted classes for each of the input rows. The ``prob`` array,\n\
if provided, must be of type ``float64`` (like ``input``) and have\n\
767 768
as many rows as ``input`` and ``len(o.labels)`` columns, matching\n\
the number of classes for this SVM.\n\
769 770 771 772 773 774 775 776 777 778 779 780 781
\n\
This method always returns a tuple composed of the predicted classes\n\
for each row in the ``input`` array, with data type ``int64`` and\n\
of probabilities for each output of the SVM in a 1D or 2D ``float64``\n\
array. If you don't provide the arrays upon calling this method, we\n\
will allocate new ones internally and return them. If you are calling\n\
this method on a tight loop, it is recommended you pass the ``cls``\n\
and ``prob`` arrays to avoid constant re-allocation.\n\
");

static PyObject* PyBobLearnLibsvmMachine_predictClassAndProbabilities
(PyBobLearnLibsvmMachineObject* self, PyObject* args, PyObject* kwds) {

782 783 784 785 786
  if (!self->cxx->supportsProbability()) {
    PyErr_Format(PyExc_RuntimeError, "`%s' object does not support probabilities - in the future, use `o.probability' to query for this property", Py_TYPE(self)->tp_name);
    return 0;
  }

787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
  static const char* const_kwlist[] = {"input", "cls", "prob", 0};
  static char** kwlist = const_cast<char**>(const_kwlist);

  PyBlitzArrayObject* input = 0;
  PyBlitzArrayObject* cls = 0;
  PyBlitzArrayObject* prob= 0;

  if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&O&", kwlist,
        &PyBlitzArray_Converter, &input,
        &PyBlitzArray_OutputConverter, &cls,
        &PyBlitzArray_OutputConverter, &prob
        )) return 0;

  //protects acquired resources through this scope
  auto input_ = make_safe(input);
  auto cls_ = make_xsafe(cls);
  auto prob_ = make_xsafe(prob);

  if (input->type_num != NPY_FLOAT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for input array `input'", Py_TYPE(self)->tp_name);
    return 0;
  }

  if (cls && cls->type_num != NPY_INT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit integer arrays for output array `cls'", Py_TYPE(self)->tp_name);
    return 0;
  }

  if (prob && prob->type_num != NPY_FLOAT64) {
    PyErr_Format(PyExc_TypeError, "`%s' only supports 64-bit float arrays for probability array `prob'", Py_TYPE(self)->tp_name);
    return 0;
  }

  if (input->ndim < 1 || input->ndim > 2) {
    PyErr_Format(PyExc_TypeError, "`%s' only accepts 1 or 2-dimensional arrays (not %" PY_FORMAT_SIZE_T "dD arrays)", Py_TYPE(self)->tp_name, input->ndim);
    return 0;
  }

  if (cls && cls->ndim != 1) {
    PyErr_Format(PyExc_RuntimeError, "the `cls' array should always be 1D but you provided an object with %" PY_FORMAT_SIZE_T "d dimensions", cls->ndim);
    return 0;
  }

  if (prob && input->ndim != prob->ndim) {
    PyErr_Format(PyExc_RuntimeError, "Input and probability arrays should have matching number of dimensions, but input array `input' has %" PY_FORMAT_SIZE_T "d dimensions while output array `prob' has %" PY_FORMAT_SIZE_T "d dimensions", input->ndim, prob->ndim);
    return 0;
  }

  if (input->ndim == 1) {
    if (input->shape[0] != (Py_ssize_t)self->cxx->inputSize()) {
      PyErr_Format(PyExc_RuntimeError, "1D `input' array should have %" PY_FORMAT_SIZE_T "d elements matching `%s' input size, not %" PY_FORMAT_SIZE_T "d elements", self->cxx->inputSize(), Py_TYPE(self)->tp_name, input->shape[0]);
      return 0;
    }
    if (cls && cls->shape[0] != 1) {
      PyErr_Format(PyExc_RuntimeError, "1D `cls' array should have 1 element, not %" PY_FORMAT_SIZE_T "d elements", cls->shape[0]);
      return 0;
    }
844 845
    if (prob && prob->shape[0] != (Py_ssize_t)self->cxx->numberOfClasses()) {
      PyErr_Format(PyExc_RuntimeError, "1D `prob' array should have %" PY_FORMAT_SIZE_T "d elements matching the number of classes of `%s', not %" PY_FORMAT_SIZE_T "d elements", self->cxx->numberOfClasses(), Py_TYPE(self)->tp_name, prob->shape[0]);
846 847 848 849 850 851 852 853 854 855 856 857
      return 0;
    }
  }
  else {
    if (input->shape[1] != (Py_ssize_t)self->cxx->inputSize()) {
      PyErr_Format(PyExc_RuntimeError, "2D `input' array should have %" PY_FORMAT_SIZE_T "d columns, matching `%s' input size, not %" PY_FORMAT_SIZE_T "d elements", self->cxx->inputSize(), Py_TYPE(self)->tp_name, input->shape[1]);
      return 0;
    }
    if (cls && input->shape[0] != cls->shape[0]) {
      PyErr_Format(PyExc_RuntimeError, "1D `cls' array should have %" PY_FORMAT_SIZE_T "d elements matching the number of rows on `input', not %" PY_FORMAT_SIZE_T "d rows", input->shape[0], cls->shape[0]);
      return 0;
    }
858 859
    if (prob && prob->shape[1] != (Py_ssize_t)self->cxx->numberOfClasses()) {
      PyErr_Format(PyExc_RuntimeError, "2D `prob' array should have %" PY_FORMAT_SIZE_T "d columns matching the number of classes of `%s', not %" PY_FORMAT_SIZE_T "d elements", self->cxx->numberOfClasses(), Py_TYPE(self)->tp_name, prob->shape[1]);
860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
      return 0;
    }
    if (prob && input->shape[0] != prob->shape[0]) {
      PyErr_Format(PyExc_RuntimeError, "2D `prob' array should have %" PY_FORMAT_SIZE_T "d rows matching `input' size, not %" PY_FORMAT_SIZE_T "d rows", input->shape[0], prob->shape[0]);
      return 0;
    }
  }

  /** if ``cls`` was not pre-allocated, do it now **/
  if (!cls) {
    Py_ssize_t osize = 1;
    if (input->ndim == 2) osize = input->shape[0];
    cls = (PyBlitzArrayObject*)PyBlitzArray_SimpleNew(NPY_INT64, 1, &osize);
    cls_ = make_safe(cls);
  }

  /** if ``prob`` was not pre-allocated, do it now **/
  if (!prob) {
    Py_ssize_t osize[2];
    if (input->ndim == 1) {
880
      osize[0] = self->cxx->numberOfClasses();
881 882 883
    }
    else {
      osize[0] = input->shape[0];
884
      osize[1] = self->cxx->numberOfClasses();
885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927
    }
    prob = (PyBlitzArrayObject*)PyBlitzArray_SimpleNew(NPY_FLOAT64, input->ndim, osize);
    prob_ = make_safe(prob);
  }

  /** all basic checks are done, can call the machine now **/
  try {
    if (input->ndim == 1) {
      auto bzin = PyBlitzArrayCxx_AsBlitz<double,1>(input);
      auto bzcls = PyBlitzArrayCxx_AsBlitz<int64_t,1>(cls);
      auto bzprob = PyBlitzArrayCxx_AsBlitz<double,1>(prob);
      (*bzcls)(0) = self->cxx->predictClassAndProbabilities_(*bzin, *bzprob);
    }
    else {
      auto bzin = PyBlitzArrayCxx_AsBlitz<double,2>(input);
      auto bzcls = PyBlitzArrayCxx_AsBlitz<int64_t,1>(cls);
      auto bzprob = PyBlitzArrayCxx_AsBlitz<double,2>(prob);
      blitz::Range all = blitz::Range::all();
      for (int k=0; k<bzin->extent(0); ++k) {
        blitz::Array<double,1> i_ = (*bzin)(k, all);
        blitz::Array<double,1> p_ = (*bzprob)(k, all);
        (*bzcls)(k) = self->cxx->predictClassAndProbabilities_(i_, p_);
      }
    }
  }
  catch (std::exception& e) {
    PyErr_SetString(PyExc_RuntimeError, e.what());
    return 0;
  }
  catch (...) {
    PyErr_Format(PyExc_RuntimeError, "%s cannot forward data: unknown exception caught", Py_TYPE(self)->tp_name);
    return 0;
  }

  Py_INCREF(cls);
  Py_INCREF(prob);
  return Py_BuildValue("OO",
      PyBlitzArray_NUMPY_WRAP(reinterpret_cast<PyObject*>(cls)),
      PyBlitzArray_NUMPY_WRAP(reinterpret_cast<PyObject*>(prob))
      );

}

928 929
PyDoc_STRVAR(s_save_str, "save");
PyDoc_STRVAR(s_save_doc,
930
"o.save(path) -> None\n\
931
\n\
932
o.save(hdf5file) -> None\n\
933
\n\
934
Saves itself at a LIBSVM model file or into a\n\
André Anjos's avatar
André Anjos committed
935 936
:py:class:`bob.io.HDF5File`. Saving the SVM into an\n\
:py:class:`bob.io.HDF5File` object, has the advantage of saving\n\
937 938 939
input normalization options together with the machine, which are\n\
automatically reloaded when you re-initialize it from the same\n\
:py:class:`HDF5File`.\n\
940 941
");

942 943
static PyObject* PyBobLearnLibsvmMachine_Save
(PyBobLearnLibsvmMachineObject* self, PyObject* f) {
944

945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
  if (PyBobIoHDF5File_Check(f)) {
    auto h5f = reinterpret_cast<PyBobIoHDF5FileObject*>(f);
    try {
      self->cxx->save(*h5f->f);
    }
    catch (std::exception& e) {
      PyErr_SetString(PyExc_RuntimeError, e.what());
      return 0;
    }
    catch (...) {
      PyErr_Format(PyExc_RuntimeError, "`%s' cannot write data to file `%s' (at group `%s'): unknown exception caught", Py_TYPE(self)->tp_name,
          h5f->f->filename().c_str(), h5f->f->cwd().c_str());
      return 0;
    }
    Py_RETURN_NONE;
960 961
  }

962 963 964 965 966
  // try a filename conversion and use libsvm's original file format
  PyObject* filename = 0;
  int ok = PyBobIo_FilenameConverter(f, &filename);
  if (!ok) {
    PyErr_Format(PyExc_TypeError, "cannot convert `%s' into a valid string for a file path - objects of type `%s' can only save to HDF5 files or text files using LIBSVM's original file format (pass a string referring to a valid filesystem path in this case)", Py_TYPE(f)->tp_name, Py_TYPE(self)->tp_name);
967 968 969
    return 0;
  }

970 971
  // at this point we know we have a valid file system string
  auto filename_ = make_safe(filename);
972

973 974 975 976 977
#if PY_VERSION_HEX >= 0x03000000
  const char* c_filename = PyBytes_AS_STRING(filename);
#else
  const char* c_filename = PyString_AS_STRING(filename);
#endif
978 979

  try {
980
    self->cxx->save(c_filename);
981 982 983 984 985 986
  }
  catch (std::exception& ex) {
    PyErr_SetString(PyExc_RuntimeError, ex.what());
    return 0;
  }
  catch (...) {
987
    PyErr_Format(PyExc_RuntimeError, "`%s' cannot write data to file `%s' (using LIBSVM's original text format): unknown exception caught", Py_TYPE(self)->tp_name, c_filename);
988 989 990 991 992 993 994
    return 0;
  }

  Py_RETURN_NONE;

}

995 996 997
PyDoc_STRVAR(s_predict_class_str, "predict_class");

static PyMethodDef PyBobLearnLibsvmMachine_methods[] = {
998 999
  {
    s_forward_str,
1000
    (PyCFunction)PyBobLearnLibsvmMachine_forward,
1001 1002 1003 1004
    METH_VARARGS|METH_KEYWORDS,
    s_forward_doc
  },
  {
1005 1006 1007 1008
    s_predict_class_str,
    (PyCFunction)PyBobLearnLibsvmMachine_forward,
    METH_VARARGS|METH_KEYWORDS,
    s_forward_doc
1009
  },
1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
  {
    s_scores_str,
    (PyCFunction)PyBobLearnLibsvmMachine_predictClassAndScores,
    METH_VARARGS|METH_KEYWORDS,
    s_scores_doc,
  },
  {
    s_probabilities_str,
    (PyCFunction)PyBobLearnLibsvmMachine_predictClassAndProbabilities,
    METH_VARARGS|METH_KEYWORDS,
    s_probabilities_doc,
  },
1022 1023
  {
    s_save_str,
1024
    (PyCFunction)PyBobLearnLibsvmMachine_Save,
1025 1026 1027 1028 1029 1030
    METH_O,
    s_save_doc
  },
  {0} /* Sentinel */
};

1031
static PyObject* PyBobLearnLibsvmMachine_new
1032 1033 1034
(PyTypeObject* type, PyObject*, PyObject*) {

  /* Allocates the python object itself */
1035 1036
  PyBobLearnLibsvmMachineObject* self =
    (PyBobLearnLibsvmMachineObject*)type->tp_alloc(type, 0);
1037 1038 1039 1040 1041 1042 1043

  self->cxx = 0;

  return reinterpret_cast<PyObject*>(self);

}

1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
PyObject* PyBobLearnLibsvmMachine_NewFromMachine
(bob::learn::libsvm::Machine* m) {

  PyBobLearnLibsvmMachineObject* retval = (PyBobLearnLibsvmMachineObject*)PyBobLearnLibsvmMachine_new(&PyBobLearnLibsvmMachine_Type, 0, 0);

  retval->cxx = m; ///< takes ownership

  return reinterpret_cast<PyObject*>(retval);

}

1055
PyTypeObject PyBobLearnLibsvmMachine_Type = {
1056
    PyVarObject_HEAD_INIT(0, 0)
1057 1058
    s_svm_str,                                        /* tp_name */
    sizeof(PyBobLearnLibsvmMachineObject),            /* tp_basicsize */
1059
    0,                                                /* tp_itemsize */
1060
    (destructor)PyBobLearnLibsvmMachine_delete,       /* tp_dealloc */
1061 1062 1063 1064
    0,                                                /* tp_print */
    0,                                                /* tp_getattr */
    0,                                                /* tp_setattr */
    0,                                                /* tp_compare */
1065
    (reprfunc)PyBobLearnLibsvmMachine_Repr,           /* tp_repr */
1066 1067 1068 1069
    0,                                                /* tp_as_number */
    0,                                                /* tp_as_sequence */
    0,                                                /* tp_as_mapping */
    0,                                                /* tp_hash */
1070 1071
    (ternaryfunc)PyBobLearnLibsvmMachine_forward,     /* tp_call */
    (reprfunc)PyBobLearnLibsvmMachine_Str,            /* tp_str */
1072 1073 1074 1075
    0,                                                /* tp_getattro */
    0,                                                /* tp_setattro */
    0,                                                /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,         /* tp_flags */
1076
    s_svm_doc,                                        /* tp_doc */
1077 1078
    0,                                                /* tp_traverse */
    0,                                                /* tp_clear */
1079
    0,                                                /* tp_richcompare */
1080 1081 1082
    0,                                                /* tp_weaklistoffset */
    0,                                                /* tp_iter */
    0,                                                /* tp_iternext */
1083
    PyBobLearnLibsvmMachine_methods,                  /* tp_methods */
1084
    0,                                                /* tp_members */
1085
    PyBobLearnLibsvmMachine_getseters,                /* tp_getset */
1086 1087 1088 1089 1090
    0,                                                /* tp_base */
    0,                                                /* tp_dict */
    0,                                                /* tp_descr_get */
    0,                                                /* tp_descr_set */
    0,                                                /* tp_dictoffset */
1091
    (initproc)PyBobLearnLibsvmMachine_init,           /* tp_init */
1092
    0,                                                /* tp_alloc */
1093
    PyBobLearnLibsvmMachine_new,                      /* tp_new */
1094
};