perClass Documentation
version 5.4 (7-Dec-2018)

Chapter 20: Classifier deployment using perClass runtime library

Table of contents

20.1. Introduction ↩

perClass provides tools for easy deployment of pattern recognition algorithms in products. The corner-stone of the deployment framework is the pipeline object, discussed in Chapter: Pipelines. Pipelines always execute through the perClass Runtime library writen in C. Under Matlab, the execution is routed through the MEX interface. In order to execute the pipeline in a custom application outside Matlab, we need to export it using the sdexport function.

Let us illustrate classifier deployment on a simple example. We build a simple Gaussian classifier:

>> load fruit
>> a
'Fruit set' 260 by 2 sddata, 3 classes: 'apple'(100) 'banana'(100) 'stone'(60) 
>> p=sdgauss(a)
sequential pipeline       2x1 'Gaussian model+Decision'
 1 Gaussian model          2x3  full cov.mat.
 2 Decision                3x1  weighting, 3 classes

We may execute the classifier on a new sample:

>> sddata([1 2])*p
sdlab with one entry: 'apple'

Now we export the pipeline p to the external pipeline file myclassifier.ppl:

>> sdexport(p,'myclassifier.ppl')
Exporting pipeline..ok
This pipeline requires perClass runtime version 4.0 (11-jul-2013) or higher.

Classifier may be now directly executed outside Matlab using the command-line sdrun utility (in interfaces/sdrun directory):

> sdrun.exe myclassifier.ppl -d " 1 2 "
apple

We have provided sdrun command with the pipeline file and data. It outputs the classifier decision. sdrun is the simplest way to execute the classifiers outside Matlab.

In addition to sdrun utility, perClass offers tools for easy embeddedding of classifiers in C/C++ programs or their execution from any environment capable of calling a DLL.

20.2. Execution of classifiers with command-line sdrun utility ↩

The sdrun utility allows execution of perClass classifiers from operating system command line (Windows key+R to open the Run dialog and enter cmd). It is available for each supported platform under interfaces\sdrun directory. Because it's statically linked with the perClass runtime, sdrun utility does not have any external dependencies. It requires only a license file and a pipeline file. The license file is assumed to be located in the same directory as the sdrun executable.

20.2.1. Displaying pipeline information ↩

When providing sdrun only with a pipeline file, it displays the basic pipeline info:

> ./sdrun.exe myclassifier.ppl 
perClass Pro 3.0.0 (01-Jun-2011), Copyright (C) 2007-2011, PR Sys Design, All rights reserved
Commercial license. This license will expire on 1-aug-2011 (PR Sys Design)

pipeline name: 'Gaussian model+Decision'
input type: double, dimensionality: 2
output type: int, dimensionality: 1, decisions
possible decisions: apple,banana,stone

sdrun lists the pipeline name, input and output dimensionality and data type and the type of output (soft outputs or decisions). For the decision-returning pipelines, it also provides the list of possible decisions.

The pipeline name may be set by the user in Matlab using the setname command:

>> pd2=setname(pd,'myclassifier')
sequential pipeline     2x1 'myclassifier'
 1  Gaussian model          2x3  3 classes, 3 components (sdp_normal)
 2  Decision                3x1  weighting, 3 classes, 1 ops at op 1 (sdp_decide)

>> sdexport(pd2,'../src/perclass/myclassifier.ppl')
Exporting pipeline for deployment using perClass runtime

Note, that in the final product using perClass DLL, the pipelines do not need to be stored as separate files visible to the end-user. Instead, they may be stored in an internal application resource or buffer. Example of loading pipelines from a buffer using C API is given in ex_buffer.c file in SDK directory.

20.2.2. Executing classifier on a data file ↩

sdrun utility may execute the pipeline on a set of observations stored in a comma-separated text file. This option is useful for a quick batch processing. The data file should store individual samples (feature vectors) as comma-separated lists, one row per sample.

> sdrun.exe myclassifier.ppl data.txt 
apple
banana
banana
apple
apple
...

20.2.3. Executing classifier on samples provided in a string ↩

sdrun utility can be executed on one or few data samples provided directly on the command-line input. Using the -d option, we may specify the string with space-separated feature values. Multiple feature vectors may be separated by semicolons.

