Mercurial > hg > minc-tools
changeset 2637:0b9a5b816213
* added mincmorph
* updated README and COPYING
author | Andrew L Janke <a.janke@gmail.com> |
---|---|
date | Wed, 14 Mar 2012 02:57:00 +1000 |
parents | b0e5228b9c74 |
children | 9a50bb928cad |
files | COPYING ChangeLog Makefile.am README progs/mincmorph/ChangeLog progs/mincmorph/README progs/mincmorph/kernel_io.c progs/mincmorph/kernel_io.h progs/mincmorph/kernel_ops.c progs/mincmorph/kernel_ops.h progs/mincmorph/kernels/3x3_4-conn.kern progs/mincmorph/kernels/3x3_8-conn.kern progs/mincmorph/kernels/3x3x3_26-conn.kern progs/mincmorph/kernels/3x3x3_6-conn.kern progs/mincmorph/mincmorph.c |
diffstat | 15 files changed, 2193 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ -Copyright Peter Neelin and David MacDonald, McConnell Brain -Imaging Centre, Montreal Neurological Institute, McGill University. +Copyright the MINC developers, McConnell Brain Imaging Centre, +Montreal Neurological Institute, McGill University. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted,
--- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ + +2012-03-14 Andrew L Janke <a.janke@gmail.com> + * added progs/mincmorph -- Release of minc 2.1.10 --
--- a/Makefile.am +++ b/Makefile.am @@ -85,6 +85,8 @@ progs/minccalc/errx.h \ progs/minccalc/node.h \ progs/minccalc/gram.h \ + progs/mincmorph/kernel_io.h \ + progs/mincmorph/kernel_ops.h \ progs/mincresample/mincresample.h \ progs/mincreshape/mincreshape.h \ libsrc2/minc2_private.h \ @@ -202,6 +204,7 @@ mincmakescalar \ mincmakevector \ mincmath \ + mincmorph \ mincresample \ mincreshape \ mincstats \ @@ -324,6 +327,12 @@ mincmath_SOURCES = progs/mincmath/mincmath.c +mincmorph_CFLAGS = -Iprogs/mincmorph -I$(srcdir)/progs/mincmorph +mincmorph_SOURCES = \ + progs/mincmorph/mincmorph.c \ + progs/mincmorph/kernel_io.c \ + progs/mincmorph/kernel_ops.c + mincresample_SOURCES = \ progs/mincresample/mincresample.c \ progs/mincresample/resample_volumes.c \
--- a/README +++ b/README @@ -39,6 +39,10 @@ MINC requires that NetCDF and HDF5 are installed. +MINC development is performed on github + + http://github.com/BIC-MNI/minc + SUPPORT FOR HDF5 "MINC 2.0" FORMAT FILES ----------------------------------------
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/ChangeLog @@ -0,0 +1,20 @@ +2009-11-10 Andrew L Janke <a.janke@gmail.com> + * Added local correlation option (I[]) + * changed to VIO_ code for clean MINC2 build + +2005-09-23 Bert Vincent <bert@bic.mni.mcgill.ca> + * Fix reading of kernel files + * Force calc_volume_range() to return with a max value strictly + greater than the min. + +2004-10-08 Andrew L. Janke <a.janke@gmail.com> + * Added 4 inbuilt kernels + +2004-05-25 Andrew L. Janke <a.janke@gmail.com> + * Added -median_dilate option for mark + +2004-04-14 Andrew L. Janke <a.janke@gmail.com> + * Fixed bug in get_string_from_string() mallocing + +2003-12-17 Andrew L. Janke <a.janke@gmail.com> + * Added support for grey level dilation and erosion
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/README @@ -0,0 +1,16 @@ +mincmorph - morphological operations 101 + +Comments to: +Andrew Janke - a.janke@gmail.com + +Much of the theory behind what this does can be found at these web sites.. + http://www.mmorph.com/faq.htm + http://www.dai.ed.ac.uk/HIPR2/skeleton.htm + + SNF - http://www.umiacs.umd.edu/research/EXPAR/papers/3449/node7.html#SECTION00041000000000000000 + + CC8 - http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/OWENS/LECT3/node2.html + - http://www.dai.ed.ac.uk/HIPR2/label.htm + +HOW TO USE IT: + mincmorph -help for more information
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernel_io.c @@ -0,0 +1,409 @@ +/* kernel_io.c - reads kernel files */ + +#include <volume_io.h> +#include "kernel_io.h" +#define MAX_KERNEL_ELEMS 1000 + +extern int verbose; + +static const STRING KERNEL_FILE_HEADER = "MNI Morphology Kernel File"; +static const STRING KERNEL_TYPE = "Kernel_Type"; +static const STRING NORMAL_KERNEL = "Normal_Kernel"; +static const STRING KERNEL = "Kernel"; + +/* inbuilt kernels */ +int n_inbuilt_kern = 4; + +/* returns a new Kernel struct (pointer) */ +Kernel *new_kernel(int nelems) +{ + int i, j; + Kernel *tmp; + + ALLOC_VAR_SIZED_STRUCT(tmp, Real, 10); + + /* allocate and initialise the Kernel Array */ + SET_ARRAY_SIZE(tmp->K, 0, nelems, 10); + for(i = 0; i < nelems; i++){ + ALLOC(tmp->K[i], KERNEL_DIMS + 1); + + for(j = 0; j < KERNEL_DIMS; j++){ + tmp->K[i][j] = 0.0; + } + tmp->K[i][KERNEL_DIMS] = 1.0; + + } + tmp->nelems = nelems; + + return tmp; + } + +/* reads in a Kernel from a file */ +Status input_kernel(const char *kernel_file, Kernel * kernel) +{ + int i, j; + + STRING line; + STRING type_name; + STRING str; + Real tmp_real; + FILE *file; + + /* parameter checking */ + if(kernel_file == NULL){ + print_error("input_kernel(): passed NULL FILE.\n"); + return (ERROR); + } + + file = fopen(kernel_file, "r"); + if(file == NULL){ + print_error("input_kernel(): error opening Kernel file.\n"); + return (ERROR); + } + + /* okay read the header */ + if(mni_input_string(file, &line, (char)0, (char)0) != OK){ + delete_string(line); + print_error("input_kernel(): could not read header in file.\n"); + return (ERROR); + } + + if(!equal_strings(line, KERNEL_FILE_HEADER)){ + delete_string(line); + print_error("input_kernel(): invalid header in file.\n"); + return (ERROR); + } + + /* --- read the type of Kernel */ + if(mni_input_keyword_and_equal_sign(file, KERNEL_TYPE, FALSE) != OK){ + return (ERROR); + } + + if(mni_input_string(file, &type_name, (char)';', (char)0) != OK){ + print_error("input_kernel(): missing kernel type.\n"); + return (ERROR); + } + + if(mni_skip_expected_character(file, (char)';') != OK){ + return (ERROR); + } + + if(!equal_strings(type_name, NORMAL_KERNEL)){ + print_error("input_kernel(): invalid kernel type.\n"); + delete_string(type_name); + return (ERROR); + } + delete_string(type_name); + + /* --- read the next string */ + if(mni_input_string(file, &str, (char)'=', (char)0) != OK) + return (ERROR); + + if(!equal_strings(str, KERNEL)){ + print_error("Expected %s =\n", KERNEL); + delete_string(str); + return (ERROR); + } + delete_string(str); + + if(mni_skip_expected_character(file, (char)'=') != OK){ + return (ERROR); + } + + /* now read the elements (lines) of the kernel */ + if(verbose){ + fprintf(stderr, "Reading [%s]", kernel_file); + } + for(i = 0; i < MAX_KERNEL_ELEMS; i++){ + + /* allocate a bit of memory */ + SET_ARRAY_SIZE(kernel->K, kernel->nelems, kernel->nelems + 1, 10); + ALLOC(kernel->K[i], KERNEL_DIMS + 1); + + /* get the 5 dimension vectors and the coefficient */ + for(j = 0; j < 6; j++){ + if(mni_input_real(file, &tmp_real) == OK){ + kernel->K[i][j] = tmp_real; + } + else { + /* check for end */ + if(mni_skip_expected_character(file, (char)';') == OK){ + kernel->nelems = i; + if(verbose){ + fprintf(stderr, " %dx%d Kernel elements read\n", i, kernel->nelems); + } + return (OK); + } + else { + print_error("input_kernel(): error reading kernel [%d,%d]\n", i + 1, + j + 1); + return (ERROR); + } + } + } + kernel->nelems++; + + if(verbose){ + fprintf(stderr, "."); + fflush(stderr); + } + } + + /* SHOLDN'T BE REACHED */ + print_error("input_kernel(): Glark! Something is amiss in the State of Kansas\n"); + return (ERROR); + } + +/* pretty print a kernel */ +int print_kernel(Kernel * kernel) +{ + int i, j; + + fprintf(stderr, " x y z t v coeff\n"); + fprintf(stderr, " -----------------------------------------------\n"); + for(i = 0; i < kernel->nelems; i++){ + fprintf(stderr, "[%02d]", i); + for(j = 0; j < KERNEL_DIMS + 1; j++){ + fprintf(stderr, "%8.02f", kernel->K[i][j]); + } + fprintf(stderr, "\n"); + } + + return (TRUE); + } + +int setup_pad_values(Kernel * kernel) +{ + int c, n; + + /* init padding values */ + for(n = 0; n < KERNEL_DIMS; n++){ + kernel->pre_pad[n] = 0; + kernel->post_pad[n] = 0; + } + + /* find the padding sizes */ + for(c = 0; c < kernel->nelems; c++){ + for(n = 0; n < KERNEL_DIMS; n++){ + if(kernel->K[c][n] < kernel->pre_pad[n]){ + kernel->pre_pad[n] = kernel->K[c][n]; + } + + if(kernel->K[c][n] > kernel->post_pad[n]){ + kernel->post_pad[n] = kernel->K[c][n]; + } + } + } + + return (TRUE); + } + +/* 2D 4 connectivity kernel */ +/* x y z t v coeff */ +/* ----------------------------------------------- */ +/* [00] 1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [01] -1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [02] 0.00 1.00 0.00 0.00 0.00 1.00 */ +/* [03] 0.00 -1.00 0.00 0.00 0.00 1.00 */ +Kernel *get_2D04_kernel(void) +{ + Kernel *K = new_kernel(4); + + K->K[0][0] = 1.0; + K->K[1][0] = -1.0; + + K->K[2][1] = 1.0; + K->K[3][1] = -1.0; + + return K; + } + +/* 2D 8 connectivity kernel */ +/* x y z t v coeff */ +/* ----------------------------------------------- */ +/* [00] 1.00 1.00 0.00 0.00 0.00 1.00 */ +/* [01] 1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [02] 1.00 -1.00 0.00 0.00 0.00 1.00 */ +/* */ +/* [03] 0.00 1.00 0.00 0.00 0.00 1.00 */ +/* [04] 0.00 -1.00 0.00 0.00 0.00 1.00 */ +/* */ +/* [05] -1.00 1.00 0.00 0.00 0.00 1.00 */ +/* [06] -1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [07] -1.00 -1.00 0.00 0.00 0.00 1.00 */ +Kernel *get_2D08_kernel(void) +{ + Kernel *K = new_kernel(8); + + K->K[0][0] = 1.0; + K->K[0][1] = 1.0; + + K->K[1][0] = 1.0; + + K->K[2][0] = 1.0; + K->K[2][1] = -1.0; + + K->K[3][1] = 1.0; + + K->K[4][1] = -1.0; + + K->K[5][0] = -1.0; + K->K[5][1] = 1.0; + + K->K[6][0] = -1.0; + + K->K[7][0] = -1.0; + K->K[7][1] = -1.0; + + return K; + } + +/* 3D 6 connectivity kernel */ +/* x y z t v coeff */ +/* ----------------------------------------------- */ +/* [00] 1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [01] -1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [02] 0.00 1.00 0.00 0.00 0.00 1.00 */ +/* [03] 0.00 -1.00 0.00 0.00 0.00 1.00 */ +/* [04] 0.00 0.00 1.00 0.00 0.00 1.00 */ +/* [05] 0.00 0.00 -1.00 0.00 0.00 1.00 */ +Kernel *get_3D06_kernel(void) +{ + Kernel *K = new_kernel(6); + + K->K[0][0] = 1.0; + K->K[1][0] = -1.0; + + K->K[2][1] = 1.0; + K->K[3][1] = -1.0; + + K->K[4][2] = 1.0; + K->K[5][2] = -1.0; + + return K; + } + +/* 3D 26 connectivity kernel */ +/* x y z t v coeff */ +/* ----------------------------------------------- */ +/* [00] 1.00 1.00 1.00 0.00 0.00 1.00 */ +/* [01] 1.00 1.00 0.00 0.00 0.00 1.00 */ +/* [02] 1.00 1.00 -1.00 0.00 0.00 1.00 */ + +/* [03] 1.00 0.00 1.00 0.00 0.00 1.00 */ +/* [04] 1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [05] 1.00 0.00 -1.00 0.00 0.00 1.00 */ + +/* [06] 1.00 -1.00 1.00 0.00 0.00 1.00 */ +/* [07] 1.00 -1.00 0.00 0.00 0.00 1.00 */ +/* [08] 1.00 -1.00 -1.00 0.00 0.00 1.00 */ + +/* [09] 0.00 1.00 1.00 0.00 0.00 1.00 */ +/* [10] 0.00 1.00 0.00 0.00 0.00 1.00 */ +/* [11] 0.00 1.00 -1.00 0.00 0.00 1.00 */ + +/* [12] 0.00 0.00 1.00 0.00 0.00 1.00 */ +/* ---- 0.00 0.00 0.00 0.00 0.00 ---- */ +/* [13] 0.00 0.00 -1.00 0.00 0.00 1.00 */ + +/* [14] 0.00 -1.00 1.00 0.00 0.00 1.00 */ +/* [15] 0.00 -1.00 0.00 0.00 0.00 1.00 */ +/* [16] 0.00 -1.00 -1.00 0.00 0.00 1.00 */ + +/* [17] -1.00 1.00 1.00 0.00 0.00 1.00 */ +/* [18] -1.00 1.00 0.00 0.00 0.00 1.00 */ +/* [19] -1.00 1.00 -1.00 0.00 0.00 1.00 */ + +/* [20] -1.00 0.00 1.00 0.00 0.00 1.00 */ +/* [21] -1.00 0.00 0.00 0.00 0.00 1.00 */ +/* [22] -1.00 0.00 -1.00 0.00 0.00 1.00 */ + +/* [23] -1.00 -1.00 1.00 0.00 0.00 1.00 */ +/* [24] -1.00 -1.00 0.00 0.00 0.00 1.00 */ +/* [25] -1.00 -1.00 -1.00 0.00 0.00 1.00 */ +Kernel *get_3D26_kernel(void) +{ + Kernel *K = new_kernel(26); + + K->K[0][0] = 1.0; + K->K[0][1] = 1.0; + K->K[0][2] = 1.0; + + K->K[1][0] = 1.0; + K->K[1][1] = 1.0; + + K->K[2][0] = 1.0; + K->K[2][1] = 1.0; + K->K[2][2] = -1.0; + + K->K[3][0] = 1.0; + K->K[3][2] = 1.0; + + K->K[4][0] = 1.0; + + K->K[5][0] = 1.0; + K->K[5][2] = -1.0; + + K->K[6][0] = 1.0; + K->K[6][1] = -1.0; + K->K[6][2] = 1.0; + + K->K[7][0] = 1.0; + K->K[7][1] = -1.0; + + K->K[8][0] = 1.0; + K->K[8][1] = -1.0; + K->K[8][2] = -1.0; + + K->K[9][1] = 1.0; + K->K[9][2] = 1.0; + + K->K[10][1] = 1.0; + + K->K[11][1] = 1.0; + K->K[11][2] = -1.0; + + K->K[12][2] = 1.0; + + K->K[13][2] = -1.0; + + K->K[14][1] = -1.0; + K->K[14][2] = 1.0; + + K->K[15][1] = -1.0; + + K->K[16][1] = -1.0; + K->K[16][2] = -1.0; + + K->K[17][0] = -1.0; + K->K[17][1] = 1.0; + K->K[17][2] = 1.0; + + K->K[18][0] = -1.0; + K->K[18][1] = 1.0; + + K->K[19][0] = -1.0; + K->K[19][1] = 1.0; + K->K[19][2] = -1.0; + + K->K[20][0] = -1.0; + K->K[20][2] = 1.0; + + K->K[21][0] = -1.0; + + K->K[22][0] = -1.0; + K->K[22][2] = -1.0; + + K->K[23][0] = -1.0; + K->K[23][1] = -1.0; + K->K[23][2] = 1.0; + + K->K[24][0] = -1.0; + K->K[24][1] = -1.0; + + K->K[25][0] = -1.0; + K->K[25][1] = -1.0; + K->K[25][2] = -1.0; + + return K; + }
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernel_io.h @@ -0,0 +1,42 @@ +/* kernel_io.h */ + +#ifndef KERNEL_IO +#define KERNEL_IO + +#define KERNEL_DIMS 5 + +/* inbuilt kernels */ +int n_inbuilt_kern; + +typedef enum { + K_NULL = 0, + K_2D04, K_2D08, K_3D06, K_3D26 + } kern_types; + +/* Structure for Kernel information */ +typedef struct { + int nelems; + int pre_pad[KERNEL_DIMS]; + int post_pad[KERNEL_DIMS]; + VIO_Real **K; + } Kernel; + +/* returns a new B_Matrix struct (pointer) */ +Kernel *new_kernel(int nelems); + +/* reads in a B_Matrix from a file (pointer) */ +VIO_Status input_kernel(const char *kernel_file, Kernel * kernel); + +/* pretty print a kernel */ +int print_kernel(Kernel * kernel); + +/* calculate start and step offsets for this kernel */ +int setup_pad_values(Kernel * kernel); + +/* return the default kernel(s) */ +Kernel *get_2D04_kernel(void); +Kernel *get_2D08_kernel(void); +Kernel *get_3D06_kernel(void); +Kernel *get_3D26_kernel(void); + +#endif
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernel_ops.c @@ -0,0 +1,797 @@ +/* kernel_ops.c */ + +#include <float.h> +#include <limits.h> +#include "kernel_ops.h" + +extern int verbose; + +/* function prototypes */ +void split_kernel(Kernel * K, Kernel * k1, Kernel * k2); +int compare_ints(const void *a, const void *b); +int compare_groups(const void *a, const void *b); + +/* structure for group information */ +typedef struct { + unsigned int orig_label; + unsigned int count; + } group_info_struct; + +typedef group_info_struct *Group_info; + +int compare_ints(const void *a, const void *b) +{ + return (*(int *)a - *(int *)b); + } + +int compare_groups(const void *a, const void *b) +{ + return (*(Group_info *) b)->count - (*(Group_info *) a)->count; + } + +void split_kernel(Kernel * K, Kernel * k1, Kernel * k2) +{ + int c, k1c, k2c; + + /* fill the two sub kernels */ + k1c = k2c = 0; + for(c = 0; c < K->nelems; c++){ + if((K->K[c][2] < 0) || + (K->K[c][1] < 0 && K->K[c][2] <= 0) || + (K->K[c][0] < 0 && K->K[c][1] <= 0 && K->K[c][2] <= 0)){ + + k1->K[k1c] = K->K[c]; + k1c++; + } + else { + k2->K[k2c] = K->K[c]; + k2c++; + } + } + k1->nelems = k1c; + k2->nelems = k2c; + } + +/* binarise a volume between a range */ +Volume *binarise(Volume * vol, double floor, double ceil, double fg, double bg) +{ + int x, y, z; + int sizes[MAX_VAR_DIMS]; + double value; + progress_struct progress; + + if(verbose){ + fprintf(stdout, "Binarising, range: [%g:%g] fg/bg: [%g:%g]\n", floor, ceil, fg, bg); + } + get_volume_sizes(*vol, sizes); + + initialize_progress_report(&progress, FALSE, sizes[2], "Binarise"); + for(z = sizes[0]; z--;){ + for(y = sizes[1]; y--;){ + for(x = sizes[2]; x--;){ + + value = get_volume_voxel_value(*vol, z, y, x, 0, 0); + if((value >= floor) && (value <= ceil)){ + set_volume_voxel_value(*vol, z, y, x, 0, 0, fg); + } + else { + set_volume_voxel_value(*vol, z, y, x, 0, 0, bg); + } + + } + } + update_progress_report(&progress, z + 1); + } + terminate_progress_report(&progress); + + return (vol); + } + +/* clamp a volume between a range */ +Volume *clamp(Volume * vol, double floor, double ceil, double bg) +{ + int x, y, z; + int sizes[MAX_VAR_DIMS]; + double value; + progress_struct progress; + + if(verbose){ + fprintf(stdout, "Clamping, range: [%g:%g] bg: %g\n", floor, ceil, bg); + } + + get_volume_sizes(*vol, sizes); + + initialize_progress_report(&progress, FALSE, sizes[2], "Clamping"); + for(z = sizes[0]; z--;){ + for(y = sizes[1]; y--;){ + for(x = sizes[2]; x--;){ + value = get_volume_voxel_value(*vol, z, y, x, 0, 0); + if((value < floor) || (value > ceil)){ + set_volume_voxel_value(*vol, z, y, x, 0, 0, bg); + } + } + } + update_progress_report(&progress, z + 1); + } + + terminate_progress_report(&progress); + return (vol); + } + +/* pad a volume using the background value */ +Volume *pad(Kernel * K, Volume * vol, double bg) +{ + int x, y, z; + int sizes[MAX_VAR_DIMS]; + + get_volume_sizes(*vol, sizes); + + /* z */ + for(y = 0; y < sizes[1]; y++){ + for(x = 0; x < sizes[2]; x++){ + for(z = 0; z < -K->pre_pad[2]; z++){ + set_volume_real_value(*vol, z, y, x, 0, 0, bg); + } + for(z = sizes[0] - K->post_pad[2]; z < sizes[0]; z++){ + set_volume_real_value(*vol, z, y, x, 0, 0, bg); + } + } + } + + /* y */ + for(z = 0; z < sizes[0]; z++){ + for(x = 0; x < sizes[2]; x++){ + for(y = 0; y < -K->pre_pad[1]; y++){ + set_volume_real_value(*vol, z, y, x, 0, 0, bg); + } + for(y = sizes[1] - K->post_pad[1]; y < sizes[1]; y++){ + set_volume_real_value(*vol, z, y, x, 0, 0, bg); + } + } + } + + /* x */ + for(z = 0; z < sizes[0]; z++){ + for(y = 0; y < sizes[1]; y++){ + for(x = 0; x < -K->pre_pad[0]; x++){ + set_volume_real_value(*vol, z, y, x, 0, 0, bg); + } + for(x = sizes[2] - K->post_pad[0]; x < sizes[2]; x++){ + set_volume_real_value(*vol, z, y, x, 0, 0, bg); + } + } + } + + return (vol); + } + +/* perform a dilation on a volume */ +Volume *dilation_kernel(Kernel * K, Volume * vol) +{ + int x, y, z, c; + double value; + int sizes[MAX_VAR_DIMS]; + progress_struct progress; + Volume tmp_vol; + + if(verbose){ + fprintf(stdout, "Dilation kernel\n"); + } + get_volume_sizes(*vol, sizes); + initialize_progress_report(&progress, FALSE, sizes[2], "Dilation"); + + /* copy the volume */ + tmp_vol = copy_volume(*vol); + + for(z = -K->pre_pad[2]; z < sizes[0] - K->post_pad[2]; z++){ + for(y = -K->pre_pad[1]; y < sizes[1] - K->post_pad[1]; y++){ + for(x = -K->pre_pad[0]; x < sizes[2] - K->post_pad[0]; x++){ + + value = get_volume_real_value(tmp_vol, z, y, x, 0, 0); + for(c = 0; c < K->nelems; c++){ + if(get_volume_real_value(*vol, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], + 0 + K->K[c][3], 0 + K->K[c][4]) < value){ + set_volume_real_value(*vol, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], + 0 + K->K[c][3], + 0 + K->K[c][4], value * K->K[c][5]); + } + } + } + } + + update_progress_report(&progress, z + 1); + } + + delete_volume(tmp_vol); + terminate_progress_report(&progress); + return (vol); + } + +/* perform a median kernel operation on a volume */ +Volume *median_dilation_kernel(Kernel * K, Volume * vol) +{ + int x, y, z, c, i; + int sizes[MAX_VAR_DIMS]; + progress_struct progress; + Volume tmp_vol; + double value; + + unsigned int kvalue; + unsigned int neighbours[K->nelems]; + + if(verbose){ + fprintf(stdout, "Median Dilation kernel\n"); + } + get_volume_sizes(*vol, sizes); + initialize_progress_report(&progress, FALSE, sizes[2], "Median Dilation"); + + /* copy the volume */ + tmp_vol = copy_volume(*vol); + + for(z = -K->pre_pad[2]; z < sizes[0] - K->post_pad[2]; z++){ + for(y = -K->pre_pad[1]; y < sizes[1] - K->post_pad[1]; y++){ + for(x = -K->pre_pad[0]; x < sizes[2] - K->post_pad[0]; x++){ + + /* only modify background voxels */ + value = get_volume_voxel_value(tmp_vol, z, y, x, 0, 0); + if(value == 0.0){ + + i = 0; + for(c = 0; c < K->nelems; c++){ + + kvalue = (unsigned int)get_volume_voxel_value(tmp_vol, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], + 0 + K->K[c][3], + 0 + K->K[c][4]); + if(kvalue != 0){ + neighbours[i] = kvalue; + i++; + } + } + + /* only run this for adjacent voxels */ + if(i > 0){ + + /* find the median of our little array */ + qsort(&neighbours[0], (size_t) i, sizeof(unsigned int), &compare_ints); + + /* store the median value */ + set_volume_voxel_value(*vol, z, y, x, 0, 0, + (double)neighbours[(int)floor((i - 1) / 2)]); + } + } + + /* else just copy the original value over */ + else { + set_volume_voxel_value(*vol, z, y, x, 0, 0, value); + } + } + } + + update_progress_report(&progress, z + 1); + } + + delete_volume(tmp_vol); + terminate_progress_report(&progress); + return (vol); + } + +/* perform an erosion on a volume */ +Volume *erosion_kernel(Kernel * K, Volume * vol) +{ + int x, y, z, c; + double value; + int sizes[MAX_VAR_DIMS]; + progress_struct progress; + Volume tmp_vol; + + if(verbose){ + fprintf(stdout, "Erosion kernel\n"); + } + get_volume_sizes(*vol, sizes); + initialize_progress_report(&progress, FALSE, sizes[2], "Erosion"); + + /* copy the volume */ + tmp_vol = copy_volume(*vol); + + for(z = -K->pre_pad[2]; z < sizes[0] - K->post_pad[2]; z++){ + for(y = -K->pre_pad[1]; y < sizes[1] - K->post_pad[1]; y++){ + for(x = -K->pre_pad[0]; x < sizes[2] - K->post_pad[0]; x++){ + + value = get_volume_real_value(tmp_vol, z, y, x, 0, 0); + for(c = 0; c < K->nelems; c++){ + if(get_volume_real_value(*vol, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], + 0 + K->K[c][3], 0 + K->K[c][4]) > value){ + set_volume_real_value(*vol, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], + 0 + K->K[c][3], + 0 + K->K[c][4], value * K->K[c][5]); + } + } + + value = get_volume_real_value(tmp_vol, z, y, x, 0, 0); + } + } + update_progress_report(&progress, z + 1); + } + + delete_volume(tmp_vol); + terminate_progress_report(&progress); + return (vol); + } + +/* convolve a volume with a input kernel */ +Volume *convolve_kernel(Kernel * K, Volume * vol) +{ + int x, y, z, c; + double value; + int sizes[MAX_VAR_DIMS]; + progress_struct progress; + Volume tmp_vol; + + if(verbose){ + fprintf(stdout, "Convolve kernel\n"); + } + get_volume_sizes(*vol, sizes); + initialize_progress_report(&progress, FALSE, sizes[2], "Convolve"); + + /* copy the volume */ + tmp_vol = copy_volume(*vol); + + for(z = -K->pre_pad[2]; z < sizes[0] - K->post_pad[2]; z++){ + for(y = -K->pre_pad[1]; y < sizes[1] - K->post_pad[1]; y++){ + for(x = -K->pre_pad[0]; x < sizes[2] - K->post_pad[0]; x++){ + + value = 0; + for(c = 0; c < K->nelems; c++){ + value += get_volume_real_value(tmp_vol, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], 0 + K->K[c][3], + 0 + K->K[c][4]) * K->K[c][5]; + } + set_volume_real_value(*vol, z, y, x, 0, 0, value); + } + } + + update_progress_report(&progress, z + 1); + } + + delete_volume(tmp_vol); + terminate_progress_report(&progress); + return (vol); + } + +/* should really only work on binary images */ +/* from the original 2 pass Borgefors alg */ +Volume *distance_kernel(Kernel * K, Volume * vol, double bg) +{ + int x, y, z, c; + double value, min; + int sizes[MAX_VAR_DIMS]; + progress_struct progress; + Kernel *k1, *k2; + + /* split the Kernel */ + k1 = new_kernel(K->nelems); + k2 = new_kernel(K->nelems); + split_kernel(K, k1, k2); + + setup_pad_values(k1); + setup_pad_values(k2); + + if(verbose){ + fprintf(stdout, "Distance kernel - background %g\n", bg); + fprintf(stdout, "forward direction kernel:\n"); + print_kernel(k1); + fprintf(stdout, "\nreverse direction kernel:\n"); + print_kernel(k2); + } + + get_volume_sizes(*vol, sizes); + initialize_progress_report(&progress, FALSE, sizes[2] * 2, "Distance"); + + /* forward raster direction */ + for(z = -K->pre_pad[2]; z < sizes[0] - K->post_pad[2]; z++){ + for(y = -K->pre_pad[1]; y < sizes[1] - K->post_pad[1]; y++){ + for(x = -K->pre_pad[0]; x < sizes[2] - K->post_pad[0]; x++){ + + if(get_volume_real_value(*vol, z, y, x, 0, 0) != bg){ + + /* find the minimum */ + min = DBL_MAX; + for(c = 0; c < k1->nelems; c++){ + value = get_volume_real_value(*vol, + z + k1->K[c][2], + y + k1->K[c][1], + x + k1->K[c][0], + 0 + k1->K[c][3], 0 + k1->K[c][4]) + 1; + if(value < min){ + min = value; + } + } + + set_volume_real_value(*vol, z, y, x, 0, 0, min); + } + } + } + update_progress_report(&progress, z + 1); + } + + /* reverse raster direction */ + for(z = sizes[0] - k2->post_pad[2] - 1; z >= -k2->pre_pad[2]; z--){ + for(y = sizes[1] - k2->post_pad[1] - 1; y >= -k2->pre_pad[1]; y--){ + for(x = sizes[2] - k2->post_pad[0] - 1; x >= -k2->pre_pad[0]; x--){ + + min = get_volume_real_value(*vol, z, y, x, 0, 0); + if(min != bg){ + + /* find the minimum distance to bg in the neighbouring vectors */ + for(c = 0; c < k2->nelems; c++){ + value = get_volume_real_value(*vol, + z + k2->K[c][2], + y + k2->K[c][1], + x + k2->K[c][0], + 0 + k2->K[c][3], 0 + k2->K[c][4]) + 1; + if(value < min){ + min = value; + } + } + + set_volume_real_value(*vol, z, y, x, 0, 0, min); + } + } + } + update_progress_report(&progress, sizes[2] + z + 1); + } + + free(k1); + free(k2); + terminate_progress_report(&progress); + return (vol); + } + +/* do connected components labelling on a volume */ +/* resulting groups are sorted WRT size */ +Volume *group_kernel(Kernel * K, Volume * vol, double bg) +{ + int x, y, z; + int sizes[MAX_VAR_DIMS]; + progress_struct progress; + Volume tmp_vol; + Kernel *k1, *k2; + + unsigned int *equiv; + unsigned int *counts; + unsigned int *trans; + unsigned int neighbours[K->nelems]; + + /* counters */ + unsigned int c; + unsigned int value; + unsigned int group_idx; /* label for the next group */ + unsigned int num_groups; + + unsigned int min_label; + unsigned int curr_label; + unsigned int prev_label; + unsigned int num_matches; + + /* structure for group data */ + Group_info *group_data; + + /* split the Kernel into forward and backwards kernels */ + k1 = new_kernel(K->nelems); + k2 = new_kernel(K->nelems); + split_kernel(K, k1, k2); + + setup_pad_values(k1); + setup_pad_values(k2); + + if(verbose){ + fprintf(stdout, "Group kernel - background %g\n", bg); + fprintf(stdout, "forward direction kernel:\n"); + print_kernel(k1); + fprintf(stdout, "\nreverse direction kernel:\n"); + print_kernel(k2); + } + + get_volume_sizes(*vol, sizes); + initialize_progress_report(&progress, FALSE, sizes[2], "Groups"); + + /* copy and then zero out the original volume */ + tmp_vol = copy_volume(*vol); + for(z = sizes[0]; z--;){ + for(y = sizes[1]; y--;){ + for(x = sizes[2]; x--;){ + set_volume_voxel_value(*vol, z, y, x, 0, 0, 0); + } + } + } + + /* pass 1 - forward direction (we assume a symmetric kernel) */ + + /* our first group is given the label 1 */ + group_idx = 1; + + /* initialise the equiv and counts arrays */ + SET_ARRAY_SIZE(equiv, 0, group_idx, 500); + equiv[0] = 0; + + SET_ARRAY_SIZE(counts, 0, group_idx, 500); + counts[0] = 0; + + for(z = -k1->pre_pad[2]; z < sizes[0] - k1->post_pad[2]; z++){ + for(y = -k1->pre_pad[1]; y < sizes[1] - k1->post_pad[1]; y++){ + for(x = -k1->pre_pad[0]; x < sizes[2] - k1->post_pad[0]; x++){ + + if(get_volume_voxel_value(tmp_vol, z, y, x, 0, 0) != bg){ + + /* search this voxels neighbours */ + num_matches = 0; + min_label = INT_MAX; + + for(c = 0; c < k1->nelems; c++){ + value = (unsigned int)get_volume_voxel_value(*vol, + z + k1->K[c][2], + y + k1->K[c][1], + x + k1->K[c][0], + 0 + k1->K[c][3], + 0 + k1->K[c][4]); + if(value != 0){ + if(value < min_label){ + min_label = value; + } + neighbours[num_matches] = value; + num_matches++; + } + } + + switch (num_matches){ + case 0: + /* no neighbours, make a new label and increment */ + set_volume_voxel_value(*vol, z, y, x, 0, 0, (Real) group_idx); + + SET_ARRAY_SIZE(equiv, group_idx, group_idx + 1, 500); + equiv[group_idx] = group_idx; + + SET_ARRAY_SIZE(counts, group_idx, group_idx + 1, 500); + counts[group_idx] = 1; + + group_idx++; + break; + + case 1: + /* only one neighbour, no equivalences needed */ + set_volume_voxel_value(*vol, z, y, x, 0, 0, (Real) min_label); + counts[min_label]++; + break; + + default: + /* more than one neighbour */ + + /* first sort the neighbours array */ + qsort(&neighbours[0], (size_t) num_matches, sizeof(unsigned int), + &compare_ints); + + /* find the minimum possible label for this voxel, */ + /* this is done by descending through each neighbours */ + /* equivalences until an equivalence equal to itself */ + /* is found */ + prev_label = -1; + for(c = 0; c < num_matches; c++){ + curr_label = neighbours[c]; + + /* recurse this label if we haven't yet */ + if(curr_label != prev_label){ + while(equiv[curr_label] != equiv[equiv[curr_label]]){ + curr_label = equiv[curr_label]; + } + + /* check against the current minimum value */ + if(equiv[curr_label] < min_label){ + min_label = equiv[curr_label]; + } + } + + prev_label = neighbours[c]; + } + + /* repeat, setting equivalences to the min_label */ + prev_label = -1; + for(c = 0; c < num_matches; c++){ + curr_label = neighbours[c]; + + if(curr_label != prev_label){ + while(equiv[curr_label] != equiv[equiv[curr_label]]){ + curr_label = equiv[curr_label]; + + equiv[curr_label] = min_label; + } + + /* set the label itself */ + if(equiv[neighbours[c]] != min_label){ + equiv[neighbours[c]] = min_label; + } + } + + prev_label = neighbours[c]; + } + + /* finally set the voxel in question to the minimum value */ + set_volume_voxel_value(*vol, z, y, x, 0, 0, (Real) min_label); + counts[min_label]++; + break; + } /* end case */ + + } + } + } + update_progress_report(&progress, z + 1); + } + terminate_progress_report(&progress); + + /* reduce the equiv and counts array */ + num_groups = 0; + for(c = 0; c < group_idx; c++){ + + /* if this equivalence is not resolved yet */ + if(c != equiv[c]){ + + /* find the min label value */ + min_label = equiv[c]; + while(min_label != equiv[min_label]){ + min_label = equiv[min_label]; + } + + /* update the label and its counters */ + equiv[c] = min_label; + counts[min_label] += counts[c]; + counts[c] = 0; + } + else { + num_groups++; + } + } + + /* Allocate space for the array of groups */ + group_data = (Group_info *) malloc(num_groups * sizeof(Group_info)); + + num_groups = 0; + for(c = 0; c < group_idx; c++){ + if(counts[c] > 0){ + /* allocate space for this element */ + group_data[num_groups] = malloc(sizeof(group_info_struct)); + + group_data[num_groups]->orig_label = equiv[c]; + group_data[num_groups]->count = counts[c]; + num_groups++; + } + } + + /* sort the groups by the count size */ + if(verbose){ + fprintf(stdout, "Found %d unique groups from %d, sorting...\n", num_groups, + group_idx); + } + qsort(group_data, num_groups, sizeof(Group_info), &compare_groups); + + /* set up the transpose array */ + trans = (unsigned int *)malloc(sizeof(unsigned int) * group_idx); + for(c = 0; c < num_groups; c++){ + trans[group_data[c]->orig_label] = c + 1; /* +1 to bump past 0 */ + } + + /* pass 2 - resolve equivalences in the output data */ + if(verbose){ + fprintf(stdout, "Resolving equivalences...\n"); + } + for(z = sizes[0]; z--;){ + for(y = sizes[1]; y--;){ + for(x = sizes[2]; x--;){ + value = (unsigned int)get_volume_voxel_value(*vol, z, y, x, 0, 0); + if(value != 0){ + value = trans[equiv[value]]; + set_volume_voxel_value(*vol, z, y, x, 0, 0, (Real) value); + } + } + } + } + + /* tidy up */ + delete_volume(tmp_vol); + for(c = 0; c < num_groups; c++){ + free(group_data[c]); + } + free(group_data); + free(trans); + free(k1); + free(k2); + + return (vol); + } + +/* do local correlation to another volume */ +/* xcorr = sum((a*b)^2) / (sqrt(sum(a^2)) * sqrt(sum(b^2)) */ +VIO_Volume *lcorr_kernel(Kernel * K, VIO_Volume * vol, VIO_Volume *cmp) +{ + int x, y, z, c; + double value, v1, v2; + double ssum_v1, ssum_v2, sum_prd, denom; + int sizes[MAX_VAR_DIMS]; + progress_struct progress; + Volume tmp_vol; + + if(verbose){ + fprintf(stdout, "Local Correlation kernel\n"); + } + get_volume_sizes(*vol, sizes); + initialize_progress_report(&progress, FALSE, sizes[2], "Local Correlation"); + + /* copy the volume */ + tmp_vol = copy_volume(*vol); + + /* zero the output volume */ + for(z = sizes[0]; z--;){ + for(y = sizes[1]; y--;){ + for(x = sizes[2]; x--;){ + set_volume_voxel_value(*vol, z, y, x, 0, 0, 0); + } + } + } + + /* set output range */ + set_volume_real_range(*vol, 0.0, 1.0); + + for(z = -K->pre_pad[2]; z < sizes[0] - K->post_pad[2]; z++){ + for(y = -K->pre_pad[1]; y < sizes[1] - K->post_pad[1]; y++){ + for(x = -K->pre_pad[0]; x < sizes[2] - K->post_pad[0]; x++){ + + /* init counters */ + ssum_v1 = ssum_v2 = sum_prd = 0; + for(c = 0; c < K->nelems; c++){ + v1 = get_volume_real_value(tmp_vol, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], 0 + K->K[c][3], + 0 + K->K[c][4]) * K->K[c][5]; + v2 = get_volume_real_value(*cmp, + z + K->K[c][2], + y + K->K[c][1], + x + K->K[c][0], 0 + K->K[c][3], + 0 + K->K[c][4]) * K->K[c][5]; + + /* increment counters */ + ssum_v1 += v1*v1; + ssum_v2 += v2*v2; + sum_prd += v1*v2; + } + + denom = sqrt(ssum_v1 * ssum_v2); + value = (denom == 0.0) ? 0.0 : sum_prd / denom; + + set_volume_real_value(*vol, z, y, x, 0, 0, value); + } + } + update_progress_report(&progress, z + 1); + } + terminate_progress_report(&progress); + + /* tidy up */ + delete_volume(tmp_vol); + + return (vol); + }
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernel_ops.h @@ -0,0 +1,21 @@ +/* kernel_ops.h */ + +#ifndef KERNEL_OPS +#define KERNEL_OPS + +#include <volume_io.h> +#include "kernel_io.h" + +/* kernel functions */ +VIO_Volume *binarise(VIO_Volume * vol, double floor, double ceil, double fg, double bg); +VIO_Volume *clamp(VIO_Volume * vol, double floor, double ceil, double bg); +VIO_Volume *pad(Kernel * K, VIO_Volume * vol, double bg); +VIO_Volume *erosion_kernel(Kernel * K, VIO_Volume * vol); +VIO_Volume *dilation_kernel(Kernel * K, VIO_Volume * vol); +VIO_Volume *median_dilation_kernel(Kernel * K, VIO_Volume * vol); +VIO_Volume *convolve_kernel(Kernel * K, VIO_Volume * vol); +VIO_Volume *distance_kernel(Kernel * K, VIO_Volume * vol, double bg); +VIO_Volume *group_kernel(Kernel * K, VIO_Volume * vol, double bg); +VIO_Volume *lcorr_kernel(Kernel * K, VIO_Volume * vol, VIO_Volume *cmp); + +#endif
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernels/3x3_4-conn.kern @@ -0,0 +1,20 @@ +MNI Morphology Kernel File +% +% 2D 4-connectivity ie: +% +% 1 +% 1 0 1 +% 1 +% +% 0 - center voxel +% Format: vector for voxel in 5 dimensions (x,y,z,t,v) then multiplier. + +Kernel_Type = Normal_Kernel; +Kernel = +% x y z t v coeff +% ----------------------------------- + 1.0 0.0 0.0 0.0 0.0 1.0 + -1.0 0.0 0.0 0.0 0.0 1.0 + 0.0 1.0 0.0 0.0 0.0 1.0 + 0.0 -1.0 0.0 0.0 0.0 1.0; +
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernels/3x3_8-conn.kern @@ -0,0 +1,27 @@ +MNI Morphology Kernel File +% +% 2D 8-connectivity ie: +% +% 1 1 1 +% 1 0 1 +% 1 1 1 +% +% 0 - center voxel +% Format: vector for voxel in 5 dimensions (x,y,z,t,v) then multiplier. + +Kernel_Type = Normal_Kernel; +Kernel = +% x y z t v coeff +% ----------------------------------- + 1.0 1.0 0.0 0.0 0.0 1.0 + 1.0 0.0 0.0 0.0 0.0 1.0 + 1.0 -1.0 0.0 0.0 0.0 1.0 +% + 0.0 1.0 0.0 0.0 0.0 1.0 +%% 0.0 0.0 0.0 0.0 0.0 1.0 + 0.0 -1.0 0.0 0.0 0.0 1.0 +% + -1.0 1.0 0.0 0.0 0.0 1.0 + -1.0 0.0 0.0 0.0 0.0 1.0 + -1.0 -1.0 0.0 0.0 0.0 1.0; +
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernels/3x3x3_26-conn.kern @@ -0,0 +1,53 @@ +MNI Morphology Kernel File +% +% 3D 26-connectivity ie: (in 2D) +% +% 1 1 1 +% 1 0 1 +% 1 1 1 +% +% 0 - center voxel +% Format: vector for voxel in 5 dimensions (x,y,z,t,v) then multiplier. + +Kernel_Type = Normal_Kernel; +Kernel = +% x y z t v coeff +% ----------------------------------- + 1.0 1.0 1.0 0.0 0.0 1.0 + 1.0 1.0 0.0 0.0 0.0 1.0 + 1.0 1.0 -1.0 0.0 0.0 1.0 +% + 1.0 0.0 1.0 0.0 0.0 1.0 + 1.0 0.0 0.0 0.0 0.0 1.0 + 1.0 0.0 -1.0 0.0 0.0 1.0 +% + 1.0 -1.0 1.0 0.0 0.0 1.0 + 1.0 -1.0 0.0 0.0 0.0 1.0 + 1.0 -1.0 -1.0 0.0 0.0 1.0 +% +% + 0.0 1.0 1.0 0.0 0.0 1.0 + 0.0 1.0 0.0 0.0 0.0 1.0 + 0.0 1.0 -1.0 0.0 0.0 1.0 +% + 0.0 0.0 1.0 0.0 0.0 1.0 +%% 0.0 0.0 0.0 0.0 0.0 1.0 + 0.0 0.0 -1.0 0.0 0.0 1.0 +% + 0.0 -1.0 1.0 0.0 0.0 1.0 + 0.0 -1.0 0.0 0.0 0.0 1.0 + 0.0 -1.0 -1.0 0.0 0.0 1.0 +% +% + -1.0 1.0 1.0 0.0 0.0 1.0 + -1.0 1.0 0.0 0.0 0.0 1.0 + -1.0 1.0 -1.0 0.0 0.0 1.0 +% + -1.0 0.0 1.0 0.0 0.0 1.0 + -1.0 0.0 0.0 0.0 0.0 1.0 + -1.0 0.0 -1.0 0.0 0.0 1.0 +% + -1.0 -1.0 1.0 0.0 0.0 1.0 + -1.0 -1.0 0.0 0.0 0.0 1.0 + -1.0 -1.0 -1.0 0.0 0.0 1.0; +
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/kernels/3x3x3_6-conn.kern @@ -0,0 +1,22 @@ +MNI Morphology Kernel File +% +% 3D 6-connectivity ie: (in 2D) +% +% 1 +% 1 0 1 +% 1 +% +% 0 - center voxel +% Format: vector for voxel in 5 dimensions (x,y,z,t,v) then multiplier. + +Kernel_Type = Normal_Kernel; +Kernel = +% x y z t v coeff +% ----------------------------------- + 1.0 0.0 0.0 0.0 0.0 1.0 + -1.0 0.0 0.0 0.0 0.0 1.0 + 0.0 1.0 0.0 0.0 0.0 1.0 + 0.0 -1.0 0.0 0.0 0.0 1.0 + 0.0 0.0 1.0 0.0 0.0 1.0 + 0.0 0.0 -1.0 0.0 0.0 1.0; +
new file mode 100644 --- /dev/null +++ b/progs/mincmorph/mincmorph.c @@ -0,0 +1,748 @@ +/* mincmorph.c + + Copyright 2006-2012 + Andrew Janke - a.janke@gmail.com + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies. The + author and the University make no representations about the + suitability of this software for any purpose. It is provided "as is" + without express or implied warranty. +*/ + + +#include <config.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/param.h> +#include <float.h> +#include <string.h> + +#include <volume_io.h> + +#include <ParseArgv.h> +#include <time_stamp.h> +#include "kernel_io.h" +#include "kernel_ops.h" + +#define INTERNAL_PREC NC_FLOAT /* should be NC_FLOAT or NC_DOUBLE */ +#define DEF_DOUBLE -DBL_MAX + +/* function prototypes */ +char *get_real_from_string(char *string, double *value); +char *get_string_from_string(char *string, char **value); +void calc_volume_range(VIO_Volume * vol, double *min, double *max); +void print_version_info(void); + +/* kernel names for pretty output */ +char *KERN_names[] = { "NULL", "2D04", "2D08", "3D06", "3D26" }; + +/* typedefs */ +typedef enum { + UNDEF = 0, + BINARISE, CLAMP, PAD, ERODE, DILATE, MDILATE, + OPEN, CLOSE, LPASS, HPASS, CONVOLVE, DISTANCE, + GROUP, READ_KERNEL, WRITE, LCORR + } op_types; + +typedef struct { + op_types type; + char op_c; + char *kernel_fn; + kern_types kernel_id; + char *cmpfile; + char *outfile; + double range[2]; + double foreground; + double background; + } Operation; + +/* Argument variables */ +int verbose = FALSE; +int clobber = FALSE; +int is_signed = FALSE; +nc_type dtype = NC_SHORT; +double range[2] = { -DBL_MAX, DBL_MAX }; +double foreground = 1.0; +double background = 0.0; +kern_types kernel_id = K_NULL; +char *kernel_fn = NULL; +char *succ_txt = "B"; + +char successive_help[] = "Successive operations (Maximum: 100) \ +\n\tB[floor:ceil:fg:bg] - binarise in the range, using foreground and background \ +\n\tK[floor:ceil:bg] - clamp betwen the specified range. Set other voxels to 'bg' (default: 0) \ +\n\tP[bg] - pad volume with respect to the current kernel using 'bg' (default: 0)\ +\n\tE - erosion \ +\n\tD - dilation \ +\n\tM - median dilation \ +\n\tO - open \ +\n\tC - close \ +\n\tL - lowpass filter \ +\n\tH - highpass filter \ +\n\tX - convolve \ +\n\tF - distance transform (binary input only - not checked) \ +\n\tG - Label the groups in the volume in ascending order \ +\n\tR[TYPE|file.kern] - (2D04|2D08|3D06|3D26) or read in a kernel file \ +\n\tW[file.mnc] - write out current results \ +\n\tI[cmp.mnc] - local xcorr between current file and cmp.mnc \ +\n\tDefault: "; + +/* Argument table */ +ArgvInfo argTable[] = { + {NULL, ARGV_HELP, (char *)NULL, (char *)NULL, + "General options:"}, + {"-version", ARGV_FUNC, (char *)print_version_info, (char *)NULL, + "print version info and exit"}, + {"-verbose", ARGV_CONSTANT, (char *)TRUE, (char *)&verbose, + "be verbose"}, + {"-clobber", ARGV_CONSTANT, (char *)TRUE, (char *)&clobber, + "clobber existing files"}, + + {NULL, ARGV_HELP, NULL, NULL, + "\nOutfile Options"}, + {"-filetype", ARGV_CONSTANT, (char *)NC_UNSPECIFIED, (char *)&dtype, + "Use data type of the input file."}, + {"-byte", ARGV_CONSTANT, (char *)NC_BYTE, (char *)&dtype, + "Write out byte data."}, + {"-short", ARGV_CONSTANT, (char *)NC_SHORT, (char *)&dtype, + "Write out short integer data. (Default)"}, + {"-int", ARGV_CONSTANT, (char *)NC_INT, (char *)&dtype, + "Write out long integer data."}, + {"-float", ARGV_CONSTANT, (char *)NC_FLOAT, (char *)&dtype, + "Write out single-precision data."}, + {"-double", ARGV_CONSTANT, (char *)NC_DOUBLE, (char *)&dtype, + "Write out double-precision data."}, + {"-signed", ARGV_CONSTANT, (char *)TRUE, (char *)&is_signed, + "Write signed integer data."}, + {"-unsigned", ARGV_CONSTANT, (char *)FALSE, (char *)&is_signed, + "Write unsigned integer data."}, + + {NULL, ARGV_HELP, NULL, NULL, "\nKernel Options"}, + {"-2D04", ARGV_CONSTANT, (char *)K_2D04, (char *)&kernel_id, + "Use a 2D 4-connectivity kernel."}, + {"-2D08", ARGV_CONSTANT, (char *)K_2D08, (char *)&kernel_id, + "Use a 2D 8-connectivity kernel."}, + {"-3D06", ARGV_CONSTANT, (char *)K_3D06, (char *)&kernel_id, + "Use a 3D 6-connectivity kernel. (default)"}, + {"-3D26", ARGV_CONSTANT, (char *)K_3D26, (char *)&kernel_id, + "Use a 3D 26-connectivity kernel."}, + {"-kernel", ARGV_STRING, (char *)1, (char *)&kernel_fn, + "<kernel.kern> read in a custom kernel file"}, + + {NULL, ARGV_HELP, NULL, NULL, "\nMorphology Options"}, + {"-floor", ARGV_FLOAT, (char *)1, (char *)&range[0], + "lowwer value for binarising or clamping"}, + {"-ceil", ARGV_FLOAT, (char *)1, (char *)&range[1], + "upper value for binarising or clamping (incl)"}, + {"-range", ARGV_FLOAT, (char *)2, (char *)range, + "range for binarising or clamping (incl)"}, + {"-foreground", ARGV_FLOAT, (char *)1, (char *)&foreground, + "foreground value"}, + {"-background", ARGV_FLOAT, (char *)1, (char *)&background, + "background value"}, + + {NULL, ARGV_HELP, (char *)NULL, (char *)NULL, "\nSingle morphological operations:"}, + {"-binarise", ARGV_CONSTANT, (char *)"B", (char *)&succ_txt, + "binarise volume using the input range"}, + {"-clamp", ARGV_CONSTANT, (char *)"K", (char *)&succ_txt, + "clamp volume using the input range"}, + {"-pad", ARGV_CONSTANT, (char *)"P", (char *)&succ_txt, + "pad volume with respect to the current kernel (-background to specify value)"}, + {"-erosion", ARGV_CONSTANT, (char *)"E", (char *)&succ_txt, + "do a single erosion"}, + {"-dilation", ARGV_CONSTANT, (char *)"D", (char *)&succ_txt, + "do a single dilation"}, + {"-median_dilation", ARGV_CONSTANT, (char *)"M", (char *)&succ_txt, + "do a single median dilation (note: this is not a median filter!)"}, + {"-open", ARGV_CONSTANT, (char *)"O", (char *)&succ_txt, + "open: dilation(erosion(X))"}, + {"-close", ARGV_CONSTANT, (char *)"C", (char *)&succ_txt, + "close: erosion(dilation(X))"}, + {"-lowpass", ARGV_CONSTANT, (char *)"L", (char *)&succ_txt, + "lowpass filter: close(open(X))"}, + {"-highpass", ARGV_CONSTANT, (char *)"H", (char *)&succ_txt, + "highpass filter: X - lowpass(X)"}, + {"-convolve", ARGV_CONSTANT, (char *)"X", (char *)&succ_txt, + "convolve file with kernel"}, + {"-distance", ARGV_CONSTANT, (char *)"F", (char *)&succ_txt, + "distance transform"}, + {"-group", ARGV_CONSTANT, (char *)"G", (char *)&succ_txt, + "label groups in ascending order"}, + + {NULL, ARGV_HELP, (char *)NULL, (char *)NULL, + "\nSuccessive morphological operations:"}, + {"-successive", ARGV_STRING, (char *)1, (char *)&succ_txt, successive_help}, + + {NULL, ARGV_HELP, NULL, NULL, ""}, + {NULL, ARGV_END, NULL, NULL, NULL} + }; + +int main(int argc, char *argv[]) +{ + int c; + char *arg_string; + char *infile; + char *outfile; + + VIO_Volume *volume; + VIO_Volume *cmpvol; + Kernel *kernel; + int num_ops; + Operation operation[100]; + Operation *op; + char *tmp_str; + char ext_txt[256]; + char tmp_filename[MAXPATHLEN]; + double tmp_double[4]; + double min, max; + char *ptr; + + char *axis_order[3] = { MIzspace, MIyspace, MIxspace }; + + /* Save time stamp and args */ + arg_string = time_stamp(argc, argv); + + /* Get arguments */ + if(ParseArgv(&argc, argv, argTable, 0) || (argc < 2)){ + fprintf(stderr, "\nUsage: %s [options] <in.mnc> <out.mnc>\n", argv[0]); + fprintf(stderr, " %s -help\n\n", argv[0]); + exit(EXIT_FAILURE); + } + infile = argv[1]; + outfile = argv[2]; + + /* check for the infile */ + if(access(infile, F_OK) != 0){ + fprintf(stderr, "%s: Couldn't find %s\n\n", argv[0], infile); + exit(EXIT_FAILURE); + } + + /* check for the outfile */ + if(access(outfile, F_OK) == 0 && !clobber){ + fprintf(stderr, "%s: %s exists! (use -clobber to overwrite)\n\n", argv[0], outfile); + exit(EXIT_FAILURE); + } + + /* check kernel args */ + if(kernel_fn != NULL && kernel_id != K_NULL){ + fprintf(stderr, "%s: specify either a kernel file or a set kernel (not both)\n\n", + argv[0], outfile); + exit(EXIT_FAILURE); + } + + /* set the default kernel */ + if(kernel_fn == NULL && kernel_id == K_NULL){ + kernel_id = K_3D06; + } + + /* add the implicit read kernel operation */ + num_ops = 0; + op = &operation[num_ops++]; + + op->type = READ_KERNEL; + op->kernel_fn = kernel_fn; + op->kernel_id = kernel_id; + + /* setup operations and check them... */ + if(verbose){ + fprintf(stdout, "---Checking Operation(s): %s---\n", succ_txt); + } + + ptr = succ_txt; + while(ptr[0] != '\0'){ + + /* set up counters and extra text */ + strcpy(ext_txt, ""); + op = &operation[num_ops++]; + + /* get the operation type */ + op->op_c = ptr[0]; + ptr++; + + switch (op->op_c){ + case 'B': + op->type = BINARISE; + + /* get 4 possible values */ + ptr = get_real_from_string(ptr, &tmp_double[0]); + ptr = get_real_from_string(ptr, &tmp_double[1]); + ptr = get_real_from_string(ptr, &tmp_double[2]); + ptr = get_real_from_string(ptr, &tmp_double[3]); + op->range[0] = (tmp_double[0] == DEF_DOUBLE) ? range[0] : tmp_double[0]; + op->range[1] = (tmp_double[1] == DEF_DOUBLE) ? range[1] : tmp_double[1]; + op->foreground = (tmp_double[2] == DEF_DOUBLE) ? foreground : tmp_double[2]; + op->background = (tmp_double[3] == DEF_DOUBLE) ? background : tmp_double[3]; + + sprintf(ext_txt, "range: [%g:%g] fg/bg: [%g:%g]", op->range[0], + op->range[1], op->foreground, op->background); + break; + + case 'K': + op->type = CLAMP; + + /* get 3 possible values */ + ptr = get_real_from_string(ptr, &tmp_double[0]); + ptr = get_real_from_string(ptr, &tmp_double[1]); + ptr = get_real_from_string(ptr, &tmp_double[2]); + op->range[0] = (tmp_double[0] == DEF_DOUBLE) ? range[0] : tmp_double[0]; + op->range[1] = (tmp_double[1] == DEF_DOUBLE) ? range[1] : tmp_double[1]; + op->background = (tmp_double[2] == DEF_DOUBLE) ? background : tmp_double[2]; + + sprintf(ext_txt, "range: [%g:%g] fg/bg: [%g:%g]", op->range[0], + op->range[1], op->foreground, op->background); + break; + + case 'P': + op->type = PAD; + + /* get 1 possible value */ + ptr = get_real_from_string(ptr, &tmp_double[0]); + op->background = (tmp_double[0] == DEF_DOUBLE) ? background : tmp_double[0]; + + sprintf(ext_txt, "fill value: %g", op->background); + break; + + case 'E': + op->type = ERODE; + break; + + case 'D': + op->type = DILATE; + break; + + case 'M': + op->type = MDILATE; + break; + + case 'O': + op->type = OPEN; + break; + + case 'C': + op->type = CLOSE; + break; + + case 'L': + op->type = LPASS; + break; + + case 'H': + op->type = HPASS; + break; + + case 'X': + op->type = CONVOLVE; + break; + + case 'F': + op->type = DISTANCE; + break; + + case 'G': + op->type = GROUP; + break; + + case 'R': + op->type = READ_KERNEL; + + /* get the filename */ + ptr = get_string_from_string(ptr, &tmp_str); + if(tmp_str == NULL){ + fprintf(stderr, "%s: R[TYPE|file.kern] requires a file or kernel name\n\n", + argv[0]); + exit(EXIT_FAILURE); + } + + /* check if the input kernel is an inbuilt one */ + op->kernel_id = K_NULL; + for(c = 1; c <= n_inbuilt_kern; c++){ + if(strcmp(tmp_str, KERN_names[c]) == 0){ + op->kernel_id = (kern_types) c; + } + } + + /* if no inbuilt found, assume it's a file */ + if(op->kernel_id == K_NULL){ + + /* set up and check for the real filename */ + (void)realpath(tmp_str, tmp_filename); + if(access(tmp_filename, F_OK) != 0){ + fprintf(stderr, "%s: Couldn't find kernel file: %s\n\n", argv[0], + tmp_filename); + exit(EXIT_FAILURE); + } + + op->kernel_fn = strdup(tmp_filename); + sprintf(ext_txt, "kernel_fn: %s", op->kernel_fn); + } + else { + sprintf(ext_txt, "inbuilt_kernel[%d]: %s", op->kernel_id, + KERN_names[op->kernel_id]); + } + break; + + case 'W': + op->type = WRITE; + + /* get the filename */ + ptr = get_string_from_string(ptr, &op->outfile); + + if(op->outfile == NULL){ + fprintf(stderr, "%s: W[file.mnc] _requires_ a filename\n\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* check for the outfile */ + if(access(op->outfile, F_OK) == 0 && !clobber){ + fprintf(stderr, "%s: %s exists! (use -clobber to overwrite)\n\n", argv[0], + op->outfile); + exit(EXIT_FAILURE); + } + + sprintf(ext_txt, "filename: %s", op->outfile); + break; + + case 'I': + op->type = LCORR; + + /* get the filenames */ + ptr = get_string_from_string(ptr, &op->cmpfile); + + if(op->cmpfile == NULL){ + fprintf(stderr, "%s: I[cmp.mnc] requires a filename\n\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* check for cmpfile */ + if(access(op->cmpfile, F_OK) != 0){ + fprintf(stderr, "%s: Couldn't find compare file: %s\n\n", argv[0], op->cmpfile); + exit(EXIT_FAILURE); + } + + sprintf(ext_txt, "compare filename: %s", op->cmpfile); + break; + + default: + fprintf(stderr, "\nUnknown op: %c\n\n %s -help for operations\n\n", + op->op_c, argv[0]); + exit(EXIT_FAILURE); + } + + if(verbose){ + fprintf(stdout, " Op[%02d] %c = %d\t\t%s\n", num_ops, op->op_c, op->type, + ext_txt); + } + } + + /* add an implicit write statment to the end if needed */ + if(operation[num_ops - 1].type != WRITE){ + operation[num_ops].type = WRITE; + operation[num_ops].outfile = outfile; + num_ops++; + } + + /* malloc space for volume structure and read in infile */ + volume = (VIO_Volume *) malloc(sizeof(VIO_Volume)); + input_volume(infile, MAX_VAR_DIMS, axis_order, + INTERNAL_PREC, TRUE, 0.0, 0.0, TRUE, volume, NULL); + get_type_range(get_volume_data_type(*volume), &min, &max); + set_volume_real_range(*volume, min, max); + + /* init and then do some operations */ + kernel = new_kernel(0); + + if(verbose){ + fprintf(stdout, "\n---Doing %d Operation(s)---\n", num_ops); + } + for(c = 0; c < num_ops; c++){ + op = &operation[c]; + + switch (op->type){ + case BINARISE: + volume = binarise(volume, op->range[0], op->range[1], + op->foreground, op->background); + break; + + case CLAMP: + volume = clamp(volume, op->range[0], op->range[1], op->background); + break; + + case PAD: + volume = pad(kernel, volume, op->background); + break; + + case ERODE: + volume = erosion_kernel(kernel, volume); + break; + + case DILATE: + volume = dilation_kernel(kernel, volume); + break; + + case MDILATE: + volume = median_dilation_kernel(kernel, volume); + break; + + case OPEN: + volume = erosion_kernel(kernel, volume); + volume = dilation_kernel(kernel, volume); + break; + + case CLOSE: + volume = dilation_kernel(kernel, volume); + volume = erosion_kernel(kernel, volume); + break; + + case LPASS: + volume = erosion_kernel(kernel, volume); + volume = dilation_kernel(kernel, volume); + volume = dilation_kernel(kernel, volume); + volume = erosion_kernel(kernel, volume); + break; + + case HPASS: + fprintf(stderr, "%s: GNFARK! Highpass Not implemented yet..\n\n", argv[0]); + break; + + case CONVOLVE: + volume = convolve_kernel(kernel, volume); + break; + + case DISTANCE: + volume = distance_kernel(kernel, volume, background); + break; + + case GROUP: + volume = group_kernel(kernel, volume, background); + break; + + case READ_KERNEL: + /* free the existing kernel then set the pointer to the new one */ + free(kernel); + + /* read in the kernel or set the kernel to an inbuilt one */ + if(op->kernel_id == K_NULL){ + kernel = new_kernel(0); + if(input_kernel(op->kernel_fn, kernel) != OK){ + fprintf(stderr, "%s: Died reading in kernel file: %s\n\n", argv[0], + op->kernel_fn); + exit(EXIT_FAILURE); + } + } + else { + + switch (op->kernel_id){ + case K_2D04: + kernel = get_2D04_kernel(); + break; + + case K_2D08: + kernel = get_2D08_kernel(); + break; + + case K_3D06: + kernel = get_3D06_kernel(); + break; + + case K_3D26: + kernel = get_3D26_kernel(); + break; + + default: + fprintf(stderr, "%s: This shouldn't happen -- much bad\n\n", argv[0]); + exit(EXIT_FAILURE); + } + } + + setup_pad_values(kernel); + if(verbose){ + fprintf(stdout, "Input kernel:\n"); + print_kernel(kernel); + } + break; + + case WRITE: + if(op->outfile == NULL){ + fprintf(stdout, "%s: WRITE passed a NULL pointer! - this is bad\n\n", + argv[0]); + exit(EXIT_FAILURE); + } + + if(verbose){ + fprintf(stdout, "Outputting to %s\n", op->outfile); + } + + /* get the resulting range */ + calc_volume_range(volume, &min, &max); + + /* set the range to something sensible (if possible) */ + if(dtype == NC_BYTE && is_signed == FALSE){ + if(min >= 0 && max < 255){ + fprintf(stdout, "BYTE data, setting 1:1 mapping (0-256)\n"); + min = 0; + max = 255; + } + } + set_volume_real_range(*volume, min, max); + + output_modified_volume(op->outfile, + dtype, is_signed, + 0.0, 0.0, *volume, infile, arg_string, NULL); + break; + + case LCORR: + if(op->cmpfile == NULL){ + fprintf(stdout, "%s: LCORR passed a NULL pointer! - this is bad\n\n", + argv[0]); + exit(EXIT_FAILURE); + } + + if(verbose){ + fprintf(stdout, "Comparing to %s\n", op->cmpfile); + } + + /* malloc space for volume structure and read cmpfile */ + cmpvol = (VIO_Volume *) malloc(sizeof(VIO_Volume)); + input_volume(op->cmpfile, MAX_VAR_DIMS, axis_order, + INTERNAL_PREC, TRUE, 0.0, 0.0, TRUE, cmpvol, NULL); + + /* run the local correlation */ + volume = lcorr_kernel(kernel, volume, cmpvol); + + /* clean up */ + delete_volume(*cmpvol); + free(cmpvol); + + break; + + default: + fprintf(stderr, "\n%s: Unknown operation (This is very bad, call Houston)\n\n", argv[0]); + exit(EXIT_FAILURE); + } + } + + /* jump through operations freeing stuff */ + // free(op.kernel); + + delete_volume(*volume); + return (EXIT_SUCCESS); + } + +/* get a real from a char* stream */ +/* with possible trailing or leading square bracket */ +/* and possible leading ':' */ +/* return the string advanced to the next token or */ +/* as it was input if nothing found */ +char *get_real_from_string(char *string, double *value) +{ + char *ptr; + + /* skip a [ or : else we probably don't belong here */ + if(string[0] == '[' || string[0] == ':'){ + string++; + } + + /* get a double */ + *value = strtod(&string[0], &ptr); + + /* if nothing found return a default value */ + if(&string[0] == ptr){ + *value = DEF_DOUBLE; + } + + /* skip over a possible ']' */ + if(ptr[0] == ']'){ + ptr++; + } + + return ptr; + } + +/* get a copy of the string between [ and ] from a char* */ +/* return the string advanced to the next token or */ +/* as it was input if nothing found */ +char *get_string_from_string(char *string, char **value) +{ + int offset; + char *malloc_string; + + /* initialise the return values first */ + *value = NULL; + + /* get the string if there is one */ + if(string[0] == '['){ + string++; + + /* get the length of the string in question */ + offset = strcspn(string, "]"); + + /* alloc some space for the string */ + malloc_string = (char *)malloc((offset + 1) * sizeof(char)); + + /* get a copy of it and append the null charater */ + strncpy(malloc_string, string, offset); + malloc_string[offset] = '\0'; + + /* store the result */ + *value = malloc_string; + + /* increment the pointer and skip over a possible '[' */ + string += offset; + if(string[0] == ']'){ + string++; + } + } + + return string; + } + +void calc_volume_range(VIO_Volume * vol, double *min, double *max) +{ + + int x, y, z; + int sizes[MAX_VAR_DIMS]; + double value; + VIO_progress_struct progress; + + *min = DBL_MAX; + *max = -DBL_MIN; + + get_volume_sizes(*vol, sizes); + + initialize_progress_report(&progress, FALSE, sizes[2], "Finding Range"); + for(z = sizes[0]; z--;){ + for(y = sizes[1]; y--;){ + for(x = sizes[2]; x--;){ + + value = get_volume_voxel_value(*vol, z, y, x, 0, 0); + if(value < *min){ + *min = value; + } + else if(value > *max){ + *max = value; + } + } + } + update_progress_report(&progress, z + 1); + } + terminate_progress_report(&progress); + + if (*min == *max) { + *max = *min + 1.0; + } + + if(verbose){ + fprintf(stdout, "Found range of [%g:%g]\n", *min, *max); + } + } + +void print_version_info(void) +{ + fprintf(stdout, "%s version %s\n", PACKAGE, VERSION); + fprintf(stdout, "Comments to %s\n", PACKAGE_BUGREPORT); + fprintf(stdout, "\n"); + exit(EXIT_SUCCESS); + }