/Users/maurits/Documents/studie/afstuderen/biosphere/modules/fusion/bsmod_fusion.c

Go to the documentation of this file.
00001 /*
00002  * Author: MA Hartman
00003  * Date: april 10, 2007
00004  * 
00005  * Function:
00006  * A module which offers multimodal biometric fusion at different levels.
00007  * 
00008  * License information:
00009  * 
00010  * Copyright (c) 2006 Maurits Hartman
00011  *
00012  * Permission is hereby granted, free of charge, to any person
00013  * obtaining a copy of this software and associated documentation
00014  * files (the "Software"), to deal in the Software without
00015  * restriction, including without limitation the rights to use,
00016  * copy, modify, merge, publish, distribute, sublicense, and/or sell
00017  * copies of the Software, and to permit persons to whom the
00018  * Software is furnished to do so, subject to the following
00019  * conditions:
00020  * 
00021  * The above copyright notice and this permission notice shall be
00022  * included in all copies or substantial portions of the Software.
00023  * 
00024  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00025  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
00026  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00027  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
00028  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
00029  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
00030  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
00031  * OTHER DEALINGS IN THE SOFTWARE.
00032  * 
00033  */
00034 
00035 #include <biosphere_module.h>
00036 #include "util.h"
00037 
00038 #include <string.h>
00039 #include <stdio.h>
00040 #include <stdlib.h>
00041 #include <assert.h>
00042 #include <math.h>
00043 
00050 #define NORM_METHOD_NO 0        
00051 #define NORM_METHOD_MM 1        
00052 #define NORM_METHOD_ZS 2        
00053 #define NORM_METHOD_TH 3        
00054 #define NORM_METHOD_QQ 4        
00055 #define NORM_METHOD_QLQ 5       
00061 bs_module bsmod_symtable;
00062 
00063 
00067 static bs_status bsmod_fusion_init(void)
00068 {
00069         return BS_OK;
00070 }
00071 
00072 
00076 static bs_status bsmod_fusion_cleanup(void)
00077 {
00078         return BS_OK;   
00079 }
00080 
00081 
00082 static bs_service_response *create_biometric_decision_response(
00083                 const bs_service_request *request,
00084                 bs_bool decision)
00085 {
00086         bs_service_response *resp;
00087         bs_message_instance *output;
00088         bs_part_instance *part;
00089         bs_data_type *type;
00090         unsigned i;
00091         
00092         resp = (bs_service_response *) calloc(1, sizeof(bs_service_response));
00093         for (i = 0; i < 16; i++)
00094                 resp->uuid[i] = request->uuid[i];
00095         resp->service = (char *) malloc(len(request->service) + 1);
00096         resp->port = (char *) malloc(len(request->port) + 1);
00097         resp->operation = (char *) malloc(len(request->operation) + 1);
00098         strcpy(resp->service, request->service);
00099         strcpy(resp->port, request->port);
00100         strcpy(resp->operation, request->operation);
00101         
00102         output = (bs_message_instance *) calloc(1, sizeof(bs_service_response));
00103         output->name = (char *) malloc(len("biometric_decision") + 1);
00104         strcpy(output->name, "biometric_decision");
00105         output->num_parts = 1;
00106         output->parts = (bs_part_instance **) calloc(1, sizeof(bs_part_instance *));
00107         resp->output = output;
00108         
00109         part = (bs_part_instance *) calloc(1, sizeof(bs_part_instance));
00110         part->name = (char *) malloc(len("") + 1);
00111         strcpy(part->name, "decision");
00112         part->data = (char *) calloc(1, 20);
00113         if (decision) sprintf(part->data, "ACCEPT");
00114         else sprintf(part->data, "REJECT");
00115         part->size = len(part->data); 
00116         output->parts[0] = part;
00117         
00118         type = (bs_data_type *) calloc(1, sizeof(bs_data_type));
00119         type->name = (char *) malloc(len("bs_string") + 1);
00120         strcpy(type->name, "bs_string");
00121         type->builtin = TRUE;
00122         type->from_mp = FALSE;
00123         part->type = type;
00124         
00125         return resp;
00126 }
00127 
00128 
00129 static bs_service_response *create_biometric_response(
00130                 const bs_service_request *request,
00131                 bs_double score)
00132 {
00133         bs_service_response *resp;
00134         bs_message_instance *output;
00135         bs_part_instance *part;
00136         bs_data_type *type;
00137         unsigned i;
00138         
00139         resp = (bs_service_response *) calloc(1, sizeof(bs_service_response));
00140         for (i = 0; i < 16; i++)
00141                 resp->uuid[i] = request->uuid[i];
00142         resp->service = (char *) malloc(len(request->service) + 1);
00143         resp->port = (char *) malloc(len(request->port) + 1);
00144         resp->operation = (char *) malloc(len(request->operation) + 1);
00145         strcpy(resp->service, request->service);
00146         strcpy(resp->port, request->port);
00147         strcpy(resp->operation, request->operation);
00148         
00149         output = (bs_message_instance *) calloc(1, sizeof(bs_service_response));
00150         output->name = (char *) malloc(len("biometric_score") + 1);
00151         strcpy(output->name, "biometric_score");
00152         output->num_parts = 1;
00153         output->parts = (bs_part_instance **) calloc(1, sizeof(bs_part_instance *));
00154         resp->output = output;
00155         
00156         part = (bs_part_instance *) calloc(1, sizeof(bs_part_instance));
00157         part->name = (char *) malloc(len("") + 1);
00158         strcpy(part->name, "score");
00159         part->data = (char *) calloc(1, 20);
00160         sprintf(part->data, "%lf", score);
00161         part->size = len(part->data); 
00162         output->parts[0] = part;
00163         
00164         type = (bs_data_type *) calloc(1, sizeof(bs_data_type));
00165         type->name = (char *) malloc(len("bs_double") + 1);
00166         strcpy(type->name, "bs_double");
00167         type->builtin = TRUE;
00168         type->from_mp = FALSE;
00169         part->type = type;
00170         
00171         return resp;
00172 }
00173 
00174 
00182 static int get_normalization(const char *norm)
00183 {
00184         if (strlen(norm) < 2) return NORM_METHOD_NO;
00185         if (norm[0] == 'M' && norm[1] == 'M') return NORM_METHOD_MM;
00186         if (norm[0] == 'Z' && norm[1] == 'S') return NORM_METHOD_ZS;
00187         if (norm[0] == 'T' && norm[1] == 'H') return NORM_METHOD_TH;
00188         if (norm[0] == 'Q' && norm[1] == 'Q') return NORM_METHOD_QQ;
00189         if (norm[0] == 'Q' && norm[1] == 'L' && norm[2] == 'Q')
00190                 return NORM_METHOD_QLQ;
00191         else return NORM_METHOD_NO;     
00192 }
00193 
00194 #define IMPOSSIBLE 1000000000.0
00195 
00205 static double normalize(double s, char *norm)
00206 {
00207         int normtype = get_normalization(norm);
00208         double p1 = IMPOSSIBLE, p2 = IMPOSSIBLE;
00209         double c = IMPOSSIBLE, w = IMPOSSIBLE, Nmm;
00210         unsigned i = 1;
00211         
00212         if (normtype == NORM_METHOD_NO) return s;
00213         
00214         /* Retrieve the parameters (two or four): */
00215         while (norm[i] != '\0') {
00216                 if (norm[i-1] == ' ' && norm[i] != ' ') {
00217                         char *n = norm + i;
00218                         p1 = atof(n);
00219                         i++;
00220                         break;
00221                 }
00222                 i++;    
00223         }
00224         while (norm[i] != '\0') {
00225                 if (norm[i-1] == ' ' && norm[i] != ' ') {
00226                         char *n = norm + i;
00227                         p2 = atof(n);
00228                         i++;
00229                         break;
00230                 }
00231                 i++;    
00232         }
00233         while (norm[i] != '\0') {
00234                 if (norm[i-1] == ' ' && norm[i] != ' ') {
00235                         char *n = norm + i;
00236                         c = atof(n);
00237                         i++;
00238                         break;
00239                 }
00240                 i++;    
00241         }
00242         while (norm[i] != '\0') {
00243                 if (norm[i-1] == ' ' && norm[i] != ' ') {
00244                         char *n = norm + i;
00245                         w = atof(n);
00246                         i++;
00247                         break;
00248                 }
00249                 i++;    
00250         }
00251         if (p1 == IMPOSSIBLE || p2 == IMPOSSIBLE) return -1.0;
00252         
00253         /* Calculate different normalizations: */
00254         switch(normtype) {
00255                 case NORM_METHOD_MM:
00256                         /* p1 = min, p2 = max, map linearly to [0,1]: */
00257                         return (s - p1) / (p2 - p1);
00258                 case NORM_METHOD_ZS:
00259                         /* p1 = mean, p2 = stdev: */
00260                         return (s - p1) / p2;
00261                 case NORM_METHOD_TH:
00262                         /* p1 = mean, p2 = stdev: */
00263                         return 0.5 * (tanh(0.01 * (s - p1) / p2) + 1);
00264                 case NORM_METHOD_QQ:
00265                         /* QQ function with Min-Max mapping (Nmm) to [0,1]: */
00266                         if (c == IMPOSSIBLE) return -1.0;
00267                         Nmm = (s - p1) / (p2 - p1);
00268                         if (Nmm <= c) return Nmm * Nmm / c;
00269                         else return c + sqrt((1 - c) * (Nmm - c));
00270                 case NORM_METHOD_QLQ:
00271                         /* Similar to QQ, but with overlapped zone w: */
00272                         if (c == IMPOSSIBLE || w == IMPOSSIBLE) return -1.0;
00273                         Nmm = (s - p1) / (p2 - p1);
00274                         if (Nmm <= c - w/2) return Nmm * Nmm / (c - w/2);
00275                         else if (c - w/2 < Nmm && Nmm <= c + w/2) return Nmm;
00276                         else return c + w/2 + sqrt((1 - c - w/2) * (Nmm - c - w/2));
00277         }
00278         return s;
00279 }
00280 
00281 
00282 static double fuse_mw(unsigned ns, double s1, double s2, double s3,
00283                 char *meth)
00284 {
00285         double w1 = IMPOSSIBLE, w2 = IMPOSSIBLE, w3 = IMPOSSIBLE;
00286         unsigned i = 1;
00287         
00288         while (meth[i] != '\0') {
00289                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00290                         char *n = meth + i;
00291                         w1 = atof(n);
00292                         i++;
00293                         break;
00294                 }
00295                 i++;    
00296         }
00297         while (meth[i] != '\0') {
00298                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00299                         char *n = meth + i;
00300                         w2 = atof(n);
00301                         i++;
00302                         break;
00303                 }
00304                 i++;    
00305         }
00306         while (meth[i] != '\0') {
00307                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00308                         char *n = meth + i;
00309                         w3 = atof(n);
00310                         i++;
00311                         break;
00312                 }
00313                 i++;    
00314         }
00315         
00316         if (ns == 2 && w1 != IMPOSSIBLE && w2 != IMPOSSIBLE) {
00317                 double mw1, mw2;
00318                 mw1 = 1.0 / (w1 * (1/w1 + 1/w2));
00319                 mw2 = 1.0 / (w2 * (1/w1 + 1/w2));
00320                 return s1 * mw1 + s2 * mw2;
00321         }
00322         else if (ns == 3 && w1 != IMPOSSIBLE && w2 != IMPOSSIBLE &&
00323                         w3 != IMPOSSIBLE) {
00324                 double mw1, mw2, mw3;
00325                 mw1 = 1.0 / (w1 * (1/w1 + 1/w2 + 1/w3));
00326                 mw2 = 1.0 / (w2 * (1/w1 + 1/w2 + 1/w3));
00327                 mw3 = 1.0 / (w3 * (1/w1 + 1/w2 + 1/w3));
00328                 return s1 * mw1 + s2 * mw2 + s3 * mw3;
00329         }
00330         return -1.0;    
00331 }
00332 
00333 static bs_bool fuse_dw(unsigned ns, bs_bool s1a, bs_bool s2a, bs_bool s3a,
00334                 char *meth)
00335 {
00336         double s1 = s1a ? 1.0: 0.0;
00337         double s2 = s2a ? 1.0: 0.0;
00338         double s3 = s3a ? 1.0: 0.0;
00339         double threshold;
00340         
00341         double w1 = IMPOSSIBLE, w2 = IMPOSSIBLE, w3 = IMPOSSIBLE;
00342         unsigned i = 1;
00343         
00344         /* Extract parameters: */
00345         while (meth[i] != '\0') {
00346                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00347                         char *n = meth + i;
00348                         threshold = atof(n);
00349                         i++;
00350                         break;
00351                 }
00352                 i++;    
00353         }
00354         while (meth[i] != '\0') {
00355                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00356                         char *n = meth + i;
00357                         w1 = atof(n);
00358                         i++;
00359                         break;
00360                 }
00361                 i++;    
00362         }
00363         while (meth[i] != '\0') {
00364                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00365                         char *n = meth + i;
00366                         w2 = atof(n);
00367                         i++;
00368                         break;
00369                 }
00370                 i++;    
00371         }
00372         while (meth[i] != '\0') {
00373                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00374                         char *n = meth + i;
00375                         w3 = atof(n);
00376                         i++;
00377                         break;
00378                 }
00379                 i++;    
00380         }
00381         
00382         if (ns == 2 && w1 != IMPOSSIBLE && w2 != IMPOSSIBLE) {
00383                 double mw1, mw2;
00384                 mw1 = 1.0 / (w1 * (1/w1 + 1/w2));
00385                 mw2 = 1.0 / (w2 * (1/w1 + 1/w2));
00386                 return s1 * mw1 + s2 * mw2 >= threshold;
00387         }
00388         else if (ns == 3 && w1 != IMPOSSIBLE && w2 != IMPOSSIBLE &&
00389                         w3 != IMPOSSIBLE) {
00390                 double mw1, mw2, mw3;
00391                 mw1 = 1.0 / (w1 * (1/w1 + 1/w2 + 1/w3));
00392                 mw2 = 1.0 / (w2 * (1/w1 + 1/w2 + 1/w3));
00393                 mw3 = 1.0 / (w3 * (1/w1 + 1/w2 + 1/w3));
00394                 return s1 * mw1 + s2 * mw2 + s3 * mw3 >= threshold;
00395         }
00396         return FALSE;
00397 }
00398 
00399 
00404 static bs_bool majority_vote(unsigned ns, double s1, double s2, double s3,
00405                 char *meth)
00406 {
00407         double threshold = 1.5;
00408         unsigned i = 1, num_accept = 0;
00409         
00410         /* Extract parameters: */
00411         while (meth[i] != '\0') {
00412                 if (meth[i-1] == ' ' && meth[i] != ' ') {
00413                         char *n = meth + i;
00414                         threshold = atof(n);
00415                         i++;
00416                         break;
00417                 }
00418                 i++;    
00419         }
00420         
00421         /* Count accept votes: */
00422         if (s1) num_accept++;
00423         if (s2) num_accept++;
00424         if (ns > 2 && s3) num_accept++;
00425         return num_accept >= threshold;
00426 }
00427 
00428 
00435 static bs_status handle_fuse2(const bs_service_request *request,
00436                 bs_service_response **response)
00437 {
00438         char *meth, *norm1, *norm2;
00439         double s1, s2, result = -1.0;
00440         
00441         /* Get parts: */
00442         if (request->input->num_parts != 5) return BS_SERVICE_NUM_PARTS_MISMATCH;
00443         meth = request->input->parts[0]->data;
00444         if (strlen(meth) < 2) return BS_SERVICE_PART_INCORRECT;
00445         norm1 = request->input->parts[1]->data;
00446         norm2 = request->input->parts[2]->data;
00447         s1 = atof(request->input->parts[3]->data);
00448         s2 = atof(request->input->parts[4]->data);
00449         
00450         /* Normalize the scores: */
00451         s1 = normalize(s1, norm1);
00452         s2 = normalize(s2, norm2);
00453         
00454         /* Calculate fusion: */
00455         if (meth[0] == 'S' && meth[1] == 'S')
00456                 result = s1 + s2;
00457         else if (meth[0] == 'M' && meth[1] == 'I')
00458                 result = min(s1, s2, 10000000.0); /* TODO A tad unsafe... */
00459         else if (meth[0] == 'M' && meth[1] == 'A')
00460                 result = max(s1, s2, -10000000.0); /* TODO A tad unsafe... */
00461         else if (meth[0] == 'M' && meth[1] == 'W')
00462                 result = fuse_mw(2, s1, s2, 0.0, meth);
00463         
00464         /* Create response: */
00465         *response = create_biometric_response(request, result);
00466         return BS_OK;
00467 }
00468 
00469 
00476 static bs_status handle_fuse3(const bs_service_request *request,
00477                 bs_service_response **response)
00478 {
00479         char *meth, *norm1, *norm2, *norm3;
00480         double s1, s2, s3, result = -1.0;
00481         
00482         /* Get parts: */
00483         if (request->input->num_parts != 7) return BS_SERVICE_NUM_PARTS_MISMATCH;
00484         meth = request->input->parts[0]->data;
00485         if (strlen(meth) < 2) return BS_SERVICE_PART_INCORRECT;
00486         norm1 = request->input->parts[1]->data;
00487         norm2 = request->input->parts[2]->data;
00488         norm3 = request->input->parts[3]->data;
00489         s1 = atof(request->input->parts[4]->data);
00490         s2 = atof(request->input->parts[5]->data);
00491         s3 = atof(request->input->parts[6]->data);
00492         
00493         /* Normalize the scores: */
00494         s1 = normalize(s1, norm1);
00495         s2 = normalize(s2, norm2);
00496         s3 = normalize(s3, norm3);
00497         
00498         /* Calculate fusion: */
00499         if (meth[0] == 'S' && meth[1] == 'S')
00500                 result = s1 + s2 + s3;
00501         else if (meth[0] == 'M' && meth[1] == 'I')
00502                 result = min(s1, s2, s3);
00503         else if (meth[0] == 'M' && meth[1] == 'A')
00504                 result = max(s1, s2, s3);
00505         else if (meth[0] == 'M' && meth[1] == 'W')
00506                 result = fuse_mw(3, s1, s2, s3, meth);
00507         
00508         /* Create response: */
00509         *response = create_biometric_response(request, result);
00510         return BS_OK;
00511 }
00512 
00513 
00520 static bs_status handle_fuse2_decision(const bs_service_request *request,
00521                 bs_service_response **response)
00522 {
00523         char *meth;
00524         bs_bool s1, s2, result = FALSE;
00525         
00526         /* Get parts: */
00527         if (request->input->num_parts != 3) return BS_SERVICE_NUM_PARTS_MISMATCH;
00528         meth = request->input->parts[0]->data;
00529         if (strlen(meth) < 2) return BS_SERVICE_PART_INCORRECT;
00530         s1 = decision_to_bool(request->input->parts[1]->data);
00531         s2 = decision_to_bool(request->input->parts[2]->data);
00532         
00533         
00534         /* Calculate fusion: */
00535         if (meth[0] == 'B' && meth[1] == 'A') {/* Boolean And */
00536                 if (s1 && s2) result = TRUE;
00537         }
00538         else if (meth[0] == 'B' && meth[1] == 'O') {/* Boolean Or */
00539                 if (s1 || s2) result = TRUE;
00540         }
00541         else if (meth[0] == 'M' && meth[1] == 'V') /* Majority voting */
00542                 result = majority_vote(2, s1, s2, FALSE, meth);
00543         else if (meth[0] == 'D' && meth[1] == 'W') /* Decision weighting */
00544                 result = fuse_dw(2, s1, s2, FALSE, meth);
00545         else return BS_ERROR;
00546         
00547         /* Create response: */
00548         *response = create_biometric_decision_response(request, result);
00549         return BS_OK;
00550 }
00551 
00552 
00559 static bs_status handle_fuse3_decision(const bs_service_request *request,
00560                 bs_service_response **response)
00561 {
00562         char *meth;
00563         bs_bool s1, s2, s3, result = FALSE;
00564         
00565         /* Get parts: */
00566         if (request->input->num_parts != 4) return BS_SERVICE_NUM_PARTS_MISMATCH;
00567         meth = request->input->parts[0]->data;
00568         if (strlen(meth) < 2) return BS_SERVICE_PART_INCORRECT;
00569         s1 = decision_to_bool(request->input->parts[1]->data);
00570         s2 = decision_to_bool(request->input->parts[2]->data);
00571         s3 = decision_to_bool(request->input->parts[3]->data);
00572         
00573         
00574         /* Calculate fusion: */
00575         if (meth[0] == 'B' && meth[1] == 'A') {/* Boolean And */
00576                 if (s1 && s2 && s3) result = TRUE;
00577         }
00578         else if (meth[0] == 'B' && meth[1] == 'O') {/* Boolean Or */
00579                 if (s1 || s2 || s3) result = TRUE;
00580         }
00581         else if (meth[0] == 'M' && meth[1] == 'V') /* Majority voting */
00582                 result = majority_vote(3, s1, s2, s3, meth);
00583         else if (meth[0] == 'D' && meth[1] == 'W') /* Decision weighting */
00584                 result = fuse_dw(3, s1, s2, s3, meth);
00585         else return BS_ERROR;
00586         
00587         /* Create response: */
00588         *response = create_biometric_decision_response(request, result);
00589         return BS_OK;
00590 }
00591 
00592 
00597 static bs_status bsmod_fusion_handle_service(
00598                 const bs_service_request *request,
00599                 bs_service_response **response, void *extra)
00600 {
00601         if (!streq(request->service, "BSModFusionServices"))
00602                 return BS_NO_SERVICE;
00603         
00604         if (streq(request->port, "MatchLevel")) {
00605                 if (streq(request->operation, "fuse2"))
00606                         return handle_fuse2(request, response);
00607                 else if (streq(request->operation, "fuse3"))
00608                         return handle_fuse3(request, response);
00609         }
00610         else if (streq(request->port, "DecisionLevel")) {
00611                 if (streq(request->operation, "fuse2_decision"))
00612                         return handle_fuse2_decision(request, response);
00613                 else if (streq(request->operation, "fuse3_decision"))
00614                         return handle_fuse3_decision(request, response);
00615         }
00616         
00617         return BS_NO_SERVICE;                   
00618 }
00619 
00620 
00621 /* Here come functions offered by the module: */
00622 
00623 /* Final declaration of the bs_module struct: */
00624 bs_module bsmod_symtable = {
00625         /* The major and minor version of the API used by this module: */
00626         0,
00627         0,
00628         
00629         /* The major and minor version of this module: */
00630         0,
00631         1,
00632         
00633         /* The name, author, date and copyright information: */
00634         "multimodal fusion module",
00635         "M.A.Hartman",
00636         "may 5, 2007",
00637         "(c) 2007, M.A.Hartman. See distributed LICENSE file for more information.",
00638         "A module which offers various biometric fusion functions and normalization "
00639         "methods. Fusion methods are: Simple Sum (SS), Minimum Score (MI), "
00640         "Maximum Score (MA) and Matcher Weighting (MW). Available normalization "
00641         "that can be applied to the score before fusion are: Min-Max (MM), Z-score (ZS), "
00642         "Tanh (TH) and Adaptive (QQ and QLQ). Some of the normalization functions "
00643         "require extra parameters. These should be provided after the name of the "
00644         "normalization, separated by spaces, in the 'norm' part.",
00645         
00646         /* Init and cleanup routines: */
00647         bsmod_fusion_init,
00648         bsmod_fusion_cleanup,
00649         
00650         /* Service request handling function: */
00651         bsmod_fusion_handle_service,
00652         NULL,
00653         "bsmod_fusion.wsdl"
00654 };
00655 

Generated on Tue Jul 17 09:50:52 2007 for Bio-SPHERE by  doxygen 1.5.1