> sdrun.exe myclassifier.ppl -d "1 2; -4 10; 0 4.55"
apple
stone
stone

20.2.4. Displaying license info ↩

sdrun utility can display license information with -l option:

> sdrun.exe -l
machine: hostid="00a3ffa0" ip=192.168.2.124
license: product="runtime.demo" present=1 exp=30-sep-2017 exp_days=143 issued=9-may-2017 hostid="ANY"
license: product="runtime" present=1 exp=11-may-2017 exp_days=1 issued=8-may-2017 options="CP,imaging" customer="" contract="" hostid="00a3ffa0"

The machine line lists hostid and IP address of current machine. The license line(s) list runtime licenses present with details such as issue-date, options or days to expiration.

20.3. Classifier execution in Microsoft Excel worksheets ↩

perClass classifiers may be executed directly in MS Excel worksheet. The example for Excel 2010 or higher is provided in the interfaces\Excel\perclass_excel_example directory.

Note: It is important to open the Excel example worksheet perClass_Excel.xlsm from Excel File/Open menu, not by double-clicking on the file icon. The reason is that only then Excel correctly assigns the "default file location" and will find the perclass.dll runtime library.

When we open the example worksheet, we can see the three parameters needed for classifier execution on the left and the green execution button on the right.

Classifier execution in Excel

We will first need to specify the path to the example directory in the B1 cell. This helps Excel to locate the perClass.dll library. Second, we need to provide the path to the pipeline file in B2. We may use our own classifier exported using sdexport command or try one of the three pipelines included with the example (fisher.ppl, parzen.ppl and parzen_dec.ppl).

Now, we may prepare the input data. We fill the data matrix directly in the worksheet. We must only specify the range of input data in the cell B3.

Classifier execution in Excel

By clicking on "Execute classifier" button the pipeline outputs are written to the right of the input data:

Classifier decisions in Excel

20.3.1. Execution on data stored in a different workbook ↩

Input data may reside in any other sheet or even in an entirely different workbook. We only need to provide the correct reference in the B3 cell of the perClass_example.xlsm file. This allows us to execute classifiers directly in our spreadsheets, without including any specific code or libraries.

Classifier execution in other Excel workbook

20.4. Executing classifiers from LabView ↩

perClass classifiers may be executed from LabView environment using the example interface perClass_example1.vi in interfaces\LabView directory.

Classifier execution in LabView

20.5. Executing classifiers from Matlab/Matlab compiler ↩

perClass 3.0 introduces new deployment tool for execution of classifiers from Matlab or Matlab compiler. The MEX library sdrun uses perClass deployment license instead of the perClass toolbox license. Therefore, you may use it to distribute classifiers to third parties.

The sdrun MEX is fully self-contained. To embed classifier execution in your Matlab/Matlab compiler application, we only need to provide the sdrun binary, license file and a classifier pipeline.

To use sdrun MEX in Matlab, simply add the interfaces/MatlabCompiler/PLATFORM directory on your Matlab path:

>> addpath /home/pavel/perClass_Demo/interfaces/MatlabCompiler/mac64/

Typing sdrun, we will receive basic information and usage example:

>> sdrun
perClass Demo 3.0.0 (18-Jun-2011), Copyright (C) 2007-2011, PR Sys Design, All rights reserved
Demo license. Only for evaluation purposes. This license will expire on 03-aug-2011 ()

sdrun mex allows execution of classifiers trained in perClass Toolbox
in deployment mode (e.g. in custom applications made with Matlab compiler).

Usage: pind=sdrun('pipeline.ppl')   Load pipeline from file
   out=sdrun(pind,data)         Execute pipeline pind on the data

No pipelines loaded

20.5.1. Loading a classifier pipeline ↩

To load a classifier pipeline file, such as the myclassifier.ppl created above, we provide the file name to the sdrun MEX:

>> i=sdrun('myclassifier.ppl')
Pipeline 1 loaded: 'myclassifier.ppl' ('Gaussian model+Decision')

i =

       1

The sdrun loads the pipeline and returns the pipeline index.

Typing sdrun again, we will see the list of loaded pipelines:

