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