Surface
detect surface points in an image stack
|
00001 /* 00002 * This work is licensed under a Creative Commons 00003 * Attribution-Noncommercial-Share Alike 3.0 United States License. 00004 * 00005 * http://creativecommons.org/licenses/by-nc-sa/3.0/us/ 00006 * 00007 * You are free: 00008 * 00009 * to Share - to copy, distribute, display, and perform the work 00010 * to Remix - to make derivative works 00011 * 00012 * Under the following conditions: 00013 * 00014 * Attribution. You must attribute the work in the manner specified by the 00015 * author or licensor (but not in any way that suggests that they endorse you 00016 * or your use of the work). 00017 * 00018 * Noncommercial. You may not use this work for commercial purposes. 00019 * 00020 * Share Alike. If you alter, transform, or build upon this work, you may 00021 * distribute the resulting work only under the same or similar license to 00022 * this one. 00023 * 00024 * For any reuse or distribution, you must make clear to others the license 00025 * terms of this work. The best way to do this is by including this header. 00026 * 00027 * Any of the above conditions can be waived if you get permission from the 00028 * copyright holder. 00029 * 00030 * Apart from the remix rights granted under this license, nothing in this 00031 * license impairs or restricts the author's moral rights. 00032 */ 00033 00034 00105 #include <config.h> 00106 00107 #include <stdlib.h> 00108 #include <stdio.h> 00109 #include <assert.h> 00110 #include <inttypes.h> 00111 #include <string.h> 00112 00113 #ifdef SURFACE_HAVE_DMALLOC 00114 #include <dmalloc.h> 00115 #endif 00116 00117 #include <jwsc/base/error.h> 00118 #include <jwsc/base/option.h> 00119 #include <jwsc/matblock/matblock.h> 00120 #include <jwsc/imgblock/imgblock.h> 00121 #include <jwsc/imgblock/imgblock_io.h> 00122 #include <jwsc/imgblock/imgblock_util.h> 00123 #include <jwsc/imgblock/surface.h> 00124 00125 00126 #define DEFAULT_BEGIN_THRESH 0.06f 00127 #define DEFAULT_END_THRESH 0.025f 00128 #define DEFAULT_SIGMA 1.0f 00129 #define DEFAULT_SAMPLE_RATE 1.0f 00130 #define DEFAULT_GRAD_IMG_WEIGHT 1.0f 00131 #define DEFAULT_GRAD_ROW_WEIGHT 1.0f 00132 #define DEFAULT_GRAD_COL_WEIGHT 1.0f 00133 #define DEFAULT_RES_IMG 1.0f 00134 #define DEFAULT_RES_ROW 1.0f 00135 #define DEFAULT_RES_COL 1.0f 00136 00137 #define NUM_OPTS_NO_ARG 3 00138 #define NUM_OPTS_WITH_ARG 9 00139 00140 00142 static Option_no_arg opts_no_arg[NUM_OPTS_NO_ARG]; 00143 00145 static Option_with_arg opts_with_arg[NUM_OPTS_WITH_ARG]; 00146 00147 00149 static const char* pts_fname; 00150 00152 static const char* map_fname; 00153 00155 static const char* colored_fname; 00156 00161 static float begin_thresh; 00162 00166 static float end_thresh; 00167 00169 static float sigma; 00170 00172 static float sample_rate; 00173 00175 static float grad_img_weight; 00176 00178 static float grad_row_weight; 00179 00181 static float grad_col_weight; 00182 00187 static uint8_t images_named; 00188 00190 static float res_img; 00191 00193 static float res_row; 00194 00196 static float res_col; 00197 00198 00200 static void print_usage(void) 00201 { 00202 fprintf(stderr, "usage: surface OPTIONS [img-%%d num_imgs | img-1 ... img-n]\n"); 00203 fprintf(stderr, "where OPTIONS is one or more of the following:\n"); 00204 print_options(stderr, 25, NUM_OPTS_NO_ARG, opts_no_arg, NUM_OPTS_WITH_ARG, 00205 opts_with_arg); 00206 } 00207 00208 00210 static Error* process_help_opt(void) 00211 { 00212 print_usage(); 00213 exit(EXIT_SUCCESS); 00214 return NULL; 00215 } 00216 00217 00219 static Error* process_version_opt(void) 00220 { 00221 fprintf(stderr, "%s\n", SURFACE_PACKAGE_STRING); 00222 exit(EXIT_SUCCESS); 00223 return NULL; 00224 } 00225 00226 00228 static Error* process_named_opt(void) 00229 { 00230 images_named = 1; 00231 return NULL; 00232 } 00233 00234 00236 static Error* process_colored_out_opt(Option_arg arg) 00237 { 00238 if (arg == NULL) 00239 { 00240 return JWSC_EARG("Option 'colored-out' requires an argument"); 00241 } 00242 colored_fname = arg; 00243 return NULL; 00244 } 00245 00246 00248 static Error* process_map_out_opt(Option_arg arg) 00249 { 00250 if (arg == NULL) 00251 { 00252 return JWSC_EARG("Option 'map-out' requires an argument"); 00253 } 00254 map_fname = arg; 00255 return NULL; 00256 } 00257 00258 00260 static Error* process_pts_out_opt(Option_arg arg) 00261 { 00262 if (arg == NULL) 00263 { 00264 return JWSC_EARG("Option 'pts-out' requires an argument"); 00265 } 00266 pts_fname = arg; 00267 return NULL; 00268 } 00269 00270 00272 static Error* process_begin_thresh_opt(Option_arg arg) 00273 { 00274 if (arg == NULL) 00275 { 00276 return JWSC_EARG("Option 'begin-thresh' requires an argument"); 00277 } 00278 if (sscanf(arg, "%f", &begin_thresh) != 1 || begin_thresh < 0) 00279 { 00280 return JWSC_EARG("Option 'begin-thresh' must be >= 0"); 00281 } 00282 return NULL; 00283 } 00284 00285 00287 static Error* process_end_thresh_opt(Option_arg arg) 00288 { 00289 if (arg == NULL) 00290 { 00291 return JWSC_EARG("Option 'end-thresh' requires an argument"); 00292 } 00293 if (sscanf(arg, "%f", &end_thresh) != 1 || end_thresh < 0) 00294 { 00295 return JWSC_EARG("Option 'end-thresh' must be >= 0"); 00296 } 00297 return NULL; 00298 } 00299 00300 00302 static Error* process_sigma_opt(Option_arg arg) 00303 { 00304 if (arg == NULL) 00305 { 00306 return JWSC_EARG("Option 'sigma' requires an argument"); 00307 } 00308 if (sscanf(arg, "%f", &sigma) != 1 || sigma <= 0) 00309 { 00310 return JWSC_EARG("Option 'sigma' must be > 0"); 00311 } 00312 return NULL; 00313 } 00314 00315 00317 static Error* process_sample_rate_opt(Option_arg arg) 00318 { 00319 if (arg == NULL) 00320 { 00321 return JWSC_EARG("Option 'sample-rate' requires an argument"); 00322 } 00323 if (sscanf(arg, "%f", &sample_rate) != 1 || sample_rate <= 0 || 00324 sample_rate > 1.0) 00325 { 00326 return JWSC_EARG("Option 'sample-rate' must be in (0, 1]"); 00327 } 00328 return NULL; 00329 } 00330 00331 00333 static Error* process_grad_weight_opt(Option_arg arg) 00334 { 00335 if (arg == NULL) 00336 { 00337 return JWSC_EARG("Option 'grad-weight' requires an argument"); 00338 } 00339 if (sscanf(arg, "%f,%f,%f", &grad_img_weight, &grad_row_weight, 00340 &grad_col_weight) != 3) 00341 { 00342 return JWSC_EARG("Option 'grad-weight' has format img,row,col"); 00343 } 00344 if (grad_img_weight < 0 || grad_row_weight < 0 || grad_col_weight < 0) 00345 { 00346 return JWSC_EARG("Option 'grad-weight' must have img,row,col >= 0"); 00347 } 00348 if ((grad_img_weight + grad_row_weight + grad_col_weight) <= 1.0e-8) 00349 { 00350 return JWSC_EARG("Option 'grad-weight' must have img+row+col > 0"); 00351 } 00352 return NULL; 00353 } 00354 00355 00357 static Error* process_resolution_opt(Option_arg arg) 00358 { 00359 if (arg == NULL) 00360 { 00361 return JWSC_EARG("Option 'resolution' requires an argument"); 00362 } 00363 if (sscanf(arg, "%f,%f,%f", &res_img, &res_row, &res_col) != 3) 00364 { 00365 return JWSC_EARG("Option 'resolution' has format img,row,col"); 00366 } 00367 if (res_img <= 0 || res_img > 1 || res_row <= 0 || res_row > 1 || 00368 res_col <= 0 || res_col > 1) 00369 { 00370 return JWSC_EARG("Option 'resolution' must be in (0,1]"); 00371 } 00372 return NULL; 00373 } 00374 00375 00376 00377 00378 00380 int main(int argc, const char** argv) 00381 { 00382 uint32_t num_rows, num_cols; 00383 uint32_t num_pts; 00384 uint32_t i, p; 00385 int32_t num_imgs; 00386 int argi; 00387 int arg; 00388 00389 char* chars; 00390 char** names; 00391 const char* input_fname; 00392 00393 Pixel_f pxl; 00394 00395 Imgblock_f* img_in = NULL; 00396 Imgblock_f* img_out = NULL; 00397 Matblock_f* m = NULL; 00398 Error* e = NULL; 00399 Surface_point_f* pts = NULL; 00400 Surface_point_f* pt = NULL; 00401 00402 /* Init the options. */ 00403 init_option_no_arg(&(opts_no_arg[0]), "help", 'h', 00404 "Prints program usage.", process_help_opt); 00405 init_option_no_arg(&(opts_no_arg[1]), "version", 'v', 00406 "Prints program version.", process_version_opt); 00407 init_option_no_arg(&(opts_no_arg[2]), "named", 'n', 00408 "Indicates that the images to read are fully named and listed in sequence on the command line. The default is to read the images as numbered by a printf-formatted name.", process_named_opt); 00409 init_option_with_arg(&(opts_with_arg[0]), "colored-out", 'c', 00410 "Original images with surface colored. Use a printf formatted name to output the images in a numbered sequence.", process_colored_out_opt); 00411 init_option_with_arg(&(opts_with_arg[1]), "map-out", 'm', 00412 "Bi-level image surface map. Use a printf formatted name to output the images in a numbered sequence.", process_map_out_opt); 00413 init_option_with_arg(&(opts_with_arg[2]), "pts-out", 'p', 00414 "ASCII formatted surface points.", process_pts_out_opt); 00415 init_option_with_arg(&(opts_with_arg[3]), "begin-thresh", 'b', 00416 "Hysterisis starting threshold.", process_begin_thresh_opt); 00417 init_option_with_arg(&(opts_with_arg[4]), "end-thresh", 'e', 00418 "Hysterisis stopping threshold.", process_end_thresh_opt); 00419 init_option_with_arg(&(opts_with_arg[5]), "sigma", 's', 00420 "Gaussian smoothing sigma.", process_sigma_opt); 00421 init_option_with_arg(&(opts_with_arg[6]), "sample-rate", 'r', 00422 "Surface point sampling rate.", process_sample_rate_opt); 00423 init_option_with_arg(&(opts_with_arg[7]), "grad-weight", 'w', 00424 "Weights for each direction of the gradient. Format for the argument is 'img,row,col'. They all must be non-negative and sum to > 0.", 00425 process_grad_weight_opt); 00426 init_option_with_arg(&(opts_with_arg[8]), "resolution", 'w', 00427 "Down-sampling resolution for the input image stack. Format for the argument is 'img,row,col'. They all must be in (0,1].", 00428 process_resolution_opt); 00429 00430 /* Init default option values. */ 00431 map_fname = NULL; 00432 colored_fname = NULL; 00433 pts_fname = NULL; 00434 begin_thresh = DEFAULT_BEGIN_THRESH; 00435 end_thresh = DEFAULT_END_THRESH; 00436 sigma = DEFAULT_SIGMA; 00437 sample_rate = DEFAULT_SAMPLE_RATE; 00438 grad_img_weight = DEFAULT_GRAD_IMG_WEIGHT; 00439 grad_row_weight = DEFAULT_GRAD_ROW_WEIGHT; 00440 grad_col_weight = DEFAULT_GRAD_COL_WEIGHT; 00441 res_img = DEFAULT_RES_IMG; 00442 res_row = DEFAULT_RES_ROW; 00443 res_col = DEFAULT_RES_COL; 00444 images_named = 0; 00445 00446 /* Check for the help option first. */ 00447 process_option_no_arg(argc, argv, &argi, &(opts_no_arg[0])); 00448 00449 if ((e = process_options(argc, argv, &argi, NUM_OPTS_NO_ARG, opts_no_arg, 00450 NUM_OPTS_WITH_ARG, opts_with_arg)) != NULL) 00451 { 00452 print_error_msg_exit("surface", e->msg); 00453 } 00454 00455 /* Either read the image block as a printf numbered sequence, or a list of 00456 * names in argv. */ 00457 if (images_named) 00458 { 00459 if ((argc - argi) < 1) 00460 { 00461 print_error_msg_exit("surface", "No input images"); 00462 } 00463 00464 num_imgs = argc - argi; 00465 chars = calloc(num_imgs*256, sizeof(char)); 00466 names = malloc(num_imgs*sizeof(char*)); 00467 arg = argi; 00468 for (i = 0; i < num_imgs; i++) 00469 { 00470 names[ i ] = &(chars[ i*256 ]); 00471 strncpy(names[ i ], argv[ arg++ ], 255); 00472 } 00473 if ((e = read_imgblock_named_f(&img_in, (const char**)names, num_imgs)) 00474 != NULL) 00475 { 00476 print_error_msg_exit("surface", e->msg); 00477 } 00478 free(chars); 00479 free(names); 00480 } 00481 else 00482 { 00483 if ((argc - argi) < 2) 00484 { 00485 print_error_msg_exit("surface", "No input image format and count"); 00486 } 00487 00488 input_fname = argv[ argi ]; 00489 if (sscanf(argv[ argi+1 ], "%d", &num_imgs) != 1 || num_imgs <= 0) 00490 { 00491 print_error_msg_exit("surface", "No input image count"); 00492 } 00493 00494 if ((e = read_imgblock_numbered_f(&img_in, input_fname, num_imgs)) 00495 != NULL) 00496 { 00497 print_error_msg_exit("surface", e->msg); 00498 } 00499 } 00500 00501 00502 /* Down sample the image data if requested. */ 00503 assert(down_sample_imgblock_f(&img_in, img_in, res_img, res_row, res_col) 00504 == NULL); 00505 num_imgs = img_in->num_imgs; 00506 num_rows = img_in->num_rows; 00507 num_cols = img_in->num_cols; 00508 00509 00510 /* Detect surface and sample them if requested. */ 00511 create_matblock_from_imgblock_f(&m, img_in, 1.0f, 1.0f, 1.0f); 00512 if ((e = detect_matblock_surface_points_f(&pts, &num_pts, m, sigma, 00513 grad_img_weight, grad_row_weight, grad_col_weight, 00514 begin_thresh, end_thresh)) != NULL) 00515 { 00516 print_error_msg_exit("surface", e->msg); 00517 return EXIT_FAILURE; 00518 } 00519 00520 fprintf(stdout, "Number of detected surface points: %d\n", num_pts); 00521 sample_surface_points_f(&pts, &num_pts, pts, num_pts, sample_rate); 00522 fprintf(stdout, "Number of sampled surface points: %d\n", num_pts); 00523 00524 /* Write a surface points if requested. */ 00525 if (pts_fname) 00526 { 00527 if ((e = write_surface_points_f(pts, num_pts, pts_fname)) != NULL) 00528 { 00529 print_error_msg("surface", NULL); 00530 print_error_msg(pts_fname, e->msg); 00531 free_error(e); 00532 } 00533 } 00534 00535 /* Write a surface map image block if requested. */ 00536 if (map_fname) 00537 { 00538 create_zero_matblock_f(&m, num_imgs, num_rows, num_cols); 00539 00540 for (p = 0; p < num_pts; p++) 00541 { 00542 pt = &(pts[ p ]); 00543 m->elts[ pt->mat ][ pt->row ][ pt->col ] = 1.0f; 00544 } 00545 00546 create_imgblock_from_matblock_f(&img_out, m); 00547 00548 if ((e = write_imgblock_numbered_f(img_out, map_fname)) != NULL) 00549 { 00550 print_error_msg("surface", e->msg); 00551 free_error(e); 00552 } 00553 } 00554 00555 /* Write the image block with the surface colored on it if requested. */ 00556 if (colored_fname) 00557 { 00558 pxl.r = 1.0f; 00559 pxl.g = 0; 00560 pxl.b = 0; 00561 00562 color_surface_points_f(&img_out, img_in, pts, num_pts, &pxl); 00563 00564 if ((e = write_imgblock_numbered_f(img_out, colored_fname)) != NULL) 00565 { 00566 print_error_msg("surface", e->msg); 00567 free_error(e); 00568 } 00569 } 00570 00571 free(pts); 00572 free_imgblock_f(img_in); 00573 free_imgblock_f(img_out); 00574 free_matblock_f(m); 00575 00576 return EXIT_SUCCESS; 00577 }