>> sdrun
perClass Demo 3.0.0 (18-Jun-2011), Copyright (C) 2007-2011, PR Sys Design, All rights reserved
Demo license. Only for evaluation purposes. This license will expire on 03-aug-2011 ()

sdrun mex allows execution of classifiers trained in perClass Toolbox
in deployment mode (e.g. in custom applications made with Matlab compiler).

Usage: pind=sdrun('pipeline.ppl')   Load pipeline from file
   out=sdrun(pind,data)         Execute pipeline pind on the data

One pipeline loaded: 
pind  name
 1 : 'Gaussian model+Decision'  input dim: 2, output dim: 1 (decisions: apple,banana,stone)

20.5.2. Executing a classifier on new data ↩

To execute the classifier on new data, simply provide pipeline index and data matrix. The sdrun MEX will return decisions as numerical indices.

>> out=sdrun(1,[0 0; 0 1; -10 10])

out =

       1
       1
       3

20.5.3. Working with multiple pipelines ↩

sdrun allows us to work with multiple pipelines. Here we load another pipeline:

>> sdexport(pd(1),'myclassifier2.ppl')
Exporting pipeline..ok
>> i=sdrun('myclassifier2.ppl')
Pipeline 2 loaded: 'myclassifier2.ppl' ('Gaussian model')

i =

       2

>> sdrun
perClass Demo 3.0.0 (18-Jun-2011), Copyright (C) 2007-2011, PR Sys Design, All rights reserved
Demo license. Only for evaluation purposes. This license will expire on 03-aug-2011 ()

sdrun mex allows execution of classifiers trained in perClass Toolbox
in deployment mode (e.g. in custom applications made with Matlab compiler).

Usage: pind=sdrun('pipeline.ppl')   Load pipeline from file
   out=sdrun(pind,data)         Execute pipeline pind on the data

2 pipelines loaded: 
pind  name
 1 : 'Gaussian model+Decision'  input dim: 2, output dim: 1 (decisions: apple,banana,stone)
 2 : 'Gaussian model'  input dim: 2, output dim: 3 (soft outputs)

Executing pipeline 2 on the same data returns soft outputs (probability densities):

>> out=sdrun(2,[0 0; 0 1; -10 10])

out =

0.0038    0.0012    0.0001
0.0026    0.0012    0.0003
0.0000    0.0000    0.0004

20.5.4. Removing pipelines from memory ↩

To remove pipelines from memory, use clear mex:

>> clear mex
>> sdrun
perClass Demo 3.0.0 (18-Jun-2011), Copyright (C) 2007-2011, PR Sys Design, All rights reserved
Demo license. Only for evaluation purposes. This license will expire on 03-aug-2011 ()

sdrun mex allows execution of classifiers trained in perClass Toolbox
in deployment mode (e.g. in custom applications made with Matlab compiler).

Usage: pind=sdrun('pipeline.ppl')   Load pipeline from file
   out=sdrun(pind,data)         Execute pipeline pind on the data

No pipelines loaded

20.6. Classifier embedding using C/C++ language API ↩

Complete functionality of perClass execution is available through the C/C++ interface in SDK directory. Runtime API changes slightly with 3.0 release, see this article for details.

Steps that need to be taken to execute a classifier from a custom application:

After that, the pipeline is ready for execution. To process more data, you can write them directly into the input buffer and execute the pipeline again.

20.6.1. Complete C application example ↩

