indexamajig.c 17 KB
Newer Older
Thomas White's avatar
Thomas White committed
1
/*
2
 * indexamajig.c
Thomas White's avatar
Thomas White committed
3
4
5
 *
 * Find hits, index patterns, output hkl+intensity etc.
 *
Thomas White's avatar
Thomas White committed
6
 * (c) 2006-2010 Thomas White <taw@physics.org>
Thomas White's avatar
Thomas White committed
7
8
9
10
11
12
13
14
15
16
 *
 * Part of CrystFEL - crystallography with a FEL
 *
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

17
#define _GNU_SOURCE 1
Thomas White's avatar
Thomas White committed
18
19
20
21
22
23
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
Thomas White's avatar
Thomas White committed
24
#include <hdf5.h>
Thomas White's avatar
Thomas White committed
25
#include <gsl/gsl_errno.h>
26
#include <pthread.h>
Thomas White's avatar
Thomas White committed
27
#include <sys/time.h>
Thomas White's avatar
Thomas White committed
28
29
30

#include "utils.h"
#include "hdf5-file.h"
Thomas White's avatar
Thomas White committed
31
#include "index.h"
32
#include "peaks.h"
33
#include "diffraction.h"
Thomas White's avatar
Thomas White committed
34
#include "diffraction-gpu.h"
35
#include "detector.h"
36
#include "sfac.h"
Thomas White's avatar
Thomas White committed
37
#include "filters.h"
38
#include "reflections.h"
Thomas White's avatar
Thomas White committed
39
40


41
42
43
44
45
#define MAX_THREADS (96)

struct process_args
{
	char *filename;
Thomas White's avatar
Thomas White committed
46
	int id;
47
48
	pthread_mutex_t *output_mutex;  /* Protects stdout */
	pthread_mutex_t *gpu_mutex;     /* Protects "gctx" */
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
	UnitCell *cell;
	int config_cmfilter;
	int config_noisefilter;
	int config_writedrx;
	int config_dumpfound;
	int config_verbose;
	int config_alternate;
	int config_nearbragg;
	int config_gpu;
	int config_simulate;
	int config_nomatch;
	IndexingMethod indm;
	const double *intensities;
	const unsigned int *counts;
	struct gpu_context *gctx;
};

struct process_result
{
	int hit;
	struct process_args *pargs;
};


Thomas White's avatar
Thomas White committed
73
74
75
76
77
78
79
80
81
82
static void show_help(const char *s)
{
	printf("Syntax: %s [options]\n\n", s);
	printf(
"Process and index FEL diffraction images.\n"
"\n"
"  -h, --help              Display this help message.\n"
"\n"
"  -i, --input=<filename>  Specify file containing list of images to process.\n"
"                           '-' means stdin, which is the default.\n"
Thomas White's avatar
Thomas White committed
83
84
85
"      --indexing=<method> Use 'method' for indexing.  Choose from:\n"
"                           none     : no indexing\n"
"                           dirax    : invoke DirAx\n"
Thomas White's avatar
Thomas White committed
86
87
"\n\nWith just the above options, this program does not do much of practical "
"use.\nYou should also enable some of the following:\n\n"
88
"      --near-bragg        Output a list of reflection intensities to stdout.\n"
Thomas White's avatar
Thomas White committed
89
90
91
92
93
94
95
96
97
98
"                           When pixels with fractional indices within 0.1 of\n"
"                           integer values (the Bragg condition) are found,\n"
"                           the integral of pixels within a ten pixel radius\n"
"                           of the nearest-to-Bragg pixel will be reported as\n"
"                           the intensity.  The centroid of the pixels will\n"
"                           be given as the coordinates, as well as the h,k,l\n"
"                           (integer) indices of the reflection.  If a peak\n"
"                           was located by the initial peak search close to\n"
"                           the \"near Bragg\" location, its coordinates will\n"
"                           be taken as the centre instead.\n"
99
100
"      --simulate          Simulate the diffraction pattern using the indexed\n"
"                           unit cell.\n"
101
102
103
104
105
106
107
"      --filter-cm         Perform common-mode noise subtraction on images\n"
"                           before proceeding.  Intensities will be extracted\n"
"                           from the image as it is after this processing.\n"
"      --filter-noise      Apply an aggressive noise filter which sets all\n"
"                           pixels in each 3x3 region to zero if any of them\n"
"                           have negative values.  Intensity measurement will\n"
"                           be performed on the image as it was before this.\n"
108
109
110
111
112
"      --write-drx         Write 'xfel.drx' for visualisation of reciprocal\n"
"                           space.  Implied by any indexing method other than\n"
"                           'none'.  Beware: the units in this file are\n"
"                           reciprocal Angstroms.\n"
"      --dump-peaks        Write the results of the peak search to stdout.\n"
113
114
"                           The intensities in this list are from the\n"
"                           centroid/integration procedure.\n"
115
116
117
"      --no-match          Don't attempt to match the indexed cell to the\n"
"                           model, just proceed with the one generated by the\n"
"                           auto-indexing procedure.\n"
Thomas White's avatar
Thomas White committed
118
119
120
121
122
"\n\nOptions for greater performance or verbosity:\n\n"
"      --verbose           Be verbose about indexing.\n"
"      --gpu               Use the GPU to speed up the simulation.\n"
"  -j <n>                  Run <n> analyses in parallel.  Default 1.\n"
"\n\nControl of model and data input:\n\n"
Thomas White's avatar
Thomas White committed
123
124
125
"     --intensities=<file> Specify file containing reflection intensities\n"
"                           to use when simulating.\n"
" -p, --pdb=<file>         PDB file from which to get the unit cell to match.\n"
126
" -x, --prefix=<p>         Prefix filenames from input file with 'p'.\n"
Thomas White's avatar
Thomas White committed
127
);
Thomas White's avatar
Thomas White committed
128
129
130
}


