1 #include "mrilib.h"
2
3 static int verb = 1 ;
4
5 #define USE_ALL_VALS 1
6 #ifndef USE_ALL_VALS
7 static int USE_ALL_VALS = 0 ; /* 17 May 2016 */
8 #endif
9
10 #ifdef USE_OMP
11 # include <omp.h>
12 #endif
13
14 static int do_EPI = 0 ; /* 01 Mar 2017 */
15 static int do_double = 1 ; /* duplo? */
16 static int do_squash = 1 ; /* squash big values? [04 May 2020] */
17
18 static THD_3dim_dataset *inset=NULL , *outset=NULL ;
19
20 static float Upbot = 70.0f ; /* percentile bottom and top */
21 static float Uptop = 80.0f ;
22 static float Uprad = 18.3f ; /* sphere radius */
23
24 #define PKVAL 1000.0f /* peak value for WM */
25 #define PKMID 666.0f /* middle value for GM */
26 #define WMCUT 1300.0f /* level for WM squashing */
27 #define WMSCL 200.0f /* scale for WM squashing */
28
29 static MRI_IMAGE *sclim = NULL ; /* 25 Jun 2013 */
30 static char *sspref = NULL ;
31 static char *ampref = NULL ; /* 20 Nov 2018 */
32
33 #if 0
34 /*---------------------------------------------------------------------------*/
35
36 void mri_invertcontrast_inplace( MRI_IMAGE *im , float uperc , byte *mask )
37 {
38 byte *mmm=NULL ;
39 int nvox , nhist , ii ; float *hist=NULL , *imar , ucut ;
40
41 if( im == NULL || im->kind != MRI_float ) return ;
42 if( uperc < 90.0f ) uperc = 90.0f ;
43 else if( uperc > 100.0f ) uperc = 100.0f ;
44
45 mmm = mask ;
46 if( mmm == NULL ) mmm = mri_automask_image(im) ;
47
48 nvox = im->nvox ;
49 hist = (float *)malloc(sizeof(float)*nvox) ;
50 imar = MRI_FLOAT_PTR(im) ;
51 for( nhist=ii=0 ; ii < nvox ; ii++ ){ if( mmm[ii] ) hist[nhist++] = imar[ii]; }
52 if( nhist < 100 ){
53 if( mmm != mask ) free(mmm) ;
54 free(hist) ; return ;
55 }
56 qsort_float(nhist,hist) ;
57 ii = (int)rintf(nhist*uperc*0.01f) ; ucut = hist[ii] ; free(hist) ;
58 for( ii=0 ; ii < nvox ; ii++ ){
59 if( mmm[ii] ) imar[ii] = ucut - imar[ii] ;
60 if( !mmm[ii] || imar[ii] < 0.0f ) imar[ii] = 0.0f ;
61 }
62 if( mmm != mask ) free(mmm) ;
63 return ;
64 }
65 #endif
66
67 /*---------------------------------------------------------------------------*/
68
mri_clipedges_inplace(MRI_IMAGE * qm,float uclip,float vclip)69 void mri_clipedges_inplace( MRI_IMAGE *qm , float uclip , float vclip )
70 {
71 int ii,jj,kk , ip,jp,kp , im,jm,km , nx,ny,nz,nxy,nxyz , pp,qq , nclip ;
72 float *imar , *qmar ;
73
74 if( qm == NULL || qm->kind != MRI_float ) return ;
75
76 nx = qm->nx ; ny = qm->ny ; nz = qm->nz ; nxy = nx*ny ; nxyz = nxy*nz ;
77 qmar = MRI_FLOAT_PTR(qm) ;
78 imar = malloc(sizeof(float)*nxyz) ;
79
80 #define ISEDGE(i,j,k) (imar[(i)+(j)*nx+(k)*nxy]==0.0f)
81
82 if( verb ) fprintf(stderr,"e") ;
83 do{
84 memcpy(imar,qmar,sizeof(float)*nxyz) ;
85 for( nclip=pp=0 ; pp < nxyz ; pp++ ){
86 if( imar[pp] < uclip ) continue ;
87 kk = pp / nxy ; ii = pp % nx ; jj = (pp-kk*nxy)/nx ;
88 kp = kk+1 ; if( kp >= nz ) kp = nz-1 ;
89 km = kk-1 ; if( km < 0 ) km = 0 ;
90 jp = jj+1 ; if( jp >= ny ) jp = ny-1 ;
91 jm = jj-1 ; if( jm < 0 ) jm = 0 ;
92 ip = ii+1 ; if( ip >= nx ) ip = nx-1 ;
93 im = ii-1 ; if( im < 0 ) im = 0 ;
94 if( ISEDGE(ip,jj,kk) + ISEDGE(im,jj,kk) +
95 ISEDGE(ii,jp,kk) + ISEDGE(ii,jm,kk) +
96 ISEDGE(ii,jj,kp) + ISEDGE(ii,jj,km) > 2 ){
97 qmar[pp] = 0.0f ; nclip++ ;
98 }
99 }
100 for( pp=0 ; pp < nxyz ; pp++ ){
101 if( imar[pp] < vclip ) continue ;
102 kk = pp / nxy ; ii = pp % nx ; jj = (pp-kk*nxy)/nx ;
103 kp = kk+1 ; if( kp >= nz ) kp = nz-1 ;
104 km = kk-1 ; if( km < 0 ) km = 0 ;
105 jp = jj+1 ; if( jp >= ny ) jp = ny-1 ;
106 jm = jj-1 ; if( jm < 0 ) jm = 0 ;
107 ip = ii+1 ; if( ip >= nx ) ip = nx-1 ;
108 im = ii-1 ; if( im < 0 ) im = 0 ;
109 if( ISEDGE(ip,jj,kk) + ISEDGE(im,jj,kk) +
110 ISEDGE(ii,jp,kk) + ISEDGE(ii,jm,kk) +
111 ISEDGE(ii,jj,kp) + ISEDGE(ii,jj,km) > 4 ){
112 qmar[pp] = 0.0f ; nclip++ ;
113 }
114 }
115 } while(nclip > 0) ;
116
117 free(imar) ; return ;
118 }
119
120 /*---------------------------------------------------------------------------*/
121 #undef SWAP
122 #define SWAP(x,y) (temp=x,x=y,y=temp)
123 #undef SORT2
124 #define SORT2(a,b) if(a>b) SWAP(a,b)
125
126 /*----- fast median-of-7 [mangles input array] -----*/
127
median7(float * p)128 static INLINE float median7(float *p)
129 {
130 register float temp ;
131 SORT2(p[0],p[1]) ; SORT2(p[4],p[5]) ; SORT2(p[1],p[2]) ;
132 SORT2(p[5],p[6]) ; SORT2(p[0],p[1]) ; SORT2(p[4],p[5]) ;
133 SORT2(p[0],p[4]) ; SORT2(p[2],p[6]) ; SORT2(p[1],p[3]) ;
134 SORT2(p[3],p[5]) ; SORT2(p[1],p[3]) ; SORT2(p[2],p[3]) ;
135 SORT2(p[3],p[4]) ; SORT2(p[2],p[3]) ; return(p[3]) ;
136 }
137
138 /*---------------------------------------------------------------------------*/
139 /* Shrink a 3D image down by a factor of 2 in all dimensions,
140 by taking the median of each point and its 6 nearest neighbors. */
141
142 #undef FSUB
143 #define FSUB(far,i,j,k,ni,nij) far[(i)+(j)*(ni)+(k)*(nij)]
144
mri_double_down(MRI_IMAGE * fim)145 MRI_IMAGE * mri_double_down( MRI_IMAGE *fim )
146 {
147 MRI_IMAGE *gim=NULL ;
148 float *far , *gar , par[7] ;
149 int nxf,nyf,nzf , nxg,nyg,nzg , nxyf,nxyg , ii,jj,kk ;
150 int iuu,iup,ium , juu,jum,jup , kuu,kum,kup ;
151
152 ENTRY("mri_double_down") ;
153
154 if( fim == NULL ) RETURN(NULL) ;
155
156 /* process non-float image? */
157
158 if( fim->kind != MRI_float ){
159 MRI_IMAGE *qim = mri_to_float(fim) ;
160 gim = mri_double_down(qim) ; mri_free(qim) ; RETURN(gim) ;
161 }
162
163 /* f=input g=output */
164
165 nxf = fim->nx ; nyf = fim->ny ; nzf = fim->nz ;
166 nxg = nxf / 2 ; if( nxg < 1 ) nxg = 1 ;
167 nyg = nyf / 2 ; if( nyg < 1 ) nyg = 1 ;
168 nzg = nzf / 2 ; if( nzg < 1 ) nzg = 1 ;
169
170 nxyf = nxf*nyf ; nxyg = nxg*nyg ;
171
172 gim = mri_new_vol(nxg,nyg,nzg,MRI_float) ;
173 gar = MRI_FLOAT_PTR(gim) ;
174 far = MRI_FLOAT_PTR(fim) ;
175
176 /* for each output voxel (gim), take the median of the
177 corresponding input voxel and its 6 nearest neighbors */
178
179 for( kk=0 ; kk < nzg ; kk++ ){
180 kuu = 2*kk ; kum = kuu-1 ; if( kum < 0 ) kum = 0 ;
181 kup = kuu+1 ; if( kup >= nzf ) kup = nzf-1 ;
182 for( jj=0 ; jj < nyg ; jj++ ){
183 juu = 2*jj ; jum = juu-1 ; if( jum < 0 ) jum = 0 ;
184 jup = juu+1 ; if( jup >= nyf ) jup = nyf-1 ;
185 for( ii=0 ; ii < nxg ; ii++ ){
186 iuu = 2*ii ; ium = iuu-1 ; if( ium < 0 ) ium = 0 ;
187 iup = iuu+1 ; if( iup >= nxf ) iup = nxf-1 ;
188 par[0] = FSUB(far,iuu,juu,kuu,nxf,nxyf) ; /* load par */
189 par[1] = FSUB(far,ium,juu,kuu,nxf,nxyf) ; /* with the */
190 par[2] = FSUB(far,iup,juu,kuu,nxf,nxyf) ; /* 7 values */
191 par[3] = FSUB(far,iuu,jum,kuu,nxf,nxyf) ; /* at and */
192 par[4] = FSUB(far,iuu,jup,kuu,nxf,nxyf) ; /* around */
193 par[5] = FSUB(far,iuu,juu,kum,nxf,nxyf) ; /* the voxel */
194 par[6] = FSUB(far,iuu,juu,kup,nxf,nxyf) ;
195 FSUB(gar,ii,jj,kk,nxg,nxyg) = median7(par) ;
196 }}}
197
198 RETURN(gim) ;
199 }
200
201 /*---------------------------------------------------------------------------*/
202 /* Expand a 3D image by a factor of 2 in all directions, by averaging.
203 Plus 1 more in x if xadd is nonzero, etc.
204 *//*-------------------------------------------------------------------------*/
205
mri_double_up(MRI_IMAGE * fim,int xadd,int yadd,int zadd)206 MRI_IMAGE * mri_double_up( MRI_IMAGE *fim , int xadd,int yadd,int zadd )
207 {
208 MRI_IMAGE *gim ;
209 int nxf,nyf,nzf , nxg,nyg,nzg , nxyf,nxyg , ii,jj,kk , im,jm,km,ip,jp,kp ;
210 float *far , *gar ;
211
212 ENTRY("mri_double_up") ;
213
214 if( fim == NULL ) RETURN(NULL) ;
215
216 /* process a non-float image? */
217
218 if( fim->kind != MRI_float ){
219 MRI_IMAGE *qim = mri_to_float(fim) ;
220 gim = mri_double_up(qim,xadd,yadd,zadd) ; mri_free(qim) ; RETURN(gim) ;
221 }
222
223 /* f=input g=output */
224
225 nxf = fim->nx ; nyf = fim->ny ; nzf = fim->nz ;
226
227 nxg = (nxf == 1) ? 1 : (2*nxf+(xadd != 0)) ;
228 nyg = (nyf == 1) ? 1 : (2*nyf+(yadd != 0)) ;
229 nzg = (nzf == 1) ? 1 : (2*nzf+(zadd != 0)) ;
230
231 nxyf = nxf*nyf ; nxyg = nxg*nyg ;
232
233 gim = mri_new_vol(nxg,nyg,nzg,MRI_float) ;
234 gar = MRI_FLOAT_PTR(gim) ;
235 far = MRI_FLOAT_PTR(fim) ;
236
237 /* for even output indexes, use the corresponding index in the input;
238 for odd output indexes, use the neighboring indexes in the input. */
239
240 for( kk=0 ; kk < nzg ; kk++ ){
241 kp = km = kk/2 ; if( kp >= nzf ) kp = km = nzf-1 ;
242 if( kk%2 ){ kp++; if( kp >= nzf ) kp = nzf-1; }
243 for( jj=0 ; jj < nyg ; jj++ ){
244 jp = jm = jj/2 ; if( jp >= nyf ) jp = jm = nyf-1 ;
245 if( jj%2 ){ jp++; if( jp >= nyf ) jp = nyf-1; }
246 for( ii=0 ; ii < nxg ; ii++ ){
247 ip = im = ii/2 ; if( ip >= nxf ) ip = im = nxf-1 ;
248 if( ii%2 ){ ip++; if( ip >= nxf ) ip = nxf-1; }
249 FSUB(gar,ii,jj,kk,nxg,nxyg) =
250 0.125f * ( FSUB(far,im,jm,km,nxf,nxyf) + FSUB(far,ip,jm,km,nxf,nxyf)
251 +FSUB(far,im,jp,km,nxf,nxyf) + FSUB(far,ip,jp,km,nxf,nxyf)
252 +FSUB(far,im,jm,kp,nxf,nxyf) + FSUB(far,ip,jm,kp,nxf,nxyf)
253 +FSUB(far,im,jp,kp,nxf,nxyf) + FSUB(far,ip,jp,kp,nxf,nxyf) ) ;
254 }}}
255
256 RETURN(gim) ;
257 }
258
259 /*---------------------------------------------------------------------------*/
260 /* Find the percentile perc (0..100) in a radius of vrad voxels.
261 (Not really used in this program, but here it is if you want it.) */
262
mri_local_percentile(MRI_IMAGE * fim,float vrad,float perc)263 MRI_IMAGE * mri_local_percentile( MRI_IMAGE *fim , float vrad , float perc )
264 {
265 MRI_IMAGE *aim , *bim , *cim , *dim ;
266 float *aar , *bar , *car , *dar , *nbar ;
267 byte *ams , *bms ;
268 MCW_cluster *nbhd ;
269 int ii,jj,kk , nbar_num , nx,ny,nz,nxy ;
270 float fq,val ; int qq,qp,qm ;
271
272 ENTRY("mri_local_percentile") ;
273
274 if( fim == NULL || vrad < 4.0f || perc < 0.0f || perc > 100.0f ) RETURN(NULL) ;
275
276 /* compute automask */
277
278 aim = mri_to_float(fim) ; aar = MRI_FLOAT_PTR(aim) ;
279 ams = mri_automask_image(aim) ;
280 if( ams == NULL ){ mri_free(aim) ; RETURN(NULL) ; }
281
282 /* apply automask to image copy */
283
284 for( ii=0 ; ii < aim->nvox ; ii++ ) if( ams[ii] == 0 ) aar[ii] = 0.0f ;
285 free(ams) ;
286
287 /* shrink image by 2 for speedup */
288
289 if( do_double ){
290 bim = mri_double_down(aim) ; bar = MRI_FLOAT_PTR(bim) ; mri_free(aim) ;
291 } else {
292 bim = aim ; bar = MRI_FLOAT_PTR(bim) ;
293 }
294 bms = (byte *)malloc(sizeof(byte)*bim->nvox) ;
295 for( ii=0 ; ii < bim->nvox ; ii++ ) bms[ii] = (bar[ii] != 0.0f) ;
296
297 /* neighborhood has 1/2 radius in the shrunken volume */
298
299 if( do_double )
300 nbhd = MCW_spheremask( 1.0f,1.0f,1.0f , 0.5f*vrad+0.001f ) ;
301 else
302 nbhd = MCW_spheremask( 1.0f,1.0f,1.0f , vrad ) ;
303
304 nbar = (float *)malloc(sizeof(float)*nbhd->num_pt) ;
305
306 cim = mri_new_conforming(bim,MRI_float) ; car = MRI_FLOAT_PTR(cim) ;
307 SetSearchAboutMaskedVoxel(1) ;
308
309 nx = bim->nx ; ny = bim->ny ; nz = bim->nz ; nxy = nx*ny ;
310
311 /* for each voxel:
312 extract neighborhood array, sort it, get result */
313
314 for( kk=0 ; kk < nz ; kk++ ){
315 for( jj=0 ; jj < ny ; jj++ ){
316 for( ii=0 ; ii < nx ; ii++ ){
317 nbar_num = mri_get_nbhd_array( bim,bms , ii,jj,kk , nbhd,nbar ) ;
318 if( nbar_num < 1 ){
319 val = 0.0f ;
320 } else {
321 qsort_float(nbar_num,nbar) ;
322 if( nbar_num == 1 || perc <= 0.000001f ){
323 val = nbar[0] ;
324 } else if( perc >= 99.9999f ){
325 val = nbar[nbar_num-1] ;
326 } else {
327 fq = (0.01f*perc)*nbar_num ;
328 qq = (int)fq ; qp = qq+1 ; if( qp == nbar_num ) qp = qq ;
329 qm = qq-1 ; if( qm < 0 ) qm = 0 ;
330 val = 0.3333333f * ( nbar[qm] + nbar[qq] + nbar[qp] ) ;
331 }
332 }
333 FSUB(car,ii,jj,kk,nx,nxy) = val ;
334 }}}
335
336 mri_free(bim) ; free(bms) ; free(nbar) ; KILL_CLUSTER(nbhd) ;
337
338 if( do_double ){
339 dim = mri_double_up( cim , fim->nx%2 , fim->ny%2 , fim->nz%2 ) ;
340 mri_free(cim) ;
341 } else {
342 dim = cim ;
343 }
344
345 RETURN(dim) ;
346 }
347
348 /*---------------------------------------------------------------------------*/
349
vstep_print(void)350 static void vstep_print(void) /* for -verb voxel loop message */
351 {
352 static char xx[10] = "0123456789" ; static int vn=0 ;
353 fprintf(stderr , "%c" , xx[vn%10] ) ;
354 if( vn%10 == 9) fprintf(stderr,".") ;
355 vn++ ;
356 }
357
358 /*---------------------------------------------------------------------------*/
359 /* Get the local (vrad radius) average value between percentiles p1 and p2. */
360
mri_local_percmean(MRI_IMAGE * fim,float vrad,float p1,float p2)361 MRI_IMAGE * mri_local_percmean( MRI_IMAGE *fim , float vrad , float p1, float p2 )
362 {
363 MRI_IMAGE *aim , *bim , *cim , *dim ;
364 float *aar , *bar , *car , *dar ;
365 byte *ams , *bms ;
366 MCW_cluster *nbhd ;
367 int ii , nx,ny,nz,nxy,nxyz ;
368 float vbot=0.0f ;
369
370 ENTRY("mri_local_percmean") ;
371
372 if( p1 > p2 ){ float val = p1; p1 = p2; p2 = val; }
373
374 if( fim == NULL || vrad < 4.0f || p1 < 0.0f || p2 > 100.0f ) RETURN(NULL) ;
375
376 /* just one percentile? */
377
378 if( p1 == p2 ) RETURN( mri_local_percentile(fim,vrad,p1) ) ;
379
380 if( verb ) fprintf(stderr,"A") ;
381
382 /* create automask of copy of input image */
383
384 aim = mri_to_float(fim) ; aar = MRI_FLOAT_PTR(aim) ;
385 ams = mri_automask_image(aim) ;
386 if( ams == NULL ){ mri_free(aim) ; RETURN(NULL) ; }
387
388 /* apply automask to copy of input image */
389
390 for( ii=0 ; ii < aim->nvox ; ii++ ) if( ams[ii] == 0 ) aar[ii] = 0.0f ;
391 free(ams) ;
392
393 if( ampref != NULL ){ /* 20 Nov 2018 */
394 STATUS("output -amsave") ;
395 outset = EDIT_empty_copy( inset ) ;
396 EDIT_dset_items( outset ,
397 ADN_prefix , ampref ,
398 ADN_nvals , 1 ,
399 ADN_ntt , 0 ,
400 ADN_none ) ;
401 EDIT_substitute_brick( outset , 0 , MRI_float , aar ) ;
402 DSET_write(outset) ; outset = NULL ;
403 }
404
405 /* shrink image by 2 for speed */
406
407 if( verb && do_double ) fprintf(stderr,"D") ;
408
409 if( do_double ){
410 bim = mri_double_down(aim) ; bar = MRI_FLOAT_PTR(bim) ; mri_free(aim) ;
411 } else {
412 bim = aim ; bar = MRI_FLOAT_PTR(bim) ;
413 }
414
415 bms = (byte *)malloc(sizeof(byte)*bim->nvox) ;
416 for( ii=0 ; ii < bim->nvox ; ii++ ) bms[ii] = (bar[ii] != 0.0f) ;
417
418 if( !USE_ALL_VALS ){
419 vbot = 0.00666f * mri_max(bim) ;
420 }
421
422 /* create neighborhood mask (1/2 radius in the shrunken copy) */
423
424 if( do_double )
425 nbhd = MCW_spheremask( 1.0f,1.0f,1.0f , 0.5f*vrad+0.001f ) ;
426 else
427 nbhd = MCW_spheremask( 1.0f,1.0f,1.0f , vrad ) ;
428
429 cim = mri_new_conforming(bim,MRI_float) ; car = MRI_FLOAT_PTR(cim) ;
430 SetSearchAboutMaskedVoxel(1) ;
431
432 nx = bim->nx ; ny = bim->ny ; nz = bim->nz ; nxy = nx*ny ; nxyz = nxy*nz ;
433
434 /* for each output voxel,
435 extract neighborhood array, sort it, average desired range.
436 Since this is the slowest part of the code, it is now OpenMP-ized. */
437
438 #ifndef USE_OMP /* old serial code */
439 { int vvv,vstep , ii,jj,kk , nbar_num ; float val , *nbar ;
440 nbar = (float *)malloc(sizeof(float)*nbhd->num_pt) ;
441 vstep = (verb) ? nxyz/50 : 0 ;
442 if( vstep ) fprintf(stderr,"\n + Voxel loop: ") ;
443 for( vvv=kk=0 ; kk < nz ; kk++ ){
444 for( jj=0 ; jj < ny ; jj++ ){
445 for( ii=0 ; ii < nx ; ii++,vvv++ ){
446 if( vstep && vvv%vstep == vstep-1 ) vstep_print() ;
447 nbar_num = mri_get_nbhd_array( bim,bms , ii,jj,kk , nbhd,nbar ) ;
448 if( nbar_num < 1 ){ /* no data */
449 val = 0.0f ;
450 } else {
451 qsort_float(nbar_num,nbar) ; /* sort */
452 if( nbar_num == 1 ){ /* stoopid case */
453 val = nbar[0] ;
454 } else { /* average values from p1 to p2 percentiles */
455 int q1,q2,qq , qb;
456 if( !USE_ALL_VALS ){ /* Ignore tiny values [17 May 2016] */
457 for( qb=0 ; qb < nbar_num && nbar[qb] <= vbot ; qb++ ) ; /*nada*/
458 if( qb == nbar_num ){
459 val = 0.0f ;
460 } else if( qb == nbar_num-1 ){
461 val = nbar[qb] ;
462 } else {
463 q1 = (int)( 0.01f*p1*(nbar_num-1-qb)) + qb; if( q1 > nbar_num-1 ) q1 = nbar_num-1;
464 q2 = (int)( 0.01f*p2*(nbar_num-1-qb)) + qb; if( q2 > nbar_num-1 ) q2 = nbar_num-1;
465 for( qq=q1,val=0.0f ; qq <= q2 ; qq++ ) val += nbar[qq] ;
466 val /= (q2-q1+1.0f) ;
467 }
468 } else { /* Use all values [the olden way] */
469 q1 = (int)( 0.01f*p1*(nbar_num-1) ) ; /* p1 location */
470 q2 = (int)( 0.01f*p2*(nbar_num-1) ) ; /* p2 location */
471 for( qq=q1,val=0.0f ; qq <= q2 ; qq++ ) val += nbar[qq] ;
472 val /= (q2-q1+1.0f) ;
473 }
474 }
475 }
476 FSUB(car,ii,jj,kk,nx,nxy) = val ;
477 }}}
478 free(nbar) ;
479 if( vstep ) fprintf(stderr,"!") ;
480 }
481 #else /* new parallel code [06 Mar 2013 = Snowquestration Day!] */
482 AFNI_OMP_START ;
483 if( verb ) fprintf(stderr,"V") ;
484 #pragma omp parallel
485 { int vvv , ii,jj,kk,qq , nbar_num ; float val , *nbar ;
486 nbar = (float *)malloc(sizeof(float)*nbhd->num_pt) ;
487 #pragma omp for
488 for( vvv=0 ; vvv < nxyz ; vvv++ ){
489 ii = vvv % nx ; kk = vvv / nxy ; jj = (vvv-kk*nxy) / nx ;
490 nbar_num = mri_get_nbhd_array( bim,bms , ii,jj,kk , nbhd,nbar ) ;
491 if( nbar_num < 1 ){ /* no data */
492 val = 0.0f ;
493 } else {
494 qsort_float(nbar_num,nbar) ; /* sort */
495 if( nbar_num == 1 ){ /* stoopid case */
496 val = nbar[0] ;
497 } else { /* average values from p1 to p2 percentiles */
498 int q1,q2,qq , qb;
499 if( !USE_ALL_VALS ){ /* Ignore tiny values [17 May 2016] */
500 for( qb=0 ; qb < nbar_num && nbar[qb] <= vbot ; qb++ ) ; /*nada*/
501 if( qb == nbar_num ){
502 val = 0.0f ;
503 } else if( qb == nbar_num-1 ){
504 val = nbar[qb] ;
505 } else {
506 q1 = (int)( 0.01f*p1*(nbar_num-1-qb)) + qb; if( q1 > nbar_num-1 ) q1 = nbar_num-1;
507 q2 = (int)( 0.01f*p2*(nbar_num-1-qb)) + qb; if( q2 > nbar_num-1 ) q2 = nbar_num-1;
508 for( qq=q1,val=0.0f ; qq <= q2 ; qq++ ) val += nbar[qq] ;
509 val /= (q2-q1+1.0f) ;
510 }
511 } else { /* Use all values [the olden way] */
512 q1 = (int)( 0.01f*p1*(nbar_num-1) ) ; /* p1 location */
513 q2 = (int)( 0.01f*p2*(nbar_num-1) ) ; /* p2 location */
514 for( qq=q1,val=0.0f ; qq <= q2 ; qq++ ) val += nbar[qq] ;
515 val /= (q2-q1+1.0f) ;
516 }
517 if( verb && vvv%66666==0 ) fprintf(stderr,".") ;
518 }
519 }
520 car[vvv] = val ;
521 }
522 free(nbar) ;
523 } /* end parallel code */
524 AFNI_OMP_END ;
525 #endif
526
527 mri_free(bim) ; free(bms) ; KILL_CLUSTER(nbhd) ;
528
529 /* expand output image back to original size */
530
531 if( do_double ){
532 dim = mri_double_up( cim , fim->nx%2 , fim->ny%2 , fim->nz%2 ) ;
533 mri_free(cim) ;
534 } else {
535 dim = cim ;
536 }
537
538 if( verb ) fprintf(stderr,"U") ;
539
540 RETURN(dim) ;
541 }
542
543 /*---------------------------------------------------------------------------*/
544
545 /* White Matter uniformization */
546
mri_WMunifize(MRI_IMAGE * fim)547 MRI_IMAGE * mri_WMunifize( MRI_IMAGE *fim )
548 {
549 MRI_IMAGE *pim, *gim ; float *par,*gar , pval ; int ii,nsq ;
550
551 ENTRY("mri_WMunifize") ;
552
553 if( fim == NULL ) RETURN(NULL) ;
554
555 /* create image of local high-intensity value */
556
557 if( do_double ) do_double = (fim->nvox > 1000000) ; /* duplo? */
558 #if 0
559 INFO_message("do_double = %d",do_double) ;
560 #endif
561
562 pim = mri_local_percmean( fim , Uprad , Upbot,Uptop ) ;
563 if( pim == NULL ) RETURN(NULL) ;
564 gim = mri_to_float(fim) ; /* output = copy of input image */
565 gar = MRI_FLOAT_PTR(gim) ;
566 par = MRI_FLOAT_PTR(pim) ; /* scaling image */
567
568 /* scale output by the pim created above */
569
570 for( nsq=ii=0 ; ii < gim->nvox ; ii++ ){
571 pval = par[ii] = (par[ii] <= 0.0f) ? 0.0f : PKVAL / par[ii] ;
572 gar[ii] = gar[ii] * pval ;
573 if( do_squash && gar[ii] > WMCUT ){ /* top clipping [30 Jan 2019] */
574 gar[ii] = WMCUT + WMSCL*tanhf((gar[ii]-WMCUT)/WMSCL) ; nsq++ ;
575 }
576 }
577
578 if( verb ) fprintf(stderr,"W") ;
579 if( verb && nsq > 0 ) fprintf(stderr,"[s%d]",nsq) ;
580
581 if( sclim != NULL ) mri_free(sclim) ; /* 25 Jun 2013: save scale image */
582 sclim = pim ;
583 RETURN(gim) ;
584 }
585
586 /*---------------------------------------------------------------------------*/
587 /* Gray Matter normalization */
588
mri_GMunifize(MRI_IMAGE * gim)589 void mri_GMunifize( MRI_IMAGE *gim )
590 {
591 float *gar=MRI_FLOAT_PTR(gim) , *pval , pupper,plower,pmid,pfac ;
592 int ii,jj , npval , nvox=gim->nvox ;
593
594 ENTRY("mri_GMunifize") ;
595
596 /* extract all values above the WM-unifized peak value */
597
598 for( npval=ii=0 ; ii < nvox ; ii++ )
599 if( gar[ii] > PKVAL ) npval++ ;
600 if( npval < 111 ) EXRETURN ; /* 1/6 of being beastly bad */
601
602 pval = (float *)malloc(sizeof(float)*npval) ;
603 for( ii=jj=0 ; ii < nvox ; ii++ )
604 if( gar[ii] > PKVAL ) pval[jj++] = gar[ii] ;
605
606 /* get the median of these large values */
607
608 pupper = qmed_float(npval,pval) ; free(pval) ;
609
610 /* reflect below the peak value to get the upper cutoff for GM */
611
612 pupper = PKVAL - 1.987654321f * (pupper-PKVAL) ;
613
614 /* set the lower cutoff for GM from the AFNI auto-clip level */
615
616 plower = THD_cliplevel(gim,0.4321f) ;
617
618 /* extract all values between these 2 cutoffs */
619
620 for( npval=ii=0 ; ii < nvox ; ii++ )
621 if( gar[ii] >= plower && gar[ii] <= pupper) npval++ ;
622 if( npval < 111 ) EXRETURN ; /* badly bad */
623
624 pval = (float *)malloc(sizeof(float)*npval) ;
625 for( ii=jj=0 ; ii < nvox ; ii++ )
626 if( gar[ii] >= plower && gar[ii] <= pupper) pval[jj++] = gar[ii] ;
627
628 /* compute the median of these intermediate-value 'GM' voxels */
629
630 pmid = qmed_float(npval,pval) ; free(pval) ;
631
632 /* scale globally to put this pmid value at a standard value for GM */
633
634 pfac = (PKVAL-PKMID) / (PKVAL-pmid) ;
635
636 plower *= 0.333f ;
637 for( ii=0 ; ii < nvox ; ii++ ){
638 if( gar[ii] >= plower ){
639 gar[ii] = pfac * (gar[ii]-PKVAL) + PKVAL ;
640 if( gar[ii] < 0.0f ) gar[ii] = 0.0f ;
641 } else {
642 gar[ii] = 0.0f ;
643 }
644 }
645
646 if( verb ) fprintf(stderr,"G") ;
647 EXRETURN ;
648 }
649
650 /*---------------------------------------------------------------------------*/
651 /*---------------------------------------------------------------------------*/
652
main(int argc,char * argv[])653 int main( int argc , char *argv[] )
654 {
655 int iarg , ct , do_GM=0 ;
656 int do_T2=0 ; float T2_uperc=98.5f ; byte *T2_mask=NULL ;
657 char *prefix = "Unifized" ;
658 MRI_IMAGE *imin , *imout ;
659 float clfrac=0.2f ;
660 int do_mask = 1 ; /* 08 Aug 2018 = 8/8/18 */
661
662 AFNI_SETUP_OMP(0) ; /* 24 Jun 2013 */
663
664 if( argc < 2 || strcmp(argv[1],"-help") == 0 ){
665 printf("\n"
666 "Usage: 3dUnifize [options] inputdataset\n"
667 "\n"
668 "* The input dataset is supposed to be a T1-weighted volume,\n"
669 " possibly already skull-stripped (e.g., via 3dSkullStrip).\n"
670 " ++ However, this program can be a useful step to take BEFORE\n"
671 " 3dSkullStrip, since the latter program can fail if the input\n"
672 " volume is strongly shaded -- 3dUnifize will (mostly) remove\n"
673 " such shading artifacts.\n"
674 "\n"
675 "* The output dataset has the white matter (WM) intensity approximately\n"
676 " uniformized across space, and scaled to peak at about 1000.\n"
677 "\n"
678 "* The output dataset is always stored in float format!\n"
679 "\n"
680 "* If the input dataset has more than 1 sub-brick, only sub-brick\n"
681 " #0 will be processed!\n"
682 "\n"
683 "* If you have a lot of tissue inferior to the brain, you might have\n"
684 " to cut it off (using 3dZeropad -I -xxx to cut off the most inferior\n"
685 " xxx slices -- where you pick the number xxx visually), before\n"
686 " using 3dUnifize.\n"
687 "\n"
688 "* Want to correct EPI datasets for nonuniformity?\n"
689 " You can try the new and experimental [Mar 2017] '-EPI' option.\n"
690 "\n"
691 "* Method: Obi-Wan's personal variant of Ziad's sneaky trick.\n"
692 " (If you want to know what his trick is, you'll have to ask him, or\n"
693 " read Obi-Wan's source code [which is a world of ecstasy and exaltation],\n"
694 " or just read all the way to the end of this help output.)\n"
695 "\n"
696 "* The principal motive for this program is for use in an image\n"
697 " registration script, and it may or may not be useful otherwise.\n"
698 "\n"
699 "* This program replaces the older (and very different) 3dUniformize,\n"
700 " which is no longer maintained and may sublimate at any moment.\n"
701 " (In other words, we do not recommend the use of 3dUniformize.)\n"
702 "\n"
703 "--------\n"
704 "Options:\n"
705 "--------\n"
706 "\n"
707 " -prefix pp = Use 'pp' for prefix of output dataset.\n"
708 "\n"
709 " -input dd = Alternative way to specify input dataset.\n"
710 "\n"
711 " -T2 = Treat the input as if it were T2-weighted, rather than\n"
712 " T1-weighted. This processing is done simply by inverting\n"
713 " the image contrast, processing it as if that result were\n"
714 " T1-weighted, and then re-inverting the results.\n"
715 " ++ This option is NOT guaranteed to be useful for anything!\n"
716 " ++ Of course, nothing in AFNI comes with a guarantee :-)\n"
717 " ++ If you want to be REALLY sneaky, giving this option twice\n"
718 " will skip the second inversion step, so the result will\n"
719 " look like a T1-weighted volume (except at the edges and\n"
720 " near blood vessels).\n"
721 " ++ Might be useful for skull-stripping T2-weighted datasets.\n"
722 " ++ Don't try the '-T2 -T2' trick on FLAIR-T2-weighted datasets.\n"
723 " The results aren't pretty!\n"
724 "\n"
725 " -GM = Also scale to unifize 'gray matter' = lower intensity voxels\n"
726 " (to aid in registering images from different scanners).\n"
727 " ++ For many datasets (especially those created by averaging),\n"
728 " using '-GM' will increase the WM-GM contrast somewhat;\n"
729 " however, that depends on the original WM-GM contrast.\n"
730 " ++ This option is recommended for use with 3dQwarp when\n"
731 " aligning 2 T1-weighted volumes, in order to make the\n"
732 " WM-GM contrast about the same for the datasets, even\n"
733 " if they don't come from the same scanner/pulse-sequence.\n"
734 " ++ Note that standardizing the contrasts with 3dUnifize will help\n"
735 " 3dQwarp match the source dataset to the base dataset. If you\n"
736 " later want the original source dataset to be warped, you can\n"
737 " do so using the 3dNwarpApply program.\n"
738 " ++ In particular, the template dataset MNI152_2009_template_SSW.nii.gz\n"
739 " (supplied with AFNI) has been treated with '-GM'. This dataset\n"
740 " is the one used by the @SSwarper script, so that script applies\n"
741 " 3dUnifize with this '-GM' option to help with the alignment.\n"
742 "\n"
743 " -Urad rr = Sets the radius (in voxels) of the ball used for the sneaky trick.\n"
744 " ++ Default value is %.1f, and should be changed proportionally\n"
745 " if the dataset voxel size differs significantly from 1 mm.\n"
746 "\n"
747 " -ssave ss = Save the scale factor used at each voxel into a dataset 'ss'.\n"
748 " ++ This is the white matter scale factor, and does not include\n"
749 " the factor from the '-GM' option (if that was included).\n"
750 " ++ The input dataset is multiplied by the '-ssave' image\n"
751 " (voxel-wise) to get the WM-unifized image.\n"
752 " ++ Another volume (with the same grid dimensions) could be\n"
753 " scaled the same way using 3dcalc, if that is needed.\n"
754 " ++ This saved scaled factor does NOT include any GM scaling :(\n"
755 "\n"
756 " -amsave aa = Save the automask-ed input dataset.\n"
757 " ++ This option and the previous one are used mostly for\n"
758 " figuring out why something peculiar happened, and are\n"
759 " otherwise useless.\n"
760 "\n"
761 " -quiet = Don't print the fun fun fun progress messages (but whyyyy?).\n"
762 " ++ For the curious, the codes used during this printout are:\n"
763 " A = Automask\n"
764 " D = Duplo down (process a half-size volume)\n"
765 " V = Voxel-wise histograms to get local scale factors\n"
766 " U = duplo Up (convert local scale factors to full-size volume)\n"
767 " W = multiply by White matter factors\n"
768 " G = multiply by Gray matter factors [cf -GM option]\n"
769 " I = contrast inversion [cf -T2 option]\n"
770 " M = compute median volume [cf -EPI option]\n"
771 " E = compute scaled EPI datasets [cf -EPI option]\n"
772 " [sXXX] = XXX voxel values were 'squashed' [cf -nosquash]\n"
773 " ++ 'Duplo down' means to scale the input volume to be half the\n"
774 " grid size in each direction for speed when computing the\n"
775 " voxel-wise histograms. The sub-sampling is done using the\n"
776 " median of the central voxel value and its 6 nearest neighbors.\n"
777 "\n"
778 " -noduplo = Do NOT use the 'duplo down' step; this can be useful for lower\n"
779 " resolution datasets.\n"
780 " ++ If a dataset has less than 1 million voxels in a 3D volume,\n"
781 " 'duplo down' will not be used in any case.\n"
782 "\n"
783 " -EPI = Assume the input dataset is a T2 (or T2*) weighted EPI time\n"
784 " series. After computing the scaling, apply it to ALL volumes\n"
785 " (TRs) in the input dataset. That is, a given voxel will be\n"
786 " scaled by the same factor at each TR.\n"
787 " ++ This option also implies '-noduplo' and '-T2'.\n"
788 " ++ This option turns off '-GM' if you turned it on.\n"
789 " -->>++ This option is experimental; check your results!\n"
790 " ++ Remember: the program tries to uniform-ize the White Matter\n"
791 " regions, so the overall appearance of the image may become\n"
792 " less uniform, especially if it was fairly uniform already.\n"
793 " ++ For most purposes in AFNI processing, uniform-izing\n"
794 " EPI datasets is not needed.\n"
795 " -- If you are having trouble getting a good result from\n"
796 " 3dAutomask, try adding the option '-clfrac 0.2'.\n"
797 " -- There is no reason to apply 3dUnifize to EPI datasets\n"
798 " that do not have significant shading artifacts.\n"
799 " -- EPI data from 7T systems might be 'improved' by 3dUnifize.\n"
800 " -- You might need to run 3dDespike before using 3dUnifize.\n"
801 "\n"
802 "------------------------------------------\n"
803 "Special options for Jedi AFNI Masters ONLY:\n"
804 "------------------------------------------\n"
805 " -rbt R b t = Specify the 3 parameters for the algorithm, as 3 numbers\n"
806 " following the '-rbt':\n"
807 " R = radius; same as given by option '-Urad' [default=%.1f]\n"
808 " b = bottom percentile of normalizing data range [default=%.1f]\n"
809 " r = top percentile of normalizing data range [default=%.1f]\n"
810 "\n"
811 " -T2up uu = Set the upper percentile point used for T2-T1 inversion.\n"
812 " The default value is 98.5 (for no good reason), and 'uu' is\n"
813 " allowed to be anything between 90 and 100 (inclusive).\n"
814 " ++ The histogram of the data is built, and the uu-th percentile\n"
815 " point value is called 'U'. The contrast inversion is simply\n"
816 " given by output_value = max( 0 , U - input_value ).\n"
817 "\n"
818 " -clfrac cc = Set the automask 'clip level fraction' to 'cc', which\n"
819 " must be a number between 0.1 and 0.9.\n"
820 " A small 'cc' means to make the initial threshold\n"
821 " for clipping (a la 3dClipLevel) smaller, which\n"
822 " will tend to make the mask larger. [default=0.1]\n"
823 " ++ [22 May 2013] The previous version of this program used a\n"
824 " clip level fraction of 0.5, which proved to be too large\n"
825 " for some users, who had images with very strong shading issues.\n"
826 " Thus, the default value for this parameter was lowered to 0.1.\n"
827 " ++ [24 May 2016] The default value for this parameter was\n"
828 " raised to 0.2, since the lower value often left a lot of\n"
829 " noise outside the head on non-3dSkullStrip-ed datasets.\n"
830 " You can still manually set -clfrac to 0.1 if you need to\n"
831 " correct for very large shading artifacts.\n"
832 " ++ If the results of 3dUnifize have a lot of noise outside the head,\n"
833 " then using '-clfrac 0.5' (or even larger) will probably help.\n"
834 " ++ If the results have 'hot spots' in the WM, also try setting\n"
835 " '-clfrac 0.5', which should help with this problem.\n"
836 #ifndef USE_ALL_VALS
837 "\n"
838 " -useall = The 'old' way of operating was to use all dataset values\n"
839 " in the local WM histogram. The 'new' way [May 2016] is to\n"
840 " only use positive values. If you want to use the 'old' way,\n"
841 " then this option is what you want.\n"
842 #endif
843 "\n"
844 " -nosquash = In Jan 2019, a change was made to 'squash' (reduce) large\n"
845 " values that sometimes occur in images - values larger than\n"
846 " typical WM intensities. For some applications, this procedure\n"
847 " does not produce images that are useful for 3dAllineate\n"
848 " (or so I was told, by people doing pig brain imaging).\n"
849 " This option will turn off the squashing step. [04 May 2020]\n"
850 " (I thought of calling it '-oink', but that would be)\n"
851 " (absurd, and as you know, Obi-Wan hates absurdity.)\n"
852 " ++ If you want to know HOW the squashing is computed,\n"
853 " you know what Obi-Wan says: 'Trust in the Source, Luke'.\n"
854 "\n"
855 "-- Feb 2013 - by Obi-Wan Unifobi\n"
856 " - can always be found at the Everest Bakery in Namche Bazaar,\n"
857 " if you have any questions about this program\n"
858 #ifdef USE_OMP
859 "\n"
860 "-- This code uses OpenMP to speed up the slowest part (voxel-wise histograms).\n"
861 #endif
862 , Uprad , Uprad , Upbot , Uptop ) ;
863
864 printf("\n"
865 "----------------------------------------------------------------------------\n"
866 "HOW IT WORKS (Ziad's sneaky trick is revealed at last! And more.)\n"
867 "----------------------------------------------------------------------------\n"
868 "The basic idea is that white matter in T1-weighted images is reasonably\n"
869 "uniform in intensity, at least when averaged over 'large-ish' regions.\n"
870 "\n"
871 "The first step is to create a local white matter intensity volume.\n"
872 "Around each voxel (inside the volume 'automask'), the ball of values\n"
873 "within a fixed radius (default=18.3 voxels) is extracted and these\n"
874 "numbers are sorted. The values in the high-intensity range of the\n"
875 "histogram (default=70%% to 80%%) are averaged. The result from this\n"
876 "step is a smooth 3D map of the 'white matter intensity' (WMI).\n"
877 "\n"
878 " [The parameters of the above process can be altered with the '-rbt' option.]\n"
879 " [For speed, the WMI map is produced on an image that is half-size in all ]\n"
880 " [directions ('Duplo down'), and then is expanded back to the full-size ]\n"
881 " [volume ('Duplo up'). The automask procedure can be somewhat controlled ]\n"
882 " [via the '-clfrac' option. The default setting is designed to deal with ]\n"
883 " [heavily shaded images, where the WMI varies by a factor of 5 or more over ]\n"
884 " [the image volume. ]\n"
885 "\n"
886 "The second step is to scale the value at every voxel location x in the input\n"
887 "volume by the factor 1000/WMI(x), so that the 'white matter intensity' is\n"
888 "now uniform-ized to be 1000 everywhere. (This is Ziad's 'trick'; it is easy,\n"
889 "works well, and doesn't require fitting some spatial model to the data: the\n"
890 "data provides its own model.)\n"
891 "\n"
892 "If the '-GM' option is used, then this scaled volume is further processed\n"
893 "to make the lower intensity values (presumably gray matter) have a contrast\n"
894 "similar to that from a collection of 3 Tesla MP-RAGE images that were\n"
895 "acquired at the NIH. (This procedure is not Ziad's fault, and should be\n"
896 "blamed on the reclusive Obi-Wan Unifobi.)\n"
897 "\n"
898 "From the WM-uniform-ized volume, the median of all values larger than 1000\n"
899 "is computed; call this value P. P-1000 represents the upward dispersion\n"
900 "of the high-intensity (white matter) voxels in the volume. This value is\n"
901 "'reflected' below 1000 to Q = 1000 - 2*(P-1000), and Q is taken to be the\n"
902 "upper bound for gray matter voxel intensities. A lower bound for gray\n"
903 "matter voxel values is estimated via the 'clip fraction' algorithm as\n"
904 "implemented in program 3dClipLevel; call this lower bound R. The median\n"
905 "of all values between R and Q is computed; call this value G, which is taken\n"
906 "to be a 'typical' gray matter voxel instensity. Then the values z in the\n"
907 "entire volume are linearly scaled by the formula\n"
908 " z_out = (1000-666)/(1000-G) * (z_in-1000) + 1000\n"
909 "so that the WM uniform-ized intensity of 1000 remains at 1000, and the gray\n"
910 "matter median intensity of G is mapped to 666. (Values z_out that end up\n"
911 "negative are set to 0; as a result, some of CSF might end up as 0.)\n"
912 "The value 666 was chosen because it gave results visually comparable to\n"
913 "various NIH-generated 3 Tesla T1-weighted datasets. (Any suggestions that\n"
914 "this value was chosen for other reasons will be treated as 'beastly'.)\n"
915 "\n"
916 "To recap: the WM uniform-ization process provides a linear scaling factor\n"
917 "that varies for each voxel ('local'), while the GM normalization process\n"
918 "uses a global linear scaling. The GM process is optional, and is simply\n"
919 "designed to make the various T1-weighted images look similar.\n"
920 "\n"
921 "-----** CAVEAT **-----\n"
922 "This procedure was primarily developed to aid in 3D registration, especially\n"
923 "when using 3dQwarp, so that the registration algorithms are trying to match\n"
924 "images that are alike. It is *NOT* intended to be used for quantification\n"
925 "purposes, such as Voxel Based Morphometry! That would better be done via\n"
926 "the 3dSeg program, which is far more complicated.\n"
927 "----------------------------------------------------------------------------\n"
928 ) ;
929 PRINT_COMPILE_DATE ; exit(0) ;
930 }
931
932 mainENTRY("3dUnifize main"); machdep(); AFNI_logger("3dUnifize",argc,argv);
933 PRINT_VERSION("3dUnifize") ;
934 ct = NI_clock_time() ;
935
936 /*-- scan command line --*/
937
938 THD_automask_set_clipfrac(0.1f) ; /* 22 May 2013 */
939 THD_automask_extclip(1) ; /* 19 Dec 2014 */
940
941 iarg = 1 ;
942 while( iarg < argc && argv[iarg][0] == '-' ){
943
944 if( strcmp(argv[iarg],"-nodouble") == 0 || strcmp(argv[iarg],"-noduplo") == 0 ){
945 do_double = 0 ; iarg++ ; continue ;
946 }
947
948 if( strcasecmp(argv[iarg],"-EPI") == 0 ){
949 do_EPI = 1 ; iarg++ ; continue ;
950 }
951
952 if( strcmp(argv[iarg],"-clfrac") == 0 || strcmp(argv[iarg],"-mfrac") == 0 ){ /* 22 May 2013 */
953 if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ;
954 clfrac = (float)strtod( argv[iarg] , NULL ) ;
955 if( clfrac < 0.1f || clfrac > 0.9f )
956 ERROR_exit("-clfrac value %f is illegal!",clfrac) ;
957 THD_automask_set_clipfrac(clfrac) ;
958 iarg++ ; continue ;
959 }
960
961 if( strcmp(argv[iarg],"-prefix") == 0 ){
962 if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ;
963 prefix = argv[iarg] ;
964 if( !THD_filename_ok(prefix) ) ERROR_exit("Illegal value after -prefix!") ;
965 iarg++ ; continue ;
966 }
967
968 if( strcmp(argv[iarg],"-ssave") == 0 ){
969 if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ;
970 sspref = strdup(argv[iarg]) ;
971 if( !THD_filename_ok(sspref) ) ERROR_exit("Illegal value after -ssave!") ;
972 iarg++ ; continue ;
973 }
974
975 if( strcmp(argv[iarg],"-amsave") == 0 ){
976 if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ;
977 ampref = strdup(argv[iarg]) ;
978 if( !THD_filename_ok(ampref) ) ERROR_exit("Illegal value after -amsave!") ;
979 iarg++ ; continue ;
980 }
981
982 if( strcmp(argv[iarg],"-input") == 0 || strcmp(argv[iarg],"-inset") == 0 ){
983 if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ;
984 if( inset != NULL ) ERROR_exit("Can't use '%s' twice" ,argv[iarg-1]) ;
985 inset = THD_open_dataset( argv[iarg] ) ;
986 CHECK_OPEN_ERROR(inset,argv[iarg]) ;
987 iarg++ ; continue ;
988 }
989
990 if( strcmp(argv[iarg],"-Urad") == 0 ){
991 if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ;
992 Uprad = (float)strtod(argv[iarg],NULL) ;
993 if( Uprad < 5.0f || Uprad > 99.0f )
994 ERROR_exit("Illegal value %f after option -Urad",Uprad) ;
995 iarg++ ; continue ;
996 }
997
998 #ifndef USE_ALL_VALS
999 if( strcmp(argv[iarg],"-useall") == 0 ){ /* 17 May 2016 */
1000 USE_ALL_VALS = 1 ; iarg++ ; continue ;
1001 }
1002 #else
1003 if( strcmp(argv[iarg],"-useall") == 0 ){
1004 WARNING_message("-useall option is disabled in this version") ;
1005 iarg++ ; continue ;
1006 }
1007 #endif
1008
1009 if( strcmp(argv[iarg],"-param") == 0 || /*--- HIDDEN OPTION ---*/
1010 strcmp(argv[iarg],"-rbt" ) == 0 ){
1011 if( ++iarg >= argc-2 ) ERROR_exit("Need 3 arguments (R pb pt) after '%s'",argv[iarg-1]) ;
1012 Uprad = (float)strtod(argv[iarg++],NULL) ;
1013 Upbot = (float)strtod(argv[iarg++],NULL) ;
1014 Uptop = (float)strtod(argv[iarg++],NULL) ;
1015 if( Uprad < 5.0f || Uprad > 99.0f ||
1016 Upbot < 30.0f || Upbot > 80.0f ||
1017 Uptop <= Upbot || Uptop > 90.0f )
1018 ERROR_exit("Illegal values (R pb pt) after '%s'",argv[iarg-4]) ;
1019 continue ;
1020 }
1021
1022 if( strcasecmp(argv[iarg],"-GM") == 0 ){
1023 do_GM++ ; iarg++ ; continue ;
1024 }
1025
1026 if( strcasecmp(argv[iarg],"-nosquash") == 0 ||
1027 strcasecmp(argv[iarg],"-oink" ) == 0 ){ /* 04 May 2020 */
1028 do_squash = 0 ; iarg++ ; continue ;
1029 }
1030
1031 if( strcasecmp(argv[iarg],"-T2") == 0 ){ /* 18 Dec 2014 */
1032 do_T2++ ; iarg++ ; continue ;
1033 }
1034
1035 if( strcasecmp(argv[iarg],"-T2up") == 0 ){ /* 18 Dec 2014 */
1036 T2_uperc = (float)strtod( argv[++iarg] , NULL ) ;
1037 if( T2_uperc < 90.0f || T2_uperc > 100.0f )
1038 ERROR_exit("-T2up value is out of range 90..100 :-(") ;
1039 iarg++ ; continue ;
1040 }
1041
1042 if( strcasecmp(argv[iarg],"-quiet") == 0 ){
1043 verb = 0 ; iarg++ ; continue ;
1044 }
1045 if( strcasecmp(argv[iarg],"-verb") == 0 ){
1046 verb++ ; iarg++ ; continue ;
1047 }
1048
1049 ERROR_exit("Unknown option: %s\n",argv[iarg]);
1050 }
1051
1052 /* read input dataset, if not already there */
1053
1054 if( inset == NULL ){
1055 if( iarg >= argc ) ERROR_exit("No dataset name on command line?\n") ;
1056 inset = THD_open_dataset( argv[iarg] ) ;
1057 CHECK_OPEN_ERROR(inset,argv[iarg]) ;
1058 }
1059
1060 if( do_EPI ){
1061 float uu ;
1062 do_T2 = 1 ; do_double = 0 ;
1063 if( do_GM ){ INFO_message("-EPI turns off -GM") ; do_GM = 0 ; }
1064 uu = cbrtf(fabsf(DSET_DX(inset)*DSET_DY(inset)*DSET_DZ(inset))) ;
1065 uu = 18.3 / uu ; if( uu < 4.14f ) uu = 4.14f ;
1066 Uprad = uu ;
1067 INFO_message("-EPI changes -Urad to %.1f voxels",Uprad) ;
1068 }
1069
1070 if( verb ) fprintf(stderr," + Pre-processing: ") ;
1071
1072 /* load input from disk */
1073
1074 DSET_load( inset ) ; CHECK_LOAD_ERROR(inset) ;
1075 if( ! do_EPI && DSET_NVALS(inset) > 1 ){
1076 WARNING_message("Only processing sub-brick #0 (out of %d)",DSET_NVALS(inset)) ;
1077 } else if( do_EPI && DSET_NVALS(inset) == 1 ){
1078 WARNING_message("-EPI was used, but only 1 volume in the input dataset") ;
1079 do_EPI = 0 ;
1080 }
1081
1082 /* make a float copy of the input brick for processing */
1083
1084 if( !do_EPI ){
1085 imin = THD_extract_float_brick(0,inset) ;
1086 if( imin == NULL ) ERROR_exit("Can't copy input dataset brick?!") ;
1087 } else {
1088 if( verb ) fprintf(stderr,"M") ;
1089 imin = THD_median_brick(inset) ;
1090 if( imin == NULL ) ERROR_exit("Can't compute median of EPI dataset?!") ;
1091 }
1092 if( ! do_EPI ) DSET_unload(inset) ;
1093
1094 #if 0
1095 THD_cliplevel_search(imin) ; exit(0) ; /* experimentation only */
1096 #endif
1097
1098 THD_automask_set_clipfrac(clfrac) ;
1099 if( verb > 1 ) THD_automask_verbose(verb-1) ;
1100
1101 /* invert T2? */
1102
1103 if( do_T2 ){
1104 if( verb ) fprintf(stderr,"I") ;
1105 T2_mask = mri_automask_image(imin) ;
1106 mri_invertcontrast_inplace( imin , T2_uperc , T2_mask ) ;
1107 }
1108
1109 /* do the actual work */
1110
1111 imout = mri_WMunifize(imin) ; /* local WM scaling */
1112 free(imin) ;
1113
1114 if( sspref != NULL && sclim != NULL ){ /* 25 Jun 2013 */
1115 STATUS("output -ssave") ;
1116 outset = EDIT_empty_copy( inset ) ;
1117 EDIT_dset_items( outset ,
1118 ADN_prefix , sspref ,
1119 ADN_nvals , 1 ,
1120 ADN_ntt , 0 ,
1121 ADN_none ) ;
1122 EDIT_substitute_brick( outset , 0 , MRI_float , MRI_FLOAT_PTR(sclim) ) ;
1123 tross_Copy_History( inset , outset ) ;
1124 tross_Make_History( "3dUnifize" , argc,argv , outset ) ;
1125 DSET_write(outset) ; outset = NULL ;
1126 }
1127
1128 /*-- Compute the EPI output here [01 Mar 2017] --*/
1129
1130 if( do_EPI ){
1131 int nvals=DSET_NVALS(inset) , nxyz=DSET_NVOX(inset) , ii,iv ;
1132 float *scar , *imar ;
1133
1134 if( sclim == NULL ) /* should never happen */
1135 ERROR_exit("Can't process EPI data: scale image missing :(") ;
1136 scar = MRI_FLOAT_PTR(sclim) ;
1137
1138 outset = EDIT_empty_copy( inset ) ; /* create shell of output */
1139 EDIT_dset_items( outset ,
1140 ADN_prefix , prefix ,
1141 ADN_datum_all , MRI_float ,
1142 ADN_brick_fac , NULL ,
1143 ADN_none ) ;
1144
1145 DSET_load(inset) ;
1146
1147 /* scale each volume */
1148
1149 if( verb ) fprintf(stderr,"E") ;
1150 for( iv=0 ; iv < nvals ; iv++ ){
1151 if( iv%100 == 47 ) fprintf(stderr,".") ;
1152 /* get input volume */
1153 imin = THD_extract_float_brick(iv,inset) ;
1154 DSET_unload_one(inset,iv) ;
1155 if( imin == NULL )
1156 ERROR_exit("Can't load sub-brick #%d of input dataset :(",iv) ;
1157 imar = MRI_FLOAT_PTR(imin) ;
1158 /* invert contrast */
1159 mri_invertcontrast_inplace( imin , T2_uperc , T2_mask ) ;
1160 /* scale */
1161 for( ii=0 ; ii < nxyz ; ii++ ) imar[ii] *= scar[ii] ;
1162 /* invert contrast back */
1163 mri_invertcontrast_inplace( imin , T2_uperc , T2_mask ) ;
1164 /* put result into output dataset */
1165 EDIT_substitute_brick( outset , iv , MRI_float , MRI_FLOAT_PTR(imin) ) ;
1166 /* toss the empty shell of the processed image */
1167 mri_clear_and_free(imin) ;
1168 }
1169 if( verb ) fprintf(stderr,"\n") ;
1170
1171 /* write the output and head back to the bakery */
1172
1173 DSET_unload(inset) ;
1174 tross_Copy_History( inset , outset ) ;
1175 tross_Make_History( "3dUnifize" , argc,argv , outset ) ;
1176 DSET_write(outset) ;
1177 WROTE_DSET(outset) ;
1178 exit(0) ;
1179
1180 } /* end of -EPI output */
1181
1182 /*-- Continue the standard (1 volume) processing --*/
1183
1184 if( sclim != NULL ){ mri_free(sclim) ; sclim = NULL ; }
1185
1186 if( imout == NULL ){ /* this is bad-ositiness */
1187 if( verb ) fprintf(stderr,"\n") ;
1188 ERROR_exit("Can't compute Unifize-d dataset for some reason :-(") ;
1189 }
1190
1191 if( do_GM ) mri_GMunifize(imout) ; /* global GM scaling */
1192
1193 if( do_T2 == 1 ){ /* re-invert T2? */
1194 if( verb ) fprintf(stderr,"I") ;
1195 mri_invertcontrast_inplace( imout , T2_uperc , T2_mask ) ;
1196 } else if( do_T2 == 2 ){ /* don't re-invert, but clip off bright edges */
1197 mri_clipedges_inplace( imout , PKVAL*1.111f , PKVAL*1.055f ) ;
1198 }
1199
1200 if( do_mask ){ /* 08 Aug 2018 */
1201 byte *mmm = mri_automask_image(imout) ;
1202 float *fff = MRI_FLOAT_PTR(imout) ;
1203 int ii , nvox=imout->nvox ;
1204 if( verb ) fprintf(stderr,"m") ;
1205 for( ii=0 ; ii < nvox ; ii++ ){ if( !mmm[ii] ) fff[ii] = 0.0f ; }
1206 free(mmm) ;
1207 }
1208
1209 if( verb ) fprintf(stderr,"\n") ;
1210
1211 /* create output dataset, and write it into the historical record */
1212
1213 outset = EDIT_empty_copy( inset ) ;
1214 EDIT_dset_items( outset ,
1215 ADN_prefix , prefix ,
1216 ADN_nvals , 1 ,
1217 ADN_ntt , 0 ,
1218 ADN_none ) ;
1219 EDIT_substitute_brick( outset , 0 , MRI_float , MRI_FLOAT_PTR(imout) ) ;
1220 tross_Copy_History( inset , outset ) ;
1221 tross_Make_History( "3dUnifize" , argc,argv , outset ) ;
1222 DSET_write(outset) ;
1223 WROTE_DSET(outset) ;
1224 DSET_delete(outset) ; DSET_delete(inset) ;
1225
1226 /* vamoose the ranch */
1227
1228 if( verb ){
1229 double cput = COX_cpu_time() ;
1230 if( cput > 0.05 )
1231 INFO_message("===== CPU time = %.1f sec Elapsed = %.1f\n",
1232 COX_cpu_time() , 0.001*(NI_clock_time()-ct) ) ;
1233 else
1234 INFO_message("===== Elapsed = %.1f sec\n", 0.001*(NI_clock_time()-ct) ) ;
1235 }
1236 exit(0) ;
1237 }
1238