This example (ex_basic.c) shows a complete application loading a classifier, processing some data and writing out the results. The example assumes that the input data are 2D feature vectors.

  1. /*  
  2.    ex_basic.c: Example of calling perClass runtime from C code and getting 
  3.    decisions for new data samples. 
  4.  
  5. */  
  6. #include <stdio.h>  
  7. #include <stdlib.h>  
  8. #include "perclass.h"  
  9.   
  10. #define SD_ABORT(pk)   \  
  11.   printf("%d: %s\n",sd_GetErrorCode(pk),sd_GetErrorMsg(pk));  \  
  12.   if( pk!=NULL ) sd_ReleaseKernel(pk);  \  
  13.   return(SD_ERROR);   
  14.   
  15. int main(void)  
  16. {  
  17.   prkernel* pk=NULL;  
  18.   int res,pind,sc,fc1,fc2,i;  
  19.   prbuf* pbin, *pbout;  
  20.   FILE* File;  
  21.   
  22.   /* initialize the PRSD library: pass NULL as we provide the license 
  23.      file in the same directory as the library binary 
  24.    */  
  25.   pk=sd_InitKernel(NULL);  
  26.   if( pk == NULL ) { SD_ABORT(pk); };  
  27.   
  28.   /* initially, the message buffer contais library version information */  
  29.   printf("%s\n",sd_GetErrorMsg(pk));  
  30.   
  31.   /* load the pipeline: returns the pipeline index */  
  32.   pind=sd_LoadPipeline(pk,"fisher_dec.ppl");  
  33.   if( pind==SD_ERROR ) { SD_ABORT(pk); };  
  34.   
  35.   /* print the pipeline name */  
  36.   printf("pipeline name='%s'\n",sd_GetPipelineName(pk,pind));  
  37.   
  38.   /* make sure the pipeline returns decisions */  
  39.   if( sd_GetDecCount(pk,pind)==0 ) {  
  40.     printf("Error: This example assumes that pipeline returns decisions.\n");  
  41.     sd_ReleaseKernel(pk);  
  42.     return(SD_ERROR);  
  43.   }  
  44.   
  45.   /* pipeline input dimensionality */  
  46.   fc1=sd_GetInputFc(pk,pind);  
  47.   
  48.   /* allocate the input buffer for two samples */  
  49.   sc=2;  
  50.   pbin=sd_BufNew(SD_DOUBLE,sc,fc1);  
  51.   
  52.   /* fill-in two samples  
  53.      IMPORTANT: we assume in this example that fc1==2 
  54.    */  
  55.   sd_BufSetValueDouble(pbin, 0, 0, 1.0); /* first sample, first feature */  
  56.   sd_BufSetValueDouble(pbin, 0, 1, 2.0); /* first sample, second feature */  
  57.   sd_BufSetValueDouble(pbin, 1, 0, -5.0); /* second sample, first feature */  
  58.   sd_BufSetValueDouble(pbin, 1, 1, 10.0); /* second sample, second feature */  
  59.   
  60.   /* attach input buffer to the pipeline */  
  61.   res=sd_BufAttachToInput(pk,pind,pbin);  
  62.   if( res==SD_ERROR ) {  
  63.     sd_BufFree(pbin);  
  64.     SD_ABORT(pk);  
  65.   }  
  66.   
  67.   /* pipeline output dimensionality */  
  68.   fc2=1; /* 1D because the pipeline returns decisions */  
  69.   
  70.   /* allocate output buffer */  
  71.   pbout=sd_BufNew(SD_INT,sc,fc2);  
  72.   
  73.   /* attach output buffer to the pipeline */  
  74.   res=sd_BufAttachToOutput(pk,pind,pbout);  
  75.   if( res==SD_ERROR ) {  
  76.     sd_BufFree(pbout);  
  77.     SD_ABORT(pk);  
  78.   }  
  79.   
  80.   /* Execute pipeline */  
  81.   res=sd_Execute(pk,pind);  
  82.   
  83.   /* print out the outputs (we assume 1D output) */  
  84.   for(i=0; i<sc; i++) {  
  85.     res=sd_BufGetValueInt(pbout,i,0); /* decision as integer */  
  86.     printf("out(%d)=%d, '%s'\n",i, res, sd_GetDecName(pk,pind,res) );  
  87.   }  
  88.   
  89.   /* releasing all buffers we allocated ourselves is our responsibility */  
  90.   sd_BufFree(pbin);  
  91.   sd_BufFree(pbout);  
  92.   
  93.   /* release the PRSD library */  
  94.   sd_ReleaseKernel(pk);  
  95.     
  96.   return(0);  
  97. }  

Notes:

20.6.2. Using multiple pipelines ↩

libPRSD allows switching between multiple pipelines in one session. A call to sd_LoadPipeline returns a pipeline index pind. All functions operating on a pipeline specify the working pipeline using this index.

20.6.3. Handling decisions ↩

Pipeline may return soft outputs (e.g. probabilities) or decisions. To test if pipeline returns decisions, use sd_getDecCount function. If it returns 0, the pipeline returns soft outputs.