131
static struct image *get_simage(struct image *template, int alternate)
132
{
133
	struct image *image;
134
	struct panel panels[2];
135

136
137
	image = malloc(sizeof(*image));

138
	/* Simulate a diffraction pattern */
139
140
141
	image->twotheta = NULL;
	image->data = NULL;
	image->det = template->det;
142
143

	/* View head-on (unit cell is tilted) */
144
145
146
147
	image->orientation.w = 1.0;
	image->orientation.x = 0.0;
	image->orientation.y = 0.0;
	image->orientation.z = 0.0;
148
149
150

	/* Detector geometry for the simulation
	 * - not necessarily the same as the original. */
151
152
153
	image->width = 1024;
	image->height = 1024;
	image->det.n_panels = 2;
154

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
	if ( alternate ) {

		/* Upper */
		panels[0].min_x = 0;
		panels[0].max_x = 1023;
		panels[0].min_y = 512;
		panels[0].max_y = 1023;
		panels[0].cx = 523.6;
		panels[0].cy = 502.5;
		panels[0].clen = 56.4e-2;  /* 56.4 cm */
		panels[0].res = 13333.3;   /* 75 microns/pixel */

		/* Lower */
		panels[1].min_x = 0;
		panels[1].max_x = 1023;
		panels[1].min_y = 0;
		panels[1].max_y = 511;
		panels[1].cx = 520.8;
		panels[1].cy = 772.1;
		panels[1].clen = 56.7e-2;  /* 56.7 cm */
		panels[1].res = 13333.3;   /* 75 microns/pixel */

		image->det.panels = panels;
178

179
180
181
182
183
184
	} else {

		/* Copy pointer to old geometry */
		image->det.panels = template->det.panels;

	}
185

186
	image->lambda = ph_en_to_lambda(eV_to_J(1.8e3));
187
	image->features = template->features;
188
189
190

	return image;
}
191
192


193
static void simulate_and_write(struct image *simage, struct gpu_context **gctx,
194
195
                               const double *intensities,
                               const unsigned int *counts, UnitCell *cell)
196
{
197
	/* Set up GPU if necessary */
Thomas White's avatar
Thomas White committed
198
	if ( (gctx != NULL) && (*gctx == NULL) ) {
199
		*gctx = setup_gpu(0, simage, intensities, counts);
200
201
	}

Thomas White's avatar
Thomas White committed
202
	if ( (gctx != NULL) && (*gctx != NULL) ) {
203
		get_diffraction_gpu(*gctx, simage, 24, 24, 40, cell);
204
	} else {
205
206
		get_diffraction(simage, 24, 24, 40,
		                intensities, counts, cell, 0);
207
	}
208
	record_image(simage, 0);
209

210
	hdf5_write("simulated.h5", simage->data, simage->width, simage->height,
211
212
213
214
		   H5T_NATIVE_FLOAT);
}


