1 /******************************************************************************
2 
3 This file is part of the Export Control subset of the United States NIST
4 Biometric Image Software (NBIS) distribution:
5     http://fingerprint.nist.gov/NBIS/index.html
6 
7 It is our understanding that this falls within ECCN 3D980, which covers
8 software associated with the development, production or use of certain
9 equipment controlled in accordance with U.S. concerns about crime control
10 practices in specific countries.
11 
12 Therefore, this file should not be exported, or made available on fileservers,
13 except as allowed by U.S. export control laws.
14 
15 Do not remove this notice.
16 
17 ******************************************************************************/
18 
19 /* NOTE: Despite the above notice (which I have not removed), this file is
20  * being legally distributed within libfprint; the U.S. Export Administration
21  * Regulations do not place export restrictions upon distribution of
22  * "publicly available technology and software", as stated in EAR section
23  * 734.3(b)(3)(i). libfprint qualifies as publicly available technology as per
24  * the definition in section 734.7(a)(1).
25  *
26  * For further information, see http://reactivated.net/fprint/US_export_control
27  */
28 
29 /*******************************************************************************
30 
31 License:
32 This software was developed at the National Institute of Standards and
33 Technology (NIST) by employees of the Federal Government in the course
34 of their official duties. Pursuant to title 17 Section 105 of the
35 United States Code, this software is not subject to copyright protection
36 and is in the public domain. NIST assumes no responsibility  whatsoever for
37 its use by other parties, and makes no guarantees, expressed or implied,
38 about its quality, reliability, or any other characteristic.
39 
40 Disclaimer:
41 This software was developed to promote biometric standards and biometric
42 technology testing for the Federal Government in accordance with the USA
43 PATRIOT Act and the Enhanced Border Security and Visa Entry Reform Act.
44 Specific hardware and software products identified in this software were used
45 in order to perform the software development.  In no case does such
46 identification imply recommendation or endorsement by the National Institute
47 of Standards and Technology, nor does it imply that the products and equipment
48 identified are necessarily the best available for the purpose.
49 
50 *******************************************************************************/
51 
52 /***********************************************************************
53       LIBRARY: FING - NIST Fingerprint Systems Utilities
54 
55       FILE:           BZ_IO.C
56       ALGORITHM:      Allan S. Bozorth (FBI)
57       MODIFICATIONS:  Michael D. Garris (NIST)
58                       Stan Janet (NIST)
59       DATE:           09/21/2004
60 
61       Contains routines responsible for supporting command line
62       processing, file and data input to, and output from the
63       Bozorth3 fingerprint matching algorithm.
64 
65 ***********************************************************************
66 
67       ROUTINES:
68 #cat: parse_line_range - parses strings of the form #-# into the upper
69 #cat:            and lower bounds of a range corresponding to lines in
70 #cat:            an input file list
71 #cat: set_progname - stores the program name for the current invocation
72 #cat: set_probe_filename - stores the name of the current probe file
73 #cat:            being processed
74 #cat: set_gallery_filename - stores the name of the current gallery file
75 #cat:            being processed
76 #cat: get_progname - retrieves the program name for the current invocation
77 #cat: get_probe_filename - retrieves the name of the current probe file
78 #cat:            being processed
79 #cat: get_gallery_filename - retrieves the name of the current gallery
80 #cat:            file being processed
81 #cat: get_next_file - gets the next probe (or gallery) filename to be
82 #cat:            processed, either from the command line or from a
83 #cat:            file list
84 #cat: get_score_filename - returns the filename to which the output line
85 #cat:            should be written
86 #cat: get_score_line - formats output lines based on command line options
87 #cat:            specified
88 #cat: bz_load -  loads the contents of the specified XYT file into
89 #cat:            structured memory
90 #cat: fd_readable - when multiple bozorth processes are being run
91 #cat:            concurrently and one of the processes determines a
92 #cat:            has been found, the other processes poll a file
93 #cat:            descriptor using this function to see if they
94 #cat:            should exit as well
95 
96 ***********************************************************************/
97 
98 #include <string.h>
99 #include <ctype.h>
100 #include <sys/time.h>
101 #include <bozorth.h>
102 
103 static const int verbose_load = 0;
104 static const int verbose_main = 0;
105 
106 /***********************************************************************/
parse_line_range(const char * sb,int * begin,int * end)107 int parse_line_range( const char * sb, int * begin, int * end )
108 {
109 int ib, ie;
110 char * se;
111 
112 
113 if ( ! isdigit(*sb) )
114 	return -1;
115 ib = atoi( sb );
116 
117 se = strchr( sb, '-' );
118 if ( se != (char *) NULL ) {
119 	se++;
120 	if ( ! isdigit(*se) )
121 		return -2;
122 	ie = atoi( se );
123 } else {
124 	ie = ib;
125 }
126 
127 if ( ib <= 0 ) {
128 	if ( ie <= 0 ) {
129 		return -3;
130 	} else {
131 		return -4;
132 	}
133 }
134 
135 if ( ie <= 0 ) {
136 	return -5;
137 }
138 
139 if ( ib > ie )
140 	return -6;
141 
142 *begin = ib;
143 *end   = ie;
144 
145 return 0;
146 }
147 
148 /***********************************************************************/
149 
150 /* Used by the following set* and get* routines */
151 static char program_buffer[ 1024 ];
152 static char * pfile;
153 static char * gfile;
154 
155 /***********************************************************************/
set_progname(int use_pid,char * basename,pid_t pid)156 void set_progname( int use_pid, char * basename, pid_t pid )
157 {
158 if ( use_pid )
159 	sprintf( program_buffer, "%s pid %ld", basename, (long) pid );
160 else
161 	sprintf( program_buffer, "%s", basename );
162 }
163 
164 /***********************************************************************/
set_probe_filename(char * filename)165 void set_probe_filename( char * filename )
166 {
167 pfile = filename;
168 }
169 
170 /***********************************************************************/
set_gallery_filename(char * filename)171 void set_gallery_filename( char * filename )
172 {
173 gfile = filename;
174 }
175 
176 /***********************************************************************/
get_progname(void)177 char * get_progname( void )
178 {
179 return program_buffer;
180 }
181 
182 /***********************************************************************/
get_probe_filename(void)183 char * get_probe_filename( void )
184 {
185 return pfile;
186 }
187 
188 /***********************************************************************/
get_gallery_filename(void)189 char * get_gallery_filename( void )
190 {
191 return gfile;
192 }
193 
194 /***********************************************************************/
get_next_file(char * fixed_file,FILE * list_fp,FILE * mates_fp,int * done_now,int * done_afterwards,char * line,int argc,char ** argv,int * optind,int * lineno,int begin,int end)195 char * get_next_file(
196 		char * fixed_file,
197 		FILE * list_fp,
198 		FILE * mates_fp,
199 		int * done_now,
200 		int * done_afterwards,
201 		char * line,
202 		int argc,
203 		char ** argv,
204 		int * optind,
205 
206 		int * lineno,
207 		int begin,
208 		int end
209 		)
210 {
211 char * p;
212 FILE * fp;
213 
214 
215 
216 if ( fixed_file != (char *) NULL ) {
217 	if ( verbose_main )
218 		fprintf( stderr, "returning fixed filename: %s\n", fixed_file );
219 	return fixed_file;
220 }
221 
222 
223 fp = list_fp;
224 if ( fp == (FILE *) NULL )
225 	fp = mates_fp;
226 if ( fp != (FILE *) NULL ) {
227 	while (1) {
228 		if ( fgets( line, MAX_LINE_LENGTH, fp ) == (char *) NULL ) {
229 			*done_now = 1;
230 			if ( verbose_main )
231 				fprintf( stderr, "returning NULL -- reached EOF\n" );
232 			return (char *) NULL;
233 		}
234 		++*lineno;
235 
236 		if ( begin <= 0 )			/* no line number range was specified */
237 			break;
238 		if ( *lineno > end ) {
239 			*done_now = 1;
240 			if ( verbose_main )
241 				fprintf( stderr, "returning NULL -- current line (%d) > end line (%d)\n",
242 										*lineno, end );
243 			return (char *) NULL;
244 		}
245 		if ( *lineno >= begin ) {
246 			break;
247 		}
248 		/* Otherwise ( *lineno < begin ) so read another line */
249 	}
250 
251 	p = strchr( line, '\n' );
252 	if ( p == (char *) NULL ) {
253 		*done_now = 1;
254 		if ( verbose_main )
255 			fprintf( stderr, "returning NULL -- missing newline character\n" );
256 		return (char *) NULL;
257 	}
258 	*p = '\0';
259 
260 	p = line;
261 	if ( verbose_main )
262 		fprintf( stderr, "returning filename from next line: %s\n", p );
263 	return p;
264 }
265 
266 
267 p = argv[*optind];
268 ++*optind;
269 if ( *optind >= argc )
270 	*done_afterwards = 1;
271 if ( verbose_main )
272 	fprintf( stderr, "returning next argv: %s [done_afterwards=%d]\n", p, *done_afterwards );
273 return p;
274 }
275 
276 /***********************************************************************/
277 /* returns CNULL on error */
get_score_filename(const char * outdir,const char * listfile)278 char * get_score_filename( const char * outdir, const char * listfile )
279 {
280 const char * basename;
281 int baselen;
282 int dirlen;
283 int extlen;
284 char * outfile;
285 
286 /* These are now exteranlly defined in bozorth.h */
287 /* extern FILE * stderr; */
288 /* extern char * get_progname( void ); */
289 
290 
291 
292 basename = strrchr( listfile, '/' );
293 if ( basename == CNULL ) {
294 	basename = listfile;
295 } else {
296 	++basename;
297 }
298 baselen = strlen( basename );
299 if ( baselen == 0 ) {
300 	fprintf( stderr, "%s: ERROR: couldn't find basename of %s\n", get_progname(), listfile );
301 	return(CNULL);
302 }
303 dirlen = strlen( outdir );
304 if ( dirlen == 0 ) {
305 	fprintf( stderr, "%s: ERROR: illegal output directory %s\n", get_progname(), outdir );
306 	return(CNULL);
307 }
308 
309 extlen = strlen( SCOREFILE_EXTENSION );
310 outfile = malloc_or_return_error( dirlen + baselen + extlen + 2, "output filename" );
311 if ( outfile == CNULL)
312 	return(CNULL);
313 
314 sprintf( outfile, "%s/%s%s", outdir, basename, SCOREFILE_EXTENSION );
315 
316 return outfile;
317 }
318 
319 /***********************************************************************/
get_score_line(const char * probe_file,const char * gallery_file,int n,int static_flag,const char * fmt)320 char * get_score_line(
321 		const char * probe_file,
322 		const char * gallery_file,
323 		int n,
324 		int static_flag,
325 		const char * fmt
326 		)
327 {
328 int nchars;
329 char * bufptr;
330 static char linebuf[1024];
331 
332 nchars = 0;
333 bufptr = &linebuf[0];
334 while ( *fmt ) {
335 	if ( nchars++ > 0 )
336 		*bufptr++ = ' ';
337 	switch ( *fmt++ ) {
338 		case 's':
339 			sprintf( bufptr, "%d", n );
340 			break;
341 		case 'p':
342 			sprintf( bufptr, "%s", probe_file );
343 			break;
344 		case 'g':
345 			sprintf( bufptr, "%s", gallery_file );
346 			break;
347 		default:
348 			return (char *) NULL;
349 	}
350 	bufptr = strchr( bufptr, '\0' );
351 }
352 *bufptr++ = '\n';
353 *bufptr   = '\0';
354 
355 return static_flag ? &linebuf[0] : strdup( linebuf );
356 }
357 
358 /************************************************************************
359 Load a 3-4 column (X,Y,T[,Q]) set of minutiae from the specified file.
360 Row 3's value is an angle which is normalized to the interval (-180,180].
361 A maximum of MAX_BOZORTH_MINUTIAE minutiae can be returned -- fewer if
362 "DEFAULT_BOZORTH_MINUTIAE" is smaller.  If the file contains more minutiae than are
363 to be returned, the highest-quality minutiae are returned.
364 *************************************************************************/
365 
366 /***********************************************************************/
bz_load(const char * xyt_file)367 struct xyt_struct * bz_load( const char * xyt_file )
368 {
369 int nminutiae;
370 int j;
371 int m;
372 int nargs_expected;
373 FILE * fp;
374 struct xyt_struct * s;
375 int * xptr;
376 int * yptr;
377 int * tptr;
378 int * qptr;
379 struct minutiae_struct c[MAX_FILE_MINUTIAE];
380 int	xvals_lng[MAX_FILE_MINUTIAE],	/* Temporary lists to store all the minutaie from a file */
381 	yvals_lng[MAX_FILE_MINUTIAE],
382 	tvals_lng[MAX_FILE_MINUTIAE],
383 	qvals_lng[MAX_FILE_MINUTIAE];
384 int order[MAX_FILE_MINUTIAE];		/* The ranked order, after sort, for each index */
385 int	xvals[MAX_BOZORTH_MINUTIAE],	/* Temporary lists to hold input coordinates */
386 	yvals[MAX_BOZORTH_MINUTIAE],
387 	tvals[MAX_BOZORTH_MINUTIAE],
388 	qvals[MAX_BOZORTH_MINUTIAE];
389 char xyt_line[ MAX_LINE_LENGTH ];
390 
391 /* This is now externally defined in bozorth.h */
392 /* extern FILE * stderr; */
393 
394 
395 
396 #define C1 0
397 #define C2 1
398 
399 
400 
401 fp = fopen( xyt_file, "r" );
402 if ( fp == (FILE *) NULL ) {
403 	fprintf( stderr, "%s: ERROR: fopen() of minutiae file \"%s\" failed: %s\n",
404 							get_progname(), xyt_file, strerror(errno) );
405 	return XYT_NULL;
406 }
407 
408 nminutiae = 0;
409 nargs_expected = 0;
410 while ( fgets( xyt_line, sizeof xyt_line, fp ) != CNULL ) {
411 
412 	m = sscanf( xyt_line, "%d %d %d %d",
413 				&xvals_lng[nminutiae],
414 				&yvals_lng[nminutiae],
415 				&tvals_lng[nminutiae],
416 				&qvals_lng[nminutiae] );
417 	if ( nminutiae == 0 ) {
418 		if ( m != 3 && m != 4 ) {
419 			fprintf( stderr, "%s: ERROR: sscanf() failed on line %u in minutiae file \"%s\"\n",
420 							get_progname(), nminutiae+1, xyt_file );
421 			return XYT_NULL;
422 		}
423 		nargs_expected = m;
424 	} else {
425 		if ( m != nargs_expected ) {
426 			fprintf( stderr, "%s: ERROR: inconsistent argument count on line %u of minutiae file \"%s\"\n",
427 							get_progname(), nminutiae+1, xyt_file );
428 			return XYT_NULL;
429 		}
430 	}
431 	if ( m == 3 )
432 		qvals_lng[nminutiae] = 1;
433 
434 
435 
436 	if ( tvals_lng[nminutiae] > 180 )
437 		tvals_lng[nminutiae] -= 360;
438 
439 	/*
440 	if ( C1 ) {
441 		c[nminutiae].col[0] = xvals_lng[nminutiae];
442 		c[nminutiae].col[1] = yvals_lng[nminutiae];
443 		c[nminutiae].col[2] = tvals_lng[nminutiae];
444 		c[nminutiae].col[3] = qvals_lng[nminutiae];
445 	}
446 	*/
447 
448 	++nminutiae;
449 	if ( nminutiae == MAX_FILE_MINUTIAE )
450 		break;
451 }
452 
453 if ( fclose(fp) != 0 ) {
454 	fprintf( stderr, "%s: ERROR: fclose() of minutiae file \"%s\" failed: %s\n",
455 						get_progname(), xyt_file, strerror(errno) );
456 	return XYT_NULL;
457 }
458 
459 
460 
461 
462 if ( nminutiae > DEFAULT_BOZORTH_MINUTIAE ) {
463 	if ( verbose_load )
464 		fprintf( stderr, "%s: WARNING: bz_load(): trimming minutiae to the %d of highest quality\n",
465 						get_progname(), DEFAULT_BOZORTH_MINUTIAE );
466 
467 	if ( verbose_load )
468 		fprintf( stderr, "Before quality sort:\n" );
469 	if ( sort_order_decreasing( qvals_lng, nminutiae, order )) {
470 		fprintf( stderr, "%s: ERROR: sort failed and returned on error\n", get_progname());
471 		return XYT_NULL;
472 	}
473 
474 	for ( j = 0; j < nminutiae; j++ ) {
475 
476 		if ( verbose_load )
477 			fprintf( stderr, "   %3d: %3d %3d %3d ---> order = %3d\n",
478 						j, xvals_lng[j], yvals_lng[j], qvals_lng[j], order[j] );
479 
480 		if ( j == 0 )
481 			continue;
482 		if ( qvals_lng[order[j]] > qvals_lng[order[j-1]] ) {
483 			fprintf( stderr, "%s: ERROR: sort failed: j=%d; qvals_lng[%d] > qvals_lng[%d]\n",
484 						get_progname(), j, order[j], order[j-1] );
485 			return XYT_NULL;
486 		}
487 	}
488 
489 
490 	if ( verbose_load )
491 		fprintf( stderr, "\nAfter quality sort:\n" );
492 	for ( j = 0; j < DEFAULT_BOZORTH_MINUTIAE; j++ ) {
493 		xvals[j] = xvals_lng[order[j]];
494 		yvals[j] = yvals_lng[order[j]];
495 		tvals[j] = tvals_lng[order[j]];
496 		qvals[j] = qvals_lng[order[j]];
497 		if ( verbose_load )
498 			fprintf( stderr, "   %3d: %3d %3d %3d\n", j, xvals[j], yvals[j], qvals[j] );
499 	}
500 
501 
502 	if ( C1 ) {
503 	if ( verbose_load )
504 		fprintf( stderr, "\nAfter qsort():\n" );
505 	qsort( (void *) &c, (size_t) nminutiae, sizeof(struct minutiae_struct), sort_quality_decreasing );
506 	for ( j = 0; j < nminutiae; j++ ) {
507 
508 		if ( verbose_load )
509 			fprintf( stderr, "Q  %3d: %3d %3d %3d\n",
510 						j, c[j].col[0], c[j].col[1], c[j].col[3] );
511 
512 		if ( j > 0 && c[j].col[3] > c[j-1].col[3] ) {
513 			fprintf( stderr, "%s: ERROR: sort failed: c[%d].col[3] > c[%d].col[3]\n",
514 						get_progname(), j, j-1 );
515 			return XYT_NULL;
516 		}
517 	}
518 	}
519 
520 	if ( verbose_load )
521 		fprintf( stderr, "\n" );
522 
523 	xptr = xvals;
524 	yptr = yvals;
525 	tptr = tvals;
526 	qptr = qvals;
527 
528 	nminutiae = DEFAULT_BOZORTH_MINUTIAE;
529 } else{
530 	xptr = xvals_lng;
531 	yptr = yvals_lng;
532 	tptr = tvals_lng;
533 	qptr = qvals_lng;
534 }
535 
536 
537 
538 for ( j=0; j < nminutiae; j++ ) {
539 	c[j].col[0] = xptr[j];
540 	c[j].col[1] = yptr[j];
541 	c[j].col[2] = tptr[j];
542 	c[j].col[3] = qptr[j];
543 }
544 qsort( (void *) &c, (size_t) nminutiae, sizeof(struct minutiae_struct), sort_x_y );
545 
546 
547 
548 
549 if ( verbose_load ) {
550 	fprintf( stderr, "\nSorted on increasing x, then increasing y\n" );
551 	for ( j = 0; j < nminutiae; j++ ) {
552 		fprintf( stderr, "%d : %3d, %3d, %3d, %3d\n", j, c[j].col[0], c[j].col[1], c[j].col[2], c[j].col[3] );
553 		if ( j > 0 ) {
554 			if ( c[j].col[0] < c[j-1].col[0] ) {
555 				fprintf( stderr, "%s: ERROR: sort failed: c[%d].col[0]=%d > c[%d].col[0]=%d\n",
556 							get_progname(),
557 							j, c[j].col[0], j-1, c[j-1].col[0]
558 							);
559 				return XYT_NULL;
560 			}
561 			if ( c[j].col[0] == c[j-1].col[0] && c[j].col[1] < c[j-1].col[1] ) {
562 				fprintf( stderr, "%s: ERROR: sort failed: c[%d].col[0]=%d == c[%d].col[0]=%d; c[%d].col[0]=%d == c[%d].col[0]=%d\n",
563 							get_progname(),
564 							j, c[j].col[0], j-1, c[j-1].col[0],
565 							j, c[j].col[1], j-1, c[j-1].col[1]
566 							);
567 				return XYT_NULL;
568 			}
569 		}
570 	}
571 }
572 
573 
574 
575 s = (struct xyt_struct *) malloc( sizeof( struct xyt_struct ) );
576 if ( s == XYT_NULL ) {
577 	fprintf( stderr, "%s: ERROR: malloc() failure while loading minutiae file \"%s\" failed: %s\n",
578 							get_progname(),
579 							xyt_file,
580 							strerror(errno)
581 							);
582 	return XYT_NULL;
583 }
584 
585 
586 
587 for ( j = 0; j < nminutiae; j++ ) {
588 	s->xcol[j]     = c[j].col[0];
589 	s->ycol[j]     = c[j].col[1];
590 	s->thetacol[j] = c[j].col[2];
591 }
592 s->nrows = nminutiae;
593 
594 
595 
596 
597 if ( verbose_load )
598 	fprintf( stderr, "Loaded %s\n", xyt_file );
599 
600 return s;
601 }
602 
603 /***********************************************************************/
604 #ifdef PARALLEL_SEARCH
fd_readable(int fd)605 int fd_readable( int fd )
606 {
607 int retval;
608 fd_set rfds;
609 struct timeval tv;
610 
611 
612 FD_ZERO( &rfds );
613 FD_SET( fd, &rfds );
614 tv.tv_sec = 0;
615 tv.tv_usec = 0;
616 
617 retval = select( fd+1, &rfds, NULL, NULL, &tv );
618 
619 if ( retval < 0 ) {
620 	perror( "select() failed" );
621 	return 0;
622 }
623 
624 if ( FD_ISSET( fd, &rfds ) ) {
625 	/*fprintf( stderr, "data is available now.\n" );*/
626 	return 1;
627 }
628 
629 /* fprintf( stderr, "no data is available\n" ); */
630 return 0;
631 }
632 #endif
633