For the complete example, see ex_decisions.c file.

ex_decisions.c example of returning decisions
view plaincopy to clipboardprint?
  1. if( sd_GetDecCount(pk,pind)==0 ) {  
  2.   printf("Expected pipeline returnind decisions. Aborting.\n");  
  3.   SD_ABORT(pk);  
  4. }  

Pipeline decisions are provided as numerical decision codes that need to be translated into decision name if needed. Decision codes preserve the same values that were used when creating the classifier in Matlab.

Let us consider a simple example building a three-class linear classifier:

>> load fruit
>> a
'Fruit set' 260 by 2 sddata, 3 classes: 'apple'(100) 'banana'(100) 'stone'(60) 
>> p=sdlinear(a)*sddecide
sequential pipeline     2x1 'Gauss eq.cov.+Output normalization+Decision'
 1  Gauss eq.cov.           2x3  3 classes, 3 components (sdp_normal)
 2  Output normalization    3x3  (sdp_norm)
 3  Decision                3x1  weighting, 3 classes, 1 ops at op 1 (sdp_decide)

>> sdexport(p,'linear_dec.ppl')
Exporting pipeline for deployment using libPRSD

>> p.list
sdlist (3 entries)
 ind name
   1 apple 
   2 banana
   3 stone 

>> [1 2; -5 10]*p

ans =

 1
 3

The classifier returns one of the three decisions, numerically 1=apple, 2=banana, 3=stone.

To display the pipeline decisions in C as strings, we use the following code:

ex_decisions.c example of returning decisions
view plaincopy to clipboardprint?
  1. /* print out the outputs (we assume 1D output) */  
  2. for(i=0; i<sc; i++) {  
  3.   res=sd_BufGetValueInt(pbout,i,0);  
  4.   
  5.   printf("out(%d): %d='%s'\n",i, res,sd_GetDecName(pk,pind,res) );  
  6. }  

On the line 3, we read the integer pipeline output into res. On the line 5, we print both the numerical decision code res and the corresponding decision name.

The outputs of the C code for the two samples used also in Listing 1 ([1 2; -5 10]) are:

pipeline name='Gauss eq.cov.+Output normalization+Decision'
out(0): 1='apple'
out(1): 3='stone'

20.7. Directly applying pipelines to uint8/uint16 data ↩

20.7.1. Introduction ↩

By default, perClass works with double precision data. Starting with perClass 4.6, it is possible to directly deal with uint8 or uint16 input data buffers such as raw image content. The idea is to add a conversion step, constructed with the sdconvert command. This conversion step will cast the input uint8 or uint16 data into double precision inside the runtime so the rest of the pipeline may stay in double precision.

In the following example, we build a k-NN based classifier for an RGB image. We define mean prototypes for our three classes manually:

% create a data set
>> data=[ 119   115    71 ; ...
   176   179   153 ; ...
   201   243   246];

>> tr=sddata( data , sdlab('grass','road','sky') )
3 by 3 sddata, 3 classes: 'grass'(1) 'road'(1) 'sky'(1) 

>> p=sdknn(tr)
sequential pipeline       3x1 '1-NN+Decision'
 1 1-NN                    3x3  3 prototypes
 2 Decision                3x1  weighting, 3 classes

The pipeline p can classify any RGB vector into grass, road or sky classes. However, it requires double precision inputs.

We may add a conversion step with sdconvert:

>> pall=sdconvert('uint8-double',p)
sequential pipeline       3x1 'Type conversion+1-NN+Decision'
 1 Type conversion         3x3  uint8-double
 2 1-NN                    3x3  3 prototypes
 3 Decision                3x1  weighting, 3 classes

The pall pipeline expects uint8 inputs. We may now apply it to uint8 data matrix with sdexe:

>> data

data =

   119   115    71
   176   179   153
   201   243   246

>> class(data)

ans =

double

>> udata=uint8(data)

udata =

  119  115   71
  176  179  153
  201  243  246

>> sdexe(pc,udata)

ans =

       1
       2
       3

As expected, we can see decisions 1,2,3 as our training data became k-NN prototypes in the same order and thus have zero distance to each other.