215
static void *process_image(void *pargsv)
216
{
217
	struct process_args *pargs = pargsv;
218
219
220
221
222
	struct hdfile *hdfile;
	struct image image;
	struct image *simage;
	float *data_for_measurement;
	size_t data_size;
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
	const char *filename = pargs->filename;
	UnitCell *cell = pargs->cell;
	int config_cmfilter = pargs->config_cmfilter;
	int config_noisefilter = pargs->config_noisefilter;
	int config_writedrx = pargs->config_writedrx;
	int config_dumpfound = pargs->config_dumpfound;
	int config_verbose = pargs->config_verbose;
	int config_alternate  = pargs->config_alternate;
	int config_nearbragg = pargs->config_nearbragg;
	int config_gpu = pargs->config_gpu;
	int config_simulate = pargs->config_simulate;
	int config_nomatch = pargs->config_nomatch;
	IndexingMethod indm = pargs->indm;
	const double *intensities = pargs->intensities;
	const unsigned int *counts = pargs->counts;
	struct gpu_context *gctx = pargs->gctx;
	struct process_result *result;
240
241
242
243

	image.features = NULL;
	image.data = NULL;
	image.indexed_cell = NULL;
Thomas White's avatar
Thomas White committed
244
	image.id = pargs->id;
245
246
247

	STATUS("Processing '%s'\n", filename);

248
249
250
251
	result = malloc(sizeof(*result));
	if ( result == NULL ) return NULL;
	result->pargs = pargs;

252
253
	hdfile = hdfile_open(filename);
	if ( hdfile == NULL ) {
254
255
		result->hit = 0;
		return result;
256
257
	} else if ( hdfile_set_first_image(hdfile, "/") ) {
		ERROR("Couldn't select path\n");
258
259
		result->hit = 0;
		return result;
260
261
	}

262
263
	#include "geometry-lcls.tmp"

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
	hdf5_read(hdfile, &image);

	if ( config_cmfilter ) {
		filter_cm(&image);
	}

	/* Take snapshot of image after CM subtraction but before
	 * the aggressive noise filter. */
	data_size = image.width*image.height*sizeof(float);
	data_for_measurement = malloc(data_size);

	if ( config_noisefilter ) {
		filter_noise(&image, data_for_measurement);
	} else {

		int x, y;

		for ( x=0; x<image.width; x++ ) {
		for ( y=0; y<image.height; y++ ) {
			float val;
			val = image.data[x+image.width*y];
			data_for_measurement[x+image.width*y] = val;
		}
		}

	}

	/* Perform 'fine' peak search */
	search_peaks(&image);
	if ( image_feature_count(image.features) < 5 ) goto done;

	if ( config_dumpfound ) dump_peaks(&image);

	/* Not indexing nor writing xfel.drx?
	 * Then there's nothing left to do. */
	if ( (!config_writedrx) && (indm == INDEXING_NONE) ) {
		goto done;
	}

	/* Calculate orientation matrix (by magic) */
	if ( config_writedrx || (indm != INDEXING_NONE) ) {
		index_pattern(&image, cell, indm, config_nomatch,
		              config_verbose);
	}

	/* No cell at this point?  Then we're done. */
	if ( image.indexed_cell == NULL ) goto done;

	simage = get_simage(&image, config_alternate);

	/* Measure intensities if requested */
	if ( config_nearbragg ) {
		/* Use original data (temporarily) */
		simage->data = data_for_measurement;
318
		pthread_mutex_lock(pargs->output_mutex);
319
		output_intensities(simage, image.indexed_cell);
320
		pthread_mutex_unlock(pargs->output_mutex);
321
322
323
324
325
326
		simage->data = NULL;
	}

	/* Simulate if requested */
	if ( config_simulate ) {
		if ( config_gpu ) {
327
			pthread_mutex_lock(pargs->gpu_mutex);
328
329
			simulate_and_write(simage, &gctx, intensities,
			                   counts, cell);
330
			pthread_mutex_unlock(pargs->gpu_mutex);
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
		} else {
			simulate_and_write(simage, NULL, intensities,
			                   counts, cell);
		}
	}

	/* Finished with alternate image */
	if ( simage->twotheta != NULL ) free(simage->twotheta);
	if ( simage->data != NULL ) free(simage->data);
	free(simage);

	/* Only free cell if found */
	free(image.indexed_cell);

done:
	free(image.data);
	free(image.det.panels);
	image_feature_list_free(image.features);
	free(data_for_measurement);
	hdfile_close(hdfile);

352
353
354
355
356
357
	if ( image.indexed_cell == NULL ) {
		result->hit = 0;
	} else {
		result->hit = 1;
	}
	return result;
358
359
360
}


