diff --git a/libfprint/Makefile.am b/libfprint/Makefile.am index a685774..f857e6b 100644 --- a/libfprint/Makefile.am +++ b/libfprint/Makefile.am @@ -6,14 +6,46 @@ AES4000_SRC = drivers/aes4000.c DRIVER_SRC = $(UPEKTS_SRC) $(URU4000_SRC) $(AES4000_SRC) -libfprint_la_CFLAGS = -fvisibility=hidden $(LIBUSB_CFLAGS) $(GLIB_CFLAGS) $(AM_CFLAGS) +NBIS_SRC = \ + nbis/mindtct/binar.c \ + nbis/mindtct/block.c \ + nbis/mindtct/chaincod.c \ + nbis/mindtct/contour.c \ + nbis/mindtct/detect.c \ + nbis/mindtct/dft.c \ + nbis/mindtct/free.c \ + nbis/mindtct/getmin.c \ + nbis/mindtct/globals.c \ + nbis/mindtct/imgutil.c \ + nbis/mindtct/init.c \ + nbis/mindtct/isempty.c \ + nbis/mindtct/line.c \ + nbis/mindtct/link.c \ + nbis/mindtct/log.c \ + nbis/mindtct/loop.c \ + nbis/mindtct/maps.c \ + nbis/mindtct/matchpat.c \ + nbis/mindtct/minutia.c \ + nbis/mindtct/morph.c \ + nbis/mindtct/mytime.c \ + nbis/mindtct/quality.c \ + nbis/mindtct/remove.c \ + nbis/mindtct/results.c \ + nbis/mindtct/ridges.c \ + nbis/mindtct/shape.c \ + nbis/mindtct/sort.c \ + nbis/mindtct/util.c \ + nbis/mindtct/xytreps.c + +libfprint_la_CFLAGS = -fvisibility=hidden -Inbis/include $(LIBUSB_CFLAGS) $(GLIB_CFLAGS) $(AM_CFLAGS) libfprint_la_LDFLAGS = -version-info @lt_major@:@lt_revision@:@lt_age@ -libfprint_la_LIBADD = $(LIBUSB_LIBS) $(GLIB_LIBS) +libfprint_la_LIBADD = -lm $(LIBUSB_LIBS) $(GLIB_LIBS) libfprint_la_SOURCES = \ core.c \ data.c \ img.c \ imgdev.c \ - $(DRIVER_SRC) + $(DRIVER_SRC) \ + $(NBIS_SRC) diff --git a/libfprint/fp_internal.h b/libfprint/fp_internal.h index 32ae1c5..f998dc5 100644 --- a/libfprint/fp_internal.h +++ b/libfprint/fp_internal.h @@ -178,6 +178,8 @@ struct fp_img *fpi_img_new(size_t length); struct fp_img *fpi_img_new_for_imgdev(struct fp_img_dev *dev); struct fp_img *fpi_img_resize(struct fp_img *img, size_t newsize); gboolean fpi_img_is_sane(struct fp_img *img); +int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *img, + struct fp_print_data **ret); #define bswap16(x) (((x & 0xff) << 8) | (x >> 8)) #if __BYTE_ORDER == __LITTLE_ENDIAN diff --git a/libfprint/img.c b/libfprint/img.c index f2bfe9c..2418ffd 100644 --- a/libfprint/img.c +++ b/libfprint/img.c @@ -25,6 +25,8 @@ #include #include "fp_internal.h" +#include "nbis/include/bozorth.h" +#include "nbis/include/lfs.h" struct fp_img *fpi_img_new(size_t length) { @@ -172,3 +174,106 @@ API_EXPORTED void fp_img_standardize(struct fp_img *img) img->flags &= ~FP_IMG_COLORS_INVERTED; } } + +static int sort_x_y(const void *a, const void *b) +{ + struct minutiae_struct *af = (struct minutiae_struct *) a; + struct minutiae_struct *bf = (struct minutiae_struct *) b; + + if (af->col[0] < bf->col[0]) + return -1; + if (af->col[0] > bf->col[0]) + return 1; + + if (af->col[1] < bf->col[1]) + return -1; + if (af->col[1] > bf->col[1]) + return 1; + + return 0; +} + +/* Based on write_minutiae_XYTQ and bz_load */ +static void minutiae_to_xyt(MINUTIAE *minutiae, int bwidth, + int bheight, unsigned char *buf) +{ + int i; + MINUTIA *minutia; + struct minutiae_struct c[MAX_FILE_MINUTIAE]; + struct xyt_struct *xyt = (struct xyt_struct *) buf; + + /* FIXME: only considers first 150 minutiae (MAX_FILE_MINUTIAE) */ + /* nist does weird stuff with 150 vs 1000 limits */ + int nmin = min(minutiae->num, MAX_FILE_MINUTIAE); + + for (i = 0; i < nmin; i++){ + minutia = minutiae->list[i]; + + lfs2nist_minutia_XYT(&c[i].col[0], &c[i].col[1], &c[i].col[2], + minutia, bwidth, bheight); + c[i].col[3] = sround(minutia->reliability * 100.0); + + if (c[i].col[2] > 180) + c[i].col[2] -= 360; + } + + qsort((void *) &c, (size_t) nmin, sizeof(struct minutiae_struct), + sort_x_y); + + for (i = 0; i < nmin; i++) { + xyt->xcol[i] = c[i].col[0]; + xyt->ycol[i] = c[i].col[1]; + xyt->thetacol[i] = c[i].col[2]; + } + xyt->nrows = nmin; +} + +int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *img, + struct fp_print_data **ret) +{ + MINUTIAE *minutiae; + int r; + int *direction_map, *low_contrast_map, *low_flow_map; + int *high_curve_map, *quality_map; + int map_w, map_h; + unsigned char *bdata; + int bw, bh, bd; + struct fp_print_data *print; + GTimer *timer; + + /* 25.4 mm per inch */ + timer = g_timer_new(); + r = get_minutiae(&minutiae, &quality_map, &direction_map, + &low_contrast_map, &low_flow_map, &high_curve_map, + &map_w, &map_h, &bdata, &bw, &bh, &bd, + img->data, img->width, img->height, 8, + DEFAULT_PPI / (double)25.4, &lfsparms_V2); + g_timer_stop(timer); + fp_dbg("minutiae scan completed in %f secs", g_timer_elapsed(timer, NULL)); + g_timer_destroy(timer); + if (r) { + fp_err("get minutiae failed, code %d", r); + return r; + } + fp_dbg("detected %d minutiae", minutiae->num); + r = minutiae->num; + + /* FIXME: space is wasted if we dont hit the max minutiae count. would + * be good to make this dynamic. */ + print = fpi_print_data_new(imgdev->dev, sizeof(struct xyt_struct)); + minutiae_to_xyt(minutiae, bw, bh, print->buffer); + /* FIXME: the print buffer at this point is endian-specific, and will + * only work when loaded onto machines with identical endianness. not good! + * data format should be platform-independant. */ + *ret = print; + + free_minutiae(minutiae); + free(quality_map); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free(bdata); + + return r; +} diff --git a/libfprint/imgdev.c b/libfprint/imgdev.c index cb6f86e..1ff9bc6 100644 --- a/libfprint/imgdev.c +++ b/libfprint/imgdev.c @@ -138,10 +138,43 @@ API_EXPORTED int fp_imgdev_capture(struct fp_img_dev *imgdev, return r; } +#define MIN_ACCEPTABLE_MINUTIAE 5 + +int img_dev_enroll(struct fp_dev *dev, gboolean initial, int stage, + struct fp_print_data **ret) +{ + struct fp_img *img; + struct fp_img_dev *imgdev = dev->priv; + struct fp_print_data *print; + int r; + + /* FIXME: convert to 3-stage enroll mechanism, where we scan 3 prints, + * use NFIQ to pick the best one, and discard the others */ + + r = fp_imgdev_capture(imgdev, 0, &img); + if (r) + return r; + + fp_img_standardize(img); + r = fpi_img_detect_minutiae(imgdev, img, &print); + fp_img_free(img); + if (r < 0) + return r; + if (r < MIN_ACCEPTABLE_MINUTIAE) { + fp_dbg("not enough minutiae, %d/%d", r, MIN_ACCEPTABLE_MINUTIAE); + fp_print_data_free(print); + return FP_ENROLL_RETRY; + } + + *ret = print; + return FP_ENROLL_COMPLETE; +} + void fpi_img_driver_setup(struct fp_img_driver *idriver) { idriver->driver.type = DRIVER_IMAGING; idriver->driver.init = img_dev_init; idriver->driver.exit = img_dev_exit; + idriver->driver.enroll = img_dev_enroll; } diff --git a/libfprint/nbis/include/bozorth.h b/libfprint/nbis/include/bozorth.h new file mode 100644 index 0000000..7d37b37 --- /dev/null +++ b/libfprint/nbis/include/bozorth.h @@ -0,0 +1,263 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef _BOZORTH_H +#define _BOZORTH_H + +/* The max number of points in any Probe or Gallery XYT is set to 200; */ +/* a pointwise comparison table therefore has a maximum number of: */ +/* (200^2)/2 = 20000 comparisons. */ + + +#include +#include +#include +#include /* Needed for type pid_t */ +#include + +/* If not defined in sys/param.h */ +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +/**************************************************************************/ +/* Math-Related Macros, Definitions & Prototypes */ +/**************************************************************************/ +#include + /* This macro adjusts angles to the range (-180,180] */ +#define IANGLE180(deg) ( ( (deg) > 180 ) ? ( (deg) - 360 ) : ( (deg) <= -180 ? ( (deg) + 360 ) : (deg) ) ) + +#define SENSE(a,b) ( (a) < (b) ? (-1) : ( ( (a) == (b) ) ? 0 : 1 ) ) +#define SENSE_NEG_POS(a,b) ( (a) < (b) ? (-1) : 1 ) + +#define SQUARED(n) ( (n) * (n) ) + +#ifdef ROUND_USING_LIBRARY +/* These functions should be declared in math.h: + extern float roundf( float ); + extern double round( double ); +*/ +#define ROUND(f) (roundf(f)) +#else +#define ROUND(f) ( ( (f) < 0.0F ) ? ( (int) ( (f) - 0.5F ) ) : ( (int) ( (f) + 0.5F ) ) ) +#endif + +/* PI is used in: bozorth3.c, comp.c */ +#ifdef M_PI +#define PI M_PI +#define PI_SINGLE ( (float) PI ) +#else +#define PI 3.14159 +#define PI_SINGLE 3.14159F +#endif + +/* Provide prototype for atanf() */ +extern float atanf( float ); + +/**************************************************************************/ +/* Array Length Definitions */ +/**************************************************************************/ +#include + + +/**************************************************************************/ +/**************************************************************************/ + /* GENERAL DEFINITIONS */ +/**************************************************************************/ + +#define FPNULL ((FILE *) NULL) +#define CNULL ((char *) NULL) + +#define PROGRAM "bozorth3" + +#define MAX_LINE_LENGTH 1024 + +#define SCOREFILE_EXTENSION ".scr" + +#define MAX_FILELIST_LENGTH 10000 + +#define DEFAULT_BOZORTH_MINUTIAE 150 +#define MAX_BOZORTH_MINUTIAE 200 +#define MIN_BOZORTH_MINUTIAE 0 +#define MIN_COMPUTABLE_BOZORTH_MINUTIAE 10 + +#define DEFAULT_MAX_MATCH_SCORE 400 +#define ZERO_MATCH_SCORE 0 + +#define DEFAULT_SCORE_LINE_FORMAT "s" + +#define DM 125 +#define FD 5625 +#define FDD 500 +#define TK 0.05F +#define TXS 121 +#define CTXS 121801 +#define MSTR 3 +#define MMSTR 8 +#define WWIM 10 + +#define QQ_SIZE 4000 + +#define QQ_OVERFLOW_SCORE QQ_SIZE + +/**************************************************************************/ +/**************************************************************************/ + /* MACROS DEFINITIONS */ +/**************************************************************************/ +#define INT_SET(dst,count,value) { \ + int * int_set_dst = (dst); \ + int int_set_count = (count); \ + int int_set_value = (value); \ + while ( int_set_count-- > 0 ) \ + *int_set_dst++ = int_set_value; \ + } + +/* The code that calls it assumed dst gets bumped, so don't assign to a local variable */ +#define INT_COPY(dst,src,count) { \ + int * int_copy_src = (src); \ + int int_copy_count = (count); \ + while ( int_copy_count-- > 0 ) \ + *dst++ = *int_copy_src++; \ + } + + +/**************************************************************************/ +/**************************************************************************/ + /* STRUCTURES & TYPEDEFS */ +/**************************************************************************/ + +/**************************************************************************/ +/* In BZ_SORT.C - supports stdlib qsort() and customized quicksort */ +/**************************************************************************/ + +/* Used by call to stdlib qsort() */ +struct minutiae_struct { + int col[4]; +}; + +/* Used by custom quicksort */ +#define BZ_STACKSIZE 1000 +struct cell { + int index; /* pointer to an array of pointers to index arrays */ + int item; /* pointer to an item array */ +}; + +/**************************************************************************/ +/* In BZ_IO : Supports the loading and manipulation of XYT data */ +/**************************************************************************/ +#define MAX_FILE_MINUTIAE 1000 /* bz_load() */ + +struct xyt_struct { + int nrows; + int xcol[ MAX_BOZORTH_MINUTIAE ]; + int ycol[ MAX_BOZORTH_MINUTIAE ]; + int thetacol[ MAX_BOZORTH_MINUTIAE ]; +}; + +#define XYT_NULL ( (struct xyt_struct *) NULL ) /* bz_load() */ + + +/**************************************************************************/ +/**************************************************************************/ + /* GLOBAL VARIABLES */ +/**************************************************************************/ + +/**************************************************************************/ +/* In: SRC/BIN/BOZORTH3/BOZORTH3.C */ +/**************************************************************************/ +/* Globals supporting command line options */ +extern int m1_xyt; +extern int max_minutiae; +extern int min_computable_minutiae; +extern int verbose_main; +extern int verbose_load; +extern int verbose_bozorth; +extern int verbose_threshold; +/* Global supporting error reporting */ +extern FILE *errorfp; + +/**************************************************************************/ +/* In: BZ_GBLS.C */ +/**************************************************************************/ +/* Global arrays supporting "core" bozorth algorithm */ +extern int colp[ COLP_SIZE_1 ][ COLP_SIZE_2 ]; +extern int scols[ SCOLS_SIZE_1 ][ COLS_SIZE_2 ]; +extern int fcols[ FCOLS_SIZE_1 ][ COLS_SIZE_2 ]; +extern int * scolpt[ SCOLPT_SIZE ]; +extern int * fcolpt[ FCOLPT_SIZE ]; +extern int sc[ SC_SIZE ]; +extern int yl[ YL_SIZE_1 ][ YL_SIZE_2 ]; +/* Global arrays supporting "core" bozorth algorithm continued: */ +/* Globals used significantly by sift() */ +extern int rq[ RQ_SIZE ]; +extern int tq[ TQ_SIZE ]; +extern int zz[ ZZ_SIZE ]; +extern int rx[ RX_SIZE ]; +extern int mm[ MM_SIZE ]; +extern int nn[ NN_SIZE ]; +extern int qq[ QQ_SIZE ]; +extern int rk[ RK_SIZE ]; +extern int cp[ CP_SIZE ]; +extern int rp[ RP_SIZE ]; +extern int rf[RF_SIZE_1][RF_SIZE_2]; +extern int cf[CF_SIZE_1][CF_SIZE_2]; +extern int y[20000]; + +/**************************************************************************/ +/**************************************************************************/ +/* ROUTINE PROTOTYPES */ +/**************************************************************************/ +/* In: BZ_DRVRS.C */ +extern int bozorth_probe_init( struct xyt_struct *); +extern int bozorth_gallery_init( struct xyt_struct *); +extern int bozorth_to_gallery(int, struct xyt_struct *, struct xyt_struct *); +extern int bozorth_main(struct xyt_struct *, struct xyt_struct *); +/* In: BOZORTH3.C */ +extern void bz_comp(int, int [], int [], int [], int *, int [][COLS_SIZE_2], + int *[]); +extern void bz_find(int *, int *[]); +extern int bz_match(int, int); +extern int bz_match_score(int, struct xyt_struct *, struct xyt_struct *); +extern void bz_sift(int *, int, int *, int, int, int, int *, int *); +/* In: BZ_ALLOC.C */ +extern char *malloc_or_exit(int, const char *); +extern char *malloc_or_return_error(int, const char *); +/* In: BZ_IO.C */ +extern int parse_line_range(const char *, int *, int *); +extern void set_progname(int, char *, pid_t); +extern void set_probe_filename(char *); +extern void set_gallery_filename(char *); +extern char *get_progname(void); +extern char *get_probe_filename(void); +extern char *get_gallery_filename(void); +extern char *get_next_file(char *, FILE *, FILE *, int *, int *, char *, + int, char **, int *, int *, int, int); +extern char *get_score_filename(const char *, const char *); +extern char *get_score_line(const char *, const char *, int, int, const char *); +extern struct xyt_struct *bz_load(const char *); +extern int fd_readable(int); +/* In: BZ_SORT.C */ +extern int sort_quality_decreasing(const void *, const void *); +extern int sort_order_decreasing(int [], int, int []); + +#endif /* !_BOZORTH_H */ diff --git a/libfprint/nbis/include/bz_array.h b/libfprint/nbis/include/bz_array.h new file mode 100644 index 0000000..19b7da0 --- /dev/null +++ b/libfprint/nbis/include/bz_array.h @@ -0,0 +1,121 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef _BZ_ARRAY_H +#define _BZ_ARRAY_H + +#define STATIC static +/* #define BAD_BOUNDS 1 */ + +#define COLP_SIZE_1 20000 +#define COLP_SIZE_2 5 + +#define COLS_SIZE_2 6 +#define SCOLS_SIZE_1 20000 +#define FCOLS_SIZE_1 20000 + +#define SCOLPT_SIZE 20000 +#define FCOLPT_SIZE 20000 + +#define SC_SIZE 20000 + + +#define RQ_SIZE 20000 +#define TQ_SIZE 20000 +#define ZZ_SIZE 20000 + + + +#define RX_SIZE 100 +#define MM_SIZE 100 +#define NN_SIZE 20 + + + +#define RK_SIZE 20000 + + + +#define RR_SIZE 100 +#define AVN_SIZE 5 +#define AVV_SIZE_1 2000 +#define AVV_SIZE_2 5 +#define CT_SIZE 2000 +#define GCT_SIZE 2000 +#define CTT_SIZE 2000 + + +#ifdef BAD_BOUNDS +#define CTP_SIZE_1 2000 +#define CTP_SIZE_2 1000 +#else +#define CTP_SIZE_1 2000 +#define CTP_SIZE_2 2500 +#endif + + + +/* +rp[x] == ctp[][x] :: sct[x][] +*/ + + + + +#define RF_SIZE_1 100 +#define RF_SIZE_2 10 + +#define CF_SIZE_1 100 +#define CF_SIZE_2 10 + +#define Y_SIZE 20000 + + + + + + +#define YL_SIZE_1 2 +#define YL_SIZE_2 2000 + + + + +#define YY_SIZE_1 1000 +#define YY_SIZE_2 2 +#define YY_SIZE_3 2000 + + + +#ifdef BAD_BOUNDS +#define SCT_SIZE_1 1000 +#define SCT_SIZE_2 1000 +#else +#define SCT_SIZE_1 2500 +#define SCT_SIZE_2 1000 +#endif + +#define CP_SIZE 20000 +#define RP_SIZE 20000 + +#endif /* !_BZ_ARRAY_H */ diff --git a/libfprint/nbis/include/defs.h b/libfprint/nbis/include/defs.h new file mode 100644 index 0000000..1e982d0 --- /dev/null +++ b/libfprint/nbis/include/defs.h @@ -0,0 +1,63 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef _DEFS_H +#define _DEFS_H + +/*********************************************************************/ +/* General Purpose Defines */ +/*********************************************************************/ +#ifndef True +#define True 1 +#define False 0 +#endif +#ifndef TRUE +#define TRUE True +#define FALSE False +#endif +#define Yes True +#define No False +#define Empty NULL +#ifndef None +#define None -1 +#endif +#ifndef FOUND +#define FOUND 1 +#endif +#define NOT_FOUND_NEG -1 +#define EOL EOF +#ifndef DEG2RAD +#define DEG2RAD (double)(57.29578) +#endif +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define sround(x) ((int) (((x)<0) ? (x)-0.5 : (x)+0.5)) +#define sround_uint(x) ((unsigned int) (((x)<0) ? (x)-0.5 : (x)+0.5)) +#define xor(a, b) (!(a && b) && (a || b)) +#define align_to_16(_v_) ((((_v_)+15)>>4)<<4) +#define align_to_32(_v_) ((((_v_)+31)>>5)<<5) +#ifndef CHUNKS +#define CHUNKS 100 +#endif + +#endif /* !_DEFS_H */ diff --git a/libfprint/nbis/include/lfs.h b/libfprint/nbis/include/lfs.h new file mode 100644 index 0000000..b37147e --- /dev/null +++ b/libfprint/nbis/include/lfs.h @@ -0,0 +1,1208 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef _LFS_H +#define _LFS_H + +/*********************************************************************** + PACKAGE: NIST Latent Fingerprint System + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 10/26/1999 by MDG + Comments added to guide changes to blocksize + or number of detected directions. + UPDATED: 03/11/2005 by MDG + + FILE: LFS.H + + Contains all custom structure definitions, constant definitions, + external function definitions, and external global variable + definitions required by the NIST Latent Fingerprint System (LFS). +***********************************************************************/ + +#include +#include + +/*************************************************************************/ +/* OUTPUT FILE EXTENSIONS */ +/*************************************************************************/ +#define MIN_TXT_EXT "min" +#define LOW_CONTRAST_MAP_EXT "lcm" +#define HIGH_CURVE_MAP_EXT "hcm" +#define DIRECTION_MAP_EXT "dm" +#define LOW_FLOW_MAP_EXT "lfm" +#define QUALITY_MAP_EXT "qm" +#define AN2K_OUT_EXT "mdt" +#define BINARY_IMG_EXT "brw" +#define XYT_EXT "xyt" + +/*************************************************************************/ +/* MINUTIAE XYT REPRESENTATION SCHEMES */ +/*************************************************************************/ +#define NIST_INTERNAL_XYT_REP 0 +#define M1_XYT_REP 1 + +/*************************************************************************/ +/* MACRO DEFINITIONS */ +/*************************************************************************/ + +#define max(a, b) ((a) > (b) ? (a) : (b)) +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define sround(x) ((int) (((x)<0) ? (x)-0.5 : (x)+0.5)) +#define trunc_dbl_precision(x, scale) ((double) (((x)<0.0) \ + ? ((int)(((x)*(scale))-0.5))/(scale) \ + : ((int)(((x)*(scale))+0.5))/(scale))) + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +/*************************************************************************/ +/* STRUCTURE DEFINITIONS */ +/*************************************************************************/ + +/* Lookup tables for converting from integer directions */ +/* to angles in radians. */ +typedef struct dir2rad{ + int ndirs; + double *cos; + double *sin; +} DIR2RAD; + +/* DFT wave form structure containing both cosine and */ +/* sine components for a specific frequency. */ +typedef struct dftwave{ + double *cos; + double *sin; +} DFTWAVE; + +/* DFT wave forms structure containing all wave forms */ +/* to be used in DFT analysis. */ +typedef struct dftwaves{ + int nwaves; + int wavelen; + DFTWAVE **waves; +}DFTWAVES; + +/* Rotated pixel offsets for a grid of specified dimensions */ +/* rotated at a specified number of different orientations */ +/* (directions). This structure used by the DFT analysis */ +/* when generating a Direction Map and also for conducting */ +/* isotropic binarization. */ +typedef struct rotgrids{ + int pad; + int relative2; + double start_angle; + int ngrids; + int grid_w; + int grid_h; + int **grids; +} ROTGRIDS; + +/*************************************************************************/ +/* 10, 2X3 pixel pair feature patterns used to define ridge endings */ +/* and bifurcations. */ +/* 2nd pixel pair is permitted to repeat multiple times in match. */ +#define NFEATURES 10 +#define BIFURCATION 0 +#define RIDGE_ENDING 1 +#define DISAPPEARING 0 +#define APPEARING 1 + +typedef struct minutia{ + int x; + int y; + int ex; + int ey; + int direction; + double reliability; + int type; + int appearing; + int feature_id; + int *nbrs; + int *ridge_counts; + int num_nbrs; +} MINUTIA; + +typedef struct minutiae{ + int alloc; + int num; + MINUTIA **list; +} MINUTIAE; + +typedef struct feature_pattern{ + int type; + int appearing; + int first[2]; + int second[2]; + int third[2]; +} FEATURE_PATTERN; + +/* SHAPE structure definitions. */ +typedef struct rows{ + int y; /* Y-coord of current row in shape. */ + int *xs; /* X-coords for shape contour points on current row. */ + int alloc; /* Number of points allocate for x-coords on row. */ + int npts; /* Number of points assigned for x-coords on row. */ +} ROW; + +typedef struct shape{ + int ymin; /* Y-coord of top-most scanline in shape. */ + int ymax; /* Y-coord of bottom-most scanline in shape. */ + ROW **rows; /* List of row pointers comprising the shape. */ + int alloc; /* Number of rows allocated for shape. */ + int nrows; /* Number of rows assigned to shape. */ +} SHAPE; + +/* Parameters used by LFS for setting thresholds and */ +/* defining testing criterion. */ +typedef struct lfsparms{ + /* Image Controls */ + int pad_value; + int join_line_radius; + + /* Map Controls */ + int blocksize; /* Pixel dimension image block. */ + int windowsize; /* Pixel dimension window surrounding block. */ + int windowoffset; /* Offset in X & Y from block to window origin. */ + int num_directions; + double start_dir_angle; + int rmv_valid_nbr_min; + double dir_strength_min; + int dir_distance_max; + int smth_valid_nbr_min; + int vort_valid_nbr_min; + int highcurv_vorticity_min; + int highcurv_curvature_min; + int min_interpolate_nbrs; + int percentile_min_max; + int min_contrast_delta; + + /* DFT Controls */ + int num_dft_waves; + double powmax_min; + double pownorm_min; + double powmax_max; + int fork_interval; + double fork_pct_powmax; + double fork_pct_pownorm; + + /* Binarization Controls */ + int dirbin_grid_w; + int dirbin_grid_h; + int isobin_grid_dim; + int num_fill_holes; + + /* Minutiae Detection Controls */ + int max_minutia_delta; + double max_high_curve_theta; + int high_curve_half_contour; + int min_loop_len; + double min_loop_aspect_dist; + double min_loop_aspect_ratio; + + /* Minutiae Link Controls */ + int link_table_dim; + int max_link_dist; + int min_theta_dist; + int maxtrans; + double score_theta_norm; + double score_dist_norm; + double score_dist_weight; + double score_numerator; + + /* False Minutiae Removal Controls */ + int max_rmtest_dist; + int max_hook_len; + int max_half_loop; + int trans_dir_pix; + int small_loop_len; + int side_half_contour; + int inv_block_margin; + int rm_valid_nbr_min; + int max_overlap_dist; + int max_overlap_join_dist; + int malformation_steps_1; + int malformation_steps_2; + double min_malformation_ratio; + int max_malformation_dist; + int pores_trans_r; + int pores_perp_steps; + int pores_steps_fwd; + int pores_steps_bwd; + double pores_min_dist2; + double pores_max_ratio; + + /* Ridge Counting Controls */ + int max_nbrs; + int max_ridge_steps; +} LFSPARMS; + +/*************************************************************************/ +/* LFS CONSTANT DEFINITIONS */ +/*************************************************************************/ + +/***** IMAGE CONSTANTS *****/ + +#ifndef DEFAULT_PPI +#define DEFAULT_PPI 500 +#endif + +/* Intensity used to fill padded image area */ +#define PAD_VALUE 128 /* medium gray @ 8 bits */ + +/* Intensity used to draw on grayscale images */ +#define DRAW_PIXEL 255 /* white in 8 bits */ + +/* Definitions for 8-bit binary pixel intensities. */ +#define WHITE_PIXEL 255 +#define BLACK_PIXEL 0 + +/* Definitions for controlling join_miutia(). */ +/* Draw without opposite perimeter pixels. */ +#define NO_BOUNDARY 0 + +/* Draw with opposite perimeter pixels. */ +#define WITH_BOUNDARY 1 + +/* Radial width added to join line (not including the boundary pixels). */ +#define JOIN_LINE_RADIUS 1 + + +/***** MAP CONSTANTS *****/ + +/* Map value for not well-defined directions */ +#define INVALID_DIR -1 + +/* Map value assigned when the current block has no neighbors */ +/* with valid direction. */ +#define NO_VALID_NBRS -3 + +/* Map value designating a block is near a high-curvature */ +/* area such as a core or delta. */ +#define HIGH_CURVATURE -2 + +/* This specifies the pixel dimensions of each block in the IMAP */ +#define IMAP_BLOCKSIZE 24 + +/* Pixel dimension of image blocks. The following three constants work */ +/* together to define a system of 8X8 adjacent and non-overlapping */ +/* blocks that are assigned results from analyzing a larger 24X24 */ +/* window centered about each of the 8X8 blocks. */ +/* CAUTION: If MAP_BLOCKSIZE_V2 is changed, then the following will */ +/* likely need to be changed: MAP_WINDOWOFFSET_V2, */ +/* TRANS_DIR_PIX_V2, */ +/* INV_BLOCK_MARGIN_V2 */ +#define MAP_BLOCKSIZE_V2 8 + +/* Pixel dimension of window that surrounds the block. The result from */ +/* analyzing the content of the window is stored in the interior block. */ +#define MAP_WINDOWSIZE_V2 24 + +/* Pixel offset in X & Y from the origin of the block to the origin of */ +/* the surrounding window. */ +#define MAP_WINDOWOFFSET_V2 8 + +/* This is the number of integer directions to be used in semicircle. */ +/* CAUTION: If NUM_DIRECTIONS is changed, then the following will */ +/* likely need to be changed: HIGHCURV_VORTICITY_MIN, */ +/* HIGHCURV_CURVATURE_MIN, */ +/* FORK_INTERVAL */ +#define NUM_DIRECTIONS 16 + +/* This is the theta from which integer directions */ +/* are to begin. */ +#define START_DIR_ANGLE (double)(M_PI/2.0) /* 90 degrees */ + +/* Minimum number of valid neighbors required for a */ +/* valid block value to keep from being removed. */ +#define RMV_VALID_NBR_MIN 3 + +/* Minimum strength for a direction to be considered significant. */ +#define DIR_STRENGTH_MIN 0.2 + +/* Maximum distance allowable between valid block direction */ +/* and the average direction of its neighbors before the */ +/* direction is removed. */ +#define DIR_DISTANCE_MAX 3 + +/* Minimum number of valid neighbors required for an */ +/* INVALID block direction to receive its direction from */ +/* the average of its neighbors. */ +#define SMTH_VALID_NBR_MIN 7 + +/* Minimum number of valid neighbors required for a block */ +/* with an INVALID block direction to be measured for */ +/* vorticity. */ +#define VORT_VALID_NBR_MIN 7 + +/* The minimum vorticity value whereby an INVALID block */ +/* is determined to be high-curvature based on the directions */ +/* of it neighbors. */ +#define HIGHCURV_VORTICITY_MIN 5 + +/* The minimum curvature value whereby a VALID direction block is */ +/* determined to be high-curvature based on it value compared with */ +/* its neighbors' directions. */ +#define HIGHCURV_CURVATURE_MIN 5 + +/* Minimum number of neighbors with VALID direction for an INVALID */ +/* directon block to have its direction interpolated from those neighbors. */ +#define MIN_INTERPOLATE_NBRS 2 + +/* Definitions for creating a low contrast map. */ +/* Percentile cut off for choosing min and max pixel intensities */ +/* in a block. */ +#define PERCENTILE_MIN_MAX 10 + +/* The minimum delta between min and max percentile pixel intensities */ +/* in block for block NOT to be considered low contrast. (Note that */ +/* this value is in terms of 6-bit pixels.) */ +#define MIN_CONTRAST_DELTA 5 + + +/***** DFT CONSTANTS *****/ + +/* This specifies the number of DFT wave forms to be applied */ +#define NUM_DFT_WAVES 4 + +/* Minimum total DFT power for any given block */ +/* which is used to compute an average power. */ +/* By setting a non-zero minimum total,possible */ +/* division by zero is avoided. This value was */ +/* taken from HO39. */ +#define MIN_POWER_SUM 10.0 + +/* Thresholds and factors used by HO39. Renamed */ +/* here to give more meaning. */ + /* HO39 Name=Value */ +/* Minimum DFT power allowable in any one direction. */ +#define POWMAX_MIN 100000.0 /* thrhf=1e5f */ + +/* Minimum normalized power allowable in any one */ +/* direction. */ +#define POWNORM_MIN 3.8 /* disc=3.8f */ + +/* Maximum power allowable at the lowest frequency */ +/* DFT wave. */ +#define POWMAX_MAX 50000000.0 /* thrlf=5e7f */ + +/* Check for a fork at +- this number of units from */ +/* current integer direction. For example, */ +/* 2 dir ==> 11.25 X 2 degrees. */ +#define FORK_INTERVAL 2 + +/* Minimum DFT power allowable at fork angles is */ +/* FORK_PCT_POWMAX X block's max directional power. */ +#define FORK_PCT_POWMAX 0.7 + +/* Minimum normalized power allowable at fork angles */ +/* is FORK_PCT_POWNORM X POWNORM_MIN */ +#define FORK_PCT_POWNORM 0.75 + + +/***** BINRAIZATION CONSTANTS *****/ + +/* Directional binarization grid dimensions. */ +#define DIRBIN_GRID_W 7 +#define DIRBIN_GRID_H 9 + +/* The pixel dimension (square) of the grid used in isotropic */ +/* binarization. */ +#define ISOBIN_GRID_DIM 11 + +/* Number of passes through the resulting binary image where holes */ +/* of pixel length 1 in horizontal and vertical runs are filled. */ +#define NUM_FILL_HOLES 3 + + +/***** MINUTIAE DETECTION CONSTANTS *****/ + +/* The maximum pixel translation distance in X or Y within which */ +/* two potential minutia points are to be considered similar. */ +#define MAX_MINUTIA_DELTA 10 + +/* If the angle of a contour exceeds this angle, then it is NOT */ +/* to be considered to contain minutiae. */ +#define MAX_HIGH_CURVE_THETA (double)(M_PI/3.0) + +/* Half the length in pixels to be extracted for a high-curvature contour. */ +#define HIGH_CURVE_HALF_CONTOUR 14 + +/* Loop must be larger than this threshold (in pixels) to be considered */ +/* to contain minutiae. */ +#define MIN_LOOP_LEN 20 + +/* If loop's minimum distance half way across its contour is less than */ +/* this threshold, then loop is tested for minutiae. */ +#define MIN_LOOP_ASPECT_DIST 1.0 + +/* If ratio of loop's maximum/minimum distances half way across its */ +/* contour is >= to this threshold, then loop is tested for minutiae. */ +#define MIN_LOOP_ASPECT_RATIO 2.25 + +/* There are 10 unique feature patterns with ID = [0..9] , */ +/* so set LOOP ID to 10 (one more than max pattern ID). */ +#define LOOP_ID 10 + +/* Definitions for controlling the scanning of minutiae. */ +#define SCAN_HORIZONTAL 0 +#define SCAN_VERTICAL 1 +#define SCAN_CLOCKWISE 0 +#define SCAN_COUNTER_CLOCKWISE 1 + +/* The dimension of the chaincode loopkup matrix. */ +#define NBR8_DIM 3 + +/* Default minutiae reliability. */ +#define DEFAULT_RELIABILITY 0.99 + +/* Medium minutia reliability. */ +#define MEDIUM_RELIABILITY 0.50 + +/* High minutia reliability. */ +#define HIGH_RELIABILITY 0.99 + + +/***** MINUTIAE LINKING CONSTANTS *****/ + +/* Definitions for controlling the linking of minutiae. */ +/* Square dimensions of 2D table of potentially linked minutiae. */ +#define LINK_TABLE_DIM 20 + +/* Distance (in pixels) used to determine if the orthogonal distance */ +/* between the coordinates of 2 minutia points are sufficiently close */ +/* to be considered for linking. */ +#define MAX_LINK_DIST 20 + +/* Minimum distance (in pixels) between 2 minutia points that an angle */ +/* computed between the points may be considered reliable. */ +#define MIN_THETA_DIST 5 + +/* Maximum number of transitions along a contiguous pixel trajectory */ +/* between 2 minutia points for that trajectory to be considered "free" */ +/* of obstacles. */ +#define MAXTRANS 2 + +/* Parameters used to compute a link score between 2 minutiae. */ +#define SCORE_THETA_NORM 15.0 +#define SCORE_DIST_NORM 10.0 +#define SCORE_DIST_WEIGHT 4.0 +#define SCORE_NUMERATOR 32000.0 + + +/***** FALSE MINUTIAE REMOVAL CONSTANTS *****/ + +/* Definitions for removing hooks, islands, lakes, and overlaps. */ +/* Distance (in pixels) used to determine if the orthogonal distance */ +/* between the coordinates of 2 minutia points are sufficiently close */ +/* to be considered for removal. */ +#define MAX_RMTEST_DIST 8 + +#define MAX_RMTEST_DIST_V2 16 + +/* Length of pixel contours to be traced and analyzed for possible hooks. */ +#define MAX_HOOK_LEN 15 + +#define MAX_HOOK_LEN_V2 30 + +/* Half the maximum length of pixel contours to be traced and analyzed */ +/* for possible loops (islands/lakes). */ +#define MAX_HALF_LOOP 15 + +#define MAX_HALF_LOOP_V2 30 + +/* Definitions for removing minutiae that are sufficiently close and */ +/* point to a block with invalid ridge flow. */ +/* Distance (in pixels) in direction opposite the minutia to be */ +/* considered sufficiently close to an invalid block. */ +#define TRANS_DIR_PIX 6 + +#define TRANS_DIR_PIX_V2 4 + +/* Definitions for removing small holes (islands/lakes). */ +/* Maximum circumference (in pixels) of qualifying loops. */ +#define SMALL_LOOP_LEN 15 + +/* Definitions for removing or adusting side minutiae. */ +/* Half the number of pixels to be traced to form a complete contour. */ +#define SIDE_HALF_CONTOUR 7 + +/* Definitions for removing minutiae near invalid blocks. */ +/* Maximum orthogonal distance a minutia can be neighboring a block with */ +/* invalid ridge flow in order to be removed. */ +#define INV_BLOCK_MARGIN 6 + +#define INV_BLOCK_MARGIN_V2 4 + +/* Given a sufficiently close, neighboring invalid block, if that invalid */ +/* block has a total number of neighboring blocks with valid ridge flow */ +/* less than this threshold, then the minutia point is removed. */ +#define RM_VALID_NBR_MIN 7 + +/* Definitions for removing overlaps. */ +/* Maximum pixel distance between 2 points to be tested for overlapping */ +/* conditions. */ +#define MAX_OVERLAP_DIST 8 + +/* Maximum pixel distance between 2 points on opposite sides of an overlap */ +/* will be joined. */ +#define MAX_OVERLAP_JOIN_DIST 6 + +/* Definitions for removing "irregularly-shaped" minutiae. */ +/* Contour steps to be traced to 1st measuring point. */ +#define MALFORMATION_STEPS_1 10 +/* Contour steps to be traced to 2nd measuring point. */ +#define MALFORMATION_STEPS_2 20 +/* Minimum ratio of distances across feature at the two point to be */ +/* considered normal. */ +#define MIN_MALFORMATION_RATIO 2.0 +/* Maximum distance permitted across feature to be considered normal. */ +#define MAX_MALFORMATION_DIST 20 + +/* Definitions for removing minutiae on pores. */ +/* Translation distance (in pixels) from minutia point in opposite direction */ +/* in order to get off a valley edge and into the neighboring ridge. */ +#define PORES_TRANS_R 3 + +/* Number of steps (in pixels) to search for edge of current ridge. */ +#define PORES_PERP_STEPS 12 + +/* Number of pixels to be traced to find forward contour points. */ +#define PORES_STEPS_FWD 10 + +/* Number of pixels to be traced to find backward contour points. */ +#define PORES_STEPS_BWD 8 + +/* Minimum squared distance between points before being considered zero. */ +#define PORES_MIN_DIST2 0.5 + +/* Max ratio of computed distances between pairs of forward and backward */ +/* contour points to be considered a pore. */ +#define PORES_MAX_RATIO 2.25 + + +/***** RIDGE COUNTING CONSTANTS *****/ + +/* Definitions for detecting nearest neighbors and counting ridges. */ +/* Maximum number of nearest neighbors per minutia. */ +#define MAX_NBRS 5 + +/* Maximum number of contour steps taken to validate a ridge crossing. */ +#define MAX_RIDGE_STEPS 10 + +/*************************************************************************/ +/* QUALITY/RELIABILITY DEFINITIONS */ +/*************************************************************************/ +/* Quality map levels */ +#define QMAP_LEVELS 5 + +/* Neighborhood radius in millimeters computed from 11 pixles */ +/* scanned at 19.69 pixels/mm. */ +#define RADIUS_MM ((double)(11.0 / 19.69)) + +/* Ideal Standard Deviation of pixel values in a neighborhood. */ +#define IDEALSTDEV 64 +/* Ideal Mean of pixel values in a neighborhood. */ +#define IDEALMEAN 127 + +/* Look for neighbors this many blocks away. */ +#define NEIGHBOR_DELTA 2 + +/*************************************************************************/ +/* GENERAL DEFINITIONS */ +/*************************************************************************/ +#define LFS_VERSION_STR "NIST_LFS_VER2" + +/* This factor converts degrees to radians. */ +#ifndef DEG2RAD +#define DEG2RAD (double)(M_PI/180.0) +#endif + +#define NORTH 0 +#define SOUTH 4 +#define EAST 2 +#define WEST 6 + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef FOUND +#define FOUND TRUE +#endif +#ifndef NOT_FOUND +#define NOT_FOUND FALSE +#endif + +#define HOOK_FOUND 1 +#define LOOP_FOUND 1 +#define IGNORE 2 +#define LIST_FULL 3 +#define INCOMPLETE 3 + +/* Pixel value limit in 6-bit image. */ +#define IMG_6BIT_PIX_LIMIT 64 + +/* Maximum number (or reallocated chunks) of minutia to be detected */ +/* in an image. */ +#define MAX_MINUTIAE 1000 + +/* If both deltas in X and Y for a line of specified slope is less than */ +/* this threshold, then the angle for the line is set to 0 radians. */ +#define MIN_SLOPE_DELTA 0.5 + +/* Designates that rotated grid offsets should be relative */ +/* to the grid's center. */ +#define RELATIVE2CENTER 0 + +/* Designates that rotated grid offsets should be relative */ +/* to the grid's origin. */ +#define RELATIVE2ORIGIN 1 + +/* Truncate floating point precision by multiply, rounding, and then */ +/* dividing by this value. This enables consistant results across */ +/* different computer architectures. */ +#define TRUNC_SCALE 16384.0 + +/* Designates passed argument as undefined. */ +#define UNDEFINED -1 + +/* Dummy values for unused LFS control parameters. */ +#define UNUSED_INT 0 +#define UNUSED_DBL 0.0 + +/*************************************************************************/ +/* EXTERNAL FUNCTION DEFINITIONS */ +/*************************************************************************/ + +/* binar.c */ +extern int binarize(unsigned char **, int *, int *, + unsigned char *, const int, const int, + int *, const int, const int, + const ROTGRIDS *, const LFSPARMS *); +extern int binarize_V2(unsigned char **, int *, int *, + unsigned char *, const int, const int, + int *, const int, const int, + const ROTGRIDS *, const LFSPARMS *); +extern int binarize_image(unsigned char **, int *, int *, + unsigned char *, const int, const int, + const int *, const int, const int, const int, + const ROTGRIDS *, const int); +extern int binarize_image_V2(unsigned char **, int *, int *, + unsigned char *, const int, const int, + const int *, const int, const int, + const int, const ROTGRIDS *); +extern int dirbinarize(const unsigned char *, const int, const ROTGRIDS *); +extern int isobinarize(unsigned char *, const int, const int, const int); + +/* block.c */ +extern int block_offsets(int **, int *, int *, const int, const int, + const int, const int); +extern int low_contrast_block(const int, const int, + unsigned char *, const int, const int, const LFSPARMS *); +extern int find_valid_block(int *, int *, int *, int *, int *, + const int, const int, const int, const int, + const int, const int); +extern void set_margin_blocks(int *, const int, const int, const int); + +/* chaincod.c */ +extern int chain_code_loop(int **, int *, const int *, const int *, const int); +extern int is_chain_clockwise(const int *, const int, const int); + +/* contour.c */ +extern int allocate_contour(int **, int **, int **, int **, const int); +extern void free_contour(int *, int *, int *, int *); +extern int get_high_curvature_contour(int **, int **, int **, int **, int *, + const int, const int, const int, const int, const int, + unsigned char *, const int, const int); +extern int get_centered_contour(int **, int **, int **, int **, int *, + const int, const int, const int, const int, const int, + unsigned char *, const int, const int); +extern int trace_contour(int **, int **, int **, int **, int *, + const int, const int, const int, const int, const int, + const int, const int, const int, + unsigned char *, const int, const int); +extern int search_contour(const int, const int, const int, + const int, const int, const int, const int, const int, + unsigned char *, const int, const int); +extern int next_contour_pixel(int *, int *, int *, int *, + const int, const int, const int, const int, const int, + unsigned char *, const int, const int); +extern int start_scan_nbr(const int, const int, const int, const int); +extern int next_scan_nbr(const int, const int); +extern int min_contour_theta(int *, double *, const int, const int *, + const int *, const int); +extern void contour_limits(int *, int *, int *, int *, const int *, + const int *, const int); +extern void fix_edge_pixel_pair(int *, int *, int *, int *, + unsigned char *, const int, const int); + +/* detect.c */ +extern int lfs_detect_minutiae( MINUTIAE **, + int **, int **, int *, int *, + unsigned char **, int *, int *, + unsigned char *, const int, const int, + const LFSPARMS *); + +extern int lfs_detect_minutiae_V2(MINUTIAE **, + int **, int **, int **, int **, int *, int *, + unsigned char **, int *, int *, + unsigned char *, const int, const int, + const LFSPARMS *); + +/* dft.c */ +extern int dft_dir_powers(double **, unsigned char *, const int, + const int, const int, const DFTWAVES *, + const ROTGRIDS *); +extern void sum_rot_block_rows(int *, const unsigned char *, const int *, + const int); +extern void dft_power(double *, const int *, const DFTWAVE *, const int); +extern int dft_power_stats(int *, double *, int *, double *, double **, + const int, const int, const int); +extern void get_max_norm(double *, int *, double *, const double *, const int); +extern int sort_dft_waves(int *, const double *, const double *, const int); + +/* free.c */ +extern void free_dir2rad(DIR2RAD *); +extern void free_dftwaves(DFTWAVES *); +extern void free_rotgrids(ROTGRIDS *); +extern void free_dir_powers(double **, const int); + +/* getmin.c */ +extern int get_minutiae(MINUTIAE **, int **, int **, int **, + int **, int **, int *, int *, + unsigned char **, int *, int *, int *, + unsigned char *, const int, const int, + const int, const double, const LFSPARMS *); + +/* imgutil.c */ +extern void bits_6to8(unsigned char *, const int, const int); +extern void bits_8to6(unsigned char *, const int, const int); +extern void gray2bin(const int, const int, const int, + unsigned char *, const int, const int); +extern int pad_uchar_image(unsigned char **, int *, int *, + unsigned char *, const int, const int, const int, + const int); +extern void fill_holes(unsigned char *, const int, const int); +extern int free_path(const int, const int, const int, const int, + unsigned char *, const int, const int, const LFSPARMS *); +extern int search_in_direction(int *, int *, int *, int *, const int, + const int, const int, const double, const double, + const int, unsigned char *, const int, const int); + +/* init.c */ +extern int init_dir2rad(DIR2RAD **, const int); +extern int init_dftwaves(DFTWAVES **, const double *, const int, const int); +extern int get_max_padding(const int, const int, const int, const int); +extern int get_max_padding_V2(const int, const int, const int, const int); +extern int init_rotgrids(ROTGRIDS **, const int, const int, const int, + const double, const int, const int, const int, const int); +extern int alloc_dir_powers(double ***, const int, const int); +extern int alloc_power_stats(int **, double **, int **, double **, const int); + +/* isempty.c */ +extern int is_image_empty(int *, const int, const int); +extern int is_qmap_empty(int *, const int, const int); + + +/* line.c */ +extern int line_points(int **, int **, int *, + const int, const int, const int, const int); +extern int bresenham_line_points(int **, int **, int *, + const int, const int, const int, const int); + +/* link.c */ +extern int link_minutiae(MINUTIAE *, unsigned char *, const int, const int, + int *, const int, const int, const LFSPARMS *); +extern int create_link_table(int **, int **, int **, int *, int *, int *, + const int, const int, const MINUTIAE *, const int *, + int *, const int, const int, unsigned char *, + const int, const int, const LFSPARMS *); +extern int update_link_table(int *, int *, int *, int *, int *, int *, + const int, int *, int *, int *, int *, + const int, const int, const int); +extern int order_link_table(int *, int *, int *, const int, const int, + const int, const int, const MINUTIAE *, const int); +extern int process_link_table(const int *, const int *, const int *, + const int, const int, const int, const int, MINUTIAE *, + int *, unsigned char *, const int, const int, + const LFSPARMS *); +extern double link_score(const double, const double, const LFSPARMS *); + +/* loop.c */ +extern int get_loop_list(int **, MINUTIAE *, const int, unsigned char *, + const int, const int); +extern int on_loop(const MINUTIA *, const int, unsigned char *, const int, + const int); +extern int on_island_lake(int **, int **, int **, int **, int *, + const MINUTIA *, const MINUTIA *, const int, + unsigned char *, const int, const int); +extern int on_hook(const MINUTIA *, const MINUTIA *, const int, + unsigned char *, const int, const int); +extern int is_loop_clockwise(const int *, const int *, const int, const int); +extern int process_loop(MINUTIAE *, const int *, const int *, + const int *, const int *, const int, + unsigned char *, const int, const int, const LFSPARMS *); +extern int process_loop_V2(MINUTIAE *, const int *, const int *, + const int *, const int *, const int, + unsigned char *, const int, const int, + int *, const LFSPARMS *); +extern void get_loop_aspect(int *, int *, double *, int *, int *, double *, + const int *, const int *, const int); +extern int fill_loop(const int *, const int *, const int, + unsigned char *, const int, const int); +extern void fill_partial_row(const int, const int, const int, const int, + unsigned char *, const int, const int); +extern void flood_loop(const int *, const int *, const int, + unsigned char *, const int, const int); +extern void flood_fill4(const int, const int, const int, + unsigned char *, const int, const int); + +/* maps.c */ +extern int gen_image_maps(int **, int **, int **, int **, int *, int *, + unsigned char *, const int, const int, + const DIR2RAD *, const DFTWAVES *, + const ROTGRIDS *, const LFSPARMS *); +extern int gen_initial_maps(int **, int **, int **, + int *, const int, const int, + unsigned char *, const int, const int, + const DFTWAVES *, const ROTGRIDS *, const LFSPARMS *); +extern int interpolate_direction_map(int *, int *, const int, const int, + const LFSPARMS *); +extern int morph_TF_map(int *, const int, const int, const LFSPARMS *); +extern int pixelize_map(int **, const int, const int, + int *, const int, const int, const int); +extern void smooth_direction_map(int *, int *, const int, const int, + const DIR2RAD *, const LFSPARMS *); +extern int gen_high_curve_map(int **, int *, const int, const int, + const LFSPARMS *); +extern int gen_imap(int **, int *, int *, + unsigned char *, const int, const int, + const DIR2RAD *, const DFTWAVES *, const ROTGRIDS *, + const LFSPARMS *); +extern int gen_initial_imap(int **, int *, const int, const int, + unsigned char *, const int, const int, + const DFTWAVES *, const ROTGRIDS *, const LFSPARMS *); +extern int primary_dir_test(double **, const int *, const double *, + const int *, const double *, const int, + const LFSPARMS *); +extern int secondary_fork_test(double **, const int *, const double *, + const int *, const double *, const int, + const LFSPARMS *); +extern void remove_incon_dirs(int *, const int, const int, + const DIR2RAD *, const LFSPARMS *); +extern int test_top_edge(const int, const int, const int, const int, + int *, const int, const int, const DIR2RAD *, + const LFSPARMS *); +extern int test_right_edge(const int, const int, const int, const int, + int *, const int, const int, const DIR2RAD *, + const LFSPARMS *); +extern int test_bottom_edge(const int, const int, const int, const int, + int *, const int, const int, const DIR2RAD *, + const LFSPARMS *); +extern int test_left_edge(const int, const int, const int, const int, + int *, const int, const int, const DIR2RAD *, + const LFSPARMS *); +extern int remove_dir(int *, const int, const int, const int, const int, + const DIR2RAD *, const LFSPARMS *); +extern void average_8nbr_dir(int *, double *, int *, int *, const int, + const int, const int, const int, const DIR2RAD *); +extern int num_valid_8nbrs(int *, const int, const int, const int, const int); +extern void smooth_imap(int *, const int, const int, const DIR2RAD *, + const LFSPARMS *); +extern int gen_nmap(int **, int *, const int, const int, const LFSPARMS *); +extern int vorticity(int *, const int, const int, const int, const int, + const int); +extern void accum_nbr_vorticity(int *, const int, const int, const int); +extern int curvature(int *, const int, const int, const int, const int, + const int); + +/* matchpat.c */ +extern int match_1st_pair(unsigned char, unsigned char, int *, int *); +extern int match_2nd_pair(unsigned char, unsigned char, int *, int *); +extern int match_3rd_pair(unsigned char, unsigned char, int *, int *); +extern void skip_repeated_horizontal_pair(int *, const int, + unsigned char **, unsigned char **, const int, const int); +extern void skip_repeated_vertical_pair(int *, const int, + unsigned char **, unsigned char **, const int, const int); + +/* minutia.c */ +extern int alloc_minutiae(MINUTIAE **, const int); +extern int realloc_minutiae(MINUTIAE *, const int); +extern int detect_minutiae(MINUTIAE *, unsigned char *, const int, const int, + const int *, const int *, const int, const int, + const LFSPARMS *); +extern int detect_minutiae_V2(MINUTIAE *, + unsigned char *, const int, const int, + int *, int *, int *, const int, const int, + const LFSPARMS *); +extern int update_minutiae(MINUTIAE *, MINUTIA *, unsigned char *, + const int, const int, const LFSPARMS *); +extern int update_minutiae_V2(MINUTIAE *, MINUTIA *, const int, const int, + unsigned char *, const int, const int, + const LFSPARMS *); +extern int sort_minutiae(MINUTIAE *, const int, const int); +extern int sort_minutiae_y_x(MINUTIAE *, const int, const int); +extern int sort_minutiae_x_y(MINUTIAE *, const int, const int); +extern int rm_dup_minutiae(MINUTIAE *); +extern void dump_minutiae(FILE *, const MINUTIAE *); +extern void dump_minutiae_pts(FILE *, const MINUTIAE *); +extern void dump_reliable_minutiae_pts(FILE *, const MINUTIAE *, const double); +extern int create_minutia(MINUTIA **, const int, const int, + const int, const int, const int, const double, + const int, const int, const int); +extern void free_minutiae(MINUTIAE *); +extern void free_minutia(MINUTIA *); +extern int remove_minutia(const int, MINUTIAE *); +extern int join_minutia(const MINUTIA *, const MINUTIA *, unsigned char *, + const int, const int, const int, const int); +extern int minutia_type(const int); +extern int is_minutia_appearing(const int, const int, const int, const int); +extern int choose_scan_direction(const int, const int); +int scan4minutiae(MINUTIAE *, unsigned char *, const int, const int, + const int *, const int *, const int, const int, + const int, const int, const int, const int, + const int, const int, const int, const LFSPARMS *); +extern int scan4minutiae_horizontally(MINUTIAE *, unsigned char *, + const int, const int, const int, const int, + const int, const int, const int, const int, + const LFSPARMS *); +extern int scan4minutiae_horizontally_V2(MINUTIAE *, + unsigned char *, const int, const int, + int *, int *, int *, + const LFSPARMS *); +extern int scan4minutiae_vertically(MINUTIAE *, unsigned char *, + const int, const int, const int, const int, + const int, const int, const int, const int, + const LFSPARMS *); +extern int rescan4minutiae_horizontally(MINUTIAE *, unsigned char *bdata, + const int, const int, const int *, const int *, + const int, const int, const int, const int, + const int, const int, const int, const int, + const LFSPARMS *); +extern int scan4minutiae_vertically_V2(MINUTIAE *, + unsigned char *, const int, const int, + int *, int *, int *, const LFSPARMS *); +extern int rescan4minutiae_vertically(MINUTIAE *, unsigned char *, + const int, const int, const int *, const int *, + const int, const int, const int, const int, + const int, const int, const int, const int, + const LFSPARMS *); +extern int rescan_partial_horizontally(const int, MINUTIAE *, + unsigned char *, const int, const int, + const int *, const int *, + const int, const int, const int, const int, + const int, const int, const int, const int, + const LFSPARMS *); +extern int rescan_partial_vertically(const int, MINUTIAE *, + unsigned char *, const int, const int, + const int *, const int *, + const int, const int, const int, const int, + const int, const int, const int, const int, + const LFSPARMS *); +extern int get_nbr_block_index(int *, const int, const int, const int, + const int, const int); +extern int adjust_horizontal_rescan(const int, int *, int *, int *, int *, + const int, const int, const int, const int, const int); +extern int adjust_vertical_rescan(const int, int *, int *, int *, int *, + const int, const int, const int, const int, const int); +extern int process_horizontal_scan_minutia(MINUTIAE *, const int, const int, + const int, const int, + unsigned char *, const int, const int, + const int, const int, const LFSPARMS *); +extern int process_horizontal_scan_minutia_V2(MINUTIAE *, + const int, const int, const int, const int, + unsigned char *, const int, const int, + int *, int *, int *, const LFSPARMS *); +extern int process_vertical_scan_minutia(MINUTIAE *, const int, const int, + const int, const int, + unsigned char *, const int, const int, + const int, const int, const LFSPARMS *); +extern int process_vertical_scan_minutia_V2(MINUTIAE *, const int, const int, + const int, const int, + unsigned char *, const int, const int, + int *, int *, int *, const LFSPARMS *); +extern int update_minutiae_V2(MINUTIAE *, MINUTIA *, const int, const int, + unsigned char *, const int, const int, + const LFSPARMS *); +extern int adjust_high_curvature_minutia(int *, int *, int *, int *, int *, + const int, const int, const int, const int, + unsigned char *, const int, const int, + MINUTIAE *, const LFSPARMS *); +extern int adjust_high_curvature_minutia_V2(int *, int *, int *, + int *, int *, const int, const int, + const int, const int, + unsigned char *, const int, const int, + int *, MINUTIAE *, const LFSPARMS *); +extern int get_low_curvature_direction(const int, const int, const int, + const int); + +/* quality.c */ +extern int gen_quality_map(int **, int *, int *, int *, int *, + const int, const int); +extern int combined_minutia_quality(MINUTIAE *, int *, const int, const int, + const int, unsigned char *, const int, const int, + const int, const double); +double grayscale_reliability(MINUTIA *, unsigned char *, + const int, const int, const int); +extern void get_neighborhood_stats(double *, double *, MINUTIA *, + unsigned char *, const int, const int, const int); +extern int reliability_fr_quality_map(MINUTIAE *, int *, const int, + const int, const int, const int, const int); + +/* remove.c */ +extern int remove_false_minutia(MINUTIAE *, + unsigned char *, const int, const int, + int *, const int, const int, const LFSPARMS *); +extern int remove_false_minutia_V2(MINUTIAE *, + unsigned char *, const int, const int, + int *, int *, int *, const int, const int, + const LFSPARMS *); +extern int remove_holes(MINUTIAE *, unsigned char *, const int, const int, + const LFSPARMS *); +extern int remove_hooks(MINUTIAE *, + unsigned char *, const int, const int, const LFSPARMS *); +extern int remove_hooks_islands_lakes_overlaps(MINUTIAE *, unsigned char *, + const int, const int, const LFSPARMS *); +extern int remove_islands_and_lakes(MINUTIAE *, + unsigned char *, const int, const int, const LFSPARMS *); +extern int remove_malformations(MINUTIAE *, + unsigned char *, const int, const int, + int *, const int, const int, const LFSPARMS *); +extern int remove_near_invblock(MINUTIAE *, int *, const int, const int, + const LFSPARMS *); +extern int remove_near_invblock_V2(MINUTIAE *, int *, + const int, const int, const LFSPARMS *); +extern int remove_pointing_invblock(MINUTIAE *, int *, const int, const int, + const LFSPARMS *); +extern int remove_pointing_invblock_V2(MINUTIAE *, + int *, const int, const int, const LFSPARMS *); +extern int remove_overlaps(MINUTIAE *, + unsigned char *, const int, const int, const LFSPARMS *); +extern int remove_pores(MINUTIAE *, + unsigned char *, const int, const int, + int *, const int, const int, const LFSPARMS *); +extern int remove_pores_V2(MINUTIAE *, + unsigned char *, const int, const int, + int *, int *, int *, const int, const int, + const LFSPARMS *); +extern int remove_or_adjust_side_minutiae(MINUTIAE *, unsigned char *, + const int, const int, const LFSPARMS *); +extern int remove_or_adjust_side_minutiae_V2(MINUTIAE *, + unsigned char *, const int, const int, + int *, const int, const int, const LFSPARMS *); + +/* results.c */ +extern int write_text_results(char *, const int, const int, const int, + const MINUTIAE *, int *, int *, int *, int *, int *, + const int, const int); +extern int write_minutiae_XYTQ(char *ofile, const int, + const MINUTIAE *, const int, const int); +extern void dump_map(FILE *, int *, const int, const int); +extern int drawimap(int *, const int, const int, unsigned char *, + const int, const int, const ROTGRIDS *, const int); +extern void drawimap2(int *, const int *, const int, const int, + unsigned char *, const int, const int, + const double, const int, const int); +extern void drawblocks(const int *, const int, const int, + unsigned char *, const int, const int, const int ); +extern int drawrotgrid(const ROTGRIDS *, const int, unsigned char *, + const int, const int, const int, const int); +extern void dump_link_table(FILE *, const int *, const int *, const int *, + const int, const int, const int, const MINUTIAE *); + +/* ridges.c */ +extern int count_minutiae_ridges(MINUTIAE *, + unsigned char *, const int, const int, + const LFSPARMS *); +extern int count_minutia_ridges(const int, MINUTIAE *, + unsigned char *, const int, const int, + const LFSPARMS *); +extern int find_neighbors(int **, int *, const int, const int, MINUTIAE *); +extern int update_nbr_dists(int *, double *, int *, const int, + const int, const int, MINUTIAE *); +extern int insert_neighbor(const int, const int, const double, + int *, double *, int *, const int); +extern int sort_neighbors(int *, const int, const int, MINUTIAE *); +extern int ridge_count(const int, const int, MINUTIAE *, + unsigned char *, const int, const int, const LFSPARMS *); +extern int find_transition(int *, const int, const int, + const int *, const int *, const int, + unsigned char *, const int, const int); +extern int validate_ridge_crossing(const int, const int, + const int *, const int *, const int, + unsigned char *, const int, const int, const int); + +/* shape.c */ +extern int alloc_shape(SHAPE **, const int, const int, const int, const int); +extern void free_shape(SHAPE *); +extern void dump_shape(FILE *, const SHAPE *); +extern int shape_from_contour(SHAPE **, const int *, const int *, const int); +extern void sort_row_on_x(ROW *); + +/* sort.c */ +extern int sort_indices_int_inc(int **, int *, const int); +extern int sort_indices_double_inc(int **, double *, const int); +extern void bubble_sort_int_inc_2(int *, int *, const int); +extern void bubble_sort_double_inc_2(double *, int *, const int); +extern void bubble_sort_double_dec_2(double *, int *, const int); +extern void bubble_sort_int_inc(int *, const int); + +/* util.c */ +extern int maxv(const int *, const int); +extern int minv(const int *, const int); +extern int minmaxs(int **, int **, int **, int *, int *, + const int *, const int); +extern double distance(const int, const int, const int, const int); +extern double squared_distance(const int, const int, const int, const int); +extern int in_int_list(const int, const int *, const int); +extern int remove_from_int_list(const int, int *, const int); +extern int find_incr_position_dbl(const double, double *, const int); +extern double angle2line(const int, const int, const int, const int); +extern int line2direction(const int, const int, const int, const int, + const int); +extern int closest_dir_dist(const int, const int, const int); + +/* xytreps.c */ +extern void lfs2nist_minutia_XYT(int *, int *, int *, + const MINUTIA *, const int, const int); +extern void lfs2m1_minutia_XYT(int *, int *, int *, const MINUTIA *); + + +/*************************************************************************/ +/* EXTERNAL GLOBAL VARIABLE DEFINITIONS */ +/*************************************************************************/ +extern double dft_coefs[]; +extern LFSPARMS lfsparms; +extern LFSPARMS lfsparms_V2; +extern int nbr8_dx[]; +extern int nbr8_dy[]; +extern int chaincodes_nbr8[]; +extern FEATURE_PATTERN feature_patterns[]; + +#endif diff --git a/libfprint/nbis/include/log.h b/libfprint/nbis/include/log.h new file mode 100644 index 0000000..36967f1 --- /dev/null +++ b/libfprint/nbis/include/log.h @@ -0,0 +1,48 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef _LOG_H +#define _LOG_H + +/* Definitions and references to support log report files. */ +/* UPDATED: 03/16/2005 by MDG */ + +#include +#include +#include + +#ifdef LOG_REPORT +/* Uncomment the following line to enable logging. */ +#define LOG_FILE "log.txt" +#endif + +extern FILE *logfp; +extern int avrdir; +extern float dir_strength; +extern int nvalid; + +extern int open_logfile(void); +extern int close_logfile(void); +extern void print2log(char *, ...); + +#endif diff --git a/libfprint/nbis/include/morph.h b/libfprint/nbis/include/morph.h new file mode 100644 index 0000000..782d331 --- /dev/null +++ b/libfprint/nbis/include/morph.h @@ -0,0 +1,39 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef __MORPH_H__ +#define __MORPH_H__ + +/* Modified 10/26/1999 by MDG to avoid indisciminate erosion of pixels */ +/* along the edge of the binary image. */ + +extern void erode_charimage_2(unsigned char *, unsigned char *, + const int, const int); +extern void dilate_charimage_2(unsigned char *, unsigned char *, + const int, const int); +extern char get_south8_2(char *, const int, const int, const int, const int); +extern char get_north8_2(char *, const int, const int, const int); +extern char get_east8_2(char *, const int, const int, const int); +extern char get_west8_2(char *, const int, const int); + +#endif /* !__MORPH_H__ */ diff --git a/libfprint/nbis/include/mytime.h b/libfprint/nbis/include/mytime.h new file mode 100644 index 0000000..4abb1ac --- /dev/null +++ b/libfprint/nbis/include/mytime.h @@ -0,0 +1,80 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef _MYTIME_H +#define _MYTIME_H + +/* this file needed to support timer and ticks */ +/* UPDATED: 03/16/2005 by MDG */ + +#ifdef TIMER +#include +#include +#endif + +#ifdef TIMER +#define set_timer(_timer_); \ + { \ + _timer_ = (unsigned long)ticks(); +#else +#define set_timer(_timer_); +#endif + +#ifdef TIMER +#define time_accum(_timer_, _var_); \ + _var_ += ((unsigned long)ticks() - _timer_)/(float)ticksPerSec(); \ + } +#else +#define time_accum(_timer_, _var_); +#endif + +#ifdef TIMER +#define print_time(_fp_, _fmt_, _var_); \ + fprintf(_fp_, _fmt_, _var_); +#else +#define print_time(_fp_, _fmt_, _var_); +#endif + +extern unsigned long ticks(void); +extern int ticksPerSec(void); + +extern unsigned long total_timer; +extern float total_time; + +extern unsigned long imap_timer; +extern float imap_time; + +extern unsigned long bin_timer; +extern float bin_time; + +extern unsigned long minutia_timer; +extern float minutia_time; + +extern unsigned long rm_minutia_timer; +extern float rm_minutia_time; + +extern unsigned long ridge_count_timer; +extern float ridge_count_time; + +#endif + diff --git a/libfprint/nbis/include/sunrast.h b/libfprint/nbis/include/sunrast.h new file mode 100644 index 0000000..3d7deea --- /dev/null +++ b/libfprint/nbis/include/sunrast.h @@ -0,0 +1,76 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +#ifndef _SUNRAST_H +#define _SUNRAST_H + +/************************************************************/ +/* File Name: Sunrast.h */ +/* Package: Sun Rasterfile I/O */ +/* Author: Michael D. Garris */ +/* Date: 8/19/99 */ +/* Updated: 03/16/2005 by MDG */ +/* */ +/************************************************************/ + +/* Contains header information related to Sun Rasterfile images. */ + +typedef struct sunrasterhdr { + int magic; /* magic number */ + int width; /* width (in pixels) of image */ + int height; /* height (in pixels) of image */ + int depth; /* depth (1, 8, or 24 bits) of pixel */ + int raslength; /* length (in bytes) of image */ + int rastype; /* type of file; see SUN_* below */ + int maptype; /* type of colormap; see MAP_* below */ + int maplength; /* length (bytes) of following map */ + /* color map follows for maplength bytes, followed by image */ +} SUNHEAD; + +#define SUN_MAGIC 0x59a66a95 + + /* Sun supported ras_type's */ +#define SUN_STANDARD 1 /* Raw pixrect image in 68000 byte order */ +#define SUN_RUN_LENGTH 2 /* Run-length compression of bytes */ +#define SUN_FORMAT_RGB 3 /* XRGB or RGB instead of XBGR or BGR */ +#define SUN_FORMAT_TIFF 4 /* tiff <-> standard rasterfile */ +#define SUN_FORMAT_IFF 5 /* iff (TAAC format) <-> standard rasterfile */ + + /* Sun supported maptype's */ +#define MAP_RAW 2 +#define MAP_NONE 0 /* maplength is expected to be 0 */ +#define MAP_EQUAL_RGB 1 /* red[maplength/3],green[],blue[] */ + +/* + * NOTES: + * Each line of a bitmap image should be rounded out to a multiple + * of 16 bits. + */ + +/* sunrast.c */ +extern int ReadSunRaster(const char *, SUNHEAD **, unsigned char **, int *, + unsigned char **, int *, int *, int *, int *); +extern int WriteSunRaster(char *, unsigned char *, const int, const int, + const int); + +#endif diff --git a/libfprint/nbis/mindtct/binar.c b/libfprint/nbis/mindtct/binar.c new file mode 100644 index 0000000..2c4355c --- /dev/null +++ b/libfprint/nbis/mindtct/binar.c @@ -0,0 +1,455 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: BINAR.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for binarizing a grayscale image based + on an arbitrarily-sized image and its precomputed direcitonal ridge + flow (IMAP) as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + binarize() + binarize_V2() + binarize_image() + binarize_image_V2() + dirbinarize() + isobinarize() + +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: binarize - Takes a padded grayscale input image and its associated ridge +#cat: direction flow NMAP and produces a binarized version of the +#cat: image. It then fills horizontal and vertical "holes" in the +#cat: binary image results. + + Input: + pdata - padded input grayscale image + pw - padded width (in pixels) of input image + ph - padded height (in pixels) of input image + nmap - 2-D vector of IMAP directions and other codes + mw - width (in blocks) of the NMAP + mh - height (in blocks) of the NMAP + dirbingrids - set of rotated grid offsets used for directional + binarization + lfsparms - parameters and thresholds for controlling LFS + Output: + optr - points to created (unpadded) binary image + ow - width of binary image + oh - height of binary image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int binarize(unsigned char **optr, int *ow, int *oh, + unsigned char *pdata, const int pw, const int ph, + int *nmap, const int mw, const int mh, + const ROTGRIDS *dirbingrids, const LFSPARMS *lfsparms) +{ + unsigned char *bdata; + int i, bw, bh, ret; /* return code */ + + /* 1. Binarize the padded input image using NMAP information. */ + if((ret = binarize_image(&bdata, &bw, &bh, pdata, pw, ph, + nmap, mw, mh, lfsparms->blocksize, + dirbingrids, lfsparms->isobin_grid_dim))){ + return(ret); + } + + /* 2. Fill black and white holes in binary image. */ + /* LFS scans the binary image, filling holes, 3 times. */ + for(i = 0; i < lfsparms->num_fill_holes; i++) + fill_holes(bdata, bw, bh); + + /* Return binarized input image. */ + *optr = bdata; + *ow = bw; + *oh = bh; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: binarize_V2 - Takes a padded grayscale input image and its associated +#cat: Direction Map and produces a binarized version of the +#cat: image. It then fills horizontal and vertical "holes" in +#cat: the binary image results. Note that the input image must +#cat: be padded sufficiently to contain in memory rotated +#cat: directional binarization grids applied to pixels along the +#cat: perimeter of the input image. + + Input: + pdata - padded input grayscale image + pw - padded width (in pixels) of input image + ph - padded height (in pixels) of input image + direction_map - 2-D vector of discrete ridge flow directions + mw - width (in blocks) of the map + mh - height (in blocks) of the map + dirbingrids - set of rotated grid offsets used for directional + binarization + lfsparms - parameters and thresholds for controlling LFS + Output: + odata - points to created (unpadded) binary image + ow - width of binary image + oh - height of binary image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int binarize_V2(unsigned char **odata, int *ow, int *oh, + unsigned char *pdata, const int pw, const int ph, + int *direction_map, const int mw, const int mh, + const ROTGRIDS *dirbingrids, const LFSPARMS *lfsparms) +{ + unsigned char *bdata; + int i, bw, bh, ret; /* return code */ + + /* 1. Binarize the padded input image using directional block info. */ + if((ret = binarize_image_V2(&bdata, &bw, &bh, pdata, pw, ph, + direction_map, mw, mh, + lfsparms->blocksize, dirbingrids))){ + return(ret); + } + + /* 2. Fill black and white holes in binary image. */ + /* LFS scans the binary image, filling holes, 3 times. */ + for(i = 0; i < lfsparms->num_fill_holes; i++) + fill_holes(bdata, bw, bh); + + /* Return binarized input image. */ + *odata = bdata; + *ow = bw; + *oh = bh; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: binarize_image - Takes a grayscale input image and its associated +#cat: NMAP and generates a binarized version of the image. + + Input: + pdata - padded input grayscale image + pw - padded width (in pixels) of input image + ph - padded height (in pixels) of input image + nmap - 2-D vector of IMAP directions and other codes + mw - width (in blocks) of the NMAP + mh - height (in blocks) of the NMAP + imap_blocksize - dimension (in pixels) of each NMAP block + dirbingrids - set of rotated grid offsets used for directional + binarization + isobin_grid_dim - dimension (in pixels) of grid used for isotropic + binarization + Output: + optr - points to binary image results + ow - points to binary image width + oh - points to binary image height + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int binarize_image(unsigned char **optr, int *ow, int *oh, + unsigned char *pdata, const int pw, const int ph, + const int *nmap, const int mw, const int mh, + const int imap_blocksize, const ROTGRIDS *dirbingrids, + const int isobin_grid_dim) +{ + int ix, iy, bw, bh, bx, by, nmapval; + unsigned char *bdata, *bptr; + unsigned char *pptr, *spptr; + + /* Compute dimensions of "unpadded" binary image results. */ + bw = pw - (dirbingrids->pad<<1); + bh = ph - (dirbingrids->pad<<1); + + bdata = (unsigned char *)malloc(bw*bh*sizeof(unsigned char)); + if(bdata == (unsigned char *)NULL){ + fprintf(stderr, "ERROR : binarize_image : malloc : bdata\n"); + return(-110); + } + + bptr = bdata; + spptr = pdata + (dirbingrids->pad * pw) + dirbingrids->pad; + for(iy = 0; iy < bh; iy++){ + /* Set pixel pointer to start of next row in grid. */ + pptr = spptr; + for(ix = 0; ix < bw; ix++){ + /* Compute which block the current pixel is in. */ + bx = (int)(ix/imap_blocksize); + by = (int)(iy/imap_blocksize); + /* Get corresponding value in NMAP */ + nmapval = *(nmap + (by*mw) + bx); + /* If current block has no neighboring blocks with */ + /* VALID directions ... */ + if(nmapval == NO_VALID_NBRS) + /* Set binary pixel to white (255). */ + *bptr = WHITE_PIXEL; + /* Otherwise, if block's NMAP has a valid direction ... */ + else if(nmapval >= 0) + /* Use directional binarization based on NMAP direction. */ + *bptr = dirbinarize(pptr, nmapval, dirbingrids); + else + /* Otherwise, the block's NMAP is either INVALID or */ + /* HIGH-CURVATURE, so use isotropic binarization. */ + *bptr = isobinarize(pptr, pw, ph, isobin_grid_dim); + /* Bump input and output pixel pointers. */ + pptr++; + bptr++; + } + /* Bump pointer to the next row in padded input image. */ + spptr += pw; + } + + *optr = bdata; + *ow = bw; + *oh = bh; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: binarize_image_V2 - Takes a grayscale input image and its associated +#cat: Direction Map and generates a binarized version of the +#cat: image. Note that there is no "Isotropic" binarization +#cat: used in this version. + + Input: + pdata - padded input grayscale image + pw - padded width (in pixels) of input image + ph - padded height (in pixels) of input image + direction_map - 2-D vector of discrete ridge flow directions + mw - width (in blocks) of the map + mh - height (in blocks) of the map + blocksize - dimension (in pixels) of each NMAP block + dirbingrids - set of rotated grid offsets used for directional + binarization + Output: + odata - points to binary image results + ow - points to binary image width + oh - points to binary image height + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int binarize_image_V2(unsigned char **odata, int *ow, int *oh, + unsigned char *pdata, const int pw, const int ph, + const int *direction_map, const int mw, const int mh, + const int blocksize, const ROTGRIDS *dirbingrids) +{ + int ix, iy, bw, bh, bx, by, mapval; + unsigned char *bdata, *bptr; + unsigned char *pptr, *spptr; + + /* Compute dimensions of "unpadded" binary image results. */ + bw = pw - (dirbingrids->pad<<1); + bh = ph - (dirbingrids->pad<<1); + + bdata = (unsigned char *)malloc(bw*bh*sizeof(unsigned char)); + if(bdata == (unsigned char *)NULL){ + fprintf(stderr, "ERROR : binarize_image_V2 : malloc : bdata\n"); + return(-600); + } + + bptr = bdata; + spptr = pdata + (dirbingrids->pad * pw) + dirbingrids->pad; + for(iy = 0; iy < bh; iy++){ + /* Set pixel pointer to start of next row in grid. */ + pptr = spptr; + for(ix = 0; ix < bw; ix++){ + + /* Compute which block the current pixel is in. */ + bx = (int)(ix/blocksize); + by = (int)(iy/blocksize); + /* Get corresponding value in Direction Map. */ + mapval = *(direction_map + (by*mw) + bx); + /* If current block has has INVALID direction ... */ + if(mapval == INVALID_DIR) + /* Set binary pixel to white (255). */ + *bptr = WHITE_PIXEL; + /* Otherwise, if block has a valid direction ... */ + else /*if(mapval >= 0)*/ + /* Use directional binarization based on block's direction. */ + *bptr = dirbinarize(pptr, mapval, dirbingrids); + + /* Bump input and output pixel pointers. */ + pptr++; + bptr++; + } + /* Bump pointer to the next row in padded input image. */ + spptr += pw; + } + + *odata = bdata; + *ow = bw; + *oh = bh; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: dirbinarize - Determines the binary value of a grayscale pixel based +#cat: on a VALID IMAP ridge flow direction. + + CAUTION: The image to which the input pixel points must be appropriately + padded to account for the radius of the rotated grid. Otherwise, + this routine may access "unkown" memory. + + Input: + pptr - pointer to current grayscale pixel + idir - IMAP integer direction associated with the block the + current is in + dirbingrids - set of precomputed rotated grid offsets + Return Code: + BLACK_PIXEL - pixel intensity for BLACK + WHITE_PIXEL - pixel intensity of WHITE +**************************************************************************/ +int dirbinarize(const unsigned char *pptr, const int idir, + const ROTGRIDS *dirbingrids) +{ + int gx, gy, gi, cy; + int rsum, gsum, csum = 0; + int *grid; + double dcy; + + /* Assign nickname pointer. */ + grid = dirbingrids->grids[idir]; + /* Calculate center (0-oriented) row in grid. */ + dcy = (dirbingrids->grid_h-1)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + dcy = trunc_dbl_precision(dcy, TRUNC_SCALE); + cy = sround(dcy); + /* Initialize grid's pixel offset index to zero. */ + gi = 0; + /* Initialize grid's pixel accumulator to zero */ + gsum = 0; + + /* Foreach row in grid ... */ + for(gy = 0; gy < dirbingrids->grid_h; gy++){ + /* Initialize row pixel sum to zero. */ + rsum = 0; + /* Foreach column in grid ... */ + for(gx = 0; gx < dirbingrids->grid_w; gx++){ + /* Accumulate next pixel along rotated row in grid. */ + rsum += *(pptr+grid[gi]); + /* Bump grid's pixel offset index. */ + gi++; + } + /* Accumulate row sum into grid pixel sum. */ + gsum += rsum; + /* If current row is center row, then save row sum separately. */ + if(gy == cy) + csum = rsum; + } + + /* If the center row sum treated as an average is less than the */ + /* total pixel sum in the rotated grid ... */ + if((csum * dirbingrids->grid_h) < gsum) + /* Set the binary pixel to BLACK. */ + return(BLACK_PIXEL); + else + /* Otherwise set the binary pixel to WHITE. */ + return(WHITE_PIXEL); +} + +/************************************************************************* +************************************************************************** +#cat: isobinarize - Determines the binary value of a grayscale pixel based +#cat: on comparing the grayscale value with a surrounding +#cat: neighborhood grid of pixels. If the current pixel (treated +#cat: as an average) is less than the sum of the pixels in +#cat: the neighborhood, then the binary value is set to BLACK, +#cat: otherwise it is set to WHITE. This binarization technique +#cat: is used when there is no VALID IMAP direction for the +#cat: block in which the current pixel resides. + + CAUTION: The image to which the input pixel points must be appropriately + padded to account for the radius of the neighborhood. Otherwise, + this routine may access "unkown" memory. + + Input: + pptr - pointer to curent grayscale pixel + pw - padded width (in pixels) of the grayscale image + ph - padded height (in pixels) of the grayscale image + isobin_grid_dim - dimension (in pixels) of the neighborhood + Return Code: + BLACK_PIXEL - pixel intensity for BLACK + WHITE_PIXEL - pixel intensity of WHITE +**************************************************************************/ +int isobinarize(unsigned char *pptr, const int pw, const int ph, + const int isobin_grid_dim) +{ + unsigned char *sptr, *cptr; + int px, py; + int radius; + int bsum; + double drad; + + /* Initialize grid pixel sum to zero. */ + bsum = 0; + /* Compute radius from current pixel based on isobin_grid_dim. */ + drad = (isobin_grid_dim - 1)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + drad = trunc_dbl_precision(drad, TRUNC_SCALE); + radius = sround(drad); + /* Set pointer to origin of grid centered on the current pixel. */ + sptr = pptr - (radius*pw) - radius; + + /* For each row in the grid ... */ + for(py = 0; py < isobin_grid_dim; py++){ + /* Set pixel pointer to start of next row in grid. */ + cptr = sptr; + /* For each column in the grid ... */ + for(px = 0; px < isobin_grid_dim; px++){ + /* Accumulate next pixel in the grid. */ + bsum += *cptr; + /* Bump pixel pointer. */ + cptr++; + } + /* Bump to the start of the next row in the grid. */ + sptr += pw; + } + + /* If current (center) pixel when treated as an average for the */ + /* entire grid is less than the total pixel sum of the grid ... */ + if((*pptr * isobin_grid_dim * isobin_grid_dim) < bsum) + /* Set the binary pixel to BLACK. */ + return(BLACK_PIXEL); + else + /* Otherwise, set the binary pixel to WHITE. */ + return(WHITE_PIXEL); +} diff --git a/libfprint/nbis/mindtct/block.c b/libfprint/nbis/mindtct/block.c new file mode 100644 index 0000000..7c7a656 --- /dev/null +++ b/libfprint/nbis/mindtct/block.c @@ -0,0 +1,377 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: BLOCK.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for partitioning arbitrarily- + sized images into equally-sized blocks as part of the NIST + Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + block_offsets() + low_contrast_block() + find_valid_block() + set_margin_blocks() + +***********************************************************************/ + +#include +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: block_offsets - Divides an image into mw X mh equally sized blocks, +#cat: returning a list of offsets to the top left corner of each block. +#cat: For images that are even multiples of BLOCKSIZE, blocks do not +#cat: not overlap and are immediately adjacent to each other. For image +#cat: that are NOT even multiples of BLOCKSIZE, blocks continue to be +#cat: non-overlapping up to the last column and/or last row of blocks. +#cat: In these cases the blocks are adjacent to the edge of the image and +#cat: extend inwards BLOCKSIZE units, overlapping the neighboring column +#cat: or row of blocks. This routine also accounts for image padding +#cat: which makes things a little more "messy". This routine is primarily +#cat: responsible providing the ability to processs arbitrarily-sized +#cat: images. The strategy used here is simple, but others are possible. + + Input: + iw - width (in pixels) of the orginal input image + ih - height (in pixels) of the orginal input image + pad - the padding (in pixels) required to support the desired + range of block orientations for DFT analysis. This padding + is required along the entire perimeter of the input image. + For certain applications, the pad may be zero. + blocksize - the width and height (in pixels) of each image block + Output: + optr - points to the list of pixel offsets to the origin of + each block in the "padded" input image + ow - the number of horizontal blocks in the input image + oh - the number of vertical blocks in the input image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int block_offsets(int **optr, int *ow, int *oh, + const int iw, const int ih, const int pad, const int blocksize) +{ + int *blkoffs, bx, by, bw, bh, bi, bsize; + int blkrow_start, blkrow_size, offset; + int lastbw, lastbh; + int pad2, pw, ph; + + /* Test if unpadded image is smaller than a single block */ + if((iw < blocksize) || (ih < blocksize)){ + fprintf(stderr, + "ERROR : block_offsets : image must be at least %d by %d in size\n", + blocksize, blocksize); + return(-80); + } + + /* Compute padded width and height of image */ + pad2 = pad<<1; + pw = iw + pad2; + ph = ih + pad2; + + /* Compute the number of columns and rows of blocks in the image. */ + /* Take the ceiling to account for "leftovers" at the right and */ + /* bottom of the unpadded image */ + bw = (int)ceil(iw / (double)blocksize); + bh = (int)ceil(ih / (double)blocksize); + + /* Total number of blocks in the image */ + bsize = bw*bh; + + /* The index of the last column */ + lastbw = bw - 1; + /* The index of the last row */ + lastbh = bh - 1; + + /* Allocate list of block offsets */ + blkoffs = (int *)malloc(bsize * sizeof(int)); + if(blkoffs == (int *)NULL){ + fprintf(stderr, "ERROR : block_offsets : malloc : blkoffs\n"); + return(-81); + } + + /* Current block index */ + bi = 0; + + /* Current offset from top of padded image to start of new row of */ + /* unpadded image blocks. It is initialize to account for the */ + /* padding and will always be indented the size of the padding */ + /* from the left edge of the padded image. */ + blkrow_start = (pad * pw) + pad; + + /* Number of pixels in a row of blocks in the padded image */ + blkrow_size = pw * blocksize; /* row width X block height */ + + /* Foreach non-overlapping row of blocks in the image */ + for(by = 0; by < lastbh; by++){ + /* Current offset from top of padded image to beginning of */ + /* the next block */ + offset = blkrow_start; + /* Foreach non-overlapping column of blocks in the image */ + for(bx = 0; bx < lastbw; bx++){ + /* Store current block offset */ + blkoffs[bi++] = offset; + /* Bump to the beginning of the next block */ + offset += blocksize; + } + + /* Compute and store "left-over" block in row. */ + /* This is the block in the last column of row. */ + /* Start at far right edge of unpadded image data */ + /* and come in BLOCKSIZE pixels. */ + blkoffs[bi++] = blkrow_start + iw - blocksize; + /* Bump to beginning of next row of blocks */ + blkrow_start += blkrow_size; + } + + /* Compute and store "left-over" row of blocks at bottom of image */ + /* Start at bottom edge of unpadded image data and come up */ + /* BLOCKSIZE pixels. This too must account for padding. */ + blkrow_start = ((pad + ih - blocksize) * pw) + pad; + /* Start the block offset for the last row at this point */ + offset = blkrow_start; + /* Foreach non-overlapping column of blocks in last row of the image */ + for(bx = 0; bx < lastbw; bx++){ + /* Store current block offset */ + blkoffs[bi++] = offset; + /* Bump to the beginning of the next block */ + offset += blocksize; + } + + /* Compute and store last "left-over" block in last row. */ + /* Start at right edge of unpadded image data and come in */ + /* BLOCKSIZE pixels. */ + blkoffs[bi++] = blkrow_start + iw - blocksize; + + *optr = blkoffs; + *ow = bw; + *oh = bh; + return(0); +} + +/************************************************************************* +#cat: low_contrast_block - Takes the offset to an image block of specified +#cat: dimension, and analyzes the pixel intensities in the block +#cat: to determine if there is sufficient contrast for further +#cat: processing. + + Input: + blkoffset - byte offset into the padded input image to the origin of + the block to be analyzed + blocksize - dimension (in pixels) of the width and height of the block + (passing separate blocksize from LFSPARMS on purpose) + pdata - padded input image data (8 bits [0..256) grayscale) + pw - width (in pixels) of the padded input image + ph - height (in pixels) of the padded input image + lfsparms - parameters and thresholds for controlling LFS + Return Code: + TRUE - block has sufficiently low contrast + FALSE - block has sufficiently hight contrast + Negative - system error +************************************************************************** +**************************************************************************/ +int low_contrast_block(const int blkoffset, const int blocksize, + unsigned char *pdata, const int pw, const int ph, + const LFSPARMS *lfsparms) +{ + int pixtable[IMG_6BIT_PIX_LIMIT], numpix; + int px, py, pi; + unsigned char *sptr, *pptr; + int delta; + double tdbl; + int prctmin = 0, prctmax = 0, prctthresh; + int pixsum, found; + + numpix = blocksize*blocksize; + memset(pixtable, 0, IMG_6BIT_PIX_LIMIT*sizeof(int)); + + tdbl = (lfsparms->percentile_min_max/100.0) * (double)(numpix-1); + tdbl = trunc_dbl_precision(tdbl, TRUNC_SCALE); + prctthresh = sround(tdbl); + + sptr = pdata+blkoffset; + for(py = 0; py < blocksize; py++){ + pptr = sptr; + for(px = 0; px < blocksize; px++){ + pixtable[*pptr]++; + pptr++; + } + sptr += pw; + } + + pi = 0; + pixsum = 0; + found = FALSE; + while(pi < IMG_6BIT_PIX_LIMIT){ + pixsum += pixtable[pi]; + if(pixsum >= prctthresh){ + prctmin = pi; + found = TRUE; + break; + } + pi++; + } + if(!found){ + fprintf(stderr, + "ERROR : low_contrast_block : min percentile pixel not found\n"); + return(-510); + } + + pi = IMG_6BIT_PIX_LIMIT-1; + pixsum = 0; + found = FALSE; + while(pi >= 0){ + pixsum += pixtable[pi]; + if(pixsum >= prctthresh){ + prctmax = pi; + found = TRUE; + break; + } + pi--; + } + if(!found){ + fprintf(stderr, + "ERROR : low_contrast_block : max percentile pixel not found\n"); + return(-511); + } + + delta = prctmax - prctmin; + + if(delta < lfsparms->min_contrast_delta) + return(TRUE); + else + return(FALSE); +} + +/************************************************************************* +************************************************************************** +#cat: find_valid_block - Take a Direction Map, Low Contrast Map, +#cat: Starting block address, a direction and searches the +#cat: maps in the specified direction until either a block valid +#cat: direction is encountered or a block flagged as LOW CONTRAST +#cat: is encountered. If a valid direction is located, it and the +#cat: address of the corresponding block are returned with a +#cat: code of FOUND. Otherwise, a code of NOT_FOUND is returned. + + Input: + direction_map - map of blocks containing directional ridge flows + low_contrast_map - map of blocks flagged as LOW CONTRAST + sx - X-block coord where search starts in maps + sy - Y-block coord where search starts in maps + mw - number of blocks horizontally in the maps + mh - number of blocks vertically in the maps + x_incr - X-block increment to direct search + y_incr - Y-block increment to direct search + Output: + nbr_dir - valid direction found + nbr_x - X-block coord where valid direction found + nbr_y - Y-block coord where valid direction found + Return Code: + FOUND - neighboring block with valid direction found + NOT_FOUND - neighboring block with valid direction NOT found +**************************************************************************/ +int find_valid_block(int *nbr_dir, int *nbr_x, int *nbr_y, + int *direction_map, int *low_contrast_map, + const int sx, const int sy, + const int mw, const int mh, + const int x_incr, const int y_incr) +{ + int x, y, dir; + + /* Initialize starting block coords. */ + x = sx + x_incr; + y = sy + y_incr; + + /* While we are not outside the boundaries of the map ... */ + while((x >= 0) && (x < mw) && (y >= 0) && (y < mh)){ + /* Stop unsuccessfully if we encounter a LOW CONTRAST block. */ + if(*(low_contrast_map+(y*mw)+x)) + return(NOT_FOUND); + + /* Stop successfully if we encounter a block with valid direction. */ + if((dir = *(direction_map+(y*mw)+x)) >= 0){ + *nbr_dir = dir; + *nbr_x = x; + *nbr_y = y; + return(FOUND); + } + + /* Otherwise, advance to the next block in the map. */ + x += x_incr; + y += y_incr; + } + + /* If we get here, then we did not find a valid block in the given */ + /* direction in the map. */ + return(NOT_FOUND); +} + +/************************************************************************* +************************************************************************** +#cat: set_margin_blocks - Take an image map and sets its perimeter values to +#cat: the specified value. + + Input: + map - map of blocks to be modified + mw - number of blocks horizontally in the map + mh - number of blocks vertically in the map + margin_value - value to be assigned to the perimeter blocks + Output: + map - resulting map +**************************************************************************/ +void set_margin_blocks(int *map, const int mw, const int mh, + const int margin_value) +{ + int x, y; + int *ptr1, *ptr2; + + ptr1 = map; + ptr2 = map+((mh-1)*mw); + for(x = 0; x < mw; x++){ + *ptr1++ = margin_value; + *ptr2++ = margin_value; + } + + ptr1 = map + mw; + ptr2 = map + mw + mw - 1; + for(y = 1; y < mh-1; y++){ + *ptr1 = margin_value; + *ptr2 = margin_value; + ptr1 += mw; + ptr2 += mw; + } + +} + diff --git a/libfprint/nbis/mindtct/chaincod.c b/libfprint/nbis/mindtct/chaincod.c new file mode 100644 index 0000000..006c996 --- /dev/null +++ b/libfprint/nbis/mindtct/chaincod.c @@ -0,0 +1,191 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: CHAINCODE.C + AUTHOR: Michael D. Garris + DATE: 05/11/1999 + + Contains routines responsible for generating and manipulating + chain codes as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + chain_code_loop() + is_chain_clockwise() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: chain_code_loop - Converts a feature's contour points into an +#cat: 8-connected chain code vector. This encoding represents +#cat: the direction taken between each adjacent point in the +#cat: contour. Chain codes may be used for many purposes, such +#cat: as computing the perimeter or area of an object, and they +#cat: may be used in object detection and recognition. + + Input: + contour_x - x-coord list for feature's contour points + contour_y - y-coord list for feature's contour points + ncontour - number of points in contour + Output: + ochain - resulting vector of chain codes + onchain - number of codes in chain + (same as number of points in contour) + Return Code: + Zero - chain code successful derived + Negative - system error +**************************************************************************/ +int chain_code_loop(int **ochain, int *onchain, + const int *contour_x, const int *contour_y, const int ncontour) +{ + int *chain; + int i, j, dx, dy; + + /* If we don't have at least 3 points in the contour ... */ + if(ncontour <= 3){ + /* Then we don't have a loop, so set chain length to 0 */ + /* and return without any allocations. */ + *onchain = 0; + return(0); + } + + /* Allocate chain code vector. It will be the same length as the */ + /* number of points in the contour. There will be one chain code */ + /* between each point on the contour including a code between the */ + /* last to the first point on the contour (completing the loop). */ + chain = (int *)malloc(ncontour * sizeof(int)); + /* If the allocation fails ... */ + if(chain == (int *)NULL){ + fprintf(stderr, "ERROR : chain_code_loop : malloc : chain\n"); + return(-170); + } + + /* For each neighboring point in the list (with "i" pointing to the */ + /* previous neighbor and "j" pointing to the next neighbor... */ + for(i = 0, j=1; i < ncontour-1; i++, j++){ + /* Compute delta in X between neighbors. */ + dx = contour_x[j] - contour_x[i]; + /* Compute delta in Y between neighbors. */ + dy = contour_y[j] - contour_y[i]; + /* Derive chain code index from neighbor deltas. */ + /* The deltas are on the range [-1..1], so to use them as indices */ + /* into the code list, they must first be incremented by one. */ + chain[i] = *(chaincodes_nbr8+((dy+1)*NBR8_DIM)+dx+1); + } + + /* Now derive chain code between last and first points in the */ + /* contour list. */ + dx = contour_x[0] - contour_x[i]; + dy = contour_y[0] - contour_y[i]; + chain[i] = *(chaincodes_nbr8+((dy+1)*NBR8_DIM)+dx+1); + + /* Store results to the output pointers. */ + *ochain = chain; + *onchain = ncontour; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: is_chain_clockwise - Takes an 8-connected chain code vector and +#cat: determines if the codes are ordered clockwise or +#cat: counter-clockwise. +#cat: The routine also requires a default return value be +#cat: specified in the case the the routine is not able to +#cat: definitively determine the chains direction. This allows +#cat: the default response to be application-specific. + + Input: + chain - chain code vector + nchain - number of codes in chain + default_ret - default return code (used when we can't tell the order) + Return Code: + TRUE - chain determined to be ordered clockwise + FALSE - chain determined to be ordered counter-clockwise + Default - could not determine the order of the chain +**************************************************************************/ +int is_chain_clockwise(const int *chain, const int nchain, + const int default_ret) +{ + int i, j, d, sum; + + /* Initialize turn-accumulator to 0. */ + sum = 0; + + /* Foreach neighboring code in chain, compute the difference in */ + /* direction and accumulate. Left-hand turns increment, whereas */ + /* right-hand decrement. */ + for(i = 0, j =1; i < nchain-1; i++, j++){ + /* Compute delta in neighbor direction. */ + d = chain[j] - chain[i]; + /* Make the delta the "inner" distance. */ + /* If delta >= 4, for example if chain_i==2 and chain_j==7 (which */ + /* means the contour went from a step up to step down-to-the-right) */ + /* then 5=(7-2) which is >=4, so -3=(5-8) which means that the */ + /* change in direction is a righ-hand turn of 3 units). */ + if(d >= 4) + d -= 8; + /* If delta <= -4, for example if chain_i==7 and chain_j==2 (which */ + /* means the contour went from a step down-to-the-right to step up) */ + /* then -5=(2-7) which is <=-4, so 3=(-5+8) which means that the */ + /* change in direction is a left-hand turn of 3 units). */ + else if (d <= -4) + d += 8; + + /* The delta direction is then accumulated. */ + sum += d; + } + + /* Now we need to add in the final delta direction between the last */ + /* and first codes in the chain. */ + d = chain[0] - chain[i]; + if(d >= 4) + d -= 8; + else if (d <= -4) + d += 8; + sum += d; + + /* If the final turn_accumulator == 0, then we CAN'T TELL the */ + /* direction of the chain code, so return the default return value. */ + if(sum == 0) + return(default_ret); + /* Otherwise, if the final turn-accumulator is positive ... */ + else if(sum > 0) + /* Then we had a greater amount of left-hand turns than right-hand */ + /* turns, so the chain is in COUNTER-CLOCKWISE order, so return FALSE. */ + return(FALSE); + /* Otherwise, the final turn-accumulator is negative ... */ + else + /* So we had a greater amount of right-hand turns than left-hand */ + /* turns, so the chain is in CLOCKWISE order, so return TRUE. */ + return(TRUE); +} diff --git a/libfprint/nbis/mindtct/contour.c b/libfprint/nbis/mindtct/contour.c new file mode 100644 index 0000000..ae43471 --- /dev/null +++ b/libfprint/nbis/mindtct/contour.c @@ -0,0 +1,1274 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: CONTOUR.C + AUTHOR: Michael D. Garris + DATE: 05/11/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for extracting and analyzing + minutia feature contour lists as part of the NIST Latent + Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + allocate_contour() + free_contour() + get_high_curvature_contour() + get_centered_contour() + trace_contour() + search_contour() + next_contour_pixel() + start_scan_nbr() + next_scan_nbr() + min_contour_theta() + contour_limits() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: allocate_contour - Allocates the lists needed to represent the +#cat: contour of a minutia feature (a ridge or valley-ending). +#cat: This includes two lists of coordinate pairs. The first is +#cat: the 8-connected chain of points interior to the feature +#cat: and are called the feature's "contour points". +#cat: The second is a list or corresponding points each +#cat: adjacent to its respective feature contour point in the first +#cat: list and on the exterior of the feature. These second points +#cat: are called the feature's "edge points". Don't be confused, +#cat: both lists of points are on the "edge". The first set is +#cat: guaranteed 8-connected and the color of the feature. The +#cat: second set is NOT guaranteed to be 8-connected and its points +#cat: are opposite the color of the feature. Remeber that "feature" +#cat: means either ridge-ending (black pixels) or valley-ending +#cat: (white pixels). + + Input: + ncontour - number of items in each coordinate list to be allocated + Output: + ocontour_x - allocated x-coord list for feature's contour points + ocontour_y - allocated y-coord list for feature's contour points + ocontour_ex - allocated x-coord list for feature's edge points + ocontour_ey - allocated y-coord list for feature's edge points + Return Code: + Zero - lists were successfully allocated + Negative - system (allocation) error +**************************************************************************/ +int allocate_contour(int **ocontour_x, int **ocontour_y, + int **ocontour_ex, int **ocontour_ey, const int ncontour) +{ + int *contour_x, *contour_y, *contour_ex, *contour_ey; + + /* Allocate contour's x-coord list. */ + contour_x = (int *)malloc(ncontour*sizeof(int)); + /* If allocation error... */ + if(contour_x == (int *)NULL){ + fprintf(stderr, "ERROR : allocate_contour : malloc : contour_x\n"); + return(-180); + } + + /* Allocate contour's y-coord list. */ + contour_y = (int *)malloc(ncontour*sizeof(int)); + /* If allocation error... */ + if(contour_y == (int *)NULL){ + /* Deallocate memory allocated to this point in this routine. */ + free(contour_x); + fprintf(stderr, "ERROR : allocate_contour : malloc : contour_y\n"); + return(-181); + } + + /* Allocate contour's edge x-coord list. */ + contour_ex = (int *)malloc(ncontour*sizeof(int)); + /* If allocation error... */ + if(contour_ex == (int *)NULL){ + /* Deallocate memory allocated to this point in this routine. */ + free(contour_x); + free(contour_y); + fprintf(stderr, "ERROR : allocate_contour : malloc : contour_ex\n"); + return(-182); + } + + /* Allocate contour's edge y-coord list. */ + contour_ey = (int *)malloc(ncontour*sizeof(int)); + /* If allocation error... */ + if(contour_ey == (int *)NULL){ + /* Deallocate memory allocated to this point in this routine. */ + free(contour_x); + free(contour_y); + free(contour_ex); + fprintf(stderr, "ERROR : allocate_contour : malloc : contour_ey\n"); + return(-183); + } + + /* Otherwise, allocations successful, so assign output pointers. */ + *ocontour_x = contour_x; + *ocontour_y = contour_y; + *ocontour_ex = contour_ex; + *ocontour_ey = contour_ey; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: free_contour - Deallocates the lists used to represent the +#cat: contour of a minutia feature (a ridge or valley-ending). +#cat: This includes two lists of coordinate pairs. The first is +#cat: the 8-connected chain of points interior to the feature +#cat: and are called the feature's "contour points". +#cat: The second is a list or corresponding points each +#cat: adjacent to its respective feature contour point in the first +#cat: list and on the exterior of the feature. These second points +#cat: are called the feature's "edge points". + + Input: + contour_x - x-coord list for feature's contour points + contour_y - y-coord list for feature's contour points + contour_ex - x-coord list for feature's edge points + contour_ey - y-coord list for feature's edge points +**************************************************************************/ +void free_contour(int *contour_x, int *contour_y, + int *contour_ex, int *contour_ey) +{ + free(contour_x); + free(contour_y); + free(contour_ex); + free(contour_ey); +} + +/************************************************************************* +************************************************************************** +#cat: get_high_curvature_contour - Takes the pixel coordinate of a detected +#cat: minutia feature point and its corresponding/adjacent edge +#cat: pixel and attempts to extract a contour of specified length +#cat: of the feature's edge. The contour is extracted by walking +#cat: the feature's edge a specified number of steps clockwise and +#cat: then counter-clockwise. If a loop is detected while +#cat: extracting the contour, the contour of the loop is returned +#cat: with a return code of (LOOP_FOUND). If the process fails +#cat: to extract a contour of total specified length, then +#cat: the returned contour length is set to Zero, NO allocated +#cat: memory is returned in this case, and the return code is set +#cat: to Zero. An alternative implementation would be to return +#cat: the incomplete contour with a return code of (INCOMPLETE). +#cat: For now, NO allocated contour is returned in this case. + + Input: + half_contour - half the length of the extracted contour + (full-length non-loop contour = (half_contourX2)+1) + x_loc - starting x-pixel coord of feature (interior to feature) + y_loc - starting y-pixel coord of feature (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel + (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel + (exterior to feature) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + ocontour_x - x-pixel coords of contour (interior to feature) + ocontour_y - y-pixel coords of contour (interior to feature) + ocontour_ex - x-pixel coords of corresponding edge (exterior to feature) + ocontour_ey - y-pixel coords of corresponding edge (exterior to feature) + oncontour - number of contour points returned + Return Code: + Zero - resulting contour was successfully extracted or is empty + LOOP_FOUND - resulting contour forms a complete loop + Negative - system error +**************************************************************************/ +int get_high_curvature_contour(int **ocontour_x, int **ocontour_y, + int **ocontour_ex, int **ocontour_ey, int *oncontour, + const int half_contour, + const int x_loc, const int y_loc, + const int x_edge, const int y_edge, + unsigned char *bdata, const int iw, const int ih) +{ + int max_contour; + int *half1_x, *half1_y, *half1_ex, *half1_ey, nhalf1; + int *half2_x, *half2_y, *half2_ex, *half2_ey, nhalf2; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int i, j, ret; + + /* Compute maximum length of complete contour */ + /* (2 half contours + feature point). */ + max_contour = (half_contour<<1) + 1; + + /* Initialize output contour length to 0. */ + *oncontour = 0; + + /* Get 1st half contour with clockwise neighbor trace. */ + if((ret = trace_contour(&half1_x, &half1_y, &half1_ex, &half1_ey, &nhalf1, + half_contour, x_loc, y_loc, x_loc, y_loc, x_edge, y_edge, + SCAN_CLOCKWISE, bdata, iw, ih))){ + + /* If trace was not possible ... */ + if(ret == IGNORE) + /* Return, with nothing allocated and contour length equal to 0. */ + return(0); + + /* If 1st half contour forms a loop ... */ + if(ret == LOOP_FOUND){ + /* Need to reverse the 1st half contour so that the points are */ + /* in consistent order. */ + /* We need to add the original feature point to the list, so */ + /* set new contour length to one plus length of 1st half contour. */ + ncontour = nhalf1+1; + /* Allocate new contour list. */ + if((ret = allocate_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, ncontour))){ + /* If allcation error, then deallocate memory allocated to */ + /* this point in this routine. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + /* Return error code. */ + return(ret); + } + + /* Otherwise, we have the new contour allocated, so store the */ + /* original feature point. */ + contour_x[0] = x_loc; + contour_y[0] = y_loc; + contour_ex[0] = x_edge; + contour_ey[0] = y_edge; + + /* Now store the first half contour in reverse order. */ + for(i = 1, j = nhalf1-1; i < ncontour; i++, j--){ + contour_x[i] = half1_x[j]; + contour_y[i] = half1_y[j]; + contour_ex[i] = half1_ex[j]; + contour_ey[i] = half1_ey[j]; + } + + /* Deallocate the first half contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + + /* Assign the output pointers. */ + *ocontour_x = contour_x; + *ocontour_y = contour_y; + *ocontour_ex = contour_ex; + *ocontour_ey = contour_ey; + *oncontour = ncontour; + + /* Return LOOP_FOUND for further processing. */ + return(LOOP_FOUND); + } + + /* Otherwise, return the system error code from the first */ + /* call to trace_contour. */ + return(ret); + } + + /* If 1st half contour not complete ... */ + if(nhalf1 < half_contour){ + /* Deallocate the partial contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + /* Return, with nothing allocated and contour length equal to 0. */ + return(0); + } + + /* Otherwise, we have a complete 1st half contour... */ + /* Get 2nd half contour with counter-clockwise neighbor trace. */ + /* Use the last point from the first contour trace as the */ + /* point to test for a loop when tracing the second contour. */ + if((ret = trace_contour(&half2_x, &half2_y, &half2_ex, &half2_ey, &nhalf2, + half_contour, half1_x[nhalf1-1], half1_y[nhalf1-1], + x_loc, y_loc, x_edge, y_edge, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih))){ + + /* If 2nd trace was not possible ... */ + if(ret == IGNORE){ + /* Deallocate the 1st half contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + /* Return, with nothing allocated and contour length equal to 0. */ + return(0); + } + + /* If non-zero return code is NOT LOOP_FOUND, then system error ... */ + if(ret != LOOP_FOUND){ + /* Deallocate the 1st half contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + /* Return system error. */ + return(ret); + } + } + + /* If 2nd half NOT a loop AND not complete ... */ + if((ret != LOOP_FOUND) && (nhalf2 < half_contour)){ + /* Deallocate both half contours. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + free_contour(half2_x, half2_y, half2_ex, half2_ey); + /* Return, with nothing allocated and contour length equal to 0. */ + return(0); + } + + /* Otherwise we have a full 1st half contour and a 2nd half contour */ + /* that is either a loop or complete. In either case we need to */ + /* concatenate the two half contours into one longer contour. */ + + /* Allocate output contour list. Go ahead and allocate the */ + /* "max_contour" amount even though the resulting contour will */ + /* likely be shorter if it forms a loop. */ + if((ret = allocate_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, max_contour))){ + /* If allcation error, then deallocate memory allocated to */ + /* this point in this routine. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + free_contour(half2_x, half2_y, half2_ex, half2_ey); + /* Return error code. */ + return(ret); + } + + /* Set the current contour point counter to 0 */ + ncontour = 0; + + /* Copy 1st half contour into output contour buffers. */ + /* This contour was collected clockwise, so it's points */ + /* are entered in reverse order of the trace. The result */ + /* is the first point in the output contour if farthest */ + /* from the starting feature point. */ + for(i = 0, j = nhalf1-1; i < nhalf1; i++, j--){ + contour_x[i] = half1_x[j]; + contour_y[i] = half1_y[j]; + contour_ex[i] = half1_ex[j]; + contour_ey[i] = half1_ey[j]; + ncontour++; + } + + /* Deallocate 1st half contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + + /* Next, store starting feature point into output contour buffers. */ + contour_x[nhalf1] = x_loc; + contour_y[nhalf1] = y_loc; + contour_ex[nhalf1] = x_edge; + contour_ey[nhalf1] = y_edge; + ncontour++; + + /* Now, append 2nd half contour to permanent contour buffers. */ + for(i = 0, j = nhalf1+1; i < nhalf2; i++, j++){ + contour_x[j] = half2_x[i]; + contour_y[j] = half2_y[i]; + contour_ex[j] = half2_ex[i]; + contour_ey[j] = half2_ey[i]; + ncontour++; + } + + /* Deallocate 2nd half contour. */ + free_contour(half2_x, half2_y, half2_ex, half2_ey); + + /* Assign outputs contour to output ponters. */ + *ocontour_x = contour_x; + *ocontour_y = contour_y; + *ocontour_ex = contour_ex; + *ocontour_ey = contour_ey; + *oncontour = ncontour; + + /* Return the resulting return code form the 2nd call to trace_contour */ + /* (the value will either be 0 or LOOP_FOUND). */ + return(ret); +} + +/************************************************************************* +************************************************************************** +#cat: get_centered_contour - Takes the pixel coordinate of a detected +#cat: minutia feature point and its corresponding/adjacent edge +#cat: pixel and attempts to extract a contour of specified length +#cat: of the feature's edge. The contour is extracted by walking +#cat: the feature's edge a specified number of steps clockwise and +#cat: then counter-clockwise. If a loop is detected while +#cat: extracting the contour, no contour is returned with a return +#cat: code of (LOOP_FOUND). If the process fails to extract a +#cat: a complete contour, a code of INCOMPLETE is returned. + + Input: + half_contour - half the length of the extracted contour + (full-length non-loop contour = (half_contourX2)+1) + x_loc - starting x-pixel coord of feature (interior to feature) + y_loc - starting y-pixel coord of feature (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel + (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel + (exterior to feature) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + ocontour_x - x-pixel coords of contour (interior to feature) + ocontour_y - y-pixel coords of contour (interior to feature) + ocontour_ex - x-pixel coords of corresponding edge (exterior to feature) + ocontour_ey - y-pixel coords of corresponding edge (exterior to feature) + oncontour - number of contour points returned + Return Code: + Zero - resulting contour was successfully extracted or is empty + LOOP_FOUND - resulting contour forms a complete loop + IGNORE - contour could not be traced due to problem starting + conditions + INCOMPLETE - resulting contour was not long enough + Negative - system error +**************************************************************************/ +int get_centered_contour(int **ocontour_x, int **ocontour_y, + int **ocontour_ex, int **ocontour_ey, int *oncontour, + const int half_contour, + const int x_loc, const int y_loc, + const int x_edge, const int y_edge, + unsigned char *bdata, const int iw, const int ih) +{ + int max_contour; + int *half1_x, *half1_y, *half1_ex, *half1_ey, nhalf1; + int *half2_x, *half2_y, *half2_ex, *half2_ey, nhalf2; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int i, j, ret; + + /* Compute maximum length of complete contour */ + /* (2 half contours + feature point). */ + max_contour = (half_contour<<1) + 1; + + /* Initialize output contour length to 0. */ + *oncontour = 0; + + /* Get 1st half contour with clockwise neighbor trace. */ + ret = trace_contour(&half1_x, &half1_y, &half1_ex, &half1_ey, &nhalf1, + half_contour, x_loc, y_loc, x_loc, y_loc, x_edge, y_edge, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible ... */ + if(ret == IGNORE) + /* Return IGNORE, with nothing allocated. */ + return(IGNORE); + + /* If 1st half contour forms a loop ... */ + if(ret == LOOP_FOUND){ + /* Deallocate loop's contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + /* Return LOOP_FOUND, with nothing allocated. */ + return(LOOP_FOUND); + } + + /* If 1st half contour not complete ... */ + if(nhalf1 < half_contour){ + /* Deallocate the partial contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + /* Return, with nothing allocated and contour length equal to 0. */ + return(INCOMPLETE); + } + + /* Otherwise, we have a complete 1st half contour... */ + /* Get 2nd half contour with counter-clockwise neighbor trace. */ + /* Use the last point from the first contour trace as the */ + /* point to test for a loop when tracing the second contour. */ + ret = trace_contour(&half2_x, &half2_y, &half2_ex, &half2_ey, &nhalf2, + half_contour, half1_x[nhalf1-1], half1_y[nhalf1-1], + x_loc, y_loc, x_edge, y_edge, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred on 2nd trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If 2nd trace was not possible ... */ + if(ret == IGNORE){ + /* Deallocate the 1st half contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + /* Return, with nothing allocated and contour length equal to 0. */ + return(IGNORE); + } + + /* If 2nd trace forms a loop ... */ + if(ret == LOOP_FOUND){ + /* Deallocate 1st and 2nd half contours. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + free_contour(half2_x, half2_y, half2_ex, half2_ey); + /* Return LOOP_FOUND, with nothing allocated. */ + return(LOOP_FOUND); + } + + /* If 2nd half contour not complete ... */ + if(nhalf2 < half_contour){ + /* Deallocate 1st and 2nd half contours. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + free_contour(half2_x, half2_y, half2_ex, half2_ey); + /* Return, with nothing allocated and contour length equal to 0. */ + return(INCOMPLETE); + } + + /* Otherwise we have a full 1st half contour and a 2nd half contour */ + /* that do not form a loop and are complete. We now need to */ + /* concatenate the two half contours into one longer contour. */ + + /* Allocate output contour list. */ + if((ret = allocate_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, max_contour))){ + /* If allcation error, then deallocate memory allocated to */ + /* this point in this routine. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + free_contour(half2_x, half2_y, half2_ex, half2_ey); + /* Return error code. */ + return(ret); + } + + /* Set the current contour point counter to 0 */ + ncontour = 0; + + /* Copy 1st half contour into output contour buffers. */ + /* This contour was collected clockwise, so it's points */ + /* are entered in reverse order of the trace. The result */ + /* is the first point in the output contour if farthest */ + /* from the starting feature point. */ + for(i = 0, j = nhalf1-1; i < nhalf1; i++, j--){ + contour_x[i] = half1_x[j]; + contour_y[i] = half1_y[j]; + contour_ex[i] = half1_ex[j]; + contour_ey[i] = half1_ey[j]; + ncontour++; + } + + /* Deallocate 1st half contour. */ + free_contour(half1_x, half1_y, half1_ex, half1_ey); + + /* Next, store starting feature point into output contour buffers. */ + contour_x[nhalf1] = x_loc; + contour_y[nhalf1] = y_loc; + contour_ex[nhalf1] = x_edge; + contour_ey[nhalf1] = y_edge; + ncontour++; + + /* Now, append 2nd half contour to permanent contour buffers. */ + for(i = 0, j = nhalf1+1; i < nhalf2; i++, j++){ + contour_x[j] = half2_x[i]; + contour_y[j] = half2_y[i]; + contour_ex[j] = half2_ex[i]; + contour_ey[j] = half2_ey[i]; + ncontour++; + } + + /* Deallocate 2nd half contour. */ + free_contour(half2_x, half2_y, half2_ex, half2_ey); + + /* Assign outputs contour to output ponters. */ + *ocontour_x = contour_x; + *ocontour_y = contour_y; + *ocontour_ex = contour_ex; + *ocontour_ey = contour_ey; + *oncontour = ncontour; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: trace_contour - Takes the pixel coordinate of a detected minutia +#cat: feature point and its corresponding/adjacent edge pixel +#cat: and extracts a contour (up to a specified maximum length) +#cat: of the feature's edge in either a clockwise or counter- +#cat: clockwise direction. A second point is specified, such that +#cat: if this point is encounted while extracting the contour, +#cat: it is to be assumed that a loop has been found and a code +#cat: of (LOOP_FOUND) is returned with the contour. By independently +#cat: specifying this point, successive calls can be made to +#cat: this routine from the same starting point, and loops across +#cat: successive calls can be detected. + + Input: + max_len - maximum length of contour to be extracted + x_loop - x-pixel coord of point, if encountered, triggers LOOP_FOUND + y_loop - y-pixel coord of point, if encountered, triggers LOOP_FOUND + x_loc - starting x-pixel coord of feature (interior to feature) + y_loc - starting y-pixel coord of feature (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel (exterior to feature) + scan_clock - direction in which neighboring pixels are to be scanned + for the next contour pixel + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + ocontour_x - x-pixel coords of contour (interior to feature) + ocontour_y - y-pixel coords of contour (interior to feature) + ocontour_ex - x-pixel coords of corresponding edge (exterior to feature) + ocontour_ey - y-pixel coords of corresponding edge (exterior to feature) + oncontour - number of contour points returned + Return Code: + Zero - resulting contour was successfully allocated and extracted + LOOP_FOUND - resulting contour forms a complete loop + IGNORE - trace is not possible due to state of inputs + Negative - system error +**************************************************************************/ +int trace_contour(int **ocontour_x, int **ocontour_y, + int **ocontour_ex, int **ocontour_ey, int *oncontour, + const int max_len, const int x_loop, const int y_loop, + const int x_loc, const int y_loc, + const int x_edge, const int y_edge, + const int scan_clock, + unsigned char *bdata, const int iw, const int ih) +{ + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int cur_x_loc, cur_y_loc; + int cur_x_edge, cur_y_edge; + int next_x_loc, next_y_loc; + int next_x_edge, next_y_edge; + int i, ret; + + /* Check to make sure that the feature and edge values are opposite. */ + if(*(bdata+(y_loc*iw)+x_loc) == + *(bdata+(y_edge*iw)+x_edge)) + /* If not opposite, then the trace will not work, so return IGNORE. */ + return(IGNORE); + + /* Allocate contour buffers. */ + if((ret = allocate_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, max_len))){ + /* If allocation error, return code. */ + return(ret); + } + + /* Set pixel counter to 0. */ + ncontour = 0; + + /* Set up for finding first contour pixel. */ + cur_x_loc = x_loc; + cur_y_loc = y_loc; + cur_x_edge = x_edge; + cur_y_edge = y_edge; + + /* Foreach pixel to be collected on the feature's contour... */ + for(i = 0; i < max_len; i++){ + /* Find the next contour pixel. */ + if(next_contour_pixel(&next_x_loc, &next_y_loc, + &next_x_edge, &next_y_edge, + cur_x_loc, cur_y_loc, + cur_x_edge, cur_y_edge, + scan_clock, bdata, iw, ih)){ + + /* If we trace back around to the specified starting */ + /* feature location... */ + if((next_x_loc == x_loop) && (next_y_loc == y_loop)){ + /* Then we have found a loop, so return what we */ + /* have traced to this point. */ + *ocontour_x = contour_x; + *ocontour_y = contour_y; + *ocontour_ex = contour_ex; + *ocontour_ey = contour_ey; + *oncontour = ncontour; + return(LOOP_FOUND); + } + + /* Otherwise, we found another point on our feature's contour, */ + /* so store the new contour point. */ + contour_x[i] = next_x_loc; + contour_y[i] = next_y_loc; + contour_ex[i] = next_x_edge; + contour_ey[i] = next_y_edge; + /* Bump the number of points stored. */ + ncontour++; + + /* Set up for finding next contour pixel. */ + cur_x_loc = next_x_loc; + cur_y_loc = next_y_loc; + cur_x_edge = next_x_edge; + cur_y_edge = next_y_edge; + } + /* Otherwise, no new contour point found ... */ + else{ + /* So, stop short and return the number of pixels found */ + /* on the contour to this point. */ + *ocontour_x = contour_x; + *ocontour_y = contour_y; + *ocontour_ex = contour_ex; + *ocontour_ey = contour_ey; + *oncontour = ncontour; + + /* Return normally. */ + return(0); + } + } + + /* If we get here, we successfully found the maximum points we */ + /* were looking for on the feature contour, so assign the contour */ + /* buffers to the output pointers and return. */ + *ocontour_x = contour_x; + *ocontour_y = contour_y; + *ocontour_ex = contour_ex; + *ocontour_ey = contour_ey; + *oncontour = ncontour; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: search_contour - Walk the contour of a minutia feature starting at a +#cat: specified point on the feature and walking N steps in the +#cat: specified direction (clockwise or counter-clockwise), looking +#cat: for a second specified point. In this code, "feature" is +#cat: consistently referring to either the black interior edge of +#cat: a ridge-ending or the white interior edge of a valley-ending +#cat: (bifurcation). The term "edge of the feature" refers to +#cat: neighboring pixels on the "exterior" edge of the feature. +#cat: So "edge" pixels are opposite in color from the interior +#cat: feature pixels. + + Input: + x_search - x-pixel coord of point being searched for + y_search - y-pixel coord of point being searched for + search_len - number of step to walk contour in search + x_loc - starting x-pixel coord of feature (interior to feature) + y_loc - starting y-pixel coord of feature (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel + (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel + (exterior to feature) + scan_clock - direction in which neighbor pixels are to be scanned + (clockwise or counter-clockwise) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Return Code: + NOT_FOUND - desired pixel not found along N steps of feature's contour + FOUND - desired pixel WAS found along N steps of feature's contour +**************************************************************************/ +int search_contour(const int x_search, const int y_search, + const int search_len, + const int x_loc, const int y_loc, + const int x_edge, const int y_edge, + const int scan_clock, + unsigned char *bdata, const int iw, const int ih) +{ + int cur_x_loc, cur_y_loc; + int cur_x_edge, cur_y_edge; + int next_x_loc, next_y_loc; + int next_x_edge, next_y_edge; + int i; + + /* Set up for finding first contour pixel. */ + cur_x_loc = x_loc; + cur_y_loc = y_loc; + cur_x_edge = x_edge; + cur_y_edge = y_edge; + + /* Foreach point to be collected on the feature's contour... */ + for(i = 0; i < search_len; i++){ + /* Find the next contour pixel. */ + if(next_contour_pixel(&next_x_loc, &next_y_loc, + &next_x_edge, &next_y_edge, + cur_x_loc, cur_y_loc, + cur_x_edge, cur_y_edge, + scan_clock, bdata, iw, ih)){ + + /* If we find the point we are looking for on the contour... */ + if((next_x_loc == x_search) && (next_y_loc == y_search)){ + /* Then return FOUND. */ + return(FOUND); + } + + /* Otherwise, set up for finding next contour pixel. */ + cur_x_loc = next_x_loc; + cur_y_loc = next_y_loc; + cur_x_edge = next_x_edge; + cur_y_edge = next_y_edge; + } + /* Otherwise, no new contour point found ... */ + else{ + /* So, stop searching, and return NOT_FOUND. */ + return(NOT_FOUND); + } + } + + /* If we get here, we successfully searched the maximum points */ + /* without finding our desired point, so return NOT_FOUND. */ + return(NOT_FOUND); +} + +/************************************************************************* +************************************************************************** +#cat: next_contour_pixel - Takes a pixel coordinate of a point determined +#cat: to be on the interior edge of a feature (ridge or valley- +#cat: ending), and attempts to locate a neighboring pixel on the +#cat: feature's contour. Neighbors of the current feature pixel +#cat: are searched in a specified direction (clockwise or counter- +#cat: clockwise) and the first pair of adjacent/neigboring pixels +#cat: found with the first pixel having the color of the feature +#cat: and the second the opposite color are returned as the next +#cat: point on the contour. One exception happens when the new +#cat: point is on an "exposed" corner. + + Input: + cur_x_loc - x-pixel coord of current point on feature's + interior contour + cur_y_loc - y-pixel coord of current point on feature's + interior contour + cur_x_edge - x-pixel coord of corresponding edge pixel + (exterior to feature) + cur_y_edge - y-pixel coord of corresponding edge pixel + (exterior to feature) + scan_clock - direction in which neighboring pixels are to be scanned + for the next contour pixel + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + next_x_loc - x-pixel coord of next point on feature's interior contour + next_y_loc - y-pixel coord of next point on feature's interior contour + next_x_edge - x-pixel coord of corresponding edge (exterior to feature) + next_y_edge - y-pixel coord of corresponding edge (exterior to feature) + Return Code: + TRUE - next contour point found and returned + FALSE - next contour point NOT found +**************************************************************************/ +/*************************************************************************/ +int next_contour_pixel(int *next_x_loc, int *next_y_loc, + int *next_x_edge, int *next_y_edge, + const int cur_x_loc, const int cur_y_loc, + const int cur_x_edge, const int cur_y_edge, + const int scan_clock, + unsigned char *bdata, const int iw, const int ih) +{ + int feature_pix, edge_pix; + int prev_nbr_pix, prev_nbr_x, prev_nbr_y; + int cur_nbr_pix, cur_nbr_x, cur_nbr_y; + int ni, nx, ny, npix; + int nbr_i, i; + + /* Get the feature's pixel value. */ + feature_pix = *(bdata + (cur_y_loc * iw) + cur_x_loc); + /* Get the feature's edge pixel value. */ + edge_pix = *(bdata + (cur_y_edge * iw) + cur_x_edge); + + /* Get the nieghbor position of the feature's edge pixel in relationship */ + /* to the feature's actual position. */ + /* REMEBER: The feature's position is always interior and on a ridge */ + /* ending (black pixel) or (for bifurcations) on a valley ending (white */ + /* pixel). The feature's edge pixel is an adjacent pixel to the feature */ + /* pixel that is exterior to the ridge or valley ending and opposite in */ + /* pixel value. */ + nbr_i = start_scan_nbr(cur_x_loc, cur_y_loc, cur_x_edge, cur_y_edge); + + /* Set current neighbor scan pixel to the feature's edge pixel. */ + cur_nbr_x = cur_x_edge; + cur_nbr_y = cur_y_edge; + cur_nbr_pix = edge_pix; + + /* Foreach pixel neighboring the feature pixel ... */ + for(i = 0; i < 8; i++){ + + /* Set current neighbor scan pixel to previous scan pixel. */ + prev_nbr_x = cur_nbr_x; + prev_nbr_y = cur_nbr_y; + prev_nbr_pix = cur_nbr_pix; + + /* Bump pixel neighbor index clockwise or counter-clockwise. */ + nbr_i = next_scan_nbr(nbr_i, scan_clock); + + /* Set current scan pixel to the new neighbor. */ + /* REMEMBER: the neighbors are being scanned around the original */ + /* feature point. */ + cur_nbr_x = cur_x_loc + nbr8_dx[nbr_i]; + cur_nbr_y = cur_y_loc + nbr8_dy[nbr_i]; + + /* If new neighbor is not within image boundaries... */ + if((cur_nbr_x < 0) || (cur_nbr_x >= iw) || + (cur_nbr_y < 0) || (cur_nbr_y >= ih)) + /* Return (FALSE==>Failure) if neighbor out of bounds. */ + return(FALSE); + + /* Get the new neighbor's pixel value. */ + cur_nbr_pix = *(bdata + (cur_nbr_y * iw) + cur_nbr_x); + + /* If the new neighbor's pixel value is the same as the feature's */ + /* pixel value AND the previous neighbor's pixel value is the same */ + /* as the features's edge, then we have "likely" found our next */ + /* contour pixel. */ + if((cur_nbr_pix == feature_pix) && (prev_nbr_pix == edge_pix)){ + + /* Check to see if current neighbor is on the corner of the */ + /* neighborhood, and if so, test to see if it is "exposed". */ + /* The neighborhood corners have odd neighbor indicies. */ + if(nbr_i % 2){ + /* To do this, look ahead one more neighbor pixel. */ + ni = next_scan_nbr(nbr_i, scan_clock); + nx = cur_x_loc + nbr8_dx[ni]; + ny = cur_y_loc + nbr8_dy[ni]; + /* If new neighbor is not within image boundaries... */ + if((nx < 0) || (nx >= iw) || + (ny < 0) || (ny >= ih)) + /* Return (FALSE==>Failure) if neighbor out of bounds. */ + return(FALSE); + npix = *(bdata + (ny * iw) + nx); + + /* If the next neighbor's value is also the same as the */ + /* feature's pixel, then corner is NOT exposed... */ + if(npix == feature_pix){ + /* Assign the current neighbor pair to the output pointers. */ + *next_x_loc = cur_nbr_x; + *next_y_loc = cur_nbr_y; + *next_x_edge = prev_nbr_x; + *next_y_edge = prev_nbr_y; + /* Return TRUE==>Success. */ + return(TRUE); + } + /* Otherwise, corner pixel is "exposed" so skip it. */ + else{ + /* Skip current corner neighbor by resetting it to the */ + /* next neighbor, which upon the iteration will immediately */ + /* become the previous neighbor. */ + cur_nbr_x = nx; + cur_nbr_y = ny; + cur_nbr_pix = npix; + /* Advance neighbor index. */ + nbr_i = ni; + /* Advance neighbor count. */ + i++; + } + } + /* Otherwise, current neighbor is not a corner ... */ + else{ + /* Assign the current neighbor pair to the output pointers. */ + *next_x_loc = cur_nbr_x; + *next_y_loc = cur_nbr_y; + *next_x_edge = prev_nbr_x; + *next_y_edge = prev_nbr_y; + /* Return TRUE==>Success. */ + return(TRUE); + } + } + } + + /* If we get here, then we did not find the next contour pixel */ + /* within the 8 neighbors of the current feature pixel so */ + /* return (FALSE==>Failure). */ + /* NOTE: This must mean we found a single isolated pixel. */ + /* Perhaps this should be filled? */ + return(FALSE); +} + +/************************************************************************* +************************************************************************** +#cat: start_scan_nbr - Takes a two pixel coordinates that are either +#cat: aligned north-to-south or east-to-west, and returns the +#cat: position the second pixel is in realtionship to the first. +#cat: The positions returned are based on 8-connectedness. +#cat: NOTE, this routine does NOT account for diagonal positions. + + Input: + x_prev - x-coord of first point + y_prev - y-coord of first point + x_next - x-coord of second point + y_next - y-coord of second point + Return Code: + NORTH - second pixel above first + SOUTH - second pixel below first + EAST - second pixel right of first + WEST - second pixel left of first +**************************************************************************/ +int start_scan_nbr(const int x_prev, const int y_prev, + const int x_next, const int y_next) +{ + if((x_prev==x_next) && (y_next > y_prev)) + return(SOUTH); + else if ((x_prev==x_next) && (y_next < y_prev)) + return(NORTH); + else if ((x_next > x_prev) && (y_prev==y_next)) + return(EAST); + else if ((x_next < x_prev) && (y_prev==y_next)) + return(WEST); + + /* Added by MDG on 03-16-05 */ + /* Should never reach here. Added to remove compiler warning. */ + return(INVALID_DIR); /* -1 */ +} + +/************************************************************************* +************************************************************************** +#cat: next_scan_nbr - Advances the given 8-connected neighbor index +#cat: on location in the specifiec direction (clockwise or +#cat: counter-clockwise). + + Input: + nbr_i - current 8-connected neighbor index + scan_clock - direction in which the neighbor index is to be advanced + Return Code: + Next neighbor - 8-connected index of next neighbor +**************************************************************************/ +int next_scan_nbr(const int nbr_i, const int scan_clock) +{ + int new_i; + + /* If scanning neighbors clockwise ... */ + if(scan_clock == SCAN_CLOCKWISE) + /* Advance one neighbor clockwise. */ + new_i = (nbr_i+1)%8; + /* Otherwise, scanning neighbors counter-clockwise ... */ + else + /* Advance one neighbor counter-clockwise. */ + /* There are 8 pixels in the neighborhood, so to */ + /* decrement with wrapping from 0 around to 7, add */ + /* the nieghbor index by 7 and mod with 8. */ + new_i = (nbr_i+7)%8; + + /* Return the new neighbor index. */ + return(new_i); +} + +/************************************************************************* +************************************************************************** +#cat: min_contour_theta - Takes a contour list and analyzes it locating the +#cat: point at which the contour has highest curvature +#cat: (or minimum interior angle). The angle of curvature is +#cat: computed by searching a majority of points on the contour. +#cat: At each of these points, a left and right segment (or edge) +#cat: are extended out N number of pixels from the center point +#cat: on the contour. The angle is formed between the straight line +#cat: connecting the center point to the end point on the left edge +#cat: and the line connecting the center point to the end of the +#cat: right edge. The point of highest curvature is determined +#cat: by locating the where the minimum of these angles occurs. + + Input: + angle_edge - length of the left and right edges extending from a + common/centered pixel on the contour + contour_x - x-coord list for contour points + contour_y - y-coord list for contour points + ncontour - number of points in contour + Output: + omin_i - index of contour point where minimum occurred + omin_theta - minimum angle found along the contour + Return Code: + Zero - minimum angle successfully located + IGNORE - ignore the contour + Negative - system error +**************************************************************************/ +int min_contour_theta(int *omin_i, double *omin_theta, + const int angle_edge, const int *contour_x, + const int *contour_y, const int ncontour) +{ + int pleft, pcenter, pright; + double theta1, theta2, dtheta; + int min_i; + double min_theta; + + /* If the contour length is too short for processing... */ + if(ncontour < (angle_edge<<1)+1) + /* Return IGNORE. */ + return(IGNORE); + + /* Intialize running minimum values. */ + min_theta = M_PI; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when comparing doubles. */ + min_theta = trunc_dbl_precision(min_theta, TRUNC_SCALE); + min_i = -1; + + /* Set left angle point to first contour point. */ + pleft = 0; + /* Set center angle point to "angle_edge" points into contour. */ + pcenter = angle_edge; + /* Set right angle point to "angle_edge" points from pcenter. */ + pright = pcenter + angle_edge; + + /* Loop until the right angle point exceeds the contour list. */ + while(pright < ncontour){ + /* Compute angle to first edge line (connecting pcenter to pleft). */ + theta1 = angle2line(contour_x[pcenter],contour_y[pcenter], + contour_x[pleft],contour_y[pleft]); + /* Compute angle to second edge line (connecting pcenter to pright). */ + theta2 = angle2line(contour_x[pcenter],contour_y[pcenter], + contour_x[pright],contour_y[pright]); + + /* Compute delta between angles accounting for an inner */ + /* and outer distance between the angles. */ + dtheta = fabs(theta2 - theta1); + dtheta = min(dtheta, (M_PI*2.0)-dtheta); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when comparing doubles. */ + dtheta = trunc_dbl_precision(dtheta, TRUNC_SCALE); + + /* Keep track of running minimum theta. */ + if(dtheta < min_theta){ + min_i = pcenter; + min_theta = dtheta; + } + + /* Bump to next points on contour. */ + pleft++; + pcenter++; + pright++; + } + + /* If no minimum found (then contour is perfectly flat) so minimum */ + /* to center point on contour. */ + if(min_i == -1){ + *omin_i = ncontour>>1; + *omin_theta = min_theta; + } + else{ + /* Assign minimum theta information to output pointers. */ + *omin_i = min_i; + *omin_theta = min_theta; + } + + /* Return successfully. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: contour_limits - Determines the X and Y coordinate limits of the +#cat: given contour list. + + Input: + contour_x - x-coord list for contour points + contour_y - y-coord list for contour points + ncontour - number of points in contour + Output: + xmin - left-most x-coord in contour + ymin - top-most y-coord in contour + xmax - right-most x-coord in contour + ymax - bottom-most y-coord in contour +**************************************************************************/ +void contour_limits(int *xmin, int *ymin, int *xmax, int *ymax, + const int *contour_x, const int *contour_y, const int ncontour) +{ + /* Find the minimum x-coord from the list of contour points. */ + *xmin = minv(contour_x, ncontour); + /* Find the minimum y-coord from the list of contour points. */ + *ymin = minv(contour_y, ncontour); + /* Find the maximum x-coord from the list of contour points. */ + *xmax = maxv(contour_x, ncontour); + /* Find the maximum y-coord from the list of contour points. */ + *ymax = maxv(contour_y, ncontour); +} + +/************************************************************************* +************************************************************************** +#cat: fix_edge_pixel_pair - Takes a pair of pixel points with the first +#cat: pixel on a feature and the second adjacent and off the feature, +#cat: determines if the pair neighbor diagonally. If they do, their +#cat: locations are adjusted so that the resulting pair retains the +#cat: same pixel values, but are neighboring either to the N,S,E or W. +#cat: This routine is needed in order to prepare the pixel pair for +#cat: contour tracing. + + Input: + feat_x - pointer to x-pixel coord on feature + feat_y - pointer to y-pixel coord on feature + edge_x - pointer to x-pixel coord on edge of feature + edge_y - pointer to y-pixel coord on edge of feature + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + feat_x - pointer to resulting x-pixel coord on feature + feat_y - pointer to resulting y-pixel coord on feature + edge_x - pointer to resulting x-pixel coord on edge of feature + edge_y - pointer to resulting y-pixel coord on edge of feature +**************************************************************************/ +void fix_edge_pixel_pair(int *feat_x, int *feat_y, int *edge_x, int *edge_y, + unsigned char *bdata, const int iw, const int ih) +{ + int dx, dy; + int px, py, cx, cy; + int feature_pix; + + /* Get the pixel value of the feature. */ + feature_pix = *(bdata + ((*feat_y) * iw) + (*feat_x)); + + /* Store the input points to current and previous points. */ + cx = *feat_x; + cy = *feat_y; + px = *edge_x; + py = *edge_y; + + /* Compute detlas between current and previous point. */ + dx = px - cx; + dy = py - cy; + + /* If previous point (P) is diagonal neighbor of */ + /* current point (C)... This is a problem because */ + /* the contour tracing routine requires that the */ + /* "edge" pixel be north, south, east, or west of */ + /* of the feature point. If the previous pixel is */ + /* diagonal neighbor, then we need to adjust either */ + /* the positon of te previous or current pixel. */ + if((abs(dx)==1) && (abs(dy)==1)){ + /* Then we have one of the 4 following conditions: */ + /* */ + /* *C C* */ + /* 1. P* 2. P* 3. *P 4. *P */ + /* *C C* */ + /* */ + /* dx = -1 -1 1 1 */ + /* dy = 1 -1 -1 1 */ + /* */ + /* Want to test values in positions of '*': */ + /* Let point P == (px, py) */ + /* p1 == '*' positon where x changes */ + /* p2 == '*' positon where y changes */ + /* */ + /* p1 = px+1,py px+1,py px-1,py px-1,py */ + /* p2 = px,py-1 px,py+1 px,py+1 px,py-1 */ + /* */ + /* These can all be rewritten: */ + /* p1 = px-dx,py */ + /* p2 = px,py-dy */ + + /* Check if 'p1' is NOT the value we are searching for... */ + if(*(bdata+(py*iw)+(px-dx)) != feature_pix) + /* Then set x-coord of edge pixel to p1. */ + px -= dx; + /* Check if 'p2' is NOT the value we are searching for... */ + else if(*(bdata+((py-dy)*iw)+px) != feature_pix) + /* Then set y-coord of edge pixel to p2. */ + py -= dy; + /* Otherwise, the current pixel 'C' is exposed on a corner ... */ + else{ + /* Set pixel 'C' to 'p1', which also has the pixel */ + /* value we are searching for. */ + cy += dy; + } + + /* Set the pointers to the resulting values. */ + *feat_x = cx; + *feat_y = cy; + *edge_x = px; + *edge_y = py; + } + + /* Otherwise, nothing has changed. */ +} diff --git a/libfprint/nbis/mindtct/detect.c b/libfprint/nbis/mindtct/detect.c new file mode 100644 index 0000000..3f56738 --- /dev/null +++ b/libfprint/nbis/mindtct/detect.c @@ -0,0 +1,709 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: DETECT.C + AUTHOR: Michael D. Garris + DATE: 08/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Takes an 8-bit grayscale fingerpinrt image and detects minutiae + as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + lfs_detect_minutiae() + lfs_detect_minutiae_V2() + +***********************************************************************/ + +#include +#include +#include +#include +#include + +/************************************************************************* +#cat: lfs_detect_minutiae - Takes a grayscale fingerprint image (of arbitrary +#cat: size), and returns a map of directional ridge flow in the image +#cat: (2 versions), a binarized image designating ridges from valleys, +#cat: and a list of minutiae (including position, type, direction, +#cat: neighbors, and ridge counts to neighbors). + + Input: + idata - input 8-bit grayscale fingerprint image data + iw - width (in pixels) of the image + ih - height (in pixels) of the image + lfsparms - parameters and thresholds for controlling LFS + Output: + ominutiae - resulting list of minutiae + oimap - resulting IMAP + {invalid (-1) or valid ridge directions} + onmap - resulting NMAP + {invalid (-1), high-curvature (-2), blanked blocks {-3} or + valid ridge directions} + omw - width (in blocks) of image maps + omh - height (in blocks) of image maps + obdata - resulting binarized image + {0 = black pixel (ridge) and 255 = white pixel (valley)} + obw - width (in pixels) of the binary image + obh - height (in pixels) of the binary image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int lfs_detect_minutiae(MINUTIAE **ominutiae, + int **oimap, int **onmap, int *omw, int *omh, + unsigned char **obdata, int *obw, int *obh, + unsigned char *idata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + unsigned char *pdata, *bdata; + int pw, ph, bw, bh; + DIR2RAD *dir2rad; + DFTWAVES *dftwaves; + ROTGRIDS *dftgrids; + ROTGRIDS *dirbingrids; + int *imap, *nmap, mw, mh; + int ret, maxpad; + MINUTIAE *minutiae; + + set_timer(total_timer); + + /******************/ + /* INITIALIZATION */ + /******************/ + + /* If LOG_REPORT defined, open log report file. */ + if((ret = open_logfile())) + /* If system error, exit with error code. */ + return(ret); + + /* Determine the maximum amount of image padding required to support */ + /* LFS processes. */ + maxpad = get_max_padding(lfsparms->blocksize, + lfsparms->dirbin_grid_w, lfsparms->dirbin_grid_h, + lfsparms->isobin_grid_dim); + + /* Initialize lookup table for converting integer IMAP directions */ + /* to angles in radians. */ + if((ret = init_dir2rad(&dir2rad, lfsparms->num_directions))){ + /* Free memory allocated to this point. */ + return(ret); + } + + /* Initialize wave form lookup tables for DFT analyses. */ + /* used for direction binarization. */ + if((ret = init_dftwaves(&dftwaves, dft_coefs, lfsparms->num_dft_waves, + lfsparms->blocksize))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + return(ret); + } + + /* Initialize lookup table for pixel offsets to rotated grids */ + /* used for DFT analyses. */ + if((ret = init_rotgrids(&dftgrids, iw, ih, maxpad, + lfsparms->start_dir_angle, lfsparms->num_directions, + lfsparms->blocksize, lfsparms->blocksize, + RELATIVE2ORIGIN))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + return(ret); + } + + /* Pad input image based on max padding. */ + if(maxpad > 0){ /* May not need to pad at all */ + if((ret = pad_uchar_image(&pdata, &pw, &ph, idata, iw, ih, + maxpad, lfsparms->pad_value))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + return(ret); + } + } + else{ + /* If padding is unnecessary, then copy the input image. */ + pdata = (unsigned char *)malloc(iw*ih); + if(pdata == (unsigned char *)NULL){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + fprintf(stderr, "ERROR : lfs_detect_minutiae : malloc : pdata\n"); + return(-430); + } + memcpy(pdata, idata, iw*ih); + pw = iw; + ph = ih; + } + + /* Scale input image to 6 bits [0..63] */ + /* !!! Would like to remove this dependency eventualy !!! */ + /* But, the DFT computations will need to be changed, and */ + /* could not get this work upon first attempt. */ + bits_8to6(pdata, pw, ph); + + print2log("\nINITIALIZATION AND PADDING DONE\n"); + + /******************/ + /* IMAP */ + /******************/ + set_timer(imap_timer); + + /* Generate IMAP for the input image. */ + if((ret = gen_imap(&imap, &mw, &mh, pdata, pw, ph, dir2rad, + dftwaves, dftgrids, lfsparms))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + free(pdata); + return(ret); + } + + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + + print2log("\nIMAP DONE\n"); + + /* Generate NMAP from the IMAP of the input image. */ + if((ret = gen_nmap(&nmap, imap, mw, mh, lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(imap); + return(ret); + } + + print2log("\nNMAP DONE\n"); + + time_accum(imap_timer, imap_time); + + /******************/ + /* BINARIZARION */ + /******************/ + set_timer(bin_timer); + + /* Initialize lookup table for pixel offsets to rotated grids */ + /* used for directional binarization. */ + if((ret = init_rotgrids(&dirbingrids, iw, ih, maxpad, + lfsparms->start_dir_angle, lfsparms->num_directions, + lfsparms->dirbin_grid_w, lfsparms->dirbin_grid_h, + RELATIVE2CENTER))){ + /* Free memory allocated to this point. */ + free(pdata); + free(imap); + free(nmap); + return(ret); + } + + /* Binarize input image based on NMAP information. */ + if((ret = binarize(&bdata, &bw, &bh, pdata, pw, ph, nmap, mw, mh, + dirbingrids, lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(imap); + free(nmap); + free_rotgrids(dirbingrids); + return(ret); + } + free_rotgrids(dirbingrids); + + /* Check dimension of binary image. If they are different from */ + /* the input image, then ERROR. */ + if((iw != bw) || (ih != bh)){ + /* Free memory allocated to this point. */ + free(pdata); + free(imap); + free(nmap); + free(bdata); + fprintf(stderr, + "ERROR : lfs_detect_minutiae : binary image has bad dimensions : %d, %d\n", + bw, bh); + return(-431); + } + + print2log("\nBINARIZATION DONE\n"); + + time_accum(bin_timer, bin_time); + + /******************/ + /* DETECTION */ + /******************/ + set_timer(minutia_timer); + + /* Convert 8-bit grayscale binary image [0,255] to */ + /* 8-bit binary image [0,1]. */ + gray2bin(1, 1, 0, bdata, iw, ih); + + /* Allocate list of maximum number of minutia pointers. */ + if((ret = alloc_minutiae(&minutiae, MAX_MINUTIAE))){ + return(ret); + } + + /* Detect the minutiae in the binarized image. */ + if((ret = detect_minutiae(minutiae, bdata, iw, ih, imap, nmap, mw, mh, + lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(imap); + free(nmap); + free(bdata); + return(ret); + } + + time_accum(minutia_timer, minutia_time); + + set_timer(rm_minutia_timer); + + if((ret = remove_false_minutia(minutiae, bdata, iw, ih, nmap, mw, mh, + lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(imap); + free(nmap); + free(bdata); + free_minutiae(minutiae); + return(ret); + } + + print2log("\nMINUTIA DETECTION DONE\n"); + + time_accum(rm_minutia_timer, rm_minutia_time); + + /******************/ + /* RIDGE COUNTS */ + /******************/ + set_timer(ridge_count_timer); + + if((ret = count_minutiae_ridges(minutiae, bdata, iw, ih, lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(imap); + free(nmap); + free(bdata); + free_minutiae(minutiae); + return(ret); + } + + + print2log("\nNEIGHBOR RIDGE COUNT DONE\n"); + + time_accum(ridge_count_timer, ridge_count_time); + + /******************/ + /* WRAP-UP */ + /******************/ + + /* Convert 8-bit binary image [0,1] to 8-bit */ + /* grayscale binary image [0,255]. */ + gray2bin(1, 255, 0, bdata, iw, ih); + + /* Deallocate working memory. */ + free(pdata); + + /* Assign results to output pointers. */ + *oimap = imap; + *onmap = nmap; + *omw = mw; + *omh = mh; + *obdata = bdata; + *obw = bw; + *obh = bh; + *ominutiae = minutiae; + + time_accum(total_timer, total_time); + + /******************/ + /* PRINT TIMINGS */ + /******************/ + /* These Timings will print when TIMER is defined. */ + /* print IMAP generation timing statistics */ + print_time(stderr, "TIMER: IMAP time = %f (secs)\n", imap_time); + /* print binarization timing statistics */ + print_time(stderr, "TIMER: Binarization time = %f (secs)\n", bin_time); + /* print minutia detection timing statistics */ + print_time(stderr, "TIMER: Minutia Detection time = %f (secs)\n", + minutia_time); + /* print minutia removal timing statistics */ + print_time(stderr, "TIMER: Minutia Removal time = %f (secs)\n", + rm_minutia_time); + /* print neighbor ridge count timing statistics */ + print_time(stderr, "TIMER: Neighbor Ridge Counting time = %f (secs)\n", + ridge_count_time); + /* print total timing statistics */ + print_time(stderr, "TIMER: Total time = %f (secs)\n", total_time); + + /* If LOG_REPORT defined, close log report file. */ + if((ret = close_logfile())) + return(ret); + + return(0); +} + +/************************************************************************* +#cat: lfs_detect_minutiae_V2 - Takes a grayscale fingerprint image (of +#cat: arbitrary size), and returns a set of image block maps, +#cat: a binarized image designating ridges from valleys, +#cat: and a list of minutiae (including position, reliability, +#cat: type, direction, neighbors, and ridge counts to neighbors). +#cat: The image maps include a ridge flow directional map, +#cat: a map of low contrast blocks, a map of low ridge flow blocks. +#cat: and a map of high-curvature blocks. + + Input: + idata - input 8-bit grayscale fingerprint image data + iw - width (in pixels) of the image + ih - height (in pixels) of the image + lfsparms - parameters and thresholds for controlling LFS + + Output: + ominutiae - resulting list of minutiae + odmap - resulting Direction Map + {invalid (-1) or valid ridge directions} + olcmap - resulting Low Contrast Map + {low contrast (TRUE), high contrast (FALSE)} + olfmap - resulting Low Ridge Flow Map + {low ridge flow (TRUE), high ridge flow (FALSE)} + ohcmap - resulting High Curvature Map + {high curvature (TRUE), low curvature (FALSE)} + omw - width (in blocks) of image maps + omh - height (in blocks) of image maps + obdata - resulting binarized image + {0 = black pixel (ridge) and 255 = white pixel (valley)} + obw - width (in pixels) of the binary image + obh - height (in pixels) of the binary image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int lfs_detect_minutiae_V2(MINUTIAE **ominutiae, + int **odmap, int **olcmap, int **olfmap, int **ohcmap, + int *omw, int *omh, + unsigned char **obdata, int *obw, int *obh, + unsigned char *idata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + unsigned char *pdata, *bdata; + int pw, ph, bw, bh; + DIR2RAD *dir2rad; + DFTWAVES *dftwaves; + ROTGRIDS *dftgrids; + ROTGRIDS *dirbingrids; + int *direction_map, *low_contrast_map, *low_flow_map, *high_curve_map; + int mw, mh; + int ret, maxpad; + MINUTIAE *minutiae; + + set_timer(total_timer); + + /******************/ + /* INITIALIZATION */ + /******************/ + + /* If LOG_REPORT defined, open log report file. */ + if((ret = open_logfile())) + /* If system error, exit with error code. */ + return(ret); + + /* Determine the maximum amount of image padding required to support */ + /* LFS processes. */ + maxpad = get_max_padding_V2(lfsparms->windowsize, lfsparms->windowoffset, + lfsparms->dirbin_grid_w, lfsparms->dirbin_grid_h); + + /* Initialize lookup table for converting integer directions */ + /* to angles in radians. */ + if((ret = init_dir2rad(&dir2rad, lfsparms->num_directions))){ + /* Free memory allocated to this point. */ + return(ret); + } + + /* Initialize wave form lookup tables for DFT analyses. */ + /* used for direction binarization. */ + if((ret = init_dftwaves(&dftwaves, dft_coefs, lfsparms->num_dft_waves, + lfsparms->windowsize))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + return(ret); + } + + /* Initialize lookup table for pixel offsets to rotated grids */ + /* used for DFT analyses. */ + if((ret = init_rotgrids(&dftgrids, iw, ih, maxpad, + lfsparms->start_dir_angle, lfsparms->num_directions, + lfsparms->windowsize, lfsparms->windowsize, + RELATIVE2ORIGIN))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + return(ret); + } + + /* Pad input image based on max padding. */ + if(maxpad > 0){ /* May not need to pad at all */ + if((ret = pad_uchar_image(&pdata, &pw, &ph, idata, iw, ih, + maxpad, lfsparms->pad_value))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + return(ret); + } + } + else{ + /* If padding is unnecessary, then copy the input image. */ + pdata = (unsigned char *)malloc(iw*ih); + if(pdata == (unsigned char *)NULL){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + fprintf(stderr, "ERROR : lfs_detect_minutiae_V2 : malloc : pdata\n"); + return(-580); + } + memcpy(pdata, idata, iw*ih); + pw = iw; + ph = ih; + } + + /* Scale input image to 6 bits [0..63] */ + /* !!! Would like to remove this dependency eventualy !!! */ + /* But, the DFT computations will need to be changed, and */ + /* could not get this work upon first attempt. Also, if not */ + /* careful, I think accumulated power magnitudes may overflow */ + /* doubles. */ + bits_8to6(pdata, pw, ph); + + print2log("\nINITIALIZATION AND PADDING DONE\n"); + + /******************/ + /* MAPS */ + /******************/ + set_timer(imap_timer); + + /* Generate block maps from the input image. */ + if((ret = gen_image_maps(&direction_map, &low_contrast_map, + &low_flow_map, &high_curve_map, &mw, &mh, + pdata, pw, ph, dir2rad, dftwaves, dftgrids, lfsparms))){ + /* Free memory allocated to this point. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + free(pdata); + return(ret); + } + /* Deallocate working memories. */ + free_dir2rad(dir2rad); + free_dftwaves(dftwaves); + free_rotgrids(dftgrids); + + print2log("\nMAPS DONE\n"); + + time_accum(imap_timer, imap_time); + + /******************/ + /* BINARIZARION */ + /******************/ + set_timer(bin_timer); + + /* Initialize lookup table for pixel offsets to rotated grids */ + /* used for directional binarization. */ + if((ret = init_rotgrids(&dirbingrids, iw, ih, maxpad, + lfsparms->start_dir_angle, lfsparms->num_directions, + lfsparms->dirbin_grid_w, lfsparms->dirbin_grid_h, + RELATIVE2CENTER))){ + /* Free memory allocated to this point. */ + free(pdata); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + return(ret); + } + + /* Binarize input image based on NMAP information. */ + if((ret = binarize_V2(&bdata, &bw, &bh, + pdata, pw, ph, direction_map, mw, mh, + dirbingrids, lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free_rotgrids(dirbingrids); + return(ret); + } + + /* Deallocate working memory. */ + free_rotgrids(dirbingrids); + + /* Check dimension of binary image. If they are different from */ + /* the input image, then ERROR. */ + if((iw != bw) || (ih != bh)){ + /* Free memory allocated to this point. */ + free(pdata); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free(bdata); + fprintf(stderr, "ERROR : lfs_detect_minutiae_V2 :"); + fprintf(stderr,"binary image has bad dimensions : %d, %d\n", + bw, bh); + return(-581); + } + + print2log("\nBINARIZATION DONE\n"); + + time_accum(bin_timer, bin_time); + + /******************/ + /* DETECTION */ + /******************/ + set_timer(minutia_timer); + + /* Convert 8-bit grayscale binary image [0,255] to */ + /* 8-bit binary image [0,1]. */ + gray2bin(1, 1, 0, bdata, iw, ih); + + /* Allocate initial list of minutia pointers. */ + if((ret = alloc_minutiae(&minutiae, MAX_MINUTIAE))){ + return(ret); + } + + /* Detect the minutiae in the binarized image. */ + if((ret = detect_minutiae_V2(minutiae, bdata, iw, ih, + direction_map, low_flow_map, high_curve_map, + mw, mh, lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free(bdata); + return(ret); + } + + time_accum(minutia_timer, minutia_time); + + set_timer(rm_minutia_timer); + + if((ret = remove_false_minutia_V2(minutiae, bdata, iw, ih, + direction_map, low_flow_map, high_curve_map, mw, mh, + lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free(bdata); + free_minutiae(minutiae); + return(ret); + } + + print2log("\nMINUTIA DETECTION DONE\n"); + + time_accum(rm_minutia_timer, rm_minutia_time); + + /******************/ + /* RIDGE COUNTS */ + /******************/ + set_timer(ridge_count_timer); + + if((ret = count_minutiae_ridges(minutiae, bdata, iw, ih, lfsparms))){ + /* Free memory allocated to this point. */ + free(pdata); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free_minutiae(minutiae); + return(ret); + } + + + print2log("\nNEIGHBOR RIDGE COUNT DONE\n"); + + time_accum(ridge_count_timer, ridge_count_time); + + /******************/ + /* WRAP-UP */ + /******************/ + + /* Convert 8-bit binary image [0,1] to 8-bit */ + /* grayscale binary image [0,255]. */ + gray2bin(1, 255, 0, bdata, iw, ih); + + /* Deallocate working memory. */ + free(pdata); + + /* Assign results to output pointers. */ + *odmap = direction_map; + *olcmap = low_contrast_map; + *olfmap = low_flow_map; + *ohcmap = high_curve_map; + *omw = mw; + *omh = mh; + *obdata = bdata; + *obw = bw; + *obh = bh; + *ominutiae = minutiae; + + time_accum(total_timer, total_time); + + /******************/ + /* PRINT TIMINGS */ + /******************/ + /* These Timings will print when TIMER is defined. */ + /* print MAP generation timing statistics */ + print_time(stderr, "TIMER: MAPS time = %f (secs)\n", imap_time); + /* print binarization timing statistics */ + print_time(stderr, "TIMER: Binarization time = %f (secs)\n", bin_time); + /* print minutia detection timing statistics */ + print_time(stderr, "TIMER: Minutia Detection time = %f (secs)\n", + minutia_time); + /* print minutia removal timing statistics */ + print_time(stderr, "TIMER: Minutia Removal time = %f (secs)\n", + rm_minutia_time); + /* print neighbor ridge count timing statistics */ + print_time(stderr, "TIMER: Neighbor Ridge Counting time = %f (secs)\n", + ridge_count_time); + /* print total timing statistics */ + print_time(stderr, "TIMER: Total time = %f (secs)\n", total_time); + + /* If LOG_REPORT defined, close log report file. */ + if((ret = close_logfile())) + return(ret); + + return(0); +} + diff --git a/libfprint/nbis/mindtct/dft.c b/libfprint/nbis/mindtct/dft.c new file mode 100644 index 0000000..1159fa1 --- /dev/null +++ b/libfprint/nbis/mindtct/dft.c @@ -0,0 +1,358 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: DFT.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for conducting Discrete Fourier + Transforms (DFT) analysis on a block of image data as part of + the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + dft_dir_powers() + sum_rot_block_rows() + dft_power() + dft_power_stats() + get_max_norm() + sort_dft_waves() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: dft_dir_powers - Conducts the DFT analysis on a block of image data. +#cat: The image block is sampled across a range of orientations +#cat: (directions) and multiple wave forms of varying frequency are +#cat: applied at each orientation. At each orentation, pixels are +#cat: accumulated along each rotated pixel row, creating a vector +#cat: of pixel row sums. Each DFT wave form is then applied +#cat: individually to this vector of pixel row sums. A DFT power +#cat: value is computed for each wave form (frequency0 at each +#cat: orientaion within the image block. Therefore, the resulting DFT +#cat: power vectors are of dimension (N Waves X M Directions). +#cat: The power signatures derived form this process are used to +#cat: determine dominant direction flow within the image block. + + Input: + pdata - the padded input image. It is important that the image + be properly padded, or else the sampling at various block + orientations may result in accessing unkown memory. + blkoffset - the pixel offset form the origin of the padded image to + the origin of the current block in the image + pw - the width (in pixels) of the padded input image + ph - the height (in pixels) of the padded input image + dftwaves - structure containing the DFT wave forms + dftgrids - structure containing the rotated pixel grid offsets + Output: + powers - DFT power computed from each wave form frequencies at each + orientation (direction) in the current image block + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int dft_dir_powers(double **powers, unsigned char *pdata, + const int blkoffset, const int pw, const int ph, + const DFTWAVES *dftwaves, const ROTGRIDS *dftgrids) +{ + int w, dir; + int *rowsums; + unsigned char *blkptr; + + /* Allocate line sum vector, and initialize to zeros */ + /* This routine requires square block (grid), so ERROR otherwise. */ + if(dftgrids->grid_w != dftgrids->grid_h){ + fprintf(stderr, "ERROR : dft_dir_powers : DFT grids must be square\n"); + return(-90); + } + rowsums = (int *)malloc(dftgrids->grid_w * sizeof(int)); + if(rowsums == (int *)NULL){ + fprintf(stderr, "ERROR : dft_dir_powers : malloc : rowsums\n"); + return(-91); + } + + /* Foreach direction ... */ + for(dir = 0; dir < dftgrids->ngrids; dir++){ + /* Compute vector of line sums from rotated grid */ + blkptr = pdata + blkoffset; + sum_rot_block_rows(rowsums, blkptr, + dftgrids->grids[dir], dftgrids->grid_w); + + /* Foreach DFT wave ... */ + for(w = 0; w < dftwaves->nwaves; w++){ + dft_power(&(powers[w][dir]), rowsums, + dftwaves->waves[w], dftwaves->wavelen); + } + } + + /* Deallocate working memory. */ + free(rowsums); + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: sum_rot_block_rows - Computes a vector or pixel row sums by sampling +#cat: the current image block at a given orientation. The +#cat: sampling is conducted using a precomputed set of rotated +#cat: pixel offsets (called a grid) relative to the orgin of +#cat: the image block. + + Input: + blkptr - the pixel address of the origin of the current image block + grid_offsets - the rotated pixel offsets for a block-sized grid + rotated according to a specific orientation + blocksize - the width and height of the image block and thus the size + of the rotated grid + Output: + rowsums - the resulting vector of pixel row sums +**************************************************************************/ +void sum_rot_block_rows(int *rowsums, const unsigned char *blkptr, + const int *grid_offsets, const int blocksize) +{ + int ix, iy, gi; + + /* Initialize rotation offset index. */ + gi = 0; + + /* For each row in block ... */ + for(iy = 0; iy < blocksize; iy++){ + /* The sums are accumlated along the rotated rows of the grid, */ + /* so initialize row sum to 0. */ + rowsums[iy] = 0; + /* Foreach column in block ... */ + for(ix = 0; ix < blocksize; ix++){ + /* Accumulate pixel value at rotated grid position in image */ + rowsums[iy] += *(blkptr + grid_offsets[gi]); + gi++; + } + } +} + +/************************************************************************* +************************************************************************** +#cat: dft_power - Computes the DFT power by applying a specific wave form +#cat: frequency to a vector of pixel row sums computed from a +#cat: specific orientation of the block image + + Input: + rowsums - accumulated rows of pixels from within a rotated grid + overlaying an input image block + wave - the wave form (cosine and sine components) at a specific + frequency + wavelen - the length of the wave form (must match the height of the + image block which is the length of the rowsum vector) + Output: + power - the computed DFT power for the given wave form at the + given orientation within the image block +**************************************************************************/ +void dft_power(double *power, const int *rowsums, + const DFTWAVE *wave, const int wavelen) +{ + int i; + double cospart, sinpart; + + /* Initialize accumulators */ + cospart = 0.0; + sinpart = 0.0; + + /* Accumulate cos and sin components of DFT. */ + for(i = 0; i < wavelen; i++){ + /* Multiply each rotated row sum by its */ + /* corresponding cos or sin point in DFT wave. */ + cospart += (rowsums[i] * wave->cos[i]); + sinpart += (rowsums[i] * wave->sin[i]); + } + + /* Power is the sum of the squared cos and sin components */ + *power = (cospart * cospart) + (sinpart * sinpart); +} + +/************************************************************************* +************************************************************************** +#cat: dft_power_stats - Derives statistics from a set of DFT power vectors. +#cat: Statistics are computed for all but the lowest frequency +#cat: wave form, including the Maximum power for each wave form, +#cat: the direction at which the maximum power occured, and a +#cat: normalized value for the maximum power. In addition, the +#cat: statistics are ranked in descending order based on normalized +#cat: squared maximum power. These statistics are fundamental +#cat: to selecting a dominant direction flow for the current +#cat: input image block. + + Input: + powers - DFT power vectors (N Waves X M Directions) computed for + the current image block from which the values in the + statistics arrays are derived + fw - the beginning of the range of wave form indices from which + the statistcs are to derived + tw - the ending of the range of wave form indices from which + the statistcs are to derived (last index is tw-1) + ndirs - number of orientations (directions) at which the DFT + analysis was conducted + Output: + wis - list of ranked wave form indicies of the corresponding + statistics based on normalized squared maximum power. These + indices will be used as indirect addresses when processing + the power statistics in descending order of "dominance" + powmaxs - array holding the maximum DFT power for each wave form + (other than the lowest frequecy) + powmax_dirs - array to holding the direction corresponding to + each maximum power value in powmaxs + pownorms - array to holding the normalized maximum powers corresponding + to each value in powmaxs + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int dft_power_stats(int *wis, double *powmaxs, int *powmax_dirs, + double *pownorms, double **powers, + const int fw, const int tw, const int ndirs) +{ + int w, i; + int ret; /* return code */ + + for(w = fw, i = 0; w < tw; w++, i++){ + get_max_norm(&(powmaxs[i]), &(powmax_dirs[i]), + &(pownorms[i]), powers[w], ndirs); + } + + /* Get sorted order of applied DFT waves based on normalized power */ + if((ret = sort_dft_waves(wis, powmaxs, pownorms, tw-fw))) + return(ret); + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: get_max_norm - Analyses a DFT power vector for a specific wave form +#cat: applied at different orientations (directions) to the +#cat: current image block. The routine retuns the maximum +#cat: power value in the vector, the direction at which the +#cat: maximum occurs, and a normalized power value. The +#cat: normalized power is computed as the maximum power divided +#cat: by the average power across all the directions. These +#cat: simple statistics are fundamental to the selection of +#cat: a dominant direction flow for the image block. + + Input: + power_vector - the DFT power values derived form a specific wave form + applied at different directions + ndirs - the number of directions to which the wave form was applied + Output: + powmax - the maximum power value in the DFT power vector + powmax_dir - the direciton at which the maximum power value occured + pownorm - the normalized power corresponding to the maximum power +**************************************************************************/ +void get_max_norm(double *powmax, int *powmax_dir, + double *pownorm, const double *power_vector, const int ndirs) +{ + int dir; + double max_v, powsum; + int max_i; + double powmean; + + /* Find max power value and store corresponding direction */ + max_v = power_vector[0]; + max_i = 0; + + /* Sum the total power in a block at a given direction */ + powsum = power_vector[0]; + + /* For each direction ... */ + for(dir = 1; dir < ndirs; dir++){ + powsum += power_vector[dir]; + if(power_vector[dir] > max_v){ + max_v = power_vector[dir]; + max_i = dir; + } + } + + *powmax = max_v; + *powmax_dir = max_i; + + /* Powmean is used as denominator for pownorm, so setting */ + /* a non-zero minimum avoids possible division by zero. */ + powmean = max(powsum, MIN_POWER_SUM)/(double)ndirs; + + *pownorm = *powmax / powmean; +} + +/************************************************************************* +************************************************************************** +#cat: sort_dft_waves - Creates a ranked list of DFT wave form statistics +#cat: by sorting on the normalized squared maximum power. + + Input: + powmaxs - maximum DFT power for each wave form used to derive + statistics + pownorms - normalized maximum power corresponding to values in powmaxs + nstats - number of wave forms used to derive statistics (N Wave - 1) + Output: + wis - sorted list of indices corresponding to the ranked set of + wave form statistics. These indices will be used as + indirect addresses when processing the power statistics + in descending order of "dominance" + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int sort_dft_waves(int *wis, const double *powmaxs, const double *pownorms, + const int nstats) +{ + int i; + double *pownorms2; + + /* Allocate normalized power^2 array */ + pownorms2 = (double *)malloc(nstats * sizeof(double)); + if(pownorms2 == (double *)NULL){ + fprintf(stderr, "ERROR : sort_dft_waves : malloc : pownorms2\n"); + return(-100); + } + + for(i = 0; i < nstats; i++){ + /* Wis will hold the sorted statistic indices when all is done. */ + wis[i] = i; + /* This is normalized squared max power. */ + pownorms2[i] = powmaxs[i] * pownorms[i]; + } + + /* Sort the statistic indices on the normalized squared power. */ + bubble_sort_double_dec_2(pownorms2, wis, nstats); + + /* Deallocate the working memory. */ + free(pownorms2); + + return(0); +} + diff --git a/libfprint/nbis/mindtct/free.c b/libfprint/nbis/mindtct/free.c new file mode 100644 index 0000000..fd7612e --- /dev/null +++ b/libfprint/nbis/mindtct/free.c @@ -0,0 +1,116 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: FREE.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + + Contains routines responsible for deallocating + memories required by the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + free_dir2rad() + free_dftwaves() + free_rotgrids() + free_dir_powers() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: free_dir2rad - Deallocates memory associated with a DIR2RAD structure + + Input: + dir2rad - pointer to memory to be freed +*************************************************************************/ +void free_dir2rad(DIR2RAD *dir2rad) +{ + free(dir2rad->cos); + free(dir2rad->sin); + free(dir2rad); +} + +/************************************************************************* +************************************************************************** +#cat: free_dftwaves - Deallocates the memory associated with a DFTWAVES +#cat: structure + + Input: + dftwaves - pointer to memory to be freed +**************************************************************************/ +void free_dftwaves(DFTWAVES *dftwaves) +{ + int i; + + for(i = 0; i < dftwaves->nwaves; i++){ + free(dftwaves->waves[i]->cos); + free(dftwaves->waves[i]->sin); + free(dftwaves->waves[i]); + } + free(dftwaves->waves); + free(dftwaves); +} + +/************************************************************************* +************************************************************************** +#cat: free_rotgrids - Deallocates the memory associated with a ROTGRIDS +#cat: structure + + Input: + rotgrids - pointer to memory to be freed +**************************************************************************/ +void free_rotgrids(ROTGRIDS *rotgrids) +{ + int i; + + for(i = 0; i < rotgrids->ngrids; i++) + free(rotgrids->grids[i]); + free(rotgrids->grids); + free(rotgrids); +} + +/************************************************************************* +************************************************************************** +#cat: free_dir_powers - Deallocate memory associated with DFT power vectors + + Input: + powers - vectors of DFT power values (N Waves X M Directions) + nwaves - number of DFT wave forms used +**************************************************************************/ +void free_dir_powers(double **powers, const int nwaves) +{ + int w; + + for(w = 0; w < nwaves; w++) + free(powers[w]); + + free(powers); +} + diff --git a/libfprint/nbis/mindtct/getmin.c b/libfprint/nbis/mindtct/getmin.c new file mode 100644 index 0000000..22404f1 --- /dev/null +++ b/libfprint/nbis/mindtct/getmin.c @@ -0,0 +1,155 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: GETMIN.C + AUTHOR: Michael D. Garris + DATE: 09/10/2004 + UPDATED: 03/16/2005 by MDG + + Takes an 8-bit grayscale fingerpinrt image and detects minutiae + as part of the NIST Latent Fingerprint System (LFS), returning + minutiae with final reliabilities and maps including a merged + quality map. + +*********************************************************************** + ROUTINES: + get_minutiae() + +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: get_minutiae - Takes a grayscale fingerprint image, binarizes the input +#cat: image, and detects minutiae points using LFS Version 2. +#cat: The routine passes back the detected minutiae, the +#cat: binarized image, and a set of image quality maps. + + Input: + idata - grayscale fingerprint image data + iw - width (in pixels) of the grayscale image + ih - height (in pixels) of the grayscale image + id - pixel depth (in bits) of the grayscale image + ppmm - the scan resolution (in pixels/mm) of the grayscale image + lfsparms - parameters and thresholds for controlling LFS + Output: + ominutiae - points to a structure containing the + detected minutiae + oquality_map - resulting integrated image quality map + odirection_map - resulting direction map + olow_contrast_map - resulting low contrast map + olow_flow_map - resulting low ridge flow map + ohigh_curve_map - resulting high curvature map + omap_w - width (in blocks) of image maps + omap_h - height (in blocks) of image maps + obdata - points to binarized image data + obw - width (in pixels) of binarized image + obh - height (in pixels) of binarized image + obd - pixel depth (in bits) of binarized image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int get_minutiae(MINUTIAE **ominutiae, int **oquality_map, + int **odirection_map, int **olow_contrast_map, + int **olow_flow_map, int **ohigh_curve_map, + int *omap_w, int *omap_h, + unsigned char **obdata, int *obw, int *obh, int *obd, + unsigned char *idata, const int iw, const int ih, + const int id, const double ppmm, const LFSPARMS *lfsparms) +{ + int ret; + MINUTIAE *minutiae; + int *direction_map, *low_contrast_map, *low_flow_map; + int *high_curve_map, *quality_map; + int map_w, map_h; + unsigned char *bdata; + int bw, bh; + + /* If input image is not 8-bit grayscale ... */ + if(id != 8){ + fprintf(stderr, "ERROR : get_minutiae : input image pixel "); + fprintf(stderr, "depth = %d != 8.\n", id); + return(-2); + } + + /* Detect minutiae in grayscale fingerpeint image. */ + if((ret = lfs_detect_minutiae_V2(&minutiae, + &direction_map, &low_contrast_map, + &low_flow_map, &high_curve_map, + &map_w, &map_h, + &bdata, &bw, &bh, + idata, iw, ih, lfsparms))){ + return(ret); + } + + /* Build integrated quality map. */ + if((ret = gen_quality_map(&quality_map, + direction_map, low_contrast_map, + low_flow_map, high_curve_map, map_w, map_h))){ + free_minutiae(minutiae); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free(bdata); + return(ret); + } + + /* Assign reliability from quality map. */ + if((ret = combined_minutia_quality(minutiae, quality_map, map_w, map_h, + lfsparms->blocksize, + idata, iw, ih, id, ppmm))){ + free_minutiae(minutiae); + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free(high_curve_map); + free(quality_map); + free(bdata); + return(ret); + } + + /* Set output pointers. */ + *ominutiae = minutiae; + *oquality_map = quality_map; + *odirection_map = direction_map; + *olow_contrast_map = low_contrast_map; + *olow_flow_map = low_flow_map; + *ohigh_curve_map = high_curve_map; + *omap_w = map_w; + *omap_h = map_h; + *obdata = bdata; + *obw = bw; + *obh = bh; + *obd = id; + + /* Return normally. */ + return(0); +} diff --git a/libfprint/nbis/mindtct/globals.c b/libfprint/nbis/mindtct/globals.c new file mode 100644 index 0000000..01454db --- /dev/null +++ b/libfprint/nbis/mindtct/globals.c @@ -0,0 +1,293 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: GLOBALS.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + + Contains general global variable definitions required by the + NIST Latent Fingerprint System (LFS). +***********************************************************************/ + +#include + +/*************************************************************************/ +/* GOBAL DECLARATIONS */ +/*************************************************************************/ + +#ifdef LOG_REPORT +FILE *logfp; +#endif + +/* Constants (C) for defining 4 DFT frequencies, where */ +/* frequency is defined as C*(PI_FACTOR). PI_FACTOR */ +/* regulates the period of the function in x, so: */ +/* 1 = one period in range X. */ +/* 2 = twice the frequency in range X. */ +/* 3 = three times the frequency in reange X. */ +/* 4 = four times the frequency in ranage X. */ +double dft_coefs[NUM_DFT_WAVES] = { 1,2,3,4 }; + +/* Allocate and initialize a global LFS parameters structure. */ +LFSPARMS lfsparms = { + /* Image Controls */ + PAD_VALUE, + JOIN_LINE_RADIUS, + + /* Map Controls */ + IMAP_BLOCKSIZE, + UNUSED_INT, /* windowsize */ + UNUSED_INT, /* windowoffset */ + NUM_DIRECTIONS, + START_DIR_ANGLE, + RMV_VALID_NBR_MIN, + DIR_STRENGTH_MIN, + DIR_DISTANCE_MAX, + SMTH_VALID_NBR_MIN, + VORT_VALID_NBR_MIN, + HIGHCURV_VORTICITY_MIN, + HIGHCURV_CURVATURE_MIN, + UNUSED_INT, /* min_interpolate_nbrs */ + UNUSED_INT, /* percentile_min_max */ + UNUSED_INT, /* min_contrast_delta */ + + /* DFT Controls */ + NUM_DFT_WAVES, + POWMAX_MIN, + POWNORM_MIN, + POWMAX_MAX, + FORK_INTERVAL, + FORK_PCT_POWMAX, + FORK_PCT_POWNORM, + + /* Binarization Controls */ + DIRBIN_GRID_W, + DIRBIN_GRID_H, + ISOBIN_GRID_DIM, + NUM_FILL_HOLES, + + /* Minutiae Detection Controls */ + MAX_MINUTIA_DELTA, + MAX_HIGH_CURVE_THETA, + HIGH_CURVE_HALF_CONTOUR, + MIN_LOOP_LEN, + MIN_LOOP_ASPECT_DIST, + MIN_LOOP_ASPECT_RATIO, + + /* Minutiae Link Controls */ + LINK_TABLE_DIM, + MAX_LINK_DIST, + MIN_THETA_DIST, + MAXTRANS, + SCORE_THETA_NORM, + SCORE_DIST_NORM, + SCORE_DIST_WEIGHT, + SCORE_NUMERATOR, + + /* False Minutiae Removal Controls */ + MAX_RMTEST_DIST, + MAX_HOOK_LEN, + MAX_HALF_LOOP, + TRANS_DIR_PIX, + SMALL_LOOP_LEN, + SIDE_HALF_CONTOUR, + INV_BLOCK_MARGIN, + RM_VALID_NBR_MIN, + UNUSED_INT, /* max_overlap_dist */ + UNUSED_INT, /* max_overlap_join_dist */ + UNUSED_INT, /* malformation_steps_1 */ + UNUSED_INT, /* malformation_steps_2 */ + UNUSED_DBL, /* min_malformation_ratio */ + UNUSED_INT, /* max_malformation_dist */ + PORES_TRANS_R, + PORES_PERP_STEPS, + PORES_STEPS_FWD, + PORES_STEPS_BWD, + PORES_MIN_DIST2, + PORES_MAX_RATIO, + + /* Ridge Counting Controls */ + MAX_NBRS, + MAX_RIDGE_STEPS +}; + + +/* Allocate and initialize VERSION 2 global LFS parameters structure. */ +LFSPARMS lfsparms_V2 = { + /* Image Controls */ + PAD_VALUE, + JOIN_LINE_RADIUS, + + /* Map Controls */ + MAP_BLOCKSIZE_V2, + MAP_WINDOWSIZE_V2, + MAP_WINDOWOFFSET_V2, + NUM_DIRECTIONS, + START_DIR_ANGLE, + RMV_VALID_NBR_MIN, + DIR_STRENGTH_MIN, + DIR_DISTANCE_MAX, + SMTH_VALID_NBR_MIN, + VORT_VALID_NBR_MIN, + HIGHCURV_VORTICITY_MIN, + HIGHCURV_CURVATURE_MIN, + MIN_INTERPOLATE_NBRS, + PERCENTILE_MIN_MAX, + MIN_CONTRAST_DELTA, + + /* DFT Controls */ + NUM_DFT_WAVES, + POWMAX_MIN, + POWNORM_MIN, + POWMAX_MAX, + FORK_INTERVAL, + FORK_PCT_POWMAX, + FORK_PCT_POWNORM, + + /* Binarization Controls */ + DIRBIN_GRID_W, + DIRBIN_GRID_H, + UNUSED_INT, /* isobin_grid_dim */ + NUM_FILL_HOLES, + + /* Minutiae Detection Controls */ + MAX_MINUTIA_DELTA, + MAX_HIGH_CURVE_THETA, + HIGH_CURVE_HALF_CONTOUR, + MIN_LOOP_LEN, + MIN_LOOP_ASPECT_DIST, + MIN_LOOP_ASPECT_RATIO, + + /* Minutiae Link Controls */ + UNUSED_INT, /* link_table_dim */ + UNUSED_INT, /* max_link_dist */ + UNUSED_INT, /* min_theta_dist */ + MAXTRANS, /* used for removing overlaps as well */ + UNUSED_DBL, /* score_theta_norm */ + UNUSED_DBL, /* score_dist_norm */ + UNUSED_DBL, /* score_dist_weight */ + UNUSED_DBL, /* score_numerator */ + + /* False Minutiae Removal Controls */ + MAX_RMTEST_DIST_V2, + MAX_HOOK_LEN_V2, + MAX_HALF_LOOP_V2, + TRANS_DIR_PIX_V2, + SMALL_LOOP_LEN, + SIDE_HALF_CONTOUR, + INV_BLOCK_MARGIN_V2, + RM_VALID_NBR_MIN, + MAX_OVERLAP_DIST, + MAX_OVERLAP_JOIN_DIST, + MALFORMATION_STEPS_1, + MALFORMATION_STEPS_2, + MIN_MALFORMATION_RATIO, + MAX_MALFORMATION_DIST, + PORES_TRANS_R, + PORES_PERP_STEPS, + PORES_STEPS_FWD, + PORES_STEPS_BWD, + PORES_MIN_DIST2, + PORES_MAX_RATIO, + + /* Ridge Counting Controls */ + MAX_NBRS, + MAX_RIDGE_STEPS +}; + +/* Variables for conducting 8-connected neighbor analyses. */ +/* Pixel neighbor offsets: 0 1 2 3 4 5 6 7 */ /* 7 0 1 */ +int nbr8_dx[] = { 0, 1, 1, 1, 0,-1,-1,-1 }; /* 6 C 2 */ +int nbr8_dy[] = { -1,-1, 0, 1, 1, 1, 0,-1 }; /* 5 4 3 */ + +/* The chain code lookup matrix for 8-connected neighbors. */ +/* Should put this in globals. */ +int chaincodes_nbr8[]={ 3, 2, 1, + 4,-1, 0, + 5, 6, 7}; + +/* Global array of feature pixel pairs. */ +FEATURE_PATTERN feature_patterns[]= + {{RIDGE_ENDING, /* a. Ridge Ending (appearing) */ + APPEARING, + {0,0}, + {0,1}, + {0,0}}, + + {RIDGE_ENDING, /* b. Ridge Ending (disappearing) */ + DISAPPEARING, + {0,0}, + {1,0}, + {0,0}}, + + {BIFURCATION, /* c. Bifurcation (disappearing) */ + DISAPPEARING, + {1,1}, + {0,1}, + {1,1}}, + + {BIFURCATION, /* d. Bifurcation (appearing) */ + APPEARING, + {1,1}, + {1,0}, + {1,1}}, + + {BIFURCATION, /* e. Bifurcation (disappearing) */ + DISAPPEARING, + {1,0}, + {0,1}, + {1,1}}, + + {BIFURCATION, /* f. Bifurcation (disappearing) */ + DISAPPEARING, + {1,1}, + {0,1}, + {1,0}}, + + {BIFURCATION, /* g. Bifurcation (appearing) */ + APPEARING, + {1,1}, + {1,0}, + {0,1}}, + + {BIFURCATION, /* h. Bifurcation (appearing) */ + APPEARING, + {0,1}, + {1,0}, + {1,1}}, + + {BIFURCATION, /* i. Bifurcation (disappearing) */ + DISAPPEARING, + {1,0}, + {0,1}, + {1,0}}, + + {BIFURCATION, /* j. Bifurcation (appearing) */ + APPEARING, + {0,1}, + {1,0}, + {0,1}}}; diff --git a/libfprint/nbis/mindtct/imgutil.c b/libfprint/nbis/mindtct/imgutil.c new file mode 100644 index 0000000..c4fa868 --- /dev/null +++ b/libfprint/nbis/mindtct/imgutil.c @@ -0,0 +1,469 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: IMGUTIL.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 03/16/2005 by MDG + + Contains general support image routines required by the NIST + Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + bits_6to8() + bits_8to6() + gray2bin() + pad_uchar_image() + fill_holes() + free_path() + search_in_direction() + +***********************************************************************/ + +#include +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: bits_6to8 - Takes an array of unsigned characters and bitwise shifts +#cat: each value 2 postitions to the left. This is equivalent +#cat: to multiplying each value by 4. This puts original values +#cat: on the range [0..64) now on the range [0..256). Another +#cat: way to say this, is the original 6-bit values now fit in +#cat: 8 bits. This is to be used to undo the effects of bits_8to6. + + Input: + idata - input array of unsigned characters + iw - width (in characters) of the input array + ih - height (in characters) of the input array + Output: + idata - contains the bit-shifted results +**************************************************************************/ +void bits_6to8(unsigned char *idata, const int iw, const int ih) +{ + int i, isize; + unsigned char *iptr; + + isize = iw * ih; + iptr = idata; + for(i = 0; i < isize; i++){ + /* Multiply every pixel value by 4 so that [0..64) -> [0..255) */ + *iptr++ <<= 2; + } +} + +/************************************************************************* +************************************************************************** +#cat: bits_8to6 - Takes an array of unsigned characters and bitwise shifts +#cat: each value 2 postitions to the right. This is equivalent +#cat: to dividing each value by 4. This puts original values +#cat: on the range [0..256) now on the range [0..64). Another +#cat: way to say this, is the original 8-bit values now fit in +#cat: 6 bits. I would really like to make this dependency +#cat: go away. + + Input: + idata - input array of unsigned characters + iw - width (in characters) of the input array + ih - height (in characters) of the input array + Output: + idata - contains the bit-shifted results +**************************************************************************/ +void bits_8to6(unsigned char *idata, const int iw, const int ih) +{ + int i, isize; + unsigned char *iptr; + + isize = iw * ih; + iptr = idata; + for(i = 0; i < isize; i++){ + /* Divide every pixel value by 4 so that [0..256) -> [0..64) */ + *iptr++ >>= 2; + } +} + +/************************************************************************* +************************************************************************** +#cat: gray2bin - Takes an 8-bit threshold value and two 8-bit pixel values. +#cat: Those pixels in the image less than the threhsold are set +#cat: to the first specified pixel value, whereas those pixels +#cat: greater than or equal to the threshold are set to the second +#cat: specified pixel value. On application for this routine is +#cat: to convert binary images from 8-bit pixels valued {0,255} to +#cat: {1,0} and vice versa. + + Input: + thresh - 8-bit pixel threshold + less_pix - pixel value used when image pixel is < threshold + greater_pix - pixel value used when image pixel is >= threshold + bdata - 8-bit image data + iw - width (in pixels) of the image + ih - height (in pixels) of the image + Output: + bdata - altered 8-bit image data +**************************************************************************/ +void gray2bin(const int thresh, const int less_pix, const int greater_pix, + unsigned char *bdata, const int iw, const int ih) +{ + int i; + + for(i = 0; i < iw*ih; i++){ + if(bdata[i] >= thresh) + bdata[i] = (unsigned char)greater_pix; + else + bdata[i] = (unsigned char)less_pix; + } +} + +/************************************************************************* +************************************************************************** +#cat: pad_uchar_image - Copies an 8-bit grayscale images into a larger +#cat: output image centering the input image so as to +#cat: add a specified amount of pixel padding along the +#cat: entire perimeter of the input image. The amount of +#cat: pixel padding and the intensity of the pixel padding +#cat: are specified. An alternative to padding with a +#cat: constant intensity would be to copy the edge pixels +#cat: of the centered image into the adjacent pad area. + + Input: + idata - input 8-bit grayscale image + iw - width (in pixels) of the input image + ih - height (in pixels) of the input image + pad - size of padding (in pixels) to be added + pad_value - intensity of the padded area + Output: + optr - points to the newly padded image + ow - width (in pixels) of the padded image + oh - height (in pixels) of the padded image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int pad_uchar_image(unsigned char **optr, int *ow, int *oh, + unsigned char *idata, const int iw, const int ih, + const int pad, const int pad_value) +{ + unsigned char *pdata, *pptr, *iptr; + int i, pw, ph; + int pad2, psize; + + /* Account for pad on both sides of image */ + pad2 = pad<<1; + + /* Compute new pad sizes */ + pw = iw + pad2; + ph = ih + pad2; + psize = pw * ph; + + /* Allocate padded image */ + pdata = (unsigned char *)malloc(psize * sizeof(unsigned char)); + if(pdata == (unsigned char *)NULL){ + fprintf(stderr, "ERROR : pad_uchar_image : malloc : pdata\n"); + return(-160); + } + + /* Initialize values to a constant PAD value */ + memset(pdata, pad_value, psize); + + /* Copy input image into padded image one scanline at a time */ + iptr = idata; + pptr = pdata + (pad * pw) + pad; + for(i = 0; i < ih; i++){ + memcpy(pptr, iptr, iw); + iptr += iw; + pptr += pw; + } + + *optr = pdata; + *ow = pw; + *oh = ph; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: fill_holes - Takes an input image and analyzes triplets of horizontal +#cat: pixels first and then triplets of vertical pixels, filling +#cat: in holes of width 1. A hole is defined as the case where +#cat: the neighboring 2 pixels are equal, AND the center pixel +#cat: is different. Each hole is filled with the value of its +#cat: immediate neighbors. This routine modifies the input image. + + Input: + bdata - binary image data to be processed + iw - width (in pixels) of the binary input image + ih - height (in pixels) of the binary input image + Output: + bdata - points to the results +**************************************************************************/ +void fill_holes(unsigned char *bdata, const int iw, const int ih) +{ + int ix, iy, iw2; + unsigned char *lptr, *mptr, *rptr, *tptr, *bptr, *sptr; + + /* 1. Fill 1-pixel wide holes in horizontal runs first ... */ + sptr = bdata + 1; + /* Foreach row in image ... */ + for(iy = 0; iy < ih; iy++){ + /* Initialize pointers to start of next line ... */ + lptr = sptr-1; /* Left pixel */ + mptr = sptr; /* Middle pixel */ + rptr = sptr+1; /* Right pixel */ + /* Foreach column in image (less far left and right pixels) ... */ + for(ix = 1; ix < iw-1; ix++){ + /* Do we have a horizontal hole of length 1? */ + if((*lptr != *mptr) && (*lptr == *rptr)){ + /* If so, then fill it. */ + *mptr = *lptr; + /* Bump passed right pixel because we know it will not */ + /* be a hole. */ + lptr+=2; + mptr+=2; + rptr+=2; + /* We bump ix once here and then the FOR bumps it again. */ + ix++; + } + else{ + /* Otherwise, bump to the next pixel to the right. */ + lptr++; + mptr++; + rptr++; + } + } + /* Bump to start of next row. */ + sptr += iw; + } + + /* 2. Now, fill 1-pixel wide holes in vertical runs ... */ + iw2 = iw<<1; + /* Start processing column one row down from the top of the image. */ + sptr = bdata + iw; + /* Foreach column in image ... */ + for(ix = 0; ix < iw; ix++){ + /* Initialize pointers to start of next column ... */ + tptr = sptr-iw; /* Top pixel */ + mptr = sptr; /* Middle pixel */ + bptr = sptr+iw; /* Bottom pixel */ + /* Foreach row in image (less top and bottom row) ... */ + for(iy = 1; iy < ih-1; iy++){ + /* Do we have a vertical hole of length 1? */ + if((*tptr != *mptr) && (*tptr == *bptr)){ + /* If so, then fill it. */ + *mptr = *tptr; + /* Bump passed bottom pixel because we know it will not */ + /* be a hole. */ + tptr+=iw2; + mptr+=iw2; + bptr+=iw2; + /* We bump iy once here and then the FOR bumps it again. */ + iy++; + } + else{ + /* Otherwise, bump to the next pixel below. */ + tptr+=iw; + mptr+=iw; + bptr+=iw; + } + } + /* Bump to start of next column. */ + sptr++; + } +} + +/************************************************************************* +************************************************************************** +#cat: free_path - Traverses a straight line between 2 pixel points in an +#cat: image and determines if a "free path" exists between the +#cat: 2 points by counting the number of pixel value transitions +#cat: between adjacent pixels along the trajectory. + + Input: + x1 - x-pixel coord of first point + y1 - y-pixel coord of first point + x2 - x-pixel coord of second point + y2 - y-pixel coord of second point + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and threshold for controlling LFS + Return Code: + TRUE - free path determined to exist + FALSE - free path determined not to exist + Negative - system error +**************************************************************************/ +int free_path(const int x1, const int y1, const int x2, const int y2, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int *x_list, *y_list, num; + int ret; + int i, trans, preval, nextval; + + /* Compute points along line segment between the two points. */ + if((ret = line_points(&x_list, &y_list, &num, x1, y1, x2, y2))) + return(ret); + + /* Intialize the number of transitions to 0. */ + trans = 0; + /* Get the pixel value of first point along line segment. */ + preval = *(bdata+(y1*iw)+x1); + + /* Foreach remaining point along line segment ... */ + for(i = 1; i < num; i++){ + /* Get pixel value of next point along line segment. */ + nextval = *(bdata+(y_list[i]*iw)+x_list[i]); + + /* If next pixel value different from previous pixel value ... */ + if(nextval != preval){ + /* Then we have detected a transition, so bump counter. */ + trans++; + /* If number of transitions seen > than threshold (ex. 2) ... */ + if(trans > lfsparms->maxtrans){ + /* Deallocate the line segment's coordinate lists. */ + free(x_list); + free(y_list); + /* Return free path to be FALSE. */ + return(FALSE); + } + /* Otherwise, maximum number of transitions not yet exceeded. */ + /* Assign the next pixel value to the previous pixel value. */ + preval = nextval; + } + /* Otherwise, no transition detected this interation. */ + + } + + /* If we get here we did not exceed the maximum allowable number */ + /* of transitions. So, deallocate the line segment's coordinate lists. */ + free(x_list); + free(y_list); + + /* Return free path to be TRUE. */ + return(TRUE); +} + +/************************************************************************* +************************************************************************** +#cat: search_in_direction - Takes a specified maximum number of steps in a +#cat: specified direction looking for the first occurence of +#cat: a pixel with specified value. (Once found, adjustments +#cat: are potentially made to make sure the resulting pixel +#cat: and its associated edge pixel are 4-connected.) + + Input: + pix - value of pixel to be searched for + strt_x - x-pixel coord to start search + strt_y - y-pixel coord to start search + delta_x - increment in x for each step + delta_y - increment in y for each step + maxsteps - maximum number of steps to conduct search + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + ox - x coord of located pixel + oy - y coord of located pixel + oex - x coord of associated edge pixel + oey - y coord of associated edge pixel + Return Code: + TRUE - pixel of specified value found + FALSE - pixel of specified value NOT found +**************************************************************************/ +int search_in_direction(int *ox, int *oy, int *oex, int *oey, const int pix, + const int strt_x, const int strt_y, + const double delta_x, const double delta_y, const int maxsteps, + unsigned char *bdata, const int iw, const int ih) +{ + + int i, x, y, px, py; + double fx, fy; + + /* Set previous point to starting point. */ + px = strt_x; + py = strt_y; + /* Set floating point accumulators to starting point. */ + fx = (double)strt_x; + fy = (double)strt_y; + + /* Foreach step up to the specified maximum ... */ + for(i = 0; i < maxsteps; i++){ + + /* Increment accumulators. */ + fx += delta_x; + fy += delta_y; + /* Round to get next step. */ + x = sround(fx); + y = sround(fy); + + /* If we stepped outside the image boundaries ... */ + if((x < 0) || (x >= iw) || + (y < 0) || (y >= ih)){ + /* Return FALSE (we did not find what we were looking for). */ + *ox = -1; + *oy = -1; + *oex = -1; + *oey = -1; + return(FALSE); + } + + /* Otherwise, test to see if we found our pixel with value 'pix'. */ + if(*(bdata+(y*iw)+x) == pix){ + /* The previous and current pixels form a feature, edge pixel */ + /* pair, which we would like to use for edge following. The */ + /* previous pixel may be a diagonal neighbor however to the */ + /* current pixel, in which case the pair could not be used by */ + /* the contour tracing (which requires the edge pixel in the */ + /* pair neighbor to the N,S,E or W. */ + /* This routine adjusts the pair so that the results may be */ + /* used by the contour tracing. */ + fix_edge_pixel_pair(&x, &y, &px, &py, bdata, iw, ih); + + /* Return TRUE (we found what we were looking for). */ + *ox = x; + *oy = y; + *oex = px; + *oey = py; + return(TRUE); + } + + /* Otherwise, still haven't found pixel with desired value, */ + /* so set current point to previous and take another step. */ + px = x; + py = y; + } + + /* Return FALSE (we did not find what we were looking for). */ + *ox = -1; + *oy = -1; + *oex = -1; + *oey = -1; + return(FALSE); +} + diff --git a/libfprint/nbis/mindtct/init.c b/libfprint/nbis/mindtct/init.c new file mode 100644 index 0000000..4414165 --- /dev/null +++ b/libfprint/nbis/mindtct/init.c @@ -0,0 +1,768 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: INIT.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for allocation and/or initialization + of memories required by the NIST Latent Fingerprint System. + +*********************************************************************** + ROUTINES: + init_dir2rad() + init_dftwaves() + get_max_padding() + get_max_padding_V2() + init_rotgrids() + alloc_dir_powers() + alloc_power_stats() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: init_dir2rad - Allocates and initializes a lookup table containing +#cat: cosine and sine values needed to convert integer IMAP +#cat: directions to angles in radians. + + Input: + ndirs - the number of integer directions to be defined in a + semicircle + Output: + optr - points to the allocated/initialized DIR2RAD structure + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int init_dir2rad(DIR2RAD **optr, const int ndirs) +{ + DIR2RAD *dir2rad; + int i; + double theta, pi_factor; + double cs, sn; + + /* Allocate structure */ + dir2rad = (DIR2RAD *)malloc(sizeof(DIR2RAD)); + if(dir2rad == (DIR2RAD *)NULL){ + fprintf(stderr, "ERROR : init_dir2rad : malloc : dir2rad\n"); + return(-10); + } + + /* Assign number of directions */ + dir2rad->ndirs = ndirs; + + /* Allocate cosine vector */ + dir2rad->cos = (double *)malloc(ndirs * sizeof(double)); + if(dir2rad->cos == (double *)NULL){ + /* Free memory allocated to this point. */ + free(dir2rad); + fprintf(stderr, "ERROR : init_dir2rad : malloc : dir2rad->cos\n"); + return(-11); + } + + /* Allocate sine vector */ + dir2rad->sin = (double *)malloc(ndirs * sizeof(double)); + if(dir2rad->sin == (double *)NULL){ + /* Free memory allocated to this point. */ + free(dir2rad->cos); + free(dir2rad); + fprintf(stderr, "ERROR : init_dir2rad : malloc : dir2rad->sin\n"); + return(-12); + } + + /* Pi_factor sets the period of the trig functions to NDIRS units in x. */ + /* For example, if NDIRS==16, then pi_factor = 2(PI/16) = .3926... */ + pi_factor = 2.0*M_PI/(double)ndirs; + + /* Now compute cos and sin values for each direction. */ + for (i = 0; i < ndirs; ++i) { + theta = (double)(i * pi_factor); + cs = cos(theta); + sn = sin(theta); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures. */ + cs = trunc_dbl_precision(cs, TRUNC_SCALE); + sn = trunc_dbl_precision(sn, TRUNC_SCALE); + dir2rad->cos[i] = cs; + dir2rad->sin[i] = sn; + } + + *optr = dir2rad; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: init_dftwaves - Allocates and initializes a set of wave forms needed +#cat: to conduct DFT analysis on blocks of the input image + + Input: + dft_coefs - array of multipliers used to define the frequency for + each wave form to be computed + nwaves - number of wave forms to be computed + blocksize - the width and height of each block of image data to + be DFT analyzed + Output: + optr - points to the allocated/initialized DFTWAVES structure + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int init_dftwaves(DFTWAVES **optr, const double *dft_coefs, + const int nwaves, const int blocksize) +{ + DFTWAVES *dftwaves; + int i, j; + double pi_factor, freq, x; + double *cptr, *sptr; + + /* Allocate structure */ + dftwaves = (DFTWAVES *)malloc(sizeof(DFTWAVES)); + if(dftwaves == (DFTWAVES *)NULL){ + fprintf(stderr, "ERROR : init_dftwaves : malloc : dftwaves\n"); + return(-20); + } + + /* Set number of DFT waves */ + dftwaves->nwaves = nwaves; + /* Set wave length of the DFT waves (they all must be the same length) */ + dftwaves->wavelen = blocksize; + + /* Allocate list of wave pointers */ + dftwaves->waves = (DFTWAVE **)malloc(nwaves * sizeof(DFTWAVE *)); + if(dftwaves == (DFTWAVES *)NULL){ + /* Free memory allocated to this point. */ + free(dftwaves); + fprintf(stderr, "ERROR : init_dftwaves : malloc : dftwaves->waves\n"); + return(-21); + } + + /* Pi_factor sets the period of the trig functions to BLOCKSIZE units */ + /* in x. For example, if BLOCKSIZE==24, then */ + /* pi_factor = 2(PI/24) = .26179... */ + pi_factor = 2.0*M_PI/(double)blocksize; + + /* Foreach of 4 DFT frequency coef ... */ + for (i = 0; i < nwaves; ++i) { + /* Allocate wave structure */ + dftwaves->waves[i] = (DFTWAVE *)malloc(sizeof(DFTWAVE)); + if(dftwaves->waves[i] == (DFTWAVE *)NULL){ + /* Free memory allocated to this point. */ + { int _j; for(_j = 0; _j < i; _j++){ + free(dftwaves->waves[_j]->cos); + free(dftwaves->waves[_j]->sin); + free(dftwaves->waves[_j]); + }} + free(dftwaves->waves); + free(dftwaves); + fprintf(stderr, + "ERROR : init_dftwaves : malloc : dftwaves->waves[i]\n"); + return(-22); + } + /* Allocate cosine vector */ + dftwaves->waves[i]->cos = (double *)malloc(blocksize * sizeof(double)); + if(dftwaves->waves[i]->cos == (double *)NULL){ + /* Free memory allocated to this point. */ + { int _j; for(_j = 0; _j < i; _j++){ + free(dftwaves->waves[_j]->cos); + free(dftwaves->waves[_j]->sin); + free(dftwaves->waves[_j]); + }} + free(dftwaves->waves[i]); + free(dftwaves->waves); + free(dftwaves); + fprintf(stderr, + "ERROR : init_dftwaves : malloc : dftwaves->waves[i]->cos\n"); + return(-23); + } + /* Allocate sine vector */ + dftwaves->waves[i]->sin = (double *)malloc(blocksize * sizeof(double)); + if(dftwaves->waves[i]->sin == (double *)NULL){ + /* Free memory allocated to this point. */ + { int _j; for(_j = 0; _j < i; _j++){ + free(dftwaves->waves[_j]->cos); + free(dftwaves->waves[_j]->sin); + free(dftwaves->waves[_j]); + }} + free(dftwaves->waves[i]->cos); + free(dftwaves->waves[i]); + free(dftwaves->waves); + free(dftwaves); + fprintf(stderr, + "ERROR : init_dftwaves : malloc : dftwaves->waves[i]->sin\n"); + return(-24); + } + + /* Assign pointer nicknames */ + cptr = dftwaves->waves[i]->cos; + sptr = dftwaves->waves[i]->sin; + + /* Compute actual frequency */ + freq = pi_factor * dft_coefs[i]; + + /* Used as a 1D DFT on a 24 long vector of pixel sums */ + for (j = 0; j < blocksize; ++j) { + /* Compute sample points from frequency */ + x = freq * (double)j; + /* Store cos and sin components of sample point */ + *cptr++ = cos(x); + *sptr++ = sin(x); + } + } + + *optr = dftwaves; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: get_max_padding - Deterines the maximum amount of image pixel padding +#cat: required by all LFS processes. Padding is currently +#cat: required by the rotated grids used in DFT analyses, +#cat: rotated grids used in directional binarization, +#cat: and in the grid used for isotropic binarization. +#cat: The NIST generalized code enables the parameters +#cat: governing these processes to be redefined, so a check +#cat: at runtime is required to determine which process +#cat: requires the most padding. By using the maximum as +#cat: the padding factor, all processes will run safely +#cat: with a single padding of the input image avoiding the +#cat: need to repad for further processes. + + Input: + imap_blocksize - the size (in pixels) of each IMAP block in the image + dirbin_grid_w - the width (in pixels) of the rotated grids used in + directional binarization + dirbin_grid_h - the height (in pixels) of the rotated grids used in + directional binarization + isobin_grid_dim - the dimension (in pixels) of the square grid used in + isotropic binarization + Return Code: + Non-negative - the maximum padding required for all processes +**************************************************************************/ +int get_max_padding(const int imap_blocksize, + const int dirbin_grid_w, const int dirbin_grid_h, + const int isobin_grid_dim) +{ + int dft_pad, dirbin_pad, isobin_pad, max_pad; + double diag; + double pad; + + /* Compute pad required for rotated blocks used in DFT analyses. */ + diag = sqrt((double)(2.0 * imap_blocksize * imap_blocksize)); + /* Compute pad as difference between the IMAP blocksize */ + /* and the diagonal distance of the block. */ + /* Assumption: all block origins reside in valid/allocated memory. */ + /* DFT grids are computed with pixel offsets RELATIVE2ORIGIN. */ + pad = (diag-imap_blocksize)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + pad = trunc_dbl_precision(pad, TRUNC_SCALE); + dft_pad = sround(pad); + + /* Compute pad required for rotated blocks used in directional */ + /* binarization. */ + diag = sqrt((double)((dirbin_grid_w*dirbin_grid_w)+ + (dirbin_grid_h*dirbin_grid_h))); + /* Assumption: all grid centers reside in valid/allocated memory. */ + /* dirbin grids are computed with pixel offsets RELATIVE2CENTER. */ + pad = (diag-1)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + pad = trunc_dbl_precision(pad, TRUNC_SCALE); + dirbin_pad = sround(pad); + + /* Compute pad required for grids used in isotropic binarization. */ + pad = (isobin_grid_dim - 1)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + pad = trunc_dbl_precision(pad, TRUNC_SCALE); + isobin_pad = sround((isobin_grid_dim - 1)/(double)2.0); + + max_pad = max(dft_pad, dirbin_pad); + max_pad = max(max_pad, isobin_pad); + + /* Return the maximum of the three required paddings. This padding will */ + /* be sufficiently large for all three purposes, so that padding of the */ + /* input image will only be required once. */ + return(max_pad); +} + +/************************************************************************* +************************************************************************** +#cat: get_max_padding_V2 - Deterines the maximum amount of image pixel padding +#cat: required by all LFS (Version 2) processes. Padding is currently +#cat: required by the rotated grids used in DFT analyses and in +#cat: directional binarization. The NIST generalized code enables +#cat: the parameters governing these processes to be redefined, so a +#cat: check at runtime is required to determine which process +#cat: requires the most padding. By using the maximum as the padding +#cat: factor, all processes will run safely with a single padding of +#cat: the input image avoiding the need to repad for further processes. + + Input: + map_windowsize - the size (in pixels) of each window centered about + each block in the image used in DFT analyses + map_windowoffset - the offset (in pixels) from the orgin of the + surrounding window to the origin of the block + dirbin_grid_w - the width (in pixels) of the rotated grids used in + directional binarization + dirbin_grid_h - the height (in pixels) of the rotated grids used in + directional binarization + Return Code: + Non-negative - the maximum padding required for all processes +**************************************************************************/ +int get_max_padding_V2(const int map_windowsize, const int map_windowoffset, + const int dirbin_grid_w, const int dirbin_grid_h) +{ + int dft_pad, dirbin_pad, max_pad; + double diag; + double pad; + + + /* 1. Compute pad required for rotated windows used in DFT analyses. */ + + /* Explanation of DFT padding: + + B--------------------- + | window | + | | + | | + | A.......______|__________ + | : : | + |<-C-->: block: | + <--|--D-->: : | image + | ........ | + | | | + | | | + | | | + ---------------------- + | + | + | + + Pixel A = Origin of entire fingerprint image + = Also origin of first block in image. Each pixel in + this block gets the same DFT results computed from + the surrounding window. Note that in general + blocks are adjacent and non-overlapping. + + Pixel B = Origin of surrounding window in which DFT + analysis is conducted. Note that this window is not + completely contained in the image but extends to the + top and to the right. + + Distance C = Number of pixels in which the window extends + beyond the image (map_windowoffset). + + Distance D = Amount of padding required to hold the entire + rotated window in memory. + + */ + + /* Compute pad as difference between the MAP windowsize */ + /* and the diagonal distance of the window. */ + /* (DFT grids are computed with pixel offsets RELATIVE2ORIGIN.) */ + diag = sqrt((double)(2.0 * map_windowsize * map_windowsize)); + pad = (diag-map_windowsize)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + pad = trunc_dbl_precision(pad, TRUNC_SCALE); + /* Must add the window offset to the rotational padding. */ + dft_pad = sround(pad) + map_windowoffset; + + /* 2. Compute pad required for rotated blocks used in directional */ + /* binarization. Binarization blocks are applied to each pixel */ + /* in the input image. */ + diag = sqrt((double)((dirbin_grid_w*dirbin_grid_w)+ + (dirbin_grid_h*dirbin_grid_h))); + /* Assumption: all grid centers reside in valid/allocated memory. */ + /* (Dirbin grids are computed with pixel offsets RELATIVE2CENTER.) */ + pad = (diag-1)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + pad = trunc_dbl_precision(pad, TRUNC_SCALE); + dirbin_pad = sround(pad); + + max_pad = max(dft_pad, dirbin_pad); + + /* Return the maximum of the two required paddings. This padding will */ + /* be sufficiently large for all purposes, so that padding of the */ + /* input image will only be required once. */ + return(max_pad); +} + +/************************************************************************* +************************************************************************** +#cat: init_rotgrids - Allocates and initializes a set of offsets that address +#cat: individual rotated pixels within a grid. +#cat: These rotated grids are used to conduct DFT analyses +#cat: on blocks of input image data, and they are used +#cat: in isotropic binarization. + + Input: + iw - width (in pixels) of the input image + ih - height (in pixels) of the input image + pad - designates the number of pixels to be padded to the perimeter + of the input image. May be passed as UNDEFINED, in which + case the specific padding required by the rotated grids + will be computed and returned in ROTGRIDS. + start_dir_angle - angle from which rotations are to start + ndirs - number of rotations to compute (within a semicircle) + grid_w - width of the grid in pixels to be rotated + grid_h - height of the grid in pixels to be rotated + relative2 - designates whether pixel offsets whould be computed + relative to the ORIGIN or the CENTER of the grid + Output: + optr - points to the allcated/initialized ROTGRIDS structure + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int init_rotgrids(ROTGRIDS **optr, const int iw, const int ih, const int ipad, + const double start_dir_angle, const int ndirs, + const int grid_w, const int grid_h, const int relative2) +{ + ROTGRIDS *rotgrids; + double pi_offset, pi_incr; + int dir, ix, iy, grid_size, pw, grid_pad, min_dim; + int *grid; + double diag, theta, cs, sn, cx, cy; + double fxm, fym, fx, fy; + int ixt, iyt; + double pad; + + /* Allocate structure */ + rotgrids = (ROTGRIDS *)malloc(sizeof(ROTGRIDS)); + if(rotgrids == (ROTGRIDS *)NULL){ + fprintf(stderr, "ERROR : init_rotgrids : malloc : rotgrids\n"); + return(-30); + } + + /* Set rotgrid attributes */ + rotgrids->ngrids = ndirs; + rotgrids->grid_w = grid_w; + rotgrids->grid_h = grid_h; + rotgrids->start_angle = start_dir_angle; + rotgrids->relative2 = relative2; + + /* Compute pad based on diagonal of the grid */ + diag = sqrt((double)((grid_w*grid_w)+(grid_h*grid_h))); + switch(relative2){ + case RELATIVE2CENTER: + /* Assumption: all grid centers reside in valid/allocated memory. */ + pad = (diag-1)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + pad = trunc_dbl_precision(pad, TRUNC_SCALE); + grid_pad = sround(pad); + break; + case RELATIVE2ORIGIN: + /* Assumption: all grid origins reside in valid/allocated memory. */ + min_dim = min(grid_w, grid_h); + /* Compute pad as difference between the smallest grid dimension */ + /* and the diagonal distance of the grid. */ + pad = (diag-min_dim)/(double)2.0; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + pad = trunc_dbl_precision(pad, TRUNC_SCALE); + grid_pad = sround(pad); + break; + default: + fprintf(stderr, + "ERROR : init_rotgrids : Illegal relative flag : %d\n", + relative2); + free(rotgrids); + return(-31); + } + + /* If input padding is UNDEFINED ... */ + if(ipad == UNDEFINED) + /* Use the padding specifically required by the rotated grids herein. */ + rotgrids->pad = grid_pad; + else{ + /* Otherwise, input pad was specified, so check to make sure it is */ + /* sufficiently large to handle the rotated grids herein. */ + if(ipad < grid_pad){ + /* If input pad is NOT large enough, then ERROR. */ + fprintf(stderr, "ERROR : init_rotgrids : Pad passed is too small\n"); + free(rotgrids); + return(-32); + } + /* Otherwise, use the specified input pad in computing grid offsets. */ + rotgrids->pad = ipad; + } + + /* Total number of points in grid */ + grid_size = grid_w * grid_h; + + /* Compute width of "padded" image */ + pw = iw + (rotgrids->pad<<1); + + /* Center coord of grid (0-oriented). */ + cx = (grid_w-1)/(double)2.0; + cy = (grid_h-1)/(double)2.0; + + /* Allocate list of rotgrid pointers */ + rotgrids->grids = (int **)malloc(ndirs * sizeof(int *)); + if(rotgrids->grids == (int **)NULL){ + /* Free memory allocated to this point. */ + free(rotgrids); + fprintf(stderr, "ERROR : init_rotgrids : malloc : rotgrids->grids\n"); + return(-33); + } + + /* Pi_offset is the offset in radians from which angles are to begin. */ + pi_offset = start_dir_angle; + pi_incr = M_PI/(double)ndirs; /* if ndirs == 16, incr = 11.25 degrees */ + + /* For each direction to rotate a grid ... */ + for (dir = 0, theta = pi_offset; + dir < ndirs; dir++, theta += pi_incr) { + + /* Allocate a rotgrid */ + rotgrids->grids[dir] = (int *)malloc(grid_size * sizeof(int)); + if(rotgrids->grids[dir] == (int *)NULL){ + /* Free memory allocated to this point. */ + { int _j; for(_j = 0; _j < dir; _j++){ + free(rotgrids->grids[_j]); + }} + free(rotgrids); + fprintf(stderr, + "ERROR : init_rotgrids : malloc : rotgrids->grids[dir]\n"); + return(-34); + } + + /* Set pointer to current grid */ + grid = rotgrids->grids[dir]; + + /* Compute cos and sin of current angle */ + cs = cos(theta); + sn = sin(theta); + + /* This next section of nested FOR loops precomputes a */ + /* rotated grid. The rotation is set up to rotate a GRID_W X */ + /* GRID_H grid on its center point at C=(Cx,Cy). The current */ + /* pixel being rotated is P=(Ix,Iy). Therefore, we have a */ + /* rotation transformation of point P about pivot point C. */ + /* The rotation transformation about a pivot point in matrix */ + /* form is: */ + /* + +- -+ + | cos(T) sin(T) 0 | + [Ix Iy 1] | -sin(T) cos(T) 0 | + | (1-cos(T))*Cx + Cy*sin(T) (1-cos(T))*Cy - Cx*sin(T) 1 | + +- -+ + */ + /* Multiplying the 2 matrices and combining terms yeilds the */ + /* equations for rotated coordinates (Rx, Ry): */ + /* Rx = Cx + (Ix - Cx)*cos(T) - (Iy - Cy)*sin(T) */ + /* Ry = Cy + (Ix - Cx)*sin(T) + (Iy - Cy)*cos(T) */ + /* */ + /* Care has been taken to ensure that (for example) when */ + /* BLOCKSIZE==24 the rotated indices stay within a centered */ + /* 34X34 area. */ + /* This is important for computing an accurate padding of */ + /* the input image. The rotation occurs "in-place" so that */ + /* outer pixels in the grid are mapped at times from */ + /* adjoining blocks. As a result, to keep from accessing */ + /* "unknown" memory or pixels wrapped from the other side of */ + /* the image, the input image should first be padded by */ + /* PAD=round((DIAG - BLOCKSIZE)/2.0) where DIAG is the */ + /* diagonal distance of the grid. */ + /* For example, when BLOCKSIZE==24, Dx=34, so PAD=5. */ + + /* Foreach each y coord in block ... */ + for (iy = 0; iy < grid_h; ++iy) { + /* Compute rotation factors dependent on Iy (include constant) */ + fxm = -1.0 * ((iy - cy) * sn); + fym = ((iy - cy) * cs); + + /* If offsets are to be relative to the grids origin, then */ + /* we need to subtract CX and CY. */ + if(relative2 == RELATIVE2ORIGIN){ + fxm += cx; + fym += cy; + } + + /* foreach each x coord in block ... */ + for (ix = 0; ix < grid_w; ++ix) { + + /* Now combine factors dependent on Iy with those of Ix */ + fx = fxm + ((ix - cx) * cs); + fy = fym + ((ix - cx) * sn); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + fx = trunc_dbl_precision(fx, TRUNC_SCALE); + fy = trunc_dbl_precision(fy, TRUNC_SCALE); + ixt = sround(fx); + iyt = sround(fy); + + /* Store the current pixels relative */ + /* rotated offset. Make sure to */ + /* multiply the y-component of the */ + /* offset by the "padded" image width! */ + *grid++ = ixt + (iyt * pw); + }/* ix */ + }/* iy */ + }/* dir */ + + *optr = rotgrids; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: alloc_dir_powers - Allocates the memory associated with DFT power +#cat: vectors. The DFT analysis is conducted block by block in the +#cat: input image, and within each block, N wave forms are applied +#cat: at M different directions. + + Input: + nwaves - number of DFT wave forms + ndirs - number of orientations (directions) used in DFT analysis + Output: + opowers - pointer to the allcated power vectors + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int alloc_dir_powers(double ***opowers, const int nwaves, const int ndirs) +{ + int w; + double **powers; + + /* Allocate list of double pointers to hold power vectors */ + powers = (double **)malloc(nwaves * sizeof(double*)); + if(powers == (double **)NULL){ + fprintf(stderr, "ERROR : alloc_dir_powers : malloc : powers\n"); + return(-40); + } + /* Foreach DFT wave ... */ + for(w = 0; w < nwaves; w++){ + /* Allocate power vector for all directions */ + powers[w] = (double *)malloc(ndirs * sizeof(double)); + if(powers[w] == (double *)NULL){ + /* Free memory allocated to this point. */ + { int _j; for(_j = 0; _j < w; _j++){ + free(powers[_j]); + }} + free(powers); + fprintf(stderr, "ERROR : alloc_dir_powers : malloc : powers[w]\n"); + return(-41); + } + } + + *opowers = powers; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: alloc_power_stats - Allocates memory associated with set of statistics +#cat: derived from DFT power vectors computed in a block of the +#cat: input image. Statistics are not computed for the lowest DFT +#cat: wave form, so the length of the statistics arrays is 1 less +#cat: than the number of DFT wave forms used. The staistics +#cat: include the Maximum power for each wave form, the direction +#cat: at which the maximum power occured, and a normalized value +#cat: for the maximum power. In addition, the statistics are +#cat: ranked in descending order based on normalized squared +#cat: maximum power. + + Input: + nstats - the number of waves forms from which statistics are to be + derived (N Waves - 1) + Output: + owis - points to an array to hold the ranked wave form indicies + of the corresponding statistics + opowmaxs - points to an array to hold the maximum DFT power for each + wave form + opowmax_dirs - points to an array to hold the direction corresponding to + each maximum power value + opownorms - points to an array to hold the normalized maximum power + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int alloc_power_stats(int **owis, double **opowmaxs, int **opowmax_dirs, + double **opownorms, const int nstats) +{ + int *wis, *powmax_dirs; + double *powmaxs, *pownorms; + + /* Allocate DFT wave index vector */ + wis = (int *)malloc(nstats * sizeof(int)); + if(wis == (int *)NULL){ + fprintf(stderr, "ERROR : alloc_power_stats : malloc : wis\n"); + return(-50); + } + + /* Allocate max power vector */ + powmaxs = (double *)malloc(nstats * sizeof(double)); + if(powmaxs == (double *)NULL){ + /* Free memory allocated to this point. */ + free(wis); + fprintf(stderr, "ERROR : alloc_power_stats : malloc : powmaxs\n"); + return(-51); + } + + /* Allocate max power direction vector */ + powmax_dirs = (int *)malloc(nstats * sizeof(int)); + if(powmax_dirs == (int *)NULL){ + /* Free memory allocated to this point. */ + free(wis); + free(powmaxs); + fprintf(stderr, "ERROR : alloc_power_stats : malloc : powmax_dirs\n"); + return(-52); + } + + /* Allocate normalized power vector */ + pownorms = (double *)malloc(nstats * sizeof(double)); + if(pownorms == (double *)NULL){ + /* Free memory allocated to this point. */ + free(wis); + free(powmaxs); + free(pownorms); + fprintf(stderr, "ERROR : alloc_power_stats : malloc : pownorms\n"); + return(-53); + } + + *owis = wis; + *opowmaxs = powmaxs; + *opowmax_dirs = powmax_dirs; + *opownorms = pownorms; + return(0); +} + + + diff --git a/libfprint/nbis/mindtct/isempty.c b/libfprint/nbis/mindtct/isempty.c new file mode 100644 index 0000000..da42cc3 --- /dev/null +++ b/libfprint/nbis/mindtct/isempty.c @@ -0,0 +1,94 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/************************************************************************/ +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: ISEMPTY.C + AUTHOR: Michael D. Garris + DATE: 09/13/2004 + + Contains routines responsible for determining if a fingerprint + image is empty. + +*********************************************************************** + ROUTINES: + is_image_empty() + is_qmap_empty() + +***********************************************************************/ + +#include + +/*********************************************************************** +************************************************************************ +#cat: is_image_empty - Routine determines if statistics passed indicate +#cat: an empty image. + + Input: + quality_map - quality map computed by NIST's Mindtct + map_w - width of map + map_h - height of map + Return Code: + True - image determined empty + False - image determined NOT empty +************************************************************************/ +int is_image_empty(int *quality_map, const int map_w, const int map_h) +{ + /* This routine is designed to be expanded as more statistical */ + /* tests are developed. */ + + if(is_qmap_empty(quality_map, map_w, map_h)) + return(TRUE); + else + return(FALSE); +} + +/*********************************************************************** +************************************************************************ +#cat: is_qmap_empty - Routine determines if quality map is all set to zero + + Input: + quality_map - quality map computed by NIST's Mindtct + map_w - width of map + map_h - height of map + Return Code: + True - quality map is empty + False - quality map is NOT empty +************************************************************************/ +int is_qmap_empty(int *quality_map, const int map_w, const int map_h) +{ + int i, maplen; + int *qptr; + + qptr = quality_map; + maplen = map_w * map_h; + for(i = 0; i < maplen; i++){ + if(*qptr++ != 0){ + return(FALSE); + } + } + return(TRUE); +} + diff --git a/libfprint/nbis/mindtct/line.c b/libfprint/nbis/mindtct/line.c new file mode 100644 index 0000000..5d9a8ed --- /dev/null +++ b/libfprint/nbis/mindtct/line.c @@ -0,0 +1,203 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: LINE.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + + Contains routines that compute contiguous linear trajectories + between two coordinate points required by the NIST Latent + Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + line_points() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: line_points - Returns the contiguous coordinates of a line connecting +#cat: 2 specified points. + + Input: + x1 - x-coord of first point + y1 - y-coord of first point + x2 - x-coord of second point + y2 - y-coord of second point + Output: + ox_list - x-coords along line trajectory + oy_list - y-coords along line trajectory + onum - number of points along line trajectory + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int line_points(int **ox_list, int **oy_list, int *onum, + const int x1, const int y1, const int x2, const int y2) +{ + int asize; + int dx, dy, adx, ady; + int x_incr, y_incr; + int i, inx, iny, intx, inty; + double x_factor, y_factor; + double rx, ry; + int ix, iy; + int *x_list, *y_list; + + /* Compute maximum number of points needed to hold line segment. */ + asize = max(abs(x2-x1)+2, abs(y2-y1)+2); + + /* Allocate x and y-pixel coordinate lists to length 'asize'. */ + x_list = (int *)malloc(asize*sizeof(int)); + if(x_list == (int *)NULL){ + fprintf(stderr, "ERROR : line_points : malloc : x_list\n"); + return(-410); + } + y_list = (int *)malloc(asize*sizeof(int)); + if(y_list == (int *)NULL){ + free(x_list); + fprintf(stderr, "ERROR : line_points : malloc : y_list\n"); + return(-411); + } + + /* Compute delta x and y. */ + dx = x2 - x1; + dy = y2 - y1; + + /* Set x and y increments. */ + if(dx >= 0) + x_incr = 1; + else + x_incr = -1; + + if(dy >= 0) + y_incr = 1; + else + y_incr = -1; + + /* Compute |DX| and |DY|. */ + adx = abs(dx); + ady = abs(dy); + + /* Set x-orientation. */ + if(adx > ady) + inx = 1; + else + inx = 0; + + /* Set y-orientation. */ + if(ady > adx) + iny = 1; + else + iny = 0; + + /* CASE 1: |DX| > |DY| */ + /* Increment in X by +-1 */ + /* in Y by +-|DY|/|DX| */ + /* inx = 1 */ + /* iny = 0 */ + /* intx = 1 (inx) */ + /* inty = 0 (iny) */ + /* CASE 2: |DX| < |DY| */ + /* Increment in Y by +-1 */ + /* in X by +-|DX|/|DY| */ + /* inx = 0 */ + /* iny = 1 */ + /* intx = 0 (inx) */ + /* inty = 1 (iny) */ + /* CASE 3: |DX| == |DY| */ + /* inx = 0 */ + /* iny = 0 */ + /* intx = 1 */ + /* inty = 1 */ + intx = 1 - iny; + inty = 1 - inx; + + /* DX */ + /* x_factor = (inx * +-1) + ( iny * ------------ ) */ + /* max(1, |DY|) */ + /* */ + x_factor = (inx * x_incr) + (iny * ((double)dx/max(1, ady))); + + /* DY */ + /* y_factor = (iny * +-1) + ( inx * ------------ ) */ + /* max(1, |DX|) */ + /* */ + y_factor = (iny * y_incr) + (inx * ((double)dy/max(1, adx))); + + /* Initialize integer coordinates. */ + ix = x1; + iy = y1; + /* Set floating point coordinates. */ + rx = (double)x1; + ry = (double)y1; + + /* Initialize to first point in line segment. */ + i = 0; + + /* Assign first point into coordinate list. */ + x_list[i] = x1; + y_list[i++] = y1; + + while((ix != x2) || (iy != y2)){ + + if(i >= asize){ + fprintf(stderr, "ERROR : line_points : coord list overflow\n"); + free(x_list); + free(y_list); + return(-412); + } + + rx += x_factor; + ry += y_factor; + + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when truncating doubles. */ + rx = trunc_dbl_precision(rx, TRUNC_SCALE); + ry = trunc_dbl_precision(ry, TRUNC_SCALE); + + /* Compute new x and y-pixel coords in floating point and */ + /* then round to the nearest integer. */ + ix = (intx * (ix + x_incr)) + (iny * (int)(rx + 0.5)); + iy = (inty * (iy + y_incr)) + (inx * (int)(ry + 0.5)); + + /* Assign first point into coordinate list. */ + x_list[i] = ix; + y_list[i++] = iy; + } + + /* Set output pointers. */ + *ox_list = x_list; + *oy_list = y_list; + *onum = i; + + /* Return normally. */ + return(0); +} diff --git a/libfprint/nbis/mindtct/link.c b/libfprint/nbis/mindtct/link.c new file mode 100644 index 0000000..2a86452 --- /dev/null +++ b/libfprint/nbis/mindtct/link.c @@ -0,0 +1,1238 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: LINK.C + AUTHOR: Michael D. Garris + DATE: 08/02/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for linking compatible minutiae + together as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + link_minutiae() + create_link_table() + update_link_table() + order_link_table() + process_link_table() + link_score() +***********************************************************************/ + +#include +#include +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: link_minutiae - Clusters minutiae that are sufficiently close to each +#cat: other and have compatible directions to be considered part +#cat: of the same ridge or valley and then links them together. +#cat: In linking two minutia, the respective minutia features +#cat: in the image are joined by drawing pixels and the points +#cat: are removed from the list. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + nmap - IMAP ridge flow matrix with invalid, high-curvature, + and no-valid-neighbor regions identified + mw - width in blocks of the NMAP + mh - height in blocks of the NMAP + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + bdata - edited binary image with breaks in ridges and valleys filled + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int link_minutiae(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *nmap, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int i, ret, *onloop; + MINUTIA *main_min; /* Renamed by MDG on 03-16-05 */ + int main_x, main_y; + int *link_table, *x_axis, *y_axis, nx_axis, ny_axis, n_entries; + + print2log("\nLINKING MINUTIA:\n"); + + /* Go through the list of minutiae and detect any small loops (ex. */ + /* < 15 pixels in circumference), and remove any minutiae */ + /* (bifurcations) with bad contours. */ + if((ret = get_loop_list(&onloop, minutiae, lfsparms->small_loop_len, + bdata, iw, ih))) + return(ret); + + i = 0; + /* Foreach minutia treated as a "main" minutia */ + /* (except for the very last one) ... */ + while(i < minutiae->num-1){ + + /* Set current minutia to "main" minutia. */ + main_min = minutiae->list[i]; + main_x = main_min->x; + main_y = main_min->y; + + /* If the main minutia is NOT on a small loop ... */ + if(!onloop[i]){ + + /* Then minutia is a ridge-ending OR a bifurcation */ + /* which is NOT to be skipped ... */ + + /* We now want to build a connected graph (link table) of */ + /* all those minutiae sufficiently close and complementing */ + /* each other that they potentially could be merged (linked) */ + /* together. (Ex. table has max dimensions of 20). */ + if((ret = create_link_table(&link_table, &x_axis, &y_axis, + &nx_axis, &ny_axis, &n_entries, + lfsparms->link_table_dim, + i, minutiae, onloop, nmap, mw, mh, + bdata, iw, ih, lfsparms))){ + /* Deallocate working memory. */ + free(onloop); + /* Return error code. */ + return(ret); + } + + /* Put the link table in sorted order based on x and then y-axis */ + /* entries. These minutia are sorted based on their point of */ + /* perpendicular intersection with a line running from the origin */ + /* at an angle equal to the average direction of all entries in */ + /* the link table. */ + if((ret = order_link_table(link_table, x_axis, y_axis, + nx_axis, ny_axis, n_entries, + lfsparms->link_table_dim, minutiae, + lfsparms->num_directions))){ + /* Deallocate working memories. */ + free(link_table); + free(x_axis); + free(y_axis); + free(onloop); + /* Return error code. */ + return(ret); + } + + /* Process the link table deciding which minutia pairs in */ + /* the table should be linked (ie. joined in the image and */ + /* removed from the minutiae list (and from onloop). */ + if((ret = process_link_table(link_table, x_axis, y_axis, + nx_axis, ny_axis, n_entries, + lfsparms->link_table_dim, + minutiae, onloop, bdata, iw, ih, lfsparms))){ + /* Deallocate working memories. */ + free(link_table); + free(x_axis); + free(y_axis); + free(onloop); + /* Return error code. */ + return(ret); + } + + /* Deallocate link table buffers. */ + free(link_table); + free(x_axis); + free(y_axis); + } + /* Otherwise, skip minutia on a small loop. */ + + /* Check to see if the current "main" minutia has been removed */ + /* from the minutiae list. If it has not, then we need to */ + /* advance to the next minutia in the list. If it has been */ + /* removed, then we are pointing to the next minutia already. */ + if((minutiae->list[i]->x == main_x) && + (minutiae->list[i]->y == main_y)) + /* Advance to the next main feature in the list. */ + i++; + + /* At this point 'i' is pointing to the next main minutia to be */ + /* processed, so continue. */ + } + + free(onloop); + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: create_link_table - Builds a 2D minutia link table where each cell in the +#cat: table represents a potential linking of 2 different +#cat: minutia points. Minutia IDs are stored on each axes +#cat: and scores representing the degree of compatibility +#cat: between 2 minutia are stored in each cell. Note that +#cat: the table is sparsely filled with scores. + + Input: + tbldim - dimension of each axes of the link table + start - index position of starting minutia point in input list + minutiae - list of minutia + onloop - list of loop flags (on flag for each minutia point in list) + nmap - IMAP ridge flow matrix with invalid, high-curvature, + and no-valid-neighbor regions identified + mw - width in blocks of the NMAP + mh - height in blocks of the NMAP + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + olink_table - sparse 2D table containing scores of potentially + linked minutia pairs + ox_axis - minutia IDs registered along x-axis + oy_axis - minutia IDs registered along y-axis + onx_axis - number of minutia registered along x-axis + ony_axis - number of minutia registered along y-axis + on_entries - number of scores currently entered in the table + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int create_link_table(int **olink_table, int **ox_axis, int **oy_axis, + int *onx_axis, int *ony_axis, int *on_entries, const int tbldim, + const int start, const MINUTIAE *minutiae, const int *onloop, + int *nmap, const int mw, const int mh, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int ret, first, second; + int full_ndirs, qtr_ndirs, half_ndirs, low_curve_min_deltadir, deltadir; + int *link_table, *x_axis, *y_axis, nx_axis, ny_axis, n_entries, tblalloc; + int *queue, *inqueue, head, tail; + MINUTIA *minutia1, *minutia2; + int xblk, yblk; + int nmapval, opp1dir, joindir, iscore; + double jointheta, joindist, dscore; + + /* Compute number of directions on full circle. */ + full_ndirs = lfsparms->num_directions<<1; + /* Compute number of directions in 45=(180/4) degrees. */ + qtr_ndirs = lfsparms->num_directions>>2; + /* Compute number of directions in 90=(180/2) degrees. */ + half_ndirs = lfsparms->num_directions>>1; + + /* Minimum allowable deltadir to link minutia in low-curvature region. */ + /* (The closer the deltadir is to 180 degrees, the more likely the link. */ + /* When ndirs==16, then this value is 11=(3*4)-1 == 123.75 degrees. */ + /* I chose to parameterize this threshold based on a fixed fraction of */ + /* 'ndirs' rather than on passing in a parameter in degrees and doing */ + /* the conversion. I doubt the difference matters. */ + low_curve_min_deltadir = (3 * qtr_ndirs) - 1; + + /* Allocate and initialize link table buffers. */ + /* Note: The use of "calloc" initializes all table values to 0. */ + tblalloc = tbldim * tbldim; + link_table = (int *)calloc(tblalloc, sizeof(int)); + if(link_table == (int *)NULL){ + fprintf(stderr, "ERROR : create_link_table : calloc : link_table\n"); + return(-330); + } + /* Allocate horizontal axis entries in table. */ + x_axis = (int *)malloc(tbldim * sizeof(int)); + if(x_axis == (int *)NULL){ + free(link_table); + fprintf(stderr, "ERROR : create_link_table : malloc : x_axis\n"); + return(-331); + } + /* Allocate vertical axis entries in table. */ + y_axis = (int *)malloc(tbldim * sizeof(int)); + if(y_axis == (int *)NULL){ + free(link_table); + free(x_axis); + fprintf(stderr, "ERROR : create_link_table : malloc : y_axis\n"); + return(-332); + } + nx_axis = 0; + ny_axis = 0; + n_entries = 0; + + /* Allocate and initalize queue buffers. As minutia are entered into */ + /* the link table they are placed in the queue for subsequent matching. */ + queue = (int *)malloc(minutiae->num * sizeof(int)); + if(queue == (int *)NULL){ + free(link_table); + free(x_axis); + free(y_axis); + fprintf(stderr, "ERROR : create_link_table : malloc : queue\n"); + return(-333); + } + /* List of flags to indicate if a manutia has been entered in the queue. */ + /* Once a minutia "in queue" status is set to TRUE it will not be reset. */ + /* This way a minutia will only ever be processed as a primary minutia */ + /* once when builing the link table. Note that the calloc() initializes */ + /* the flags to FALSE (as the queue is initially empty). */ + inqueue = (int *)calloc(minutiae->num, sizeof(int)); + if(inqueue == (int *)NULL){ + free(link_table); + free(x_axis); + free(y_axis); + free(queue); + fprintf(stderr, "ERROR : create_link_table : calloc : inqueue\n"); + return(-334); + } + /* Initialize head and tail to start of queue. */ + head = 0; + tail = 0; + + /* Push the index of the "main" manutia point onto the queue. */ + queue[tail++] = start; + /* Set "main" minutia inqueue flag to TRUE. */ + inqueue[start] = TRUE; + + print2log("BUILD TABLE:\n"); + + /* While the queue is NOT empty ... */ + while(head != tail){ + /* Pop the next manutia point from the queue and refer to it as */ + /* the primary (first) minutia. */ + first = queue[head++]; + minutia1 = minutiae->list[first]; + + /* Look for those minutia points that potentially match the */ + /* "first" minutia and add them to the link table. These */ + /* potentially matching minutia are secondary and refered to */ + /* as the "second" minutia. Always restart the search at the */ + /* original "main" manutia. */ + second = start+1; + + /* While secondary manutae remain to be matched to the current */ + /* first minutia... */ + while(second < minutiae->num){ + /* Assign second minutia to temporary pointer. */ + minutia2 = minutiae->list[second]; + + print2log("1:%d(%d,%d)%d 2:%d(%d,%d)%d ", + first, minutia1->x, minutia1->y, minutia1->type, + second, minutia2->x, minutia2->y, minutia2->type); + + /* 1. If y-delta from second to first minutia is small (ex. */ + /* <= 20 pixels) ... */ + if((minutia2->y - minutia1->y) <= lfsparms->max_link_dist){ + + print2log("1DY "); + + + /* 2. If first and second minutia are not the same point ... */ + /* (Remeber that the search for matching seconds starts */ + /* all the way back to the starting "main" minutia.) */ + if(first != second){ + + print2log("2NE "); + + /* 3. If first and second minutia are the same type ... */ + if(minutia1->type == minutia2->type){ + + print2log("3TP "); + + /* 4. If |x-delta| between minutiae is small (ex. <= */ + /* 20 pixels) ... */ + if(abs(minutia1->x - minutia2->x) <= + lfsparms->max_link_dist){ + + print2log("4DX "); + + /* 5. If second minutia is NOT on a small loop ... */ + if(!onloop[second]){ + + print2log("5NL "); + + /* The second minutia is ridge-ending OR a */ + /* bifurcation NOT to be skipped ... */ + + /* Compute the "inner" distance between the */ + /* first and second minutia's directions. */ + deltadir = closest_dir_dist(minutia1->direction, + minutia2->direction, full_ndirs); + /* If the resulting direction is INVALID */ + /* (this should never happen, but just in case)... */ + if(deltadir == INVALID_DIR){ + free(link_table); + free(x_axis); + free(y_axis); + free(queue); + free(inqueue); + /* Then there is a problem. */ + fprintf(stderr, + "ERROR : create_link_table : INVALID direction\n"); + return(-335); + } + + /* Compute first minutia's block coords from */ + /* its pixel coords. */ + xblk = minutia1->x/lfsparms->blocksize; + yblk = minutia1->y/lfsparms->blocksize; + /* Get corresponding block's NMAP value. */ + /* -3 == NO_VALID_NBRS */ + /* -2 == HIGH_CURVATURE */ + /* -1 == INVALID_DIR */ + /* 0 <= VALID_DIR */ + nmapval = *(nmap+(yblk*mw)+xblk); + /* 6. CASE I: If block has VALID_DIR and deltadir */ + /* relatively close to 180 degrees (at */ + /* least 123.75 deg when ndirs==16)... */ + /* OR */ + /* CASE II: If block is HIGH_CURVATURE and */ + /* deltadir is at least 45 degrees... */ + if(((nmapval >= 0) && + (deltadir >= low_curve_min_deltadir)) || + ((nmapval == HIGH_CURVATURE) && + (deltadir >= qtr_ndirs))){ + + print2log("6DA "); + + /* Then compute direction of "joining" vector. */ + /* First, compute direction of line from first */ + /* to second minutia points. */ + joindir = line2direction(minutia1->x, minutia1->y, + minutia2->x, minutia2->y, + lfsparms->num_directions); + + /* Comptue opposite direction of first minutia. */ + opp1dir = (minutia1->direction+ + lfsparms->num_directions)%full_ndirs; + /* Take "inner" distance on full circle between */ + /* the first minutia's opposite direction and */ + /* the joining direction. */ + joindir = abs(opp1dir - joindir); + joindir = min(joindir, full_ndirs - joindir); + /* 7. If join angle is <= 90 deg... */ + if(joindir <= half_ndirs){ + + print2log("7JA "); + + /* Convert integer join direction to angle */ + /* in radians on full circle. Multiply */ + /* direction by (2PI)/32==PI/16 radians per */ + /* unit direction and you get radians. */ + jointheta = joindir * + (M_PI/(double)lfsparms->num_directions); + /* Compute squared distance between frist */ + /* and second minutia points. */ + joindist = distance(minutia1->x, minutia1->y, + minutia2->x, minutia2->y); + /* 8. If the 2 minutia points are close enough */ + /* (ex. thresh == 20 pixels)... */ + if(joindist <= lfsparms->max_link_dist){ + + print2log("8JD "); + + /* 9. Does a "free path" exist between the */ + /* 2 minutia points? */ + if(free_path(minutia1->x, minutia1->y, + minutia2->x, minutia2->y, + bdata, iw, ih, lfsparms)){ + + print2log("9FP "); + + /* If the join distance is very small, */ + /* join theta will be unreliable, so set */ + /* join theta to zero. */ + /* (ex. thresh == 5 pixels)... */ + if(joindist < lfsparms->min_theta_dist) + /* Set the join theta to zero. */ + jointheta = 0.0; + /* Combine the join theta and distance */ + /* to compute a link score. */ + dscore = link_score(jointheta, joindist, + lfsparms); + /* Round off the floating point score. */ + /* Need to truncate so answers are same */ + /* on different computers. */ + dscore = trunc_dbl_precision(dscore, + TRUNC_SCALE); + iscore = sround(dscore); + /* Add minutia pair and their link score */ + /* to the Link Table. */ + + if(iscore > 0){ + print2log("UPDATE"); + + if((ret = update_link_table(link_table, + x_axis, y_axis, &nx_axis, &ny_axis, + &n_entries, tbldim, + queue, &head, &tail, inqueue, + first, second, iscore))){ + /* If update ERROR, deallocate */ + /* working memories. */ + free(link_table); + free(x_axis); + free(y_axis); + free(queue); + free(inqueue); + return(ret); + } + } /* Else score is <= 0, so skip second. */ + } /* 9. Else no free path, so skip second. */ + } /* 8. Else joindist too big, so skip second. */ + } /* 7. Else joindir is too big, so skip second. */ + } /* 6. Else INVALID DIR or deltadir too small, */ + /* so skip second. */ + } /* 5. Else second minutia on small loop, so skip it. */ + } /* 4. Else X distance too big, so skip second. */ + } /* 3. Else first and second NOT same type, so skip second. */ + } /* 2. Else first and second ARE same point, so skip second. */ + + /* If we get here, we want to advance to the next secondary. */ + second++; + + print2log("\n"); + + + } + /* 1. Otherwise, Y distnace too big, so we are done searching for */ + /* secondary matches to the current frist minutia. It is time */ + /* to take the next minutia in the queue and begin matching */ + /* secondaries to it. */ + else{ + + print2log("\n"); + + /* So, break out of the secondary minutiae while loop. */ + break; + } + } + /* Done matching current first minutia to secondaries. */ + } + /* Get here when queue is empty, and we have our complete link table. */ + + /* Deallocate working memories. */ + free(queue); + free(inqueue); + + /* Assign link table buffers and attributes to output pointers. */ + *olink_table = link_table; + *ox_axis = x_axis; + *oy_axis = y_axis; + *onx_axis = nx_axis; + *ony_axis = ny_axis; + *on_entries = n_entries; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: update_link_table - Takes the indices of 2 minutia and their link +#cat: compatibility score and updates the 2D link table. +#cat: The input minutia are registered to positions along +#cat: different axes, if they are not already in the table, +#cat: and a queue is maintained so that a cluster of +#cat: potentially linked points may be gathered. + + Input: + link_table - sparse 2D table containing scores of potentially linked + minutia pairs + x_axis - minutia IDs registered along x-axis + y_axis - minutia IDs registered along y-axis + nx_axis - number of minutia registered along x-axis + ny_axis - number of minutia registered along y-axis + n_entries - number of scores currently entered in the table + tbldim - dimension of each axes of the link table + queue - list of clustered minutiae yet to be used to locate + other compatible minutiae + head - head of the queue + tail - tail of the queue + inqueue - flag for each minutia point in minutiae list to signify if + it has been clustered with the points in this current link + table + first - index position of first minutia of current link pair + second - index position of second minutia of current link pair + score - degree of link compatibility of current link pair + Output: + link_table - updated sparse 2D table containing scores of potentially + linked minutia pairs + x_axis - updated minutia IDs registered along x-axis + y_axis - updated minutia IDs registered along y-axis + nx_axis - updated number of minutia registered along x-axis + ny_axis - updated number of minutia registered along y-axis + n_entries - updated number of scores currently entered in the table + queue - updated list of clustered minutiae yet to be used to locate + other compatible minutiae + tail - updated tail of the queue + inqueue - updated list of flags, one for each minutia point in + minutiae list to signify if it has been clustered with + the points in this current link table + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int update_link_table(int *link_table, int *x_axis, int *y_axis, + int *nx_axis, int *ny_axis, int *n_entries, const int tbldim, + int *queue, int *head, int *tail, int *inqueue, + const int first, const int second, const int score) +{ + int x, y, *tptr; + + /* If the link table is empty. */ + if(*n_entries == 0){ + /* Add first and second minutia to the table. */ + + /* If horizontal axis of table is full ... */ + if(*nx_axis >= tbldim) + /* Then ignore the minutia pair and return normally. */ + return(0); + /* Add first minutia to horizontal axis. */ + x_axis[*nx_axis] = first; + + /* If vertical axis of table is full ... */ + if(*ny_axis >= tbldim) + /* Then ignore the minutia pair and return normally. */ + return(0); + /* Add second minutia to vertical axis. */ + y_axis[*ny_axis] = second; + + /* Enter minutia pair score to the link table. */ + tptr = link_table + ((*ny_axis)*tbldim) + (*nx_axis); + *tptr = score; + (*n_entries)++; + + /* Bump number of entries in each axis. */ + (*nx_axis)++; + (*ny_axis)++; + + /* Add second minutia to queue (if not already in queue), so it */ + /* can be processed to see who might link to it. */ + if(!inqueue[second]){ + queue[*tail] = second; + (*tail)++; + inqueue[second] = TRUE; + } + + /* Done, so return normally. */ + return(0); + } + + /* We are filling in the table with a "faimily" or "cluster" of */ + /* potentially inter-linked points. Once the first entry is */ + /* made in the table, all subsequent updates will be based on */ + /* at least the first minutia already being in the table. */ + + /* If first minutia already stored in horizontal axis */ + /* of the link table. */ + if((x = in_int_list(first, x_axis, *nx_axis)) >= 0){ + /* If second minutia already stored in vertical axis */ + /* of the link table. */ + if((y = in_int_list(second, y_axis, *ny_axis)) >= 0){ + /* Entry may not be set or the new score may be larger. */ + tptr = link_table + (y*tbldim) + x; + if(*tptr == 0) + /* Assign the minutia pair score to the table. */ + *tptr = score; + + } + /* Otherwise, second minutia not in vertical axis of link table. */ + else{ + /* Add the second minutia to the vertical axis and */ + /* the minutia pair's score to the link table. */ + + /* If vertical axis of table is full ... */ + if(*ny_axis >= tbldim) + /* Then ignore the minutia pair and return normally. */ + return(0); + /* Add second minutia to vertical axis. */ + y_axis[*ny_axis] = second; + + /* Enter minutia pair score to the link table. */ + tptr = link_table + ((*ny_axis)*tbldim) + x; + *tptr = score; + (*n_entries)++; + + /* Bump number of entries in vertical axis. */ + (*ny_axis)++; + + /* Add second minutia to queue (if not already in queue), so it */ + /* can be processed to see who might link to it. */ + if(!inqueue[second]){ + queue[*tail] = second; + (*tail)++; + inqueue[second] = TRUE; + } + } + } + /* Otherwise, first minutia not in horizontal axis of link table. */ + else{ + /* If first minutia already stored in vertical axis */ + /* of the link table. */ + if((y = in_int_list(first, y_axis, *ny_axis)) >= 0){ + /* If second minutia already stored in horizontal axis */ + /* of the link table. */ + if((x = in_int_list(second, x_axis, *nx_axis)) >= 0){ + /* Entry may not be set or the new score may be larger. */ + tptr = link_table + (y*tbldim) + x; + if(*tptr == 0) + /* Assign the minutia pair score to the table. */ + *tptr = score; + } + /* Otherwise, second minutia not in horizontal axis of link table. */ + else{ + /* Add the second minutia to the horizontal axis and */ + /* the minutia pair's score to the link table. */ + + /* If horizontal axis of table is full ... */ + if(*nx_axis >= tbldim) + /* Then ignore the minutia pair and return normally. */ + return(0); + /* Add second minutia to vertical axis. */ + x_axis[*nx_axis] = second; + + /* Enter minutia pair score to the link table. */ + tptr = link_table + (y*tbldim) + (*nx_axis); + *tptr = score; + (*n_entries)++; + + /* Bump number of entries in horizontal axis. */ + (*nx_axis)++; + + /* Add second minutia to queue (if not already in queue), so it */ + /* can be processed to see who might link to it. */ + if(!inqueue[second]){ + queue[*tail] = second; + (*tail)++; + inqueue[second] = TRUE; + } + } + } + /* Otherwise, first minutia not in vertical or horizontal axis of */ + /* link table. This is an error, as this should only happen upon */ + /* the first point being entered, which is already handled above. */ + else{ + fprintf(stderr, + "ERROR : update_link_table : first minutia not found in table\n"); + return(-340); + } + } + + /* Done, so return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: order_link_table - Puts the link table in sorted order based on x and +#cat: then y-axis entries. These minutia are sorted based +#cat: on their point of perpendicular intersection with a +#cat: line running from the origin at an angle equal to the +#cat: average direction of all entries in the link table. + + Input: + link_table - sparse 2D table containing scores of potentially linked + minutia pairs + x_axis - minutia IDs registered along x-axis + y_axis - minutia IDs registered along y-axis + nx_axis - number of minutia registered along x-axis + ny_axis - number of minutia registered along y-axis + n_entries - number of scores currently entered in the table + tbldim - dimension of each axes of the link table + minutiae - list of minutia + ndirs - number of IMAP directions (in semicircle) + Output: + link_table - sorted sparse 2D table containing scores of potentially + linked minutia pairs + x_axis - sorted minutia IDs registered along x-axis + y_axis - sorted minutia IDs registered along y-axis + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int order_link_table(int *link_table, int *x_axis, int *y_axis, + const int nx_axis, const int ny_axis, const int n_entries, + const int tbldim, const MINUTIAE *minutiae, + const int ndirs) +{ + int i, j, ret, sumdir, avrdir, *order; + double davrdir, avrtheta, pi_factor, cs, sn; + double *dlist; + MINUTIA *minutia; + int *tlink_table, *tx_axis, *ty_axis, tblalloc; + int *toptr, *frptr; + + /* If the table is empty or if there is only one horizontal or */ + /* vertical entry in the table ... */ + if((nx_axis <= 1) || (ny_axis <= 1)) + /* Then don't reorder the table, just return normally. */ + return(0); + + /* Compute average direction (on semi-circle) of all minutia entered */ + /* in the link table. This gives an average ridge-flow direction */ + /* among all the potentially linked minutiae in the table. */ + + /* Initialize direction accumulator to 0. */ + sumdir = 0; + + /* Accumulate directions (on semi-circle) of minutia entered in the */ + /* horizontal axis of the link table. */ + for(i = 0; i < nx_axis; i++) + sumdir += (minutiae->list[x_axis[i]]->direction % ndirs); + + /* Accumulate directions of minutia entered in the vertical axis */ + /* of the link table. */ + for(i = 0; i < ny_axis; i++) + sumdir += (minutiae->list[y_axis[i]]->direction % ndirs); + + /* Compute the average direction and round off to integer. */ + davrdir = (sumdir / (double)(nx_axis + ny_axis)); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + davrdir = trunc_dbl_precision(davrdir, TRUNC_SCALE); + avrdir = sround(davrdir); + + /* Conversion factor from integer directions to radians. */ + pi_factor = M_PI / (double)ndirs; + + /* Compute sine and cosine of average direction in radians. */ + avrtheta = avrdir*pi_factor; + sn = sin(avrtheta); + cs = cos(avrtheta); + + /* Allocate list to hold distances to be sorted on. */ + dlist = (double *)malloc(tbldim * sizeof(double)); + if(dlist == (double *)NULL){ + fprintf(stderr, "ERROR : order_link_table : malloc : dlist\n"); + return(-350); + } + + /* Allocate and initialize temporary link table buffers. */ + tblalloc = tbldim * tbldim; + tlink_table = (int *)calloc(tblalloc, sizeof(int)); + if(tlink_table == (int *)NULL){ + free(dlist); + fprintf(stderr, "ERROR : order_link_table : calloc : tlink_table\n"); + return(-351); + } + tx_axis = (int *)malloc(tbldim * sizeof(int)); + if(tx_axis == (int *)NULL){ + free(dlist); + free(tlink_table); + fprintf(stderr, "ERROR : order_link_table : malloc : tx_axis\n"); + return(-352); + } + ty_axis = (int *)malloc(tbldim * sizeof(int)); + if(ty_axis == (int *)NULL){ + free(dlist); + free(tlink_table); + free(tx_axis); + fprintf(stderr, "ERROR : order_link_table : malloc : ty_axis\n"); + return(-353); + } + + /* Compute distance measures for each minutia entered in the */ + /* horizontal axis of the link table. */ + /* The measure is: dist = X*cos(avrtheta) + Y*sin(avrtheta) */ + /* which measures the distance from the origin along the line */ + /* at angle "avrtheta" to the point of perpendicular inter- */ + /* section from the point (X,Y). */ + + /* Foreach minutia in horizontal axis of the link table ... */ + for(i = 0; i < nx_axis; i++){ + minutia = minutiae->list[x_axis[i]]; + dlist[i] = (minutia->x * cs) + (minutia->y * sn); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + dlist[i] = trunc_dbl_precision(dlist[i], TRUNC_SCALE); + } + + /* Get sorted order of distance for minutiae in horizontal axis. */ + if((ret = sort_indices_double_inc(&order, dlist, nx_axis))){ + free(dlist); + return(ret); + } + + /* Store entries on y_axis into temporary list. */ + memcpy(ty_axis, y_axis, ny_axis * sizeof(int)); + + /* For each horizontal entry in link table ... */ + for(i = 0; i < nx_axis; i++){ + /* Store next minutia in sorted order to temporary x_axis. */ + tx_axis[i] = x_axis[order[i]]; + /* Store corresponding column of scores into temporary table. */ + frptr = link_table + order[i]; + toptr = tlink_table + i; + for(j = 0; j < ny_axis; j++){ + *toptr = *frptr; + toptr += tbldim; + frptr += tbldim; + } + } + + /* Deallocate sorted order of distance measures. */ + free(order); + + /* Compute distance measures for each minutia entered in the */ + /* vertical axis of the temporary link table (already sorted */ + /* based on its horizontal axis entries. */ + + /* Foreach minutia in vertical axis of the link table ... */ + for(i = 0; i < ny_axis; i++){ + minutia = minutiae->list[y_axis[i]]; + dlist[i] = (minutia->x * cs) + (minutia->y * sn); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + dlist[i] = trunc_dbl_precision(dlist[i], TRUNC_SCALE); + } + + /* Get sorted order of distance for minutiae in vertical axis. */ + if((ret = sort_indices_double_inc(&order, dlist, ny_axis))){ + free(dlist); + return(ret); + } + + /* Store entries in temporary x_axis. */ + memcpy(x_axis, tx_axis, nx_axis * sizeof(int)); + + /* For each vertical entry in the temporary link table ... */ + for(i = 0; i < ny_axis; i++){ + /* Store next minutia in sorted order to y_axis. */ + y_axis[i] = ty_axis[order[i]]; + /* Store corresponding row of scores into link table. */ + frptr = tlink_table + (order[i] * tbldim); + toptr = link_table + (i * tbldim); + for(j = 0; j < nx_axis; j++){ + *toptr++ = *frptr++; + } + } + + /* Deallocate sorted order of distance measures. */ + free(order); + + /* Link table is now sorted on x and y axes. */ + /* Deallocate the working memories. */ + free(dlist); + free(tlink_table); + free(tx_axis); + free(ty_axis); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: process_link_table - Processes the link table deciding which minutia +#cat: pairs in the table should be linked (ie. joined in +#cat: the image and removed from the minutiae list (and +#cat: from onloop). + + Input: + link_table - sparse 2D table containing scores of potentially linked + minutia pairs + x_axis - minutia IDs registered along x-axis + y_axis - minutia IDs registered along y-axis + nx_axis - number of minutia registered along x-axis + ny_axis - number of minutia registered along y-axis + n_entries - number of scores currently entered in the table + tbldim - dimension of each axes of the link table + minutiae - list of minutia + onloop - list of flags signifying which minutia lie on small lakes + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + onloop - updated loop flags + bdata - edited image with minutia features joined + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int process_link_table(const int *link_table, + const int *x_axis, const int *y_axis, + const int nx_axis, const int ny_axis, const int n_entries, + const int tbldim, MINUTIAE *minutiae, int *onloop, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int i, j, ret, first, second; + MINUTIA *minutia1, *minutia2; + int rm1, rm2; + int *to_remove; + int n_lines, line_len, entry_incr, line_incr; + int line_i, entry_i; + int start, end; + int max_v, max_tbl_i, max_line_i, max_x, max_y; + + + print2log("LINKING FROM TABLE:\n"); + + /* If link table is empty, just return normally. */ + if(n_entries == 0) + return(0); + + /* If there is only 1 entry in the table, then join the minutia pair. */ + if(n_entries == 1){ + /* Join the minutia pair in the image. */ + first = x_axis[0]; + second = y_axis[0]; + minutia1 = minutiae->list[first]; + minutia2 = minutiae->list[second]; + + /* Connect the points with a line with specified radius (ex. 1 pixel). */ + if((ret = join_minutia(minutia1, minutia2, bdata, iw, ih, + WITH_BOUNDARY, lfsparms->join_line_radius))) + return(ret); + + /* Need to remove minutiae from onloop first, as onloop is dependent */ + /* on the length of the minutiae list. We also need to remove the */ + /* highest index from the lists first or the indices will be off. */ + if(first > second){ + rm1 = first; + rm2 = second; + } + else{ + rm1 = second; + rm2 = first; + } + if((ret = remove_from_int_list(rm1, onloop, minutiae->num))) + return(ret); + if((ret = remove_from_int_list(rm2, onloop, minutiae->num-1))) + return(ret); + + /* Now, remove the minutia from the minutiae list. */ + if((ret = remove_minutia(rm1, minutiae))) + return(ret); + if((ret = remove_minutia(rm2, minutiae))) + return(ret); + + /* Return normally. */ + return(0); + } + + /* Otherwise, we need to make decisions as to who links to who. */ + + /* Allocate list of minutia indices that upon completion of linking */ + /* should be removed from the onloop and minutiae lists. Note: That */ + /* using "calloc" initializes the list to FALSE. */ + to_remove = (int *)calloc(minutiae->num, sizeof(int)); + if(to_remove == (int *)NULL){ + fprintf(stderr, "process_link_table : calloc : to_remove\n"); + return(-360); + } + + /* If the number of horizontal entries is <= vertical entries ... */ + if(nx_axis <= ny_axis){ + /* Process columns in table as lines. */ + n_lines = nx_axis; + /* Set length of each line to number of vertical entries. */ + line_len = ny_axis; + /* Increment down column to next entry in line. */ + entry_incr = tbldim; + /* Increment across row to next line. */ + line_incr = 1; + } + /* Otherwise, the number of vertical entreis < horizontal entries ... */ + else{ + /* Process rows in table as lines. */ + n_lines = ny_axis; + /* Set length of each line to number of horizontal entries. */ + line_len = nx_axis; + /* Increment across row to next entry in line. */ + entry_incr = 1; + /* Increment down column to next line. */ + line_incr = tbldim; + } + + /* Set start of next line index to origin of link table. */ + line_i = 0; + + /* Initialize the search limits for the line ... */ + start = 0; + end = line_len - n_lines + 1; + + /* Foreach line in table ... */ + for(i = 0; i < n_lines; i++){ + + /* Find max score in the line given current search limits. */ + + /* Set table entry index to start of next line. */ + entry_i = line_i; + + /* Initialize running maximum with score in current line */ + /* at offset 'start'. */ + entry_i += (start*entry_incr); + + /* Set running maximum score. */ + max_v = link_table[entry_i]; + /* Set table index of maximum score. */ + max_tbl_i = entry_i; + /* Set line index of maximum score. */ + max_line_i = start; + + /* Advance table entry index along line. */ + entry_i += entry_incr; + /* Foreach successive entry in line up to line index 'end' ... */ + + for(j = start+1; j < end; j++){ + /* If current entry > maximum score seen so far ... */ + if(link_table[entry_i] >= max_v){ + /* Store current entry as new maximum. */ + max_v = link_table[entry_i]; + max_tbl_i = entry_i; + max_line_i = j; + } + /* Advance table entry index along line. */ + entry_i += entry_incr; + } + + /* Convert entry index at maximum to table row and column indices. */ + max_x = max_tbl_i % tbldim; + max_y = max_tbl_i / tbldim; + + /* Set indices and pointers corresponding to minutia pair */ + /* with maximum score this pass. */ + first = x_axis[max_x]; + second = y_axis[max_y]; + minutia1 = minutiae->list[first]; + minutia2 = minutiae->list[second]; + + /* Check to make sure the the maximum score found in the current */ + /* line is > 0 (just to be safe) AND */ + /* If a "free path" exists between minutia pair ... */ + if( /* (max_v >0) && */ + free_path(minutia1->x, minutia1->y, minutia2->x, minutia2->y, + bdata, iw, ih, lfsparms)){ + + print2log("%d,%d to %d,%d LINK\n", + minutia1->x, minutia1->y, minutia2->x, minutia2->y); + + /* Join the minutia pair in the image. */ + if((ret = join_minutia(minutia1, minutia2, bdata, iw, ih, + WITH_BOUNDARY, lfsparms->join_line_radius))){ + free(to_remove); + return(ret); + } + + /* Set remove flags for minutia pair. A minutia point may */ + /* be linked to more than one other minutia in this process */ + /* so, just flag them to be removed for now and actually */ + /* conduct the removal after all linking is complete. */ + to_remove[first] = TRUE; + to_remove[second] = TRUE; + + } + /* Set starting line index to one passed maximum found this pass. */ + start = max_line_i + 1; + /* Bump ending line index. */ + end++; + + /* Advance start of line index to next line. */ + line_i += line_incr; + + } /* End for lines */ + + /* Now that all linking from the current table is complete, */ + /* remove any linked minutia from the onloop and minutiae lists. */ + /* NOTE: Need to remove the minutia from their lists in reverse */ + /* order, otherwise, indices will be off. */ + for(i = minutiae->num-1; i >= 0; i--){ + /* If the current minutia index is flagged for removal ... */ + if(to_remove[i]){ + /* Remove the minutia from the onloop list before removing */ + /* from minutiae list as the length of onloop depends on the */ + /* length of the minutiae list. */ + if((ret = remove_from_int_list(i, onloop, minutiae->num))){ + free(to_remove); + return(ret); + } + /* Now, remove the minutia from the minutiae list. */ + if((ret = remove_minutia(i, minutiae))){ + free(to_remove); + return(ret); + } + } + } + + /* Deallocate remove list. */ + free(to_remove); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: link_score - Takes 2 parameters, a 'join angle' and a 'join distance' +#cat: computed between 2 minutia and combines these to compute +#cat: a score representing the degree of link compatibility +#cat: between the 2 minutiae. + + Input: + jointheta - angle measured between 2 minutiae + joindist - distance between 2 minutiae + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Score - degree of link compatibility +**************************************************************************/ +double link_score(const double jointheta, const double joindist, + const LFSPARMS *lfsparms) +{ + double score, theta_factor, dist_factor; + + /* Calculates a weighted score between the distance between the 1st & */ + /* 2nd features and the delta angle between the reflected 1st feature */ + /* and the "joining" vector to the 2nd feature. */ + /* */ + /* SCORE_NUMERATOR */ + /* SCORE = ------------------------------------------ */ + /* THETA_FACTOR * DIST_FACTOR */ + /* */ + /* THETA_FACTOR = exp((jointheta/SCORE_THETA_NORM)^2) */ + /* DIST_FACTOR = (1+exp(((joindist/SCORE_DIST_NORM)-1) */ + /* *SCORE_DIST_WEIGHT)) */ + /* */ + /* For example: */ + /* SCORE_NUMERATOR = 32000.0 */ + /* SCORE_THETA_NORM = 15.0*(PI/180) */ + /* SCORE_DIST_NORM = 10.0 */ + /* SCORE_DIST_WEIGHT = 4.0 */ + /* */ + /* 3200.0 */ + /* SCORE = ----------------------------------------------------------- */ + /* exp((jointheta/15.0)^2) * (1+exp(((joindist/10.0)-1.0)*4.0))*/ + /* */ + /* In this case, increases in distance drops off faster than */ + /* in theta. */ + + /* Compute the THETA_FACTOR. */ + theta_factor = jointheta / (lfsparms->score_theta_norm * DEG2RAD); + theta_factor = exp(theta_factor * theta_factor); + /* Compute the DIST_FACTOR. */ + dist_factor = 1.0+exp(((joindist/lfsparms->score_dist_norm)-1.0) * + lfsparms->score_dist_weight); + + /* Compute the floating point score. */ + score = lfsparms->score_numerator / (theta_factor * dist_factor); + + /* Return the floating point score. */ + return(score); +} + diff --git a/libfprint/nbis/mindtct/log.c b/libfprint/nbis/mindtct/log.c new file mode 100644 index 0000000..d3aafbf --- /dev/null +++ b/libfprint/nbis/mindtct/log.c @@ -0,0 +1,90 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: LOG.C + AUTHOR: Michael D. Garris + DATE: 08/02/1999 + + Contains routines responsible for dynamically updating a log file + during the execution of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + open_logfile() + print2log() + close_logfile() +***********************************************************************/ + +#include + +/* If logging is on, declare global file pointer and supporting */ +/* global variable for logging intermediate results. */ +FILE *logfp; +int avrdir; +float dir_strength; +int nvalid; + +/***************************************************************************/ +/***************************************************************************/ +int open_logfile() +{ +#ifdef LOG_REPORT + if((logfp = fopen(LOG_FILE, "wb")) == NULL){ + fprintf(stderr, "ERROR : open_logfile : fopen : %s\n", LOG_FILE); + return(-1); + } +#endif + + return(0); +} + +/***************************************************************************/ +/***************************************************************************/ +void print2log(char *fmt, ...) +{ +#ifdef LOG_REPORT + va_list ap; + + va_start(ap, fmt); + vfprintf(logfp, fmt, ap); + va_end(ap); +#endif +} + +/***************************************************************************/ +/***************************************************************************/ +int close_logfile() +{ +#ifdef LOG_REPORT + if(fclose(logfp)){ + fprintf(stderr, "ERROR : close_logfile : fclose : %s\n", LOG_FILE); + return(-1); + } +#endif + + return(0); +} + diff --git a/libfprint/nbis/mindtct/loop.c b/libfprint/nbis/mindtct/loop.c new file mode 100644 index 0000000..1e84c46 --- /dev/null +++ b/libfprint/nbis/mindtct/loop.c @@ -0,0 +1,1228 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: LOOP.C + AUTHOR: Michael D. Garris + DATE: 05/11/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for analyzing and filling + lakes and islands within a binary image as part of the + NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + get_loop_list() + on_loop() + on_island_lake() + on_hook() + is_loop_clockwise() + process_loop() + process_loop_V2() + get_loop_aspect() + fill_loop() + fill_partial_row() + flood_loop() + flood_fill4() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: get_loop_list - Takes a list of minutia points and determines which +#cat: ones lie on loops around valleys (lakes) of a specified +#cat: maximum circumference. The routine returns a list of +#cat: flags, one for each minutia in the input list, and if +#cat: the minutia is on a qualifying loop, the corresponding +#cat: flag is set to TRUE, otherwise it is set to FALSE. +#cat: If for some reason it was not possible to trace the +#cat: minutia's contour, then it is removed from the list. +#cat: This can occur due to edits dynamically taking place +#cat: in the image by other routines. + + Input: + minutiae - list of true and false minutiae + loop_len - maximum size of loop searched for + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + oonloop - loop flags: TRUE == loop, FALSE == no loop + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int get_loop_list(int **oonloop, MINUTIAE *minutiae, const int loop_len, + unsigned char *bdata, const int iw, const int ih) +{ + int i, ret; + int *onloop; + MINUTIA *minutia; + + /* Allocate a list of onloop flags (one for each minutia in list). */ + onloop = (int *)malloc(minutiae->num * sizeof(int)); + if(onloop == (int *)NULL){ + fprintf(stderr, "ERROR : get_loop_list : malloc : onloop\n"); + return(-320); + } + + i = 0; + /* Foreach minutia remaining in list ... */ + while(i < minutiae->num){ + /* Assign a temporary pointer. */ + minutia = minutiae->list[i]; + /* If current minutia is a bifurcation ... */ + if(minutia->type == BIFURCATION){ + /* Check to see if it is on a loop of specified length. */ + ret = on_loop(minutia, loop_len, bdata, iw, ih); + /* If minutia is on a loop... */ + if(ret == LOOP_FOUND){ + /* Then set the onloop flag to TRUE. */ + onloop[i] = TRUE; + /* Advance to next minutia in the list. */ + i++; + } + /* If on loop test IGNORED ... */ + else if (ret == IGNORE){ + /* Remove the current minutia from the list. */ + if((ret = remove_minutia(i, minutiae))){ + /* Deallocate working memory. */ + free(onloop); + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + } + /* If the minutia is NOT on a loop... */ + else if (ret == FALSE){ + /* Then set the onloop flag to FALSE. */ + onloop[i] = FALSE; + /* Advance to next minutia in the list. */ + i++; + } + /* Otherwise, an ERROR occurred while looking for loop. */ + else{ + /* Deallocate working memory. */ + free(onloop); + /* Return error code. */ + return(ret); + } + } + /* Otherwise, the current minutia is a ridge-ending... */ + else{ + /* Ridge-endings will never be on a loop, so set flag to FALSE. */ + onloop[i] = FALSE; + /* Advance to next minutia in the list. */ + i++; + } + } + + /* Store flag list to output pointer. */ + *oonloop = onloop; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: on_loop - Determines if a minutia point lies on a loop (island or lake) +#cat: of specified maximum circumference. + + Input: + minutiae - list of true and false minutiae + max_loop_len - maximum size of loop searched for + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Return Code: + IGNORE - minutia contour could not be traced + LOOP_FOUND - minutia determined to lie on qualifying loop + FALSE - minutia determined not to lie on qualifying loop + Negative - system error +**************************************************************************/ +int on_loop(const MINUTIA *minutia, const int max_loop_len, + unsigned char *bdata, const int iw, const int ih) +{ + int ret; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + + /* Trace the contour of the feature starting at the minutia point */ + /* and stepping along up to the specified maximum number of steps. */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, max_loop_len, + minutia->x, minutia->y, minutia->x, minutia->y, + minutia->ex, minutia->ey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If trace was not possible ... */ + if(ret == IGNORE) + return(ret); + + /* If the trace completed a loop ... */ + if(ret == LOOP_FOUND){ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + return(LOOP_FOUND); + } + + /* If the trace successfully followed the minutia's contour, but did */ + /* not complete a loop within the specified number of steps ... */ + if(ret == 0){ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + return(FALSE); + } + + /* Otherwise, the trace had an error in following the contour ... */ + return(ret); +} + +/************************************************************************* +************************************************************************** +#cat: on_island_lake - Determines if two minutia points lie on the same loop +#cat: (island or lake). If a loop is detected, the contour +#cat: points of the loop are returned. + + Input: + minutia1 - first minutia point + minutia2 - second minutia point + max_half_loop - maximum size of half the loop circumference searched for + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + ocontour_x - x-pixel coords of loop contour + ocontour_y - y-pixel coords of loop contour + ocontour_x - x coord of each contour point's edge pixel + ocontour_y - y coord of each contour point's edge pixel + oncontour - number of points in the contour. + Return Code: + IGNORE - contour could not be traced + LOOP_FOUND - minutiae determined to lie on same qualifying loop + FALSE - minutiae determined not to lie on same qualifying loop + Negative - system error +**************************************************************************/ +int on_island_lake(int **ocontour_x, int **ocontour_y, + int **ocontour_ex, int **ocontour_ey, int *oncontour, + const MINUTIA *minutia1, const MINUTIA *minutia2, + const int max_half_loop, + unsigned char *bdata, const int iw, const int ih) +{ + int i, l, ret; + int *contour1_x, *contour1_y, *contour1_ex, *contour1_ey, ncontour1; + int *contour2_x, *contour2_y, *contour2_ex, *contour2_ey, ncontour2; + int *loop_x, *loop_y, *loop_ex, *loop_ey, nloop; + + /* Trace the contour of the feature starting at the 1st minutia point */ + /* and stepping along up to the specified maximum number of steps or */ + /* until 2nd mintuia point is encountered. */ + ret = trace_contour(&contour1_x, &contour1_y, + &contour1_ex, &contour1_ey, &ncontour1, max_half_loop, + minutia2->x, minutia2->y, minutia1->x, minutia1->y, + minutia1->ex, minutia1->ey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If trace was not possible, return IGNORE. */ + if(ret == IGNORE) + return(ret); + + /* If the trace encounters 2nd minutia point ... */ + if(ret == LOOP_FOUND){ + + /* Now, trace the contour of the feature starting at the 2nd minutia, */ + /* continuing to search for edge neighbors clockwise, and stepping */ + /* along up to the specified maximum number of steps or until 1st */ + /* mintuia point is encountered. */ + ret = trace_contour(&contour2_x, &contour2_y, + &contour2_ex, &contour2_ey, &ncontour2, max_half_loop, + minutia1->x, minutia1->y, minutia2->x, minutia2->y, + minutia2->ex, minutia2->ey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If trace was not possible, return IGNORE. */ + if(ret == IGNORE){ + free_contour(contour1_x, contour1_y, contour1_ex, contour1_ey); + return(ret); + } + + /* If the 2nd trace encounters 1st minutia point ... */ + if(ret == LOOP_FOUND){ + /* Combine the 2 half loop contours into one full loop. */ + + /* Compute loop length (including the minutia pair). */ + nloop = ncontour1 + ncontour2 + 2; + + /* Allocate loop contour. */ + if((ret = allocate_contour(&loop_x, &loop_y, &loop_ex, &loop_ey, + nloop))){ + free_contour(contour1_x, contour1_y, contour1_ex, contour1_ey); + free_contour(contour2_x, contour2_y, contour2_ex, contour2_ey); + return(ret); + } + + /* Store 1st minutia. */ + l = 0; + loop_x[l] = minutia1->x; + loop_y[l] = minutia1->y; + loop_ex[l] = minutia1->ex; + loop_ey[l++] = minutia1->ey; + /* Store first contour. */ + for(i = 0; i < ncontour1; i++){ + loop_x[l] = contour1_x[i]; + loop_y[l] = contour1_y[i]; + loop_ex[l] = contour1_ex[i]; + loop_ey[l++] = contour1_ey[i]; + } + /* Store 2nd minutia. */ + loop_x[l] = minutia2->x; + loop_y[l] = minutia2->y; + loop_ex[l] = minutia2->ex; + loop_ey[l++] = minutia2->ey; + /* Store 2nd contour. */ + for(i = 0; i < ncontour2; i++){ + loop_x[l] = contour2_x[i]; + loop_y[l] = contour2_y[i]; + loop_ex[l] = contour2_ex[i]; + loop_ey[l++] = contour2_ey[i]; + } + + /* Deallocate the half loop contours. */ + free_contour(contour1_x, contour1_y, contour1_ex, contour1_ey); + free_contour(contour2_x, contour2_y, contour2_ex, contour2_ey); + + /* Assign loop contour to return pointers. */ + *ocontour_x = loop_x; + *ocontour_y = loop_y; + *ocontour_ex = loop_ex; + *ocontour_ey = loop_ey; + *oncontour = nloop; + + /* Then return that an island/lake WAS found (LOOP_FOUND). */ + return(LOOP_FOUND); + } + + /* If the trace successfully followed 2nd minutia's contour, but */ + /* did not encounter 1st minutia point within the specified number */ + /* of steps ... */ + if(ret == 0){ + /* Deallocate the two contours. */ + free_contour(contour1_x, contour1_y, contour1_ex, contour1_ey); + free_contour(contour2_x, contour2_y, contour2_ex, contour2_ey); + /* Then return that an island/lake was NOT found (FALSE). */ + return(FALSE); + } + + /* Otherwise, the 2nd trace had an error in following the contour ... */ + free_contour(contour1_x, contour1_y, contour1_ex, contour1_ey); + return(ret); + } + + /* If the 1st trace successfully followed 1st minutia's contour, but */ + /* did not encounter the 2nd minutia point within the specified number */ + /* of steps ... */ + if(ret == 0){ + free_contour(contour1_x, contour1_y, contour1_ex, contour1_ey); + /* Then return that an island/lake was NOT found (FALSE). */ + return(FALSE); + } + + /* Otherwise, the 1st trace had an error in following the contour ... */ + return(ret); +} + +/************************************************************************* +************************************************************************** +#cat: on_hook - Determines if two minutia points lie on a hook on the side +#cat: of a ridge or valley. + + Input: + minutia1 - first minutia point + minutia2 - second minutia point + max_hook_len - maximum length of contour searched along for a hook + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Return Code: + IGNORE - contour could not be traced + HOOK_FOUND - minutiae determined to lie on same qualifying hook + FALSE - minutiae determined not to lie on same qualifying hook + Negative - system error +**************************************************************************/ +int on_hook(const MINUTIA *minutia1, const MINUTIA *minutia2, + const int max_hook_len, + unsigned char *bdata, const int iw, const int ih) +{ + int ret; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + + /* NOTE: This routine should only be called when the 2 minutia points */ + /* are of "opposite" type. */ + + /* Trace the contour of the feature starting at the 1st minutia's */ + /* "edge" point and stepping along up to the specified maximum number */ + /* of steps or until the 2nd minutia point is encountered. */ + /* First search for edge neighbors clockwise. */ + + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, max_hook_len, + minutia2->x, minutia2->y, minutia1->ex, minutia1->ey, + minutia1->x, minutia1->y, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If trace was not possible, return IGNORE. */ + if(ret == IGNORE) + return(ret); + + /* If the trace encountered the second minutia point ... */ + if(ret == LOOP_FOUND){ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + return(HOOK_FOUND); + } + + /* If trace had an error in following the contour ... */ + if(ret != 0) + return(ret); + + + /* Otherwise, the trace successfully followed the contour, but did */ + /* not encounter the 2nd minutia point within the specified number */ + /* of steps. */ + + /* Deallocate previously extracted contour. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /* Try searching contour from 1st minutia "edge" searching for */ + /* edge neighbors counter-clockwise. */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, max_hook_len, + minutia2->x, minutia2->y, minutia1->ex, minutia1->ey, + minutia1->x, minutia1->y, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + + /* If trace was not possible, return IGNORE. */ + if(ret == IGNORE) + return(ret); + + /* If the trace encountered the second minutia point ... */ + if(ret == LOOP_FOUND){ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + return(HOOK_FOUND); + } + + /* If the trace successfully followed the 1st minutia's contour, but */ + /* did not encounter the 2nd minutia point within the specified number */ + /* of steps ... */ + if(ret == 0){ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Then return hook NOT found (FALSE). */ + return(FALSE); + } + + /* Otherwise, the 2nd trace had an error in following the contour ... */ + return(ret); +} + +/************************************************************************* +************************************************************************** +#cat: is_loop_clockwise - Takes a feature's contour points and determines if +#cat: the points are ordered clockwise or counter-clockwise about +#cat: the feature. The routine also requires a default return +#cat: value be specified in the case the the routine is not able +#cat: to definitively determine the contour's order. This allows +#cat: the default response to be application-specific. + + Input: + contour_x - x-coord list for feature's contour points + contour_y - y-coord list for feature's contour points + ncontour - number of points in contour + default_ret - default return code (used when we can't tell the order) + Return Code: + TRUE - contour determined to be ordered clockwise + FALSE - contour determined to be ordered counter-clockwise + Default - could not determine the order of the contour + Negative - system error +**************************************************************************/ +int is_loop_clockwise(const int *contour_x, const int *contour_y, + const int ncontour, const int default_ret) +{ + int ret; + int *chain, nchain; + + /* Derive chain code from contour points. */ + if((ret = chain_code_loop(&chain, &nchain, + contour_x, contour_y, ncontour))) + /* If there is a system error, return the error code. */ + return(ret); + + /* If chain is empty... */ + if(nchain == 0){ + /* There wasn't enough contour points to tell, so return the */ + /* the default return value. No chain needs to be deallocated */ + /* in this case. */ + return(default_ret); + } + + /* If the chain code for contour is clockwise ... pass default return */ + /* value on to this routine to correctly handle the case where we can't */ + /* tell the direction of the chain code. */ + ret = is_chain_clockwise(chain, nchain, default_ret); + + /* Free the chain code and return result. */ + free(chain); + return(ret); +} + +/************************************************************************* +************************************************************************** +#cat: process_loop - Takes a contour list that has been determined to form +#cat: a complete loop, and processes it. If the loop is sufficiently +#cat: large and elongated, then two minutia points are calculated +#cat: along the loop's longest aspect axis. If it is determined +#cat: that the loop does not contain minutiae, it is filled in the +#cat: binary image. + + Input: + contour_x - x-coord list for loop's contour points + contour_y - y-coord list for loop's contour points + contour_ex - x-coord list for loop's edge points + contour_ey - y-coord list for loop's edge points + ncontour - number of points in contour + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + OR + bdata - binary image data with loop filled + Return Code: + Zero - loop processed successfully + Negative - system error +**************************************************************************/ +int process_loop(MINUTIAE *minutiae, + const int *contour_x, const int *contour_y, + const int *contour_ex, const int *contour_ey, const int ncontour, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int halfway; + int idir, type, appearing; + double min_dist, max_dist; + int min_fr, max_fr, min_to, max_to; + int mid_x, mid_y, mid_pix; + int feature_pix; + int ret; + MINUTIA *minutia; + + /* If contour is empty, then just return. */ + if(ncontour <= 0) + return(0); + + /* If loop is large enough ... */ + if(ncontour > lfsparms->min_loop_len){ + /* Get pixel value of feature's interior. */ + feature_pix = *(bdata + (contour_y[0] * iw) + contour_x[0]); + + /* Compute half the perimeter of the loop. */ + halfway = ncontour>>1; + + /* Get the aspect dimensions of the loop in units of */ + /* squared distance. */ + get_loop_aspect(&min_fr, &min_to, &min_dist, + &max_fr, &max_to, &max_dist, + contour_x, contour_y, ncontour); + + /* If loop passes aspect ratio tests ... loop is sufficiently */ + /* narrow or elongated ... */ + if((min_dist < lfsparms->min_loop_aspect_dist) || + ((max_dist/min_dist) >= lfsparms->min_loop_aspect_ratio)){ + + /* Update minutiae list with opposite points of max distance */ + /* on the loop. */ + + /* First, check if interior point has proper pixel value. */ + mid_x = (contour_x[max_fr]+contour_x[max_to])>>1; + mid_y = (contour_y[max_fr]+contour_y[max_to])>>1; + mid_pix = *(bdata + (mid_y * iw) + mid_x); + /* If interior point is the same as the feature... */ + if(mid_pix == feature_pix){ + + /* 1. Treat maximum distance point as a potential minutia. */ + + /* Compute direction from maximum loop point to its */ + /* opposite point. */ + idir = line2direction(contour_x[max_fr], contour_y[max_fr], + contour_x[max_to], contour_y[max_to], + lfsparms->num_directions); + /* Get type of minutia: BIFURCATION or RIDGE_ENDING. */ + type = minutia_type(feature_pix); + /* Determine if minutia is appearing or disappearing. */ + if((appearing = is_minutia_appearing( + contour_x[max_fr], contour_y[max_fr], + contour_ex[max_fr], contour_ey[max_fr])) < 0){ + /* Return system error code. */ + return(appearing); + } + /* Create new minutia object. */ + if((ret = create_minutia(&minutia, + contour_x[max_fr], contour_y[max_fr], + contour_ex[max_fr], contour_ey[max_fr], + idir, DEFAULT_RELIABILITY, + type, appearing, LOOP_ID))){ + /* Return system error code. */ + return(ret); + } + /* Update the minutiae list with potential new minutia. */ + ret = update_minutiae(minutiae, minutia, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* 2. Treat point opposite of maximum distance point as */ + /* a potential minutia. */ + + /* Flip the direction 180 degrees. Make sure new direction */ + /* is on the range [0..(ndirsX2)]. */ + idir += lfsparms->num_directions; + idir %= (lfsparms->num_directions<<1); + + /* The type of minutia will stay the same. */ + + /* Determine if minutia is appearing or disappearing. */ + if((appearing = is_minutia_appearing( + contour_x[max_to], contour_y[max_to], + contour_ex[max_to], contour_ey[max_to])) < 0){ + /* Return system error code. */ + return(appearing); + } + /* Create new minutia object. */ + if((ret = create_minutia(&minutia, + contour_x[max_to], contour_y[max_to], + contour_ex[max_to], contour_ey[max_to], + idir, DEFAULT_RELIABILITY, + type, appearing, LOOP_ID))){ + /* Return system error code. */ + return(ret); + } + /* Update the minutiae list with potential new minutia. */ + ret = update_minutiae(minutiae, minutia, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* Done successfully processing this loop, so return normally. */ + return(0); + + } /* Otherwise, loop interior has problems. */ + } /* Otherwise, loop is not the right shape for minutiae. */ + } /* Otherwise, loop's perimeter is too small for minutiae. */ + + /* If we get here, we have a loop that is assumed to not contain */ + /* minutiae, so remove the loop from the image. */ + ret = fill_loop(contour_x, contour_y, ncontour, bdata, iw, ih); + + /* Return either an error code from fill_loop or return normally. */ + return(ret); +} + +/************************************************************************* +************************************************************************** +#cat: process_loop_V2 - Takes a contour list that has been determined to form +#cat: a complete loop, and processes it. If the loop is sufficiently +#cat: large and elongated, then two minutia points are calculated +#cat: along the loop's longest aspect axis. If it is determined +#cat: that the loop does not contain minutiae, it is filled in the +#cat: binary image. + + Input: + contour_x - x-coord list for loop's contour points + contour_y - y-coord list for loop's contour points + contour_ex - x-coord list for loop's edge points + contour_ey - y-coord list for loop's edge points + ncontour - number of points in contour + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + plow_flow_map - pixelized Low Ridge Flow Map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + OR + bdata - binary image data with loop filled + Return Code: + Zero - loop processed successfully + Negative - system error +**************************************************************************/ +int process_loop_V2(MINUTIAE *minutiae, + const int *contour_x, const int *contour_y, + const int *contour_ex, const int *contour_ey, const int ncontour, + unsigned char *bdata, const int iw, const int ih, + int *plow_flow_map, const LFSPARMS *lfsparms) +{ + int halfway; + int idir, type, appearing; + double min_dist, max_dist; + int min_fr, max_fr, min_to, max_to; + int mid_x, mid_y, mid_pix; + int feature_pix; + int ret; + MINUTIA *minutia; + int fmapval; + double reliability; + + /* If contour is empty, then just return. */ + if(ncontour <= 0) + return(0); + + /* If loop is large enough ... */ + if(ncontour > lfsparms->min_loop_len){ + /* Get pixel value of feature's interior. */ + feature_pix = *(bdata + (contour_y[0] * iw) + contour_x[0]); + + /* Compute half the perimeter of the loop. */ + halfway = ncontour>>1; + + /* Get the aspect dimensions of the loop in units of */ + /* squared distance. */ + get_loop_aspect(&min_fr, &min_to, &min_dist, + &max_fr, &max_to, &max_dist, + contour_x, contour_y, ncontour); + + /* If loop passes aspect ratio tests ... loop is sufficiently */ + /* narrow or elongated ... */ + if((min_dist < lfsparms->min_loop_aspect_dist) || + ((max_dist/min_dist) >= lfsparms->min_loop_aspect_ratio)){ + + /* Update minutiae list with opposite points of max distance */ + /* on the loop. */ + + /* First, check if interior point has proper pixel value. */ + mid_x = (contour_x[max_fr]+contour_x[max_to])>>1; + mid_y = (contour_y[max_fr]+contour_y[max_to])>>1; + mid_pix = *(bdata + (mid_y * iw) + mid_x); + /* If interior point is the same as the feature... */ + if(mid_pix == feature_pix){ + + /* 1. Treat maximum distance point as a potential minutia. */ + + /* Compute direction from maximum loop point to its */ + /* opposite point. */ + idir = line2direction(contour_x[max_fr], contour_y[max_fr], + contour_x[max_to], contour_y[max_to], + lfsparms->num_directions); + /* Get type of minutia: BIFURCATION or RIDGE_ENDING. */ + type = minutia_type(feature_pix); + /* Determine if minutia is appearing or disappearing. */ + if((appearing = is_minutia_appearing( + contour_x[max_fr], contour_y[max_fr], + contour_ex[max_fr], contour_ey[max_fr])) < 0){ + /* Return system error code. */ + return(appearing); + } + + /* Is the new point in a LOW RIDGE FLOW block? */ + fmapval = *(plow_flow_map+(contour_y[max_fr]*iw)+ + contour_x[max_fr]); + + /* If current minutia is in a LOW RIDGE FLOW block ... */ + if(fmapval) + reliability = MEDIUM_RELIABILITY; + else + /* Otherwise, minutia is in a reliable block. */ + reliability = HIGH_RELIABILITY; + + /* Create new minutia object. */ + if((ret = create_minutia(&minutia, + contour_x[max_fr], contour_y[max_fr], + contour_ex[max_fr], contour_ey[max_fr], + idir, reliability, + type, appearing, LOOP_ID))){ + /* Return system error code. */ + return(ret); + } + /* Update the minutiae list with potential new minutia. */ + /* NOTE: Deliberately using version one of this routine. */ + ret = update_minutiae(minutiae, minutia, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* 2. Treat point opposite of maximum distance point as */ + /* a potential minutia. */ + + /* Flip the direction 180 degrees. Make sure new direction */ + /* is on the range [0..(ndirsX2)]. */ + idir += lfsparms->num_directions; + idir %= (lfsparms->num_directions<<1); + + /* The type of minutia will stay the same. */ + + /* Determine if minutia is appearing or disappearing. */ + if((appearing = is_minutia_appearing( + contour_x[max_to], contour_y[max_to], + contour_ex[max_to], contour_ey[max_to])) < 0){ + /* Return system error code. */ + return(appearing); + } + + /* Is the new point in a LOW RIDGE FLOW block? */ + fmapval = *(plow_flow_map+(contour_y[max_to]*iw)+ + contour_x[max_to]); + + /* If current minutia is in a LOW RIDGE FLOW block ... */ + if(fmapval) + reliability = MEDIUM_RELIABILITY; + else + /* Otherwise, minutia is in a reliable block. */ + reliability = HIGH_RELIABILITY; + + /* Create new minutia object. */ + if((ret = create_minutia(&minutia, + contour_x[max_to], contour_y[max_to], + contour_ex[max_to], contour_ey[max_to], + idir, reliability, + type, appearing, LOOP_ID))){ + /* Return system error code. */ + return(ret); + } + + /* Update the minutiae list with potential new minutia. */ + /* NOTE: Deliberately using version one of this routine. */ + ret = update_minutiae(minutiae, minutia, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* Done successfully processing this loop, so return normally. */ + return(0); + + } /* Otherwise, loop interior has problems. */ + } /* Otherwise, loop is not the right shape for minutiae. */ + } /* Otherwise, loop's perimeter is too small for minutiae. */ + + /* If we get here, we have a loop that is assumed to not contain */ + /* minutiae, so remove the loop from the image. */ + ret = fill_loop(contour_x, contour_y, ncontour, bdata, iw, ih); + + /* Return either an error code from fill_loop or return normally. */ + return(ret); +} + +/************************************************************************* +************************************************************************** +#cat: get_loop_aspect - Takes a contour list (determined to form a complete +#cat: loop) and measures the loop's aspect (the largest and smallest +#cat: distances across the loop) and returns the points on the +#cat: loop where these distances occur. + + Input: + contour_x - x-coord list for loop's contour points + contour_y - y-coord list for loop's contour points + ncontour - number of points in contour + Output: + omin_fr - contour point index where minimum aspect occurs + omin_to - opposite contour point index where minimum aspect occurs + omin_dist - the minimum distance across the loop + omax_fr - contour point index where maximum aspect occurs + omax_to - contour point index where maximum aspect occurs + omax_dist - the maximum distance across the loop +**************************************************************************/ +void get_loop_aspect(int *omin_fr, int *omin_to, double *omin_dist, + int *omax_fr, int *omax_to, double *omax_dist, + const int *contour_x, const int *contour_y, const int ncontour) +{ + int halfway, limit; + int i, j; + double dist; + double min_dist, max_dist; + int min_i, max_i, min_j, max_j; + + /* Compute half the perimeter of the loop. */ + halfway = ncontour>>1; + + /* Take opposite points on the contour and walk half way */ + /* around the loop. */ + i = 0; + j = halfway; + /* Compute squared distance between opposite points on loop. */ + dist = squared_distance(contour_x[i], contour_y[i], + contour_x[j], contour_y[j]); + + /* Initialize running minimum and maximum distances along loop. */ + min_dist = dist; + min_i = i; + min_j = j; + max_dist = dist; + max_i = i; + max_j = j; + /* Bump to next pair of opposite points. */ + i++; + /* Make sure j wraps around end of list. */ + j++; + j %= ncontour; + + /* If the loop is of even length, then we only need to walk half */ + /* way around as the other half will be exactly redundant. If */ + /* the loop is of odd length, then the second half will not be */ + /* be exactly redundant and the difference "may" be meaningful. */ + /* If execution speed is an issue, then probably get away with */ + /* walking only the fist half of the loop under ALL conditions. */ + + /* If loop has odd length ... */ + if(ncontour % 2) + /* Walk the loop's entire perimeter. */ + limit = ncontour; + /* Otherwise the loop has even length ... */ + else + /* Only walk half the perimeter. */ + limit = halfway; + + /* While we have not reached our perimeter limit ... */ + while(i < limit){ + /* Compute squared distance between opposite points on loop. */ + dist = squared_distance(contour_x[i], contour_y[i], + contour_x[j], contour_y[j]); + /* Check the running minimum and maximum distances. */ + if(dist < min_dist){ + min_dist = dist; + min_i = i; + min_j = j; + } + if(dist > max_dist){ + max_dist = dist; + max_i = i; + max_j = j; + } + /* Bump to next pair of opposite points. */ + i++; + /* Make sure j wraps around end of list. */ + j++; + j %= ncontour; + } + + /* Assign minimum and maximum distances to output pointers. */ + *omin_fr = min_i; + *omin_to = min_j; + *omin_dist = min_dist; + *omax_fr = max_i; + *omax_to = max_j; + *omax_dist = max_dist; +} + +/************************************************************************* +************************************************************************** +#cat: fill_loop - Takes a contour list that has been determined to form +#cat: a complete loop, and fills the loop accounting for +#cat: complex/concaved shapes. +#cat: NOTE, I tried using a flood-fill in place of this routine, +#cat: but the contour (although 8-connected) is NOT guaranteed to +#cat: be "complete" surrounded (in an 8-connected sense) by pixels +#cat: of opposite color. Therefore, the flood would occasionally +#cat: escape the loop and corrupt the binary image! + + Input: + contour_x - x-coord list for loop's contour points + contour_y - y-coord list for loop's contour points + ncontour - number of points in contour + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + bdata - binary image data with loop filled + Return Code: + Zero - loop filled successfully + Negative - system error +**************************************************************************/ +int fill_loop(const int *contour_x, const int *contour_y, + const int ncontour, unsigned char *bdata, + const int iw, const int ih) +{ + SHAPE *shape; + int ret, i, j, x, nx, y; + int lastj; + int next_pix, feature_pix, edge_pix; + + /* Create a shape structure from loop's contour. */ + if((ret = shape_from_contour(&shape, contour_x, contour_y, ncontour))) + /* If system error, then return error code. */ + return(ret); + + /* Get feature pixel value (the value on the interior of the loop */ + /* to be filled). */ + feature_pix = *(bdata+(contour_y[0]*iw)+contour_x[0]); + /* Now get edge pixel value (the value on the exterior of the loop */ + /* to be used to filled the loop). We can get this value by flipping */ + /* the feature pixel value. */ + if(feature_pix) + edge_pix = 0; + else + edge_pix = 1; + + /* Foreach row in shape... */ + for(i = 0; i < shape->nrows; i++){ + /* Get y-coord of current row in shape. */ + y = shape->rows[i]->y; + + /* There should always be at least 1 contour points in the row. */ + /* If there isn't, then something is wrong, so post a warning and */ + /* just return. This is mostly for debug purposes. */ + if(shape->rows[i]->npts < 1){ + /* Deallocate the shape. */ + free_shape(shape); + fprintf(stderr, + "WARNING : fill_loop : unexpected shape, preempting loop fill\n"); + /* This is unexpected, but not fatal, so return normally. */ + return(0); + } + + /* Reset x index on row to the left-most contour point in the row. */ + j = 0; + /* Get first x-coord corresponding to the first contour point on row. */ + x = shape->rows[i]->xs[j]; + /* Fill the first contour point on the row. */ + *(bdata+(y*iw)+x) = edge_pix; + /* Set the index of last contour point on row. */ + lastj = shape->rows[i]->npts - 1; + /* While last contour point on row has not been processed... */ + while(j < lastj){ + + /* On each interation, we have filled up to the current */ + /* contour point on the row pointed to by "j", and now we */ + /* need to determine if we need to skip some edge pixels */ + /* caused by a concavity in the shape or not. */ + + /* Get the next pixel value on the row just right of the */ + /* last contour point filled. We know there are more points */ + /* on the row because we haven't processed the last contour */ + /* point on the row yet. */ + x++; + next_pix = *(bdata+(y*iw)+x); + + /* If the next pixel is the same value as loop's edge pixels ... */ + if(next_pix == edge_pix){ + /* Then assume we have found a concavity and skip to next */ + /* contour point on row. */ + j++; + /* Fill the new contour point because we know it is on the */ + /* feature's contour. */ + x = shape->rows[i]->xs[j]; + *(bdata+(y*iw)+x) = edge_pix; + + /* Now we are ready to loop again. */ + } + + /* Otherwise, fill from current pixel up through the next contour */ + /* point to the right on the row. */ + else{ + /* Bump to the next contour point to the right on row. */ + j++; + /* Set the destination x-coord to the next contour point */ + /* to the right on row. Realize that this could be the */ + /* same pixel as the current x-coord if contour points are */ + /* adjacent. */ + nx = shape->rows[i]->xs[j]; + + /* Fill between current x-coord and next contour point to the */ + /* right on the row (including the new contour point).*/ + fill_partial_row(edge_pix, x, nx, y, bdata, iw, ih); + } + + /* Once we are here we have filled the row up to (and including) */ + /* the contour point currently pointed to by "j". */ + /* We are now ready to loop again. */ + + } /* End WHILE */ + } /* End FOR */ + + free_shape(shape); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: fill_partial_row - Fills a specified range of contiguous pixels on +#cat: a specified row of an 8-bit pixel image with a specified +#cat: pixel value. NOTE, the pixel coordinates are assumed to +#cat: be within the image boundaries. + + Input: + fill_pix - pixel value to fill with (should be on range [0..255] + frx - x-pixel coord where fill should begin + tox - x-pixel coord where fill should end (inclusive) + y - y-pixel coord of current row being filled + bdata - 8-bit image data + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + bdata - 8-bit image data with partial row filled. +**************************************************************************/ +void fill_partial_row(const int fill_pix, const int frx, const int tox, + const int y, unsigned char *bdata, const int iw, const int ih) +{ + int x; + unsigned char *bptr; + + /* Set pixel pointer to starting x-coord on current row. */ + bptr = bdata+(y*iw)+frx; + + /* Foreach pixel between starting and ending x-coord on row */ + /* (including the end points) ... */ + for(x = frx; x <= tox; x++){ + /* Set current pixel with fill pixel value. */ + *bptr = fill_pix; + /* Bump to next pixel in the row. */ + bptr++; + } +} + +/************************************************************************* +************************************************************************** +#cat: flood_loop - Fills a given contour (determined to form a complete loop) +#cat: with a specified pixel value using a recursive flood-fill +#cat: technique. +#cat: NOTE, this fill approach will NOT always work with the +#cat: contours generated in this application because they +#cat: are NOT guaranteed to be ENTIRELY surrounded by 8-connected +#cat: pixels not equal to the fill pixel value. This is unfortunate +#cat: because the flood-fill is a simple algorithm that will handle +#cat: complex/concaved shapes. + + Input: + contour_x - x-coord list for loop's contour points + contour_y - y-coord list for loop's contour points + ncontour - number of points in contour + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + bdata - binary image data with loop filled +**************************************************************************/ +void flood_loop(const int *contour_x, const int *contour_y, + const int ncontour, unsigned char *bdata, + const int iw, const int ih) +{ + int feature_pix, fill_pix; + int i; + + /* Get the pixel value of the minutia feauture. This is */ + /* the pixel value we wish to replace with the flood. */ + feature_pix = *(bdata + (contour_y[0] * iw) + contour_x[0]); + + /* Flip the feature pixel value to the value we want to */ + /* fill with and send this value to the flood routine. */ + fill_pix = !feature_pix; + + /* Flood-fill interior of contour using a 4-neighbor fill. */ + /* We are using a 4-neighbor fill because the contour was */ + /* collected using 8-neighbors, and the 4-neighbor fill */ + /* will NOT escape the 8-neighbor based contour. */ + /* The contour passed must be guarenteed to be complete for */ + /* the flood-fill to work properly. */ + /* We are initiating a flood-fill from each point on the */ + /* contour to make sure complex patterns get filled in. */ + /* The complex patterns we are concerned about are those */ + /* that "pinch" the interior of the feature off due to */ + /* skipping "exposed" corners along the contour. */ + /* Simple shapes will fill upon invoking the first contour */ + /* pixel, and the subsequent calls will immediately return */ + /* as their seed pixel will have already been flipped. */ + for(i = 0; i < ncontour; i++){ + /* Start the recursive flooding. */ + flood_fill4(fill_pix, contour_x[i], contour_y[i], + bdata, iw, ih); + } +} + +/************************************************************************* +************************************************************************** +#cat: flood_fill4 - Recursively floods a region of an 8-bit pixel image with a +#cat: specified pixel value given a starting (seed) point. The +#cat: recursion is based neighbors being 4-connected. + + Input: + fill_pix - 8-bit pixel value to be filled with (on range [0..255] + x - starting x-pixel coord + y - starting y-pixel coord + bdata - 8-bit pixel image data + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + bdata - 8-bit pixel image data with region filled +**************************************************************************/ +void flood_fill4(const int fill_pix, const int x, const int y, + unsigned char *bdata, const int iw, const int ih) +{ + unsigned char *pptr; + int y_north, y_south, x_east, x_west; + + /* Get address of current pixel. */ + pptr = bdata + (y*iw) + x; + /* If pixel needs to be filled ... */ + if(*pptr != fill_pix){ + /* Fill the current pixel. */ + *pptr = fill_pix; + + /* Recursively invoke flood on the pixel's 4 neighbors. */ + /* Test to make sure neighbors are within image boudaries */ + /* before invoking each flood. */ + y_north = y-1; + y_south = y+1; + x_west = x-1; + x_east = x+1; + + /* Invoke North */ + if(y_north >= 0) + flood_fill4(fill_pix, x, y_north, bdata, iw, ih); + + /* Invoke East */ + if(x_east < iw) + flood_fill4(fill_pix, x_east, y, bdata, iw, ih); + + /* Invoke South */ + if(y_south < ih) + flood_fill4(fill_pix, x, y_south, bdata, iw, ih); + + /* Invoke West */ + if(x_west >= 0) + flood_fill4(fill_pix, x_west, y, bdata, iw, ih); + } + + /* Otherwise, there is nothing to be done. */ +} diff --git a/libfprint/nbis/mindtct/maps.c b/libfprint/nbis/mindtct/maps.c new file mode 100644 index 0000000..3050c5c --- /dev/null +++ b/libfprint/nbis/mindtct/maps.c @@ -0,0 +1,2552 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: MAPS.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 10/26/1999 by MDG + To permit margin blocks to be flagged in + low contrast and low flow maps. + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for computing various block-based + maps (including directional ridge flow maps) from an arbitrarily- + sized image as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + gen_image_maps() + gen_initial_maps() + interpolate_direction_map() + morph_TF_map() + pixelize_map() + smooth_direction_map() + gen_high_curve_map() + gen_imap() + gen_initial_imap() + primary_dir_test() + secondary_fork_test() + remove_incon_dirs() + test_top_edge() + test_right_edge() + test_bottom_edge() + test_left_edge() + remove_dir() + average_8nbr_dir() + num_valid_8nbrs() + smooth_imap() + gen_nmap() + vorticity() + accum_nbr_vorticity() + curvature() + +***********************************************************************/ + +#include +#include +#include +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: gen_image_maps - Computes a set of image maps based on Version 2 +#cat: of the NIST LFS System. The first map is a Direction Map +#cat: which is a 2D vector of integer directions, where each +#cat: direction represents the dominant ridge flow in a block of +#cat: the input grayscale image. The Low Contrast Map flags +#cat: blocks with insufficient contrast. The Low Flow Map flags +#cat: blocks with insufficient ridge flow. The High Curve Map +#cat: flags blocks containing high curvature. This routine will +#cat: generate maps for an arbitrarily sized, non-square, image. + + Input: + pdata - padded input image data (8 bits [0..256) grayscale) + pw - padded width (in pixels) of the input image + ph - padded height (in pixels) of the input image + dir2rad - lookup table for converting integer directions + dftwaves - structure containing the DFT wave forms + dftgrids - structure containing the rotated pixel grid offsets + lfsparms - parameters and thresholds for controlling LFS + Output: + odmap - points to the created Direction Map + olcmap - points to the created Low Contrast Map + olfmap - points to the Low Ridge Flow Map + ohcmap - points to the High Curvature Map + omw - width (in blocks) of the maps + omh - height (in blocks) of the maps + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int gen_image_maps(int **odmap, int **olcmap, int **olfmap, int **ohcmap, + int *omw, int *omh, + unsigned char *pdata, const int pw, const int ph, + const DIR2RAD *dir2rad, const DFTWAVES *dftwaves, + const ROTGRIDS *dftgrids, const LFSPARMS *lfsparms) +{ + int *direction_map, *low_contrast_map, *low_flow_map, *high_curve_map; + int mw, mh, iw, ih; + int *blkoffs; + int ret; /* return code */ + + /* 1. Compute block offsets for the entire image, accounting for pad */ + /* Block_offsets() assumes square block (grid), so ERROR otherwise. */ + if(dftgrids->grid_w != dftgrids->grid_h){ + fprintf(stderr, + "ERROR : gen_image_maps : DFT grids must be square\n"); + return(-540); + } + /* Compute unpadded image dimensions. */ + iw = pw - (dftgrids->pad<<1); + ih = ph - (dftgrids->pad<<1); + if((ret = block_offsets(&blkoffs, &mw, &mh, iw, ih, + dftgrids->pad, lfsparms->blocksize))){ + return(ret); + } + + /* 2. Generate initial Direction Map and Low Contrast Map*/ + if((ret = gen_initial_maps(&direction_map, &low_contrast_map, + &low_flow_map, blkoffs, mw, mh, + pdata, pw, ph, dftwaves, dftgrids, lfsparms))){ + /* Free memory allocated to this point. */ + free(blkoffs); + return(ret); + } + + if((ret = morph_TF_map(low_flow_map, mw, mh, lfsparms))){ + return(ret); + } + + /* 3. Remove directions that are inconsistent with neighbors */ + remove_incon_dirs(direction_map, mw, mh, dir2rad, lfsparms); + + + /* 4. Smooth Direction Map values with their neighbors */ + smooth_direction_map(direction_map, low_contrast_map, mw, mh, + dir2rad, lfsparms); + + /* 5. Interpolate INVALID direction blocks with their valid neighbors. */ + if((ret = interpolate_direction_map(direction_map, low_contrast_map, + mw, mh, lfsparms))){ + return(ret); + } + + /* May be able to skip steps 6 and/or 7 if computation time */ + /* is a critical factor. */ + + /* 6. Remove directions that are inconsistent with neighbors */ + remove_incon_dirs(direction_map, mw, mh, dir2rad, lfsparms); + + /* 7. Smooth Direction Map values with their neighbors. */ + smooth_direction_map(direction_map, low_contrast_map, mw, mh, + dir2rad, lfsparms); + + /* 8. Set the Direction Map values in the image margin to INVALID. */ + set_margin_blocks(direction_map, mw, mh, INVALID_DIR); + + /* 9. Generate High Curvature Map from interpolated Direction Map. */ + if((ret = gen_high_curve_map(&high_curve_map, direction_map, mw, mh, + lfsparms))){ + return(ret); + } + + /* Deallocate working memory. */ + free(blkoffs); + + *odmap = direction_map; + *olcmap = low_contrast_map; + *olfmap = low_flow_map; + *ohcmap = high_curve_map; + *omw = mw; + *omh = mh; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: gen_initial_maps - Creates an initial Direction Map from the given +#cat: input image. It very important that the image be properly +#cat: padded so that rotated grids along the boundary of the image +#cat: do not access unkown memory. The rotated grids are used by a +#cat: DFT-based analysis to determine the integer directions +#cat: in the map. Typically this initial vector of directions will +#cat: subsequently have weak or inconsistent directions removed +#cat: followed by a smoothing process. The resulting Direction +#cat: Map contains valid directions >= 0 and INVALID values = -1. +#cat: This routine also computes and returns 2 other image maps. +#cat: The Low Contrast Map flags blocks in the image with +#cat: insufficient contrast. Blocks with low contrast have a +#cat: corresponding direction of INVALID in the Direction Map. +#cat: The Low Flow Map flags blocks in which the DFT analyses +#cat: could not determine a significant ridge flow. Blocks with +#cat: low ridge flow also have a corresponding direction of +#cat: INVALID in the Direction Map. + + Input: + blkoffs - offsets to the pixel origin of each block in the padded image + mw - number of blocks horizontally in the padded input image + mh - number of blocks vertically in the padded input image + pdata - padded input image data (8 bits [0..256) grayscale) + pw - width (in pixels) of the padded input image + ph - height (in pixels) of the padded input image + dftwaves - structure containing the DFT wave forms + dftgrids - structure containing the rotated pixel grid offsets + lfsparms - parameters and thresholds for controlling LFS + Output: + odmap - points to the newly created Direction Map + olcmap - points to the newly created Low Contrast Map + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int gen_initial_maps(int **odmap, int **olcmap, int **olfmap, + int *blkoffs, const int mw, const int mh, + unsigned char *pdata, const int pw, const int ph, + const DFTWAVES *dftwaves, const ROTGRIDS *dftgrids, + const LFSPARMS *lfsparms) +{ + int *direction_map, *low_contrast_map, *low_flow_map; + int bi, bsize, blkdir; + int *wis, *powmax_dirs; + double **powers, *powmaxs, *pownorms; + int nstats; + int ret; /* return code */ + int dft_offset; + int xminlimit, xmaxlimit, yminlimit, ymaxlimit; + int win_x, win_y, low_contrast_offset; + + print2log("INITIAL MAP\n"); + + /* Compute total number of blocks in map */ + bsize = mw * mh; + + /* Allocate Direction Map memory */ + direction_map = (int *)malloc(bsize * sizeof(int)); + if(direction_map == (int *)NULL){ + fprintf(stderr, + "ERROR : gen_initial_maps : malloc : direction_map\n"); + return(-550); + } + /* Initialize the Direction Map to INVALID (-1). */ + memset(direction_map, INVALID_DIR, bsize * sizeof(int)); + + /* Allocate Low Contrast Map memory */ + low_contrast_map = (int *)malloc(bsize * sizeof(int)); + if(low_contrast_map == (int *)NULL){ + free(direction_map); + fprintf(stderr, + "ERROR : gen_initial_maps : malloc : low_contrast_map\n"); + return(-551); + } + /* Initialize the Low Contrast Map to FALSE (0). */ + memset(low_contrast_map, 0, bsize * sizeof(int)); + + /* Allocate Low Ridge Flow Map memory */ + low_flow_map = (int *)malloc(bsize * sizeof(int)); + if(low_flow_map == (int *)NULL){ + free(direction_map); + free(low_contrast_map); + fprintf(stderr, + "ERROR : gen_initial_maps : malloc : low_flow_map\n"); + return(-552); + } + /* Initialize the Low Flow Map to FALSE (0). */ + memset(low_flow_map, 0, bsize * sizeof(int)); + + /* Allocate DFT directional power vectors */ + if((ret = alloc_dir_powers(&powers, dftwaves->nwaves, dftgrids->ngrids))){ + /* Free memory allocated to this point. */ + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + return(ret); + } + + /* Allocate DFT power statistic arrays */ + /* Compute length of statistics arrays. Statistics not needed */ + /* for the first DFT wave, so the length is number of waves - 1. */ + nstats = dftwaves->nwaves - 1; + if((ret = alloc_power_stats(&wis, &powmaxs, &powmax_dirs, + &pownorms, nstats))){ + /* Free memory allocated to this point. */ + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free_dir_powers(powers, dftwaves->nwaves); + return(ret); + } + + /* Compute special window origin limits for determining low contrast. */ + /* These pixel limits avoid analyzing the padded borders of the image. */ + xminlimit = dftgrids->pad; + yminlimit = dftgrids->pad; + xmaxlimit = pw - dftgrids->pad - lfsparms->windowsize - 1; + ymaxlimit = ph - dftgrids->pad - lfsparms->windowsize - 1; + + /* Foreach block in image ... */ + for(bi = 0; bi < bsize; bi++){ + /* Adjust block offset from pointing to block origin to pointing */ + /* to surrounding window origin. */ + dft_offset = blkoffs[bi] - (lfsparms->windowoffset * pw) - + lfsparms->windowoffset; + + /* Compute pixel coords of window origin. */ + win_x = dft_offset % pw; + win_y = (int)(dft_offset / pw); + + /* Make sure the current window does not access padded image pixels */ + /* for analyzing low contrast. */ + win_x = max(xminlimit, win_x); + win_x = min(xmaxlimit, win_x); + win_y = max(yminlimit, win_y); + win_y = min(ymaxlimit, win_y); + low_contrast_offset = (win_y * pw) + win_x; + + print2log(" BLOCK %2d (%2d, %2d) ", bi, bi%mw, bi/mw); + + /* If block is low contrast ... */ + if((ret = low_contrast_block(low_contrast_offset, lfsparms->windowsize, + pdata, pw, ph, lfsparms))){ + /* If system error ... */ + if(ret < 0){ + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free_dir_powers(powers, dftwaves->nwaves); + free(wis); + free(powmaxs); + free(powmax_dirs); + free(pownorms); + return(ret); + } + + /* Otherwise, block is low contrast ... */ + print2log("LOW CONTRAST\n"); + low_contrast_map[bi] = TRUE; + /* Direction Map's block is already set to INVALID. */ + } + /* Otherwise, sufficient contrast for DFT processing ... */ + else { + print2log("\n"); + + /* Compute DFT powers */ + if((ret = dft_dir_powers(powers, pdata, low_contrast_offset, pw, ph, + dftwaves, dftgrids))){ + /* Free memory allocated to this point. */ + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free_dir_powers(powers, dftwaves->nwaves); + free(wis); + free(powmaxs); + free(powmax_dirs); + free(pownorms); + return(ret); + } + + /* Compute DFT power statistics, skipping first applied DFT */ + /* wave. This is dependent on how the primary and secondary */ + /* direction tests work below. */ + if((ret = dft_power_stats(wis, powmaxs, powmax_dirs, pownorms, powers, + 1, dftwaves->nwaves, dftgrids->ngrids))){ + /* Free memory allocated to this point. */ + free(direction_map); + free(low_contrast_map); + free(low_flow_map); + free_dir_powers(powers, dftwaves->nwaves); + free(wis); + free(powmaxs); + free(powmax_dirs); + free(pownorms); + return(ret); + } + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + { int _w; + fprintf(logfp, " Power\n"); + for(_w = 0; _w < nstats; _w++){ + /* Add 1 to wis[w] to create index to original dft_coefs[] */ + fprintf(logfp, " wis[%d] %d %12.3f %2d %9.3f %12.3f\n", + _w, wis[_w]+1, + powmaxs[wis[_w]], powmax_dirs[wis[_w]], pownorms[wis[_w]], + powers[0][powmax_dirs[wis[_w]]]); + } + } +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* Conduct primary direction test */ + blkdir = primary_dir_test(powers, wis, powmaxs, powmax_dirs, + pownorms, nstats, lfsparms); + + if(blkdir != INVALID_DIR) + direction_map[bi] = blkdir; + else{ + /* Conduct secondary (fork) direction test */ + blkdir = secondary_fork_test(powers, wis, powmaxs, powmax_dirs, + pownorms, nstats, lfsparms); + if(blkdir != INVALID_DIR) + direction_map[bi] = blkdir; + /* Otherwise current direction in Direction Map remains INVALID */ + else + /* Flag the block as having LOW RIDGE FLOW. */ + low_flow_map[bi] = TRUE; + } + + } /* End DFT */ + } /* bi */ + + /* Deallocate working memory */ + free_dir_powers(powers, dftwaves->nwaves); + free(wis); + free(powmaxs); + free(powmax_dirs); + free(pownorms); + + *odmap = direction_map; + *olcmap = low_contrast_map; + *olfmap = low_flow_map; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: interpolate_direction_map - Take a Direction Map and Low Contrast +#cat: Map and attempts to fill in INVALID directions in the +#cat: Direction Map based on a blocks valid neighbors. The +#cat: valid neighboring directions are combined in a weighted +#cat: average inversely proportional to their distance from +#cat: the block being interpolated. Low Contrast blocks are +#cat: used to prempt the search for a valid neighbor in a +#cat: specific direction, which keeps the process from +#cat: interpolating directions for blocks in the background and +#cat: and perimeter of the fingerprint in the image. + + Input: + direction_map - map of blocks containing directional ridge flow + low_contrast_map - map of blocks flagged as LOW CONTRAST + mw - number of blocks horizontally in the maps + mh - number of blocks vertically in the maps + lfsparms - parameters and thresholds for controlling LFS + Output: + direction_map - contains the newly interpolated results + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int interpolate_direction_map(int *direction_map, int *low_contrast_map, + const int mw, const int mh, const LFSPARMS *lfsparms) +{ + int x, y, new_dir; + int n_dir, e_dir, s_dir, w_dir; + int n_dist = 0, e_dist = 0, s_dist = 0, w_dist = 0, total_dist; + int n_found, e_found, s_found, w_found, total_found; + int n_delta = 0, e_delta = 0, s_delta = 0, w_delta = 0, total_delta; + int nbr_x, nbr_y; + int *omap, *dptr, *cptr, *optr; + double avr_dir; + + print2log("INTERPOLATE DIRECTION MAP\n"); + + /* Allocate output (interpolated) Direction Map. */ + omap = (int *)malloc(mw*mh*sizeof(int)); + if(omap == (int *)NULL){ + fprintf(stderr, + "ERROR : interpolate_direction_map : malloc : omap\n"); + return(-520); + } + + /* Set pointers to the first block in the maps. */ + dptr = direction_map; + cptr = low_contrast_map; + optr = omap; + + /* Foreach block in the maps ... */ + for(y = 0; y < mh; y++){ + for(x = 0; x < mw; x++){ + + /* If image block is NOT LOW CONTRAST and has INVALID direction ... */ + if((!*cptr) && (*dptr == INVALID_DIR)){ + + /* Set neighbor accumulators to 0. */ + total_found = 0; + total_dist = 0; + + /* Find north neighbor. */ + if((n_found = find_valid_block(&n_dir, &nbr_x, &nbr_y, + direction_map, low_contrast_map, + x, y, mw, mh, 0, -1)) == FOUND){ + /* Compute north distance. */ + n_dist = y - nbr_y; + /* Accumulate neighbor distance. */ + total_dist += n_dist; + /* Bump number of neighbors found. */ + total_found++; + } + + /* Find east neighbor. */ + if((e_found = find_valid_block(&e_dir, &nbr_x, &nbr_y, + direction_map, low_contrast_map, + x, y, mw, mh, 1, 0)) == FOUND){ + /* Compute east distance. */ + e_dist = nbr_x - x; + /* Accumulate neighbor distance. */ + total_dist += e_dist; + /* Bump number of neighbors found. */ + total_found++; + } + + /* Find south neighbor. */ + if((s_found = find_valid_block(&s_dir, &nbr_x, &nbr_y, + direction_map, low_contrast_map, + x, y, mw, mh, 0, 1)) == FOUND){ + /* Compute south distance. */ + s_dist = nbr_y - y; + /* Accumulate neighbor distance. */ + total_dist += s_dist; + /* Bump number of neighbors found. */ + total_found++; + } + + /* Find west neighbor. */ + if((w_found = find_valid_block(&w_dir, &nbr_x, &nbr_y, + direction_map, low_contrast_map, + x, y, mw, mh, -1, 0)) == FOUND){ + /* Compute west distance. */ + w_dist = x - nbr_x; + /* Accumulate neighbor distance. */ + total_dist += w_dist; + /* Bump number of neighbors found. */ + total_found++; + } + + /* If a sufficient number of neighbors found (Ex. 2) ... */ + if(total_found >= lfsparms->min_interpolate_nbrs){ + + /* Accumulate weighted sum of neighboring directions */ + /* inversely related to the distance from current block. */ + total_delta = 0.0; + /* If neighbor found to the north ... */ + if(n_found){ + n_delta = total_dist - n_dist; + total_delta += n_delta; + } + /* If neighbor found to the east ... */ + if(e_found){ + e_delta = total_dist - e_dist; + total_delta += e_delta; + } + /* If neighbor found to the south ... */ + if(s_found){ + s_delta = total_dist - s_dist; + total_delta += s_delta; + } + /* If neighbor found to the west ... */ + if(w_found){ + w_delta = total_dist - w_dist; + total_delta += w_delta; + } + + avr_dir = 0.0; + + if(n_found){ + avr_dir += (n_dir*(n_delta/(double)total_delta)); + } + if(e_found){ + avr_dir += (e_dir*(e_delta/(double)total_delta)); + } + if(s_found){ + avr_dir += (s_dir*(s_delta/(double)total_delta)); + } + if(w_found){ + avr_dir += (w_dir*(w_delta/(double)total_delta)); + } + + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + avr_dir = trunc_dbl_precision(avr_dir, TRUNC_SCALE); + + /* Assign interpolated direction to output Direction Map. */ + new_dir = sround(avr_dir); + + print2log(" Block %d,%d INTERP numnbs=%d newdir=%d\n", + x, y, total_found, new_dir); + + *optr = new_dir; + } + else{ + /* Otherwise, the direction remains INVALID. */ + *optr = *dptr; + } + } + else{ + /* Otherwise, assign the current direction to the output block. */ + *optr = *dptr; + } + + /* Bump to the next block in the maps ... */ + dptr++; + cptr++; + optr++; + } + } + + /* Copy the interpolated directions into the input map. */ + memcpy(direction_map, omap, mw*mh*sizeof(int)); + /* Deallocate the working memory. */ + free(omap); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: morph_tf_map - Takes a 2D vector of TRUE and FALSE values integers +#cat: and dialates and erodes the map in an attempt to fill +#cat: in voids in the map. + + Input: + tfmap - vector of integer block values + mw - width (in blocks) of the map + mh - height (in blocks) of the map + lfsparms - parameters and thresholds for controlling LFS + Output: + tfmap - resulting morphed map +**************************************************************************/ +int morph_TF_map(int *tfmap, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + unsigned char *cimage, *mimage, *cptr; + int *mptr; + int i; + + + /* Convert TRUE/FALSE map into a binary byte image. */ + cimage = (unsigned char *)malloc(mw*mh); + if(cimage == (unsigned char *)NULL){ + fprintf(stderr, "ERROR : morph_TF_map : malloc : cimage\n"); + return(-660); + } + + mimage = (unsigned char *)malloc(mw*mh); + if(mimage == (unsigned char *)NULL){ + fprintf(stderr, "ERROR : morph_TF_map : malloc : mimage\n"); + return(-661); + } + + cptr = cimage; + mptr = tfmap; + for(i = 0; i < mw*mh; i++){ + *cptr++ = *mptr++; + } + + dilate_charimage_2(cimage, mimage, mw, mh); + dilate_charimage_2(mimage, cimage, mw, mh); + erode_charimage_2(cimage, mimage, mw, mh); + erode_charimage_2(mimage, cimage, mw, mh); + + cptr = cimage; + mptr = tfmap; + for(i = 0; i < mw*mh; i++){ + *mptr++ = *cptr++; + } + + free(cimage); + free(mimage); + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: pixelize_map - Takes a block image map and assigns each pixel in the +#cat: image its corresponding block value. This allows block +#cat: values in maps to be directly accessed via pixel addresses. + + Input: + iw - the width (in pixels) of the corresponding image + ih - the height (in pixels) of the corresponding image + imap - input block image map + mw - the width (in blocks) of the map + mh - the height (in blocks) of the map + blocksize - the dimension (in pixels) of each block + Output: + omap - points to the resulting pixelized map + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int pixelize_map(int **omap, const int iw, const int ih, + int *imap, const int mw, const int mh, const int blocksize) +{ + int *pmap; + int ret, x, y; + int *blkoffs, bw, bh, bi; + int *spptr, *pptr; + + pmap = (int *)malloc(iw*ih*sizeof(int)); + if(pmap == (int *)NULL){ + fprintf(stderr, "ERROR : pixelize_map : malloc : pmap\n"); + return(-590); + } + + if((ret = block_offsets(&blkoffs, &bw, &bh, iw, ih, 0, blocksize))){ + return(ret); + } + + if((bw != mw) || (bh != mh)){ + free(blkoffs); + fprintf(stderr, + "ERROR : pixelize_map : block dimensions do not match\n"); + return(-591); + } + + for(bi = 0; bi < mw*mh; bi++){ + spptr = pmap + blkoffs[bi]; + for(y = 0; y < blocksize; y++){ + pptr = spptr; + for(x = 0; x < blocksize; x++){ + *pptr++ = imap[bi]; + } + spptr += iw; + } + } + + /* Deallocate working memory. */ + free(blkoffs); + /* Assign pixelized map to output pointer. */ + *omap = pmap; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: smooth_direction_map - Takes a vector of integer directions and smooths +#cat: them by analyzing the direction of adjacent neighbors. + + Input: + direction_map - vector of integer block values + mw - width (in blocks) of the map + mh - height (in blocks) of the map + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Output: + imap - vector of smoothed input values +**************************************************************************/ +void smooth_direction_map(int *direction_map, int *low_contrast_map, + const int mw, const int mh, + const DIR2RAD *dir2rad, const LFSPARMS *lfsparms) +{ + int mx, my; + int *dptr, *cptr; + int avrdir, nvalid; + double dir_strength; + + print2log("SMOOTH DIRECTION MAP\n"); + + /* Assign pointers to beginning of both maps. */ + dptr = direction_map; + cptr = low_contrast_map; + + /* Foreach block in maps ... */ + for(my = 0; my < mh; my++){ + for(mx = 0; mx < mw; mx++){ + /* If the current block does NOT have LOW CONTRAST ... */ + if(!*cptr){ + + /* Compute average direction from neighbors, returning the */ + /* number of valid neighbors used in the computation, and */ + /* the "strength" of the average direction. */ + average_8nbr_dir(&avrdir, &dir_strength, &nvalid, + direction_map, mx, my, mw, mh, dir2rad); + + /* If average direction strength is strong enough */ + /* (Ex. thresh==0.2)... */ + if(dir_strength >= lfsparms->dir_strength_min){ + /* If Direction Map direction is valid ... */ + if(*dptr != INVALID_DIR){ + /* Conduct valid neighbor test (Ex. thresh==3)... */ + if(nvalid >= lfsparms->rmv_valid_nbr_min){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, " BLOCK %2d (%2d, %2d)\n", + mx+(my*mw), mx, my); + fprintf(logfp, " Average NBR : %2d %6.3f %d\n", + avrdir, dir_strength, nvalid); + fprintf(logfp, " 1. Valid NBR (%d >= %d)\n", + nvalid, lfsparms->rmv_valid_nbr_min); + fprintf(logfp, " Valid Direction = %d\n", *dptr); + fprintf(logfp, " Smoothed Direction = %d\n", avrdir); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* Reassign valid direction with average direction. */ + *dptr = avrdir; + } + } + /* Otherwise direction is invalid ... */ + else{ + /* Even if DIRECTION_MAP value is invalid, if number of */ + /* valid neighbors is big enough (Ex. thresh==7)... */ + if(nvalid >= lfsparms->smth_valid_nbr_min){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, " BLOCK %2d (%2d, %2d)\n", + mx+(my*mw), mx, my); + fprintf(logfp, " Average NBR : %2d %6.3f %d\n", + avrdir, dir_strength, nvalid); + fprintf(logfp, " 2. Invalid NBR (%d >= %d)\n", + nvalid, lfsparms->smth_valid_nbr_min); + fprintf(logfp, " Invalid Direction = %d\n", *dptr); + fprintf(logfp, " Smoothed Direction = %d\n", avrdir); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* Assign invalid direction with average direction. */ + *dptr = avrdir; + } + } + } + } + /* Otherwise, block has LOW CONTRAST, so keep INVALID direction. */ + + /* Bump to next block in maps. */ + dptr++; + cptr++; + } + } +} + +/************************************************************************* +************************************************************************** +#cat: gen_high_curve_map - Takes a Direction Map and generates a new map +#cat: that flags blocks with HIGH CURVATURE. + + Input: + direction_map - map of blocks containing directional ridge flow + mw - the width (in blocks) of the map + mh - the height (in blocks) of the map + lfsparms - parameters and thresholds for controlling LFS + Output: + ohcmap - points to the created High Curvature Map + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int gen_high_curve_map(int **ohcmap, int *direction_map, + const int mw, const int mh, const LFSPARMS *lfsparms) +{ + int *high_curve_map, mapsize; + int *hptr, *dptr; + int bx, by; + int nvalid, cmeasure, vmeasure; + + mapsize = mw*mh; + + /* Allocate High Curvature Map. */ + high_curve_map = (int *)malloc(mapsize * sizeof(int)); + if(high_curve_map == (int *)NULL){ + fprintf(stderr, + "ERROR: gen_high_curve_map : malloc : high_curve_map\n"); + return(-530); + } + /* Initialize High Curvature Map to FALSE (0). */ + memset(high_curve_map, 0, mapsize*sizeof(int)); + + hptr = high_curve_map; + dptr = direction_map; + + /* Foreach row in maps ... */ + for(by = 0; by < mh; by++){ + /* Foreach column in maps ... */ + for(bx = 0; bx < mw; bx++){ + + /* Count number of valid neighbors around current block ... */ + nvalid = num_valid_8nbrs(direction_map, bx, by, mw, mh); + + /* If valid neighbors exist ... */ + if(nvalid > 0){ + /* If current block's direction is INVALID ... */ + if(*dptr == INVALID_DIR){ + /* If a sufficient number of VALID neighbors exists ... */ + if(nvalid >= lfsparms->vort_valid_nbr_min){ + /* Measure vorticity of neighbors. */ + vmeasure = vorticity(direction_map, bx, by, mw, mh, + lfsparms->num_directions); + /* If vorticity is sufficiently high ... */ + if(vmeasure >= lfsparms->highcurv_vorticity_min) + /* Flag block as HIGH CURVATURE. */ + *hptr = TRUE; + } + } + /* Otherwise block has valid direction ... */ + else{ + /* Measure curvature around the valid block. */ + cmeasure = curvature(direction_map, bx, by, mw, mh, + lfsparms->num_directions); + /* If curvature is sufficiently high ... */ + if(cmeasure >= lfsparms->highcurv_curvature_min) + *hptr = TRUE; + } + } /* Else (nvalid <= 0) */ + + /* Bump pointers to next block in maps. */ + dptr++; + hptr++; + + } /* bx */ + } /* by */ + + /* Assign High Curvature Map to output pointer. */ + *ohcmap = high_curve_map; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: gen_imap - Computes an IMAP, which is a 2D vector of integer directions, +#cat: where each direction represents the dominant ridge flow in +#cat: a block of the input grayscale image. This routine will +#cat: generate an IMAP for arbitrarily sized, non-square, images. + + Input: + pdata - padded input image data (8 bits [0..256) grayscale) + pw - padded width (in pixels) of the input image + ph - padded height (in pixels) of the input image + dir2rad - lookup table for converting integer directions + dftwaves - structure containing the DFT wave forms + dftgrids - structure containing the rotated pixel grid offsets + lfsparms - parameters and thresholds for controlling LFS + Output: + optr - points to the created IMAP + ow - width (in blocks) of the IMAP + oh - height (in blocks) of the IMAP + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int gen_imap(int **optr, int *ow, int *oh, + unsigned char *pdata, const int pw, const int ph, + const DIR2RAD *dir2rad, const DFTWAVES *dftwaves, + const ROTGRIDS *dftgrids, const LFSPARMS *lfsparms) +{ + int *imap, mw, mh, iw, ih; + int *blkoffs; + int ret; /* return code */ + + /* 1. Compute block offsets for the entire image, accounting for pad */ + /* Block_offsets() assumes square block (grid), so ERROR otherwise. */ + if(dftgrids->grid_w != dftgrids->grid_h){ + fprintf(stderr, "ERROR : gen_imap : DFT grids must be square\n"); + return(-60); + } + /* Compute unpadded image dimensions. */ + iw = pw - (dftgrids->pad<<1); + ih = ph - (dftgrids->pad<<1); + if((ret = block_offsets(&blkoffs, &mw, &mh, iw, ih, + dftgrids->pad, dftgrids->grid_w))){ + return(ret); + } + + /* 2. Generate initial imap */ + if((ret = gen_initial_imap(&imap, blkoffs, mw, mh, pdata, pw, ph, + dftwaves, dftgrids, lfsparms))){ + /* Free memory allocated to this point. */ + free(blkoffs); + return(ret); + } + + /* 3. Remove IMAP directions that are inconsistent with neighbors */ + remove_incon_dirs(imap, mw, mh, dir2rad, lfsparms); + + /* 4. Smooth imap values with their neighbors */ + smooth_imap(imap, mw, mh, dir2rad, lfsparms); + + /* Deallocate working memory. */ + free(blkoffs); + + *optr = imap; + *ow = mw; + *oh = mh; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: gen_initial_imap - Creates an initial IMAP from the given input image. +#cat: It very important that the image be properly padded so +#cat: that rotated grids along the boudary of the image do not +#cat: access unkown memory. The rotated grids are used by a +#cat: DFT-based analysis to determine the integer directions +#cat: in the IMAP. Typically this initial vector of directions will +#cat: subsequently have weak or inconsistent directions removed +#cat: followed by a smoothing process. + + Input: + blkoffs - offsets to the pixel origin of each block in the padded image + mw - number of blocks horizontally in the padded input image + mh - number of blocks vertically in the padded input image + pdata - padded input image data (8 bits [0..256) grayscale) + pw - width (in pixels) of the padded input image + ph - height (in pixels) of the padded input image + dftwaves - structure containing the DFT wave forms + dftgrids - structure containing the rotated pixel grid offsets + lfsparms - parameters and thresholds for controlling LFS + Output: + optr - points to the newly created IMAP + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int gen_initial_imap(int **optr, int *blkoffs, const int mw, const int mh, + unsigned char *pdata, const int pw, const int ph, + const DFTWAVES *dftwaves, const ROTGRIDS *dftgrids, + const LFSPARMS *lfsparms) +{ + int *imap; + int bi, bsize, blkdir; + int *wis, *powmax_dirs; + double **powers, *powmaxs, *pownorms; + int nstats; + int ret; /* return code */ + + print2log("INITIAL MAP\n"); + + /* Compute total number of blocks in IMAP */ + bsize = mw * mh; + + /* Allocate IMAP memory */ + imap = (int *)malloc(bsize * sizeof(int)); + if(imap == (int *)NULL){ + fprintf(stderr, "ERROR : gen_initial_imap : malloc : imap\n"); + return(-70); + } + + /* Allocate DFT directional power vectors */ + if((ret = alloc_dir_powers(&powers, dftwaves->nwaves, dftgrids->ngrids))){ + /* Free memory allocated to this point. */ + free(imap); + return(ret); + } + + /* Allocate DFT power statistic arrays */ + /* Compute length of statistics arrays. Statistics not needed */ + /* for the first DFT wave, so the length is number of waves - 1. */ + nstats = dftwaves->nwaves - 1; + if((ret = alloc_power_stats(&wis, &powmaxs, &powmax_dirs, + &pownorms, nstats))){ + /* Free memory allocated to this point. */ + free(imap); + free_dir_powers(powers, dftwaves->nwaves); + return(ret); + } + + /* Initialize the imap to -1 */ + memset(imap, INVALID_DIR, bsize * sizeof(int)); + + /* Foreach block in imap ... */ + for(bi = 0; bi < bsize; bi++){ + + print2log(" BLOCK %2d (%2d, %2d)\n", bi, bi%mw, bi/mw); + + /* Compute DFT powers */ + if((ret = dft_dir_powers(powers, pdata, blkoffs[bi], pw, ph, + dftwaves, dftgrids))){ + /* Free memory allocated to this point. */ + free(imap); + free_dir_powers(powers, dftwaves->nwaves); + free(wis); + free(powmaxs); + free(powmax_dirs); + free(pownorms); + return(ret); + } + + /* Compute DFT power statistics, skipping first applied DFT */ + /* wave. This is dependent on how the primary and secondary */ + /* direction tests work below. */ + if((ret = dft_power_stats(wis, powmaxs, powmax_dirs, pownorms, powers, + 1, dftwaves->nwaves, dftgrids->ngrids))){ + /* Free memory allocated to this point. */ + free(imap); + free_dir_powers(powers, dftwaves->nwaves); + free(wis); + free(powmaxs); + free(powmax_dirs); + free(pownorms); + return(ret); + } + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + { int _w; + fprintf(logfp, " Power\n"); + for(_w = 0; _w < nstats; _w++){ + /* Add 1 to wis[w] to create index to original dft_coefs[] */ + fprintf(logfp, " wis[%d] %d %12.3f %2d %9.3f %12.3f\n", + _w, wis[_w]+1, + powmaxs[wis[_w]], powmax_dirs[wis[_w]], pownorms[wis[_w]], + powers[0][powmax_dirs[wis[_w]]]); + } + } +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* Conduct primary direction test */ + blkdir = primary_dir_test(powers, wis, powmaxs, powmax_dirs, + pownorms, nstats, lfsparms); + + if(blkdir != INVALID_DIR) + imap[bi] = blkdir; + else{ + /* Conduct secondary (fork) direction test */ + blkdir = secondary_fork_test(powers, wis, powmaxs, powmax_dirs, + pownorms, nstats, lfsparms); + if(blkdir != INVALID_DIR) + imap[bi] = blkdir; + } + + /* Otherwise current block direction in IMAP remains INVALID */ + + } /* bi */ + + /* Deallocate working memory */ + free_dir_powers(powers, dftwaves->nwaves); + free(wis); + free(powmaxs); + free(powmax_dirs); + free(pownorms); + + *optr = imap; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: primary_dir_test - Applies the primary set of criteria for selecting +#cat: an IMAP integer direction from a set of DFT results +#cat: computed from a block of image data + + Input: + powers - DFT power computed from each (N) wave frequencies at each + rotation direction in the current image block + wis - sorted order of the highest N-1 frequency power statistics + powmaxs - maximum power for each of the highest N-1 frequencies + powmax_dirs - directions associated with each of the N-1 maximum powers + pownorms - normalized power for each of the highest N-1 frequencies + nstats - N-1 wave frequencies (where N is the length of dft_coefs) + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Zero or Positive - The selected IMAP integer direction + INVALID_DIR - IMAP Integer direction could not be determined +**************************************************************************/ +int primary_dir_test(double **powers, const int *wis, + const double *powmaxs, const int *powmax_dirs, + const double *pownorms, const int nstats, + const LFSPARMS *lfsparms) +{ + int w; + + print2log(" Primary\n"); + + /* Look at max power statistics in decreasing order ... */ + for(w = 0; w < nstats; w++){ + /* 1. Test magnitude of current max power (Ex. Thresh==100000) */ + if((powmaxs[wis[w]] > lfsparms->powmax_min) && + /* 2. Test magnitude of normalized max power (Ex. Thresh==3.8) */ + (pownorms[wis[w]] > lfsparms->pownorm_min) && + /* 3. Test magnitude of power of lowest DFT frequency at current */ + /* max power direction and make sure it is not too big. */ + /* (Ex. Thresh==50000000) */ + (powers[0][powmax_dirs[wis[w]]] <= lfsparms->powmax_max)){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + /* Add 1 to wis[w] to create index to original dft_coefs[] */ + fprintf(logfp, + " Selected Wave = %d\n", wis[w]+1); + fprintf(logfp, + " 1. Power Magnitude (%12.3f > %12.3f)\n", + powmaxs[wis[w]], lfsparms->powmax_min); + fprintf(logfp, + " 2. Norm Power Magnitude (%9.3f > %9.3f)\n", + pownorms[wis[w]], lfsparms->pownorm_min); + fprintf(logfp, + " 3. Low Freq Wave Magnitude (%12.3f <= %12.3f)\n", + powers[0][powmax_dirs[wis[w]]], lfsparms->powmax_max); + fprintf(logfp, + " PASSED\n"); + fprintf(logfp, + " Selected Direction = %d\n", + powmax_dirs[wis[w]]); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* If ALL 3 criteria met, return current max power direction. */ + return(powmax_dirs[wis[w]]); + + } + } + + /* Otherwise test failed. */ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, " 1. Power Magnitude ( > %12.3f)\n", + lfsparms->powmax_min); + fprintf(logfp, " 2. Norm Power Magnitude ( > %9.3f)\n", + lfsparms->pownorm_min); + fprintf(logfp, " 3. Low Freq Wave Magnitude ( <= %12.3f)\n", + lfsparms->powmax_max); + fprintf(logfp, " FAILED\n"); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + return(INVALID_DIR); +} + +/************************************************************************* +************************************************************************** +#cat: secondary_fork_test - Applies a secondary set of criteria for selecting +#cat: an IMAP integer direction from a set of DFT results +#cat: computed from a block of image data. This test +#cat: analyzes the strongest power statistics associated +#cat: with a given frequency and direction and analyses +#cat: small changes in direction to the left and right to +#cat: determine if the block contains a "fork". + + Input: + powers - DFT power computed from each (N) wave frequencies at each + rotation direction in the current image block + wis - sorted order of the highest N-1 frequency power statistics + powmaxs - maximum power for each of the highest N-1 frequencies + powmax_dirs - directions associated with each of the N-1 maximum powers + pownorms - normalized power for each of the highest N-1 frequencies + nstats - N-1 wave frequencies (where N is the length of dft_coefs) + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Zero or Positive - The selected IMAP integer direction + INVALID_DIR - IMAP Integer direction could not be determined +**************************************************************************/ +int secondary_fork_test(double **powers, const int *wis, + const double *powmaxs, const int *powmax_dirs, + const double *pownorms, const int nstats, + const LFSPARMS *lfsparms) +{ + int ldir, rdir; + double fork_pownorm_min, fork_pow_thresh; + +#ifdef LOG_REPORT +{ int firstpart = 0; /* Flag to determine if passed 1st part ... */ + fprintf(logfp, " Secondary\n"); +#endif + + /* Relax the normalized power threshold under fork conditions. */ + fork_pownorm_min = lfsparms->fork_pct_pownorm * lfsparms->pownorm_min; + + /* 1. Test magnitude of largest max power (Ex. Thresh==100000) */ + if((powmaxs[wis[0]] > lfsparms->powmax_min) && + /* 2. Test magnitude of corresponding normalized power */ + /* (Ex. Thresh==2.85) */ + (pownorms[wis[0]] >= fork_pownorm_min) && + /* 3. Test magnitude of power of lowest DFT frequency at largest */ + /* max power direction and make sure it is not too big. */ + /* (Ex. Thresh==50000000) */ + (powers[0][powmax_dirs[wis[0]]] <= lfsparms->powmax_max)){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + /* First part passed ... */ + firstpart = 1; + fprintf(logfp, + " Selected Wave = %d\n", wis[0]+1); + fprintf(logfp, + " 1. Power Magnitude (%12.3f > %12.3f)\n", + powmaxs[wis[0]], lfsparms->powmax_min); + fprintf(logfp, + " 2. Norm Power Magnitude (%9.3f >= %9.3f)\n", + pownorms[wis[0]], fork_pownorm_min); + fprintf(logfp, + " 3. Low Freq Wave Magnitude (%12.3f <= %12.3f)\n", + powers[0][powmax_dirs[wis[0]]], lfsparms->powmax_max); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* Add FORK_INTERVALs to current direction modulo NDIRS */ + rdir = (powmax_dirs[wis[0]] + lfsparms->fork_interval) % + lfsparms->num_directions; + + /* Subtract FORK_INTERVALs from direction modulo NDIRS */ + /* For example, FORK_INTERVAL==2 & NDIRS==16, then */ + /* ldir = (dir - (16-2)) % 16 */ + /* which keeps result in proper modulo range. */ + ldir = (powmax_dirs[wis[0]] + lfsparms->num_directions - + lfsparms->fork_interval) % lfsparms->num_directions; + + print2log(" Left = %d, Current = %d, Right = %d\n", + ldir, powmax_dirs[wis[0]], rdir); + + /* Set forked angle threshold to be a % of the max directional */ + /* power. (Ex. thresh==0.7*powmax) */ + fork_pow_thresh = powmaxs[wis[0]] * lfsparms->fork_pct_powmax; + + /* Look up and test the computed power for the left and right */ + /* fork directions.s */ + /* The power stats (and thus wis) are on the range [0..nwaves-1) */ + /* as the statistics for the first DFT wave are not included. */ + /* The original power vectors exist for ALL DFT waves, therefore */ + /* wis indices must be added by 1 before addressing the original */ + /* powers vector. */ + /* LFS permits one and only one of the fork angles to exceed */ + /* the relative power threshold. */ + if(((powers[wis[0]+1][ldir] <= fork_pow_thresh) || + (powers[wis[0]+1][rdir] <= fork_pow_thresh)) && + ((powers[wis[0]+1][ldir] > fork_pow_thresh) || + (powers[wis[0]+1][rdir] > fork_pow_thresh))){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, + " 4. Left Power Magnitude (%12.3f > %12.3f)\n", + powers[wis[0]+1][ldir], fork_pow_thresh); + fprintf(logfp, + " 5. Right Power Magnitude (%12.3f > %12.3f)\n", + powers[wis[0]+1][rdir], fork_pow_thresh); + fprintf(logfp, " PASSED\n"); + fprintf(logfp, + " Selected Direction = %d\n", powmax_dirs[wis[0]]); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* If ALL the above criteria hold, then return the direction */ + /* of the largest max power. */ + return(powmax_dirs[wis[0]]); + } + } + + /* Otherwise test failed. */ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + if(!firstpart){ + fprintf(logfp, + " 1. Power Magnitude ( > %12.3f)\n", + lfsparms->powmax_min); + fprintf(logfp, + " 2. Norm Power Magnitude ( > %9.3f)\n", + fork_pownorm_min); + fprintf(logfp, + " 3. Low Freq Wave Magnitude ( <= %12.3f)\n", + lfsparms->powmax_max); + } + else{ + fprintf(logfp, " 4. Left Power Magnitude (%12.3f > %12.3f)\n", + powers[wis[0]+1][ldir], fork_pow_thresh); + fprintf(logfp, " 5. Right Power Magnitude (%12.3f > %12.3f)\n", + powers[wis[0]+1][rdir], fork_pow_thresh); + } + fprintf(logfp, " FAILED\n"); +} /* Close scope of firstpart */ +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + return(INVALID_DIR); +} + +/************************************************************************* +************************************************************************** +#cat: remove_incon_dirs - Takes a vector of integer directions and removes +#cat: individual directions that are too weak or inconsistent. +#cat: Directions are tested from the center of the IMAP working +#cat: outward in concentric squares, and the process resets to +#cat: the center and continues until no changes take place during +#cat: a complete pass. + + Input: + imap - vector of IMAP integer directions + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Output: + imap - vector of pruned input values +**************************************************************************/ +void remove_incon_dirs(int *imap, const int mw, const int mh, + const DIR2RAD *dir2rad, const LFSPARMS *lfsparms) +{ + int cx, cy; + int *iptr; + int nremoved; + int lbox, rbox, tbox, bbox; + +#ifdef LOG_REPORT +{ int numpass = 0; + fprintf(logfp, "REMOVE MAP\n"); +#endif + + /* Compute center coords of IMAP */ + cx = mw>>1; + cy = mh>>1; + + /* Do pass, while directions have been removed in a pass ... */ + do{ + +#ifdef LOG_REPORT + /* Count number of complete passes through IMAP */ + ++numpass; + fprintf(logfp, " PASS = %d\n", numpass); +#endif + + /* Reinitialize number of removed directions to 0 */ + nremoved = 0; + + /* Start at center */ + iptr = imap + (cy * mw) + cx; + /* If valid IMAP direction and test for removal is true ... */ + if((*iptr != INVALID_DIR)&& + (remove_dir(imap, cx, cy, mw, mh, dir2rad, lfsparms))){ + + /* Set to INVALID */ + *iptr = INVALID_DIR; + /* Bump number of removed IMAP directions */ + nremoved++; + } + + /* Initialize side indices of concentric boxes */ + lbox = cx-1; + tbox = cy-1; + rbox = cx+1; + bbox = cy+1; + + /* Grow concentric boxes, until ALL edges of imap are exceeded */ + while((lbox >= 0) || (rbox < mw) || (tbox >= 0) || (bbox < mh)){ + + /* test top edge of box */ + if(tbox >= 0) + nremoved += test_top_edge(lbox, tbox, rbox, bbox, imap, mw, mh, + dir2rad, lfsparms); + + /* test right edge of box */ + if(rbox < mw) + nremoved += test_right_edge(lbox, tbox, rbox, bbox, imap, mw, mh, + dir2rad, lfsparms); + + /* test bottom edge of box */ + if(bbox < mh) + nremoved += test_bottom_edge(lbox, tbox, rbox, bbox, imap, mw, mh, + dir2rad, lfsparms); + + /* test left edge of box */ + if(lbox >=0) + nremoved += test_left_edge(lbox, tbox, rbox, bbox, imap, mw, mh, + dir2rad, lfsparms); + + /* Resize current box */ + lbox--; + tbox--; + rbox++; + bbox++; + } + }while(nremoved); + +#ifdef LOG_REPORT +} /* Close scope of numpass */ +#endif + +} + +/************************************************************************* +************************************************************************** +#cat: test_top_edge - Walks the top edge of a concentric square in the IMAP, +#cat: testing directions along the way to see if they should +#cat: be removed due to being too weak or inconsistent with +#cat: respect to their adjacent neighbors. + + Input: + lbox - left edge of current concentric square + tbox - top edge of current concentric square + rbox - right edge of current concentric square + bbox - bottom edge of current concentric square + imap - vector of IMAP integer directions + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Positive - direction should be removed from IMAP + Zero - direction should NOT be remove from IMAP +**************************************************************************/ +int test_top_edge(const int lbox, const int tbox, const int rbox, + const int bbox, int *imap, const int mw, const int mh, + const DIR2RAD *dir2rad, const LFSPARMS *lfsparms) +{ + int bx, by, sx, ex; + int *iptr, *sptr, *eptr; + int nremoved; + + /* Initialize number of directions removed on edge to 0 */ + nremoved = 0; + + /* Set start pointer to top-leftmost point of box, or set it to */ + /* the leftmost point in the IMAP row (0), whichever is larger. */ + sx = max(lbox, 0); + sptr = imap + (tbox*mw) + sx; + + /* Set end pointer to either 1 point short of the top-rightmost */ + /* point of box, or set it to the rightmost point in the IMAP */ + /* row (lastx=mw-1), whichever is smaller. */ + ex = min(rbox-1, mw-1); + eptr = imap + (tbox*mw) + ex; + + /* For each point on box's edge ... */ + for(iptr = sptr, bx = sx, by = tbox; + iptr <= eptr; + iptr++, bx++){ + /* If valid IMAP direction and test for removal is true ... */ + if((*iptr != INVALID_DIR)&& + (remove_dir(imap, bx, by, mw, mh, dir2rad, lfsparms))){ + /* Set to INVALID */ + *iptr = INVALID_DIR; + /* Bump number of removed IMAP directions */ + nremoved++; + } + } + + /* Return the number of directions removed on edge */ + return(nremoved); +} + +/************************************************************************* +************************************************************************** +#cat: test_right_edge - Walks the right edge of a concentric square in the +#cat: IMAP, testing directions along the way to see if they +#cat: should be removed due to being too weak or inconsistent +#cat: with respect to their adjacent neighbors. + + Input: + lbox - left edge of current concentric square + tbox - top edge of current concentric square + rbox - right edge of current concentric square + bbox - bottom edge of current concentric square + imap - vector of IMAP integer directions + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Positive - direction should be removed from IMAP + Zero - direction should NOT be remove from IMAP +**************************************************************************/ +int test_right_edge(const int lbox, const int tbox, const int rbox, + const int bbox, int *imap, const int mw, const int mh, + const DIR2RAD *dir2rad, const LFSPARMS *lfsparms) +{ + int bx, by, sy, ey; + int *iptr, *sptr, *eptr; + int nremoved; + + /* Initialize number of directions removed on edge to 0 */ + nremoved = 0; + + /* Set start pointer to top-rightmost point of box, or set it to */ + /* the topmost point in IMAP column (0), whichever is larger. */ + sy = max(tbox, 0); + sptr = imap + (sy*mw) + rbox; + + /* Set end pointer to either 1 point short of the bottom- */ + /* rightmost point of box, or set it to the bottommost point */ + /* in the IMAP column (lasty=mh-1), whichever is smaller. */ + ey = min(bbox-1,mh-1); + eptr = imap + (ey*mw) + rbox; + + /* For each point on box's edge ... */ + for(iptr = sptr, bx = rbox, by = sy; + iptr <= eptr; + iptr+=mw, by++){ + /* If valid IMAP direction and test for removal is true ... */ + if((*iptr != INVALID_DIR)&& + (remove_dir(imap, bx, by, mw, mh, dir2rad, lfsparms))){ + /* Set to INVALID */ + *iptr = INVALID_DIR; + /* Bump number of removed IMAP directions */ + nremoved++; + } + } + + /* Return the number of directions removed on edge */ + return(nremoved); +} + +/************************************************************************* +************************************************************************** +#cat: test_bottom_edge - Walks the bottom edge of a concentric square in the +#cat: IMAP, testing directions along the way to see if they +#cat: should be removed due to being too weak or inconsistent +#cat: with respect to their adjacent neighbors. + Input: + lbox - left edge of current concentric square + tbox - top edge of current concentric square + rbox - right edge of current concentric square + bbox - bottom edge of current concentric square + imap - vector of IMAP integer directions + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Positive - direction should be removed from IMAP + Zero - direction should NOT be remove from IMAP +**************************************************************************/ +int test_bottom_edge(const int lbox, const int tbox, const int rbox, + const int bbox, int *imap, const int mw, const int mh, + const DIR2RAD *dir2rad, const LFSPARMS *lfsparms) +{ + int bx, by, sx, ex; + int *iptr, *sptr, *eptr; + int nremoved; + + /* Initialize number of directions removed on edge to 0 */ + nremoved = 0; + + /* Set start pointer to bottom-rightmost point of box, or set it to the */ + /* rightmost point in the IMAP ROW (lastx=mw-1), whichever is smaller. */ + sx = min(rbox, mw-1); + sptr = imap + (bbox*mw) + sx; + + /* Set end pointer to either 1 point short of the bottom- */ + /* lefttmost point of box, or set it to the leftmost point */ + /* in the IMAP row (x=0), whichever is larger. */ + ex = max(lbox-1, 0); + eptr = imap + (bbox*mw) + ex; + + /* For each point on box's edge ... */ + for(iptr = sptr, bx = sx, by = bbox; + iptr >= eptr; + iptr--, bx--){ + /* If valid IMAP direction and test for removal is true ... */ + if((*iptr != INVALID_DIR)&& + (remove_dir(imap, bx, by, mw, mh, dir2rad, lfsparms))){ + /* Set to INVALID */ + *iptr = INVALID_DIR; + /* Bump number of removed IMAP directions */ + nremoved++; + } + } + + /* Return the number of directions removed on edge */ + return(nremoved); +} + +/************************************************************************* +************************************************************************** +#cat: test_left_edge - Walks the left edge of a concentric square in the IMAP, +#cat: testing directions along the way to see if they should +#cat: be removed due to being too weak or inconsistent with +#cat: respect to their adjacent neighbors. + + Input: + lbox - left edge of current concentric square + tbox - top edge of current concentric square + rbox - right edge of current concentric square + bbox - bottom edge of current concentric square + imap - vector of IMAP integer directions + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Positive - direction should be removed from IMAP + Zero - direction should NOT be remove from IMAP +**************************************************************************/ +int test_left_edge(const int lbox, const int tbox, const int rbox, + const int bbox, int *imap, const int mw, const int mh, + const DIR2RAD *dir2rad, const LFSPARMS *lfsparms) +{ + int bx, by, sy, ey; + int *iptr, *sptr, *eptr; + int nremoved; + + /* Initialize number of directions removed on edge to 0 */ + nremoved = 0; + + /* Set start pointer to bottom-leftmost point of box, or set it to */ + /* the bottommost point in IMAP column (lasty=mh-1), whichever */ + /* is smaller. */ + sy = min(bbox, mh-1); + sptr = imap + (sy*mw) + lbox; + + /* Set end pointer to either 1 point short of the top-leftmost */ + /* point of box, or set it to the topmost point in the IMAP */ + /* column (y=0), whichever is larger. */ + ey = max(tbox-1, 0); + eptr = imap + (ey*mw) + lbox; + + /* For each point on box's edge ... */ + for(iptr = sptr, bx = lbox, by = sy; + iptr >= eptr; + iptr-=mw, by--){ + /* If valid IMAP direction and test for removal is true ... */ + if((*iptr != INVALID_DIR)&& + (remove_dir(imap, bx, by, mw, mh, dir2rad, lfsparms))){ + /* Set to INVALID */ + *iptr = INVALID_DIR; + /* Bump number of removed IMAP directions */ + nremoved++; + } + } + + /* Return the number of directions removed on edge */ + return(nremoved); +} + +/************************************************************************* +************************************************************************** +#cat: remove_dir - Determines if an IMAP direction should be removed based +#cat: on analyzing its adjacent neighbors + + Input: + imap - vector of IMAP integer directions + mx - IMAP X-coord of the current direction being tested + my - IMPA Y-coord of the current direction being tested + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Positive - direction should be removed from IMAP + Zero - direction should NOT be remove from IMAP +**************************************************************************/ +int remove_dir(int *imap, const int mx, const int my, + const int mw, const int mh, const DIR2RAD *dir2rad, + const LFSPARMS *lfsparms) +{ + int avrdir, nvalid, dist; + double dir_strength; + + /* Compute average direction from neighbors, returning the */ + /* number of valid neighbors used in the computation, and */ + /* the "strength" of the average direction. */ + average_8nbr_dir(&avrdir, &dir_strength, &nvalid, imap, mx, my, mw, mh, + dir2rad); + + /* Conduct valid neighbor test (Ex. thresh==3) */ + if(nvalid < lfsparms->rmv_valid_nbr_min){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, " BLOCK %2d (%2d, %2d)\n", + mx+(my*mw), mx, my); + fprintf(logfp, " Average NBR : %2d %6.3f %d\n", + avrdir, dir_strength, nvalid); + fprintf(logfp, " 1. Valid NBR (%d < %d)\n", + nvalid, lfsparms->rmv_valid_nbr_min); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + return(1); + } + + /* If stregnth of average neighbor direction is large enough to */ + /* put credence in ... (Ex. thresh==0.2) */ + if(dir_strength >= lfsparms->dir_strength_min){ + + /* Conduct direction distance test (Ex. thresh==3) */ + /* Compute minimum absolute distance between current and */ + /* average directions accounting for wrapping from 0 to NDIRS. */ + dist = abs(avrdir - *(imap+(my*mw)+mx)); + dist = min(dist, dir2rad->ndirs-dist); + if(dist > lfsparms->dir_distance_max){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, " BLOCK %2d (%2d, %2d)\n", + mx+(my*mw), mx, my); + fprintf(logfp, " Average NBR : %2d %6.3f %d\n", + avrdir, dir_strength, nvalid); + fprintf(logfp, " 1. Valid NBR (%d < %d)\n", + nvalid, lfsparms->rmv_valid_nbr_min); + fprintf(logfp, " 2. Direction Strength (%6.3f >= %6.3f)\n", + dir_strength, lfsparms->dir_strength_min); + fprintf(logfp, " Current Dir = %d, Average Dir = %d\n", + *(imap+(my*mw)+mx), avrdir); + fprintf(logfp, " 3. Direction Distance (%d > %d)\n", + dist, lfsparms->dir_distance_max); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + return(2); + } + } + + /* Otherwise, the strength of the average direciton is not strong enough */ + /* to put credence in, so leave the current block's directon alone. */ + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: average_8nbr_dir - Given an IMAP direction, computes an average +#cat: direction from its adjacent 8 neighbors returning +#cat: the average direction, its strength, and the +#cat: number of valid direction in the neighborhood. + + Input: + imap - vector of IMAP integer directions + mx - IMAP X-coord of the current direction + my - IMPA Y-coord of the current direction + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + Output: + avrdir - the average direction computed from neighbors + dir_strenght - the strength of the average direction + nvalid - the number of valid directions used to compute the + average +**************************************************************************/ +void average_8nbr_dir(int *avrdir, double *dir_strength, int *nvalid, + int *imap, const int mx, const int my, + const int mw, const int mh, + const DIR2RAD *dir2rad) +{ + int *iptr; + int e,w,n,s; + double cospart, sinpart; + double pi2, pi_factor, theta; + double avr; + + /* Compute neighbor coordinates to current IMAP direction */ + e = mx+1; /* East */ + w = mx-1; /* West */ + n = my-1; /* North */ + s = my+1; /* South */ + + /* Intialize accumulators */ + *nvalid = 0; + cospart = 0.0; + sinpart = 0.0; + + /* 1. Test NW */ + /* If NW point within IMAP boudaries ... */ + if((w >= 0) && (n >= 0)){ + iptr = imap + (n*mw) + w; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* 2. Test N */ + /* If N point within IMAP boudaries ... */ + if(n >= 0){ + iptr = imap + (n*mw) + mx; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* 3. Test NE */ + /* If NE point within IMAP boudaries ... */ + if((e < mw) && (n >= 0)){ + iptr = imap + (n*mw) + e; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* 4. Test E */ + /* If E point within IMAP boudaries ... */ + if(e < mw){ + iptr = imap + (my*mw) + e; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* 5. Test SE */ + /* If SE point within IMAP boudaries ... */ + if((e < mw) && (s < mh)){ + iptr = imap + (s*mw) + e; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* 6. Test S */ + /* If S point within IMAP boudaries ... */ + if(s < mh){ + iptr = imap + (s*mw) + mx; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* 7. Test SW */ + /* If SW point within IMAP boudaries ... */ + if((w >= 0) && (s < mh)){ + iptr = imap + (s*mw) + w; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* 8. Test W */ + /* If W point within IMAP boudaries ... */ + if(w >= 0){ + iptr = imap + (my*mw) + w; + /* If valid direction ... */ + if(*iptr != INVALID_DIR){ + /* Accumulate cosine and sine components of the direction */ + cospart += dir2rad->cos[*iptr]; + sinpart += dir2rad->sin[*iptr]; + /* Bump number of accumulated directions */ + (*nvalid)++; + } + } + + /* If there were no neighbors found with valid direction ... */ + if(*nvalid == 0){ + /* Return INVALID direction. */ + *dir_strength = 0; + *avrdir = INVALID_DIR; + return; + } + + /* Compute averages of accumulated cosine and sine direction components */ + cospart /= (double)(*nvalid); + sinpart /= (double)(*nvalid); + + /* Compute directional strength as hypotenuse (without sqrt) of average */ + /* cosine and sine direction components. Believe this value will be on */ + /* the range of [0 .. 1]. */ + *dir_strength = (cospart * cospart) + (sinpart * sinpart); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when comparing doubles. */ + *dir_strength = trunc_dbl_precision(*dir_strength, TRUNC_SCALE); + + /* If the direction strength is not sufficiently high ... */ + if(*dir_strength < DIR_STRENGTH_MIN){ + /* Return INVALID direction. */ + *dir_strength = 0; + *avrdir = INVALID_DIR; + return; + } + + /* Compute angle (in radians) from Arctan of avarage */ + /* cosine and sine direction components. I think this order */ + /* is necessary because 0 direction is vertical and positive */ + /* direction is clockwise. */ + theta = atan2(sinpart, cospart); + + /* Atan2 returns theta on range [-PI..PI]. Adjust theta so that */ + /* it is on the range [0..2PI]. */ + pi2 = 2*M_PI; + theta += pi2; + theta = fmod(theta, pi2); + + /* Pi_factor sets the period of the trig functions to NDIRS units in x. */ + /* For example, if NDIRS==16, then pi_factor = 2(PI/16) = .3926... */ + /* Dividing theta (in radians) by this factor ((1/pi_factor)==2.546...) */ + /* will produce directions on the range [0..NDIRS]. */ + pi_factor = pi2/(double)dir2rad->ndirs; /* 2(M_PI/ndirs) */ + + /* Round off the direction and return it as an average direction */ + /* for the neighborhood. */ + avr = theta / pi_factor; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + avr = trunc_dbl_precision(avr, TRUNC_SCALE); + *avrdir = sround(avr); + + /* Really do need to map values > NDIRS back onto [0..NDIRS) range. */ + *avrdir %= dir2rad->ndirs; +} + +/************************************************************************* +************************************************************************** +#cat: num_valid_8nbrs - Given a block in an IMAP, counts the number of +#cat: immediate neighbors that have a valid IMAP direction. + + Input: + imap - 2-D vector of directional ridge flows + mx - horizontal coord of current block in IMAP + my - vertical coord of current block in IMAP + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + Return Code: + Non-negative - the number of valid IMAP neighbors +**************************************************************************/ +int num_valid_8nbrs(int *imap, const int mx, const int my, + const int mw, const int mh) +{ + int e_ind, w_ind, n_ind, s_ind; + int nvalid; + + /* Initialize VALID IMAP counter to zero. */ + nvalid = 0; + + /* Compute neighbor coordinates to current IMAP direction */ + e_ind = mx+1; /* East index */ + w_ind = mx-1; /* West index */ + n_ind = my-1; /* North index */ + s_ind = my+1; /* South index */ + + /* 1. Test NW IMAP value. */ + /* If neighbor indices are within IMAP boundaries and it is VALID ... */ + if((w_ind >= 0) && (n_ind >= 0) && (*(imap + (n_ind*mw) + w_ind) >= 0)) + /* Bump VALID counter. */ + nvalid++; + + /* 2. Test N IMAP value. */ + if((n_ind >= 0) && (*(imap + (n_ind*mw) + mx) >= 0)) + nvalid++; + + /* 3. Test NE IMAP value. */ + if((n_ind >= 0) && (e_ind < mw) && (*(imap + (n_ind*mw) + e_ind) >= 0)) + nvalid++; + + /* 4. Test E IMAP value. */ + if((e_ind < mw) && (*(imap + (my*mw) + e_ind) >= 0)) + nvalid++; + + /* 5. Test SE IMAP value. */ + if((e_ind < mw) && (s_ind < mh) && (*(imap + (s_ind*mw) + e_ind) >= 0)) + nvalid++; + + /* 6. Test S IMAP value. */ + if((s_ind < mh) && (*(imap + (s_ind*mw) + mx) >= 0)) + nvalid++; + + /* 7. Test SW IMAP value. */ + if((w_ind >= 0) && (s_ind < mh) && (*(imap + (s_ind*mw) + w_ind) >= 0)) + nvalid++; + + /* 8. Test W IMAP value. */ + if((w_ind >= 0) && (*(imap + (my*mw) + w_ind) >= 0)) + nvalid++; + + /* Return number of neighbors with VALID IMAP values. */ + return(nvalid); +} + +/************************************************************************* +************************************************************************** +#cat: smooth_imap - Takes a vector of integer directions and smooths them +#cat: by analyzing the direction of adjacent neighbors. + + Input: + imap - vector of IMAP integer directions + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + dir2rad - lookup table for converting integer directions + lfsparms - parameters and thresholds for controlling LFS + Output: + imap - vector of smoothed input values +**************************************************************************/ +void smooth_imap(int *imap, const int mw, const int mh, + const DIR2RAD *dir2rad, const LFSPARMS *lfsparms) +{ + int mx, my; + int *iptr; + int avrdir, nvalid; + double dir_strength; + + print2log("SMOOTH MAP\n"); + + iptr = imap; + for(my = 0; my < mh; my++){ + for(mx = 0; mx < mw; mx++){ + /* Compute average direction from neighbors, returning the */ + /* number of valid neighbors used in the computation, and */ + /* the "strength" of the average direction. */ + average_8nbr_dir(&avrdir, &dir_strength, &nvalid, + imap, mx, my, mw, mh, dir2rad); + + /* If average direction strength is strong enough */ + /* (Ex. thresh==0.2)... */ + if(dir_strength >= lfsparms->dir_strength_min){ + /* If IMAP direction is valid ... */ + if(*iptr != INVALID_DIR){ + /* Conduct valid neighbor test (Ex. thresh==3)... */ + if(nvalid >= lfsparms->rmv_valid_nbr_min){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, " BLOCK %2d (%2d, %2d)\n", + mx+(my*mw), mx, my); + fprintf(logfp, " Average NBR : %2d %6.3f %d\n", + avrdir, dir_strength, nvalid); + fprintf(logfp, " 1. Valid NBR (%d >= %d)\n", + nvalid, lfsparms->rmv_valid_nbr_min); + fprintf(logfp, " Valid Direction = %d\n", *iptr); + fprintf(logfp, " Smoothed Direction = %d\n", avrdir); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* Reassign valid IMAP direction with average direction. */ + *iptr = avrdir; + } + } + /* Otherwise IMAP direction is invalid ... */ + else{ + /* Even if IMAP value is invalid, if number of valid */ + /* neighbors is big enough (Ex. thresh==7)... */ + if(nvalid >= lfsparms->smth_valid_nbr_min){ + +#ifdef LOG_REPORT /*vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv*/ + fprintf(logfp, " BLOCK %2d (%2d, %2d)\n", + mx+(my*mw), mx, my); + fprintf(logfp, " Average NBR : %2d %6.3f %d\n", + avrdir, dir_strength, nvalid); + fprintf(logfp, " 2. Invalid NBR (%d >= %d)\n", + nvalid, lfsparms->smth_valid_nbr_min); + fprintf(logfp, " Invalid Direction = %d\n", *iptr); + fprintf(logfp, " Smoothed Direction = %d\n", avrdir); +#endif /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/ + + /* Assign invalid IMAP direction with average direction. */ + *iptr = avrdir; + } + } + } + + /* Bump to next IMAP direction. */ + iptr++; + } + } +} + +/************************************************************************* +************************************************************************** +#cat: gen_nmap - Computes an NMAP from its associated 2D vector of integer +#cat: directions (IMAP). Each value in the NMAP either represents +#cat: a direction of dominant ridge flow in a block of the input +#cat: grayscale image, or it contains a codes describing why such +#cat: a direction was not procuded. +#cat: For example, blocks near areas of high-curvature (such as +#cat: with cores and deltas) will not produce reliable IMAP +#cat: directions. + + Input: + imap - associated input vector of IMAP directions + mw - the width (in blocks) of the IMAP + mh - the height (in blocks) of the IMAP + lfsparms - parameters and thresholds for controlling LFS + Output: + optr - points to the created NMAP + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int gen_nmap(int **optr, int *imap, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int *nmap, mapsize; + int *nptr, *iptr; + int bx, by; + int nvalid, cmeasure, vmeasure; + + mapsize = mw*mh; + nmap = (int *)malloc(mapsize * sizeof(int)); + if(nmap == (int *)NULL){ + fprintf(stderr, "ERROR: gen_nmap : malloc : nmap\n"); + return(-120); + } + + nptr = nmap; + iptr = imap; + /* Foreach row in IMAP ... */ + for(by = 0; by < mh; by++){ + /* Foreach column in IMAP ... */ + for(bx = 0; bx < mw; bx++){ + /* Count number of valid neighbors around current block ... */ + nvalid = num_valid_8nbrs(imap, bx, by, mw, mh); + /* If block has no valid neighbors ... */ + if(nvalid == 0) + /* Set NMAP value to NO VALID NEIGHBORS */ + *nptr = NO_VALID_NBRS; + else{ + /* If current IMAP value is INVALID ... */ + if(*iptr == INVALID_DIR){ + /* If not enough VALID neighbors ... */ + if(nvalid < lfsparms->vort_valid_nbr_min){ + /* Set NMAP value to INVALID */ + *nptr = INVALID_DIR; + } + else{ + /* Otherwise measure vorticity of neighbors. */ + vmeasure = vorticity(imap, bx, by, mw, mh, + lfsparms->num_directions); + /* If vorticity too low ... */ + if(vmeasure < lfsparms->highcurv_vorticity_min) + *nptr = INVALID_DIR; + else + /* Otherwise high-curvature area (Ex. core or delta). */ + *nptr = HIGH_CURVATURE; + } + } + /* Otherwise VALID IMAP value ... */ + else{ + /* Measure curvature around the VALID IMAP block. */ + cmeasure = curvature(imap, bx, by, mw, mh, + lfsparms->num_directions); + /* If curvature is too high ... */ + if(cmeasure >= lfsparms->highcurv_curvature_min) + *nptr = HIGH_CURVATURE; + else + /* Otherwise acceptable amount of curature, so assign */ + /* VALID IMAP value to NMAP. */ + *nptr = *iptr; + } + } /* end else (nvalid > 0) */ + /* BUMP IMAP and NMAP pointers. */ + iptr++; + nptr++; + + } /* bx */ + } /* by */ + + *optr = nmap; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: vorticity - Measures the amount of cummulative curvature incurred +#cat: among the IMAP neighbors of the given block. + + Input: + imap - 2D vector of ridge flow directions + mx - horizontal coord of current IMAP block + my - vertical coord of current IMAP block + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + ndirs - number of possible directions in the IMAP + Return Code: + Non-negative - the measured vorticity among the neighbors +**************************************************************************/ +int vorticity(int *imap, const int mx, const int my, + const int mw, const int mh, const int ndirs) +{ + int e_ind, w_ind, n_ind, s_ind; + int nw_val, n_val, ne_val, e_val, se_val, s_val, sw_val, w_val; + int vmeasure; + + /* Compute neighbor coordinates to current IMAP direction */ + e_ind = mx+1; /* East index */ + w_ind = mx-1; /* West index */ + n_ind = my-1; /* North index */ + s_ind = my+1; /* South index */ + + /* 1. Get NW IMAP value. */ + /* If neighbor indices are within IMAP boundaries ... */ + if((w_ind >= 0) && (n_ind >= 0)) + /* Set neighbor value to IMAP value. */ + nw_val = *(imap + (n_ind*mw) + w_ind); + else + /* Otherwise, set the neighbor value to INVALID. */ + nw_val = INVALID_DIR; + + /* 2. Get N IMAP value. */ + if(n_ind >= 0) + n_val = *(imap + (n_ind*mw) + mx); + else + n_val = INVALID_DIR; + + /* 3. Get NE IMAP value. */ + if((n_ind >= 0) && (e_ind < mw)) + ne_val = *(imap + (n_ind*mw) + e_ind); + else + ne_val = INVALID_DIR; + + /* 4. Get E IMAP value. */ + if(e_ind < mw) + e_val = *(imap + (my*mw) + e_ind); + else + e_val = INVALID_DIR; + + /* 5. Get SE IMAP value. */ + if((e_ind < mw) && (s_ind < mh)) + se_val = *(imap + (s_ind*mw) + e_ind); + else + se_val = INVALID_DIR; + + /* 6. Get S IMAP value. */ + if(s_ind < mh) + s_val = *(imap + (s_ind*mw) + mx); + else + s_val = INVALID_DIR; + + /* 7. Get SW IMAP value. */ + if((w_ind >= 0) && (s_ind < mh)) + sw_val = *(imap + (s_ind*mw) + w_ind); + else + sw_val = INVALID_DIR; + + /* 8. Get W IMAP value. */ + if(w_ind >= 0) + w_val = *(imap + (my*mw) + w_ind); + else + w_val = INVALID_DIR; + + /* Now that we have all IMAP neighbors, accumulate vorticity between */ + /* the neighboring directions. */ + + /* Initialize vorticity accumulator to zero. */ + vmeasure = 0; + + /* 1. NW & N */ + accum_nbr_vorticity(&vmeasure, nw_val, n_val, ndirs); + + /* 2. N & NE */ + accum_nbr_vorticity(&vmeasure, n_val, ne_val, ndirs); + + /* 3. NE & E */ + accum_nbr_vorticity(&vmeasure, ne_val, e_val, ndirs); + + /* 4. E & SE */ + accum_nbr_vorticity(&vmeasure, e_val, se_val, ndirs); + + /* 5. SE & S */ + accum_nbr_vorticity(&vmeasure, se_val, s_val, ndirs); + + /* 6. S & SW */ + accum_nbr_vorticity(&vmeasure, s_val, sw_val, ndirs); + + /* 7. SW & W */ + accum_nbr_vorticity(&vmeasure, sw_val, w_val, ndirs); + + /* 8. W & NW */ + accum_nbr_vorticity(&vmeasure, w_val, nw_val, ndirs); + + /* Return the accumulated vorticity measure. */ + return(vmeasure); +} + +/************************************************************************* +************************************************************************** +#cat: accum_nbor_vorticity - Accumlates the amount of curvature measures +#cat: between neighboring IMAP blocks. + + Input: + dir1 - first neighbor's integer IMAP direction + dir2 - second neighbor's integer IMAP direction + ndirs - number of possible IMAP directions + Output: + vmeasure - accumulated vorticity among neighbors measured so far +**************************************************************************/ +void accum_nbr_vorticity(int *vmeasure, const int dir1, const int dir2, + const int ndirs) +{ + int dist; + + /* Measure difference in direction between a pair of neighboring */ + /* directions. */ + /* If both neighbors are not equal and both are VALID ... */ + if((dir1 != dir2) && (dir1 >= 0)&&(dir2 >= 0)){ + /* Measure the clockwise distance from the first to the second */ + /* directions. */ + dist = dir2 - dir1; + /* If dist is negative, then clockwise distance must wrap around */ + /* the high end of the direction range. For example: */ + /* dir1 = 8 */ + /* dir2 = 3 */ + /* and ndirs = 16 */ + /* 3 - 8 = -5 */ + /* so 16 - 5 = 11 (the clockwise distance from 8 to 3) */ + if(dist < 0) + dist += ndirs; + /* If the change in clockwise direction is larger than 90 degrees as */ + /* in total the total number of directions covers 180 degrees. */ + if(dist > (ndirs>>1)) + /* Decrement the vorticity measure. */ + (*vmeasure)--; + else + /* Otherwise, bump the vorticity measure. */ + (*vmeasure)++; + } + /* Otherwise both directions are either equal or */ + /* one or both directions are INVALID, so ignore. */ +} + +/************************************************************************* +************************************************************************** +#cat: curvature - Measures the largest change in direction between the +#cat: current IMAP direction and its immediate neighbors. + + Input: + imap - 2D vector of ridge flow directions + mx - horizontal coord of current IMAP block + my - vertical coord of current IMAP block + mw - width (in blocks) of the IMAP + mh - height (in blocks) of the IMAP + ndirs - number of possible directions in the IMAP + Return Code: + Non-negative - maximum change in direction found (curvature) + Negative - No valid neighbor found to measure change in direction +**************************************************************************/ +int curvature(int *imap, const int mx, const int my, + const int mw, const int mh, const int ndirs) +{ + int *iptr; + int e_ind, w_ind, n_ind, s_ind; + int nw_val, n_val, ne_val, e_val, se_val, s_val, sw_val, w_val; + int cmeasure, dist; + + /* Compute neighbor coordinates to current IMAP direction */ + e_ind = mx+1; /* East index */ + w_ind = mx-1; /* West index */ + n_ind = my-1; /* North index */ + s_ind = my+1; /* South index */ + + /* 1. Get NW IMAP value. */ + /* If neighbor indices are within IMAP boundaries ... */ + if((w_ind >= 0) && (n_ind >= 0)) + /* Set neighbor value to IMAP value. */ + nw_val = *(imap + (n_ind*mw) + w_ind); + else + /* Otherwise, set the neighbor value to INVALID. */ + nw_val = INVALID_DIR; + + /* 2. Get N IMAP value. */ + if(n_ind >= 0) + n_val = *(imap + (n_ind*mw) + mx); + else + n_val = INVALID_DIR; + + /* 3. Get NE IMAP value. */ + if((n_ind >= 0) && (e_ind < mw)) + ne_val = *(imap + (n_ind*mw) + e_ind); + else + ne_val = INVALID_DIR; + + /* 4. Get E IMAP value. */ + if(e_ind < mw) + e_val = *(imap + (my*mw) + e_ind); + else + e_val = INVALID_DIR; + + /* 5. Get SE IMAP value. */ + if((e_ind < mw) && (s_ind < mh)) + se_val = *(imap + (s_ind*mw) + e_ind); + else + se_val = INVALID_DIR; + + /* 6. Get S IMAP value. */ + if(s_ind < mh) + s_val = *(imap + (s_ind*mw) + mx); + else + s_val = INVALID_DIR; + + /* 7. Get SW IMAP value. */ + if((w_ind >= 0) && (s_ind < mh)) + sw_val = *(imap + (s_ind*mw) + w_ind); + else + sw_val = INVALID_DIR; + + /* 8. Get W IMAP value. */ + if(w_ind >= 0) + w_val = *(imap + (my*mw) + w_ind); + else + w_val = INVALID_DIR; + + /* Now that we have all IMAP neighbors, determine largest change in */ + /* direction from current block to each of its 8 VALID neighbors. */ + + /* Initialize pointer to current IMAP value. */ + iptr = imap + (my*mw) + mx; + + /* Initialize curvature measure to negative as closest_dir_dist() */ + /* always returns -1=INVALID or a positive value. */ + cmeasure = -1; + + /* 1. With NW */ + /* Compute closest distance between neighboring directions. */ + dist = closest_dir_dist(*iptr, nw_val, ndirs); + /* Keep track of maximum. */ + if(dist > cmeasure) + cmeasure = dist; + + /* 2. With N */ + dist = closest_dir_dist(*iptr, n_val, ndirs); + if(dist > cmeasure) + cmeasure = dist; + + /* 3. With NE */ + dist = closest_dir_dist(*iptr, ne_val, ndirs); + if(dist > cmeasure) + cmeasure = dist; + + /* 4. With E */ + dist = closest_dir_dist(*iptr, e_val, ndirs); + if(dist > cmeasure) + cmeasure = dist; + + /* 5. With SE */ + dist = closest_dir_dist(*iptr, se_val, ndirs); + if(dist > cmeasure) + cmeasure = dist; + + /* 6. With S */ + dist = closest_dir_dist(*iptr, s_val, ndirs); + if(dist > cmeasure) + cmeasure = dist; + + /* 7. With SW */ + dist = closest_dir_dist(*iptr, sw_val, ndirs); + if(dist > cmeasure) + cmeasure = dist; + + /* 8. With W */ + dist = closest_dir_dist(*iptr, w_val, ndirs); + if(dist > cmeasure) + cmeasure = dist; + + /* Return maximum difference between current block's IMAP direction */ + /* and the rest of its VALID neighbors. */ + return(cmeasure); +} diff --git a/libfprint/nbis/mindtct/matchpat.c b/libfprint/nbis/mindtct/matchpat.c new file mode 100644 index 0000000..ed4fc7c --- /dev/null +++ b/libfprint/nbis/mindtct/matchpat.c @@ -0,0 +1,271 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: MATCHPAT.C + AUTHOR: Michael D. Garris + DATE: 05/11/1999 + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for matching minutia feature + patterns as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + match_1st_pair() + match_2nd_pair() + match_3rd_pair() + skip_repeated_horizontal_pair() + skip_repeated_vertical_pair() +***********************************************************************/ + +#include +#include + +/************************************************************************* +************************************************************************** +#cat: match_1st_pair - Determines which of the feature_patterns[] have their +#cat: first pixel pair match the specified pixel pair. + + Input: + p1 - first pixel value of pair + p2 - second pixel value of pair + Output: + possible - list of matching feature_patterns[] indices + nposs - number of matches + Return Code: + nposs - number of matches +*************************************************************************/ +int match_1st_pair(unsigned char p1, unsigned char p2, + int *possible, int *nposs) +{ + int i; + + /* Set possibilities to 0 */ + *nposs = 0; + + /* Foreach set of feature pairs ... */ + for(i = 0; i < NFEATURES; i++){ + /* If current scan pair matches first pair for feature ... */ + if((p1==feature_patterns[i].first[0]) && + (p2==feature_patterns[i].first[1])){ + /* Store feature as a possible match. */ + possible[*nposs] = i; + /* Bump number of stored possibilities. */ + (*nposs)++; + } + } + + /* Return number of stored possibilities. */ + return(*nposs); +} + +/************************************************************************* +************************************************************************** +#cat: match_2nd_pair - Determines which of the passed feature_patterns[] have +#cat: their second pixel pair match the specified pixel pair. + + Input: + p1 - first pixel value of pair + p2 - second pixel value of pair + possible - list of potentially-matching feature_patterns[] indices + nposs - number of potential matches + Output: + possible - list of matching feature_patterns[] indices + nposs - number of matches + Return Code: + nposs - number of matches +*************************************************************************/ +int match_2nd_pair(unsigned char p1, unsigned char p2, + int *possible, int *nposs) +{ + int i; + int tnposs; + + /* Store input possibilities. */ + tnposs = *nposs; + /* Reset output possibilities to 0. */ + *nposs = 0; + + /* If current scan pair values are the same ... */ + if(p1 == p2) + /* Simply return because pair can't be a second feature pair. */ + return(*nposs); + + /* Foreach possible match based on first pair ... */ + for(i = 0; i < tnposs; i++){ + /* If current scan pair matches second pair for feature ... */ + if((p1==feature_patterns[possible[i]].second[0]) && + (p2==feature_patterns[possible[i]].second[1])){ + /* Store feature as a possible match. */ + possible[*nposs] = possible[i]; + /* Bump number of stored possibilities. */ + (*nposs)++; + } + } + + /* Return number of stored possibilities. */ + return(*nposs); +} + +/************************************************************************* +************************************************************************** +#cat: match_3rd_pair - Determines which of the passed feature_patterns[] have +#cat: their third pixel pair match the specified pixel pair. + + Input: + p1 - first pixel value of pair + p2 - second pixel value of pair + possible - list of potentially-matching feature_patterns[] indices + nposs - number of potential matches + Output: + possible - list of matching feature_patterns[] indices + nposs - number of matches + Return Code: + nposs - number of matches +*************************************************************************/ +int match_3rd_pair(unsigned char p1, unsigned char p2, + int *possible, int *nposs) +{ + int i; + int tnposs; + + /* Store input possibilities. */ + tnposs = *nposs; + /* Reset output possibilities to 0. */ + *nposs = 0; + + /* Foreach possible match based on first and second pairs ... */ + for(i = 0; i < tnposs; i++){ + /* If current scan pair matches third pair for feature ... */ + if((p1==feature_patterns[possible[i]].third[0]) && + (p2==feature_patterns[possible[i]].third[1])){ + /* Store feature as a possible match. */ + possible[*nposs] = possible[i]; + /* Bump number of stored possibilities. */ + (*nposs)++; + } + } + + /* Return number of stored possibilities. */ + return(*nposs); +} + +/************************************************************************* +************************************************************************** +#cat: skip_repeated_horizontal_pair - Takes the location of two pixel in +#cat: adjacent pixel rows within an image region and skips +#cat: rightward until the either the pixel pair no longer repeats +#cat: itself or the image region is exhausted. + + Input: + cx - current x-coord of starting pixel pair + ex - right edge of the image region + p1ptr - pointer to current top pixel in pair + p2ptr - pointer to current bottom pixel in pair + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + cx - x-coord of where rightward skip terminated + p1ptr - points to top pixel where rightward skip terminated + p2ptr - points to bottom pixel where rightward skip terminated +*************************************************************************/ +void skip_repeated_horizontal_pair(int *cx, const int ex, + unsigned char **p1ptr, unsigned char **p2ptr, + const int iw, const int ih) +{ + int old1, old2; + + /* Store starting pixel pair. */ + old1 = **p1ptr; + old2 = **p2ptr; + + /* Bump horizontally to next pixel pair. */ + (*cx)++; + (*p1ptr)++; + (*p2ptr)++; + + /* While not at right of scan region... */ + while(*cx < ex){ + /* If one or the other pixels in the new pair are different */ + /* from the starting pixel pair... */ + if((**p1ptr != old1) || (**p2ptr != old2)) + /* Done skipping repreated pixel pairs. */ + return; + /* Otherwise, bump horizontally to next pixel pair. */ + (*cx)++; + (*p1ptr)++; + (*p2ptr)++; + } +} + +/************************************************************************* +************************************************************************** +#cat: skip_repeated_vertical_pair - Takes the location of two pixel in +#cat: adjacent pixel columns within an image region and skips +#cat: downward until the either the pixel pair no longer repeats +#cat: itself or the image region is exhausted. + + Input: + cy - current y-coord of starting pixel pair + ey - bottom of the image region + p1ptr - pointer to current left pixel in pair + p2ptr - pointer to current right pixel in pair + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + cy - y-coord of where downward skip terminated + p1ptr - points to left pixel where downward skip terminated + p2ptr - points to right pixel where donward skip terminated +*************************************************************************/ +void skip_repeated_vertical_pair(int *cy, const int ey, + unsigned char **p1ptr, unsigned char **p2ptr, + const int iw, const int ih) +{ + int old1, old2; + + /* Store starting pixel pair. */ + old1 = **p1ptr; + old2 = **p2ptr; + + /* Bump vertically to next pixel pair. */ + (*cy)++; + (*p1ptr)+=iw; + (*p2ptr)+=iw; + + /* While not at bottom of scan region... */ + while(*cy < ey){ + /* If one or the other pixels in the new pair are different */ + /* from the starting pixel pair... */ + if((**p1ptr != old1) || (**p2ptr != old2)) + /* Done skipping repreated pixel pairs. */ + return; + /* Otherwise, bump vertically to next pixel pair. */ + (*cy)++; + (*p1ptr)+=iw; + (*p2ptr)+=iw; + } +} + diff --git a/libfprint/nbis/mindtct/minutia.c b/libfprint/nbis/mindtct/minutia.c new file mode 100644 index 0000000..29f505c --- /dev/null +++ b/libfprint/nbis/mindtct/minutia.c @@ -0,0 +1,3557 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: MINUTIA.C + AUTHOR: Michael D. Garris + DATE: 05/11/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 09/13/2004 + + Contains routines responsible for detecting initial minutia + points as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + alloc_minutiae() + realloc_minutiae() + detect_minutiae() + detect_minutiae_V2() + update_minutiae() + update_minutiae_V2() + sort_minutiae_y_x() + sort_minutiae_x_y() + rm_dup_minutiae() + dump_minutiae() + dump_minutiae_pts() + dump_reliable_minutiae_pts() + create_minutia() + free_minutiae() + free_minutia() + remove_minutia() + join_minutia() + minutia_type() + is_minutia_appearing() + choose_scan_direction() + scan4minutiae() + scan4minutiae_horizontally() + scan4minutiae_horizontally_V2() + scan4minutiae_vertically() + scan4minutiae_vertically_V2() + rescan4minutiae_horizontally() + rescan4minutiae_vertically() + rescan_partial_horizontally() + rescan_partial_vertically() + get_nbr_block_index() + adjust_horizontal_rescan() + adjust_vertical_rescan() + process_horizontal_scan_minutia() + process_horizontal_scan_minutia_V2() + process_vertical_scan_minutia() + process_vertical_scan_minutia_V2() + adjust_high_curvature_minutia() + adjust_high_curvature_minutia_V2() + get_low_curvature_direction() + +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: alloc_minutiae - Allocates and initializes a minutia list based on the +#cat: specified maximum number of minutiae to be detected. + + Input: + max_minutiae - number of minutia to be allocated in list + Output: + ominutiae - points to the allocated minutiae list + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int alloc_minutiae(MINUTIAE **ominutiae, const int max_minutiae) +{ + MINUTIAE *minutiae; + + minutiae = (MINUTIAE *)malloc(sizeof(MINUTIAE)); + if(minutiae == (MINUTIAE *)NULL){ + fprintf(stderr, "ERROR : alloc_minutiae : malloc : minutiae\n"); + exit(-430); + } + minutiae->list = (MINUTIA **)malloc(max_minutiae * sizeof(MINUTIA *)); + if(minutiae->list == (MINUTIA **)NULL){ + fprintf(stderr, "ERROR : alloc_minutiae : malloc : minutiae->list\n"); + exit(-431); + } + + minutiae->alloc = max_minutiae; + minutiae->num = 0; + + *ominutiae = minutiae; + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: realloc_minutiae - Reallocates a previously allocated minutia list +#cat: extending its allocated length based on the specified +#cat: increment. + + Input: + minutiae - previously allocated list of minutiae points + max_minutiae - number of minutia to be allocated in list + Output: + minutiae - extended list of minutiae points + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int realloc_minutiae(MINUTIAE *minutiae, const int incr_minutiae) +{ + minutiae->alloc += incr_minutiae; + minutiae->list = (MINUTIA **)realloc(minutiae->list, + minutiae->alloc * sizeof(MINUTIA *)); + if(minutiae->list == (MINUTIA **)NULL){ + fprintf(stderr, "ERROR : realloc_minutiae : realloc : minutiae->list\n"); + exit(-432); + } + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: detect_minutiae - Takes a binary image and its associated IMAP and +#cat: NMAP matrices and scans each image block for potential +#cat: minutia points. + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imap - matrix of ridge flow directions + nmap - IMAP augmented with blocks of HIGH-CURVATURE and + blocks which have no neighboring valid directions. + mw - width (in blocks) of IMAP and NMAP matrices. + mh - height (in blocks) of IMAP and NMAP matrices. + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int detect_minutiae(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int *imap, const int *nmap, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int blk_i, blk_x, blk_y; + int scan_x, scan_y, scan_w, scan_h; + int scan_dir; + int ret; + + /* Start with first block in IMAP. */ + blk_i = 0; + + /* Start with first scan line in image. */ + scan_y = 0; + + /* Foreach row of blocks in IMAP... */ + for(blk_y = 0; blk_y < mh; blk_y++){ + /* Reset to beginning of new block row. */ + scan_x = 0; + /* Foreach block in current IMAP row... */ + for(blk_x = 0; blk_x < mw; blk_x++){ + + /* If IMAP is VALID ... */ + if(imap[blk_i] != INVALID_DIR){ + /* Choose the feature scan direction based on the block's */ + /* VALID IMAP direction. The scan direction will either */ + /* be HORIZONTAL or VERTICAL. */ + scan_dir = choose_scan_direction(imap[blk_i], + lfsparms->num_directions); + /* Set width of scan region. The image may not be an even */ + /* multiple of "blocksize" in width and height, so we must */ + /* account for this. */ + /* Bump right by "blocksize" pixels, but not beyond the */ + /* image boundary. */ + scan_w = min(scan_x+lfsparms->blocksize, iw); + /* Make the resulting width relative to the region's starting */ + /* x-pixel column. */ + scan_w -= scan_x; + /* Bump down by "blocksize" pixels, but not beyond the */ + /* image boundary. */ + scan_h = min(scan_y+lfsparms->blocksize, ih); + /* Make the resulting height relative to the region's starting */ + /* y-pixel row. */ + scan_h -= scan_y; + /* Scan the defined region for minutia features. */ + if((ret = scan4minutiae(minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, scan_dir, + lfsparms))){ + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + } + + } /* Otherwise, IMAP is INVALID, so ignore the block. This seems */ + /* quite drastic! */ + + /* Advance to the next IMAP block in the row in the image. */ + scan_x += lfsparms->blocksize; + /* Advance to the next IMAP block in the row. */ + blk_i++; + } /* End foreach blk_x */ + /* Advance to the next IMAP row in the image. */ + scan_y += lfsparms->blocksize; + } /* End foreach blk_y */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: detect_minutiae_V2 - Takes a binary image and its associated +#cat: Direction and Low Flow Maps and scans each image block +#cat: with valid direction for minutia points. Minutia points +#cat: detected in LOW FLOW blocks are set with lower reliability. + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + direction_map - map of image blocks containing directional ridge flow + low_flow_map - map of image blocks flagged as LOW RIDGE FLOW + high_curve_map - map of image blocks flagged as HIGH CURVATURE + mw - width (in blocks) of the maps + mh - height (in blocks) of the maps + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int detect_minutiae_V2(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *direction_map, int *low_flow_map, int *high_curve_map, + const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int ret; + int *pdirection_map, *plow_flow_map, *phigh_curve_map; + + /* Pixelize the maps by assigning block values to individual pixels. */ + if((ret = pixelize_map(&pdirection_map, iw, ih, direction_map, mw, mh, + lfsparms->blocksize))){ + return(ret); + } + + if((ret = pixelize_map(&plow_flow_map, iw, ih, low_flow_map, mw, mh, + lfsparms->blocksize))){ + free(pdirection_map); + return(ret); + } + + if((ret = pixelize_map(&phigh_curve_map, iw, ih, high_curve_map, mw, mh, + lfsparms->blocksize))){ + free(pdirection_map); + free(plow_flow_map); + return(ret); + } + + if((ret = scan4minutiae_horizontally_V2(minutiae, bdata, iw, ih, + pdirection_map, plow_flow_map, phigh_curve_map, lfsparms))){ + free(pdirection_map); + free(plow_flow_map); + free(phigh_curve_map); + return(ret); + } + + if((ret = scan4minutiae_vertically_V2(minutiae, bdata, iw, ih, + pdirection_map, plow_flow_map, phigh_curve_map, lfsparms))){ + free(pdirection_map); + free(plow_flow_map); + free(phigh_curve_map); + return(ret); + } + + /* Deallocate working memories. */ + free(pdirection_map); + free(plow_flow_map); + free(phigh_curve_map); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: update_minutiae - Takes a detected minutia point and (if it is not +#cat: determined to already be in the minutiae list) adds it to +#cat: the list. + + Input: + minutia - minutia structure for detected point + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - minutia added to successfully added to minutiae list + IGNORE - minutia is to be ignored (already in the minutiae list) + Negative - system error +**************************************************************************/ +int update_minutiae(MINUTIAE *minutiae, MINUTIA *minutia, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int i, ret, dy, dx, delta_dir; + int qtr_ndirs, full_ndirs; + + /* Check to see if minutiae list is full ... if so, then extend */ + /* the length of the allocated list of minutia points. */ + if(minutiae->num >= minutiae->alloc){ + if((ret = realloc_minutiae(minutiae, MAX_MINUTIAE))) + return(ret); + } + + /* Otherwise, there is still room for more minutia. */ + + /* Compute quarter of possible directions in a semi-circle */ + /* (ie. 45 degrees). */ + qtr_ndirs = lfsparms->num_directions>>2; + + /* Compute number of directions in full circle. */ + full_ndirs = lfsparms->num_directions<<1; + + /* Is the minutiae list empty? */ + if(minutiae->num > 0){ + /* Foreach minutia stored in the list... */ + for(i = 0; i < minutiae->num; i++){ + /* If x distance between new minutia and current list minutia */ + /* are sufficiently close... */ + dx = abs(minutiae->list[i]->x - minutia->x); + if(dx < lfsparms->max_minutia_delta){ + /* If y distance between new minutia and current list minutia */ + /* are sufficiently close... */ + dy = abs(minutiae->list[i]->y - minutia->y); + if(dy < lfsparms->max_minutia_delta){ + /* If new minutia and current list minutia are same type... */ + if(minutiae->list[i]->type == minutia->type){ + /* Test to see if minutiae have similar directions. */ + /* Take minimum of computed inner and outer */ + /* direction differences. */ + delta_dir = abs(minutiae->list[i]->direction - + minutia->direction); + delta_dir = min(delta_dir, full_ndirs-delta_dir); + /* If directional difference is <= 45 degrees... */ + if(delta_dir <= qtr_ndirs){ + /* If new minutia and current list minutia share */ + /* the same point... */ + if((dx==0) && (dy==0)){ + /* Then the minutiae match, so don't add the new one */ + /* to the list. */ + return(IGNORE); + } + /* Othewise, check if they share the same contour. */ + /* Start by searching "max_minutia_delta" steps */ + /* clockwise. */ + /* If new minutia point found on contour... */ + if(search_contour(minutia->x, minutia->y, + lfsparms->max_minutia_delta, + minutiae->list[i]->x, minutiae->list[i]->y, + minutiae->list[i]->ex, minutiae->list[i]->ey, + SCAN_CLOCKWISE, bdata, iw, ih)){ + /* Consider the new minutia to be the same as the */ + /* current list minutia, so don't add the new one */ + /* to the list. */ + return(IGNORE); + } + /* Now search "max_minutia_delta" steps counter- */ + /* clockwise along contour. */ + /* If new minutia point found on contour... */ + if(search_contour(minutia->x, minutia->y, + lfsparms->max_minutia_delta, + minutiae->list[i]->x, minutiae->list[i]->y, + minutiae->list[i]->ex, minutiae->list[i]->ey, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih)){ + /* Consider the new minutia to be the same as the */ + /* current list minutia, so don't add the new one */ + /* to the list. */ + return(IGNORE); + } + + /* Otherwise, new minutia and current list minutia do */ + /* not share the same contour, so although they are */ + /* similar in type and location, treat them as 2 */ + /* different minutia. */ + + } /* Otherwise, directions are too different. */ + } /* Otherwise, minutiae are different type. */ + } /* Otherwise, minutiae too far apart in Y. */ + } /* Otherwise, minutiae too far apart in X. */ + } /* End FOR minutia in list. */ + } /* Otherwise, minutiae list is empty. */ + + /* Otherwise, assume new minutia is not in the list, so add it. */ + minutiae->list[minutiae->num] = minutia; + (minutiae->num)++; + + /* New minutia was successfully added to the list. */ + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: update_minutiae_V2 - Takes a detected minutia point and (if it is not +#cat: determined to already be in the minutiae list or the +#cat: new point is determined to be "more compatible") adds +#cat: it to the list. + + Input: + minutia - minutia structure for detected point + scan_dir - orientation of scan when minutia was detected + dmapval - directional ridge flow of block minutia is in + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - minutia added to successfully added to minutiae list + IGNORE - minutia is to be ignored (already in the minutiae list) + Negative - system error +**************************************************************************/ +int update_minutiae_V2(MINUTIAE *minutiae, MINUTIA *minutia, + const int scan_dir, const int dmapval, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int i, ret, dy, dx, delta_dir; + int qtr_ndirs, full_ndirs; + int map_scan_dir; + + /* Check to see if minutiae list is full ... if so, then extend */ + /* the length of the allocated list of minutia points. */ + if(minutiae->num >= minutiae->alloc){ + if((ret = realloc_minutiae(minutiae, MAX_MINUTIAE))) + return(ret); + } + + /* Otherwise, there is still room for more minutia. */ + + /* Compute quarter of possible directions in a semi-circle */ + /* (ie. 45 degrees). */ + qtr_ndirs = lfsparms->num_directions>>2; + + /* Compute number of directions in full circle. */ + full_ndirs = lfsparms->num_directions<<1; + + /* Is the minutiae list empty? */ + if(minutiae->num > 0){ + /* Foreach minutia stored in the list (in reverse order) ... */ + for(i = minutiae->num-1; i >= 0; i--){ + /* If x distance between new minutia and current list minutia */ + /* are sufficiently close... */ + dx = abs(minutiae->list[i]->x - minutia->x); + if(dx < lfsparms->max_minutia_delta){ + /* If y distance between new minutia and current list minutia */ + /* are sufficiently close... */ + dy = abs(minutiae->list[i]->y - minutia->y); + if(dy < lfsparms->max_minutia_delta){ + /* If new minutia and current list minutia are same type... */ + if(minutiae->list[i]->type == minutia->type){ + /* Test to see if minutiae have similar directions. */ + /* Take minimum of computed inner and outer */ + /* direction differences. */ + delta_dir = abs(minutiae->list[i]->direction - + minutia->direction); + delta_dir = min(delta_dir, full_ndirs-delta_dir); + /* If directional difference is <= 45 degrees... */ + if(delta_dir <= qtr_ndirs){ + /* If new minutia and current list minutia share */ + /* the same point... */ + if((dx==0) && (dy==0)){ + /* Then the minutiae match, so don't add the new one */ + /* to the list. */ + return(IGNORE); + } + /* Othewise, check if they share the same contour. */ + /* Start by searching "max_minutia_delta" steps */ + /* clockwise. */ + /* If new minutia point found on contour... */ + if(search_contour(minutia->x, minutia->y, + lfsparms->max_minutia_delta, + minutiae->list[i]->x, minutiae->list[i]->y, + minutiae->list[i]->ex, minutiae->list[i]->ey, + SCAN_CLOCKWISE, bdata, iw, ih) || + search_contour(minutia->x, minutia->y, + lfsparms->max_minutia_delta, + minutiae->list[i]->x, minutiae->list[i]->y, + minutiae->list[i]->ex, minutiae->list[i]->ey, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih)){ + /* If new minutia has VALID block direction ... */ + if(dmapval >= 0){ + /* Derive feature scan direction compatible */ + /* with VALID direction. */ + map_scan_dir = choose_scan_direction(dmapval, + lfsparms->num_directions); + /* If map scan direction compatible with scan */ + /* direction in which new minutia was found ... */ + if(map_scan_dir == scan_dir){ + /* Then choose the new minutia over the one */ + /* currently in the list. */ + if((ret = remove_minutia(i, minutiae))){ + return(ret); + } + /* Continue on ... */ + } + else + /* Othersize, scan directions not compatible...*/ + /* so choose to keep the current minutia in */ + /* the list and ignore the new one. */ + return(IGNORE); + } + else{ + /* Otherwise, no reason to believe new minutia */ + /* is any better than the current one in the list,*/ + /* so consider the new minutia to be the same as */ + /* the current list minutia, and don't add the new*/ + /* one to the list. */ + return(IGNORE); + } + } + + /* Otherwise, new minutia and current list minutia do */ + /* not share the same contour, so although they are */ + /* similar in type and location, treat them as 2 */ + /* different minutia. */ + + } /* Otherwise, directions are too different. */ + } /* Otherwise, minutiae are different type. */ + } /* Otherwise, minutiae too far apart in Y. */ + } /* Otherwise, minutiae too far apart in X. */ + } /* End FOR minutia in list. */ + } /* Otherwise, minutiae list is empty. */ + + /* Otherwise, assume new minutia is not in the list, or those that */ + /* were close neighbors were selectively removed, so add it. */ + minutiae->list[minutiae->num] = minutia; + (minutiae->num)++; + + /* New minutia was successfully added to the list. */ + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: sort_minutiae_y_x - Takes a list of minutia points and sorts them +#cat: top-to-bottom and then left-to-right. + + Input: + minutiae - list of minutiae + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + minutiae - list of sorted minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int sort_minutiae_y_x(MINUTIAE *minutiae, const int iw, const int ih) +{ + int *ranks, *order; + int i, ret; + MINUTIA **newlist; + + /* Allocate a list of integers to hold 1-D image pixel offsets */ + /* for each of the 2-D minutia coordinate points. */ + ranks = (int *)malloc(minutiae->num * sizeof(int)); + if(ranks == (int *)NULL){ + fprintf(stderr, "ERROR : sort_minutiae_y_x : malloc : ranks\n"); + return(-310); + } + + /* Compute 1-D image pixel offsets form 2-D minutia coordinate points. */ + for(i = 0; i < minutiae->num; i++) + ranks[i] = (minutiae->list[i]->y * iw) + minutiae->list[i]->x; + + /* Get sorted order of minutiae. */ + if((ret = sort_indices_int_inc(&order, ranks, minutiae->num))){ + free(ranks); + return(ret); + } + + /* Allocate new MINUTIA list to hold sorted minutiae. */ + newlist = (MINUTIA **)malloc(minutiae->num * sizeof(MINUTIA *)); + if(newlist == (MINUTIA **)NULL){ + free(ranks); + free(order); + fprintf(stderr, "ERROR : sort_minutiae_y_x : malloc : newlist\n"); + return(-311); + } + + /* Put minutia into sorted order in new list. */ + for(i = 0; i < minutiae->num; i++) + newlist[i] = minutiae->list[order[i]]; + + /* Deallocate non-sorted list of minutia pointers. */ + free(minutiae->list); + /* Assign new sorted list of minutia to minutiae list. */ + minutiae->list = newlist; + + /* Free the working memories supporting the sort. */ + free(order); + free(ranks); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: sort_minutiae_x_y - Takes a list of minutia points and sorts them +#cat: left-to-right and then top-to-bottom. + + Input: + minutiae - list of minutiae + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + minutiae - list of sorted minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int sort_minutiae_x_y(MINUTIAE *minutiae, const int iw, const int ih) +{ + int *ranks, *order; + int i, ret; + MINUTIA **newlist; + + /* Allocate a list of integers to hold 1-D image pixel offsets */ + /* for each of the 2-D minutia coordinate points. */ + ranks = (int *)malloc(minutiae->num * sizeof(int)); + if(ranks == (int *)NULL){ + fprintf(stderr, "ERROR : sort_minutiae_x_y : malloc : ranks\n"); + return(-440); + } + + /* Compute 1-D image pixel offsets form 2-D minutia coordinate points. */ + for(i = 0; i < minutiae->num; i++) + ranks[i] = (minutiae->list[i]->x * iw) + minutiae->list[i]->y; + + /* Get sorted order of minutiae. */ + if((ret = sort_indices_int_inc(&order, ranks, minutiae->num))){ + free(ranks); + return(ret); + } + + /* Allocate new MINUTIA list to hold sorted minutiae. */ + newlist = (MINUTIA **)malloc(minutiae->num * sizeof(MINUTIA *)); + if(newlist == (MINUTIA **)NULL){ + free(ranks); + free(order); + fprintf(stderr, "ERROR : sort_minutiae_x_y : malloc : newlist\n"); + return(-441); + } + + /* Put minutia into sorted order in new list. */ + for(i = 0; i < minutiae->num; i++) + newlist[i] = minutiae->list[order[i]]; + + /* Deallocate non-sorted list of minutia pointers. */ + free(minutiae->list); + /* Assign new sorted list of minutia to minutiae list. */ + minutiae->list = newlist; + + /* Free the working memories supporting the sort. */ + free(order); + free(ranks); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: rm_dup_minutiae - Takes a list of minutiae sorted in some adjacent order +#cat: and detects and removes redundant minutia that have the +#cat: same exact pixel coordinate locations (even if other +#cat: attributes may differ). + + Input: + mintuiae - list of sorted minutiae + Output: + mintuiae - list of sorted minutiae with duplicates removed + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int rm_dup_minutiae(MINUTIAE *minutiae) +{ + int i, ret; + MINUTIA *minutia1, *minutia2; + + /* Work backward from the end of the list of minutiae. This way */ + /* we can selectively remove minutia from the list and not cause */ + /* problems with keeping track of current indices. */ + for(i = minutiae->num-1; i > 0; i--){ + minutia1 = minutiae->list[i]; + minutia2 = minutiae->list[i-1]; + /* If minutia pair has identical coordinates ... */ + if((minutia1->x == minutia2->x) && + (minutia1->y == minutia2->y)){ + /* Remove the 2nd minutia from the minutiae list. */ + if((ret = remove_minutia(i-1, minutiae))) + return(ret); + /* The first minutia slides into the position of the 2nd. */ + } + } + + /* Return successfully. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: dump_minutiae - Given a minutiae list, writes a formatted text report of +#cat: the list's contents to the specified open file pointer. + + Input: + minutiae - list of minutia structures + Output: + fpout - open file pointer +**************************************************************************/ +void dump_minutiae(FILE *fpout, const MINUTIAE *minutiae) +{ + int i, j; + + fprintf(fpout, "\n%d Minutiae Detected\n\n", minutiae->num); + + for(i = 0; i < minutiae->num; i++){ + /* Precision of reliablity added one decimal position */ + /* on 09-13-04 */ + fprintf(fpout, "%4d : %4d, %4d : %2d : %6.3f :", i, + minutiae->list[i]->x, minutiae->list[i]->y, + minutiae->list[i]->direction, minutiae->list[i]->reliability); + if(minutiae->list[i]->type == RIDGE_ENDING) + fprintf(fpout, "RIG : "); + else + fprintf(fpout, "BIF : "); + + if(minutiae->list[i]->appearing) + fprintf(fpout, "APP : "); + else + fprintf(fpout, "DIS : "); + + fprintf(fpout, "%2d ", minutiae->list[i]->feature_id); + + for(j = 0; j < minutiae->list[i]->num_nbrs; j++){ + fprintf(fpout, ": %4d,%4d; %2d ", + minutiae->list[minutiae->list[i]->nbrs[j]]->x, + minutiae->list[minutiae->list[i]->nbrs[j]]->y, + minutiae->list[i]->ridge_counts[j]); + } + + fprintf(fpout, "\n"); + } +} + +/************************************************************************* +************************************************************************** +#cat: dump_minutiae_pts - Given a minutiae list, writes the coordinate point +#cat: for each minutia in the list to the specified open +#cat: file pointer. + + Input: + minutiae - list of minutia structures + Output: + fpout - open file pointer +**************************************************************************/ +void dump_minutiae_pts(FILE *fpout, const MINUTIAE *minutiae) +{ + int i; + + /* First line in the output file contians the number of minutia */ + /* points to be written to the file. */ + fprintf(fpout, "%d\n", minutiae->num); + + /* Foreach minutia in list... */ + for(i = 0; i < minutiae->num; i++){ + /* Write the minutia's coordinate point to the file pointer. */ + fprintf(fpout, "%4d %4d\n", minutiae->list[i]->x, minutiae->list[i]->y); + } +} + + +/************************************************************************* +************************************************************************** +#cat: dump_reliable_minutiae_pts - Given a minutiae list, writes the +#cat: coordinate point for each minutia in the list that has +#cat: the specified reliability to the specified open +#cat: file pointer. + + Input: + minutiae - list of minutia structures + reliability - desired reliability level for minutiae to be reported + Output: + fpout - open file pointer +**************************************************************************/ +void dump_reliable_minutiae_pts(FILE *fpout, const MINUTIAE *minutiae, + const double reliability) +{ + int i, count; + + /* First count the number of qualifying minutiae so that the */ + /* MFS header may be written. */ + count = 0; + /* Foreach minutia in list... */ + for(i = 0; i < minutiae->num; i++){ + if(minutiae->list[i]->reliability == reliability) + count++; + } + + /* First line in the output file contians the number of minutia */ + /* points to be written to the file. */ + fprintf(fpout, "%d\n", count); + + /* Foreach minutia in list... */ + for(i = 0; i < minutiae->num; i++){ + if(minutiae->list[i]->reliability == reliability) + /* Write the minutia's coordinate point to the file pointer. */ + fprintf(fpout, "%4d %4d\n", + minutiae->list[i]->x, minutiae->list[i]->y); + } +} + +/************************************************************************* +************************************************************************** +#cat: create_minutia - Takes attributes associated with a detected minutia +#cat: point and allocates and initializes a minutia structure. + + Input: + x_loc - x-pixel coord of minutia (interior to feature) + y_loc - y-pixel coord of minutia (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel (exterior to feature) + idir - integer direction of the minutia + reliability - floating point measure of minutia's reliability + type - type of the minutia (ridge-ending or bifurcation) + appearing - designates the minutia as appearing or disappearing + feature_id - index of minutia's matching feature_patterns[] + Output: + ominutia - ponter to an allocated and initialized minutia structure + Return Code: + Zero - minutia structure successfully allocated and initialized + Negative - system error +*************************************************************************/ +int create_minutia(MINUTIA **ominutia, const int x_loc, const int y_loc, + const int x_edge, const int y_edge, const int idir, + const double reliability, + const int type, const int appearing, const int feature_id) +{ + MINUTIA *minutia; + + /* Allocate a minutia structure. */ + minutia = (MINUTIA *)malloc(sizeof(MINUTIA)); + /* If allocation error... */ + if(minutia == (MINUTIA *)NULL){ + fprintf(stderr, "ERROR : create_minutia : malloc : minutia\n"); + return(-230); + } + + /* Assign minutia structure attributes. */ + minutia->x = x_loc; + minutia->y = y_loc; + minutia->ex = x_edge; + minutia->ey = y_edge; + minutia->direction = idir; + minutia->reliability = reliability; + minutia->type = type; + minutia->appearing = appearing; + minutia->feature_id = feature_id; + minutia->nbrs = (int *)NULL; + minutia->ridge_counts = (int *)NULL; + minutia->num_nbrs = 0; + + /* Set minutia object to output pointer. */ + *ominutia = minutia; + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: free_minutiae - Takes a minutiae list and deallocates all memory +#cat: associated with it. + + Input: + minutiae - pointer to allocated list of minutia structures +*************************************************************************/ +void free_minutiae(MINUTIAE *minutiae) +{ + int i; + + /* Deallocate minutia structures in the list. */ + for(i = 0; i < minutiae->num; i++) + free_minutia(minutiae->list[i]); + /* Deallocate list of minutia pointers. */ + free(minutiae->list); + + /* Deallocate the list structure. */ + free(minutiae); +} + +/************************************************************************* +************************************************************************** +#cat: free_minutia - Takes a minutia pointer and deallocates all memory +#cat: associated with it. + + Input: + minutia - pointer to allocated minutia structure +*************************************************************************/ +void free_minutia(MINUTIA *minutia) +{ + /* Deallocate sublists. */ + if(minutia->nbrs != (int *)NULL) + free(minutia->nbrs); + if(minutia->ridge_counts != (int *)NULL) + free(minutia->ridge_counts); + + /* Deallocate the minutia structure. */ + free(minutia); +} + +/************************************************************************* +************************************************************************** +#cat: remove_minutia - Removes the specified minutia point from the input +#cat: list of minutiae. + + Input: + index - position of minutia to be removed from list + minutiae - input list of minutiae + Output: + minutiae - list with minutia removed + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_minutia(const int index, MINUTIAE *minutiae) +{ + int fr, to; + + /* Make sure the requested index is within range. */ + if((index < 0) && (index >= minutiae->num)){ + fprintf(stderr, "ERROR : remove_minutia : index out of range\n"); + return(-380); + } + + /* Deallocate the minutia structure to be removed. */ + free_minutia(minutiae->list[index]); + + /* Slide the remaining list of minutiae up over top of the */ + /* position of the minutia being removed. */ + for(to = index, fr = index+1; fr < minutiae->num; to++, fr++) + minutiae->list[to] = minutiae->list[fr]; + + /* Decrement the number of minutiae remaining in the list. */ + minutiae->num--; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: join_minutia - Takes 2 minutia points and connectes their features in +#cat: the input binary image. A line is drawn in the image +#cat: between the 2 minutia with a specified line-width radius +#cat: and a conditional border of pixels opposite in color +#cat: from the interior line. + + Input: + minutia1 - first minutia point to be joined + minutia2 - second minutia point to be joined + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + with_boundary - signifies the inclusion of border pixels + line_radius - line-width radius of join line + Output: + bdata - edited image with minutia features joined + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int join_minutia(const MINUTIA *minutia1, const MINUTIA *minutia2, + unsigned char *bdata, const int iw, const int ih, + const int with_boundary, const int line_radius) +{ + int dx_gte_dy, delta_x, delta_y; + int *x_list, *y_list, num; + int minutia_pix, boundary_pix; + int i, j, ret; + int x1, y1, x2, y2; + + /* Compute X and Y deltas between minutia points. */ + delta_x = abs(minutia1->x - minutia2->x); + delta_y = abs(minutia1->y - minutia2->y); + + /* Set flag based on |DX| >= |DY|. */ + /* If flag is true then add additional pixel width to the join line */ + /* by adding pixels neighboring top and bottom. */ + /* If flag is false then add additional pixel width to the join line */ + /* by adding pixels neighboring left and right. */ + if(delta_x >= delta_y) + dx_gte_dy = 1; + else + dx_gte_dy = 0; + + /* Compute points along line segment between the two minutia points. */ + if((ret = line_points(&x_list, &y_list, &num, + minutia1->x, minutia1->y, minutia2->x, minutia2->y))) + /* If error with line routine, return error code. */ + return(ret); + + /* Determine pixel color of minutia and boundary. */ + if(minutia1->type == RIDGE_ENDING){ + /* To connect 2 ridge-endings, draw black. */ + minutia_pix = 1; + boundary_pix = 0; + } + else{ + /* To connect 2 bifurcations, draw white. */ + minutia_pix = 0; + boundary_pix = 1; + } + + /* Foreach point on line connecting the minutiae points ... */ + for(i = 1; i < num-1; i++){ + /* Draw minutia pixel at current point on line. */ + *(bdata+(y_list[i]*iw)+x_list[i]) = minutia_pix; + + /* Initialize starting corrdinates for adding width to the */ + /* join line to the current point on the line. */ + x1 = x_list[i]; + y1 = y_list[i]; + x2 = x1; + y2 = y1; + /* Foreach pixel of added radial width ... */ + for(j = 0; j < line_radius; j++){ + + /* If |DX|>=|DY|, we want to add width to line by writing */ + /* to pixels neighboring above and below. */ + /* x1 -= (0=(1-1)); y1 -= 1 ==> ABOVE */ + /* x2 += (0=(1-1)); y2 += 1 ==> BELOW */ + /* If |DX|<|DY|, we want to add width to line by writing */ + /* to pixels neighboring left and right. */ + /* x1 -= (1=(1-0)); y1 -= 0 ==> LEFT */ + /* x2 += (1=(1-0)); y2 += 0 ==> RIGHT */ + + /* Advance 1st point along width dimension. */ + x1 -= (1 - dx_gte_dy); + y1 -= dx_gte_dy; + /* If pixel 1st point is within image boundaries ... */ + if((x1 >= 0) && (x1 < iw) && + (y1 >= 0) && (y1 < ih)) + /* Write the pixel ABOVE or LEFT. */ + *(bdata+(y1*iw)+x1) = minutia_pix; + + /* Advance 2nd point along width dimension. */ + x2 += (1 - dx_gte_dy); + y2 += dx_gte_dy; + /* If pixel 2nd point is within image boundaries ... */ + if((x2 >= 0) && (x2 < iw) && + /* Write the pixel BELOW or RIGHT. */ + (y2 >= 0) && (y2 < ih)) + *(bdata+(y2*iw)+x2) = minutia_pix; + } + + /* If boundary flag is set ... draw the boundary pixels.*/ + if(with_boundary){ + /* Advance 1st point along width dimension. */ + x1 -= (1 - dx_gte_dy); + y1 -= dx_gte_dy; + /* If pixel 1st point is within image boundaries ... */ + if((x1 >= 0) && (x1 < iw) && + (y1 >= 0) && (y1 < ih)) + /* Write the pixel ABOVE or LEFT of opposite color. */ + *(bdata+(y1*iw)+x1) = boundary_pix; + + /* Advance 2nd point along width dimension. */ + x2 += (1 - dx_gte_dy); + y2 += dx_gte_dy; + /* If pixel 2nd point is within image boundaries ... */ + if((x2 >= 0) && (x2 < iw) && + (y2 >= 0) && (y2 < ih)) + /* Write the pixel BELOW or RIGHT of opposite color. */ + *(bdata+(y2*iw)+x2) = boundary_pix; + } + } + + /* Deallocate points along connecting line. */ + free(x_list); + free(y_list); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: minutia_type - Given the pixel color of the detected feature, returns +#cat: whether the minutia is a ridge-ending (black pixel) or +#cat: bifurcation (white pixel). + + Input: + feature_pix - pixel color of the feature's interior + Return Code: + RIDGE_ENDING - minutia is a ridge-ending + BIFURCATION - minutia is a bifurcation (valley-ending) +**************************************************************************/ +int minutia_type(const int feature_pix) +{ + int type; + + /* If feature pixel is white ... */ + if(feature_pix == 0) + /* Then the feature is a valley-ending, so BIFURCATION. */ + type = BIFURCATION; + /* Otherwise, the feature pixel is black ... */ + else + /* So the feature is a RIDGE-ENDING. */ + type = RIDGE_ENDING; + + /* Return the type. */ + return(type); +} + +/************************************************************************* +************************************************************************** +#cat: is_minutia_appearing - Given the pixel location of a minutia feature +#cat: and its corresponding adjacent edge pixel, returns whether +#cat: the minutia is appearing or disappearing. Remeber, that +#cat: "feature" refers to either a ridge or valley-ending. + + Input: + x_loc - x-pixel coord of feature (interior to feature) + y_loc - y-pixel coord of feature (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel + (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel + (exterior to feature) + Return Code: + APPEARING - minutia is appearing (TRUE==1) + DISAPPEARING - minutia is disappearing (FALSE==0) + Negative - system error +**************************************************************************/ +int is_minutia_appearing(const int x_loc, const int y_loc, + const int x_edge, const int y_edge) +{ + /* Edge pixels will always be N,S,E,W of feature pixel. */ + + /* 1. When scanning for feature's HORIZONTALLY... */ + /* If the edge is above the feature, then appearing. */ + if(x_edge < x_loc) + return(APPEARING); + /* If the edge is below the feature, then disappearing. */ + if(x_edge > x_loc) + return(DISAPPEARING); + + /* 1. When scanning for feature's VERTICALLY... */ + /* If the edge is left of feature, then appearing. */ + if(y_edge < y_loc) + return(APPEARING); + /* If the edge is right of feature, then disappearing. */ + if(y_edge > y_loc) + return(DISAPPEARING); + + /* Should never get here, but just in case. */ + fprintf(stderr, + "ERROR : is_minutia_appearing : bad configuration of pixels\n"); + return(-240); +} + +/************************************************************************* +************************************************************************** +#cat: choose_scan_direction - Determines the orientation (horizontal or +#cat: vertical) in which a block is to be scanned for minutiae. +#cat: The orientation is based on the blocks corresponding IMAP +#cat: direction. + + Input: + imapval - Block's IMAP direction + ndirs - number of possible IMAP directions (within semicircle) + Return Code: + SCAN_HORIZONTAL - horizontal orientation + SCAN_VERTICAL - vertical orientation +**************************************************************************/ +int choose_scan_direction(const int imapval, const int ndirs) +{ + int qtr_ndirs; + + /* Compute quarter of directions in semi-circle. */ + qtr_ndirs = ndirs>>2; + + /* If ridge flow in block is relatively vertical, then we want */ + /* to scan for minutia features in the opposite direction */ + /* (ie. HORIZONTALLY). */ + if((imapval <= qtr_ndirs) || (imapval > (qtr_ndirs*3))) + return(SCAN_HORIZONTAL); + /* Otherwise, ridge flow is realtively horizontal, and we want */ + /* to scan for minutia features in the opposite direction */ + /* (ie. VERTICALLY). */ + else + return(SCAN_VERTICAL); + +} + +/************************************************************************* +************************************************************************** +#cat: scan4minutiae - Scans a block of binary image data detecting potential +#cat: minutiae points. + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imap - matrix of ridge flow directions + nmap - IMAP augmented with blocks of HIGH-CURVATURE and + blocks which have no neighboring valid directions. + blk_x - x-block coord to be scanned + blk_y - y-block coord to be scanned + mw - width (in blocks) of IMAP and NMAP matrices. + mh - height (in blocks) of IMAP and NMAP matrices. + scan_x - x-pixel coord of origin of region to be scanned + scan_y - y-pixel coord of origin of region to be scanned + scan_w - width (in pixels) of region to be scanned + scan_h - height (in pixels) of region to be scanned + scan_dir - the scan orientation (horizontal or vertical) + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int scan4minutiae(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int *imap, const int *nmap, + const int blk_x, const int blk_y, const int mw, const int mh, + const int scan_x, const int scan_y, + const int scan_w, const int scan_h, const int scan_dir, + const LFSPARMS *lfsparms) +{ + int blk_i, ret; + + /* Compute block index from block coordinates. */ + blk_i = (blk_y*mw) + blk_x; + + /* Conduct primary scan for minutiae horizontally. */ + if(scan_dir == SCAN_HORIZONTAL){ + + if((ret = scan4minutiae_horizontally(minutiae, bdata, iw, ih, + imap[blk_i], nmap[blk_i], + scan_x, scan_y, scan_w, scan_h, lfsparms))){ + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + } + + /* Rescan block vertically. */ + if((ret = rescan4minutiae_vertically(minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))){ + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + } + } + + /* Otherwise, conduct primary scan for minutiae vertically. */ + else{ + if((ret = scan4minutiae_vertically(minutiae, bdata, iw, ih, + imap[blk_i], nmap[blk_i], + scan_x, scan_y, scan_w, scan_h, lfsparms))){ + /* Return resulting code. */ + return(ret); + } + + /* Rescan block horizontally. */ + if((ret = rescan4minutiae_horizontally(minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))){ + /* Return resulting code. */ + return(ret); + } + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: scan4minutiae_horizontally - Scans a specified region of binary image +#cat: data horizontally, detecting potential minutiae points. +#cat: Minutia detected via the horizontal scan process are +#cat: by nature vertically oriented (orthogonal to the scan). +#cat: The region actually scanned is slightly larger than that +#cat: specified. This overlap attempts to minimize the number +#cat: of minutiae missed at the region boundaries. +#cat: HOWEVER, some minutiae will still be missed! + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imapval - IMAP value associated with this image region + nmapval - NMAP value associated with this image region + scan_x - x-pixel coord of origin of region to be scanned + scan_y - y-pixel coord of origin of region to be scanned + scan_w - width (in pixels) of region to be scanned + scan_h - height (in pixels) of region to be scanned + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int scan4minutiae_horizontally(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int imapval, const int nmapval, + const int scan_x, const int scan_y, + const int scan_w, const int scan_h, + const LFSPARMS *lfsparms) +{ + int sx, sy, ex, ey, cx, cy, x2; + unsigned char *p1ptr, *p2ptr; + int possible[NFEATURES], nposs; + int ret; + + /* NOTE!!! Minutia that "straddle" region boundaries may be missed! */ + + /* If possible, overlap left and right of current scan region */ + /* by 2 pixel columns to help catch some minutia that straddle the */ + /* the scan region boundaries. */ + sx = max(0, scan_x-2); + ex = min(iw, scan_x+scan_w+2); + + /* If possible, overlap the scan region below by 1 pixel row. */ + sy = scan_y; + ey = min(ih, scan_y+scan_h+1); + + /* For now, we will not adjust for IMAP edge, as the binary image */ + /* was properly padded at its edges so as not to cause anomallies. */ + + /* Start at first row in region. */ + cy = sy; + /* While second scan row not outside the bottom of the scan region... */ + while(cy+1 < ey){ + /* Start at beginning of new scan row in region. */ + cx = sx; + /* While not at end of region's current scan row. */ + while(cx < ex){ + /* Get pixel pair from current x position in current and next */ + /* scan rows. */ + p1ptr = bdata+(cy*iw)+cx; + p2ptr = bdata+((cy+1)*iw)+cx; + /* If scan pixel pair matches first pixel pair of */ + /* 1 or more features... */ + if(match_1st_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Bump forward to next scan pixel pair. */ + cx++; + p1ptr++; + p2ptr++; + /* If not at end of region's current scan row... */ + if(cx < ex){ + /* If scan pixel pair matches second pixel pair of */ + /* 1 or more features... */ + if(match_2nd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Store current x location. */ + x2 = cx; + /* Skip repeated pixel pairs. */ + skip_repeated_horizontal_pair(&cx, ex, &p1ptr, &p2ptr, + iw, ih); + /* If not at end of region's current scan row... */ + if(cx < ex){ + /* If scan pixel pair matches third pixel pair of */ + /* a single feature... */ + if(match_3rd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Process detected minutia point. */ + if((ret = process_horizontal_scan_minutia(minutiae, + cx, cy, x2, possible[0], + bdata, iw, ih, + imapval, nmapval, lfsparms))){ + /* Return code may be: */ + /* 1. ret< 0 (implying system error) */ + /* 2. ret==IGNORE (ignore current feature) */ + if(ret < 0) + return(ret); + /* Otherwise, IGNORE and continue. */ + } + } + + /* Set up to resume scan. */ + /* Test to see if 3rd pair can slide into 2nd pair. */ + /* The values of the 2nd pair MUST be different. */ + /* If 3rd pair values are different ... */ + if(*p1ptr != *p2ptr){ + /* Set next first pair to last of repeated */ + /* 2nd pairs, ie. back up one pair. */ + cx--; + } + + /* Otherwise, 3rd pair can't be a 2nd pair, so */ + /* keep pointing to 3rd pair so that it is used */ + /* in the next first pair test. */ + + } /* Else, at end of current scan row. */ + } + + /* Otherwise, 2nd pair failed, so keep pointing to it */ + /* so that it is used in the next first pair test. */ + + } /* Else, at end of current scan row. */ + } + /* Otherwise, 1st pair failed... */ + else{ + /* Bump forward to next pixel pair. */ + cx++; + } + } /* While not at end of current scan row. */ + /* Bump forward to next scan row. */ + cy++; + } /* While not out of scan rows. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: scan4minutiae_horizontally_V2 - Scans an entire binary image +#cat: horizontally, detecting potential minutiae points. +#cat: Minutia detected via the horizontal scan process are +#cat: by nature vertically oriented (orthogonal to the scan). + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + pdirection_map - pixelized Direction Map + plow_flow_map - pixelized Low Ridge Flow Map + phigh_curve_map - pixelized High Curvature Map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int scan4minutiae_horizontally_V2(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *pdirection_map, int *plow_flow_map, int *phigh_curve_map, + const LFSPARMS *lfsparms) +{ + int sx, sy, ex, ey, cx, cy, x2; + unsigned char *p1ptr, *p2ptr; + int possible[NFEATURES], nposs; + int ret; + + /* Set scan region to entire image. */ + sx = 0; + ex = iw; + sy = 0; + ey = ih; + + /* Start at first row in region. */ + cy = sy; + /* While second scan row not outside the bottom of the scan region... */ + while(cy+1 < ey){ + /* Start at beginning of new scan row in region. */ + cx = sx; + /* While not at end of region's current scan row. */ + while(cx < ex){ + /* Get pixel pair from current x position in current and next */ + /* scan rows. */ + p1ptr = bdata+(cy*iw)+cx; + p2ptr = bdata+((cy+1)*iw)+cx; + /* If scan pixel pair matches first pixel pair of */ + /* 1 or more features... */ + if(match_1st_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Bump forward to next scan pixel pair. */ + cx++; + p1ptr++; + p2ptr++; + /* If not at end of region's current scan row... */ + if(cx < ex){ + /* If scan pixel pair matches second pixel pair of */ + /* 1 or more features... */ + if(match_2nd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Store current x location. */ + x2 = cx; + /* Skip repeated pixel pairs. */ + skip_repeated_horizontal_pair(&cx, ex, &p1ptr, &p2ptr, + iw, ih); + /* If not at end of region's current scan row... */ + if(cx < ex){ + /* If scan pixel pair matches third pixel pair of */ + /* a single feature... */ + if(match_3rd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Process detected minutia point. */ + if((ret = process_horizontal_scan_minutia_V2(minutiae, + cx, cy, x2, possible[0], + bdata, iw, ih, pdirection_map, + plow_flow_map, phigh_curve_map, + lfsparms))){ + /* Return code may be: */ + /* 1. ret< 0 (implying system error) */ + /* 2. ret==IGNORE (ignore current feature) */ + if(ret < 0) + return(ret); + /* Otherwise, IGNORE and continue. */ + } + } + + /* Set up to resume scan. */ + /* Test to see if 3rd pair can slide into 2nd pair. */ + /* The values of the 2nd pair MUST be different. */ + /* If 3rd pair values are different ... */ + if(*p1ptr != *p2ptr){ + /* Set next first pair to last of repeated */ + /* 2nd pairs, ie. back up one pair. */ + cx--; + } + + /* Otherwise, 3rd pair can't be a 2nd pair, so */ + /* keep pointing to 3rd pair so that it is used */ + /* in the next first pair test. */ + + } /* Else, at end of current scan row. */ + } + + /* Otherwise, 2nd pair failed, so keep pointing to it */ + /* so that it is used in the next first pair test. */ + + } /* Else, at end of current scan row. */ + } + /* Otherwise, 1st pair failed... */ + else{ + /* Bump forward to next pixel pair. */ + cx++; + } + } /* While not at end of current scan row. */ + /* Bump forward to next scan row. */ + cy++; + } /* While not out of scan rows. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: scan4minutiae_vertically - Scans a specified region of binary image data +#cat: vertically, detecting potential minutiae points. +#cat: Minutia detected via the vetical scan process are +#cat: by nature horizontally oriented (orthogonal to the scan). +#cat: The region actually scanned is slightly larger than that +#cat: specified. This overlap attempts to minimize the number +#cat: of minutiae missed at the region boundaries. +#cat: HOWEVER, some minutiae will still be missed! + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imapval - IMAP value associated with this image region + nmapval - NMAP value associated with this image region + scan_x - x-pixel coord of origin of region to be scanned + scan_y - y-pixel coord of origin of region to be scanned + scan_w - width (in pixels) of region to be scanned + scan_h - height (in pixels) of region to be scanned + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int scan4minutiae_vertically(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int imapval, const int nmapval, + const int scan_x, const int scan_y, + const int scan_w, const int scan_h, + const LFSPARMS *lfsparms) +{ + int sx, sy, ex, ey, cx, cy, y2; + unsigned char *p1ptr, *p2ptr; + int possible[NFEATURES], nposs; + int ret; + + /* NOTE!!! Minutia that "straddle" region boundaries may be missed! */ + + /* If possible, overlap scan region to the right by 1 pixel column. */ + sx = scan_x; + ex = min(iw, scan_x+scan_w+1); + + /* If possible, overlap top and bottom of current scan region */ + /* by 2 pixel rows to help catch some minutia that straddle the */ + /* the scan region boundaries. */ + sy = max(0, scan_y-2); + ey = min(ih, scan_y+scan_h+2); + + /* For now, we will not adjust for IMAP edge, as the binary image */ + /* was properly padded at its edges so as not to cause anomalies. */ + + /* Start at first column in region. */ + cx = sx; + /* While second scan column not outside the right of the region ... */ + while(cx+1 < ex){ + /* Start at beginning of new scan column in region. */ + cy = sy; + /* While not at end of region's current scan column. */ + while(cy < ey){ + /* Get pixel pair from current y position in current and next */ + /* scan columns. */ + p1ptr = bdata+(cy*iw)+cx; + p2ptr = p1ptr+1; + /* If scan pixel pair matches first pixel pair of */ + /* 1 or more features... */ + if(match_1st_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Bump forward to next scan pixel pair. */ + cy++; + p1ptr+=iw; + p2ptr+=iw; + /* If not at end of region's current scan column... */ + if(cy < ey){ + /* If scan pixel pair matches second pixel pair of */ + /* 1 or more features... */ + if(match_2nd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Store current y location. */ + y2 = cy; + /* Skip repeated pixel pairs. */ + skip_repeated_vertical_pair(&cy, ey, &p1ptr, &p2ptr, + iw, ih); + /* If not at end of region's current scan column... */ + if(cy < ey){ + /* If scan pixel pair matches third pixel pair of */ + /* a single feature... */ + if(match_3rd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Process detected minutia point. */ + if((ret = process_vertical_scan_minutia(minutiae, + cx, cy, y2, possible[0], + bdata, iw, ih, + imapval, nmapval, lfsparms))){ + /* Return code may be: */ + /* 1. ret< 0 (implying system error) */ + /* 2. ret==IGNORE (ignore current feature) */ + if(ret < 0) + return(ret); + /* Otherwise, IGNORE and continue. */ + } + } + + /* Set up to resume scan. */ + /* Test to see if 3rd pair can slide into 2nd pair. */ + /* The values of the 2nd pair MUST be different. */ + /* If 3rd pair values are different ... */ + if(*p1ptr != *p2ptr){ + /* Set next first pair to last of repeated */ + /* 2nd pairs, ie. back up one pair. */ + cy--; + } + + /* Otherwise, 3rd pair can't be a 2nd pair, so */ + /* keep pointing to 3rd pair so that it is used */ + /* in the next first pair test. */ + + } /* Else, at end of current scan row. */ + } + + /* Otherwise, 2nd pair failed, so keep pointing to it */ + /* so that it is used in the next first pair test. */ + + } /* Else, at end of current scan column. */ + } + /* Otherwise, 1st pair failed... */ + else{ + /* Bump forward to next pixel pair. */ + cy++; + } + } /* While not at end of current scan column. */ + /* Bump forward to next scan column. */ + cx++; + } /* While not out of scan columns. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: scan4minutiae_vertically_V2 - Scans an entire binary image +#cat: vertically, detecting potential minutiae points. +#cat: Minutia detected via the vetical scan process are +#cat: by nature horizontally oriented (orthogonal to the scan). + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + pdirection_map - pixelized Direction Map + plow_flow_map - pixelized Low Ridge Flow Map + phigh_curve_map - pixelized High Curvature Map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int scan4minutiae_vertically_V2(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *pdirection_map, int *plow_flow_map, int *phigh_curve_map, + const LFSPARMS *lfsparms) +{ + int sx, sy, ex, ey, cx, cy, y2; + unsigned char *p1ptr, *p2ptr; + int possible[NFEATURES], nposs; + int ret; + + /* Set scan region to entire image. */ + sx = 0; + ex = iw; + sy = 0; + ey = ih; + + /* Start at first column in region. */ + cx = sx; + /* While second scan column not outside the right of the region ... */ + while(cx+1 < ex){ + /* Start at beginning of new scan column in region. */ + cy = sy; + /* While not at end of region's current scan column. */ + while(cy < ey){ + /* Get pixel pair from current y position in current and next */ + /* scan columns. */ + p1ptr = bdata+(cy*iw)+cx; + p2ptr = p1ptr+1; + /* If scan pixel pair matches first pixel pair of */ + /* 1 or more features... */ + if(match_1st_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Bump forward to next scan pixel pair. */ + cy++; + p1ptr+=iw; + p2ptr+=iw; + /* If not at end of region's current scan column... */ + if(cy < ey){ + /* If scan pixel pair matches second pixel pair of */ + /* 1 or more features... */ + if(match_2nd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Store current y location. */ + y2 = cy; + /* Skip repeated pixel pairs. */ + skip_repeated_vertical_pair(&cy, ey, &p1ptr, &p2ptr, + iw, ih); + /* If not at end of region's current scan column... */ + if(cy < ey){ + /* If scan pixel pair matches third pixel pair of */ + /* a single feature... */ + if(match_3rd_pair(*p1ptr, *p2ptr, possible, &nposs)){ + /* Process detected minutia point. */ + if((ret = process_vertical_scan_minutia_V2(minutiae, + cx, cy, y2, possible[0], + bdata, iw, ih, pdirection_map, + plow_flow_map, phigh_curve_map, + lfsparms))){ + /* Return code may be: */ + /* 1. ret< 0 (implying system error) */ + /* 2. ret==IGNORE (ignore current feature) */ + if(ret < 0) + return(ret); + /* Otherwise, IGNORE and continue. */ + } + } + + /* Set up to resume scan. */ + /* Test to see if 3rd pair can slide into 2nd pair. */ + /* The values of the 2nd pair MUST be different. */ + /* If 3rd pair values are different ... */ + if(*p1ptr != *p2ptr){ + /* Set next first pair to last of repeated */ + /* 2nd pairs, ie. back up one pair. */ + cy--; + } + + /* Otherwise, 3rd pair can't be a 2nd pair, so */ + /* keep pointing to 3rd pair so that it is used */ + /* in the next first pair test. */ + + } /* Else, at end of current scan row. */ + } + + /* Otherwise, 2nd pair failed, so keep pointing to it */ + /* so that it is used in the next first pair test. */ + + } /* Else, at end of current scan column. */ + } + /* Otherwise, 1st pair failed... */ + else{ + /* Bump forward to next pixel pair. */ + cy++; + } + } /* While not at end of current scan column. */ + /* Bump forward to next scan column. */ + cx++; + } /* While not out of scan columns. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: rescan4minutiae_horizontally - Rescans portions of a block of binary +#cat: image data horizontally for potential minutiae. The areas +#cat: rescanned within the block are based on the current +#cat: block's neighboring blocks' IMAP and NMAP values. + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imap - matrix of ridge flow directions + nmap - IMAP augmented with blocks of HIGH-CURVATURE and + blocks which have no neighboring valid directions. + blk_x - x-block coord to be rescanned + blk_y - y-block coord to be rescanned + mw - width (in blocks) of IMAP and NMAP matrices. + mh - height (in blocks) of IMAP and NMAP matrices. + scan_x - x-pixel coord of origin of region to be rescanned + scan_y - y-pixel coord of origin of region to be rescanned + scan_w - width (in pixels) of region to be rescanned + scan_h - height (in pixels) of region to be rescanned + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int rescan4minutiae_horizontally(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int *imap, const int *nmap, + const int blk_x, const int blk_y, + const int mw, const int mh, + const int scan_x, const int scan_y, + const int scan_w, const int scan_h, + const LFSPARMS *lfsparms) +{ + int blk_i, ret; + + /* Compute block index from block coordinates. */ + blk_i = (blk_y*mw)+blk_x; + + /* If high-curve block... */ + if(nmap[blk_i] == HIGH_CURVATURE){ + /* Rescan entire block in orthogonal direction. */ + if((ret = scan4minutiae_horizontally(minutiae, bdata, iw, ih, + imap[blk_i], nmap[blk_i], + scan_x, scan_y, scan_w, scan_h, lfsparms))) + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + } + /* Otherwise, block is low-curvature. */ + else{ + /* 1. Rescan horizontally to the North. */ + if((ret = rescan_partial_horizontally(NORTH, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + + /* 2. Rescan horizontally to the East. */ + if((ret = rescan_partial_horizontally(EAST, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + return(ret); + + /* 3. Rescan horizontally to the South. */ + if((ret = rescan_partial_horizontally(SOUTH, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + return(ret); + + /* 4. Rescan horizontally to the West. */ + if((ret = rescan_partial_horizontally(WEST, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + return(ret); + } /* End low-curvature rescan. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: rescan4minutiae_vertically - Rescans portions of a block of binary +#cat: image data vertically for potential minutiae. The areas +#cat: rescanned within the block are based on the current +#cat: block's neighboring blocks' IMAP and NMAP values. + + Input: + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imap - matrix of ridge flow directions + nmap - IMAP augmented with blocks of HIGH-CURVATURE and + blocks which have no neighboring valid directions. + blk_x - x-block coord to be rescanned + blk_y - y-block coord to be rescanned + mw - width (in blocks) of IMAP and NMAP matrices. + mh - height (in blocks) of IMAP and NMAP matrices. + scan_x - x-pixel coord of origin of region to be rescanned + scan_y - y-pixel coord of origin of region to be rescanned + scan_w - width (in pixels) of region to be rescanned + scan_h - height (in pixels) of region to be rescanned + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int rescan4minutiae_vertically(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int *imap, const int *nmap, + const int blk_x, const int blk_y, + const int mw, const int mh, + const int scan_x, const int scan_y, + const int scan_w, const int scan_h, + const LFSPARMS *lfsparms) +{ + int blk_i, ret; + + /* Compute block index from block coordinates. */ + blk_i = (blk_y*mw)+blk_x; + + /* If high-curve block... */ + if(nmap[blk_i] == HIGH_CURVATURE){ + /* Rescan entire block in orthogonal direction. */ + if((ret = scan4minutiae_vertically(minutiae, bdata, iw, ih, + imap[blk_i], nmap[blk_i], + scan_x, scan_y, scan_w, scan_h, lfsparms))) + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + } + /* Otherwise, block is low-curvature. */ + else{ + /* 1. Rescan vertically to the North. */ + if((ret = rescan_partial_vertically(NORTH, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + + /* 2. Rescan vertically to the East. */ + if((ret = rescan_partial_vertically(EAST, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + return(ret); + + /* 3. Rescan vertically to the South. */ + if((ret = rescan_partial_vertically(SOUTH, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + return(ret); + + /* 4. Rescan vertically to the West. */ + if((ret = rescan_partial_vertically(WEST, minutiae, bdata, iw, ih, + imap, nmap, blk_x, blk_y, mw, mh, + scan_x, scan_y, scan_w, scan_h, lfsparms))) + return(ret); + } /* End low-curvature rescan. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: rescan_partial_horizontally - Rescans a portion of a block of binary +#cat: image data horizontally based on the IMAP and NMAP values +#cat: of a specified neighboring block. + + Input: + nbr_dir - specifies which block neighbor {NORTH, SOUTH, EAST, WEST} + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imap - matrix of ridge flow directions + nmap - IMAP augmented with blocks of HIGH-CURVATURE and + blocks which have no neighboring valid directions. + blk_x - x-block coord to be rescanned + blk_y - y-block coord to be rescanned + mw - width (in blocks) of IMAP and NMAP matrices. + mh - height (in blocks) of IMAP and NMAP matrices. + scan_x - x-pixel coord of origin of image region + scan_y - y-pixel coord of origin of image region + scan_w - width (in pixels) of image region + scan_h - height (in pixels) of image region + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int rescan_partial_horizontally(const int nbr_dir, MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int *imap, const int *nmap, + const int blk_x, const int blk_y, + const int mw, const int mh, + const int scan_x, const int scan_y, + const int scan_w, const int scan_h, + const LFSPARMS *lfsparms) +{ + int nblk_i, blk_i; + int rescan_dir; + int rescan_x, rescan_y, rescan_w, rescan_h; + int ret; + + /* Neighbor will either be NORTH, SOUTH, EAST, OR WEST. */ + ret = get_nbr_block_index(&nblk_i, nbr_dir, blk_x, blk_y, mw, mh); + /* Will return: */ + /* 1. Neighbor index found == FOUND */ + /* 2. Neighbor not found == NOT_FOUND */ + /* 3. System error < 0 */ + + /* If system error ... */ + if(ret < 0) + /* Return the error code. */ + return(ret); + + /* If neighbor not found ... */ + if(ret == NOT_FOUND) + /* Nothing to do, so return normally. */ + return(0); + + /* Otherwise, neighboring block found ... */ + + /* If neighbor block is VALID... */ + if(imap[nblk_i] != INVALID_DIR){ + + /* Compute block index from current (not neighbor) block coordinates. */ + blk_i = (blk_y*mw)+blk_x; + + /* Select feature scan direction based on neighbor IMAP. */ + rescan_dir = choose_scan_direction(imap[nblk_i], + lfsparms->num_directions); + /* If new scan direction is HORIZONTAL... */ + if(rescan_dir == SCAN_HORIZONTAL){ + /* Adjust scan_x, scan_y, scan_w, scan_h for rescan. */ + if((ret = adjust_horizontal_rescan(nbr_dir, &rescan_x, &rescan_y, + &rescan_w, &rescan_h, + scan_x, scan_y, scan_w, scan_h, lfsparms->blocksize))) + /* Return system error code. */ + return(ret); + /* Rescan specified region in block vertically. */ + /* Pass IMAP direction for the block, NOT its neighbor. */ + if((ret = scan4minutiae_horizontally(minutiae, bdata, iw, ih, + imap[blk_i], nmap[blk_i], + rescan_x, rescan_y, rescan_w, rescan_h, lfsparms))) + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + } /* Otherwise, block has already been scanned vertically. */ + } /* Otherwise, neighbor has INVALID IMAP, so ignore rescan. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: rescan_partial_vertically - Rescans a portion of a block of binary +#cat: image data vertically based on the IMAP and NMAP values +#cat: of a specified neighboring block. + + Input: + nbr_dir - specifies which block neighbor {NORTH, SOUTH, EAST, WEST} + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imap - matrix of ridge flow directions + nmap - IMAP augmented with blocks of HIGH-CURVATURE and + blocks which have no neighboring valid directions. + blk_x - x-block coord to be rescanned + blk_y - y-block coord to be rescanned + mw - width (in blocks) of IMAP and NMAP matrices. + mh - height (in blocks) of IMAP and NMAP matrices. + scan_x - x-pixel coord of origin of image region + scan_y - y-pixel coord of origin of image region + scan_w - width (in pixels) of image region + scan_h - height (in pixels) of image region + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int rescan_partial_vertically(const int nbr_dir, MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const int *imap, const int *nmap, + const int blk_x, const int blk_y, + const int mw, const int mh, + const int scan_x, const int scan_y, + const int scan_w, const int scan_h, + const LFSPARMS *lfsparms) +{ + int nblk_i, blk_i; + int rescan_dir; + int rescan_x, rescan_y, rescan_w, rescan_h; + int ret; + + /* Neighbor will either be NORTH, SOUTH, EAST, OR WEST. */ + ret = get_nbr_block_index(&nblk_i, nbr_dir, blk_x, blk_y, mw, mh); + /* Will return: */ + /* 1. Neighbor index found == FOUND */ + /* 2. Neighbor not found == NOT_FOUND */ + /* 3. System error < 0 */ + + /* If system error ... */ + if(ret < 0) + /* Return the error code. */ + return(ret); + + /* If neighbor not found ... */ + if(ret == NOT_FOUND) + /* Nothing to do, so return normally. */ + return(0); + + /* Otherwise, neighboring block found ... */ + + /* If neighbor block is VALID... */ + if(imap[nblk_i] != INVALID_DIR){ + + /* Compute block index from current (not neighbor) block coordinates. */ + blk_i = (blk_y*mw)+blk_x; + + /* Select feature scan direction based on neighbor IMAP. */ + rescan_dir = choose_scan_direction(imap[nblk_i], + lfsparms->num_directions); + /* If new scan direction is VERTICAL... */ + if(rescan_dir == SCAN_VERTICAL){ + /* Adjust scan_x, scan_y, scan_w, scan_h for rescan. */ + if((ret = adjust_vertical_rescan(nbr_dir, &rescan_x, &rescan_y, + &rescan_w, &rescan_h, + scan_x, scan_y, scan_w, scan_h, lfsparms->blocksize))) + /* Return system error code. */ + return(ret); + /* Rescan specified region in block vertically. */ + /* Pass IMAP direction for the block, NOT its neighbor. */ + if((ret = scan4minutiae_vertically(minutiae, bdata, iw, ih, + imap[blk_i], nmap[blk_i], + rescan_x, rescan_y, rescan_w, rescan_h, lfsparms))) + /* Return code may be: */ + /* 1. ret<0 (implying system error) */ + return(ret); + } /* Otherwise, block has already been scanned horizontally. */ + } /* Otherwise, neighbor has INVALID IMAP, so ignore rescan. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: get_nbr_block_index - Determines the block index (if one exists) +#cat: for a specified neighbor of a block in the image. + + Input: + nbr_dir - specifies which block neighbor {NORTH, SOUTH, EAST, WEST} + blk_x - x-block coord to find neighbor of + blk_y - y-block coord to find neighbor of + mw - width (in blocks) of IMAP and NMAP matrices. + mh - height (in blocks) of IMAP and NMAP matrices. + Output: + oblk_i - points to neighbor's block index + Return Code: + NOT_FOUND - neighbor index does not exist + FOUND - neighbor index exists and returned + Negative - system error +**************************************************************************/ +int get_nbr_block_index(int *oblk_i, const int nbr_dir, + const int blk_x, const int blk_y, const int mw, const int mh) +{ + int nx, ny, ni; + + switch(nbr_dir){ + case NORTH: + /* If neighbor doesn't exist above... */ + if((ny = blk_y-1) < 0) + /* Done, so return normally. */ + return(NOT_FOUND); + /* Get neighbor's block index. */ + ni = (ny*mw)+blk_x; + break; + case EAST: + /* If neighbor doesn't exist to the right... */ + if((nx = blk_x+1) >= mw) + /* Done, so return normally. */ + return(NOT_FOUND); + /* Get neighbor's block index. */ + ni = (blk_y*mw)+nx; + break; + case SOUTH: + /* If neighbor doesn't exist below... */ + if((ny = blk_y+1) >= mh) + /* Return normally. */ + return(NOT_FOUND); + /* Get neighbor's block index. */ + ni = (ny*mw)+blk_x; + break; + case WEST: + /* If neighbor doesn't exist to the left... */ + if((nx = blk_x-1) < 0) + /* Return normally. */ + return(NOT_FOUND); + /* Get neighbor's block index. */ + ni = (blk_y*mw)+nx; + break; + default: + fprintf(stderr, + "ERROR : get_nbr_block_index : illegal neighbor direction\n"); + return(-200); + } + + /* Assign output pointer. */ + *oblk_i = ni; + + /* Return neighbor FOUND. */ + return(FOUND); +} + +/************************************************************************* +************************************************************************** +#cat: adjust_horizontal_rescan - Determines the portion of an image block to +#cat: be rescanned horizontally based on a specified neighbor. + + Input: + nbr_dir - specifies which block neighbor {NORTH, SOUTH, EAST, WEST} + scan_x - x-pixel coord of origin of image region + scan_y - y-pixel coord of origin of image region + scan_w - width (in pixels) of image region + scan_h - height (in pixels) of image region + blocksize - dimension of image blocks (in pixels) + Output: + rescan_x - x-pixel coord of origin of region to be rescanned + rescan_y - y-pixel coord of origin of region to be rescanned + rescan_w - width (in pixels) of region to be rescanned + rescan_h - height (in pixels) of region to be rescanned + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int adjust_horizontal_rescan(const int nbr_dir, int *rescan_x, int *rescan_y, + int *rescan_w, int *rescan_h, const int scan_x, const int scan_y, + const int scan_w, const int scan_h, const int blocksize) +{ + int half_blocksize, qtr_blocksize; + + /* Compute half of blocksize. */ + half_blocksize = blocksize>>1; + /* Compute quarter of blocksize. */ + qtr_blocksize = blocksize>>2; + + /* Neighbor will either be NORTH, SOUTH, EAST, OR WEST. */ + switch(nbr_dir){ + case NORTH: + /* + ************************* + * RESCAN NORTH * + * AREA * + ************************* + | | + | | + | | + | | + | | + | | + ------------------------- + */ + /* Rescan origin stays the same. */ + *rescan_x = scan_x; + *rescan_y = scan_y; + /* Rescan width stays the same. */ + *rescan_w = scan_w; + /* Rescan height is reduced to "qtr_blocksize" */ + /* if scan_h is larger. */ + *rescan_h = min(qtr_blocksize, scan_h); + break; + case EAST: + /* + ------------************* + | * * + | * * + | * E R * + | * A E * + | * S S * + | * T C * + | * A * + | * N * + | * * + | * * + ------------************* + */ + /* Rescan x-orign is set to half_blocksize from right edge of */ + /* block if scan width is larger. */ + *rescan_x = max(scan_x+scan_w-half_blocksize, scan_x); + /* Rescan y-origin stays the same. */ + *rescan_y = scan_y; + /* Rescan width is reduced to "half_blocksize" */ + /* if scan width is larger. */ + *rescan_w = min(half_blocksize, scan_w); + /* Rescan height stays the same. */ + *rescan_h = scan_h; + break; + case SOUTH: + /* + ------------------------- + | | + | | + | | + | | + | | + | | + ************************* + * RESCAN SOUTH * + * AREA * + ************************* + */ + /* Rescan x-origin stays the same. */ + *rescan_x = scan_x; + /* Rescan y-orign is set to qtr_blocksize from bottom edge of */ + /* block if scan height is larger. */ + *rescan_y = max(scan_y+scan_h-qtr_blocksize, scan_y); + /* Rescan width stays the same. */ + *rescan_w = scan_w; + /* Rescan height is reduced to "qtr_blocksize" */ + /* if scan height is larger. */ + *rescan_h = min(qtr_blocksize, scan_h); + break; + case WEST: + /* + *************------------ + * * | + * * | + * W R * | + * E E * | + * S S * | + * T C * | + * A * | + * N * | + * * | + * * | + *************------------ + */ + /* Rescan origin stays the same. */ + *rescan_x = scan_x; + *rescan_y = scan_y; + /* Rescan width is reduced to "half_blocksize" */ + /* if scan width is larger. */ + *rescan_w = min(half_blocksize, scan_w); + /* Rescan height stays the same. */ + *rescan_h = scan_h; + break; + default: + fprintf(stderr, + "ERROR : adjust_horizontal_rescan : illegal neighbor direction\n"); + return(-210); + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: adjust_vertical_rescan - Determines the portion of an image block to +#cat: be rescanned vertically based on a specified neighbor. + + Input: + nbr_dir - specifies which block neighbor {NORTH, SOUTH, EAST, WEST} + scan_x - x-pixel coord of origin of image region + scan_y - y-pixel coord of origin of image region + scan_w - width (in pixels) of image region + scan_h - height (in pixels) of image region + blocksize - dimension of image blocks (in pixels) + Output: + rescan_x - x-pixel coord of origin of region to be rescanned + rescan_y - y-pixel coord of origin of region to be rescanned + rescan_w - width (in pixels) of region to be rescanned + rescan_h - height (in pixels) of region to be rescanned + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int adjust_vertical_rescan(const int nbr_dir, int *rescan_x, int *rescan_y, + int *rescan_w, int *rescan_h, const int scan_x, const int scan_y, + const int scan_w, const int scan_h, const int blocksize) +{ + int half_blocksize, qtr_blocksize; + + /* Compute half of blocksize. */ + half_blocksize = blocksize>>1; + /* Compute quarter of blocksize. */ + qtr_blocksize = blocksize>>2; + + /* Neighbor will either be NORTH, SOUTH, EAST, OR WEST. */ + switch(nbr_dir){ + case NORTH: + /* + ************************* + * * + * RESCAN NORTH * + * AREA * + * * + ************************* + | | + | | + | | + | | + | | + ------------------------- + */ + /* Rescan origin stays the same. */ + *rescan_x = scan_x; + *rescan_y = scan_y; + /* Rescan width stays the same. */ + *rescan_w = scan_w; + /* Rescan height is reduced to "half_blocksize" */ + /* if scan_h is larger. */ + *rescan_h = min(half_blocksize, scan_h); + break; + case EAST: + /* + ------------------******* + | * * + | * * + | * E R * + | * A E * + | * S S * + | * T C * + | * A * + | * N * + | * * + | * * + ------------------******* + */ + /* Rescan x-orign is set to qtr_blocksize from right edge of */ + /* block if scan width is larger. */ + *rescan_x = max(scan_x+scan_w-qtr_blocksize, scan_x); + /* Rescan y-origin stays the same. */ + *rescan_y = scan_y; + /* Rescan width is reduced to "qtr_blocksize" */ + /* if scan width is larger. */ + *rescan_w = min(qtr_blocksize, scan_w); + /* Rescan height stays the same. */ + *rescan_h = scan_h; + break; + case SOUTH: + /* + ------------------------- + | | + | | + | | + | | + | | + ************************* + * * + * RESCAN SOUTH * + * AREA * + * * + ************************* + */ + /* Rescan x-origin stays the same. */ + *rescan_x = scan_x; + /* Rescan y-orign is set to half_blocksize from bottom edge of */ + /* block if scan height is larger. */ + *rescan_y = max(scan_y+scan_h-half_blocksize, scan_y); + /* Rescan width stays the same. */ + *rescan_w = scan_w; + /* Rescan height is reduced to "half_blocksize" */ + /* if scan height is larger. */ + *rescan_h = min(half_blocksize, scan_h); + break; + case WEST: + /* + *******------------------ + * * | + * * | + * W R * | + * E E * | + * S S * | + * T C * | + * A * | + * N * | + * * | + * * | + *******------------------ + */ + /* Rescan origin stays the same. */ + *rescan_x = scan_x; + *rescan_y = scan_y; + /* Rescan width is reduced to "qtr_blocksize" */ + /* if scan width is larger. */ + *rescan_w = min(qtr_blocksize, scan_w); + /* Rescan height stays the same. */ + *rescan_h = scan_h; + break; + default: + fprintf(stderr, + "ERROR : adjust_vertical_rescan : illegal neighbor direction\n"); + return(-220); + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: process_horizontal_scan_minutia - Takes a minutia point that was +#cat: detected via the horizontal scan process and +#cat: adjusts its location (if necessary), determines its +#cat: direction, and (if it is not already in the minutiae +#cat: list) adds it to the list. These minutia are by nature +#cat: vertical in orientation (orthogonal to the scan). + + Input: + cx - x-pixel coord where 3rd pattern pair of mintuia was detected + cy - y-pixel coord where 3rd pattern pair of mintuia was detected + y2 - y-pixel coord where 2nd pattern pair of mintuia was detected + feature_id - type of minutia (ex. index into feature_patterns[] list) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imapval - IMAP value associated with this image region + nmapval - NMAP value associated with this image region + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + IGNORE - minutia is to be ignored + Negative - system error +**************************************************************************/ +int process_horizontal_scan_minutia(MINUTIAE *minutiae, + const int cx, const int cy, + const int x2, const int feature_id, + unsigned char *bdata, const int iw, const int ih, + const int imapval, const int nmapval, + const LFSPARMS *lfsparms) +{ + MINUTIA *minutia; + int x_loc, y_loc; + int x_edge, y_edge; + int idir, ret; + + /* Set x location of minutia point to be half way between */ + /* first position of second feature pair and position of */ + /* third feature pair. */ + x_loc = (cx + x2)>>1; + + /* Set same x location to neighboring edge pixel. */ + x_edge = x_loc; + + /* Feature location should always point to either ending */ + /* of ridge or (for bifurcations) ending of valley. */ + /* So, if detected feature is APPEARING... */ + if(feature_patterns[feature_id].appearing){ + /* Set y location to second scan row. */ + y_loc = cy+1; + /* Set y location of neighboring edge pixel to the first scan row. */ + y_edge = cy; + } + /* Otherwise, feature is DISAPPEARING... */ + else{ + /* Set y location to first scan row. */ + y_loc = cy; + /* Set y location of neighboring edge pixel to the second scan row. */ + y_edge = cy+1; + } + + /* If current minutia is in a high-curvature block... */ + if(nmapval == HIGH_CURVATURE){ + /* Adjust location and direction locally. */ + if((ret = adjust_high_curvature_minutia(&idir, &x_loc, &y_loc, + &x_edge, &y_edge, x_loc, y_loc, x_edge, y_edge, + bdata, iw, ih, minutiae, lfsparms))){ + /* Could be a system error or IGNORE minutia. */ + return(ret); + } + /* Otherwise, we have our high-curvature minutia attributes. */ + } + /* Otherwise, minutia is in fairly low-curvature block... */ + else{ + /* Get minutia direction based on current IMAP value. */ + idir = get_low_curvature_direction(SCAN_HORIZONTAL, + feature_patterns[feature_id].appearing, + imapval, lfsparms->num_directions); + } + + /* Create a minutia object based on derived attributes. */ + if((ret = create_minutia(&minutia, x_loc, y_loc, x_edge, y_edge, idir, + DEFAULT_RELIABILITY, + feature_patterns[feature_id].type, + feature_patterns[feature_id].appearing, feature_id))) + /* Return system error. */ + return(ret); + + /* Update the minutiae list with potential new minutia. */ + ret = update_minutiae(minutiae, minutia, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* Otherwise, return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: process_horizontal_scan_minutia_V2 - Takes a minutia point that was +#cat: detected via the horizontal scan process and +#cat: adjusts its location (if necessary), determines its +#cat: direction, and (if it is not already in the minutiae +#cat: list) adds it to the list. These minutia are by nature +#cat: vertical in orientation (orthogonal to the scan). + + Input: + cx - x-pixel coord where 3rd pattern pair of mintuia was detected + cy - y-pixel coord where 3rd pattern pair of mintuia was detected + y2 - y-pixel coord where 2nd pattern pair of mintuia was detected + feature_id - type of minutia (ex. index into feature_patterns[] list) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + pdirection_map - pixelized Direction Map + plow_flow_map - pixelized Low Ridge Flow Map + phigh_curve_map - pixelized High Curvature Map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + IGNORE - minutia is to be ignored + Negative - system error +**************************************************************************/ +int process_horizontal_scan_minutia_V2(MINUTIAE *minutiae, + const int cx, const int cy, + const int x2, const int feature_id, + unsigned char *bdata, const int iw, const int ih, + int *pdirection_map, int *plow_flow_map, int *phigh_curve_map, + const LFSPARMS *lfsparms) +{ + MINUTIA *minutia; + int x_loc, y_loc; + int x_edge, y_edge; + int idir, ret; + int dmapval, fmapval, cmapval; + double reliability; + + /* Set x location of minutia point to be half way between */ + /* first position of second feature pair and position of */ + /* third feature pair. */ + x_loc = (cx + x2)>>1; + + /* Set same x location to neighboring edge pixel. */ + x_edge = x_loc; + + /* Feature location should always point to either ending */ + /* of ridge or (for bifurcations) ending of valley. */ + /* So, if detected feature is APPEARING... */ + if(feature_patterns[feature_id].appearing){ + /* Set y location to second scan row. */ + y_loc = cy+1; + /* Set y location of neighboring edge pixel to the first scan row. */ + y_edge = cy; + } + /* Otherwise, feature is DISAPPEARING... */ + else{ + /* Set y location to first scan row. */ + y_loc = cy; + /* Set y location of neighboring edge pixel to the second scan row. */ + y_edge = cy+1; + } + + dmapval = *(pdirection_map+(y_loc*iw)+x_loc); + fmapval = *(plow_flow_map+(y_loc*iw)+x_loc); + cmapval = *(phigh_curve_map+(y_loc*iw)+x_loc); + + /* If the minutia point is in a block with INVALID direction ... */ + if(dmapval == INVALID_DIR) + /* Then, IGNORE the point. */ + return(IGNORE); + + /* If current minutia is in a HIGH CURVATURE block ... */ + if(cmapval){ + /* Adjust location and direction locally. */ + if((ret = adjust_high_curvature_minutia_V2(&idir, &x_loc, &y_loc, + &x_edge, &y_edge, x_loc, y_loc, x_edge, y_edge, + bdata, iw, ih, plow_flow_map, minutiae, lfsparms))){ + /* Could be a system error or IGNORE minutia. */ + return(ret); + } + /* Otherwise, we have our high-curvature minutia attributes. */ + } + /* Otherwise, minutia is in fairly low-curvature block... */ + else{ + /* Get minutia direction based on current block's direction. */ + idir = get_low_curvature_direction(SCAN_HORIZONTAL, + feature_patterns[feature_id].appearing, dmapval, + lfsparms->num_directions); + } + + /* If current minutia is in a LOW RIDGE FLOW block ... */ + if(fmapval) + reliability = MEDIUM_RELIABILITY; + else + /* Otherwise, minutia is in a block with reliable direction and */ + /* binarization. */ + reliability = HIGH_RELIABILITY; + + /* Create a minutia object based on derived attributes. */ + if((ret = create_minutia(&minutia, x_loc, y_loc, x_edge, y_edge, idir, + reliability, + feature_patterns[feature_id].type, + feature_patterns[feature_id].appearing, feature_id))) + /* Return system error. */ + return(ret); + + /* Update the minutiae list with potential new minutia. */ + ret = update_minutiae_V2(minutiae, minutia, SCAN_HORIZONTAL, + dmapval, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* Otherwise, return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: process_vertical_scan_minutia - Takes a minutia point that was +#cat: detected in via the vertical scan process and +#cat: adjusts its location (if necessary), determines its +#cat: direction, and (if it is not already in the minutiae +#cat: list) adds it to the list. These minutia are by nature +#cat: horizontal in orientation (orthogonal to the scan). + + Input: + cx - x-pixel coord where 3rd pattern pair of mintuia was detected + cy - y-pixel coord where 3rd pattern pair of mintuia was detected + x2 - x-pixel coord where 2nd pattern pair of mintuia was detected + feature_id - type of minutia (ex. index into feature_patterns[] list) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + imapval - IMAP value associated with this image region + nmapval - NMAP value associated with this image region + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + IGNORE - minutia is to be ignored + Negative - system error +**************************************************************************/ +int process_vertical_scan_minutia(MINUTIAE *minutiae, + const int cx, const int cy, + const int y2, const int feature_id, + unsigned char *bdata, const int iw, const int ih, + const int imapval, const int nmapval, + const LFSPARMS *lfsparms) +{ + MINUTIA *minutia; + int x_loc, y_loc; + int x_edge, y_edge; + int idir, ret; + + /* Feature location should always point to either ending */ + /* of ridge or (for bifurcations) ending of valley. */ + /* So, if detected feature is APPEARING... */ + if(feature_patterns[feature_id].appearing){ + /* Set x location to second scan column. */ + x_loc = cx+1; + /* Set x location of neighboring edge pixel to the first scan column. */ + x_edge = cx; + } + /* Otherwise, feature is DISAPPEARING... */ + else{ + /* Set x location to first scan column. */ + x_loc = cx; + /* Set x location of neighboring edge pixel to the second scan column. */ + x_edge = cx+1; + } + + /* Set y location of minutia point to be half way between */ + /* first position of second feature pair and position of */ + /* third feature pair. */ + y_loc = (cy + y2)>>1; + /* Set same y location to neighboring edge pixel. */ + y_edge = y_loc; + + /* If current minutia is in a high-curvature block... */ + if(nmapval == HIGH_CURVATURE){ + /* Adjust location and direction locally. */ + if((ret = adjust_high_curvature_minutia(&idir, &x_loc, &y_loc, + &x_edge, &y_edge, x_loc, y_loc, x_edge, y_edge, + bdata, iw, ih, minutiae, lfsparms))){ + /* Could be a system error or IGNORE minutia. */ + return(ret); + } + /* Otherwise, we have our high-curvature minutia attributes. */ + } + /* Otherwise, minutia is in fairly low-curvature block... */ + else{ + /* Get minutia direction based on current IMAP value. */ + idir = get_low_curvature_direction(SCAN_VERTICAL, + feature_patterns[feature_id].appearing, + imapval, lfsparms->num_directions); + } + + /* Create a minutia object based on derived attributes. */ + if((ret = create_minutia(&minutia, x_loc, y_loc, x_edge, y_edge, idir, + DEFAULT_RELIABILITY, + feature_patterns[feature_id].type, + feature_patterns[feature_id].appearing, feature_id))) + /* Return system error. */ + return(ret); + + /* Update the minutiae list with potential new minutia. */ + ret = update_minutiae(minutiae, minutia, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* Otherwise, return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: process_vertical_scan_minutia_V2 - Takes a minutia point that was +#cat: detected in via the vertical scan process and +#cat: adjusts its location (if necessary), determines its +#cat: direction, and (if it is not already in the minutiae +#cat: list) adds it to the list. These minutia are by nature +#cat: horizontal in orientation (orthogonal to the scan). + + Input: + cx - x-pixel coord where 3rd pattern pair of mintuia was detected + cy - y-pixel coord where 3rd pattern pair of mintuia was detected + x2 - x-pixel coord where 2nd pattern pair of mintuia was detected + feature_id - type of minutia (ex. index into feature_patterns[] list) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + pdirection_map - pixelized Direction Map + plow_flow_map - pixelized Low Ridge Flow Map + phigh_curve_map - pixelized High Curvature Map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - points to a list of detected minutia structures + Return Code: + Zero - successful completion + IGNORE - minutia is to be ignored + Negative - system error +**************************************************************************/ +int process_vertical_scan_minutia_V2(MINUTIAE *minutiae, + const int cx, const int cy, + const int y2, const int feature_id, + unsigned char *bdata, const int iw, const int ih, + int *pdirection_map, int *plow_flow_map, int *phigh_curve_map, + const LFSPARMS *lfsparms) +{ + MINUTIA *minutia; + int x_loc, y_loc; + int x_edge, y_edge; + int idir, ret; + int dmapval, fmapval, cmapval; + double reliability; + + /* Feature location should always point to either ending */ + /* of ridge or (for bifurcations) ending of valley. */ + /* So, if detected feature is APPEARING... */ + if(feature_patterns[feature_id].appearing){ + /* Set x location to second scan column. */ + x_loc = cx+1; + /* Set x location of neighboring edge pixel to the first scan column. */ + x_edge = cx; + } + /* Otherwise, feature is DISAPPEARING... */ + else{ + /* Set x location to first scan column. */ + x_loc = cx; + /* Set x location of neighboring edge pixel to the second scan column. */ + x_edge = cx+1; + } + + /* Set y location of minutia point to be half way between */ + /* first position of second feature pair and position of */ + /* third feature pair. */ + y_loc = (cy + y2)>>1; + /* Set same y location to neighboring edge pixel. */ + y_edge = y_loc; + + dmapval = *(pdirection_map+(y_loc*iw)+x_loc); + fmapval = *(plow_flow_map+(y_loc*iw)+x_loc); + cmapval = *(phigh_curve_map+(y_loc*iw)+x_loc); + + /* If the minutia point is in a block with INVALID direction ... */ + if(dmapval == INVALID_DIR) + /* Then, IGNORE the point. */ + return(IGNORE); + + /* If current minutia is in a HIGH CURVATURE block... */ + if(cmapval){ + /* Adjust location and direction locally. */ + if((ret = adjust_high_curvature_minutia_V2(&idir, &x_loc, &y_loc, + &x_edge, &y_edge, x_loc, y_loc, x_edge, y_edge, + bdata, iw, ih, plow_flow_map, minutiae, lfsparms))){ + /* Could be a system error or IGNORE minutia. */ + return(ret); + } + /* Otherwise, we have our high-curvature minutia attributes. */ + } + /* Otherwise, minutia is in fairly low-curvature block... */ + else{ + /* Get minutia direction based on current block's direction. */ + idir = get_low_curvature_direction(SCAN_VERTICAL, + feature_patterns[feature_id].appearing, dmapval, + lfsparms->num_directions); + } + + /* If current minutia is in a LOW RIDGE FLOW block ... */ + if(fmapval) + reliability = MEDIUM_RELIABILITY; + else + /* Otherwise, minutia is in a block with reliable direction and */ + /* binarization. */ + reliability = HIGH_RELIABILITY; + + /* Create a minutia object based on derived attributes. */ + if((ret = create_minutia(&minutia, x_loc, y_loc, x_edge, y_edge, idir, + reliability, + feature_patterns[feature_id].type, + feature_patterns[feature_id].appearing, feature_id))) + /* Return system error. */ + return(ret); + + /* Update the minutiae list with potential new minutia. */ + ret = update_minutiae_V2(minutiae, minutia, SCAN_VERTICAL, + dmapval, bdata, iw, ih, lfsparms); + + /* If minuitia IGNORED and not added to the minutia list ... */ + if(ret == IGNORE) + /* Deallocate the minutia. */ + free_minutia(minutia); + + /* Otherwise, return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: adjust_high_curvature_minutia - Takes an initial minutia point detected +#cat: in a high-curvature area and adjusts its location and +#cat: direction. First, it walks and extracts the contour +#cat: of the detected feature looking for and processing any loop +#cat: discovered along the way. Once the contour is extracted, +#cat: the point of highest-curvature is determined and used to +#cat: adjust the location of the minutia point. The angle of +#cat: the line perpendicular to the tangent on the high-curvature +#cat: contour at the minutia point is used as the mintutia's +#cat: direction. + + Input: + x_loc - starting x-pixel coord of feature (interior to feature) + y_loc - starting y-pixel coord of feature (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel + (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel + (exterior to feature) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + oidir - direction of adjusted minutia point + ox_loc - adjusted x-pixel coord of feature + oy_loc - adjusted y-pixel coord of feature + ox_edge - adjusted x-pixel coord of corresponding edge pixel + oy_edge - adjusted y-pixel coord of corresponding edge pixel + minutiae - points to a list of detected minutia structures + Return Code: + Zero - minutia point processed successfully + IGNORE - minutia point is to be ignored + Negative - system error +**************************************************************************/ +int adjust_high_curvature_minutia(int *oidir, int *ox_loc, int *oy_loc, + int *ox_edge, int *oy_edge, + const int x_loc, const int y_loc, + const int x_edge, const int y_edge, + unsigned char *bdata, const int iw, const int ih, + MINUTIAE *minutiae, const LFSPARMS *lfsparms) +{ + int ret; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int min_i; + double min_theta; + int feature_pix; + int mid_x, mid_y, mid_pix; + int idir; + int half_contour, angle_edge; + + /* Set variable from parameter structure. */ + half_contour = lfsparms->high_curve_half_contour; + + /* Set edge length for computing contour's angle of curvature */ + /* to one quarter of desired pixel length of entire contour. */ + /* Ex. If half_contour==14, then contour length==29=(2X14)+1 */ + /* and angle_edge==7=(14/2). */ + angle_edge = half_contour>>1; + + /* Get the pixel value of current feature. */ + feature_pix = *(bdata + (y_loc * iw) + x_loc); + + /* Extract feature's contour. */ + if((ret = get_high_curvature_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, half_contour, + x_loc, y_loc, x_edge, y_edge, bdata, iw, ih))){ + /* Returns with: */ + /* 1. Successful or empty contour == 0 */ + /* If contour is empty, then contour lists are not allocated. */ + /* 2. Contour forms loop == LOOP_FOUND */ + /* 3. Sysetm error < 0 */ + + /* If the contour forms a loop... */ + if(ret == LOOP_FOUND){ + + /* If the order of the contour is clockwise, then the loops's */ + /* contour pixels are outside the corresponding edge pixels. We */ + /* definitely do NOT want to fill based on the feature pixel in */ + /* this case, because it is OUTSIDE the loop. For now we will */ + /* ignore the loop and the minutia that triggered its tracing. */ + /* It is likely that other minutia on the loop will be */ + /* detected that create a contour on the "inside" of the loop. */ + /* There is another issue here that could be addressed ... */ + /* It seems that many/multiple minutia are often detected within */ + /* the same loop, which currently requires retracing the loop, */ + /* locating minutia on opposite ends of the major axis of the */ + /* loop, and then determining that the minutia have already been */ + /* entered into the minutiae list upon processing the very first */ + /* minutia detected in the loop. There is a lot of redundant */ + /* work being done here! */ + /* Is_loop_clockwise takes a default value to be returned if the */ + /* routine is unable to determine the direction of the contour. */ + /* In this case, we want to IGNORE the loop if we can't tell its */ + /* direction so that we do not inappropriately fill the loop, so */ + /* we are passing the default value TRUE. */ + if((ret = is_loop_clockwise(contour_x, contour_y, ncontour, TRUE))){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* If we had a system error... */ + if(ret < 0) + /* Return the error code. */ + return(ret); + /* Otherwise, loop is clockwise, so return IGNORE. */ + return(IGNORE); + } + + /* Otherwise, process the clockwise-ordered contour of the loop */ + /* as it may contain minutia. If no minutia found, then it is */ + /* filled in. */ + ret = process_loop(minutiae, contour_x, contour_y, + contour_ex, contour_ey, ncontour, + bdata, iw, ih, lfsparms); + /* Returns with: */ + /* 1. Successful processing of loop == 0 */ + /* 2. System error < 0 */ + + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /* If loop processed successfully ... */ + if(ret == 0) + /* Then either a minutia pair was extracted or the loop was */ + /* filled. Either way we want to IGNORE the minutia that */ + /* started the whole loop processing in the beginning. */ + return(IGNORE); + + /* Otherwise, there was a system error. */ + /* Return the resulting code. */ + return(ret); + } + + /* Otherwise not a loop, so get_high_curvature_contour incurred */ + /* a system error. Return the error code. */ + return(ret); + } + + /* If contour is empty ... then contour lists were not allocated, so */ + /* simply return IGNORE. The contour comes back empty when there */ + /* were not a sufficient number of points found on the contour. */ + if(ncontour == 0) + return(IGNORE); + + /* Otherwise, there are contour points to process. */ + + /* Given the contour, determine the point of highest curvature */ + /* (ie. forming the minimum angle between contour walls). */ + if((ret = min_contour_theta(&min_i, &min_theta, angle_edge, + contour_x, contour_y, ncontour))){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Returns IGNORE or system error. Either way */ + /* free the contour and return the code. */ + return(ret); + } + + /* If the minimum theta found along the contour is too large... */ + if(min_theta >= lfsparms->max_high_curve_theta){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Reject the high-curvature minutia, and return IGNORE. */ + return(IGNORE); + } + + /* Test to see if interior of curvature is OK. Compute midpoint */ + /* between left and right points symmetrically distant (angle_edge */ + /* pixels) from the contour's point of minimum theta. */ + mid_x = (contour_x[min_i-angle_edge] + contour_x[min_i+angle_edge])>>1; + mid_y = (contour_y[min_i-angle_edge] + contour_y[min_i+angle_edge])>>1; + mid_pix = *(bdata + (mid_y * iw) + mid_x); + /* If the interior pixel value is not the same as the feature's... */ + if(mid_pix != feature_pix){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Reject the high-curvature minutia and return IGNORE. */ + return(IGNORE); + } + + /* Compute new direction based on line connecting adjusted feature */ + /* location and the midpoint in the feature's interior. */ + idir = line2direction(contour_x[min_i], contour_y[min_i], + mid_x, mid_y, lfsparms->num_directions); + + /* Set minutia location to minimum theta position on the contour. */ + *oidir = idir; + *ox_loc = contour_x[min_i]; + *oy_loc = contour_y[min_i]; + *ox_edge = contour_ex[min_i]; + *oy_edge = contour_ey[min_i]; + + /* Deallocate contour buffers. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /*Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: adjust_high_curvature_minutia_V2 - Takes an initial minutia point +#cat: in a high-curvature area and adjusts its location and +#cat: direction. First, it walks and extracts the contour +#cat: of the detected feature looking for and processing any loop +#cat: discovered along the way. Once the contour is extracted, +#cat: the point of highest-curvature is determined and used to +#cat: adjust the location of the minutia point. The angle of +#cat: the line perpendicular to the tangent on the high-curvature +#cat: contour at the minutia point is used as the mintutia's +#cat: direction. + + Input: + x_loc - starting x-pixel coord of feature (interior to feature) + y_loc - starting y-pixel coord of feature (interior to feature) + x_edge - x-pixel coord of corresponding edge pixel + (exterior to feature) + y_edge - y-pixel coord of corresponding edge pixel + (exterior to feature) + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + plow_flow_map - pixelized Low Ridge Flow Map + lfsparms - parameters and thresholds for controlling LFS + Output: + oidir - direction of adjusted minutia point + ox_loc - adjusted x-pixel coord of feature + oy_loc - adjusted y-pixel coord of feature + ox_edge - adjusted x-pixel coord of corresponding edge pixel + oy_edge - adjusted y-pixel coord of corresponding edge pixel + minutiae - points to a list of detected minutia structures + Return Code: + Zero - minutia point processed successfully + IGNORE - minutia point is to be ignored + Negative - system error +**************************************************************************/ +int adjust_high_curvature_minutia_V2(int *oidir, int *ox_loc, int *oy_loc, + int *ox_edge, int *oy_edge, + const int x_loc, const int y_loc, + const int x_edge, const int y_edge, + unsigned char *bdata, const int iw, const int ih, + int *plow_flow_map, MINUTIAE *minutiae, const LFSPARMS *lfsparms) +{ + int ret; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int min_i; + double min_theta; + int feature_pix; + int mid_x, mid_y, mid_pix; + int idir; + int half_contour, angle_edge; + + /* Set variable from parameter structure. */ + half_contour = lfsparms->high_curve_half_contour; + + /* Set edge length for computing contour's angle of curvature */ + /* to one quarter of desired pixel length of entire contour. */ + /* Ex. If half_contour==14, then contour length==29=(2X14)+1 */ + /* and angle_edge==7=(14/2). */ + angle_edge = half_contour>>1; + + /* Get the pixel value of current feature. */ + feature_pix = *(bdata + (y_loc * iw) + x_loc); + + /* Extract feature's contour. */ + if((ret = get_high_curvature_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, half_contour, + x_loc, y_loc, x_edge, y_edge, bdata, iw, ih))){ + /* Returns with: */ + /* 1. Successful or empty contour == 0 */ + /* If contour is empty, then contour lists are not allocated. */ + /* 2. Contour forms loop == LOOP_FOUND */ + /* 3. Sysetm error < 0 */ + + /* If the contour forms a loop... */ + if(ret == LOOP_FOUND){ + + /* If the order of the contour is clockwise, then the loops's */ + /* contour pixels are outside the corresponding edge pixels. We */ + /* definitely do NOT want to fill based on the feature pixel in */ + /* this case, because it is OUTSIDE the loop. For now we will */ + /* ignore the loop and the minutia that triggered its tracing. */ + /* It is likely that other minutia on the loop will be */ + /* detected that create a contour on the "inside" of the loop. */ + /* There is another issue here that could be addressed ... */ + /* It seems that many/multiple minutia are often detected within */ + /* the same loop, which currently requires retracing the loop, */ + /* locating minutia on opposite ends of the major axis of the */ + /* loop, and then determining that the minutia have already been */ + /* entered into the minutiae list upon processing the very first */ + /* minutia detected in the loop. There is a lot of redundant */ + /* work being done here! */ + /* Is_loop_clockwise takes a default value to be returned if the */ + /* routine is unable to determine the direction of the contour. */ + /* In this case, we want to IGNORE the loop if we can't tell its */ + /* direction so that we do not inappropriately fill the loop, so */ + /* we are passing the default value TRUE. */ + if((ret = is_loop_clockwise(contour_x, contour_y, ncontour, TRUE))){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* If we had a system error... */ + if(ret < 0) + /* Return the error code. */ + return(ret); + /* Otherwise, loop is clockwise, so return IGNORE. */ + return(IGNORE); + } + + /* Otherwise, process the clockwise-ordered contour of the loop */ + /* as it may contain minutia. If no minutia found, then it is */ + /* filled in. */ + ret = process_loop_V2(minutiae, contour_x, contour_y, + contour_ex, contour_ey, ncontour, + bdata, iw, ih, plow_flow_map, lfsparms); + /* Returns with: */ + /* 1. Successful processing of loop == 0 */ + /* 2. System error < 0 */ + + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /* If loop processed successfully ... */ + if(ret == 0) + /* Then either a minutia pair was extracted or the loop was */ + /* filled. Either way we want to IGNORE the minutia that */ + /* started the whole loop processing in the beginning. */ + return(IGNORE); + + /* Otherwise, there was a system error. */ + /* Return the resulting code. */ + return(ret); + } + + /* Otherwise not a loop, so get_high_curvature_contour incurred */ + /* a system error. Return the error code. */ + return(ret); + } + + /* If contour is empty ... then contour lists were not allocated, so */ + /* simply return IGNORE. The contour comes back empty when there */ + /* were not a sufficient number of points found on the contour. */ + if(ncontour == 0) + return(IGNORE); + + /* Otherwise, there are contour points to process. */ + + /* Given the contour, determine the point of highest curvature */ + /* (ie. forming the minimum angle between contour walls). */ + if((ret = min_contour_theta(&min_i, &min_theta, angle_edge, + contour_x, contour_y, ncontour))){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Returns IGNORE or system error. Either way */ + /* free the contour and return the code. */ + return(ret); + } + + /* If the minimum theta found along the contour is too large... */ + if(min_theta >= lfsparms->max_high_curve_theta){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Reject the high-curvature minutia, and return IGNORE. */ + return(IGNORE); + } + + /* Test to see if interior of curvature is OK. Compute midpoint */ + /* between left and right points symmetrically distant (angle_edge */ + /* pixels) from the contour's point of minimum theta. */ + mid_x = (contour_x[min_i-angle_edge] + contour_x[min_i+angle_edge])>>1; + mid_y = (contour_y[min_i-angle_edge] + contour_y[min_i+angle_edge])>>1; + mid_pix = *(bdata + (mid_y * iw) + mid_x); + /* If the interior pixel value is not the same as the feature's... */ + if(mid_pix != feature_pix){ + /* Deallocate contour lists. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Reject the high-curvature minutia and return IGNORE. */ + return(IGNORE); + } + + /* Compute new direction based on line connecting adjusted feature */ + /* location and the midpoint in the feature's interior. */ + idir = line2direction(contour_x[min_i], contour_y[min_i], + mid_x, mid_y, lfsparms->num_directions); + + /* Set minutia location to minimum theta position on the contour. */ + *oidir = idir; + *ox_loc = contour_x[min_i]; + *oy_loc = contour_y[min_i]; + *ox_edge = contour_ex[min_i]; + *oy_edge = contour_ey[min_i]; + + /* Deallocate contour buffers. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /*Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: get_low_curvature_direction - Converts a bi-direcitonal IMAP direction +#cat: (based on a semi-circle) to a uni-directional value covering +#cat: a full circle based on the scan orientation used to detect +#cat: a minutia feature (horizontal or vertical) and whether the +#cat: detected minutia is appearing or disappearing. + + Input: + scan_dir - designates the feature scan orientation + appearing - designates the minutia as appearing or disappearing + imapval - IMAP block direction + ndirs - number of IMAP directions (in semicircle) + Return Code: + New direction - bi-directonal integer direction on full circle +*************************************************************************/ +int get_low_curvature_direction(const int scan_dir, const int appearing, + const int imapval, const int ndirs) +{ + int idir; + + /* Start direction out with IMAP value. */ + idir = imapval; + + /* NOTE! */ + /* The logic in this routine should hold whether for ridge endings */ + /* or for bifurcations. The examples in the comments show ridge */ + /* ending conditions only. */ + + /* CASE I : Ridge flow in Quadrant I; directions [0..8] */ + if(imapval <= (ndirs>>1)){ + /* I.A: HORIZONTAL scan */ + if(scan_dir == SCAN_HORIZONTAL){ + /* I.A.1: Appearing Minutia */ + if(appearing){ + /* Ex. 0 0 0 */ + /* 0 1 0 */ + /* ? ? */ + /* Ridge flow is up and to the right, whereas */ + /* actual ridge is running down and to the */ + /* left. */ + /* Thus: HORIZONTAL : appearing : should be */ + /* OPPOSITE the ridge flow direction. */ + idir += ndirs; + } + /* Otherwise: */ + /* I.A.2: Disappearing Minutia */ + /* Ex. ? ? */ + /* 0 1 0 */ + /* 0 0 0 */ + /* Ridge flow is up and to the right, which */ + /* should be SAME direction from which ridge */ + /* is projecting. */ + /* Thus: HORIZONTAL : disappearing : should */ + /* be the same as ridge flow direction. */ + } /* End if HORIZONTAL scan */ + /* Otherwise: */ + /* I.B: VERTICAL scan */ + else{ + /* I.B.1: Disappearing Minutia */ + if(!appearing){ + /* Ex. 0 0 */ + /* ? 1 0 */ + /* ? 0 0 */ + /* Ridge flow is up and to the right, whereas */ + /* actual ridge is projecting down and to the */ + /* left. */ + /* Thus: VERTICAL : disappearing : should be */ + /* OPPOSITE the ridge flow direction. */ + idir += ndirs; + } + /* Otherwise: */ + /* I.B.2: Appearing Minutia */ + /* Ex. 0 0 ? */ + /* 0 1 ? */ + /* 0 0 */ + /* Ridge flow is up and to the right, which */ + /* should be SAME direction the ridge is */ + /* running. */ + /* Thus: VERTICAL : appearing : should be */ + /* be the same as ridge flow direction. */ + } /* End else VERTICAL scan */ + } /* End if Quadrant I */ + + /* Otherwise: */ + /* CASE II : Ridge flow in Quadrant II; directions [9..15] */ + else{ + /* II.A: HORIZONTAL scan */ + if(scan_dir == SCAN_HORIZONTAL){ + /* II.A.1: Disappearing Minutia */ + if(!appearing){ + /* Ex. ? ? */ + /* 0 1 0 */ + /* 0 0 0 */ + /* Ridge flow is down and to the right, */ + /* whereas actual ridge is running up and to */ + /* the left. */ + /* Thus: HORIZONTAL : disappearing : should */ + /* be OPPOSITE the ridge flow direction.*/ + idir += ndirs; + } + /* Otherwise: */ + /* II.A.2: Appearing Minutia */ + /* Ex. 0 0 0 */ + /* 0 1 0 */ + /* ? ? */ + /* Ridge flow is down and to the right, which */ + /* should be same direction from which ridge */ + /* is projecting. */ + /* Thus: HORIZONTAL : appearing : should be */ + /* the SAME as ridge flow direction. */ + } /* End if HORIZONTAL scan */ + /* Otherwise: */ + /* II.B: VERTICAL scan */ + else{ + /* II.B.1: Disappearing Minutia */ + if(!appearing){ + /* Ex. ? 0 0 */ + /* ? 1 0 */ + /* 0 0 */ + /* Ridge flow is down and to the right, */ + /* whereas actual ridge is running up and to */ + /* the left. */ + /* Thus: VERTICAL : disappearing : should be */ + /* OPPOSITE the ridge flow direction. */ + idir += ndirs; + } + /* Otherwise: */ + /* II.B.2: Appearing Minutia */ + /* Ex. 0 0 */ + /* 0 1 ? */ + /* 0 0 ? */ + /* Ridge flow is down and to the right, which */ + /* should be same direction the ridge is */ + /* projecting. */ + /* Thus: VERTICAL : appearing : should be */ + /* be the SAME as ridge flow direction. */ + } /* End else VERTICAL scan */ + } /* End else Quadrant II */ + + /* Return resulting direction on range [0..31]. */ + return(idir); +} + diff --git a/libfprint/nbis/mindtct/morph.c b/libfprint/nbis/mindtct/morph.c new file mode 100644 index 0000000..b6be74f --- /dev/null +++ b/libfprint/nbis/mindtct/morph.c @@ -0,0 +1,226 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: MORPH.C + AUTHOR: Michael D. Garris + DATE: 10/04/1999 + UPDATED: 10/26/1999 by MDG + To avoid indisciminate erosion of pixels along + the edge of the binary image. + UPDATED: 03/16/2005 by MDG + + Contains general support image morphology routines required by + the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + erode_charimage_2() + dilate_charimage_2() + get_south8_2() + get_north8_2() + get_east8_2() + get_west8_2() + +***********************************************************************/ + +#include +#include + +/************************************************************************* +************************************************************************** +#cat: erode_charimage_2 - Erodes an 8-bit image by setting true pixels to zero +#cat: if any of their 4 neighbors is zero. Allocation of the +#cat: output image is the responsibility of the caller. The +#cat: input image remains unchanged. This routine will NOT +#cat: erode pixels indiscriminately along the image border. + + Input: + inp - input 8-bit image to be eroded + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + out - contains to the resulting eroded image +**************************************************************************/ +void erode_charimage_2(unsigned char *inp, unsigned char *out, + const int iw, const int ih) +{ + int row, col; + unsigned char *itr = inp, *otr = out; + + memcpy(out, inp, iw*ih); + + /* for true pixels. kill pixel if there is at least one false neighbor */ + for ( row = 0 ; row < ih ; row++ ) + for ( col = 0 ; col < iw ; col++ ) + { + if (*itr) /* erode only operates on true pixels */ + { + /* more efficient with C's left to right evaluation of */ + /* conjuctions. E N S functions not executed if W is false */ + if (!(get_west8_2 ((char *)itr, col , 1 ) && + get_east8_2 ((char *)itr, col, iw , 1 ) && + get_north8_2((char *)itr, row, iw , 1 ) && + get_south8_2((char *)itr, row, iw, ih, 1))) + *otr = 0; + } + itr++ ; otr++; + } +} + +/************************************************************************* +************************************************************************** +#cat: dilate_charimage_2 - Dilates an 8-bit image by setting false pixels to +#cat: one if any of their 4 neighbors is non-zero. Allocation +#cat: of the output image is the responsibility of the caller. +#cat: The input image remains unchanged. + + Input: + inp - input 8-bit image to be dilated + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + out - contains to the resulting dilated image +**************************************************************************/ +void dilate_charimage_2(unsigned char *inp, unsigned char *out, + const int iw, const int ih) +{ + int row, col; + unsigned char *itr = inp, *otr = out; + + memcpy(out, inp, iw*ih); + + /* for all pixels. set pixel if there is at least one true neighbor */ + for ( row = 0 ; row < ih ; row++ ) + for ( col = 0 ; col < iw ; col++ ) + { + if (!*itr) /* pixel is already true, neighbors irrelevant */ + { + /* more efficient with C's left to right evaluation of */ + /* conjuctions. E N S functions not executed if W is false */ + if (get_west8_2 ((char *)itr, col , 0) || + get_east8_2 ((char *)itr, col, iw , 0) || + get_north8_2((char *)itr, row, iw , 0) || + get_south8_2((char *)itr, row, iw, ih, 0)) + *otr = 1; + } + itr++ ; otr++; + } +} + +/************************************************************************* +************************************************************************** +#cat: get_south8_2 - Returns the value of the 8-bit image pixel 1 below the +#cat: current pixel if defined else it returns (char)0. + + Input: + ptr - points to current pixel in image + row - y-coord of current pixel + iw - width (in pixels) of image + ih - height (in pixels) of image + failcode - return value if desired pixel does not exist + Return Code: + Zero - if neighboring pixel is undefined + (outside of image boundaries) + Pixel - otherwise, value of neighboring pixel +**************************************************************************/ +char get_south8_2(char *ptr, const int row, const int iw, const int ih, + const int failcode) +{ + if (row >= ih-1) /* catch case where image is undefined southwards */ + return failcode; /* use plane geometry and return code. */ + + return *(ptr+iw); +} + +/************************************************************************* +************************************************************************** +#cat: get_north8_2 - Returns the value of the 8-bit image pixel 1 above the +#cat: current pixel if defined else it returns (char)0. + + Input: + ptr - points to current pixel in image + row - y-coord of current pixel + iw - width (in pixels) of image + failcode - return value if desired pixel does not exist + Return Code: + Zero - if neighboring pixel is undefined + (outside of image boundaries) + Pixel - otherwise, value of neighboring pixel +**************************************************************************/ +char get_north8_2(char *ptr, const int row, const int iw, + const int failcode) +{ + if (row < 1) /* catch case where image is undefined northwards */ + return failcode; /* use plane geometry and return code. */ + + return *(ptr-iw); +} + +/************************************************************************* +************************************************************************** +#cat: get_east8_2 - Returns the value of the 8-bit image pixel 1 right of the +#cat: current pixel if defined else it returns (char)0. + + Input: + ptr - points to current pixel in image + col - x-coord of current pixel + iw - width (in pixels) of image + failcode - return value if desired pixel does not exist + Return Code: + Zero - if neighboring pixel is undefined + (outside of image boundaries) + Pixel - otherwise, value of neighboring pixel +**************************************************************************/ +char get_east8_2(char *ptr, const int col, const int iw, + const int failcode) +{ + if (col >= iw-1) /* catch case where image is undefined eastwards */ + return failcode; /* use plane geometry and return code. */ + + return *(ptr+ 1); +} + +/************************************************************************* +************************************************************************** +#cat: get_west8_2 - Returns the value of the 8-bit image pixel 1 left of the +#cat: current pixel if defined else it returns (char)0. + + Input: + ptr - points to current pixel in image + col - x-coord of current pixel + failcode - return value if desired pixel does not exist + Return Code: + Zero - if neighboring pixel is undefined + (outside of image boundaries) + Pixel - otherwise, value of neighboring pixel +**************************************************************************/ +char get_west8_2(char *ptr, const int col, const int failcode) +{ + if (col < 1) /* catch case where image is undefined westwards */ + return failcode; /* use plane geometry and return code. */ + + return *(ptr- 1); +} diff --git a/libfprint/nbis/mindtct/mytime.c b/libfprint/nbis/mindtct/mytime.c new file mode 100644 index 0000000..296503a --- /dev/null +++ b/libfprint/nbis/mindtct/mytime.c @@ -0,0 +1,60 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: MYTIME.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + + Contains global variable definitions used to record timings by + the NIST Latent Fingerprint System (LFS). +***********************************************************************/ + +/* Total time: including all initializations */ +/* : excluding all I/O except for required HO39 input image */ +/* (This is done to contrast the fact that the NIST GENHO39 */ +/* eliminates the need for this extra read.) */ +unsigned long total_timer; +float total_time = 0.0; /* time accumulator */ + +/* IMAP generation time: excluding initialization */ +unsigned long imap_timer; +float imap_time = 0.0; /* time accumulator */ + +/* Binarization time: excluding initialization */ +unsigned long bin_timer; +float bin_time = 0.0; /* time accumulator */ + +/* Minutia Detection time */ +unsigned long minutia_timer; +float minutia_time = 0.0; /* time accumulator */ + +/* Minutia Removal time */ +unsigned long rm_minutia_timer; +float rm_minutia_time = 0.0; /* time accumulator */ + +/* Neighbor Ridge Counting time */ +unsigned long ridge_count_timer; +float ridge_count_time = 0.0; /* time accumulator */ diff --git a/libfprint/nbis/mindtct/quality.c b/libfprint/nbis/mindtct/quality.c new file mode 100644 index 0000000..834767f --- /dev/null +++ b/libfprint/nbis/mindtct/quality.c @@ -0,0 +1,467 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: QUALITY.C + AUTHOR: Michael D. Garris + DATE: 09/25/2000 + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for assessing minutia quality + and assigning different reliability measures. These routines + are primarily to support the rejection of bad minutiae. + +*********************************************************************** + ROUTINES: + gen_quality_map() + combined_minutia_quality() + grayscale_reliability() + get_neighborhood_stats() + reliability_fr_quality_map() + +***********************************************************************/ + +#include +#include +#include +#include +#include + +/*********************************************************************** +************************************************************************ +#cat: gen_quality_map - Takes a direction map, low contrast map, low ridge +#cat: flow map, and high curvature map, and combines them +#cat: into a single map containing 5 levels of decreasing +#cat: quality. This is done through a set of heuristics. + + Code originally written by Austin Hicklin for FBI ATU + Modified by Michael D. Garris (NIST) Sept. 1, 2000 + + Set quality of 0(unusable)..4(good) (I call these grades A..F) + 0/F: low contrast OR no direction + 1/D: low flow OR high curve + (with low contrast OR no direction neighbor) + (or within NEIGHBOR_DELTA of edge) + 2/C: low flow OR high curve + (or good quality with low contrast/no direction neighbor) + 3/B: good quality with low flow / high curve neighbor + 4/A: good quality (none of the above) + + Generally, the features in A/B quality are useful, the C/D quality + ones are not. + + Input: + direction_map - map with blocks assigned dominant ridge flow direction + low_contrast_map - map with blocks flagged as low contrast + low_flow_map - map with blocks flagged as low ridge flow + high_curve_map - map with blocks flagged as high curvature + map_w - width (in blocks) of the maps + map_h - height (in blocks) of the maps + Output: + oqmap - points to new quality map + Return Code: + Zero - successful completion + Negative - system error +************************************************************************/ +int gen_quality_map(int **oqmap, int *direction_map, int *low_contrast_map, + int *low_flow_map, int *high_curve_map, + const int map_w, const int map_h) +{ + + int *QualMap; + int thisX, thisY; + int compX, compY; + int arrayPos, arrayPos2; + int QualOffset; + + QualMap = (int *)malloc(map_w * map_h * sizeof(int)); + if(QualMap == (int *)NULL){ + fprintf(stderr, "ERROR : gen_quality_map : malloc : QualMap\n"); + return(-2); + } + + /* Foreach row of blocks in maps ... */ + for(thisY=0; thisY map_h - 1 - NEIGHBOR_DELTA || + thisX < NEIGHBOR_DELTA || thisX > map_w - 1 - NEIGHBOR_DELTA) + /* Set block's quality to 1/E. */ + QualMap[arrayPos]=1; + /* Otherwise, test neighboring blocks ... */ + else{ + /* Initialize quality adjustment to 0. */ + QualOffset=0; + /* Foreach row in neighborhood ... */ + for(compY=thisY-NEIGHBOR_DELTA; + compY<=thisY+NEIGHBOR_DELTA;compY++){ + /* Foreach block in neighborhood */ + /* (including current block)... */ + for(compX=thisX-NEIGHBOR_DELTA; + compX<=thisX+NEIGHBOR_DELTA;compX++) { + /* Compute neighboring block's index. */ + arrayPos2 = (compY*map_w)+compX; + /* If neighbor block (which might be itself) has */ + /* low contrast or INVALID direction .. */ + if(low_contrast_map[arrayPos2] || + direction_map[arrayPos2]<0) { + /* Set quality adjustment to -2. */ + QualOffset=-2; + /* Done with neighborhood row. */ + break; + } + /* Otherwise, if neighbor block (which might be */ + /* itself) has low flow or high curvature ... */ + else if(low_flow_map[arrayPos2] || + high_curve_map[arrayPos2]) { + /* Set quality to -1 if not already -2. */ + QualOffset=min(QualOffset,-1); + } + } + } + /* Decrement minutia quality by neighborhood adjustment. */ + QualMap[arrayPos]+=QualOffset; + } + } + } + } + + /* Set output pointer. */ + *oqmap = QualMap; + /* Return normally. */ + return(0); +} + +/*********************************************************************** +************************************************************************ +#cat: combined_minutia_quality - Combines quality measures derived from +#cat: the quality map and neighboring pixel statistics to +#cat: infer a reliability measure on the scale [0...1]. + + Input: + minutiae - structure contining the detected minutia + quality_map - map with blocks assigned 1 of 5 quality levels + map_w - width (in blocks) of the map + map_h - height (in blocks) of the map + blocksize - size (in pixels) of each block in the map + idata - 8-bit grayscale fingerprint image + iw - width (in pixels) of the image + ih - height (in pixels) of the image + id - depth (in pixels) of the image + ppmm - scan resolution of the image in pixels/mm + Output: + minutiae - updated reliability members + Return Code: + Zero - successful completion + Negative - system error +************************************************************************/ +int combined_minutia_quality(MINUTIAE *minutiae, + int *quality_map, const int mw, const int mh, const int blocksize, + unsigned char *idata, const int iw, const int ih, const int id, + const double ppmm) +{ + int ret, i, index, radius_pix; + int *pquality_map, qmap_value; + MINUTIA *minutia; + double gs_reliability, reliability; + + /* If image is not 8-bit grayscale ... */ + if(id != 8){ + fprintf(stderr, "ERROR : combined_miutia_quality : "); + fprintf(stderr, "image must pixel depth = %d must be 8 ", id); + fprintf(stderr, "to compute reliability\n"); + return(-2); + } + + /* Compute pixel radius of neighborhood based on image's scan resolution. */ + radius_pix = sround(RADIUS_MM * ppmm); + + /* Expand block map values to pixel map. */ + if((ret = pixelize_map(&pquality_map, iw, ih, + quality_map, mw, mh, blocksize))){ + return(ret); + } + + /* Foreach minutiae detected ... */ + for(i = 0; i < minutiae->num; i++){ + /* Assign minutia pointer. */ + minutia = minutiae->list[i]; + + /* Compute reliability from stdev and mean of pixel neighborhood. */ + gs_reliability = grayscale_reliability(minutia, + idata, iw, ih, radius_pix); + + /* Lookup quality map value. */ + /* Compute minutia pixel index. */ + index = (minutia->y * iw) + minutia->x; + /* Switch on pixel's quality value ... */ + qmap_value = pquality_map[index]; + + /* Combine grayscale reliability and quality map value. */ + switch(qmap_value){ + /* Quality A : [50..99]% */ + case 4 : + reliability = 0.50 + (0.49 * gs_reliability); + break; + /* Quality B : [25..49]% */ + case 3 : + reliability = 0.25 + (0.24 * gs_reliability); + break; + /* Quality C : [10..24]% */ + case 2 : + reliability = 0.10 + (0.14 * gs_reliability); + break; + /* Quality D : [5..9]% */ + case 1 : + reliability = 0.05 + (0.04 * gs_reliability); + break; + /* Quality E : 1% */ + case 0 : + reliability = 0.01; + break; + /* Error if quality value not in range [0..4]. */ + default: + fprintf(stderr, "ERROR : combined_miutia_quality : "); + fprintf(stderr, "unexpected quality map value %d ", qmap_value); + fprintf(stderr, "not in range [0..4]\n"); + free(pquality_map); + return(-3); + } + minutia->reliability = reliability; + } + + /* NEW 05-08-2002 */ + free(pquality_map); + + /* Return normally. */ + return(0); +} + + +/*********************************************************************** +************************************************************************ +#cat: grayscale_reliability - Given a minutia point, computes a reliability +#cat: measure from the stdev and mean of its pixel neighborhood. + + Code originally written by Austin Hicklin for FBI ATU + Modified by Michael D. Garris (NIST) Sept. 25, 2000 + + GrayScaleReliability - reasonable reliability heuristic, returns + 0.0 .. 1.0 based on stdev and Mean of a localized histogram where + "ideal" stdev is >=64; "ideal" Mean is 127. In a 1 ridge radius + (11 pixels), if the bytevalue (shade of gray) in the image has a + stdev of >= 64 & a mean of 127, returns 1.0 (well defined + light & dark areas in equal proportions). + + Input: + minutia - structure containing detected minutia + idata - 8-bit grayscale fingerprint image + iw - width (in pixels) of the image + ih - height (in pixels) of the image + radius_pix - pixel radius of surrounding neighborhood + Return Value: + reliability - computed reliability measure +************************************************************************/ +double grayscale_reliability(MINUTIA *minutia, unsigned char *idata, + const int iw, const int ih, const int radius_pix) +{ + double mean, stdev; + double reliability; + + get_neighborhood_stats(&mean, &stdev, minutia, idata, iw, ih, radius_pix); + + reliability = min((stdev>IDEALSTDEV ? 1.0 : stdev/(double)IDEALSTDEV), + (1.0-(fabs(mean-IDEALMEAN)/(double)IDEALMEAN))); + + return(reliability); +} + + +/*********************************************************************** +************************************************************************ +#cat: get_neighborhood_stats - Given a minutia point, computes the mean +#cat: and stdev of the 8-bit grayscale pixels values in a +#cat: surrounding neighborhood with specified radius. + + Code originally written by Austin Hicklin for FBI ATU + Modified by Michael D. Garris (NIST) Sept. 25, 2000 + + Input: + minutia - structure containing detected minutia + idata - 8-bit grayscale fingerprint image + iw - width (in pixels) of the image + ih - height (in pixels) of the image + radius_pix - pixel radius of surrounding neighborhood + Output: + mean - mean of neighboring pixels + stdev - standard deviation of neighboring pixels +************************************************************************/ +void get_neighborhood_stats(double *mean, double *stdev, MINUTIA *minutia, + unsigned char *idata, const int iw, const int ih, + const int radius_pix) +{ + int i, x, y, rows, cols; + int n = 0, sumX = 0, sumXX = 0; + int histogram[256]; + + /* Zero out histogram. */ + memset(histogram, 0, 256 * sizeof(int)); + + /* Set minutia's coordinate variables. */ + x = minutia->x; + y = minutia->y; + + + /* If minutiae point is within sampleboxsize distance of image border, */ + /* a value of 0 reliability is returned. */ + if ((x < radius_pix) || (x > iw-radius_pix-1) || + (y < radius_pix) || (y > ih-radius_pix-1)) { + *mean = 0.0; + *stdev = 0.0; + return; + + } + + /* Foreach row in neighborhood ... */ + for(rows = y - radius_pix; + rows <= y + radius_pix; + rows++){ + /* Foreach column in neighborhood ... */ + for(cols = x - radius_pix; + cols <= x + radius_pix; + cols++){ + /* Bump neighbor's pixel value bin in histogram. */ + histogram[*(idata+(rows * iw)+cols)]++; + } + } + + /* Foreach grayscale pixel bin ... */ + for(i = 0; i < 256; i++){ + if(histogram[i]){ + /* Accumulate Sum(X[i]) */ + sumX += (i * histogram[i]); + /* Accumulate Sum(X[i]^2) */ + sumXX += (i * i * histogram[i]); + /* Accumulate N samples */ + n += histogram[i]; + } + } + + /* Mean = Sum(X[i])/N */ + *mean = sumX/(double)n; + /* Stdev = sqrt((Sum(X[i]^2)/N) - Mean^2) */ + *stdev = sqrt((sumXX/(double)n) - ((*mean)*(*mean))); +} + +/*********************************************************************** +************************************************************************ +#cat: reliability_fr_quality_map - Takes a set of minutiae and assigns +#cat: each one a reliability measure based on 1 of 5 possible +#cat: quality levels from its location in a quality map. + + Input: + minutiae - structure contining the detected minutia + quality_map - map with blocks assigned 1 of 5 quality levels + map_w - width (in blocks) of the map + map_h - height (in blocks) of the map + blocksize - size (in pixels) of each block in the map + Output: + minutiae - updated reliability members + Return Code: + Zero - successful completion + Negative - system error +************************************************************************/ +int reliability_fr_quality_map(MINUTIAE *minutiae, + int *quality_map, const int mw, const int mh, + const int iw, const int ih, const int blocksize) +{ + int ret, i, index; + int *pquality_map; + MINUTIA *minutia; + + /* Expand block map values to pixel map. */ + if((ret = pixelize_map(&pquality_map, iw, ih, + quality_map, mw, mh, blocksize))){ + return(ret); + } + + /* Foreach minutiae detected ... */ + for(i = 0; i < minutiae->num; i++){ + /* Assign minutia pointer. */ + minutia = minutiae->list[i]; + /* Compute minutia pixel index. */ + index = (minutia->y * iw) + minutia->x; + /* Switch on pixel's quality value ... */ + switch(pquality_map[index]){ + case 0: + minutia->reliability = 0.0; + break; + case 1: + minutia->reliability = 0.25; + break; + case 2: + minutia->reliability = 0.50; + break; + case 3: + minutia->reliability = 0.75; + break; + case 4: + minutia->reliability = 0.99; + break; + /* Error if quality value not in range [0..4]. */ + default: + fprintf(stderr, "ERROR : reliability_fr_quality_map :"); + fprintf(stderr, "unexpected quality value %d ", + pquality_map[index]); + fprintf(stderr, "not in range [0..4]\n"); + return(-2); + } + } + + /* Deallocate pixelized quality map. */ + free(pquality_map); + + /* Return normally. */ + return(0); +} diff --git a/libfprint/nbis/mindtct/remove.c b/libfprint/nbis/mindtct/remove.c new file mode 100644 index 0000000..e3ddbef --- /dev/null +++ b/libfprint/nbis/mindtct/remove.c @@ -0,0 +1,3404 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: REMOVE.C + AUTHOR: Michael D. Garris + DATE: 08/02/1999 + UPDATED: 10/04/1999 Version 2 by MDG + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for detecting and removing false + minutiae as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + remove_false_minutia() + remove_false_minutia_V2() + remove_holes() + remove_hooks() + remove_hooks_islands_overlaps() + remove_islands_and_lakes() + remove_malformations() + remove_near_invblocks() + remove_near_invblocks_V2() + remove_pointing_invblock() + remove_pointing_invblock_V2() + remove_overlaps() + remove_pores() + remove_pores_V2() + remove_or_adjust_side_minutiae() + remove_or_adjust_side_minutiae_V2() + +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: remove_false_minutia - Takes a list of true and false minutiae and +#cat: attempts to detect and remove the false minutiae based +#cat: on a series of tests. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + nmap - IMAP ridge flow matrix with invalid, high-curvature, + and no-valid-neighbor regions identified + mw - width in blocks of the NMAP + mh - height in blocks of the NMAP + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_false_minutia(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *nmap, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int ret; + + /* Sort minutiae points top-to-bottom and left-to-right. */ + if((ret = sort_minutiae_y_x(minutiae, iw, ih))){ + return(ret); + } + + if((ret = link_minutiae(minutiae, bdata, iw, ih, nmap, mw, mh, lfsparms))){ + return(ret); + } + + if((ret = remove_hooks_islands_lakes_overlaps(minutiae, bdata, iw, ih, + lfsparms))){ + return(ret); + } + + if((ret = remove_pointing_invblock(minutiae, nmap, mw, mh, lfsparms))){ + return(ret); + } + + if((ret = remove_holes(minutiae, bdata, iw, ih, lfsparms))){ + return(ret); + } + + if((ret = remove_or_adjust_side_minutiae(minutiae, + bdata, iw, ih, lfsparms))){ + return(ret); + } + + if((ret = remove_near_invblock(minutiae, nmap, mw, mh, lfsparms))){ + return(ret); + } + + if((ret = remove_pores(minutiae, bdata, iw, ih, nmap, mw, mh, lfsparms))){ + return(ret); + } + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_false_minutia_V2 - Takes a list of true and false minutiae and +#cat: attempts to detect and remove the false minutiae based +#cat: on a series of tests. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + direction_map - map of image blocks containing directional ridge flow + low_flow_map - map of image blocks flagged as LOW RIDGE FLOW + high_curve_map - map of image blocks flagged as HIGH CURVATURE + mw - width in blocks of the maps + mh - height in blocks of the maps + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_false_minutia_V2(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *direction_map, int *low_flow_map, int *high_curve_map, + const int mw, const int mh, const LFSPARMS *lfsparms) +{ + int ret; + + /* 1. Sort minutiae points top-to-bottom and left-to-right. */ + if((ret = sort_minutiae_y_x(minutiae, iw, ih))){ + return(ret); + } + + /* 2. Remove minutiae on lakes (filled with white pixels) and */ + /* islands (filled with black pixels), both defined by a pair of */ + /* minutia points. */ + if((ret = remove_islands_and_lakes(minutiae, bdata, iw, ih, lfsparms))){ + return(ret); + } + + /* 3. Remove minutiae on holes in the binary image defined by a */ + /* single point. */ + if((ret = remove_holes(minutiae, bdata, iw, ih, lfsparms))){ + return(ret); + } + + /* 4. Remove minutiae that point sufficiently close to a block with */ + /* INVALID direction. */ + if((ret = remove_pointing_invblock_V2(minutiae, direction_map, mw, mh, + lfsparms))){ + return(ret); + } + + /* 5. Remove minutiae that are sufficiently close to a block with */ + /* INVALID direction. */ + if((ret = remove_near_invblock_V2(minutiae, direction_map, mw, mh, + lfsparms))){ + return(ret); + } + + /* 6. Remove or adjust minutiae that reside on the side of a ridge */ + /* or valley. */ + if((ret = remove_or_adjust_side_minutiae_V2(minutiae, bdata, iw, ih, + direction_map, mw, mh, lfsparms))){ + return(ret); + } + + /* 7. Remove minutiae that form a hook on the side of a ridge or valley. */ + if((ret = remove_hooks(minutiae, bdata, iw, ih, lfsparms))){ + return(ret); + } + + /* 8. Remove minutiae that are on opposite sides of an overlap. */ + if((ret = remove_overlaps(minutiae, bdata, iw, ih, lfsparms))){ + return(ret); + } + + /* 9. Remove minutiae that are "irregularly" shaped. */ + if((ret = remove_malformations(minutiae, bdata, iw, ih, + low_flow_map, mw, mh, lfsparms))){ + return(ret); + } + + /* 10. Remove minutiae that form long, narrow, loops in the */ + /* "unreliable" regions in the binary image. */ + if((ret = remove_pores_V2(minutiae, bdata, iw, ih, + direction_map, low_flow_map, high_curve_map, + mw, mh, lfsparms))){ + return(ret); + } + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_holes - Removes minutia points on small loops around valleys. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_holes(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int i, ret; + MINUTIA *minutia; + + print2log("\nREMOVING HOLES:\n"); + + i = 0; + /* Foreach minutia remaining in list ... */ + while(i < minutiae->num){ + /* Assign a temporary pointer. */ + minutia = minutiae->list[i]; + /* If current minutia is a bifurcation ... */ + if(minutia->type == BIFURCATION){ + /* Check to see if it is on a loop of specified length (ex. 15). */ + ret = on_loop(minutia, lfsparms->small_loop_len, bdata, iw, ih); + /* If minutia is on a loop ... or loop test IGNORED */ + if((ret == LOOP_FOUND) || (ret == IGNORE)){ + + print2log("%d,%d RM\n", minutia->x, minutia->y); + + /* Then remove the minutia from list. */ + if((ret = remove_minutia(i, minutiae))){ + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + } + /* If the minutia is NOT on a loop... */ + else if (ret == FALSE){ + /* Simply advance to next minutia in the list. */ + i++; + } + /* Otherwise, an ERROR occurred while looking for loop. */ + else{ + /* Return error code. */ + return(ret); + } + } + /* Otherwise, the current minutia is a ridge-ending... */ + else{ + /* Advance to next minutia in the list. */ + i++; + } + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_hooks - Takes a list of true and false minutiae and +#cat: attempts to detect and remove those false minutiae that +#cat: are on a hook (white or black). + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_hooks(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int *to_remove; + int i, f, s, ret; + int delta_y, full_ndirs, qtr_ndirs, deltadir, min_deltadir; + MINUTIA *minutia1, *minutia2; + double dist; + + print2log("\nREMOVING HOOKS:\n"); + + /* Allocate list of minutia indices that upon completion of testing */ + /* should be removed from the minutiae lists. Note: That using */ + /* "calloc" initializes the list to FALSE. */ + to_remove = (int *)calloc(minutiae->num, sizeof(int)); + if(to_remove == (int *)NULL){ + fprintf(stderr, "ERROR : remove_hooks : calloc : to_remove\n"); + return(-640); + } + + /* Compute number directions in full circle. */ + full_ndirs = lfsparms->num_directions<<1; + /* Compute number of directions in 45=(180/4) degrees. */ + qtr_ndirs = lfsparms->num_directions>>2; + + /* Minimum allowable deltadir to consider joining minutia. */ + /* (The closer the deltadir is to 180 degrees, the more likely the join. */ + /* When ndirs==16, then this value is 11=(3*4)-1 == 123.75 degrees. */ + /* I chose to parameterize this threshold based on a fixed fraction of */ + /* 'ndirs' rather than on passing in a parameter in degrees and doing */ + /* the conversion. I doubt the difference matters. */ + min_deltadir = (3 * qtr_ndirs) - 1; + + f = 0; + /* Foreach primary (first) minutia (except for last one in list) ... */ + while(f < minutiae->num-1){ + + /* If current first minutia not previously set to be removed. */ + if(!to_remove[f]){ + + print2log("\n"); + + /* Set first minutia to temporary pointer. */ + minutia1 = minutiae->list[f]; + /* Foreach secondary (second) minutia to right of first minutia ... */ + s = f+1; + while(s < minutiae->num){ + /* Set second minutia to temporary pointer. */ + minutia2 = minutiae->list[s]; + + print2log("1:%d(%d,%d)%d 2:%d(%d,%d)%d ", + f, minutia1->x, minutia1->y, minutia1->type, + s, minutia2->x, minutia2->y, minutia2->type); + + /* The binary image is potentially being edited during each */ + /* iteration of the secondary minutia loop, therefore */ + /* minutia pixel values may be changed. We need to catch */ + /* these events by using the next 2 tests. */ + + /* If the first minutia's pixel has been previously changed... */ + if(*(bdata+(minutia1->y*iw)+minutia1->x) != minutia1->type){ + print2log("\n"); + /* Then break out of secondary loop and skip to next first. */ + break; + } + + /* If the second minutia's pixel has been previously changed... */ + if(*(bdata+(minutia2->y*iw)+minutia2->x) != minutia2->type) + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + + /* If the second minutia not previously set to be removed. */ + if(!to_remove[s]){ + + /* Compute delta y between 1st & 2nd minutiae and test. */ + delta_y = minutia2->y - minutia1->y; + /* If delta y small enough (ex. < 8 pixels) ... */ + if(delta_y <= lfsparms->max_rmtest_dist){ + + print2log("1DY "); + + /* Compute Euclidean distance between 1st & 2nd mintuae. */ + dist = distance(minutia1->x, minutia1->y, + minutia2->x, minutia2->y); + /* If distance is NOT too large (ex. < 8 pixels) ... */ + if(dist <= lfsparms->max_rmtest_dist){ + + print2log("2DS "); + + /* Compute "inner" difference between directions on */ + /* a full circle and test. */ + if((deltadir = closest_dir_dist(minutia1->direction, + minutia2->direction, full_ndirs)) == + INVALID_DIR){ + free(to_remove); + fprintf(stderr, + "ERROR : remove_hooks : INVALID direction\n"); + return(-641); + } + /* If the difference between dirs is large enough ... */ + /* (the more 1st & 2nd point away from each other the */ + /* more likely they should be joined) */ + if(deltadir > min_deltadir){ + + print2log("3DD "); + + /* If 1st & 2nd minutiae are NOT same type ... */ + if(minutia1->type != minutia2->type){ + /* Check to see if pair on a hook with contour */ + /* of specified length (ex. 15 pixels) ... */ + + ret = on_hook(minutia1, minutia2, + lfsparms->max_hook_len, + bdata, iw, ih); + + /* If hook detected between pair ... */ + if(ret == HOOK_FOUND){ + + print2log("4HK RM\n"); + + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + } + /* If hook test IGNORED ... */ + else if (ret == IGNORE){ + + print2log("RM\n"); + + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Skip to next 1st minutia by breaking out of */ + /* inner secondary loop. */ + break; + } + /* If system error occurred during hook test ... */ + else if (ret < 0){ + free(to_remove); + return(ret); + } + /* Otherwise, no hook found, so skip to next */ + /* second minutia. */ + else + print2log("\n"); + } + else + print2log("\n"); + /* End different type test. */ + }/* End deltadir test. */ + else + print2log("\n"); + }/* End distance test. */ + else + print2log("\n"); + } + /* Otherwise, current 2nd too far below 1st, so skip to next */ + /* 1st minutia. */ + else{ + + print2log("\n"); + + /* Break out of inner secondary loop. */ + break; + }/* End delta-y test. */ + + }/* End if !to_remove[s] */ + else + print2log("\n"); + + /* Bump to next second minutia in minutiae list. */ + s++; + }/* End secondary minutiae loop. */ + + }/* Otherwise, first minutia already flagged to be removed. */ + + /* Bump to next first minutia in minutiae list. */ + f++; + }/* End primary minutiae loop. */ + + /* Now remove all minutiae in list that have been flagged for removal. */ + /* NOTE: Need to remove the minutia from their lists in reverse */ + /* order, otherwise, indices will be off. */ + for(i = minutiae->num-1; i >= 0; i--){ + /* If the current minutia index is flagged for removal ... */ + if(to_remove[i]){ + /* Remove the minutia from the minutiae list. */ + if((ret = remove_minutia(i, minutiae))){ + free(to_remove); + return(ret); + } + } + } + + /* Deallocate flag list. */ + free(to_remove); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_hooks_islands_lakes_overlaps - Removes minutia points on hooks, +#cat: islands, lakes, and overlaps and fills in small small +#cat: loops in the binary image and joins minutia features in +#cat: the image on opposite sides of an overlap. So, this +#cat: routine not only prunes minutia points but it edits the +#cat: binary input image as well. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + bdata - edited binary image with loops filled and overlaps removed + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_hooks_islands_lakes_overlaps(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int *to_remove; + int i, f, s, ret; + int delta_y, full_ndirs, qtr_ndirs, deltadir, min_deltadir; + int *loop_x, *loop_y, *loop_ex, *loop_ey, nloop; + MINUTIA *minutia1, *minutia2; + double dist; + + print2log("\nREMOVING HOOKS, ISLANDS, LAKES, AND OVERLAPS:\n"); + + /* Allocate list of minutia indices that upon completion of testing */ + /* should be removed from the minutiae lists. Note: That using */ + /* "calloc" initializes the list to FALSE. */ + to_remove = (int *)calloc(minutiae->num, sizeof(int)); + if(to_remove == (int *)NULL){ + fprintf(stderr, + "ERROR : remove_hooks_islands_lakes_overlaps : calloc : to_remove\n"); + return(-300); + } + + /* Compute number directions in full circle. */ + full_ndirs = lfsparms->num_directions<<1; + /* Compute number of directions in 45=(180/4) degrees. */ + qtr_ndirs = lfsparms->num_directions>>2; + + /* Minimum allowable deltadir to consider joining minutia. */ + /* (The closer the deltadir is to 180 degrees, the more likely the join. */ + /* When ndirs==16, then this value is 11=(3*4)-1 == 123.75 degrees. */ + /* I chose to parameterize this threshold based on a fixed fraction of */ + /* 'ndirs' rather than on passing in a parameter in degrees and doing */ + /* the conversion. I doubt the difference matters. */ + min_deltadir = (3 * qtr_ndirs) - 1; + + f = 0; + /* Foreach primary (first) minutia (except for last one in list) ... */ + while(f < minutiae->num-1){ + + /* If current first minutia not previously set to be removed. */ + if(!to_remove[f]){ + + print2log("\n"); + + /* Set first minutia to temporary pointer. */ + minutia1 = minutiae->list[f]; + /* Foreach secondary (second) minutia to right of first minutia ... */ + s = f+1; + while(s < minutiae->num){ + /* Set second minutia to temporary pointer. */ + minutia2 = minutiae->list[s]; + + print2log("1:%d(%d,%d)%d 2:%d(%d,%d)%d ", + f, minutia1->x, minutia1->y, minutia1->type, + s, minutia2->x, minutia2->y, minutia2->type); + + /* The binary image is potentially being edited during each */ + /* iteration of the secondary minutia loop, therefore */ + /* minutia pixel values may be changed. We need to catch */ + /* these events by using the next 2 tests. */ + + /* If the first minutia's pixel has been previously changed... */ + if(*(bdata+(minutia1->y*iw)+minutia1->x) != minutia1->type){ + print2log("\n"); + /* Then break out of secondary loop and skip to next first. */ + break; + } + + /* If the second minutia's pixel has been previously changed... */ + if(*(bdata+(minutia2->y*iw)+minutia2->x) != minutia2->type) + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + + /* If the second minutia not previously set to be removed. */ + if(!to_remove[s]){ + + /* Compute delta y between 1st & 2nd minutiae and test. */ + delta_y = minutia2->y - minutia1->y; + /* If delta y small enough (ex. < 8 pixels) ... */ + if(delta_y <= lfsparms->max_rmtest_dist){ + + print2log("1DY "); + + /* Compute Euclidean distance between 1st & 2nd mintuae. */ + dist = distance(minutia1->x, minutia1->y, + minutia2->x, minutia2->y); + /* If distance is NOT too large (ex. < 8 pixels) ... */ + if(dist <= lfsparms->max_rmtest_dist){ + + print2log("2DS "); + + /* Compute "inner" difference between directions on */ + /* a full circle and test. */ + if((deltadir = closest_dir_dist(minutia1->direction, + minutia2->direction, full_ndirs)) == + INVALID_DIR){ + free(to_remove); + fprintf(stderr, + "ERROR : remove_hooks_islands_lakes_overlaps : INVALID direction\n"); + return(-301); + } + /* If the difference between dirs is large enough ... */ + /* (the more 1st & 2nd point away from each other the */ + /* more likely they should be joined) */ + if(deltadir > min_deltadir){ + + print2log("3DD "); + + /* If 1st & 2nd minutiae are NOT same type ... */ + if(minutia1->type != minutia2->type){ + /* Check to see if pair on a hook with contour */ + /* of specified length (ex. 15 pixels) ... */ + ret = on_hook(minutia1, minutia2, + lfsparms->max_hook_len, + bdata, iw, ih); + /* If hook detected between pair ... */ + if(ret == HOOK_FOUND){ + + print2log("4HK RM\n"); + + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + } + /* If hook test IGNORED ... */ + else if (ret == IGNORE){ + + print2log("RM\n"); + + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Skip to next 1st minutia by breaking out of */ + /* inner secondary loop. */ + break; + } + /* If system error occurred during hook test ... */ + else if (ret < 0){ + free(to_remove); + return(ret); + } + /* Otherwise, no hook found, so skip to next */ + /* second minutia. */ + else + print2log("\n"); + } + /* Otherwise, pair is the same type, so test to see */ + /* if both are on an island or lake. */ + else{ + /* Check to see if pair on a loop of specified */ + /* half length (ex. 15 pixels) ... */ + ret = on_island_lake(&loop_x, &loop_y, + &loop_ex, &loop_ey, &nloop, + minutia1, minutia2, + lfsparms->max_half_loop, + bdata, iw, ih); + /* If pair is on island/lake ... */ + if(ret == LOOP_FOUND){ + + print2log("4IL RM\n"); + + /* Fill the loop. */ + if((ret = fill_loop(loop_x, loop_y, nloop, + bdata, iw, ih))){ + free_contour(loop_x, loop_y, + loop_ex, loop_ey); + free(to_remove); + return(ret); + } + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + /* Deallocate loop contour. */ + free_contour(loop_x, loop_y, loop_ex, loop_ey); + } + /* If island/lake test IGNORED ... */ + else if (ret == IGNORE){ + + print2log("RM\n"); + + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Skip to next 1st minutia by breaking out of */ + /* inner secondary loop. */ + break; + } + /* If ERROR while looking for island/lake ... */ + else if (ret < 0){ + free(to_remove); + return(ret); + } + /* Otherwise, minutia pair not on island/lake, */ + /* but might be on an overlap. */ + else { + /* If free path exists between pair ... */ + if(free_path(minutia1->x, minutia1->y, + minutia2->x, minutia2->y, + bdata, iw, ih, lfsparms)){ + + print2log("4OV RM\n"); + + /* Then assume overlap, so ... */ + /* Join first and second minutiae in image. */ + if((ret = join_minutia(minutia1, minutia2, + bdata, iw, ih, NO_BOUNDARY, + JOIN_LINE_RADIUS))){ + free(to_remove); + return(ret); + } + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + } + /* Otherwise, pair not on an overlap, so skip */ + /* to next second minutia. */ + else + print2log("\n"); + }/* End overlap test. */ + }/* End same type tests (island/lake & overlap). */ + }/* End deltadir test. */ + else + print2log("\n"); + }/* End distance test. */ + else + print2log("\n"); + } + /* Otherwise, current 2nd too far below 1st, so skip to next */ + /* 1st minutia. */ + else{ + + print2log("\n"); + + /* Break out of inner secondary loop. */ + break; + }/* End delta-y test. */ + + }/* End if !to_remove[s] */ + else + print2log("\n"); + + /* Bump to next second minutia in minutiae list. */ + s++; + }/* End secondary minutiae loop. */ + + }/* Otherwise, first minutia already flagged to be removed. */ + + /* Bump to next first minutia in minutiae list. */ + f++; + }/* End primary minutiae loop. */ + + /* Now remove all minutiae in list that have been flagged for removal. */ + /* NOTE: Need to remove the minutia from their lists in reverse */ + /* order, otherwise, indices will be off. */ + for(i = minutiae->num-1; i >= 0; i--){ + /* If the current minutia index is flagged for removal ... */ + if(to_remove[i]){ + /* Remove the minutia from the minutiae list. */ + if((ret = remove_minutia(i, minutiae))){ + free(to_remove); + return(ret); + } + } + } + + /* Deallocate flag list. */ + free(to_remove); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_islands_and_lakes - Takes a list of true and false minutiae and +#cat: attempts to detect and remove those false minutiae that +#cat: are either on a common island (filled with black pixels) +#cat: or a lake (filled with white pixels). +#cat: Note that this routine edits the binary image by filling +#cat: detected lakes or islands. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_islands_and_lakes(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int *to_remove; + int i, f, s, ret; + int delta_y, full_ndirs, qtr_ndirs, deltadir, min_deltadir; + int *loop_x, *loop_y, *loop_ex, *loop_ey, nloop; + MINUTIA *minutia1, *minutia2; + double dist; + int dist_thresh, half_loop; + + print2log("\nREMOVING ISLANDS AND LAKES:\n"); + + dist_thresh = lfsparms->max_rmtest_dist; + half_loop = lfsparms->max_half_loop; + + /* Allocate list of minutia indices that upon completion of testing */ + /* should be removed from the minutiae lists. Note: That using */ + /* "calloc" initializes the list to FALSE. */ + to_remove = (int *)calloc(minutiae->num, sizeof(int)); + if(to_remove == (int *)NULL){ + fprintf(stderr, + "ERROR : remove_islands_and_lakes : calloc : to_remove\n"); + return(-610); + } + + /* Compute number directions in full circle. */ + full_ndirs = lfsparms->num_directions<<1; + /* Compute number of directions in 45=(180/4) degrees. */ + qtr_ndirs = lfsparms->num_directions>>2; + + /* Minimum allowable deltadir to consider joining minutia. */ + /* (The closer the deltadir is to 180 degrees, the more likely the join. */ + /* When ndirs==16, then this value is 11=(3*4)-1 == 123.75 degrees. */ + /* I chose to parameterize this threshold based on a fixed fraction of */ + /* 'ndirs' rather than on passing in a parameter in degrees and doing */ + /* the conversion. I doubt the difference matters. */ + min_deltadir = (3 * qtr_ndirs) - 1; + + /* Foreach primary (first) minutia (except for last one in list) ... */ + f = 0; + while(f < minutiae->num-1){ + + /* If current first minutia not previously set to be removed. */ + if(!to_remove[f]){ + + print2log("\n"); + + /* Set first minutia to temporary pointer. */ + minutia1 = minutiae->list[f]; + + /* Foreach secondary minutia to right of first minutia ... */ + s = f+1; + while(s < minutiae->num){ + /* Set second minutia to temporary pointer. */ + minutia2 = minutiae->list[s]; + + /* If the secondary minutia is desired type ... */ + if(minutia2->type == minutia1->type){ + + print2log("1:%d(%d,%d)%d 2:%d(%d,%d)%d ", + f, minutia1->x, minutia1->y, minutia1->type, + s, minutia2->x, minutia2->y, minutia2->type); + + /* The binary image is potentially being edited during */ + /* each iteration of the secondary minutia loop, */ + /* therefore minutia pixel values may be changed. We */ + /* need to catch these events by using the next 2 tests. */ + + /* If the first minutia's pixel has been previously */ + /* changed... */ + if(*(bdata+(minutia1->y*iw)+minutia1->x) != minutia1->type){ + print2log("\n"); + /* Then break out of secondary loop and skip to next */ + /* first. */ + break; + } + + /* If the second minutia's pixel has been previously */ + /* changed... */ + if(*(bdata+(minutia2->y*iw)+minutia2->x) != minutia2->type) + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + + /* If the second minutia not previously set to be removed. */ + if(!to_remove[s]){ + + /* Compute delta y between 1st & 2nd minutiae and test. */ + delta_y = minutia2->y - minutia1->y; + /* If delta y small enough (ex. <16 pixels)... */ + if(delta_y <= dist_thresh){ + + print2log("1DY "); + + /* Compute Euclidean distance between 1st & 2nd */ + /* mintuae. */ + dist = distance(minutia1->x, minutia1->y, + minutia2->x, minutia2->y); + + /* If distance is NOT too large (ex. <16 pixels)... */ + if(dist <= dist_thresh){ + + print2log("2DS "); + + /* Compute "inner" difference between directions */ + /* on a full circle and test. */ + if((deltadir = closest_dir_dist(minutia1->direction, + minutia2->direction, full_ndirs)) == + INVALID_DIR){ + free(to_remove); + fprintf(stderr, + "ERROR : remove_islands_and_lakes : INVALID direction\n"); + return(-611); + } + /* If the difference between dirs is large */ + /* enough ... */ + /* (the more 1st & 2nd point away from each */ + /* other the more likely they should be joined) */ + if(deltadir > min_deltadir){ + + print2log("3DD "); + + /* Pair is the same type, so test to see */ + /* if both are on an island or lake. */ + + /* Check to see if pair on a loop of specified */ + /* half length (ex. 30 pixels) ... */ + ret = on_island_lake(&loop_x, &loop_y, + &loop_ex, &loop_ey, &nloop, + minutia1, minutia2, + half_loop, bdata, iw, ih); + /* If pair is on island/lake ... */ + if(ret == LOOP_FOUND){ + + print2log("4IL RM\n"); + + /* Fill the loop. */ + if((ret = fill_loop(loop_x, loop_y, nloop, + bdata, iw, ih))){ + free_contour(loop_x, loop_y, + loop_ex, loop_ey); + free(to_remove); + return(ret); + } + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + /* Deallocate loop contour. */ + free_contour(loop_x,loop_y,loop_ex,loop_ey); + } + /* If island/lake test IGNORED ... */ + else if (ret == IGNORE){ + + print2log("RM\n"); + + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Skip to next 1st minutia by breaking out */ + /* of inner secondary loop. */ + break; + } + /* If ERROR while looking for island/lake ... */ + else if (ret < 0){ + free(to_remove); + return(ret); + } + else + print2log("\n"); + }/* End deltadir test. */ + else + print2log("\n"); + }/* End distance test. */ + else + print2log("\n"); + } + /* Otherwise, current 2nd too far below 1st, so skip to */ + /* next 1st minutia. */ + else{ + + print2log("\n"); + + /* Break out of inner secondary loop. */ + break; + }/* End delta-y test. */ + }/* End if !to_remove[s] */ + else + print2log("\n"); + + }/* End if 2nd not desired type */ + + /* Bump to next second minutia in minutiae list. */ + s++; + }/* End secondary minutiae loop. */ + + }/* Otherwise, first minutia already flagged to be removed. */ + + /* Bump to next first minutia in minutiae list. */ + f++; + }/* End primary minutiae loop. */ + + /* Now remove all minutiae in list that have been flagged for removal. */ + /* NOTE: Need to remove the minutia from their lists in reverse */ + /* order, otherwise, indices will be off. */ + for(i = minutiae->num-1; i >= 0; i--){ + /* If the current minutia index is flagged for removal ... */ + if(to_remove[i]){ + /* Remove the minutia from the minutiae list. */ + if((ret = remove_minutia(i, minutiae))){ + free(to_remove); + return(ret); + } + } + } + + /* Deallocate flag list. */ + free(to_remove); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_malformations - Attempts to detect and remove minutia points +#cat: that are "irregularly" shaped. Irregularity is measured +#cat: by measuring across the interior of the feature at +#cat: two progressive points down the feature's contour. The +#cat: test is triggered if a pixel of opposite color from the +#cat: feture's type is found. The ratio of the distances across +#cat: the feature at the two points is computed and if the ratio +#cat: is too large then the minutia is determined to be malformed. +#cat: A cursory test is conducted prior to the general tests in +#cat: the event that the minutia lies in a block with LOW RIDGE +#cat: FLOW. In this case, the distance across the feature at +#cat: the second progressive contour point is measured and if +#cat: too large, the point is determined to be malformed. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + low_flow_map - map of image blocks flagged as LOW RIDGE FLOW + mw - width in blocks of the map + mh - height in blocks of the map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_malformations(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *low_flow_map, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int i, j, ret; + MINUTIA *minutia; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int ax1, ay1, bx1, by1; + int ax2, ay2, bx2, by2; + int *x_list, *y_list, num; + double a_dist, b_dist, ratio; + int fmapval, removed; + int blk_x, blk_y; + + print2log("\nREMOVING MALFORMATIONS:\n"); + + for(i = minutiae->num-1; i >= 0; i--){ + minutia = minutiae->list[i]; + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->malformation_steps_2, + minutia->x, minutia->y, + minutia->x, minutia->y, minutia->ex, minutia->ey, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->malformation_steps_2)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->malformation_steps_2)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + print2log("%d,%d RMA\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store 'A1' contour point. */ + ax1 = contour_x[lfsparms->malformation_steps_1-1]; + ay1 = contour_y[lfsparms->malformation_steps_1-1]; + + /* Store 'B1' contour point. */ + bx1 = contour_x[lfsparms->malformation_steps_2-1]; + by1 = contour_y[lfsparms->malformation_steps_2-1]; + + /* Deallocate the contours. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->malformation_steps_2, + minutia->x, minutia->y, + minutia->x, minutia->y, minutia->ex, minutia->ey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->malformation_steps_2)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->malformation_steps_2)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + print2log("%d,%d RMB\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store 'A2' contour point. */ + ax2 = contour_x[lfsparms->malformation_steps_1-1]; + ay2 = contour_y[lfsparms->malformation_steps_1-1]; + + /* Store 'B2' contour point. */ + bx2 = contour_x[lfsparms->malformation_steps_2-1]; + by2 = contour_y[lfsparms->malformation_steps_2-1]; + + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /* Compute distances along A & B paths. */ + a_dist = distance(ax1, ay1, ax2, ay2); + b_dist = distance(bx1, by1, bx2, by2); + + /* Compute block coords from minutia's pixel location. */ + blk_x = minutia->x/lfsparms->blocksize; + blk_y = minutia->y/lfsparms->blocksize; + + removed = FALSE; + + /* Check to see if distances are not zero. */ + if((a_dist == 0.0) || (b_dist == 0.0)){ + /* Remove the malformation minutia. */ + print2log("%d,%d RMMAL1\n", minutia->x, minutia->y); + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + removed = TRUE; + } + + if(!removed){ + /* Determine if minutia is in LOW RIDGE FLOW block. */ + fmapval = *(low_flow_map+(blk_y*mw)+blk_x); + if(fmapval){ + /* If in LOW RIDGE LFOW, conduct a cursory distance test. */ + /* Need to test this out! */ + if(b_dist > lfsparms->max_malformation_dist){ + /* Remove the malformation minutia. */ + print2log("%d,%d RMMAL2\n", minutia->x, minutia->y); + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + removed = TRUE; + } + } + } + + if(!removed){ + /* Compute points on line between the points A & B. */ + if((ret = line_points(&x_list, &y_list, &num, + bx1, by1, bx2, by2))) + return(ret); + /* Foreach remaining point along line segment ... */ + for(j = 0; j < num; j++){ + /* If B path contains pixel opposite minutia type ... */ + if(*(bdata+(y_list[j]*iw)+x_list[j]) != minutia->type){ + /* Compute ratio of A & B path lengths. */ + ratio = b_dist / a_dist; + /* Need to truncate precision so that answers are */ + /* consistent on different computer architectures. */ + ratio = trunc_dbl_precision(ratio, TRUNC_SCALE); + /* If the B path is sufficiently longer than A path ... */ + if(ratio > lfsparms->min_malformation_ratio){ + /* Remove the malformation minutia. */ + /* Then remove the minutia. */ + print2log("%d,%d RMMAL3 (%f)\n", + minutia->x, minutia->y, ratio); + if((ret = remove_minutia(i, minutiae))){ + free(x_list); + free(y_list); + /* If system error, return error code. */ + return(ret); + } + /* Break out of FOR loop. */ + break; + } + } + } + + free(x_list); + free(y_list); + + } + } + } + } + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_near_invblocks - Removes minutia points from the given list +#cat: that are sufficiently close to a block with invalid +#cat: ridge flow or to the edge of the image. + + Input: + minutiae - list of true and false minutiae + nmap - IMAP ridge flow matrix with invalid, high-curvature, + and no-valid-neighbor regions identified + mw - width in blocks of the NMAP + mh - height in blocks of the NMAP + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_near_invblock(MINUTIAE *minutiae, int *nmap, + const int mw, const int mh, const LFSPARMS *lfsparms) +{ + int i, ret; + int ni, nbx, nby, nvalid; + int ix, iy, sbi, ebi; + int bx, by, px, py; + int removed; + MINUTIA *minutia; + int lo_margin, hi_margin; + + /* MDG: The next 2 lookup tables are indexed by 'ix' and 'iy'. */ + /* When a feature pixel lies within a 6-pixel margin of a */ + /* block, this routine examines neighboring blocks to */ + /* determine appropriate actions. */ + /* 'ix' may take on values: */ + /* 0 == x-pixel coord in leftmost margin */ + /* 1 == x-pixel coord in middle of block */ + /* 2 == x-pixel coord in rightmost margin */ + /* 'iy' may take on values: */ + /* 0 == y-pixel coord in topmost margin */ + /* 1 == y-pixel coord in middle of block */ + /* 2 == y-pixel coord in bottommost margin */ + /* Given (ix, iy): */ + /* 'startblk[ix][iy]' == starting neighbor index (sbi) */ + /* 'endblk[ix][iy]' == ending neighbor index (ebi) */ + /* so that neighbors begin to be analized from index */ + /* 'sbi' to 'ebi'. */ + /* Ex. (ix, iy) = (2, 0) */ + /* ix==2 ==> x-pixel coord in rightmost margin */ + /* iy==0 ==> y-pixel coord in topmost margin */ + /* X - marks the region in the current block */ + /* corresponding to (ix=2, iy=0). */ + /* sbi = 0 = startblk[2][0] */ + /* ebi = 2 = endblk[2][0] */ + /* so neighbors are analized on index range [0..2] */ + /* | */ + /* nbr block 0 | nbr block 1 */ + /* --------------------------+------------ */ + /* top margin | X | */ + /* _._._._._._._._._._._._._.| */ + /* | | */ + /* current block .r m| nbr block 2 */ + /* |i a| */ + /* .g g| */ + /* |h i| */ + /* .t n| */ + /* | | */ + + /* MDG: LUT for starting neighbor index given (ix, iy). */ + static int startblk[9] = { 6, 0, 0, + 6,-1, 2, + 4, 4, 2 }; + /* MDG: LUT for ending neighbor index given (ix, iy). */ + static int endblk[9] = { 8, 0, 2, + 6,-1, 2, + 6, 4, 4 }; + + /* MDG: Pixel coord offsets specifying the order in which neighboring */ + /* blocks are searched. The current block is in the middle of */ + /* 8 surrounding neighbors. The following illustrates the order */ + /* of neighbor indices. (Note that 9 overlaps 1.) */ + /* 8 */ + /* 7 0 1 */ + /* 6 C 2 */ + /* 5 4 3 */ + /* */ + /* 0 1 2 3 4 5 6 7 8 */ + static int blkdx[9] = { 0, 1, 1, 1, 0,-1,-1,-1, 0 }; /* Delta-X */ + static int blkdy[9] = { -1,-1, 0, 1, 1, 1, 0,-1,-1 }; /* Delta-Y */ + + print2log("\nREMOVING MINUTIA NEAR INVALID BLOCKS:\n"); + + /* If the margin covers more than the entire block ... */ + if(lfsparms->inv_block_margin > (lfsparms->blocksize>>1)){ + /* Then treat this as an error. */ + fprintf(stderr, + "ERROR : remove_near_invblock : margin too large for blocksize\n"); + return(-270); + } + + /* Compute the low and high pixel margin boundaries (ex. 6 pixels wide) */ + /* in the block. */ + lo_margin = lfsparms->inv_block_margin; + hi_margin = lfsparms->blocksize - lfsparms->inv_block_margin - 1; + + i = 0; + /* Foreach minutia remaining in the list ... */ + while(i < minutiae->num){ + /* Assign temporary minutia pointer. */ + minutia = minutiae->list[i]; + + /* Compute NMAP block coords from minutia's pixel location. */ + bx = minutia->x/lfsparms->blocksize; + by = minutia->y/lfsparms->blocksize; + + /* Compute pixel offset into the NMAP block corresponding to the */ + /* minutia's pixel location. */ + /* NOTE: The margins used here will necessarily correspond to the */ + /* actual block, boundaries used to compute the NMAP values. */ + /* This will be true when the image width and/or height is not an */ + /* even multiple of 'blocksize' and we are processing minutia */ + /* located in the right-most column (or bottom-most row) of */ + /* blocks. I don't think this will pose a problem in practice. */ + px = minutia->x % lfsparms->blocksize; + py = minutia->y % lfsparms->blocksize; + + /* Determine if x pixel offset into the block is in the margins. */ + /* If x pixel offset is in left margin ... */ + if(px < lo_margin) + ix = 0; + /* If x pixel offset is in right margin ... */ + else if(px > hi_margin) + ix = 2; + /* Otherwise, x pixel offset is in middle of block. */ + else + ix = 1; + + /* Determine if y pixel offset into the block is in the margins. */ + /* If y pixel offset is in top margin ... */ + if(py < lo_margin) + iy = 0; + /* If y pixel offset is in bottom margin ... */ + else if(py > hi_margin) + iy = 2; + /* Otherwise, y pixel offset is in middle of block. */ + else + iy = 1; + + /* Set remove flag to FALSE. */ + removed = FALSE; + + /* If one of the minutia's pixel offsets is in a margin ... */ + if((ix != 1) || (iy != 1)){ + + /* Compute the starting neighbor block index for processing. */ + sbi = *(startblk+(iy*3)+ix); + /* Compute the ending neighbor block index for processing. */ + ebi = *(endblk+(iy*3)+ix); + + /* Foreach neighbor in the range to be processed ... */ + for(ni = sbi; ni <= ebi; ni++){ + /* Compute the neighbor's NMAP block coords relative to */ + /* the block the current minutia is in. */ + nbx = bx + blkdx[ni]; + nby = by + blkdy[ni]; + + /* If neighbor's block coords are outside of NMAP boundaries... */ + if((nbx < 0) || (nbx >= mw) || + (nby < 0) || (nby >= mh)){ + + print2log("%d,%d RM1\n", minutia->x, minutia->y); + + /* Then the minutia is in a margin adjacent to the edge of */ + /* the image. */ + /* NOTE: This is true when the image width and/or height */ + /* is an even multiple of blocksize. When the image is not*/ + /* an even multiple, then some minutia may not be detected */ + /* as being in the margin of "the image" (not the block). */ + /* In practice, I don't think this will impact performance.*/ + if((ret = remove_minutia(i, minutiae))) + /* If system error occurred while removing minutia, */ + /* then return error code. */ + return(ret); + /* Set remove flag to TURE. */ + removed = TRUE; + /* Break out of neighboring block loop. */ + break; + } + /* If the neighboring NMAP block is INVALID ... */ + else if (*(nmap+(nby*mw)+nbx) == INVALID_DIR){ + /* Count the number of valid blocks neighboring */ + /* the current neighbor. */ + nvalid = num_valid_8nbrs(nmap, nbx, nby, mw, mh); + /* If the number of valid neighbors is < threshold */ + /* (ex. 7)... */ + if(nvalid < lfsparms->rm_valid_nbr_min){ + + print2log("%d,%d RM2\n", minutia->x, minutia->y); + + /* Then remove the current minutia from the list. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error occurred while removing minutia, */ + /* then return error code. */ + return(ret); + /* Set remove flag to TURE. */ + removed = TRUE; + /* Break out of neighboring block loop. */ + break; + } + /* Otherwise enough valid neighbors, so don't remove minutia */ + /* based on this neighboring block. */ + } + /* Otherwise neighboring block is VALID, HIGH_CURVATURE, or */ + /* NO_VALID_NBRS, so don't remove minutia based on this */ + /* neighboring block. */ + } + + } /* Otherwise not in margin, so skip to next minutia in list. */ + + /* If current minutia not removed ... */ + if(!removed) + /* Advance to the next minutia in the list. */ + i++; + /* Otherwise the next minutia has slid into the spot where current */ + /* minutia was removed, so don't bump minutia index. */ + } /* End minutia loop */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_near_invblocks_V2 - Removes minutia points from the given list +#cat: that are sufficiently close to a block with invalid +#cat: ridge flow or to the edge of the image. + + Input: + minutiae - list of true and false minutiae + direction_map - map of image blocks containing direction ridge flow + mw - width in blocks of the map + mh - height in blocks of the map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_near_invblock_V2(MINUTIAE *minutiae, int *direction_map, + const int mw, const int mh, const LFSPARMS *lfsparms) +{ + int i, ret; + int ni, nbx, nby, nvalid; + int ix, iy, sbi, ebi; + int bx, by, px, py; + int removed; + MINUTIA *minutia; + int lo_margin, hi_margin; + + /* The next 2 lookup tables are indexed by 'ix' and 'iy'. */ + /* When a feature pixel lies within a 6-pixel margin of a */ + /* block, this routine examines neighboring blocks to */ + /* determine appropriate actions. */ + /* 'ix' may take on values: */ + /* 0 == x-pixel coord in leftmost margin */ + /* 1 == x-pixel coord in middle of block */ + /* 2 == x-pixel coord in rightmost margin */ + /* 'iy' may take on values: */ + /* 0 == y-pixel coord in topmost margin */ + /* 1 == y-pixel coord in middle of block */ + /* 2 == y-pixel coord in bottommost margin */ + /* Given (ix, iy): */ + /* 'startblk[ix][iy]' == starting neighbor index (sbi) */ + /* 'endblk[ix][iy]' == ending neighbor index (ebi) */ + /* so that neighbors begin to be analized from index */ + /* 'sbi' to 'ebi'. */ + /* Ex. (ix, iy) = (2, 0) */ + /* ix==2 ==> x-pixel coord in rightmost margin */ + /* iy==0 ==> y-pixel coord in topmost margin */ + /* X - marks the region in the current block */ + /* corresponding to (ix=2, iy=0). */ + /* sbi = 0 = startblk[2][0] */ + /* ebi = 2 = endblk[2][0] */ + /* so neighbors are analized on index range [0..2] */ + /* | */ + /* nbr block 0 | nbr block 1 */ + /* --------------------------+------------ */ + /* top margin | X | */ + /* _._._._._._._._._._._._._.| */ + /* | | */ + /* current block .r m| nbr block 2 */ + /* |i a| */ + /* .g g| */ + /* |h i| */ + /* .t n| */ + /* | | */ + + /* LUT for starting neighbor index given (ix, iy). */ + static int startblk[9] = { 6, 0, 0, + 6,-1, 2, + 4, 4, 2 }; + /* LUT for ending neighbor index given (ix, iy). */ + static int endblk[9] = { 8, 0, 2, + 6,-1, 2, + 6, 4, 4 }; + + /* Pixel coord offsets specifying the order in which neighboring */ + /* blocks are searched. The current block is in the middle of */ + /* 8 surrounding neighbors. The following illustrates the order */ + /* of neighbor indices. (Note that 9 overlaps 1.) */ + /* 8 */ + /* 7 0 1 */ + /* 6 C 2 */ + /* 5 4 3 */ + /* */ + /* 0 1 2 3 4 5 6 7 8 */ + static int blkdx[9] = { 0, 1, 1, 1, 0,-1,-1,-1, 0 }; /* Delta-X */ + static int blkdy[9] = { -1,-1, 0, 1, 1, 1, 0,-1,-1 }; /* Delta-Y */ + + print2log("\nREMOVING MINUTIA NEAR INVALID BLOCKS:\n"); + + /* If the margin covers more than the entire block ... */ + if(lfsparms->inv_block_margin > (lfsparms->blocksize>>1)){ + /* Then treat this as an error. */ + fprintf(stderr, + "ERROR : remove_near_invblock_V2 : margin too large for blocksize\n"); + return(-620); + } + + /* Compute the low and high pixel margin boundaries (ex. 6 pixels wide) */ + /* in the block. */ + lo_margin = lfsparms->inv_block_margin; + hi_margin = lfsparms->blocksize - lfsparms->inv_block_margin - 1; + + i = 0; + /* Foreach minutia remaining in the list ... */ + while(i < minutiae->num){ + /* Assign temporary minutia pointer. */ + minutia = minutiae->list[i]; + + /* Compute block coords from minutia's pixel location. */ + bx = minutia->x/lfsparms->blocksize; + by = minutia->y/lfsparms->blocksize; + + /* Compute pixel offset into the image block corresponding to the */ + /* minutia's pixel location. */ + /* NOTE: The margins used here will not necessarily correspond to */ + /* the actual block boundaries used to compute the map values. */ + /* This will be true when the image width and/or height is not an */ + /* even multiple of 'blocksize' and we are processing minutia */ + /* located in the right-most column (or bottom-most row) of */ + /* blocks. I don't think this will pose a problem in practice. */ + px = minutia->x % lfsparms->blocksize; + py = minutia->y % lfsparms->blocksize; + + /* Determine if x pixel offset into the block is in the margins. */ + /* If x pixel offset is in left margin ... */ + if(px < lo_margin) + ix = 0; + /* If x pixel offset is in right margin ... */ + else if(px > hi_margin) + ix = 2; + /* Otherwise, x pixel offset is in middle of block. */ + else + ix = 1; + + /* Determine if y pixel offset into the block is in the margins. */ + /* If y pixel offset is in top margin ... */ + if(py < lo_margin) + iy = 0; + /* If y pixel offset is in bottom margin ... */ + else if(py > hi_margin) + iy = 2; + /* Otherwise, y pixel offset is in middle of block. */ + else + iy = 1; + + /* Set remove flag to FALSE. */ + removed = FALSE; + + /* If one of the minutia's pixel offsets is in a margin ... */ + if((ix != 1) || (iy != 1)){ + + /* Compute the starting neighbor block index for processing. */ + sbi = *(startblk+(iy*3)+ix); + /* Compute the ending neighbor block index for processing. */ + ebi = *(endblk+(iy*3)+ix); + + /* Foreach neighbor in the range to be processed ... */ + for(ni = sbi; ni <= ebi; ni++){ + /* Compute the neighbor's block coords relative to */ + /* the block the current minutia is in. */ + nbx = bx + blkdx[ni]; + nby = by + blkdy[ni]; + + /* If neighbor's block coords are outside of map boundaries... */ + if((nbx < 0) || (nbx >= mw) || + (nby < 0) || (nby >= mh)){ + + print2log("%d,%d RM1\n", minutia->x, minutia->y); + + /* Then the minutia is in a margin adjacent to the edge of */ + /* the image. */ + /* NOTE: This is true when the image width and/or height */ + /* is an even multiple of blocksize. When the image is not*/ + /* an even multiple, then some minutia may not be detected */ + /* as being in the margin of "the image" (not the block). */ + /* In practice, I don't think this will impact performance.*/ + if((ret = remove_minutia(i, minutiae))) + /* If system error occurred while removing minutia, */ + /* then return error code. */ + return(ret); + /* Set remove flag to TURE. */ + removed = TRUE; + /* Break out of neighboring block loop. */ + break; + } + /* If the neighboring block has INVALID direction ... */ + else if (*(direction_map+(nby*mw)+nbx) == INVALID_DIR){ + /* Count the number of valid blocks neighboring */ + /* the current neighbor. */ + nvalid = num_valid_8nbrs(direction_map, nbx, nby, mw, mh); + /* If the number of valid neighbors is < threshold */ + /* (ex. 7)... */ + if(nvalid < lfsparms->rm_valid_nbr_min){ + + print2log("%d,%d RM2\n", minutia->x, minutia->y); + + /* Then remove the current minutia from the list. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error occurred while removing minutia, */ + /* then return error code. */ + return(ret); + /* Set remove flag to TURE. */ + removed = TRUE; + /* Break out of neighboring block loop. */ + break; + } + /* Otherwise enough valid neighbors, so don't remove minutia */ + /* based on this neighboring block. */ + } + /* Otherwise neighboring block has valid direction, */ + /* so don't remove minutia based on this neighboring block. */ + } + + } /* Otherwise not in margin, so skip to next minutia in list. */ + + /* If current minutia not removed ... */ + if(!removed) + /* Advance to the next minutia in the list. */ + i++; + /* Otherwise the next minutia has slid into the spot where current */ + /* minutia was removed, so don't bump minutia index. */ + } /* End minutia loop */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_pointing_invblock - Removes minutia points that are relatively +#cat: close in the direction opposite the minutia to an NMAP +#cat: block with invalid ridge flow. + + Input: + minutiae - list of true and false minutiae + nmap - IMAP ridge flow matrix with invalid, high-curvature, + and no-valid-neighbor regions identified + mw - width in blocks of the NMAP + mh - height in blocks of the NMAP + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_pointing_invblock(MINUTIAE *minutiae, + int *nmap, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int i, ret; + int delta_x, delta_y, nmapval; + int nx, ny, bx, by; + MINUTIA *minutia; + double pi_factor, theta; + double dx, dy; + + print2log("\nREMOVING MINUTIA POINTING TO INVALID BLOCKS:\n"); + + /* Compute factor for converting integer directions to radians. */ + pi_factor = M_PI / (double)lfsparms->num_directions; + + i = 0; + /* Foreach minutia remaining in list ... */ + while(i < minutiae->num){ + /* Set temporary minutia pointer. */ + minutia = minutiae->list[i]; + /* Convert minutia's direction to radians. */ + theta = minutia->direction * pi_factor; + /* Compute translation offsets (ex. 6 pixels). */ + dx = sin(theta) * (double)lfsparms->trans_dir_pix; + dy = cos(theta) * (double)lfsparms->trans_dir_pix; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + dx = trunc_dbl_precision(dx, TRUNC_SCALE); + dy = trunc_dbl_precision(dy, TRUNC_SCALE); + delta_x = sround(dx); + delta_y = sround(dy); + /* Translate the minutia's coords. */ + nx = minutia->x - delta_x; + ny = minutia->y + delta_y; + /* Convert pixel coords to NMAP block coords. */ + bx = (int)(nx / lfsparms->blocksize); + by = (int)(ny / lfsparms->blocksize); + /* The translation could move the point out of image boundaries, */ + /* and therefore the corresponding block coords can be out of */ + /* IMAP boundaries, so limit the block coords to within boundaries. */ + bx = max(0, bx); + bx = min(mw-1, bx); + by = max(0, by); + by = min(mh-1, by); + + /* Get corresponding block's NMAP value. */ + /* -3 == NO_VALID_NBRS */ + /* -2 == HIGH_CURVATURE */ + /* -1 == INVALID_DIR */ + /* 0 <= VALID_DIR */ + nmapval = *(nmap+(by*mw)+bx); + + /* If the NMAP value of translated minutia point is INVALID ... */ + if(nmapval == INVALID_DIR){ + + print2log("%d,%d RM\n", minutia->x, minutia->y); + + /* Remove the minutia from the minutiae list. */ + if((ret = remove_minutia(i, minutiae))){ + return(ret); + } + /* No need to advance because next minutia has slid into slot. */ + } + else{ + /* Advance to next minutia in list. */ + i++; + } + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_pointing_invblock_V2 - Removes minutia points that are relatively +#cat: close in the direction opposite the minutia to a +#cat: block with INVALID ridge flow. + + Input: + minutiae - list of true and false minutiae + direction_map - map of image blocks containing directional ridge flow + mw - width in blocks of the map + mh - height in blocks of the map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_pointing_invblock_V2(MINUTIAE *minutiae, + int *direction_map, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int i, ret; + int delta_x, delta_y, dmapval; + int nx, ny, bx, by; + MINUTIA *minutia; + double pi_factor, theta; + double dx, dy; + + print2log("\nREMOVING MINUTIA POINTING TO INVALID BLOCKS:\n"); + + /* Compute factor for converting integer directions to radians. */ + pi_factor = M_PI / (double)lfsparms->num_directions; + + i = 0; + /* Foreach minutia remaining in list ... */ + while(i < minutiae->num){ + /* Set temporary minutia pointer. */ + minutia = minutiae->list[i]; + /* Convert minutia's direction to radians. */ + theta = minutia->direction * pi_factor; + /* Compute translation offsets (ex. 6 pixels). */ + dx = sin(theta) * (double)(lfsparms->trans_dir_pix); + dy = cos(theta) * (double)(lfsparms->trans_dir_pix); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + dx = trunc_dbl_precision(dx, TRUNC_SCALE); + dy = trunc_dbl_precision(dy, TRUNC_SCALE); + delta_x = sround(dx); + delta_y = sround(dy); + /* Translate the minutia's coords. */ + nx = minutia->x - delta_x; + ny = minutia->y + delta_y; + /* Convert pixel coords to block coords. */ + bx = (int)(nx / lfsparms->blocksize); + by = (int)(ny / lfsparms->blocksize); + /* The translation could move the point out of image boundaries, */ + /* and therefore the corresponding block coords can be out of */ + /* map boundaries, so limit the block coords to within boundaries. */ + bx = max(0, bx); + bx = min(mw-1, bx); + by = max(0, by); + by = min(mh-1, by); + + /* Get corresponding block's ridge flow direction. */ + dmapval = *(direction_map+(by*mw)+bx); + + /* If the block's direction is INVALID ... */ + if(dmapval == INVALID_DIR){ + + print2log("%d,%d RM\n", minutia->x, minutia->y); + + /* Remove the minutia from the minutiae list. */ + if((ret = remove_minutia(i, minutiae))){ + return(ret); + } + /* No need to advance because next minutia has slid into slot. */ + } + else{ + /* Advance to next minutia in list. */ + i++; + } + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_overlaps - Takes a list of true and false minutiae and +#cat: attempts to detect and remove those false minutiae that +#cat: are on opposite sides of an overlap. Note that this +#cat: routine does NOT edit the binary image when overlaps +#cat: are removed. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_overlaps(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int *to_remove; + int i, f, s, ret; + int delta_y, full_ndirs, qtr_ndirs, deltadir, min_deltadir; + MINUTIA *minutia1, *minutia2; + double dist; + int joindir, opp1dir, half_ndirs; + + print2log("\nREMOVING OVERLAPS:\n"); + + /* Allocate list of minutia indices that upon completion of testing */ + /* should be removed from the minutiae lists. Note: That using */ + /* "calloc" initializes the list to FALSE. */ + to_remove = (int *)calloc(minutiae->num, sizeof(int)); + if(to_remove == (int *)NULL){ + fprintf(stderr, "ERROR : remove_overlaps : calloc : to_remove\n"); + return(-650); + } + + /* Compute number directions in full circle. */ + full_ndirs = lfsparms->num_directions<<1; + /* Compute number of directions in 45=(180/4) degrees. */ + qtr_ndirs = lfsparms->num_directions>>2; + /* Compute number of directions in 90=(180/2) degrees. */ + half_ndirs = lfsparms->num_directions>>1; + + /* Minimum allowable deltadir to consider joining minutia. */ + /* (The closer the deltadir is to 180 degrees, the more likely the join. */ + /* When ndirs==16, then this value is 11=(3*4)-1 == 123.75 degrees. */ + /* I chose to parameterize this threshold based on a fixed fraction of */ + /* 'ndirs' rather than on passing in a parameter in degrees and doing */ + /* the conversion. I doubt the difference matters. */ + min_deltadir = (3 * qtr_ndirs) - 1; + + f = 0; + /* Foreach primary (first) minutia (except for last one in list) ... */ + while(f < minutiae->num-1){ + + /* If current first minutia not previously set to be removed. */ + if(!to_remove[f]){ + + print2log("\n"); + + /* Set first minutia to temporary pointer. */ + minutia1 = minutiae->list[f]; + /* Foreach secondary (second) minutia to right of first minutia ... */ + s = f+1; + while(s < minutiae->num){ + /* Set second minutia to temporary pointer. */ + minutia2 = minutiae->list[s]; + + print2log("1:%d(%d,%d)%d 2:%d(%d,%d)%d ", + f, minutia1->x, minutia1->y, minutia1->type, + s, minutia2->x, minutia2->y, minutia2->type); + + /* The binary image is potentially being edited during each */ + /* iteration of the secondary minutia loop, therefore */ + /* minutia pixel values may be changed. We need to catch */ + /* these events by using the next 2 tests. */ + + /* If the first minutia's pixel has been previously changed... */ + if(*(bdata+(minutia1->y*iw)+minutia1->x) != minutia1->type){ + print2log("\n"); + /* Then break out of secondary loop and skip to next first. */ + break; + } + + /* If the second minutia's pixel has been previously changed... */ + if(*(bdata+(minutia2->y*iw)+minutia2->x) != minutia2->type) + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + + /* If the second minutia not previously set to be removed. */ + if(!to_remove[s]){ + + /* Compute delta y between 1st & 2nd minutiae and test. */ + delta_y = minutia2->y - minutia1->y; + /* If delta y small enough (ex. < 8 pixels) ... */ + if(delta_y <= lfsparms->max_overlap_dist){ + + print2log("1DY "); + + /* Compute Euclidean distance between 1st & 2nd mintuae. */ + dist = distance(minutia1->x, minutia1->y, + minutia2->x, minutia2->y); + /* If distance is NOT too large (ex. < 8 pixels) ... */ + if(dist <= lfsparms->max_overlap_dist){ + + print2log("2DS "); + + /* Compute "inner" difference between directions on */ + /* a full circle and test. */ + if((deltadir = closest_dir_dist(minutia1->direction, + minutia2->direction, full_ndirs)) == + INVALID_DIR){ + free(to_remove); + fprintf(stderr, + "ERROR : remove_overlaps : INVALID direction\n"); + return(-651); + } + /* If the difference between dirs is large enough ... */ + /* (the more 1st & 2nd point away from each other the */ + /* more likely they should be joined) */ + if(deltadir > min_deltadir){ + + print2log("3DD "); + + /* If 1st & 2nd minutiae are same type ... */ + if(minutia1->type == minutia2->type){ + /* Test to see if both are on opposite sides */ + /* of an overlap. */ + + /* Compute direction of "joining" vector. */ + /* First, compute direction of line from first */ + /* to second minutia points. */ + joindir = line2direction(minutia1->x, minutia1->y, + minutia2->x, minutia2->y, + lfsparms->num_directions); + + /* Comptue opposite direction of first minutia. */ + opp1dir = (minutia1->direction+ + lfsparms->num_directions)%full_ndirs; + /* Take "inner" distance on full circle between */ + /* the first minutia's opposite direction and */ + /* the joining direction. */ + joindir = abs(opp1dir - joindir); + joindir = min(joindir, full_ndirs - joindir); + + print2log("joindir=%d dist=%f ", joindir,dist); + + /* If the joining angle is <= 90 degrees OR */ + /* the 2 points are sufficiently close AND */ + /* a free path exists between pair ... */ + if(((joindir <= half_ndirs) || + (dist <= lfsparms->max_overlap_join_dist)) && + free_path(minutia1->x, minutia1->y, + minutia2->x, minutia2->y, + bdata, iw, ih, lfsparms)){ + + print2log("4OV RM\n"); + + /* Then assume overlap, so ... */ + /* Set to remove first minutia. */ + to_remove[f] = TRUE; + /* Set to remove second minutia. */ + to_remove[s] = TRUE; + } + /* Otherwise, pair not on an overlap, so skip */ + /* to next second minutia. */ + else + print2log("\n"); + } + else + print2log("\n"); + /* End same type test. */ + }/* End deltadir test. */ + else + print2log("\n"); + }/* End distance test. */ + else + print2log("\n"); + } + /* Otherwise, current 2nd too far below 1st, so skip to next */ + /* 1st minutia. */ + else{ + + print2log("\n"); + + /* Break out of inner secondary loop. */ + break; + }/* End delta-y test. */ + + }/* End if !to_remove[s] */ + else + print2log("\n"); + + /* Bump to next second minutia in minutiae list. */ + s++; + }/* End secondary minutiae loop. */ + + }/* Otherwise, first minutia already flagged to be removed. */ + + /* Bump to next first minutia in minutiae list. */ + f++; + }/* End primary minutiae loop. */ + + /* Now remove all minutiae in list that have been flagged for removal. */ + /* NOTE: Need to remove the minutia from their lists in reverse */ + /* order, otherwise, indices will be off. */ + for(i = minutiae->num-1; i >= 0; i--){ + /* If the current minutia index is flagged for removal ... */ + if(to_remove[i]){ + /* Remove the minutia from the minutiae list. */ + if((ret = remove_minutia(i, minutiae))){ + free(to_remove); + return(ret); + } + } + } + + /* Deallocate flag list. */ + free(to_remove); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_pores - Attempts to detect and remove minutia points located on +#cat: pore-shaped valleys. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + nmap - IMAP ridge flow matrix with invalid, high-curvature, + and no-valid-neighbor regions identified + mw - width in blocks of the NMAP + mh - height in blocks of the NMAP + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_pores(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *nmap, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int i, ret; + int removed, blk_x, blk_y; + int rx, ry; + int px, py, pex, pey, bx, by, dx, dy; + int qx, qy, qex, qey, ax, ay, cx, cy; + MINUTIA *minutia; + double pi_factor, theta, sin_theta, cos_theta; + double ab2, cd2, ratio; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + double drx, dry; + + /* MDG: This routine attempts to locate the following points on all */ + /* bifurcations within the feature list. */ + /* 1. Compute R 3 pixels opposite the feature direction from */ + /* feature point F. */ + /* 2. Find white pixel transitions P & Q within 12 steps from */ + /* from R perpendicular to the feature's direction. */ + /* 3. Find points B & D by walking white edge from P. */ + /* 4. Find points A & C by walking white edge from Q. */ + /* 5. Measure squared distances between A-B and C-D. */ + /* 6. Compute ratio of squared distances and compare against */ + /* threshold (2.25). If A-B sufficiently larger than C-D, */ + /* then assume NOT pore, otherwise flag the feature point F.*/ + /* If along the way, finding any of these points fails, then */ + /* assume the feature is a pore and flag it. */ + /* */ + /* A */ + /* _____._ */ + /* ----___ Q C */ + /* ------____ ---_.________.___ */ + /* ---_ */ + /* (valley) F.\ .R (ridge) */ + /* ____/ */ + /* ______---- ___-.--------.--- */ + /* ____--- P D */ + /* -----.- */ + /* B */ + /* */ + /* AB^2/CD^2 <= 2.25 then flag feature */ + /* */ + + + print2log("\nREMOVING PORES:\n"); + + /* Factor for converting integer directions into radians. */ + pi_factor = M_PI/(double)lfsparms->num_directions; + + /* Initialize to the beginning of the minutia list. */ + i = 0; + /* Foreach minutia remaining in the list ... */ + while(i < minutiae->num){ + /* Set temporary minutia pointer. */ + minutia = minutiae->list[i]; + + /* Initialize remove flag to FALSE. */ + removed = FALSE; + + /* If minutia is a bifurcation ... */ + if(minutia->type == BIFURCATION){ + /* Compute NMAP block coords from minutia point. */ + blk_x = minutia->x / lfsparms->blocksize; + blk_y = minutia->y / lfsparms->blocksize; + /* If NMAP block has a VALID direction... */ + if(*(nmap+(blk_y*mw)+blk_x) >= 0){ + /* Compute radian angle from minutia direction. */ + theta = (double)minutia->direction * pi_factor; + /* Compute sine and cosine factors of this angle. */ + sin_theta = sin(theta); + cos_theta = cos(theta); + /* Translate the minutia point (ex. 3 pixels) in opposite */ + /* direction minutia is pointing. Call this point 'R'. */ + drx = (double)minutia->x - + (sin_theta * (double)lfsparms->pores_trans_r); + dry = (double)minutia->y + + (cos_theta * (double)lfsparms->pores_trans_r); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + drx = trunc_dbl_precision(drx, TRUNC_SCALE); + dry = trunc_dbl_precision(dry, TRUNC_SCALE); + rx = sround(drx); + ry = sround(dry); + + /* If 'R' is on a black pixel ... */ + if(*(bdata+(ry*iw)+rx) == 1){ + + /* Search a specified number of steps (ex. 12) from 'R' in a */ + /* perpendicular direction from the minutia direction until */ + /* the first white pixel is found. If a white pixel is */ + /* found within the specified number of steps, then call */ + /* this point 'P' (storing the point's edge pixel as well). */ + if(search_in_direction(&px, &py, &pex, &pey, + 0, rx, ry, -cos_theta, -sin_theta, + lfsparms->pores_perp_steps, + bdata, iw, ih)){ + /* Trace contour from P's edge pixel in counter-clockwise */ + /* scan and step along specified number of steps (ex. 10). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_fwd, + px, py, px, py, pex, pey, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + + print2log("%d,%d RMB\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as point 'B'. */ + bx = contour_x[ncontour-1]; + by = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + /* Trace contour from P's edge pixel in clockwise scan */ + /* and step along specified number of steps (ex. 8). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_bwd, + px, py, px, py, pex, pey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + print2log("%d,%d RMD\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as point 'D'. */ + dx = contour_x[ncontour-1]; + dy = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + /* Search a specified number of steps (ex. 12) from */ + /* 'R' in opposite direction of that used to find */ + /* 'P' until the first white pixel is found. If a */ + /* white pixel is found within the specified number */ + /* of steps, then call this point 'Q' (storing the */ + /* point's edge pixel as well). */ + if(search_in_direction(&qx, &qy, &qex, &qey, + 0, rx, ry, cos_theta, sin_theta, + lfsparms->pores_perp_steps, + bdata, iw, ih)){ + /* Trace contour from Q's edge pixel in clockwise */ + /* scan and step along specified number of steps */ + /* (ex. 10). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_fwd, + qx, qy, qx, qy, qex, qey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + print2log("%d,%d RMA\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as point 'A'. */ + ax = contour_x[ncontour-1]; + ay = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + /* Trace contour from Q's edge pixel in */ + /* counter-clockwise scan and step along a */ + /* specified number of steps (ex. 8). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_bwd, + qx, qy, qx, qy, qex, qey, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during scan ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + print2log("%d,%d RMC\n", + minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as 'C'. */ + cx = contour_x[ncontour-1]; + cy = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + /* Compute squared distance between points */ + /* 'A' and 'B'. */ + ab2 = squared_distance(ax, ay, bx, by); + /* Compute squared distance between points */ + /* 'C' and 'D'. */ + cd2 = squared_distance(cx, cy, dx, dy); + /* If CD distance is not near zero */ + /* (ex. 0,5) ... */ + if(cd2 > lfsparms->pores_min_dist2){ + /* Compute ratio of squared distances. */ + ratio = ab2 / cd2; + /* If ratio is small enough (ex. 2.25)...*/ + if(ratio <= lfsparms->pores_max_ratio){ + + print2log("%d,%d ", + minutia->x, minutia->y); + print2log("R=%d,%d P=%d,%d B=%d,%d D=%d,%d Q=%d,%d A=%d,%d C=%d,%d ", + rx, ry, px, py, bx, by, dx, dy, qx, qy, ax, ay, cx, cy); + print2log("RMRATIO\n"); + + /* Then assume pore & remove minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, ratio to big, so assume */ + /* legitimate bifurcation. */ + } /* Else, cd2 too small. */ + } /* Done with C. */ + } /* Done with A. */ + } + /* Otherwise, Q not found ... */ + else{ + + print2log("%d,%d RMQ\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } /* Done with Q. */ + } /* Done with D. */ + } /* Done with B. */ + } + /* Otherwise, P not found ... */ + else{ + + print2log("%d,%d RMP\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + } /* Else, R is on white pixel. */ + } /* Else NMAP is INVALID, HIGH_CURVATURE, or NO_VALID_NBRS. */ + } /* Else, Ridge Ending. */ + + /* If current minutia not removed ... */ + if(!removed) + /* Bump to next minutia in list. */ + i++; + /* Otherwise, next minutia has slid into slot of current removed one. */ + + } /* End While minutia remaining in list. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_pores_V2 - Attempts to detect and remove minutia points located on +#cat: pore-shaped valleys and/or ridges. Detection for +#cat: these features are only performed in blocks with +#cat: LOW RIDGE FLOW or HIGH CURVATURE. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + direction_map - map of image blocks containing directional ridge flow + low_flow_map - map of image blocks flagged as LOW RIDGE FLOW + high_curve_map - map of image blocks flagged as HIGH CURVATURE + mw - width in blocks of the maps + mh - height in blocks of the maps + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_pores_V2(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *direction_map, int *low_flow_map, + int *high_curve_map, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int i, ret; + int removed, blk_x, blk_y; + int rx, ry; + int px, py, pex, pey, bx, by, dx, dy; + int qx, qy, qex, qey, ax, ay, cx, cy; + MINUTIA *minutia; + double pi_factor, theta, sin_theta, cos_theta; + double ab2, cd2, ratio; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + double drx, dry; + + /* This routine attempts to locate the following points on all */ + /* minutia within the feature list. */ + /* 1. Compute R 3 pixels opposite the feature direction from */ + /* feature point F. */ + /* 2. Find white pixel transitions P & Q within 12 steps from */ + /* from R perpendicular to the feature's direction. */ + /* 3. Find points B & D by walking white edge from P. */ + /* 4. Find points A & C by walking white edge from Q. */ + /* 5. Measure squared distances between A-B and C-D. */ + /* 6. Compute ratio of squared distances and compare against */ + /* threshold (2.25). If A-B sufficiently larger than C-D, */ + /* then assume NOT pore, otherwise flag the feature point F.*/ + /* If along the way, finding any of these points fails, then */ + /* assume the feature is a pore and flag it. */ + /* */ + /* A */ + /* _____._ */ + /* ----___ Q C */ + /* ------____ ---_.________.___ */ + /* ---_ */ + /* (valley) F.\ .R (ridge) */ + /* ____/ */ + /* ______---- ___-.--------.--- */ + /* ____--- P D */ + /* -----.- */ + /* B */ + /* */ + /* AB^2/CD^2 <= 2.25 then flag feature */ + /* */ + + + print2log("\nREMOVING PORES:\n"); + + /* Factor for converting integer directions into radians. */ + pi_factor = M_PI/(double)lfsparms->num_directions; + + /* Initialize to the beginning of the minutia list. */ + i = 0; + /* Foreach minutia remaining in the list ... */ + while(i < minutiae->num){ + /* Set temporary minutia pointer. */ + minutia = minutiae->list[i]; + + /* Initialize remove flag to FALSE. */ + removed = FALSE; + + /* Compute block coords from minutia point. */ + blk_x = minutia->x / lfsparms->blocksize; + blk_y = minutia->y / lfsparms->blocksize; + + /* If minutia in LOW RIDGE FLOW or HIGH CURVATURE block */ + /* with a valid direction ... */ + if((*(low_flow_map+(blk_y*mw)+blk_x) || + *(high_curve_map+(blk_y*mw)+blk_x)) && + (*(direction_map+(blk_y*mw)+blk_x) >= 0)){ + /* Compute radian angle from minutia direction. */ + theta = (double)minutia->direction * pi_factor; + /* Compute sine and cosine factors of this angle. */ + sin_theta = sin(theta); + cos_theta = cos(theta); + /* Translate the minutia point (ex. 3 pixels) in opposite */ + /* direction minutia is pointing. Call this point 'R'. */ + drx = (double)minutia->x - + (sin_theta * (double)lfsparms->pores_trans_r); + dry = (double)minutia->y + + (cos_theta * (double)lfsparms->pores_trans_r); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + drx = trunc_dbl_precision(drx, TRUNC_SCALE); + dry = trunc_dbl_precision(dry, TRUNC_SCALE); + rx = sround(drx); + ry = sround(dry); + + /* If 'R' is opposite color from minutia type ... */ + if(*(bdata+(ry*iw)+rx) != minutia->type){ + + /* Search a specified number of steps (ex. 12) from 'R' in a */ + /* perpendicular direction from the minutia direction until */ + /* the first white pixel is found. If a white pixel is */ + /* found within the specified number of steps, then call */ + /* this point 'P' (storing the point's edge pixel as well). */ + if(search_in_direction(&px, &py, &pex, &pey, + minutia->type, + rx, ry, -cos_theta, -sin_theta, + lfsparms->pores_perp_steps, + bdata, iw, ih)){ + /* Trace contour from P's edge pixel in counter-clockwise */ + /* scan and step along specified number of steps (ex. 10). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_fwd, + px, py, px, py, pex, pey, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + print2log("%d,%d RMB\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as point 'B'. */ + bx = contour_x[ncontour-1]; + by = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + /* Trace contour from P's edge pixel in clockwise scan */ + /* and step along specified number of steps (ex. 8). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_bwd, + px, py, px, py, pex, pey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + print2log("%d,%d RMD\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as point 'D'. */ + dx = contour_x[ncontour-1]; + dy = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + /* Search a specified number of steps (ex. 12) from */ + /* 'R' in opposite direction of that used to find */ + /* 'P' until the first white pixel is found. If a */ + /* white pixel is found within the specified number */ + /* of steps, then call this point 'Q' (storing the */ + /* point's edge pixel as well). */ + if(search_in_direction(&qx, &qy, &qex, &qey, + minutia->type, + rx, ry, cos_theta, sin_theta, + lfsparms->pores_perp_steps, + bdata, iw, ih)){ + /* Trace contour from Q's edge pixel in clockwise */ + /* scan and step along specified number of steps */ + /* (ex. 10). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_fwd, + qx, qy, qx, qy, qex, qey, + SCAN_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during trace ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_fwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + print2log("%d,%d RMA\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as point 'A'. */ + ax = contour_x[ncontour-1]; + ay = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + /* Trace contour from Q's edge pixel in */ + /* counter-clockwise scan and step along a */ + /* specified number of steps (ex. 8). */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->pores_steps_bwd, + qx, qy, qx, qy, qex, qey, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + + /* If system error occurred during scan ... */ + if(ret < 0){ + /* Return error code. */ + return(ret); + } + + /* If trace was not possible OR loop found OR */ + /* contour is incomplete ... */ + if((ret == IGNORE) || + (ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)){ + /* If contour allocated and returned ... */ + if((ret == LOOP_FOUND) || + (ncontour < lfsparms->pores_steps_bwd)) + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + print2log("%d,%d RMC\n", + minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, traced contour is complete. */ + else{ + /* Store last point in contour as 'C'. */ + cx = contour_x[ncontour-1]; + cy = contour_y[ncontour-1]; + /* Deallocate the contour. */ + free_contour(contour_x, contour_y, + contour_ex, contour_ey); + + /* Compute squared distance between points */ + /* 'A' and 'B'. */ + ab2 = squared_distance(ax, ay, bx, by); + /* Compute squared distance between points */ + /* 'C' and 'D'. */ + cd2 = squared_distance(cx, cy, dx, dy); + /* If CD distance is not near zero */ + /* (ex. 0.5) ... */ + if(cd2 > lfsparms->pores_min_dist2){ + /* Compute ratio of squared distances. */ + ratio = ab2 / cd2; + + /* If ratio is small enough (ex. 2.25)...*/ + if(ratio <= lfsparms->pores_max_ratio){ + + print2log("%d,%d ", + minutia->x, minutia->y); + print2log("R=%d,%d P=%d,%d B=%d,%d D=%d,%d Q=%d,%d A=%d,%d C=%d,%d ", + rx, ry, px, py, bx, by, dx, dy, qx, qy, ax, ay, cx, cy); + print2log("RMRATIO %f\n", ratio); + + /* Then assume pore & remove minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + /* Otherwise, ratio to big, so assume */ + /* legitimate minutia. */ + } /* Else, cd2 too small. */ + } /* Done with C. */ + } /* Done with A. */ + } + /* Otherwise, Q not found ... */ + else{ + + print2log("%d,%d RMQ\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } /* Done with Q. */ + } /* Done with D. */ + } /* Done with B. */ + } + /* Otherwise, P not found ... */ + else{ + + print2log("%d,%d RMP\n", minutia->x, minutia->y); + + /* Then remove the minutia. */ + if((ret = remove_minutia(i, minutiae))) + /* If system error, return error code. */ + return(ret); + /* Set remove flag to TRUE. */ + removed = TRUE; + } + } /* Else, R is on pixel the same color as type, so do not */ + /* remove minutia point and skip to next one. */ + } /* Else block is unreliable or has INVALID direction. */ + + /* If current minutia not removed ... */ + if(!removed) + /* Bump to next minutia in list. */ + i++; + /* Otherwise, next minutia has slid into slot of current removed one. */ + + } /* End While minutia remaining in list. */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_or_adjust_side_minutiae - Removes loops or minutia points that +#cat: are not on complete contours of specified length. If the +#cat: contour is complete, then the minutia is adjusted based +#cat: on a minmax analysis of the rotated y-coords of the contour. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_or_adjust_side_minutiae(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int i, j, ret; + MINUTIA *minutia; + double pi_factor, theta, sin_theta, cos_theta; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int *rot_y, minloc; + int *minmax_val, *minmax_i, *minmax_type, minmax_alloc, minmax_num; + double drot_y; + + print2log("\nADJUSTING SIDE MINUTIA:\n"); + + /* Allocate working memory for holding rotated y-coord of a */ + /* minutia's contour. */ + rot_y = (int *)malloc(((lfsparms->side_half_contour<<1)+1) * sizeof(int)); + if(rot_y == (int *)NULL){ + fprintf(stderr, + "ERROR : remove_or_adjust_side_minutiae : malloc : rot_y\n"); + return(-280); + } + + /* Compute factor for converting integer directions to radians. */ + pi_factor = M_PI / (double)lfsparms->num_directions; + + i = 0; + /* Foreach minutia remaining in list ... */ + while(i < minutiae->num){ + /* Assign a temporary pointer. */ + minutia = minutiae->list[i]; + + /* Extract a contour centered on the minutia point (ex. 7 pixels */ + /* in both directions). */ + ret = get_centered_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->side_half_contour, + minutia->x, minutia->y, minutia->ex, minutia->ey, + bdata, iw, ih); + + /* If system error occurred ... */ + if(ret < 0){ + /* Deallocate working memory. */ + free(rot_y); + /* Return error code. */ + return(ret); + } + + /* If we didn't succeed in extracting a complete contour for any */ + /* other reason ... */ + if((ret == LOOP_FOUND) || + (ret == IGNORE) || + (ret == INCOMPLETE)){ + + print2log("%d,%d RM1\n", minutia->x, minutia->y); + + /* Remove minutia from list. */ + if((ret = remove_minutia(i, minutiae))){ + /* Deallocate working memory. */ + free(rot_y); + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + } + /* Otherwise, a complete contour was found and extracted ... */ + else{ + /* Rotate contour points by negative angle of feature's direction. */ + /* The contour of a well-formed minutia point will form a bowl */ + /* shape concaved in the direction of the minutia. By rotating */ + /* the contour points by the negative angle of feature's direction */ + /* the bowl will be transformed to be concaved upwards and minima */ + /* and maxima of the transformed y-coords can be analyzed to */ + /* determine if the minutia is "well-formed" or not. If well- */ + /* formed then the position of the minutia point is adjusted. If */ + /* not well-formed, then the minutia point is removed altogether. */ + + /* Normal rotation of T degrees around the origin of */ + /* the point (x,y): */ + /* rx = x*cos(T) - y*sin(T) */ + /* ry = x*cos(T) + y*sin(T) */ + /* The rotation here is for -T degrees: */ + /* rx = x*cos(-T) - y*sin(-T) */ + /* ry = x*cos(-T) + y*sin(-T) */ + /* which can be written: */ + /* rx = x*cos(T) + y*sin(T) */ + /* ry = x*sin(T) - y*cos(T) */ + + /* Convert minutia's direction to radians. */ + theta = (double)minutia->direction * pi_factor; + /* Compute sine and cosine values at theta for rotation. */ + sin_theta = sin(theta); + cos_theta = cos(theta); + + for(j = 0; j < ncontour; j++){ + /* We only need to rotate the y-coord (don't worry */ + /* about rotating the x-coord or contour edge pixels). */ + drot_y = ((double)contour_x[j] * sin_theta) - + ((double)contour_y[j] * cos_theta); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + drot_y = trunc_dbl_precision(drot_y, TRUNC_SCALE); + rot_y[j] = sround(drot_y); + } + + /* Locate relative minima and maxima in vector of rotated */ + /* y-coords of current minutia's contour. */ + if((ret = minmaxs(&minmax_val, &minmax_type, &minmax_i, + &minmax_alloc, &minmax_num, + rot_y, ncontour))){ + /* If system error, then deallocate working memories. */ + free(rot_y); + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Return error code. */ + return(ret); + } + + /* If one and only one minima was found in rotated y-coord */ + /* of contour ... */ + if((minmax_num == 1) && + (minmax_type[0] == -1)){ + + print2log("%d,%d AD1 ", minutia->x, minutia->y); + + /* Reset loation of minutia point to contour point at minima. */ + minutia->x = contour_x[minmax_i[0]]; + minutia->y = contour_y[minmax_i[0]]; + minutia->ex = contour_ex[minmax_i[0]]; + minutia->ey = contour_ey[minmax_i[0]]; + /* Advance to the next minutia in the list. */ + i++; + + print2log("%d,%d\n", minutia->x, minutia->y); + + } + /* If exactly 3 min/max found and they are min-max-min ... */ + else if((minmax_num == 3) && + (minmax_type[0] == -1)){ + /* Choose minima location with smallest rotated y-coord. */ + if(minmax_val[0] < minmax_val[2]) + minloc = minmax_i[0]; + else + minloc = minmax_i[2]; + + print2log("%d,%d AD2 ", minutia->x, minutia->y); + + /* Reset loation of minutia point to contour point at minima. */ + minutia->x = contour_x[minloc]; + minutia->y = contour_y[minloc]; + minutia->ex = contour_ex[minloc]; + minutia->ey = contour_ey[minloc]; + /* Advance to the next minutia in the list. */ + i++; + + print2log("%d,%d\n", minutia->x, minutia->y); + + } + /* Otherwise, ... */ + else{ + + print2log("%d,%d RM2\n", minutia->x, minutia->y); + + /* Remove minutia from list. */ + if((ret = remove_minutia(i, minutiae))){ + /* If system error, then deallocate working memories. */ + free(rot_y); + free_contour(contour_x, contour_y, contour_ex, contour_ey); + if(minmax_alloc > 0){ + free(minmax_val); + free(minmax_type); + free(minmax_i); + } + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + + } + + /* Deallocate contour and min/max buffers. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + if(minmax_alloc > 0){ + free(minmax_val); + free(minmax_type); + free(minmax_i); + } + } /* End else contour extracted. */ + } /* End while not end of minutiae list. */ + + /* Deallocate working memory. */ + free(rot_y); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: remove_or_adjust_side_minutiae_V2 - Removes loops or minutia points that +#cat: are not on complete contours of specified length. If the +#cat: contour is complete, then the minutia is adjusted based +#cat: on a minmax analysis of the rotated y-coords of the contour. + + Input: + minutiae - list of true and false minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + direction_map - map of image blocks containing directional ridge flow + mw - width (in blocks) of the map + mh - height (in blocks) of the map + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of pruned minutiae + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_or_adjust_side_minutiae_V2(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + int *direction_map, const int mw, const int mh, + const LFSPARMS *lfsparms) +{ + int i, j, ret; + MINUTIA *minutia; + double pi_factor, theta, sin_theta, cos_theta; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + int *rot_y, minloc; + int *minmax_val, *minmax_i, *minmax_type, minmax_alloc, minmax_num; + double drot_y; + int bx, by; + + print2log("\nADJUSTING SIDE MINUTIA:\n"); + + /* Allocate working memory for holding rotated y-coord of a */ + /* minutia's contour. */ + rot_y = (int *)malloc(((lfsparms->side_half_contour<<1)+1) * sizeof(int)); + if(rot_y == (int *)NULL){ + fprintf(stderr, + "ERROR : remove_or_adjust_side_minutiae_V2 : malloc : rot_y\n"); + return(-630); + } + + /* Compute factor for converting integer directions to radians. */ + pi_factor = M_PI / (double)lfsparms->num_directions; + + i = 0; + /* Foreach minutia remaining in list ... */ + while(i < minutiae->num){ + /* Assign a temporary pointer. */ + minutia = minutiae->list[i]; + + /* Extract a contour centered on the minutia point (ex. 7 pixels */ + /* in both directions). */ + ret = get_centered_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + lfsparms->side_half_contour, + minutia->x, minutia->y, minutia->ex, minutia->ey, + bdata, iw, ih); + + /* If system error occurred ... */ + if(ret < 0){ + /* Deallocate working memory. */ + free(rot_y); + /* Return error code. */ + return(ret); + } + + /* If we didn't succeed in extracting a complete contour for any */ + /* other reason ... */ + if((ret == LOOP_FOUND) || + (ret == IGNORE) || + (ret == INCOMPLETE)){ + + print2log("%d,%d RM1\n", minutia->x, minutia->y); + + /* Remove minutia from list. */ + if((ret = remove_minutia(i, minutiae))){ + /* Deallocate working memory. */ + free(rot_y); + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + } + /* Otherwise, a complete contour was found and extracted ... */ + else{ + /* Rotate contour points by negative angle of feature's direction. */ + /* The contour of a well-formed minutia point will form a bowl */ + /* shape concaved in the direction of the minutia. By rotating */ + /* the contour points by the negative angle of feature's direction */ + /* the bowl will be transformed to be concaved upwards and minima */ + /* and maxima of the transformed y-coords can be analyzed to */ + /* determine if the minutia is "well-formed" or not. If well- */ + /* formed then the position of the minutia point is adjusted. If */ + /* not well-formed, then the minutia point is removed altogether. */ + + /* Normal rotation of T degrees around the origin of */ + /* the point (x,y): */ + /* rx = x*cos(T) - y*sin(T) */ + /* ry = x*cos(T) + y*sin(T) */ + /* The rotation here is for -T degrees: */ + /* rx = x*cos(-T) - y*sin(-T) */ + /* ry = x*cos(-T) + y*sin(-T) */ + /* which can be written: */ + /* rx = x*cos(T) + y*sin(T) */ + /* ry = x*sin(T) - y*cos(T) */ + + /* Convert minutia's direction to radians. */ + theta = (double)minutia->direction * pi_factor; + /* Compute sine and cosine values at theta for rotation. */ + sin_theta = sin(theta); + cos_theta = cos(theta); + + for(j = 0; j < ncontour; j++){ + /* We only need to rotate the y-coord (don't worry */ + /* about rotating the x-coord or contour edge pixels). */ + drot_y = ((double)contour_x[j] * sin_theta) - + ((double)contour_y[j] * cos_theta); + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + drot_y = trunc_dbl_precision(drot_y, TRUNC_SCALE); + rot_y[j] = sround(drot_y); + } + + /* Locate relative minima and maxima in vector of rotated */ + /* y-coords of current minutia's contour. */ + if((ret = minmaxs(&minmax_val, &minmax_type, &minmax_i, + &minmax_alloc, &minmax_num, + rot_y, ncontour))){ + /* If system error, then deallocate working memories. */ + free(rot_y); + free_contour(contour_x, contour_y, contour_ex, contour_ey); + /* Return error code. */ + return(ret); + } + + /* If one and only one minima was found in rotated y-coord */ + /* of contour ... */ + if((minmax_num == 1) && + (minmax_type[0] == -1)){ + + print2log("%d,%d ", minutia->x, minutia->y); + + /* Reset loation of minutia point to contour point at minima. */ + minutia->x = contour_x[minmax_i[0]]; + minutia->y = contour_y[minmax_i[0]]; + minutia->ex = contour_ex[minmax_i[0]]; + minutia->ey = contour_ey[minmax_i[0]]; + + /* Must check if adjusted minutia is now in INVALID block ... */ + bx = minutia->x/lfsparms->blocksize; + by = minutia->y/lfsparms->blocksize; + if(*(direction_map+(by*mw)+bx) == INVALID_DIR){ + /* Remove minutia from list. */ + if((ret = remove_minutia(i, minutiae))){ + /* Deallocate working memory. */ + free(rot_y); + free_contour(contour_x, contour_y, contour_ex, contour_ey); + if(minmax_alloc > 0){ + free(minmax_val); + free(minmax_type); + free(minmax_i); + } + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + + print2log("RM2\n"); + } + else{ + /* Advance to the next minutia in the list. */ + i++; + print2log("AD1 %d,%d\n", minutia->x, minutia->y); + } + + } + /* If exactly 3 min/max found and they are min-max-min ... */ + else if((minmax_num == 3) && + (minmax_type[0] == -1)){ + /* Choose minima location with smallest rotated y-coord. */ + if(minmax_val[0] < minmax_val[2]) + minloc = minmax_i[0]; + else + minloc = minmax_i[2]; + + print2log("%d,%d ", minutia->x, minutia->y); + + /* Reset loation of minutia point to contour point at minima. */ + minutia->x = contour_x[minloc]; + minutia->y = contour_y[minloc]; + minutia->ex = contour_ex[minloc]; + minutia->ey = contour_ey[minloc]; + + /* Must check if adjusted minutia is now in INVALID block ... */ + bx = minutia->x/lfsparms->blocksize; + by = minutia->y/lfsparms->blocksize; + if(*(direction_map+(by*mw)+bx) == INVALID_DIR){ + /* Remove minutia from list. */ + if((ret = remove_minutia(i, minutiae))){ + /* Deallocate working memory. */ + free(rot_y); + free_contour(contour_x, contour_y, contour_ex, contour_ey); + if(minmax_alloc > 0){ + free(minmax_val); + free(minmax_type); + free(minmax_i); + } + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + + print2log("RM3\n"); + } + else{ + /* Advance to the next minutia in the list. */ + i++; + print2log("AD2 %d,%d\n", minutia->x, minutia->y); + } + } + /* Otherwise, ... */ + else{ + + print2log("%d,%d RM4\n", minutia->x, minutia->y); + + /* Remove minutia from list. */ + if((ret = remove_minutia(i, minutiae))){ + /* If system error, then deallocate working memories. */ + free(rot_y); + free_contour(contour_x, contour_y, contour_ex, contour_ey); + if(minmax_alloc > 0){ + free(minmax_val); + free(minmax_type); + free(minmax_i); + } + /* Return error code. */ + return(ret); + } + /* No need to advance because next minutia has "slid" */ + /* into position pointed to by 'i'. */ + } + + /* Deallocate contour and min/max buffers. */ + free_contour(contour_x, contour_y, contour_ex, contour_ey); + if(minmax_alloc > 0){ + free(minmax_val); + free(minmax_type); + free(minmax_i); + } + } /* End else contour extracted. */ + } /* End while not end of minutiae list. */ + + /* Deallocate working memory. */ + free(rot_y); + + /* Return normally. */ + return(0); +} + diff --git a/libfprint/nbis/mindtct/results.c b/libfprint/nbis/mindtct/results.c new file mode 100644 index 0000000..1f8f1db --- /dev/null +++ b/libfprint/nbis/mindtct/results.c @@ -0,0 +1,682 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: RESULTS.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 10/04/1999 Version 2 by MDG + 09/14/2004 + UPDATED: 03/16/2005 by MDG + + Contains routines useful in visualizing intermediate and final + results when exercising the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + write_text_results() + write_minutiae_XYTQ() + dump_map() + drawmap() + drawmap2() + drawblocks() + drawrotgrid() + dump_link_table() + +***********************************************************************/ + +#include +#include +#include +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: write_text_results - Takes LFS results including minutiae and image +#cat: maps and writes them to separate formatted text files. + + Input: + oroot - root pathname for output files + m1flag - if flag set, write (X,Y,T)'s out to "*.xyt" file according + to M1 (ANSI INCITS 378-2004) minutiae representation + + M1 Rep: + 1. pixel origin top left + 2. direction pointing up the ridge ending or + bifurcaiton valley + NIST Internal Rep: + 1. pixel origin bottom left + 2. direction pointing out and away from the + ridge ending or bifurcation valley + + iw - image pixel width + ih - image pixel height + minutiae - structure containing the detected minutiae + quality_map - integrated image quality map + direction_map - direction map + low_contrast_map - low contrast map + low_flow_map - low ridge flow map + high_curve_map - high curvature map + map_w - width (in blocks) of image maps + map_h - height (in blocks) of image maps + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int write_text_results(char *oroot, const int m1flag, + const int iw, const int ih, + const MINUTIAE *minutiae, int *quality_map, + int *direction_map, int *low_contrast_map, + int *low_flow_map, int *high_curve_map, + const int map_w, const int map_h) +{ + FILE *fp; + int ret; + char ofile[MAXPATHLEN]; + + /* 1. Write Minutiae results to text file ".min". */ + /* XYT's written in LFS native representation: */ + /* 1. pixel coordinates with origin top-left */ + /* 2. 11.25 degrees quantized integer orientation */ + /* on range [0..31] */ + /* 3. minutiae reliability on range [0.0 .. 1.0] */ + /* with 0.0 lowest and 1.0 highest reliability */ + sprintf(ofile, "%s.%s", oroot, MIN_TXT_EXT); + if((fp = fopen(ofile, "wb")) == (FILE *)NULL){ + fprintf(stderr, "ERROR : write_text_results : fopen : %s\n", ofile); + return(-2); + } + /* Print out Image Dimensions Header */ + /* !!!! Image dimension header added 09-13-04 !!!! */ + fprintf(fp, "Image (w,h) %d %d\n", iw, ih); + /* Print text report from the structure containing detected minutiae. */ + dump_minutiae(fp, minutiae); + if(fclose(fp)){ + fprintf(stderr, "ERROR : write_text_results : fclose : %s\n", ofile); + return(-3); + } + + /* 2. Write just minutiae XYT's & Qualities to text */ + /* file ".xyt". */ + /* */ + /* A. If M1 flag set: */ + /* XYTQ's written according to M1 (ANSI INCITS */ + /* 378-2004) representation: */ + /* 1. pixel coordinates with origin top-left */ + /* 2. orientation in degrees on range [0..360] */ + /* with 0 pointing east and increasing counter */ + /* clockwise */ + /* 3. direction pointing up the ridge ending or */ + /* bifurcaiton valley */ + /* 4. minutiae qualities on integer range [0..100] */ + /* (non-standard) */ + /* */ + /* B. If M1 flag NOT set: */ + /* XYTQ's written according to NIST internal rep. */ + /* 1. pixel coordinates with origin bottom-left */ + /* 2. orientation in degrees on range [0..360] */ + /* with 0 pointing east and increasing counter */ + /* clockwise (same as M1) */ + /* 3. direction pointing out and away from the */ + /* ridge ending or bifurcation valley */ + /* (opposite direction from M1) */ + /* 4. minutiae qualities on integer range [0..100] */ + /* (non-standard) */ + sprintf(ofile, "%s.%s", oroot, XYT_EXT); + if(m1flag){ + if((ret = write_minutiae_XYTQ(ofile, M1_XYT_REP, minutiae, iw, ih))){ + return(ret); + } + } + else{ + if((ret = write_minutiae_XYTQ(ofile, NIST_INTERNAL_XYT_REP, + minutiae, iw, ih))){ + return(ret); + } + } + + /* 3. Write Integrated Quality Map results to text file. */ + sprintf(ofile, "%s.%s", oroot, QUALITY_MAP_EXT); + if((fp = fopen(ofile, "wb")) == (FILE *)NULL){ + fprintf(stderr, "ERROR : write_text_results : fopen : %s\n", ofile); + return(-4); + } + /* Print a text report from the map. */ + dump_map(fp, quality_map, map_w, map_h); + if(fclose(fp)){ + fprintf(stderr, "ERROR : write_text_results : fclose : %s\n", ofile); + return(-5); + } + + /* 4. Write Direction Map results to text file. */ + sprintf(ofile, "%s.%s", oroot, DIRECTION_MAP_EXT); + if((fp = fopen(ofile, "wb")) == (FILE *)NULL){ + fprintf(stderr, "ERROR : write_text_results : fopen : %s\n", ofile); + return(-6); + } + /* Print a text report from the map. */ + dump_map(fp, direction_map, map_w, map_h); + if(fclose(fp)){ + fprintf(stderr, "ERROR : write_text_results : fclose : %s\n", ofile); + return(-7); + } + + /* 5. Write Low Contrast Map results to text file. */ + sprintf(ofile, "%s.%s", oroot, LOW_CONTRAST_MAP_EXT); + if((fp = fopen(ofile, "wb")) == (FILE *)NULL){ + fprintf(stderr, "ERROR : write_text_results : fopen : %s\n", ofile); + return(-8); + } + /* Print a text report from the map. */ + dump_map(fp, low_contrast_map, map_w, map_h); + if(fclose(fp)){ + fprintf(stderr, "ERROR : write_text_results : fclose : %s\n", ofile); + return(-9); + } + + /* 6. Write Low Flow Map results to text file. */ + sprintf(ofile, "%s.%s", oroot, LOW_FLOW_MAP_EXT); + if((fp = fopen(ofile, "wb")) == (FILE *)NULL){ + fprintf(stderr, "ERROR : write_text_results : fopen : %s\n", ofile); + return(-10); + } + /* Print a text report from the map. */ + dump_map(fp, low_flow_map, map_w, map_h); + if(fclose(fp)){ + fprintf(stderr, "ERROR : write_text_results : fclose : %s\n", ofile); + return(-11); + } + + /* 7. Write High Curvature Map results to text file. */ + sprintf(ofile, "%s.%s", oroot, HIGH_CURVE_MAP_EXT); + if((fp = fopen(ofile, "wb")) == (FILE *)NULL){ + fprintf(stderr, "ERROR : write_text_results : fopen : %s\n", ofile); + return(-12); + } + /* Print a text report from the map. */ + dump_map(fp, high_curve_map, map_w, map_h); + if(fclose(fp)){ + fprintf(stderr, "ERROR : write_text_results : fclose : %s\n", ofile); + return(-13); + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: write_minutiae_XYTQ - Write just minutiae XYT's & Qualities to text +#cat: file according to the specified mintuiae represenation + + Input: + ofile - output file name + reptype - specifies XYT output representation + minutiae - structure containing a list of LFS detected minutiae + iw - width (in pixels) of the input image + ih - height (in pixels) of the input image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int write_minutiae_XYTQ(char *ofile, const int reptype, + const MINUTIAE *minutiae, const int iw, const int ih) +{ + FILE *fp; + int i, ox, oy, ot, oq; + MINUTIA *minutia; + + /* A. If M1 flag set: */ + /* XYTQ's written according to M1 (ANSI INCITS */ + /* 378-2004) representation: */ + /* 1. pixel coordinates with origin top-left */ + /* 2. orientation in degrees on range [0..360] */ + /* with 0 pointing east and increasing counter */ + /* clockwise */ + /* 3. direction pointing up the ridge ending or */ + /* bifurcaiton valley */ + /* 4. minutiae qualities on integer range [0..100] */ + /* (non-standard) */ + /* */ + /* B. If M1 flag NOT set: */ + /* XYTQ's written according to NIST internal rep. */ + /* 1. pixel coordinates with origin bottom-left */ + /* 2. orientation in degrees on range [0..360] */ + /* with 0 pointing east and increasing counter */ + /* clockwise (same as M1) */ + /* 3. direction pointing out and away from the */ + /* ridge ending or bifurcation valley */ + /* (opposite direction from M1) */ + /* 4. minutiae qualities on integer range [0..100] */ + /* (non-standard) */ + + if((fp = fopen(ofile, "wb")) == (FILE *)NULL){ + fprintf(stderr, "ERROR : write_minutiae_XYTQ : fopen : %s\n", ofile); + return(-2); + } + + for(i = 0; i < minutiae->num; i++){ + minutia = minutiae->list[i]; + + switch(reptype){ + case M1_XYT_REP: + lfs2m1_minutia_XYT(&ox, &oy, &ot, minutia); + break; + case NIST_INTERNAL_XYT_REP: + lfs2nist_minutia_XYT(&ox, &oy, &ot, minutia, iw, ih); + break; + default: + fprintf(stderr, "ERROR : write_minutiae_XYTQ : "); + fprintf(stderr, "Invalid XYT representation type = %d\n", reptype); + fclose(fp); + return(-4); + } + + oq = sround(minutia->reliability * 100.0); + + fprintf(fp, "%d %d %d %d\n", ox, oy, ot, oq); + } + + + if(fclose(fp)){ + fprintf(stderr, "ERROR : write_minutiae_XYTQ : fopen : %s\n", ofile); + return(-5); + } + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: dump_map - Prints a text report to the specified open file pointer +#cat: of the integer values in a 2D integer vector. + + Input: + fpout - open file pointer + map - vector of integer directions (-1 ==> invalid direction) + mw - width (number of blocks) of map vector + mh - height (number of blocks) of map vector +**************************************************************************/ +void dump_map(FILE *fpout, int *map, const int mw, const int mh) +{ + int mx, my; + int *iptr; + + /* Simply print the map matrix out to the specified file pointer. */ + iptr = map; + for(my = 0; my < mh; my++){ + for(mx = 0; mx < mw; mx++){ + fprintf(fpout, "%2d ", *iptr++); + } + fprintf(fpout, "\n"); + } +} + +/************************************************************************* +************************************************************************** +#cat: drawmap - Draws integer direction vectors over their respective blocks +#cat: of an input image. Note that the input image is modified +#cat: upon return form this routine. + + Input: + imap - computed vector of integer directions. (-1 ==> invalid) + mw - width (in blocks) of the map + mh - height (in blocks) of the map + idata - input image data to be annotated + iw - width (in pixels) of the input image + ih - height (in pixels) of the input image + rotgrids - structure containing the rotated pixel grid offsets + draw_pixel - pixel intensity to be used when drawing on the image + Output: + idata - input image contains the results of the annoatation + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int drawmap(int *imap, const int mw, const int mh, + unsigned char *idata, const int iw, const int ih, + const ROTGRIDS *dftgrids, const int draw_pixel) +{ + int bi, *iptr; + double dy, dx, xincr, yincr; + int cbxy; + int i, xyoffset; + unsigned char *cptr, *lptr, *rptr, *eptr; + double theta, pi_incr; + int *blkoffs, bw, bh; + int ret; /* return code */ + + /* Compute block offsets into the input image. */ + /* Block_offsets() assumes square block (grid), so ERROR otherwise. */ + if(dftgrids->grid_w != dftgrids->grid_h){ + fprintf(stderr, "ERROR : drawmap : DFT grids must be square\n"); + return(-130); + } + if((ret = block_offsets(&blkoffs, &bw, &bh, iw, ih, + dftgrids->pad, dftgrids->grid_w))){ + return(ret); + } + + if((bw != mw) || (bh != mh)){ + /* Free memory allocated to this point. */ + free(blkoffs); + fprintf(stderr, + "ERROR : drawmap : block dimensions between map and image do not match\n"); + return(-131); + } + + cbxy = dftgrids->grid_w>>1; + pi_incr = M_PI/(double)dftgrids->ngrids; + + eptr = idata + (ih*iw); + iptr = imap; + /* Foreach block in image ... */ + for(bi = 0; bi < mw*mh; bi++){ + + /* If valid direction for block ... */ + if(*iptr != INVALID_DIR){ + + /* Get slope components of direction angle */ + theta = dftgrids->start_angle + (*iptr * pi_incr); + dx = cos(theta); + dy = sin(theta); + + /* Draw line rotated by the direction angle and centered */ + /* on the block. */ + /* Check if line is perfectly vertical ... */ + if(dx == 0){ + /* Draw vertical line starting at top of block shifted */ + /* over to horizontal center of the block. */ + lptr = idata + blkoffs[bi] + cbxy; + for(i = 0; i < dftgrids->grid_w; i++){ + if((lptr > idata) && (lptr < eptr)){ + *lptr = draw_pixel; + } + lptr += iw; + } + } + else{ + cptr = idata + blkoffs[bi] + (cbxy*iw) + cbxy; + + /* Draw center pixel */ + *cptr = draw_pixel; + + /* Draw left and right half of line */ + xincr = dx; + yincr = dy; + for(i = 0; i < cbxy; i++){ + xyoffset = (sround(yincr)*iw) + sround(xincr); + rptr = cptr + xyoffset; + if((rptr > idata) && (rptr < eptr)){ + *rptr = draw_pixel; + } + lptr = cptr - xyoffset; + if((lptr > idata) && (lptr < eptr)){ + *lptr = draw_pixel; + } + xincr += dx; + yincr += dy; + } + } + } + iptr++; + } + + /* Deallocate working memory */ + free(blkoffs); + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: drawmap2 - Draws integer direction vectors over their respective blocks +#cat: of an input image. Note that the input image is modified +#cat: upon return form this routine. In this version of the +#cat: routine, offsets to the origin of each block in the image +#cat: must be precomputed and passed in. + + Input: + imap - computed vector of integer directions. (-1 ==> invalid) + blkoffs - list of pixel offsets to the origin of each block in the + image from which the map was computed + mw - width (in blocks) of the map + mh - height (in blocks) of the map + pdata - input image data to be annotated + pw - width (in pixels) of the input image + ph - height (in pixels) of the input image + start_angle - the angle (in radians) that the direction 0 points in + ndirs - number of directions within a half circle + blocksize - the dimensions (in pixels) of each block + Output: + pdata - input image contains the results of the annoatation +**************************************************************************/ +void drawmap2(int *imap, const int *blkoffs, const int mw, const int mh, + unsigned char *pdata, const int pw, const int ph, + const double start_angle, const int ndirs, const int blocksize) +{ + int bi, *iptr; + double dy, dx, xincr, yincr; + int cbxy; + int i, xyoffset; + unsigned char *cptr, *lptr, *rptr, *eptr; + double theta, pi_incr; + + cbxy = blocksize>>1; + pi_incr = M_PI/(double)ndirs; + + eptr = pdata + (pw*ph); + iptr = imap; + /* Foreach block in image ... */ + for(bi = 0; bi < mw*mh; bi++){ + + /* If valid direction for block ... */ + if(*iptr != INVALID_DIR){ + + /* Get slope components of direction angle */ + theta = start_angle + (*iptr * pi_incr); + dx = cos((double)theta); + dy = sin((double)theta); + + /* Draw line rotated by the direction angle and centered */ + /* on the block. */ + /* Check if line is perfectly vertical ... */ + if(dx == 0){ + /* Draw vertical line starting at top of block shifted */ + /* over to horizontal center of the block. */ + lptr = pdata + blkoffs[bi] + cbxy; + for(i = 0; i < blocksize; i++){ + if((lptr > pdata) && (lptr < eptr)) + *lptr = 255; + lptr += pw; + } + } + else{ + cptr = pdata + blkoffs[bi] + (cbxy*pw) + cbxy; + + /* Draw center pixel */ + *cptr = 255; + + /* Draw left and right half of line */ + xincr = dx; + yincr = dy; + for(i = 0; i < cbxy; i++){ + xyoffset = (sround(yincr)*pw) + sround(xincr); + rptr = cptr + xyoffset; + if((rptr > pdata) && (rptr < eptr)) + *rptr = 255; + lptr = cptr - xyoffset; + if((lptr > pdata) && (lptr < eptr)) + *lptr = 255; + xincr += dx; + yincr += dy; + } + } + } + iptr++; + } +} + +/************************************************************************* +************************************************************************** +#cat: drawblocks - Annotates an input image with the location of each block's +#cat: origin. This routine is useful to see how blocks are +#cat: assigned to arbitrarily-sized images that are not an even +#cat: width or height of the block size. In these cases the last +#cat: column pair and row pair of blocks overlap each other. +#cat: Note that the input image is modified upon return form +#cat: this routine. + + Input: + blkoffs - offsets to the pixel origin of each block in the image + mw - number of blocks horizontally in the input image + mh - number of blocks vertically in the input image + pdata - input image data to be annotated that has pixel dimensions + compatible with the offsets in blkoffs + pw - width (in pixels) of the input image + ph - height (in pixels) of the input image + draw_pixel - pixel intensity to be used when drawing on the image + Output: + pdata - input image contains the results of the annoatation +**************************************************************************/ +void drawblocks(const int *blkoffs, const int mw, const int mh, + unsigned char *pdata, const int pw, const int ph, + const int draw_pixel) +{ + int bi; + unsigned char *bptr; + + for(bi = 0; bi < mw*mh; bi++){ + bptr = pdata + blkoffs[bi]; + *bptr = draw_pixel; + } +} + + +/************************************************************************* +************************************************************************** +#cat: drawrotgrid - Annotates an input image with a specified rotated grid. +#cat: This routine is useful to see the location and orientation +#cat: of a specific rotated grid within a specific block in the +#cat: image. Note that the input image is modified upon return +#cat: form this routine. + + Input: + rotgrids - structure containing the rotated pixel grid offsets + dir - integer direction of the rotated grid to be annontated + idata - input image data to be annotated. + blkoffset - the pixel offset from the origin of the input image to + the origin of the specific block to be annoted + iw - width (in pixels) of the input image + ih - height (in pixels) of the input image + draw_pixel - pixel intensity to be used when drawing on the image + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int drawrotgrid(const ROTGRIDS *rotgrids, const int dir, + unsigned char *idata, const int blkoffset, + const int iw, const int ih, const int draw_pixel) +{ + int i, j, gi; + + /* Check if specified rotation direction is within range of */ + /* rotated grids. */ + if(dir >= rotgrids->ngrids){ + fprintf(stderr, + "ERROR : drawrotgrid : input direction exceeds range of rotated grids\n"); + return(-140); + } + + /* Intialize grid offset index */ + gi = 0; + /* Foreach row in rotated grid ... */ + for(i = 0; i < rotgrids->grid_h; i++){ + /* Foreach column in rotated grid ... */ + for(j = 0; j < rotgrids->grid_w; j++){ + /* Draw pixels from every other rotated row to represent direction */ + /* of line sums used in DFT processing. */ + if(i%2) + *(idata+blkoffset+rotgrids->grids[dir][gi]) = draw_pixel; + /* Bump grid offset index */ + gi++; + } + } + + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: dump_link_table - takes a link table and vectors of minutia IDs +#cat: assigned to its axes and prints the table's contents out +#cat: as formatted text to the specified open file pointer. + + Input: + fpout - open file pointer + link_table - sparse 2D table containing scores of potentially linked + minutia pairs + x_axis - minutia IDs registered along x-axis + y_axis - minutia IDs registered along y-axis + nx_axis - number of minutia registered along x-axis + ny_axis - number of minutia registered along y-axis + tbldim - dimension of each axes of the link table + minutiae - list of minutia points +**************************************************************************/ +void dump_link_table(FILE *fpout, const int *link_table, + const int *x_axis, const int *y_axis, + const int nx_axis, const int ny_axis, const int tbldim, + const MINUTIAE *minutiae) +{ + int i, tx, ty, sentry, entry; + + fprintf(fpout, "DUMP LINK TABLE:\n"); + + fprintf(fpout, "X-AXIS:\n"); + for(i = 0; i < nx_axis; i++){ + fprintf(fpout, "%d: %d,%d\n", i, minutiae->list[x_axis[i]]->x, + minutiae->list[x_axis[i]]->y); + } + + fprintf(fpout, "Y-AXIS:\n"); + for(i = 0; i < ny_axis; i++){ + fprintf(fpout, "%d: %d,%d\n", i, minutiae->list[y_axis[i]]->x, + minutiae->list[y_axis[i]]->y); + } + + fprintf(fpout, "TABLE:\n"); + sentry = 0; + for(ty = 0; ty < ny_axis; ty++){ + entry = sentry; + for(tx = 0; tx < nx_axis; tx++){ + fprintf(fpout, "%7d ", link_table[entry++]); + } + fprintf(fpout, "\n"); + sentry += tbldim; + } +} + diff --git a/libfprint/nbis/mindtct/ridges.c b/libfprint/nbis/mindtct/ridges.c new file mode 100644 index 0000000..8cbfbe7 --- /dev/null +++ b/libfprint/nbis/mindtct/ridges.c @@ -0,0 +1,831 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: RIDGES.C + AUTHOR: Michael D. Garris + DATE: 08/09/1999 + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for locating nearest minutia + neighbors and counting intervening ridges as part of the + NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + count_minutiae_ridges() + count_minutia_ridges() + find_neighbors() + update_nbr_dists() + insert_neighbor() + sort_neighbors() + ridge_count() + find_transition() + validate_ridge_crossing() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: count_minutiae_ridges - Takes a list of minutiae, and for each one, +#cat: determines its closest neighbors and counts the number +#cat: of interveining ridges between the minutia point and +#cat: each of its neighbors. + + Input: + minutiae - list of minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - list of minutiae augmented with neighbors and ridge counts + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int count_minutiae_ridges(MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int ret; + int i; + + print2log("\nFINDING NBRS AND COUNTING RIDGES:\n"); + + /* Sort minutia points on x then y (column-oriented). */ + if((ret = sort_minutiae_x_y(minutiae, iw, ih))){ + return(ret); + } + + /* Remove any duplicate minutia points from the list. */ + if((ret = rm_dup_minutiae(minutiae))){ + return(ret); + } + + /* Foreach remaining sorted minutia in list ... */ + for(i = 0; i < minutiae->num-1; i++){ + /* Located neighbors and count number of ridges in between. */ + /* NOTE: neighbor and ridge count results are stored in */ + /* minutiae->list[i]. */ + if((ret = count_minutia_ridges(i, minutiae, bdata, iw, ih, lfsparms))){ + return(ret); + } + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: count_minutia_ridges - Takes a minutia, and determines its closest +#cat: neighbors and counts the number of interveining ridges +#cat: between the minutia point and each of its neighbors. + + Input: + minutia - input minutia + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Output: + minutiae - minutia augmented with neighbors and ridge counts + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int count_minutia_ridges(const int first, MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + int i, ret, *nbr_list, *nbr_nridges, nnbrs; + + /* Find up to the maximum number of qualifying neighbors. */ + if((ret = find_neighbors(&nbr_list, &nnbrs, lfsparms->max_nbrs, + first, minutiae))){ + free(nbr_list); + return(ret); + } + + print2log("NBRS FOUND: %d,%d = %d\n", minutiae->list[first]->x, + minutiae->list[first]->y, nnbrs); + + /* If no neighors found ... */ + if(nnbrs == 0){ + /* Then no list returned and no ridges to count. */ + return(0); + } + + /* Sort neighbors on delta dirs. */ + if((ret = sort_neighbors(nbr_list, nnbrs, first, minutiae))){ + free(nbr_list); + return(ret); + } + + /* Count ridges between first and neighbors. */ + /* List of ridge counts, one for each neighbor stored. */ + nbr_nridges = (int *)malloc(nnbrs * sizeof(int)); + if(nbr_nridges == (int *)NULL){ + free(nbr_list); + fprintf(stderr, "ERROR : count_minutia_ridges : malloc : nbr_nridges\n"); + return(-450); + } + + /* Foreach neighbor found and sorted in list ... */ + for(i = 0; i < nnbrs; i++){ + /* Count the ridges between the primary minutia and the neighbor. */ + ret = ridge_count(first, nbr_list[i], minutiae, bdata, iw, ih, lfsparms); + /* If system error ... */ + if(ret < 0){ + /* Deallocate working memories. */ + free(nbr_list); + free(nbr_nridges); + /* Return error code. */ + return(ret); + } + + /* Otherwise, ridge count successful, so store ridge count to list. */ + nbr_nridges[i] = ret; + } + + /* Assign neighbor indices and ridge counts to primary minutia. */ + minutiae->list[first]->nbrs = nbr_list; + minutiae->list[first]->ridge_counts = nbr_nridges; + minutiae->list[first]->num_nbrs = nnbrs; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: find_neighbors - Takes a primary minutia and a list of all minutiae +#cat: and locates a specified maximum number of closest neighbors +#cat: to the primary point. Neighbors are searched, starting +#cat: in the same pixel column, below, the primary point and then +#cat: along consecutive and complete pixel columns in the image +#cat: to the right of the primary point. + + Input: + max_nbrs - maximum number of closest neighbors to be returned + first - index of the primary minutia point + minutiae - list of minutiae + Output: + onbr_list - points to list of detected closest neighbors + onnbrs - points to number of neighbors returned + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int find_neighbors(int **onbr_list, int *onnbrs, const int max_nbrs, + const int first, MINUTIAE *minutiae) +{ + int ret, second, last_nbr; + MINUTIA *minutia1, *minutia2; + int *nbr_list, nnbrs; + double *nbr_sqr_dists, xdist, xdist2; + + /* Allocate list of neighbor minutiae indices. */ + nbr_list = (int *)malloc(max_nbrs * sizeof(int)); + if(nbr_list == (int *)NULL){ + fprintf(stderr, "ERROR : find_neighbors : malloc : nbr_list\n"); + return(-460); + } + + /* Allocate list of squared euclidean distances between neighbors */ + /* and current primary minutia point. */ + nbr_sqr_dists = (double *)malloc(max_nbrs * sizeof(double)); + if(nbr_sqr_dists == (double *)NULL){ + free(nbr_list); + fprintf(stderr, + "ERROR : find_neighbors : malloc : nbr_sqr_dists\n"); + return(-461); + } + + /* Initialize number of stored neighbors to 0. */ + nnbrs = 0; + /* Assign secondary to one passed current primary minutia. */ + second = first + 1; + /* Compute location of maximum last stored neighbor. */ + last_nbr = max_nbrs - 1; + + /* While minutia (in sorted order) still remian for processing ... */ + /* NOTE: The minutia in the input list have been sorted on X and */ + /* then on Y. So, the neighbors are selected according to those */ + /* that lie below the primary minutia in the same pixel column and */ + /* then subsequently those that lie in complete pixel columns to */ + /* the right of the primary minutia. */ + while(second < minutiae->num){ + /* Assign temporary minutia pointers. */ + minutia1 = minutiae->list[first]; + minutia2 = minutiae->list[second]; + + /* Compute squared distance between minutiae along x-axis. */ + xdist = minutia2->x - minutia1->x; + xdist2 = xdist * xdist; + + /* If the neighbor lists are not full OR the x-distance to current */ + /* secondary is smaller than maximum neighbor distance stored ... */ + if((nnbrs < max_nbrs) || + (xdist2 < nbr_sqr_dists[last_nbr])){ + /* Append or insert the new neighbor into the neighbor lists. */ + if((ret = update_nbr_dists(nbr_list, nbr_sqr_dists, &nnbrs, max_nbrs, + first, second, minutiae))){ + free(nbr_sqr_dists); + return(ret); + } + } + /* Otherwise, if the neighbor lists is full AND the x-distance */ + /* to current secondary is larger than maximum neighbor distance */ + /* stored ... */ + else + /* So, stop searching for more neighbors. */ + break; + + /* Bump to next secondary minutia. */ + second++; + } + + /* Deallocate working memory. */ + free(nbr_sqr_dists); + + /* If no neighbors found ... */ + if(nnbrs == 0){ + /* Deallocate the neighbor list. */ + free(nbr_list); + *onnbrs = 0; + } + /* Otherwise, assign neighbors to output pointer. */ + else{ + *onbr_list = nbr_list; + *onnbrs = nnbrs; + } + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: update_nbr_dists - Takes the current list of neighbors along with a +#cat: primary minutia and a potential new neighbor, and +#cat: determines if the new neighbor is sufficiently close +#cat: to be added to the list of nearest neighbors. If added, +#cat: it is placed in the list in its proper order based on +#cat: squared distance to the primary point. + + Input: + nbr_list - current list of nearest neighbor minutia indices + nbr_sqr_dists - corresponding squared euclidean distance of each + neighbor to the primary minutia point + nnbrs - number of neighbors currently in the list + max_nbrs - maximum number of closest neighbors to be returned + first - index of the primary minutia point + second - index of the secondary (new neighbor) point + minutiae - list of minutiae + Output: + nbr_list - updated list of nearest neighbor indices + nbr_sqr_dists - updated list of nearest neighbor distances + nnbrs - number of neighbors in the update lists + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int update_nbr_dists(int *nbr_list, double *nbr_sqr_dists, + int *nnbrs, const int max_nbrs, + const int first, const int second, MINUTIAE *minutiae) +{ + double dist2; + MINUTIA *minutia1, *minutia2; + int pos, last_nbr; + + /* Compute position of maximum last neighbor stored. */ + last_nbr = max_nbrs - 1; + + /* Assigne temporary minutia pointers. */ + minutia1 = minutiae->list[first]; + minutia2 = minutiae->list[second]; + + /* Compute squared euclidean distance between minutia pair. */ + dist2 = squared_distance(minutia1->x, minutia1->y, + minutia2->x, minutia2->y); + + /* If maximum number of neighbors not yet stored in lists OR */ + /* if the squared distance to current secondary is less */ + /* than the largest stored neighbor distance ... */ + if((*nnbrs < max_nbrs) || + (dist2 < nbr_sqr_dists[last_nbr])){ + + /* Find insertion point in neighbor lists. */ + pos = find_incr_position_dbl(dist2, nbr_sqr_dists, *nnbrs); + /* If the position returned is >= maximum list length (this should */ + /* never happen, but just in case) ... */ + if(pos >= max_nbrs){ + fprintf(stderr, + "ERROR : update_nbr_dists : illegal position for new neighbor\n"); + return(-470); + } + /* Insert the new neighbor into the neighbor lists at the */ + /* specified location. */ + if(insert_neighbor(pos, second, dist2, + nbr_list, nbr_sqr_dists, nnbrs, max_nbrs)) + return(-471); + + /* Otherwise, neighbor inserted successfully, so return normally. */ + return(0); + } + /* Otherwise, the new neighbor is not sufficiently close to be */ + /* added or inserted into the neighbor lists, so ignore the neighbor */ + /* and return normally. */ + else + return(0); + +} + +/************************************************************************* +************************************************************************** +#cat: insert_neighbor - Takes a minutia index and its squared distance to a +#cat: primary minutia point, and inserts them in the specified +#cat: position of their respective lists, shifting previously +#cat: stored values down and off the lists as necessary. + + Input: + pos - postions where values are to be inserted in lists + nbr_index - index of minutia being inserted + nbr_dist2 - squared distance of minutia to its primary point + nbr_list - current list of nearest neighbor minutia indices + nbr_sqr_dists - corresponding squared euclidean distance of each + neighbor to the primary minutia point + nnbrs - number of neighbors currently in the list + max_nbrs - maximum number of closest neighbors to be returned + Output: + nbr_list - updated list of nearest neighbor indices + nbr_sqr_dists - updated list of nearest neighbor distances + nnbrs - number of neighbors in the update lists + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int insert_neighbor(const int pos, const int nbr_index, const double nbr_dist2, + int *nbr_list, double *nbr_sqr_dists, + int *nnbrs, const int max_nbrs) +{ + int i; + + /* If the desired insertion position is beyond one passed the last */ + /* neighbor in the lists OR greater than equal to the maximum ... */ + /* NOTE: pos is zero-oriented while nnbrs and max_nbrs are 1-oriented. */ + if((pos > *nnbrs) || + (pos >= max_nbrs)){ + fprintf(stderr, + "ERROR : insert_neighbor : insertion point exceeds lists\n"); + return(-480); + } + + /* If the neighbor lists are NOT full ... */ + if(*nnbrs < max_nbrs){ + /* Then we have room to shift everything down to make room for new */ + /* neighbor and increase the number of neighbors stored by 1. */ + i = *nnbrs-1; + (*nnbrs)++; + } + /* Otherwise, the neighbors lists are full ... */ + else if(*nnbrs == max_nbrs) + /* So, we must bump the last neighbor in the lists off to make */ + /* room for the new neighbor (ignore last neighbor in lists). */ + i = *nnbrs-2; + /* Otherwise, there is a list overflow error condition */ + /* (shouldn't ever happen, but just in case) ... */ + else{ + fprintf(stderr, + "ERROR : insert_neighbor : overflow in neighbor lists\n"); + return(-481); + } + + /* While we havn't reached the desired insertion point ... */ + while(i >= pos){ + /* Shift the current neighbor down the list 1 positon. */ + nbr_list[i+1] = nbr_list[i]; + nbr_sqr_dists[i+1] = nbr_sqr_dists[i]; + i--; + } + + /* We are now ready to put our new neighbor in the position where */ + /* we shifted everything down from to make room. */ + nbr_list[pos] = nbr_index; + nbr_sqr_dists[pos] = nbr_dist2; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: sort_neighbors - Takes a list of primary minutia and its neighboring +#cat: minutia indices and sorts the neighbors based on their +#cat: position relative to the primary minutia point. Neighbors +#cat: are sorted starting vertical to the primary point and +#cat: proceeding clockwise. + + Input: + nbr_list - list of neighboring minutia indices + nnbrs - number of neighbors in the list + first - the index of the primary minutia point + minutiae - list of minutiae + Output: + nbr_list - neighboring minutia indices in sorted order + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int sort_neighbors(int *nbr_list, const int nnbrs, const int first, + MINUTIAE *minutiae) +{ + double *join_thetas, theta; + int i; + static double pi2 = M_PI*2.0; + + /* List of angles of lines joining the current primary to each */ + /* of the secondary neighbors. */ + join_thetas = (double *)malloc(nnbrs * sizeof(double)); + if(join_thetas == (double *)NULL){ + fprintf(stderr, "ERROR : sort_neighbors : malloc : join_thetas\n"); + return(-490); + } + + for(i = 0; i < nnbrs; i++){ + /* Compute angle to line connecting the 2 points. */ + /* Coordinates are swapped and order of points reversed to */ + /* account for 0 direction is vertical and positive direction */ + /* is clockwise. */ + theta = angle2line(minutiae->list[nbr_list[i]]->y, + minutiae->list[nbr_list[i]]->x, + minutiae->list[first]->y, + minutiae->list[first]->x); + + /* Make sure the angle is positive. */ + theta += pi2; + theta = fmod(theta, pi2); + join_thetas[i] = theta; + } + + /* Sort the neighbor indicies into rank order. */ + bubble_sort_double_inc_2(join_thetas, nbr_list, nnbrs); + + /* Deallocate the list of angles. */ + free(join_thetas); + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: ridge_count - Takes a pair of minutiae, and counts the number of +#cat: ridges crossed along the linear trajectory connecting +#cat: the 2 points in the image. + + Input: + first - index of primary minutia + second - index of secondary (neighbor) minutia + minutiae - list of minutiae + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + lfsparms - parameters and thresholds for controlling LFS + Return Code: + Zero or Positive - number of ridges counted + Negative - system error +**************************************************************************/ +int ridge_count(const int first, const int second, MINUTIAE *minutiae, + unsigned char *bdata, const int iw, const int ih, + const LFSPARMS *lfsparms) +{ + MINUTIA *minutia1, *minutia2; + int i, ret, found; + int *xlist, *ylist, num; + int ridge_count, ridge_start, ridge_end; + int prevpix, curpix; + + minutia1 = minutiae->list[first]; + minutia2 = minutiae->list[second]; + + /* If the 2 mintuia have identical pixel coords ... */ + if((minutia1->x == minutia2->x) && + (minutia1->y == minutia2->y)) + /* Then zero ridges between points. */ + return(0); + + /* Compute linear trajectory of contiguous pixels between first */ + /* and second minutia points. */ + if((ret = line_points(&xlist, &ylist, &num, + minutia1->x, minutia1->y, minutia2->x, minutia2->y))){ + return(ret); + } + + /* It there are no points on the line trajectory, then no ridges */ + /* to count (this should not happen, but just in case) ... */ + if(num == 0){ + free(xlist); + free(ylist); + return(0); + } + + /* Find first pixel opposite type along linear trajectory from */ + /* first minutia. */ + prevpix = *(bdata+(ylist[0]*iw)+xlist[0]); + i = 1; + found = FALSE; + while(i < num){ + curpix = *(bdata+(ylist[i]*iw)+xlist[i]); + if(curpix != prevpix){ + found = TRUE; + break; + } + i++; + } + + /* If opposite pixel not found ... then no ridges to count */ + if(!found){ + free(xlist); + free(ylist); + return(0); + } + + /* Ready to count ridges, so initialize counter to 0. */ + ridge_count = 0; + + print2log("RIDGE COUNT: %d,%d to %d,%d ", minutia1->x, minutia1->y, + minutia2->x, minutia2->y); + + /* While not at the end of the trajectory ... */ + while(i < num){ + /* If 0-to-1 transition not found ... */ + if(!find_transition(&i, 0, 1, xlist, ylist, num, bdata, iw, ih)){ + /* Then we are done looking for ridges. */ + free(xlist); + free(ylist); + + print2log("\n"); + + /* Return number of ridges counted to this point. */ + return(ridge_count); + } + /* Otherwise, we found a new ridge start transition, so store */ + /* its location (the location of the 1 in 0-to-1 transition). */ + ridge_start = i; + + print2log(": RS %d,%d ", xlist[i], ylist[i]); + + /* If 1-to-0 transition not found ... */ + if(!find_transition(&i, 1, 0, xlist, ylist, num, bdata, iw, ih)){ + /* Then we are done looking for ridges. */ + free(xlist); + free(ylist); + + print2log("\n"); + + /* Return number of ridges counted to this point. */ + return(ridge_count); + } + /* Otherwise, we found a new ridge end transition, so store */ + /* its location (the location of the 0 in 1-to-0 transition). */ + ridge_end = i; + + print2log("; RE %d,%d ", xlist[i], ylist[i]); + + /* Conduct the validation, tracing the contour of the ridge */ + /* from the ridge ending point a specified number of steps */ + /* scanning for neighbors clockwise and counter-clockwise. */ + /* If the ridge starting point is encounted during the trace */ + /* then we can assume we do not have a valid ridge crossing */ + /* and instead we are walking on and off the edge of the */ + /* side of a ridge. */ + ret = validate_ridge_crossing(ridge_start, ridge_end, + xlist, ylist, num, bdata, iw, ih, + lfsparms->max_ridge_steps); + + /* If system error ... */ + if(ret < 0){ + free(xlist); + free(ylist); + /* Return the error code. */ + return(ret); + } + + print2log("; V%d ", ret); + + /* If validation result is TRUE ... */ + if(ret){ + /* Then assume we have found a valid ridge crossing and bump */ + /* the ridge counter. */ + ridge_count++; + } + + /* Otherwise, ignore the current ridge start and end transitions */ + /* and go back and search for new ridge start. */ + } + + /* Deallocate working memories. */ + free(xlist); + free(ylist); + + print2log("\n"); + + /* Return the number of ridges counted. */ + return(ridge_count); +} + +/************************************************************************* +************************************************************************** +#cat: find_transition - Takes a pixel trajectory and a starting index, and +#cat: searches forward along the trajectory until the specified +#cat: adjacent pixel pair is found, returning the index where +#cat: the pair was found (the index of the second pixel). + + Input: + iptr - pointer to starting pixel index into trajectory + pix1 - first pixel value in transition pair + pix2 - second pixel value in transition pair + xlist - x-pixel coords of line trajectory + ylist - y-pixel coords of line trajectory + num - number of coords in line trajectory + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + Output: + iptr - points to location where 2nd pixel in pair is found + Return Code: + TRUE - pixel pair transition found + FALSE - pixel pair transition not found +**************************************************************************/ +int find_transition(int *iptr, const int pix1, const int pix2, + const int *xlist, const int *ylist, const int num, + unsigned char *bdata, const int iw, const int ih) +{ + int i, j; + + /* Set previous index to starting position. */ + i = *iptr; + /* Bump previous index by 1 to get next index. */ + j = i+1; + + /* While not one point from the end of the trajectory .. */ + while(i < num-1){ + /* If we have found the desired transition ... */ + if((*(bdata+(ylist[i]*iw)+xlist[i]) == pix1) && + (*(bdata+(ylist[j]*iw)+xlist[j]) == pix2)){ + /* Adjust the position pointer to the location of the */ + /* second pixel in the transition. */ + *iptr = j; + + /* Return TRUE. */ + return(TRUE); + } + /* Otherwise, the desired transition was not found in current */ + /* pixel pair, so bump to the next pair along the trajector. */ + i++; + j++; + } + + /* If we get here, then we exhausted the trajector without finding */ + /* the desired transition, so set the position pointer to the end */ + /* of the trajector, and return FALSE. */ + *iptr = num; + return(FALSE); +} + +/************************************************************************* +************************************************************************** +#cat: validate_ridge_crossing - Takes a pair of points, a ridge start +#cat: transition and a ridge end transition, and walks the +#cat: ridge contour from thre ridge end points a specified +#cat: number of steps, looking for the ridge start point. +#cat: If found, then transitions determined not to be a valid +#cat: ridge crossing. + + Input: + ridge_start - index into line trajectory of ridge start transition + ridge_end - index into line trajectory of ridge end transition + xlist - x-pixel coords of line trajectory + ylist - y-pixel coords of line trajectory + num - number of coords in line trajectory + bdata - binary image data (0==while & 1==black) + iw - width (in pixels) of image + ih - height (in pixels) of image + max_ridge_steps - number of steps taken in search in both + scan directions + Return Code: + TRUE - ridge crossing VALID + FALSE - ridge corssing INVALID + Negative - system error +**************************************************************************/ +int validate_ridge_crossing(const int ridge_start, const int ridge_end, + const int *xlist, const int *ylist, const int num, + unsigned char *bdata, const int iw, const int ih, + const int max_ridge_steps) +{ + int ret; + int feat_x, feat_y, edge_x, edge_y; + int *contour_x, *contour_y, *contour_ex, *contour_ey, ncontour; + + /* Assign edge pixel pair for contour trace. */ + feat_x = xlist[ridge_end]; + feat_y = ylist[ridge_end]; + edge_x = xlist[ridge_end-1]; + edge_y = ylist[ridge_end-1]; + + /* Adjust pixel pair if they neighbor each other diagonally. */ + fix_edge_pixel_pair(&feat_x, &feat_y, &edge_x, &edge_y, + bdata, iw, ih); + + /* Trace ridge contour, starting at the ridge end transition, and */ + /* taking a specified number of step scanning for edge neighbors */ + /* clockwise. As we trace the ridge, we want to detect if we */ + /* encounter the ridge start transition. NOTE: The ridge end */ + /* position is on the white (of a black to white transition) and */ + /* the ridge start is on the black (of a black to white trans), */ + /* so the edge trace needs to look for the what pixel (not the */ + /* black one) of the ridge start transition. */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + max_ridge_steps, + xlist[ridge_start-1], ylist[ridge_start-1], + feat_x, feat_y, edge_x, edge_y, + SCAN_CLOCKWISE, bdata, iw, ih); + /* If a system error occurred ... */ + if(ret < 0) + /* Return error code. */ + return(ret); + + /* Otherwise, if the trace was not IGNORED, then a contour was */ + /* was generated and returned. We aren't interested in the */ + /* actual contour, so deallocate it. */ + if(ret != IGNORE) + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /* If the trace was IGNORED, then we had some sort of initialization */ + /* problem, so treat this the same as if was actually located the */ + /* ridge start point (in which case LOOP_FOUND is returned). */ + /* So, If not IGNORED and ridge start not encounted in trace ... */ + if((ret != IGNORE) && + (ret != LOOP_FOUND)){ + + /* Now conduct contour trace scanning for edge neighbors counter- */ + /* clockwise. */ + ret = trace_contour(&contour_x, &contour_y, + &contour_ex, &contour_ey, &ncontour, + max_ridge_steps, + xlist[ridge_start-1], ylist[ridge_start-1], + feat_x, feat_y, edge_x, edge_y, + SCAN_COUNTER_CLOCKWISE, bdata, iw, ih); + /* If a system error occurred ... */ + if(ret < 0) + /* Return error code. */ + return(ret); + + /* Otherwise, if the trace was not IGNORED, then a contour was */ + /* was generated and returned. We aren't interested in the */ + /* actual contour, so deallocate it. */ + if(ret != IGNORE) + free_contour(contour_x, contour_y, contour_ex, contour_ey); + + /* If trace not IGNORED and ridge start not encounted in 2nd trace ... */ + if((ret != IGNORE) && + (ret != LOOP_FOUND)){ + /* If we get here, assume we have a ridge crossing. */ + return(TRUE); + } + /* Otherwise, second trace returned IGNORE or ridge start found. */ + } + /* Otherwise, first trace returned IGNORE or ridge start found. */ + + /* If we get here, then we failed to validate a ridge crossing. */ + return(FALSE); +} diff --git a/libfprint/nbis/mindtct/shape.c b/libfprint/nbis/mindtct/shape.c new file mode 100644 index 0000000..23ba16c --- /dev/null +++ b/libfprint/nbis/mindtct/shape.c @@ -0,0 +1,304 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: SHAPE.C + AUTHOR: Michael D. Garris + DATE: 05/11/1999 + UPDATED: 03/16/2005 by MDG + + Contains routines responsible for creating and manipulating + shape stuctures as part of the NIST Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + alloc_shape() + free_shape() + dump_shape() + shape_from_contour() + sort_row_on_x() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: alloc_shape - Allocates and initializes a shape structure given the +#cat: the X and Y limits of the shape. + + Input: + xmin - left-most x-coord in shape + ymin - top-most y-coord in shape + xmax - right-most x-coord in shape + ymax - bottom-most y-coord in shape + Output: + oshape - pointer to the allocated & initialized shape structure + Return Code: + Zero - Shape successfully allocated and initialized + Negative - System error +**************************************************************************/ +int alloc_shape(SHAPE **oshape, const int xmin, const int ymin, + const int xmax, const int ymax) +{ + SHAPE *shape; + int alloc_rows, alloc_pts; + int i, j, y; + + /* Compute allocation parameters. */ + /* First, compute the number of scanlines spanned by the shape. */ + alloc_rows = ymax - ymin + 1; + /* Second, compute the "maximum" number of contour points possible */ + /* on a row. Here we are allocating the maximum number of contiguous */ + /* pixels on each row which will be sufficiently larger than the */ + /* number of actual contour points. */ + alloc_pts = xmax - xmin + 1; + + /* Allocate the shape structure. */ + shape = (SHAPE *)malloc(sizeof(SHAPE)); + /* If there is an allocation error... */ + if(shape == (SHAPE *)NULL){ + fprintf(stderr, "ERROR : alloc_shape : malloc : shape\n"); + return(-250); + } + + /* Allocate the list of row pointers. We now this number will fit */ + /* the shape exactly. */ + shape->rows = (ROW **)malloc(alloc_rows * sizeof(ROW *)); + /* If there is an allocation error... */ + if(shape->rows == (ROW **)NULL){ + /* Deallocate memory alloated by this routine to this point. */ + free(shape); + fprintf(stderr, "ERROR : alloc_shape : malloc : shape->rows\n"); + return(-251); + } + + /* Initialize the shape structure's attributes. */ + shape->ymin = ymin; + shape->ymax = ymax; + /* The number of allocated rows will be exactly the number of */ + /* assigned rows for the shape. */ + shape->alloc = alloc_rows; + shape->nrows = alloc_rows; + + /* Foreach row in the shape... */ + for(i = 0, y = ymin; i < alloc_rows; i++, y++){ + /* Allocate a row structure and store it in its respective position */ + /* in the shape structure's list of row pointers. */ + shape->rows[i] = (ROW *)malloc(sizeof(ROW)); + /* If there is an allocation error... */ + if(shape->rows[i] == (ROW *)NULL){ + /* Deallocate memory alloated by this routine to this point. */ + for(j = 0; j < i; j++){ + free(shape->rows[j]->xs); + free(shape->rows[j]); + } + free(shape->rows); + free(shape); + fprintf(stderr, "ERROR : alloc_shape : malloc : shape->rows[i]\n"); + return(-252); + } + + /* Allocate the current rows list of x-coords. */ + shape->rows[i]->xs = (int *)malloc(alloc_pts * sizeof(int)); + /* If there is an allocation error... */ + if(shape->rows[i]->xs == (int *)NULL){ + /* Deallocate memory alloated by this routine to this point. */ + for(j = 0; j < i; j++){ + free(shape->rows[j]->xs); + free(shape->rows[j]); + } + free(shape->rows[i]); + free(shape->rows); + free(shape); + fprintf(stderr, + "ERROR : alloc_shape : malloc : shape->rows[i]->xs\n"); + return(-253); + } + + /* Initialize the current row structure's attributes. */ + shape->rows[i]->y = y; + shape->rows[i]->alloc = alloc_pts; + /* There are initially ZERO points assigned to the row. */ + shape->rows[i]->npts = 0; + } + + /* Assign structure to output pointer. */ + *oshape = shape; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: free_shape - Deallocates a shape structure and all its allocated +#cat: attributes. + + Input: + shape - pointer to the shape structure to be deallocated +**************************************************************************/ +void free_shape(SHAPE *shape) +{ + int i; + + /* Foreach allocated row in the shape ... */ + for(i = 0; i < shape->alloc; i++){ + /* Deallocate the current row's list of x-coords. */ + free(shape->rows[i]->xs); + /* Deallocate the current row structure. */ + free(shape->rows[i]); + } + + /* Deallocate the list of row pointers. */ + free(shape->rows); + /* Deallocate the shape structure. */ + free(shape); +} + +/************************************************************************* +************************************************************************** +#cat: dump_shape - Takes an initialized shape structure and dumps its contents +#cat: as formatted text to the specified open file pointer. + + Input: + shape - shape structure to be dumped + Output: + fpout - open file pointer to be written to +**************************************************************************/ +void dump_shape(FILE *fpout, const SHAPE *shape) +{ + int i, j; + + /* Print the shape's y-limits and number of scanlines. */ + fprintf(fpout, "shape: ymin=%d, ymax=%d, nrows=%d\n", + shape->ymin, shape->ymax, shape->nrows); + + /* Foreach row in the shape... */ + for(i = 0; i < shape->nrows; i++){ + /* Print the current row's y-coord and number of points on the row. */ + fprintf(fpout, "row %d : y=%d, npts=%d\n", i, shape->rows[i]->y, + shape->rows[i]->npts); + /* Print each successive point on the current row. */ + for(j = 0; j < shape->rows[i]->npts; j++){ + fprintf(fpout, "pt %d : %d %d\n", j, shape->rows[i]->xs[j], + shape->rows[i]->y); + } + } +} + +/************************************************************************* +************************************************************************** +#cat: shape_from_contour - Converts a contour list that has been determined +#cat: to form a complete loop into a shape representation where +#cat: the contour points on each contiguous scanline of the shape +#cat: are stored in left-to-right order. + + Input: + contour_x - x-coord list for loop's contour points + contour_y - y-coord list for loop's contour points + ncontour - number of points in contour + Output: + oshape - points to the resulting shape structure + Return Code: + Zero - shape successfully derived + Negative - system error +**************************************************************************/ +int shape_from_contour(SHAPE **oshape, const int *contour_x, + const int *contour_y, const int ncontour) +{ + SHAPE *shape; + ROW *row; + int ret, i, xmin, ymin, xmax, ymax; + + /* Find xmin, ymin, xmax, ymax on contour. */ + contour_limits(&xmin, &ymin, &xmax, &ymax, + contour_x, contour_y, ncontour); + + /* Allocate and initialize a shape structure. */ + if((ret = alloc_shape(&shape, xmin, ymin, xmax, ymax))) + /* If system error, then return error code. */ + return(ret); + + /* Foreach point on contour ... */ + for(i = 0; i < ncontour; i++){ + /* Add point to corresponding row. */ + /* First set a pointer to the current row. We need to subtract */ + /* ymin because the rows are indexed relative to the top-most */ + /* scanline in the shape. */ + row = shape->rows[contour_y[i]-ymin]; + + /* It is possible with complex shapes to reencounter points */ + /* already visited on a contour, especially at "pinching" points */ + /* along the contour. So we need to test to see if a point has */ + /* already been stored in the row. If not in row list already ... */ + if(in_int_list(contour_x[i], row->xs, row->npts) < 0){ + /* If row is full ... */ + if(row->npts >= row->alloc){ + /* This should never happen becuase we have allocated */ + /* based on shape bounding limits. */ + fprintf(stderr, + "ERROR : shape_from_contour : row overflow\n"); + return(-260); + } + /* Assign the x-coord of the current contour point to the row */ + /* and bump the row's point counter. All the contour points */ + /* on the same row share the same y-coord. */ + row->xs[row->npts++] = contour_x[i]; + } + /* Otherwise, point is already stored in row, so ignore. */ + } + + /* Foreach row in the shape. */ + for(i = 0; i < shape->nrows; i++) + /* Sort row points increasing on their x-coord. */ + sort_row_on_x(shape->rows[i]); + + /* Assign shape structure to output pointer. */ + *oshape = shape; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: sort_row_on_x - Takes a row structure and sorts its points left-to- +#cat: right on X. + + Input: + row - row structure to be sorted + Output: + row - row structure with points in sorted order +**************************************************************************/ +void sort_row_on_x(ROW *row) +{ + /* Conduct a simple increasing bubble sort on the x-coords */ + /* in the given row. A bubble sort is satisfactory as the */ + /* number of points will be relatively small. */ + bubble_sort_int_inc(row->xs, row->npts); +} + diff --git a/libfprint/nbis/mindtct/sort.c b/libfprint/nbis/mindtct/sort.c new file mode 100644 index 0000000..3a0f47c --- /dev/null +++ b/libfprint/nbis/mindtct/sort.c @@ -0,0 +1,320 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: SORT.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + UPDATED: 03/16/2005 by MDG + + Contains sorting routines required by the NIST Latent Fingerprint + System (LFS). + +*********************************************************************** + ROUTINES: + sort_indices_int_inc() + sort_indices_double_inc() + bubble_sort_int_inc_2() + bubble_sort_double_inc_2() + bubble_sort_double_dec_2() + bubble_sort_int_inc() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: sort_indices_int_inc - Takes a list of integers and returns a list of +#cat: indices referencing the integer list in increasing order. +#cat: The original list of integers is also returned in sorted +#cat: order. + + Input: + ranks - list of integers to be sorted + num - number of integers in the list + Output: + optr - list of indices referencing the integer list in sorted order + ranks - list of integers in increasing order + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int sort_indices_int_inc(int **optr, int *ranks, const int num) +{ + int *order; + int i; + + /* Allocate list of sequential indices. */ + order = (int *)malloc(num * sizeof(int)); + if(order == (int *)NULL){ + fprintf(stderr, "ERROR : sort_indices_int_inc : malloc : order\n"); + return(-390); + } + /* Initialize list of sequential indices. */ + for(i = 0; i < num; i++) + order[i] = i; + + /* Sort the indecies into rank order. */ + bubble_sort_int_inc_2(ranks, order, num); + + /* Set output pointer to the resulting order of sorted indices. */ + *optr = order; + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: sort_indices_double_inc - Takes a list of doubles and returns a list of +#cat: indices referencing the double list in increasing order. +#cat: The original list of doubles is also returned in sorted +#cat: order. + + Input: + ranks - list of doubles to be sorted + num - number of doubles in the list + Output: + optr - list of indices referencing the double list in sorted order + ranks - list of doubles in increasing order + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int sort_indices_double_inc(int **optr, double *ranks, const int num) +{ + int *order; + int i; + + /* Allocate list of sequential indices. */ + order = (int *)malloc(num * sizeof(int)); + if(order == (int *)NULL){ + fprintf(stderr, "ERROR : sort_indices_double_inc : malloc : order\n"); + return(-400); + } + /* Initialize list of sequential indices. */ + for(i = 0; i < num; i++) + order[i] = i; + + /* Sort the indicies into rank order. */ + bubble_sort_double_inc_2(ranks, order, num); + + /* Set output pointer to the resulting order of sorted indices. */ + *optr = order; + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: bubble_sort_int_inc_2 - Takes a list of integer ranks and a corresponding +#cat: list of integer attributes, and sorts the ranks +#cat: into increasing order moving the attributes +#cat: correspondingly. + + Input: + ranks - list of integers to be sort on + items - list of corresponding integer attributes + len - number of items in list + Output: + ranks - list of integers sorted in increasing order + items - list of attributes in corresponding sorted order +**************************************************************************/ +void bubble_sort_int_inc_2(int *ranks, int *items, const int len) +{ + int done = 0; + int i, p, n, trank, titem; + + /* Set counter to the length of the list being sorted. */ + n = len; + + /* While swaps in order continue to occur from the */ + /* previous iteration... */ + while(!done){ + /* Reset the done flag to TRUE. */ + done = TRUE; + /* Foreach rank in list up to current end index... */ + /* ("p" points to current rank and "i" points to the next rank.) */ + for (i=1, p = 0; i ranks[i]){ + /* Swap ranks. */ + trank = ranks[i]; + ranks[i] = ranks[p]; + ranks[p] = trank; + /* Swap items. */ + titem = items[i]; + items[i] = items[p]; + items[p] = titem; + /* Changes were made, so set done flag to FALSE. */ + done = FALSE; + } + /* Otherwise, rank pair is in order, so continue. */ + } + /* Decrement the ending index. */ + n--; + } +} + +/************************************************************************* +************************************************************************** +#cat: bubble_sort_double_inc_2 - Takes a list of double ranks and a +#cat: corresponding list of integer attributes, and sorts the +#cat: ranks into increasing order moving the attributes +#cat: correspondingly. + + Input: + ranks - list of double to be sort on + items - list of corresponding integer attributes + len - number of items in list + Output: + ranks - list of doubles sorted in increasing order + items - list of attributes in corresponding sorted order +**************************************************************************/ +void bubble_sort_double_inc_2(double *ranks, int *items, const int len) +{ + int done = 0; + int i, p, n, titem; + double trank; + + /* Set counter to the length of the list being sorted. */ + n = len; + + /* While swaps in order continue to occur from the */ + /* previous iteration... */ + while(!done){ + /* Reset the done flag to TRUE. */ + done = TRUE; + /* Foreach rank in list up to current end index... */ + /* ("p" points to current rank and "i" points to the next rank.) */ + for (i=1, p = 0; i ranks[i]){ + /* Swap ranks. */ + trank = ranks[i]; + ranks[i] = ranks[p]; + ranks[p] = trank; + /* Swap items. */ + titem = items[i]; + items[i] = items[p]; + items[p] = titem; + /* Changes were made, so set done flag to FALSE. */ + done = FALSE; + } + /* Otherwise, rank pair is in order, so continue. */ + } + /* Decrement the ending index. */ + n--; + } +} + +/*************************************************************************** +************************************************************************** +#cat: bubble_sort_double_dec_2 - Conducts a simple bubble sort returning a list +#cat: of ranks in decreasing order and their associated items in sorted +#cat: order as well. + + Input: + ranks - list of values to be sorted + items - list of items, each corresponding to a particular rank value + len - length of the lists to be sorted + Output: + ranks - list of values sorted in descending order + items - list of items in the corresponding sorted order of the ranks. + If these items are indices, upon return, they may be used as + indirect addresses reflecting the sorted order of the ranks. +****************************************************************************/ +void bubble_sort_double_dec_2(double *ranks, int *items, const int len) +{ + int done = 0; + int i, p, n, titem; + double trank; + + n = len; + while(!done){ + done = 1; + for (i=1, p = 0;i ranks[i]){ + /* Swap ranks. */ + trank = ranks[i]; + ranks[i] = ranks[p]; + ranks[p] = trank; + /* Changes were made, so set done flag to FALSE. */ + done = FALSE; + } + /* Otherwise, rank pair is in order, so continue. */ + } + /* Decrement the ending index. */ + n--; + } +} + diff --git a/libfprint/nbis/mindtct/util.c b/libfprint/nbis/mindtct/util.c new file mode 100644 index 0000000..a4e98da --- /dev/null +++ b/libfprint/nbis/mindtct/util.c @@ -0,0 +1,605 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: UTIL.C + AUTHOR: Michael D. Garris + DATE: 03/16/1999 + + Contains general support routines required by the NIST + Latent Fingerprint System (LFS). + +*********************************************************************** + ROUTINES: + maxv() + minv() + minmaxs() + distance() + squared_distance() + in_int_list() + remove_from_int_list() + find_incr_position_dbl() + angle2line() + line2direction() + closest_dir_dist() +***********************************************************************/ + +#include +#include +#include + +/************************************************************************* +************************************************************************** +#cat: maxv - Determines the maximum value in the given list of integers. +#cat: NOTE, the list is assumed to be NOT empty! + + Input: + list - non-empty list of integers to be searched + num - number of integers in the list + Return Code: + Maximum - maximum value in the list +**************************************************************************/ +int maxv(const int *list, const int num) +{ + int i; + int maxval; + + /* NOTE: The list is assumed to be NOT empty. */ + /* Initialize running maximum to first item in list. */ + maxval = list[0]; + + /* Foreach subsequent item in the list... */ + for(i = 1; i < num; i++){ + /* If current item is larger than running maximum... */ + if(list[i] > maxval) + /* Set running maximum to the larger item. */ + maxval = list[i]; + /* Otherwise, skip to next item. */ + } + + /* Return the resulting maximum. */ + return(maxval); +} + +/************************************************************************* +************************************************************************** +#cat: minv - Determines the minimum value in the given list of integers. +#cat: NOTE, the list is assumed to be NOT empty! + + Input: + list - non-empty list of integers to be searched + num - number of integers in the list + Return Code: + Minimum - minimum value in the list +**************************************************************************/ +int minv(const int *list, const int num) +{ + int i; + int minval; + + /* NOTE: The list is assumed to be NOT empty. */ + /* Initialize running minimum to first item in list. */ + minval = list[0]; + + /* Foreach subsequent item in the list... */ + for(i = 1; i < num; i++){ + /* If current item is smaller than running minimum... */ + if(list[i] < minval) + /* Set running minimum to the smaller item. */ + minval = list[i]; + /* Otherwise, skip to next item. */ + } + + /* Return the resulting minimum. */ + return(minval); +} + +/************************************************************************* +************************************************************************** +#cat: minmaxs - Takes a list of integers and identifies points of relative +#cat: minima and maxima. The midpoint of flat plateaus and valleys +#cat: are selected when they are detected. + + Input: + items - list of integers to be analyzed + num - number of items in the list + Output: + ominmax_val - value of the item at each minima or maxima + ominmax_type - identifies a minima as '-1' and maxima as '1' + ominmax_i - index of item's position in list + ominmax_alloc - number of allocated minima and/or maxima + ominmax_num - number of detected minima and/or maxima + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int minmaxs(int **ominmax_val, int **ominmax_type, int **ominmax_i, + int *ominmax_alloc, int *ominmax_num, + const int *items, const int num) +{ + int i, diff, state, start, loc; + int *minmax_val, *minmax_type, *minmax_i, minmax_alloc, minmax_num; + + + /* Determine maximum length for allocation of buffers. */ + /* If there are fewer than 3 items ... */ + if(num < 3){ + /* Then no min/max is possible, so set allocated length */ + /* to 0 and return. */ + *ominmax_alloc = 0; + *ominmax_num = 0; + return(0); + } + /* Otherwise, set allocation length to number of items - 2 */ + /* (one for the first item in the list, and on for the last). */ + /* Every other intermediate point can potentially represent a */ + /* min or max. */ + minmax_alloc = num - 2; + /* Allocate the buffers. */ + minmax_val = (int *)malloc(minmax_alloc * sizeof(int)); + if(minmax_val == (int *)NULL){ + fprintf(stderr, "ERROR : minmaxs : malloc : minmax_val\n"); + return(-290); + } + minmax_type = (int *)malloc(minmax_alloc * sizeof(int)); + if(minmax_type == (int *)NULL){ + free(minmax_val); + fprintf(stderr, "ERROR : minmaxs : malloc : minmax_type\n"); + return(-291); + } + minmax_i = (int *)malloc(minmax_alloc * sizeof(int)); + if(minmax_i == (int *)NULL){ + free(minmax_val); + free(minmax_type); + fprintf(stderr, "ERROR : minmaxs : malloc : minmax_i\n"); + return(-292); + } + + /* Initialize number of min/max to 0. */ + minmax_num = 0; + + /* Start witht the first item in the list. */ + i = 0; + + /* Get starting state between first pair of items. */ + diff = items[1] - items[0]; + if(diff > 0) + state = 1; + else if (diff < 0) + state = -1; + else + state = 0; + + /* Set start location to first item in list. */ + start = 0; + + /* Bump to next item in list. */ + i++; + + /* While not at the last item in list. */ + while(i < num-1){ + + /* Compute difference between next pair of items. */ + diff = items[i+1] - items[i]; + /* If items are increasing ... */ + if(diff > 0){ + /* If previously increasing ... */ + if(state == 1){ + /* Reset start to current location. */ + start = i; + } + /* If previously decreasing ... */ + else if (state == -1){ + /* Then we have incurred a minima ... */ + /* Compute midpoint of minima. */ + loc = (start + i)/2; + /* Store value at minima midpoint. */ + minmax_val[minmax_num] = items[loc]; + /* Store type code for minima. */ + minmax_type[minmax_num] = -1; + /* Store location of minima midpoint. */ + minmax_i[minmax_num++] = loc; + /* Change state to increasing. */ + state = 1; + /* Reset start location. */ + start = i; + } + /* If previously level (this state only can occur at the */ + /* beginning of the list of items) ... */ + else { + /* If more than one level state in a row ... */ + if(i-start > 1){ + /* Then consider a minima ... */ + /* Compute midpoint of minima. */ + loc = (start + i)/2; + /* Store value at minima midpoint. */ + minmax_val[minmax_num] = items[loc]; + /* Store type code for minima. */ + minmax_type[minmax_num] = -1; + /* Store location of minima midpoint. */ + minmax_i[minmax_num++] = loc; + /* Change state to increasing. */ + state = 1; + /* Reset start location. */ + start = i; + } + /* Otherwise, ignore single level state. */ + else{ + /* Change state to increasing. */ + state = 1; + /* Reset start location. */ + start = i; + } + } + } + /* If items are decreasing ... */ + else if(diff < 0){ + /* If previously decreasing ... */ + if(state == -1){ + /* Reset start to current location. */ + start = i; + } + /* If previously increasing ... */ + else if (state == 1){ + /* Then we have incurred a maxima ... */ + /* Compute midpoint of maxima. */ + loc = (start + i)/2; + /* Store value at maxima midpoint. */ + minmax_val[minmax_num] = items[loc]; + /* Store type code for maxima. */ + minmax_type[minmax_num] = 1; + /* Store location of maxima midpoint. */ + minmax_i[minmax_num++] = loc; + /* Change state to decreasing. */ + state = -1; + /* Reset start location. */ + start = i; + } + /* If previously level (this state only can occur at the */ + /* beginning of the list of items) ... */ + else { + /* If more than one level state in a row ... */ + if(i-start > 1){ + /* Then consider a maxima ... */ + /* Compute midpoint of maxima. */ + loc = (start + i)/2; + /* Store value at maxima midpoint. */ + minmax_val[minmax_num] = items[loc]; + /* Store type code for maxima. */ + minmax_type[minmax_num] = 1; + /* Store location of maxima midpoint. */ + minmax_i[minmax_num++] = loc; + /* Change state to decreasing. */ + state = -1; + /* Reset start location. */ + start = i; + } + /* Otherwise, ignore single level state. */ + else{ + /* Change state to decreasing. */ + state = -1; + /* Reset start location. */ + start = i; + } + } + } + /* Otherwise, items are level, so continue to next item pair. */ + + /* Advance to next item pair in list. */ + i++; + } + + /* Set results to output pointers. */ + *ominmax_val = minmax_val; + *ominmax_type = minmax_type; + *ominmax_i = minmax_i; + *ominmax_alloc = minmax_alloc; + *ominmax_num = minmax_num; + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: distance - Takes two coordinate points and computes the +#cat: Euclidean distance between the two points. + + Input: + x1 - x-coord of first point + y1 - y-coord of first point + x2 - x-coord of second point + y2 - y-coord of second point + Return Code: + Distance - computed Euclidean distance +**************************************************************************/ +double distance(const int x1, const int y1, const int x2, const int y2) +{ + double dx, dy, dist; + + /* Compute delta x between points. */ + dx = (double)(x1 - x2); + /* Compute delta y between points. */ + dy = (double)(y1 - y2); + /* Compute the squared distance between points. */ + dist = (dx*dx) + (dy*dy); + /* Take square root of squared distance. */ + dist = sqrt(dist); + + /* Return the squared distance. */ + return(dist); +} + +/************************************************************************* +************************************************************************** +#cat: squared_distance - Takes two coordinate points and computes the +#cat: squared distance between the two points. + + Input: + x1 - x-coord of first point + y1 - y-coord of first point + x2 - x-coord of second point + y2 - y-coord of second point + Return Code: + Distance - computed squared distance +**************************************************************************/ +double squared_distance(const int x1, const int y1, const int x2, const int y2) +{ + double dx, dy, dist; + + /* Compute delta x between points. */ + dx = (double)(x1 - x2); + /* Compute delta y between points. */ + dy = (double)(y1 - y2); + /* Compute the squared distance between points. */ + dist = (dx*dx) + (dy*dy); + + /* Return the squared distance. */ + return(dist); +} + +/************************************************************************* +************************************************************************** +#cat: in_int_list - Determines if a specified value is store in a list of +#cat: integers and returns its location if found. + + Input: + item - value to search for in list + list - list of integers to be searched + len - number of integers in search list + Return Code: + Zero or greater - first location found equal to search value + Negative - search value not found in the list of integers +**************************************************************************/ +int in_int_list(const int item, const int *list, const int len) +{ + int i; + + /* Foreach item in list ... */ + for(i = 0; i < len; i++){ + /* If search item found in list ... */ + if(list[i] == item) + /* Return the location in list where found. */ + return(i); + } + + /* If we get here, then search item not found in list, */ + /* so return -1 ==> NOT FOUND. */ + return(-1); +} + +/************************************************************************* +************************************************************************** +#cat: remove_from_int_list - Takes a position index into an integer list and +#cat: removes the value from the list, collapsing the resulting +#cat: list. + + Input: + index - position of value to be removed from list + list - input list of integers + num - number of integers in the list + Output: + list - list with specified integer removed + num - decremented number of integers in list + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +int remove_from_int_list(const int index, int *list, const int num) +{ + int fr, to; + + /* Make sure the requested index is within range. */ + if((index < 0) && (index >= num)){ + fprintf(stderr, "ERROR : remove_from_int_list : index out of range\n"); + return(-370); + } + + /* Slide the remaining list of integers up over top of the */ + /* position of the integer being removed. */ + for(to = index, fr = index+1; fr < num; to++, fr++) + list[to] = list[fr]; + + /* NOTE: Decrementing the number of integers remaining in the list is */ + /* the responsibility of the caller! */ + + /* Return normally. */ + return(0); +} + +/************************************************************************* +************************************************************************** +#cat: ind_incr_position_dbl - Takes a double value and a list of doubles and +#cat: determines where in the list the double may be inserted, +#cat: preserving the increasing sorted order of the list. + + Input: + val - value to be inserted into the list + list - list of double in increasing sorted order + num - number of values in the list + Return Code: + Zero or Positive - insertion position in the list +**************************************************************************/ +int find_incr_position_dbl(const double val, double *list, const int num) +{ + int i; + + /* Foreach item in double list ... */ + for(i = 0; i < num; i++){ + /* If the value is smaller than the current item in list ... */ + if(val < list[i]) + /* Then we found were to insert the value in the list maintaining */ + /* an increasing sorted order. */ + return(i); + + /* Otherwise, the value is still larger than current item, so */ + /* continue to next item in the list. */ + } + + /* Otherwise, we never found a slot within the list to insert the */ + /* the value, so place at the end of the sorted list. */ + return(i); +} + +/************************************************************************* +************************************************************************** +#cat: angle2line - Takes two coordinate points and computes the angle +#cat: to the line formed by the two points. + + Input: + fx - x-coord of first point + fy - y-coord of first point + tx - x-coord of second point + ty - y-coord of second point + Return Code: + Angle - angle to the specified line +**************************************************************************/ +double angle2line(const int fx, const int fy, const int tx, const int ty) +{ + double dx, dy, theta; + + /* Compute slope of line connecting the 2 specified points. */ + dy = (double)(fy - ty); + dx = (double)(tx - fx); + /* If delta's are sufficiently small ... */ + if((fabs(dx) < MIN_SLOPE_DELTA) && (fabs(dy) < MIN_SLOPE_DELTA)) + theta = 0.0; + /* Otherwise, compute angle to the line. */ + else + theta = atan2(dy, dx); + + /* Return the compute angle in radians. */ + return(theta); +} + +/************************************************************************* +************************************************************************** +#cat: line2direction - Takes two coordinate points and computes the +#cat: directon (on a full circle) in which the first points +#cat: to the second. + + Input: + fx - x-coord of first point (pointing from) + fy - y-coord of first point (pointing from) + tx - x-coord of second point (pointing to) + ty - y-coord of second point (pointing to) + ndirs - number of IMAP directions (in semicircle) + Return Code: + Direction - determined direction on a "full" circle +**************************************************************************/ +int line2direction(const int fx, const int fy, + const int tx, const int ty, const int ndirs) +{ + double theta, pi_factor; + int idir, full_ndirs; + static double pi2 = M_PI*2.0; + + /* Compute angle to line connecting the 2 points. */ + /* Coordinates are swapped and order of points reversed to */ + /* account for 0 direction is vertical and positive direction */ + /* is clockwise. */ + theta = angle2line(ty, tx, fy, fx); + + /* Make sure the angle is positive. */ + theta += pi2; + theta = fmod(theta, pi2); + /* Convert from radians to integer direction on range [0..(ndirsX2)]. */ + /* Multiply radians by units/radian ((ndirsX2)/(2PI)), and you get */ + /* angle in integer units. */ + /* Compute number of directions on full circle. */ + full_ndirs = ndirs<<1; + /* Compute the radians to integer direction conversion factor. */ + pi_factor = (double)full_ndirs/pi2; + /* Convert radian angle to integer direction on full circle. */ + theta *= pi_factor; + /* Need to truncate precision so that answers are consistent */ + /* on different computer architectures when rounding doubles. */ + theta = trunc_dbl_precision(theta, TRUNC_SCALE); + idir = sround(theta); + /* Make sure on range [0..(ndirsX2)]. */ + idir %= full_ndirs; + + /* Return the integer direction. */ + return(idir); +} + +/************************************************************************* +************************************************************************** +#cat: closest_dir_dist - Takes to integer IMAP directions and determines the +#cat: closest distance between them accounting for +#cat: wrap-around either at the beginning or ending of +#cat: the range of directions. + + Input: + dir1 - integer value of the first direction + dir2 - integer value of the second direction + ndirs - the number of possible directions + Return Code: + Non-negative - distance between the 2 directions +**************************************************************************/ +int closest_dir_dist(const int dir1, const int dir2, const int ndirs) +{ + int d1, d2, dist; + + /* Initialize distance to -1 = INVALID. */ + dist = INVALID_DIR; + + /* Measure shortest distance between to directions. */ + /* If both neighbors are VALID ... */ + if((dir1 >= 0)&&(dir2 >= 0)){ + /* Compute inner and outer distances to account for distances */ + /* that wrap around the end of the range of directions, which */ + /* may in fact be closer. */ + d1 = abs(dir2 - dir1); + d2 = ndirs - d1; + dist = min(d1, d2); + } + /* Otherwise one or both directions are INVALID, so ignore */ + /* and return INVALID. */ + + /* Return determined closest distance. */ + return(dist); +} + diff --git a/libfprint/nbis/mindtct/xytreps.c b/libfprint/nbis/mindtct/xytreps.c new file mode 100644 index 0000000..65621ea --- /dev/null +++ b/libfprint/nbis/mindtct/xytreps.c @@ -0,0 +1,133 @@ +/******************************************************************************* + +License: +This software was developed at the National Institute of Standards and +Technology (NIST) by employees of the Federal Government in the course +of their official duties. Pursuant to title 17 Section 105 of the +United States Code, this software is not subject to copyright protection +and is in the public domain. NIST assumes no responsibility whatsoever for +its use by other parties, and makes no guarantees, expressed or implied, +about its quality, reliability, or any other characteristic. + +Disclaimer: +This software was developed to promote biometric standards and biometric +technology testing for the Federal Government in accordance with the USA +PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act. +Specific hardware and software products identified in this software were used +in order to perform the software development. In no case does such +identification imply recommendation or endorsement by the National Institute +of Standards and Technology, nor does it imply that the products and equipment +identified are necessarily the best available for the purpose. + +*******************************************************************************/ + +/*********************************************************************** + LIBRARY: LFS - NIST Latent Fingerprint System + + FILE: XYTREPS.C + AUTHOR: Michael D. Garris + DATE: 09/16/2004 + + Contains routines useful in converting minutiae in LFS "native" + representation into other representations, such as + M1 (ANSI INCITS 378-2004) & NIST internal representations. + +*********************************************************************** + ROUTINES: + lfs2nist_minutia_XTY() + lfs2m1_minutia_XTY() + +***********************************************************************/ + +#include +#include + +/************************************************************************* +************************************************************************** +#cat: lfs2nist_minutia_XYT - Converts XYT minutiae attributes in LFS native +#cat: representation to NIST internal representation + + Input: + minutia - LFS minutia structure containing attributes to be converted + Output: + ox - NIST internal based x-pixel coordinate + oy - NIST internal based y-pixel coordinate + ot - NIST internal based minutia direction/orientation + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +void lfs2nist_minutia_XYT(int *ox, int *oy, int *ot, + const MINUTIA *minutia, const int iw, const int ih) +{ + int x, y, t; + float degrees_per_unit; + + /* XYT's according to NIST internal rep: */ + /* 1. pixel coordinates with origin bottom-left */ + /* 2. orientation in degrees on range [0..360] */ + /* with 0 pointing east and increasing counter */ + /* clockwise (same as M1) */ + /* 3. direction pointing out and away from the */ + /* ridge ending or bifurcation valley */ + /* (opposite direction from M1) */ + + x = minutia->x; + y = ih - minutia->y; + + degrees_per_unit = 180 / (float)NUM_DIRECTIONS; + + t = (270 - sround(minutia->direction * degrees_per_unit)) % 360; + if(t < 0){ + t += 360; + } + + *ox = x; + *oy = y; + *ot = t; +} + +/************************************************************************* +************************************************************************** +#cat: lfs2m1_minutia_XYT - Converts XYT minutiae attributes in LFS native +#cat: representation to M1 (ANSI INCITS 378-2004) representation + + Input: + minutia - LFS minutia structure containing attributes to be converted + Output: + ox - M1 based x-pixel coordinate + oy - M1 based y-pixel coordinate + ot - M1 based minutia direction/orientation + Return Code: + Zero - successful completion + Negative - system error +**************************************************************************/ +void lfs2m1_minutia_XYT(int *ox, int *oy, int *ot, const MINUTIA *minutia) +{ + int x, y, t; + float degrees_per_unit; + + /* XYT's according to M1 (ANSI INCITS 378-2004): */ + /* 1. pixel coordinates with origin top-left */ + /* 2. orientation in degrees on range [0..179] */ + /* with 0 pointing east and increasing counter */ + /* clockwise */ + /* 3. direction pointing up the ridge ending or */ + /* bifurcaiton valley */ + + x = minutia->x; + y = minutia->y; + + degrees_per_unit = 180 / (float)NUM_DIRECTIONS; + t = (90 - sround(minutia->direction * degrees_per_unit)) % 360; + if(t < 0){ + t += 360; + } + + /* range of theta is 0..179 because angles are in units of 2 degress */ + t = t / 2; + + *ox = x; + *oy = y; + *ot = t; +}