Exporting a pipeline containing sdconvert step shows that perClass Runtime 4.6 or later is needed:

>> sdexport(pc,'test.ppl')
Exporting pipeline..ok
This pipeline requires perClass runtime version 4.6 (29-jun-2015) or higher.

20.7.2. Feature selection on uint8 data ↩

The conversion pipeline can directly perform feature selection on input data. This is useful in order to limit pipeline execution to some band or bands in the original image without converting the rest.

We may use 'select' option to define feature indices. Note, however, that we must construct a conversion step directly and join pipeline manually.

% classifier trained only on the green channel
>> tr.featlab=sdlab('R','G','B' )
3 by 3 sddata, 3 classes: 'grass'(1) 'road'(1) 'sky'(1) 

>> pk=sdknn(tr(:,'G'))
sequential pipeline       1x1 '1-NN+Decision'
 1 1-NN                    1x3  3 prototypes
 2 Decision                3x1  weighting, 3 classes

>> pc=sdconvert('uint8-double','inputs',3,'select',[2])
Type conversion pipeline  3x1  uint8-double

>> pall=pc*pk
sequential pipeline       3x1 'Type conversion+1-NN+Decision'
 1 Type conversion         3x1  uint8-double
 2 1-NN                    1x3  3 prototypes
 3 Decision                3x1  weighting, 3 classes

20.7.3. Runtime API supporting uint8/uint16 data types ↩

On the side of perClass Runtime API, uint8 and uint16 data types are denoted by SD_UINT8 and SD_UINT16 constants, respectivaly.

Identically to the standard double-precision pipelines, there are two ways how you may provide an input buffer to uint8/uint16 pipelines. The first one is to attach uint8 pipeline directly to your existing uint8 memory buffer with sd_AttachMemToInput function. You only need to provide it with a pointer to unsigned char array, casted as void*.

Second option is to construct a prbuf buffer object containing uint8 data. If you prefer that perClass runtime allocates the buffer, use the sd_BufNew function with SD_UINT8 or SD_UIN16 as type. Alternatively, you may wrap an existing memory area in your application memory space into a buffer with sd_BufReferrringTo. In both case, the values may be read/stored using sd_BufGetValueUint8 or sd_BufSetValueUint8, respectively (or sd_BufGetValueUint16 and sd_BufSetValueUint16 for uint16 data).

For an example, see SDK ex_uint8 example, namely the ex_uint8.m Matlab file exporting a uint8 pipeline file and ex_uint8.c C example executing it on new data.

20.8. Measuring time of classifier execution ↩

20.8.1. Timers in C API ↩

perClass Runtime API provides cross-platform high-precision timer via sd_Tic and sd_Toc functions. The timer returns a double-precision value with elapsed time in seconds.

double elapsed_sec;
...
pk=sd_InitKernel(NULL);
...
sd_Tic(pk);
/* your code to time */
elapsed_sec=sd_Toc(pk);

20.8.2. Timing classifier execution via sdrun ↩

The sdrun utility can time classifier execution with -t option. When provided with a pipeline, the sdrun creates an empty input and output buffers and executes the pipeline 10 times. Average time of classifier execution is returned:

$ ./sdrun fisher_dec.ppl  -t
perClass Demo 4.3 (20-May-2014), Copyright (C) 2007-2014, PR Sys Design, All rights reserved
Demo license. Only for evaluation purposes. This license will expire on 19-jun-2014

Measuring execution speed for 'Fisher linear discriminant'
double precision, 2D input: ..........
Average speed on 10 rounds of 100000 samples: 16.9 msec.
Total thoughput: 5.91 Mega samples/sec.

By default, the input buffer contains 100 000 samples. This may be changed with an extra argument of -t option:

$ ./sdrun parzen_dec.ppl -t 10000
perClass Demo 4.3 (20-May-2014), Copyright (C) 2007-2014, PR Sys Design, All rights reserved
Demo license. Only for evaluation purposes. This license will expire on 19-jun-2014

Measuring execution speed for 'Parzen model+Decision'
double precision, 2D input: ..........
Average speed on 10 rounds of 10000 samples: 54.0 msec.
Total thoughput: 185.06 kilo samples/sec.