1 #include "mrilib.h"
2 
3 
set_edge_dog_defaults(void)4 PARAMS_edge_dog set_edge_dog_defaults(void)
5 {
6 
7    PARAMS_edge_dog defopt;
8 
9    defopt.input_name = NULL;
10    defopt.mask_name  = NULL;
11    defopt.prefix     = NULL;
12    defopt.prefix_dog = NULL;
13 
14    // can do automasking, and have the automask dilated amask_ndil
15    // times (with NN=2 dilation)
16    defopt.do_automask        = 0;
17    defopt.amask_ndil         = 0;
18 
19    defopt.do_output_intermed = 0;
20 
21    // units=mm; from typical adult human GM thick.  Will allow this to
22    // be anisotropic, hence array of 3
23    defopt.sigma_rad[0] = 2.0;
24    defopt.sigma_rad[1] = 2.0;
25    defopt.sigma_rad[2] = 2.0;
26 
27    // units=none; alt. to sigma_rad, scale vox dims by this factor to
28    // get sigma vals.  Will allow this to be anisotropic, hence array
29    // of 3
30    defopt.sigma_nvox[0] = 0.0;
31    defopt.sigma_nvox[1] = 0.0;
32    defopt.sigma_nvox[2] = 0.0;
33 
34    // ratio of outer/inner gaussians; from MH1980
35    // Note: from testing different values, decreasing this ratio toward
36    // 1.1 shows more details---but it gets noisy, too.
37    defopt.ratio_sigma = 1.4;
38 
39    /*
40      EDGE CONTROL PARAMS
41 
42      --- For the EDT (Euler-distance based) case ---
43 
44        edge_bnd_NN: Use NN value to select "corner-ness" of output
45        boundary edges. This will be used to threshold the
46        voxel-counted EDT maps---this works elegantly because we will
47        output distance-squared maps of number of voxels from the EDT:
48          + NN=1 -> for face only
49          + NN=2 -> for face+edge
50          + NN=3 -> for face+edge+node
51        NB: We increase them *slightly* for the actual comparisons.
52 
53        edge_bnd_side: determine which boundary of the EDT to use for the
54        edge.  Encoding is (user enters char string keyword to select):
55          + "NEG"  -> -1 -> for negative (inner) boundary
56          + "POS"  ->  1 -> for positive (outer) boundary
57          + "BOTH" ->  2 -> for both (inner+outer) boundary
58          + "BOTH_SIGN"-> 3 -> for both (inner+outer) boundary,
59                               with pos/neg sides keeping sign
60        Noting that using "NEG"/-1 seems best, by eye (in preliminary tests).
61        NB: edge_bnd_side_user is just displayed in the help file---just keep
62        it consistent with the internal value.
63 
64        edge_bnd_scale: use the values of the gradients to scale the
65        edges.
66    */
67    defopt.edge_bnd_NN = 1;
68    defopt.edge_bnd_side = -1;
69    defopt.edge_bnd_side_user = "NEG";
70    defopt.edge_bnd_scale = 0;
71 
72    defopt.verb = 1;
73 
74    // same as in PARAMS_euler_dist
75    defopt.only2D = NULL;
76    defopt.axes_to_proc[0] = 1;
77    defopt.axes_to_proc[1] = 1;
78    defopt.axes_to_proc[2] = 1;
79 
80    return defopt;
81 };
82 
83 // ---------------------------------------------------------------------------
84 
85 /*
86   Build prefixes for supplementary data output, based on user's chosen
87   prefix.
88 
89   opts     :struct of default/user input (here, getting inopts->prefix)
90   suffix   :that which will be inserted at the end of the prefix_noext
91             from inopts->prefix
92 
93 */
build_edge_dog_suppl_prefix(PARAMS_edge_dog * opts,char * ostr,char * suffix)94 int build_edge_dog_suppl_prefix( PARAMS_edge_dog *opts, char *ostr,
95                                  char *suffix)
96 {
97    char *ext, nullch, tprefix[THD_MAX_PREFIX-strlen(suffix)];
98 
99    ENTRY("build_edge_dog_suppl_prefix");
100 
101    sprintf(tprefix, "%s", opts->prefix);
102 
103    if( has_known_non_afni_extension(opts->prefix) ){
104       ext = find_filename_extension(opts->prefix);
105       tprefix[strlen(opts->prefix) - strlen(ext)] = '\0';
106    }
107    else {
108       nullch = '\0';
109       ext = &nullch;
110    }
111 
112    sprintf(ostr, "%s%s%s", tprefix, suffix, ext);
113 
114    return 0;
115 }
116 
117 // ---------------------------------------------------------------------------
118 
119 /*
120   Use the data in the opts struct to decide how to much to blur in
121   each direction.  Blurring sigmas can be anisotropic, but the ratio
122   between inner and outer blurring is constant across dimension.
123 
124   opts     :struct of default/user opts
125   Ledge    :fl arr of len=3 of voxel edge lengths (could be NULL for sigma_nvox)
126   rad_in   :fl arr of len=3 of inner Gaussian sigmas (basically an output here)
127   diff_rad_out  :fl arr of len=3 of outer Gaussian sigmas, but these are
128             *differential* ones, bc applied to the rad_in-blurred data, for
129             computational efficiency (basically an output here)
130 
131 */
calc_edge_dog_sigmas(PARAMS_edge_dog opts,float * Ledge,float * rad_in,float * diff_rad_out)132 int calc_edge_dog_sigmas(PARAMS_edge_dog opts, float *Ledge,
133                          float *rad_in, float *diff_rad_out)
134 {
135    int ii;
136    float fac;
137 
138    ENTRY("calc_edge_dog_sigmas");
139 
140    if( opts.sigma_nvox[0] && opts.sigma_nvox[1] && \
141        opts.sigma_nvox[2] ){ // user chose to scale voxel edge lengths
142       for( ii=0 ; ii<3 ; ii++ )
143          rad_in[ii] = opts.sigma_nvox[ii]*Ledge[ii];
144    }
145    else{ // user chose sigmas with physical mm values
146       for( ii=0 ; ii<3 ; ii++ )
147          rad_in[ii] = opts.sigma_rad[ii];
148    }
149 
150    // from formula:   sigma_outer**2 = sigma_inner**2 + fac**2,
151    // where sigma_outer = sigma_inner*ratio_sigma.
152    fac = sqrt(opts.ratio_sigma * opts.ratio_sigma  - 1.0);
153 
154    for( ii=0 ; ii<3 ; ii++ )
155       diff_rad_out[ii] = rad_in[ii] * fac;
156 
157    return 0;
158 }
159 
160 // ---------------------------------------------------------------------------
161 
162 /*
163   Calculate the difference of gaussian (DOG) dataset, which will be
164   thresholded to be the edge map.
165 
166   dset_dog    :the dset that will be the DOG dataset (essentially, the output)
167   opts         :options from the user, with some other quantities calc'ed
168   dset_input   :the input dataset of which DOG/edges will be calculated
169   ival         :index of subvolume of 'dset_input' to process
170   ival         :shared index of subvol of 'dset_input' & 'dset_dog' to process
171 
172 */
calc_edge_dog_DOG(THD_3dim_dataset * dset_dog,PARAMS_edge_dog opts,THD_3dim_dataset * dset_input,int ival)173 int calc_edge_dog_DOG( THD_3dim_dataset *dset_dog, PARAMS_edge_dog opts,
174                        THD_3dim_dataset *dset_input, int ival)
175 {
176    int ii, idx;
177    int nx, ny, nz, nvox;
178    float Ledge[3];
179 
180    MRI_IMAGE *im_tmp = NULL, *im_inner = NULL, *im_outer = NULL;
181    float *fl_im_inner = NULL, *fl_im_outer = NULL;
182    float *tmp_arr = NULL;
183 
184    float rad_in[3], diff_rad_out[3];
185 
186    ENTRY("calc_edge_dog_DOG");
187 
188    nx = DSET_NX(dset_input);
189    ny = DSET_NY(dset_input);
190    nz = DSET_NZ(dset_input);
191    nvox = DSET_NVOX(dset_input);
192    Ledge[0] = DSET_DX(dset_input);
193    Ledge[1] = DSET_DY(dset_input);
194    Ledge[2] = DSET_DZ(dset_input);
195 
196    // get radii
197    ii = calc_edge_dog_sigmas(opts, Ledge, rad_in, diff_rad_out);
198 
199    // copy the subvolume's image (floatizing, if necessary)
200    im_tmp = dset_input->dblk->brick->imarr[ival];
201    im_inner = (im_tmp->kind != MRI_float) ? mri_to_float(im_tmp) : \
202       mri_copy(im_tmp);
203 
204    fl_im_inner = MRI_FLOAT_PTR(im_inner);
205 
206    // apply inner and outer blurring
207    EDIT_blur_volume_3d( nx, ny, nz, Ledge[0], Ledge[1], Ledge[2],
208                         MRI_float, fl_im_inner,
209                         rad_in[0], rad_in[1], rad_in[2] );
210 
211    /*
212      The Rorden Rule: make the im_outer by applying an *additive* blur
213      to the im_inner.  Saves time, saves money, whitens teeth.
214     */
215    im_outer = mri_copy(im_inner);
216    fl_im_outer = MRI_FLOAT_PTR(im_outer);
217 
218    EDIT_blur_volume_3d( nx, ny, nz, Ledge[0], Ledge[1], Ledge[2],
219                         MRI_float, fl_im_outer,
220                         diff_rad_out[0], diff_rad_out[1], diff_rad_out[2] );
221 
222    // subtract the outer from the inner at each voxel
223    tmp_arr = (float *)calloc(nvox, sizeof(float));
224    for ( idx=0 ; idx<nvox ; idx++ )
225       tmp_arr[idx] = fl_im_inner[idx]- fl_im_outer[idx];
226 
227    // load this array into the dset subvolume
228    EDIT_substitute_brick(dset_dog, ival, MRI_float, tmp_arr);
229    tmp_arr = NULL;
230 
231    // free
232    if( im_inner )
233       mri_free( im_inner );
234    if( im_outer )
235       mri_free( im_outer );
236    // don't free im_tmp: it is just a pointer to dset_input
237 
238    return 0;
239 }
240 
241 // ---------------------------------------------------------------------------
242 
243 /*
244   Calculate the boundaries from the dog dset.  Might be many ways of
245   doing this.
246 
247   dset_bnd     :the dset that will be the boundary map (essentially, the output)
248   opts         :options from the user, with some other quantities calc'ed
249   dset_dog     :the input dataset of unthresholded/'raw' DOG values
250   ival         :shared index of subvolume of 'dset_bnd' & 'dset_dog' to process
251   argc         :(int) for history of intermed EDT output, if made
252                 (can be 0, to not add to history)
253   argv         :(*char) for history of intermed EDT output, if made
254                 (can be NULL, to not add to history)
255 
256 */
calc_edge_dog_BND(THD_3dim_dataset * dset_bnd,PARAMS_edge_dog opts,THD_3dim_dataset * dset_dog,int ival,int argc,char * argv[])257 int calc_edge_dog_BND( THD_3dim_dataset *dset_bnd, PARAMS_edge_dog opts,
258                        THD_3dim_dataset *dset_dog, int ival,
259                        int argc, char *argv[])
260 {
261    int i, idx;
262    int nvox;
263    short *tmp_arr = NULL;
264    THD_3dim_dataset *dset_edt = NULL;
265    char prefix_edt[THD_MAX_PREFIX];
266    PARAMS_euler_dist EdgeDogOpts;
267 
268    ENTRY("calc_edge_dog_BND");
269 
270    nvox = DSET_NVOX(dset_dog);
271    tmp_arr = (short *) calloc( nvox, sizeof(short) );
272    if( tmp_arr == NULL )
273       ERROR_exit("MemAlloc failure.\n");
274 
275    // ------------------ prep to run EDT calc --------------------
276 
277    // dset_bnd gets a preliminary role: be the ROI map entered into EDT
278 
279    // make ROI=1 where DOG>=0, and background/ROI=0 elsewhere
280    for( idx=0 ; idx<nvox ; idx++ )
281       tmp_arr[idx] = (THD_get_voxel(dset_dog, idx, ival) >= 0.0 ) ? 1 : 0;
282 
283    EDIT_substitute_brick(dset_bnd, ival, MRI_short, tmp_arr);
284    tmp_arr=NULL;
285 
286    // ------------------ run EDT calc --------------------
287 
288    // Make empty EDT dset (NB: just a single volume, FYI, if outputting)
289    dset_edt = EDIT_empty_copy( dset_bnd );
290    i = build_edge_dog_suppl_prefix( &opts, prefix_edt, "_EDT2" );
291    EDIT_dset_items(dset_edt,
292                    ADN_nvals, 1,
293                    ADN_datum_all, MRI_short,
294                    ADN_prefix, prefix_edt,
295                    ADN_none );
296 
297    // fill EDT option struct with defaults and a couple desired props
298    EdgeDogOpts = set_euler_dist_defaults();
299    EdgeDogOpts.binary_only = 1;      // for faster runtime
300    EdgeDogOpts.ignore_voxdims = 1;
301    EdgeDogOpts.zero_region_sign = -1;
302    EdgeDogOpts.dist_sq = 1;          // so NN values are directly thresholds
303    EdgeDogOpts.verb = 0;
304    if( opts.only2D ){
305       EdgeDogOpts.only2D = strdup(opts.only2D);
306       for( i=0 ; i<3 ; i++ )
307          EdgeDogOpts.axes_to_proc[i] = opts.axes_to_proc[i];
308    }
309 
310 
311    // run EDT
312    INFO_message("Calculate EDT for vol %d", ival);
313    i = calc_EDT_3D_BIN( dset_edt, EdgeDogOpts, dset_bnd, NULL, ival);
314 
315    // can output this intermediate dset for the [0]th volume, if the
316    // user asks
317    if( opts.do_output_intermed && ival==0 ) {
318       INFO_message("Output intermediate dset ([0]th vol): %s", prefix_edt);
319 
320       THD_load_statistics( dset_edt );
321       if( !THD_ok_overwrite() && THD_is_ondisk(DSET_HEADNAME(dset_edt)) )
322          ERROR_exit("Can't overwrite existing dataset '%s'",
323                     DSET_HEADNAME(dset_edt));
324       tross_Make_History("3dedgedog", argc, argv, dset_edt);
325 
326       // write and free dset
327       THD_write_3dim_dataset(NULL, NULL, dset_edt, True);
328    }
329 
330    // threshold EDT to make boundaries; dset_edt has only one volume
331    // here (to save memory), but dset_bnd can be 4D---hence two indices
332    i = calc_edge_dog_thr_EDT( dset_bnd, opts, dset_edt, 0, ival);
333 
334    // free dset
335 	DSET_delete(dset_edt);
336   	free(dset_edt);
337 
338    return 0;
339 }
340 
341 // ----------------------------------------
342 
343 /*
344    Same inputs as calc_edge_dog_BND().
345 
346    Get 2- and 98-%ile values of DOG values across edge voxels,
347    and use DOG values scaled within this range for output
348    coloration.
349 
350    Here, we basically follow what happens in 3dBrickStat.c.
351 */
scale_edge_dog_BND(THD_3dim_dataset * dset_bnd,PARAMS_edge_dog opts,THD_3dim_dataset * dset_dog,int ival)352 int scale_edge_dog_BND( THD_3dim_dataset *dset_bnd, PARAMS_edge_dog opts,
353                         THD_3dim_dataset *dset_dog, int ival)
354 {
355    void *tmp_vec = NULL;
356    byte *mmm = NULL;  // to be byte mask where edges are
357    int nvox = 0;
358    int ninmask = 0;
359 
360    int N_mp = 2;                     // number of percentiles to calc
361    double mpv[2] = {0.0, 0.0};       // the percentile values to calc
362    double perc[2] = {0.0, 0.0};      // will hold the percentile estimates
363    int zero_flag = 0, pos_flag = 1, neg_flag = 1; // %ile in nonzero
364 
365    int i;
366    float bot, top, ran, val;
367    float *flim = NULL;
368    short *tmp_arr = NULL;
369 
370    ENTRY("scale_edge_dog_BND");
371 
372    nvox = DSET_NVOX(dset_bnd);
373    tmp_arr = (short *) calloc( nvox, sizeof(short) );
374    if( tmp_arr == NULL )
375       ERROR_exit("MemAlloc failure.\n");
376 
377    mmm = THD_makemask( dset_bnd, ival, 0.0, -1.0 );
378    if ( !mmm ) {
379       ERROR_message("Failed to general %ile mask.");
380       exit(1);
381    }
382    ninmask = THD_countmask(nvox, mmm);
383 
384    // The percentile ranges depend on which kind of boundaries we have
385    if( opts.edge_bnd_side == 1 ){
386       mpv[0] = 0.02;
387       mpv[1] = 0.50;
388    }
389    else if( opts.edge_bnd_side == -1 ){
390       mpv[0] = 0.98;
391       mpv[1] = 0.50;
392    }
393    else if( opts.edge_bnd_side == 2 || opts.edge_bnd_side == 3 ){
394       mpv[0] = 0.25;
395       mpv[1] = 0.75;
396    }
397 
398    tmp_vec = Percentate( DSET_ARRAY(dset_dog, ival), mmm, nvox,
399                          DSET_BRICK_TYPE(dset_dog, ival), mpv, N_mp,
400                          1, perc,
401                          zero_flag, pos_flag, neg_flag );
402    if ( !tmp_vec ) {
403       ERROR_message("Failed to compute percentiles.");
404       exit(1);
405    }
406 
407    //INFO_message("RANGE: %.6f %.6f", perc[0], perc[1]);
408 
409    flim = MRI_FLOAT_PTR(dset_dog->dblk->brick->imarr[ival]);
410 
411    // Decide on boundary values.  Nothing can have zero EDT here, so
412    // don't need to worry about doubling up on that.
413    if( opts.edge_bnd_side == 1 || opts.edge_bnd_side == -1) {
414       bot = (float) perc[0];
415       top = (float) perc[1];
416       ran = top - bot;
417 
418       for( i=0 ; i<nvox ; i++ )
419          if( mmm[i] ){
420             val = (flim[i] - bot)/ran;
421             val = ( val > 0.0 ) ? val : 0.0;
422             tmp_arr[i] = (val >= 1.0 ) ? 100 : 99*val+1;
423          }
424    }
425    else if( opts.edge_bnd_side == 2 || opts.edge_bnd_side == 3) {
426       bot = (float) perc[0];
427       top = (float) perc[1];
428 
429       for( i=0 ; i<nvox ; i++ )
430          if( mmm[i] ){
431             if( flim[i] >= 0 )
432                val = flim[i]/top;
433             else
434                val = flim[i]/bot;
435             tmp_arr[i] = (val >= 1.0 ) ? 100 : 99*val+1;
436 
437             if( flim[i] < 0 && opts.edge_bnd_side == 3 )
438                tmp_arr[i]*= -1;
439          }
440    }
441 
442    EDIT_substitute_brick(dset_bnd, ival, MRI_short, tmp_arr);
443    tmp_arr=NULL;
444 
445    if( tmp_vec ){
446       free(tmp_vec);
447       tmp_vec = NULL;
448    }
449    if( mmm )
450       free(mmm);
451 
452    flim = NULL;
453 
454    return 0;
455 };
456 
457 // ---------------------------------------------------------------------------
458 
459 /*
460   Threshold the boundaries of the EDT dset.  Might be many ways of
461   doing this.
462 
463   dset_bnd     :the dset that will be the edge dataset (essentially, the output)
464   opts         :options from the user, with some other quantities calc'ed
465   dset_edt     :the input dataset EDT values, to be thresholded for edges
466   ival_bnd     :index of subvolume of 'dset_bnd' to process
467   ival_edt     :index of subvolume of 'dset_edt' to process
468 
469 */
calc_edge_dog_thr_EDT(THD_3dim_dataset * dset_bnd,PARAMS_edge_dog opts,THD_3dim_dataset * dset_edt,int ival_bnd,int ival_edt)470 int calc_edge_dog_thr_EDT( THD_3dim_dataset *dset_bnd, PARAMS_edge_dog opts,
471                            THD_3dim_dataset *dset_edt, int ival_bnd,
472                            int ival_edt)
473 {
474    int i, idx;
475    int nvox;
476    float bot=0, top=-1, val;
477    short *tmp_arr = NULL;
478 
479    ENTRY("calc_edge_dog_thr_EDT");
480 
481    nvox = DSET_NVOX(dset_edt);
482    tmp_arr = (short *) calloc( nvox, sizeof(short) );
483    if( tmp_arr == NULL )
484       ERROR_exit("MemAlloc failure.\n");
485 
486    // Decide on boundary values.  Nothing can have zero EDT here, so
487    // don't need to worry about doubling up on that.
488    if( opts.edge_bnd_side == 1 ) {
489       bot = 0.0;
490       top = opts.edge_bnd_NN * 1.01;
491    }
492    else if( opts.edge_bnd_side == -1 ) {
493       bot = -opts.edge_bnd_NN * 1.01;
494       top = 0;
495    }
496    else if( opts.edge_bnd_side == 2 || opts.edge_bnd_side == 3 ) {
497       bot = -opts.edge_bnd_NN * 1.01;
498       top = opts.edge_bnd_NN * 1.01;
499    }
500 
501    // Go through EDT values, pick out what becomes a boundary
502    for ( idx=0 ; idx<nvox ; idx++ ) {
503       val = THD_get_voxel(dset_edt, idx, ival_edt);
504       if( bot <= val && val <= top ) {
505          tmp_arr[idx] = 1;
506          if(  opts.edge_bnd_side == 3 )
507             if( val < 0 )
508                tmp_arr[idx]*= -1;
509       }
510    }
511 
512    // load this array into the dset subvolume
513    EDIT_substitute_brick(dset_bnd, ival_bnd, MRI_short, tmp_arr);
514    tmp_arr = NULL;
515 
516    return 0;
517 }
518