Thomas White's avatar
Thomas White committed
361
362
363
int main(int argc, char *argv[])
{
	int c;
Thomas White's avatar
Thomas White committed
364
	struct gpu_context *gctx = NULL;
Thomas White's avatar
Thomas White committed
365
366
	char *filename = NULL;
	FILE *fh;
367
	char *rval = NULL;
Thomas White's avatar
Thomas White committed
368
369
	int n_images;
	int n_hits;
370
	int config_noindex = 0;
Thomas White's avatar
Thomas White committed
371
	int config_dumpfound = 0;
372
	int config_nearbragg = 0;
Thomas White's avatar
Thomas White committed
373
	int config_writedrx = 0;
374
	int config_simulate = 0;
375
376
	int config_cmfilter = 0;
	int config_noisefilter = 0;
377
	int config_nomatch = 0;
Thomas White's avatar
Thomas White committed
378
	int config_gpu = 0;
379
	int config_verbose = 0;
380
	int config_alternate = 0;
Thomas White's avatar
Thomas White committed
381
382
	IndexingMethod indm;
	char *indm_str = NULL;
383
384
385
	UnitCell *cell;
	double *intensities = NULL;
	char *intfile = NULL;
386
	unsigned int *counts = NULL;
Thomas White's avatar
Thomas White committed
387
	char *pdb = NULL;
388
	char *prefix = NULL;
389
390
391
	int nthreads = 1;
	pthread_t workers[MAX_THREADS];
	struct process_args *worker_args[MAX_THREADS];
Thomas White's avatar
Thomas White committed
392
	int worker_active[MAX_THREADS];
393
	int i;
394
	pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER;
395
	pthread_mutex_t gpu_mutex = PTHREAD_MUTEX_INITIALIZER;
Thomas White's avatar
Thomas White committed
396
397
398
399
400

	/* Long options */
	const struct option longopts[] = {
		{"help",               0, NULL,               'h'},
		{"input",              1, NULL,               'i'},
Thomas White's avatar
Thomas White committed
401
		{"gpu",                0, &config_gpu,         1},
402
		{"no-index",           0, &config_noindex,     1},
Thomas White's avatar
Thomas White committed
403
		{"dump-peaks",         0, &config_dumpfound,   1},
404
		{"near-bragg",         0, &config_nearbragg,   1},
Thomas White's avatar
Thomas White committed
405
406
		{"write-drx",          0, &config_writedrx,    1},
		{"indexing",           1, NULL,               'z'},
407
		{"simulate",           0, &config_simulate,    1},
408
409
		{"filter-cm",          0, &config_cmfilter,    1},
		{"filter-noise",       0, &config_noisefilter, 1},
410
		{"no-match",           0, &config_nomatch,     1},
411
		{"verbose",            0, &config_verbose,     1},
412
		{"alternate",          0, &config_alternate,   1},
413
		{"intensities",        1, NULL,               'q'},
Thomas White's avatar
Thomas White committed
414
		{"pdb",                1, NULL,               'p'},
415
		{"prefix",             1, NULL,               'x'},
Thomas White's avatar
Thomas White committed
416
417
418
419
		{0, 0, NULL, 0}
	};

	/* Short options */
420
	while ((c = getopt_long(argc, argv, "hi:wp:j:", longopts, NULL)) != -1) {
Thomas White's avatar
Thomas White committed
421
422
423
424
425
426
427
428
429
430
431
432

		switch (c) {
		case 'h' : {
			show_help(argv[0]);
			return 0;
		}

		case 'i' : {
			filename = strdup(optarg);
			break;
		}

Thomas White's avatar
Thomas White committed
433
434
435
436
437
		case 'z' : {
			indm_str = strdup(optarg);
			break;
		}

438
439
440
441
442
		case 'q' : {
			intfile = strdup(optarg);
			break;
		}

Thomas White's avatar
Thomas White committed
443
444
445
446
447
		case 'p' : {
			pdb = strdup(optarg);
			break;
		}

448
449
450
451
452
		case 'x' : {
			prefix = strdup(optarg);
			break;
		}

453
454
455
456
457
		case 'j' : {
			nthreads = atoi(optarg);
			break;
		}

Thomas White's avatar
Thomas White committed
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
		case 0 : {
			break;
		}

		default : {
			return 1;
		}
		}

	}

	if ( filename == NULL ) {
		filename = strdup("-");
	}
	if ( strcmp(filename, "-") == 0 ) {
		fh = stdin;
	} else {
		fh = fopen(filename, "r");
	}
	free(filename);
	if ( fh == NULL ) {
		ERROR("Failed to open input file\n");
		return 1;
	}

483
	if ( intfile != NULL ) {
484
		intensities = read_reflections(intfile, counts);
485
486
	} else {
		intensities = NULL;
487
		counts = NULL;
488
489
	}

Thomas White's avatar
Thomas White committed
490
491
492
493
	if ( pdb == NULL ) {
		pdb = strdup("molecule.pdb");
	}

494
	if ( prefix == NULL ) {
Thomas White's avatar
Thomas White committed
495
		prefix = strdup("");
496
497
	}

498
	if ( (nthreads == 0) || (nthreads > MAX_THREADS) ) {
499
500
501
502
		ERROR("Invalid number of threads.\n");
		return 1;
	}

Thomas White's avatar
Thomas White committed
503
504
	if ( indm_str == NULL ) {
		STATUS("You didn't specify an indexing method, so I won't"
505
506
507
		       " try to index anything.\n"
		       "If that isn't what you wanted, re-run with"
		       " --indexing=<method>.\n");
Thomas White's avatar
Thomas White committed
508
509
510
511
512
513
514
515
516
		indm = INDEXING_NONE;
	} else if ( strcmp(indm_str, "none") == 0 ) {
		indm = INDEXING_NONE;
	} else if ( strcmp(indm_str, "dirax") == 0) {
		indm = INDEXING_DIRAX;
	} else {
		ERROR("Unrecognised indexing method '%s'\n", indm_str);
		return 1;
	}
517
	free(indm_str);
Thomas White's avatar
Thomas White committed
518

Thomas White's avatar
Thomas White committed
519
520
	cell = load_cell_from_pdb(pdb);
	free(pdb);
521
522
	if ( cell == NULL ) {
		ERROR("Couldn't read unit cell (from molecule.pdb)\n");
523
524
525
		return 1;
	}

Thomas White's avatar
Thomas White committed
526
	gsl_set_error_handler_off();
Thomas White's avatar
Thomas White committed
527
528
	n_images = 0;
	n_hits = 0;
529
530
531

	/* Initially, fire off the full number of threads */
	for ( i=0; i<nthreads; i++ ) {
Thomas White's avatar
Thomas White committed
532
533

		char line[1024];
534
535
536
		char *prefixed;
		struct process_args *pargs;
		int r;
Thomas White's avatar
Thomas White committed
537

Thomas White's avatar
Thomas White committed
538
539
		worker_active[i] = 0;

Thomas White's avatar
Thomas White committed
540
		rval = fgets(line, 1023, fh);
Thomas White's avatar
Thomas White committed
541
		if ( rval == NULL ) continue;
Thomas White's avatar
Thomas White committed
542
		chomp(line);
543
		prefixed = malloc(1024);
544
545
		snprintf(prefixed, 1023, "%s%s", prefix, line);

546
547
		n_images++;

548
549
		pargs = malloc(sizeof(*pargs));
		pargs->filename = prefixed;
550
		pargs->output_mutex = &output_mutex;
551
		pargs->gpu_mutex = &gpu_mutex;
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
		pargs->config_cmfilter = config_cmfilter;
		pargs->config_noisefilter = config_noisefilter;
		pargs->config_writedrx = config_writedrx;
		pargs->config_dumpfound = config_dumpfound;
		pargs->config_verbose = config_verbose;
		pargs->config_alternate = config_alternate;
		pargs->config_nearbragg = config_nearbragg;
		pargs->config_gpu = config_gpu;
		pargs->config_simulate = config_simulate;
		pargs->config_nomatch = config_nomatch;
		pargs->cell = cell;
		pargs->indm = indm;
		pargs->intensities = intensities;
		pargs->counts = counts;
		pargs->gctx = gctx;
Thomas White's avatar
Thomas White committed
567
		pargs->id = i;
568
569
		worker_args[i] = pargs;

Thomas White's avatar
Thomas White committed
570
		worker_active[i] = 1;
571
572
		r = pthread_create(&workers[i], NULL, process_image, pargs);
		if ( r != 0 ) {
Thomas White's avatar
Thomas White committed
573
			worker_active[i] = 0;
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
			ERROR("Couldn't start thread %i\n", i);
		}

	}

	/* Start new threads as old ones finish */
	do {

		int i;

		for ( i=0; i<nthreads; i++ ) {

			char line[1024];
			char *prefixed;
			int r;
			struct process_result *result = NULL;
			struct timespec t;
Thomas White's avatar
Thomas White committed
591
			struct timeval tv;
592
593
			struct process_args *pargs;

Thomas White's avatar
Thomas White committed
594
595
596
597
598
			if ( !worker_active[i] ) continue;

			gettimeofday(&tv, NULL);
			t.tv_sec = tv.tv_sec;
			t.tv_nsec = tv.tv_usec * 1000 + 20000;
599
600
601
602
603

			r = pthread_timedjoin_np(workers[i], (void *)&result,
			                         &t);
			if ( r != 0 ) continue; /* Not ready yet */

Thomas White's avatar
Thomas White committed
604
605
			worker_active[i] = 0;

606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
			if ( result != NULL ) {
				n_hits += result->hit;
				free(result);
			}

			rval = fgets(line, 1023, fh);
			if ( rval == NULL ) break;
			chomp(line);
			prefixed = malloc(1024);
			snprintf(prefixed, 1023, "%s%s", prefix, line);

			pargs = worker_args[i];
			free(pargs->filename);
			pargs->filename = prefixed;
			/* Other arguments unchanged */

Thomas White's avatar
Thomas White committed
622
			worker_active[i] = 1;
623
624
625
			r = pthread_create(&workers[i], NULL, process_image,
			                   pargs);
			if ( r != 0 ) {
Thomas White's avatar
Thomas White committed
626
				worker_active[i] = 0;
627
628
629
630
631
				ERROR("Couldn't start thread %i\n", i);
			}

			n_images++;
		}
Thomas White's avatar
Thomas White committed
632

Thomas White's avatar
Thomas White committed
633
634
	} while ( rval != NULL );

Thomas White's avatar
Thomas White committed
635
	/* Catch all remaining threads */
636
	for ( i=0; i<nthreads; i++ ) {
Thomas White's avatar
Thomas White committed
637
638
639
640
641
642
643
644
645
646
647
648
649
650

		struct process_result *result = NULL;

		if ( !worker_active[i] ) continue;

		pthread_join(workers[i], (void *)&result);

		worker_active[i] = 0;

		if ( result != NULL ) {
			n_hits += result->hit;
			free(result);
		}

651
652
		free(worker_args[i]->filename);
		free(worker_args[i]);
Thomas White's avatar
Thomas White committed
653

654
655
656
657
	}

	free(prefix);
	free(cell);
Thomas White's avatar
Thomas White committed
658
659
660
661
662
	fclose(fh);

	STATUS("There were %i images.\n", n_images);
	STATUS("%i hits were found.\n", n_hits);

Thomas White's avatar
Thomas White committed
663
664
665
666
	if ( gctx != NULL ) {
		cleanup_gpu(gctx);
	}

Thomas White's avatar
Thomas White committed
667
668
	return 0;
}