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