int main( int argc , char * argv[] ) { float mrad=0.0f , fwhm=0.0f ; int nrep=1 ; char *prefix = "Polyfit" ; char *resid = NULL ; char *cfnam = NULL ; int iarg , verb=0 , do_automask=0 , nord=3 , meth=2 , do_mclip=0 ; THD_3dim_dataset *inset ; MRI_IMAGE *imout , *imin ; byte *mask=NULL ; int nvmask=0 , nmask=0 , do_mone=0 , do_byslice=0 ; MRI_IMARR *exar=NULL ; floatvec *fvit=NULL ; /* 26 Feb 2019 */ if( argc < 2 || strcasecmp(argv[1],"-help") == 0 ){ printf("\n" "Usage: 3dPolyfit [options] dataset ~1~\n" "\n" "* Fits a polynomial in space to the input dataset and outputs that fitted dataset.\n" "\n" "* You can also add your own basis datasets to the fitting mix, using the\n" " '-base' option.\n" "\n" "* You can get the fit coefficients using the '-1Dcoef' option.\n" "\n" "--------\n" "Options: ~1~\n" "--------\n" "\n" " -nord n = Maximum polynomial order (0..9) [default order=3]\n" " [n=0 is the constant 1]\n" " [n=-1 means only use volumes from '-base']\n" "\n" " -blur f = Gaussian blur input dataset (inside mask) with FWHM='f' (mm)\n" "\n" " -mrad r = Radius (voxels) of preliminary median filter of input\n" " [default is no blurring of either type; you can]\n" " [do both types (Gaussian and median), but why??]\n" " [N.B.: median blur is slower than Gaussian]\n" "\n" " -prefix pp = Use 'pp' for prefix of output dataset (the fit).\n" " [default prefix is 'Polyfit'; use NULL to skip this output]\n" "\n" " -resid rr = Use 'rr' for the prefix of the residual dataset.\n" " [default is not to output residuals]\n" "\n" " -1Dcoef cc = Save coefficients of fit into text file cc.1D.\n" " [default is not to save these coefficients]\n" "\n" " -automask = Create a mask (a la 3dAutomask)\n" " -mask mset = Create a mask from nonzero voxels in 'mset'.\n" " [default is not to use a mask, which is probably a bad idea]\n" "\n" " -mone = Scale the mean value of the fit (inside the mask) to 1.\n" " [probably this option is not useful for anything]\n" "\n" " -mclip = Clip fit values outside the rectilinear box containing the\n" " mask to the edge of that box, to avoid weird artifacts.\n" "\n" " -meth mm = Set 'mm' to 2 for least squares fit;\n" " set it to 1 for L1 fit [default method=2]\n" " [Note that L1 fitting is slower than L2 fitting!]\n" "\n" " -base bb = In addition to the polynomial fit, also use\n" " the volumes in dataset 'bb' as extra basis functions.\n" " [If you use a base dataset, then you can set nord]\n" " [to -1, to skip using any spatial polynomial fit.]\n" "\n" " -verb = Print fun and useful progress reports :-)\n" "\n" "------\n" "Notes: ~1~\n" "------\n" "* Output dataset is always stored in float format.\n" "\n" "* If the input dataset has more than 1 sub-brick, only sub-brick #0\n" " is processed. To fit more than one volume, you'll have to use a script\n" " to loop over the input sub-bricks, and then glue (3dTcat) the results\n" " together to get a final result. A simple example:\n" " #!/bin/tcsh\n" " set base = model.nii\n" " set dset = errts.nii\n" " set nval = `3dnvals $dset`\n" " @ vtop = $nval - 1\n" " foreach vv ( `count 0 $vtop` )\n" " 3dPolyfit -base \"$base\" -nord 0 -mask \"$base\" -1Dcoef QQ.$vv -prefix QQ.$vv.nii $dset\"[$vv]\"\n" " end\n" " 3dTcat -prefix QQall.nii QQ.0*.nii\n" " 1dcat QQ.0*.1D > QQall.1D\n" " \rm QQ.0*\n" " exit 0\n" "\n" "* If the '-base' dataset has multiple sub-bricks, all of them are used.\n" "\n" "* You can use the '-base' option more than once, if desired or needed.\n" "\n" "* The original motivation for this program was to fit a spatial model\n" " to a field map MRI, but that didn't turn out to be useful. Nevertheless,\n" " I make this program available to someone who might find it beguiling.\n" "\n" "* If you really want, I could allow you to put sign constraints on the\n" " fit coefficients (e.g., say that the coefficient for a given base volume\n" " should be non-negative). But you'll have to beg for this.\n" "\n" "-- Emitted by RWCox\n" ) ; PRINT_COMPILE_DATE ; exit(0) ; } /*-- startup paperwork --*/ mainENTRY("3dPolyfit main"); machdep(); AFNI_logger("3dPolyfit",argc,argv); PRINT_VERSION("3dPolyfit") ; /*-- scan command line --*/ iarg = 1 ; while( iarg < argc && argv[iarg][0] == '-' ){ if( strcasecmp(argv[iarg],"-base") == 0 ){ THD_3dim_dataset *bset ; int kk ; MRI_IMAGE *bim ; if( ++iarg >= argc ) ERROR_exit("Need argument after '-base'") ; bset = THD_open_dataset(argv[iarg]) ; CHECK_OPEN_ERROR(bset,argv[iarg]) ; DSET_load(bset) ; CHECK_LOAD_ERROR(bset) ; if( exar == NULL ) INIT_IMARR(exar) ; for( kk=0 ; kk < DSET_NVALS(bset) ; kk++ ){ bim = THD_extract_float_brick(kk,bset) ; if( bim != NULL ) ADDTO_IMARR(exar,bim) ; DSET_unload_one(bset,kk) ; } DSET_delete(bset) ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-verb") == 0 ){ verb++ ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-hermite") == 0 ){ /* 25 Mar 2013 [New Year's Day] */ mri_polyfit_set_basis("hermite") ; /* HIDDEN */ iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-byslice") == 0 ){ /* 25 Mar 2013 [New Year's Day] */ do_byslice++ ; iarg++ ; continue ; /* HIDDEN */ } if( strcasecmp(argv[iarg],"-mask") == 0 ){ THD_3dim_dataset *mset ; if( ++iarg >= argc ) ERROR_exit("Need argument after '-mask'") ; if( mask != NULL || do_automask ) ERROR_exit("Can't have two mask inputs") ; mset = THD_open_dataset(argv[iarg]) ; CHECK_OPEN_ERROR(mset,argv[iarg]) ; DSET_load(mset) ; CHECK_LOAD_ERROR(mset) ; nvmask = DSET_NVOX(mset) ; mask = THD_makemask( mset , 0 , 0.5f, 0.0f ) ; DSET_delete(mset) ; if( mask == NULL ) ERROR_exit("Can't make mask from dataset '%s'",argv[iarg]) ; nmask = THD_countmask( nvmask , mask ) ; if( nmask < 99 ) ERROR_exit("Too few voxels in mask (%d)",nmask) ; if( verb ) INFO_message("Number of voxels in mask = %d",nmask) ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-nord") == 0 ){ nord = (int)strtol( argv[++iarg], NULL , 10 ) ; if( nord < -1 || nord > 9 ) ERROR_exit("Illegal value after -nord :(") ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-meth") == 0 ){ meth = (int)strtol( argv[++iarg], NULL , 10 ) ; if( meth < 1 || meth > 2 ) ERROR_exit("Illegal value after -meth :(") ; iarg++ ; continue ; } if( strncmp(argv[iarg],"-automask",5) == 0 ){ if( mask != NULL ) ERROR_exit("Can't use -mask and -automask together!") ; do_automask++ ; iarg++ ; continue ; } if( strncmp(argv[iarg],"-mclip",5) == 0 ){ do_mclip++ ; iarg++ ; continue ; } if( strncmp(argv[iarg],"-mone",5) == 0 ){ do_mone++ ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-mrad") == 0 ){ mrad = strtod( argv[++iarg] , NULL ) ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-blur") == 0 ){ fwhm = strtod( argv[++iarg] , NULL ) ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-prefix") == 0 ){ prefix = argv[++iarg] ; if( !THD_filename_ok(prefix) ) ERROR_exit("Illegal value after -prefix :("); if( strcasecmp(prefix,"NULL") == 0 ) prefix = NULL ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-resid") == 0 ){ resid = argv[++iarg] ; if( !THD_filename_ok(resid) ) ERROR_exit("Illegal value after -resid :("); if( strcasecmp(resid,"NULL") == 0 ) resid = NULL ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-1Dcoef") == 0 ){ /* 26 Feb 2019 */ cfnam = argv[++iarg] ; if( !THD_filename_ok(cfnam) ) ERROR_exit("Illegal value after -1Dcoef :("); if( strcasecmp(cfnam,"NULL") == 0 ) cfnam = NULL ; iarg++ ; continue ; } ERROR_exit("Unknown option: %s\n",argv[iarg]); } /*--- check for blatant errors ---*/ if( iarg >= argc ) ERROR_exit("No input dataset name on command line?"); if( prefix == NULL && resid == NULL && cfnam == NULL ) ERROR_exit("-prefix and -resid and -1Dcoef are all NULL?!") ; if( do_byslice && cfnam != NULL ){ WARNING_message("-byslice does not work with -1Dcoef option :(") ; cfnam = NULL ; } if( nord < 0 && exar == NULL ) ERROR_exit("no polynomial fit AND no -base option ==> nothing to compute :(") ; /*-- read input --*/ if( verb ) INFO_message("Load input dataset") ; inset = THD_open_dataset( argv[iarg] ) ; CHECK_OPEN_ERROR(inset,argv[iarg]) ; DSET_load(inset) ; CHECK_LOAD_ERROR(inset) ; if( DSET_NVALS(inset) > 1 ) WARNING_message( "Only processing sub-brick #0 (out of %d)" , DSET_NVALS(inset) ); /* check input mask or create automask */ if( mask != NULL ){ if( nvmask != DSET_NVOX(inset) ) ERROR_exit("-mask and input datasets don't match in voxel counts :-(") ; } else if( do_automask ){ THD_automask_verbose( (verb > 1) ) ; THD_automask_extclip( 1 ) ; mask = THD_automask( inset ) ; nvmask = DSET_NVOX(inset) ; nmask = THD_countmask( nvmask , mask ) ; if( nmask < 99 ) ERROR_exit("Too few voxels in automask (%d)",nmask) ; if( verb ) ININFO_message("Number of voxels in automask = %d",nmask) ; } else { WARNING_message("3dPolyfit is running without a mask") ; } #undef GOOD #define GOOD(i) (mask == NULL || mask[i]) /* check -base input datasets */ if( exar != NULL ){ int ii,kk , nvbad=0 , nvox=DSET_NVOX(inset),nm ; float *ex , exb ; for( kk=0 ; kk < IMARR_COUNT(exar) ; kk++ ){ if( nvox != IMARR_SUBIM(exar,kk)->nvox ){ if( IMARR_SUBIM(exar,kk)->nvox != nvbad ){ ERROR_message("-base volume (%d voxels) doesn't match input dataset grid size (%d voxels)", IMARR_SUBIM(exar,kk)->nvox , nvox ) ; nvbad = IMARR_SUBIM(exar,kk)->nvox ; } } } if( nvbad != 0 ) ERROR_exit("Cannot continue :-(") ; /* subtract mean from each base input, if is a constant polynomial in the fit */ if( nord >= 0 ){ if( verb ) INFO_message("subtracting spatial mean from '-base'") ; for( kk=0 ; kk < IMARR_COUNT(exar) ; kk++ ){ exb = 0.0f ; ex = MRI_FLOAT_PTR(IMARR_SUBIM(exar,kk)) ; for( nm=ii=0 ; ii < nvox ; ii++ ){ if( GOOD(ii) ){ exb += ex[ii]; nm++; } } exb /= nm ; for( ii=0 ; ii < nvox ; ii++ ) ex[ii] -= exb ; } } } /* if blurring, edit mask a little */ if( mask != NULL && (fwhm > 0.0f || mrad > 0.0f) ){ int ii ; ii = THD_mask_remove_isolas( DSET_NX(inset),DSET_NY(inset),DSET_NZ(inset),mask ) ; if( ii > 0 ){ nmask = THD_countmask( nvmask , mask ) ; if( verb ) ININFO_message("Removed %d isola%s from mask, leaving %d voxels" , ii,(ii==1)?"\0":"s" , nmask ) ; if( nmask < 99 ) ERROR_exit("Too few voxels left in mask after isola removal :-(") ; } } /* convert input to float, which is simpler to deal with */ imin = THD_extract_float_brick(0,inset) ; if( imin == NULL ) ERROR_exit("Can't extract input dataset brick?! :-(") ; DSET_unload(inset) ; if( verb ) INFO_message("Start fitting process") ; /* do the Gaussian blurring */ if( fwhm > 0.0f ){ if( verb ) ININFO_message("Gaussian blur: FWHM=%g mm",fwhm) ; imin->dx = fabsf(DSET_DX(inset)) ; imin->dy = fabsf(DSET_DY(inset)) ; imin->dz = fabsf(DSET_DZ(inset)) ; mri_blur3D_addfwhm( imin , mask , fwhm ) ; } /* do the fitting */ mri_polyfit_verb(verb) ; if( do_byslice ) imout = mri_polyfit_byslice( imin , nord , exar , mask , mrad , meth ) ; else imout = mri_polyfit ( imin , nord , exar , mask , mrad , meth ) ; /* WTF? */ if( imout == NULL ) ERROR_exit("Can't compute polynomial fit :-( !?") ; if( resid == NULL ) mri_free(imin) ; if( ! do_byslice ) fvit = mri_polyfit_get_fitvec() ; /* get coefficients of fit [26 Feb 2019] */ /* scale the fit dataset? */ if( do_mone ){ float sum=0.0f ; int nsum=0 , ii,nvox ; float *par=MRI_FLOAT_PTR(imout) ; nvox = imout->nvox ; for( ii=0 ; ii < nvox ; ii++ ){ if( mask != NULL && mask[ii] == 0 ) continue ; sum += par[ii] ; nsum++ ; } if( nsum > 0 && sum != 0.0f ){ sum = nsum / sum ; if( verb ) ININFO_message("-mone: scaling fit by %g",sum) ; for( ii=0 ; ii < nvox ; ii++ ) par[ii] *= sum ; } } /* if there's a mask, clip values outside of its box */ #undef PF #define PF(i,j,k) par[(i)+(j)*nx+(k)*nxy] if( mask != NULL && do_mclip ){ int xm,xp,ym,yp,zm,zp , ii,jj,kk , nx,ny,nz,nxy ; float *par ; MRI_IMAGE *bim = mri_empty_conforming( imout , MRI_byte ) ; mri_fix_data_pointer(mask,bim) ; if( verb ) ININFO_message("-mclip: polynomial fit to autobox of mask") ; MRI_autobbox( bim , &xm,&xp , &ym,&yp , &zm,&zp ) ; mri_clear_data_pointer(bim) ; mri_free(bim) ; nx = imout->nx ; ny = imout->ny ; nz = imout->nz ; nxy = nx*ny ; par = MRI_FLOAT_PTR(imout) ; for( ii=0 ; ii < xm ; ii++ ) for( kk=0 ; kk < nz ; kk++ ) for( jj=0 ; jj < ny ; jj++ ) PF(ii,jj,kk) = PF(xm,jj,kk) ; for( ii=xp+1 ; ii < nx ; ii++ ) for( kk=0 ; kk < nz ; kk++ ) for( jj=0 ; jj < ny ; jj++ ) PF(ii,jj,kk) = PF(xp,jj,kk) ; for( jj=0 ; jj < ym ; jj++ ) for( kk=0 ; kk < nz ; kk++ ) for( ii=0 ; ii < nx ; ii++ ) PF(ii,jj,kk) = PF(ii,ym,kk) ; for( jj=yp+1 ; jj < ny ; jj++ ) for( kk=0 ; kk < nz ; kk++ ) for( ii=0 ; ii < nx ; ii++ ) PF(ii,jj,kk) = PF(ii,yp,kk) ; for( kk=0 ; kk < zm ; kk++ ) for( jj=0 ; jj < ny ; jj++ ) for( ii=0 ; ii < nx ; ii++ ) PF(ii,jj,kk) = PF(ii,jj,zm) ; for( kk=zp+1 ; kk < nz ; kk++ ) for( jj=0 ; jj < ny ; jj++ ) for( ii=0 ; ii < nx ; ii++ ) PF(ii,jj,kk) = PF(ii,jj,zp) ; } if( mask != NULL ) free(mask) ; /* write outputs */ if( prefix != NULL ){ THD_3dim_dataset *outset = EDIT_empty_copy( inset ) ; EDIT_dset_items( outset , ADN_prefix , prefix , ADN_nvals , 1 , ADN_ntt , 0 , ADN_none ) ; EDIT_substitute_brick( outset , 0 , MRI_float , MRI_FLOAT_PTR(imout) ) ; tross_Copy_History( inset , outset ) ; tross_Make_History( "3dPolyfit" , argc,argv , outset ) ; DSET_write(outset) ; WROTE_DSET(outset) ; } if( resid != NULL ){ THD_3dim_dataset *outset = EDIT_empty_copy( inset ) ; float *inar=MRI_FLOAT_PTR(imin) , *outar=MRI_FLOAT_PTR(imout) ; int nx,ny,nz , nxyz , kk ; nx = imout->nx ; ny = imout->ny ; nz = imout->nz ; nxyz = nx*ny*nz ; for( kk=0 ; kk < nxyz ; kk++ ) outar[kk] = inar[kk] - outar[kk] ; mri_free(imin) ; EDIT_dset_items( outset , ADN_prefix , resid , ADN_nvals , 1 , ADN_ntt , 0 , ADN_none ) ; EDIT_substitute_brick( outset , 0 , MRI_float , MRI_FLOAT_PTR(imout) ) ; tross_Copy_History( inset , outset ) ; tross_Make_History( "3dPolyfit" , argc,argv , outset ) ; DSET_write(outset) ; WROTE_DSET(outset) ; } if( cfnam != NULL && fvit != NULL ){ /* won't work with '-byslice' */ char *qn ; qn = STRING_HAS_SUFFIX(cfnam,".1D") ? cfnam : modify_afni_prefix(cfnam,NULL,".1D") ; mri_write_floatvec( qn , fvit ) ; } exit(0) ; }
int main( int argc , char *argv[] ) { int iarg , ct , do_GM=0 ; int do_T2=0 ; float T2_uperc=98.5f ; byte *T2_mask=NULL ; char *prefix = "Unifized" ; THD_3dim_dataset *inset=NULL , *outset=NULL ; MRI_IMAGE *imin , *imout ; float clfrac=0.2f ; AFNI_SETUP_OMP(0) ; /* 24 Jun 2013 */ if( argc < 2 || strcmp(argv[1],"-help") == 0 ){ printf("\n" "Usage: 3dUnifize [options] inputdataset\n\n" "* The input dataset is supposed to be a T1-weighted volume,\n" " possibly already skull-stripped (e.g., via 3dSkullStrip).\n" " ++ However, this program can be a useful step to take BEFORE\n" " 3dSkullStrip, since the latter program can fail if the input\n" " volume is strongly shaded -- 3dUnifize will (mostly) remove\n" " such shading artifacts.\n" "* The output dataset has the white matter (WM) intensity approximately\n" " uniformized across space, and scaled to peak at about 1000.\n" "* The output dataset is always stored in float format!\n" "* If the input dataset has more than 1 sub-brick, only sub-brick\n" " #0 will be processed!\n" "* Method: Obi-Wan's personal variant of Ziad's sneaky trick.\n" " (If you want to know what his trick is, you'll have to ask him, or\n" " read Obi-Wan's source code, which is a world of ecstasy and exaltation,\n" " or just read all the way to the end of this help output.)\n" "* The principal motive for this program is for use in an image\n" " registration script, and it may or may not be useful otherwise.\n" "\n" "--------\n" "Options:\n" "--------\n" " -prefix pp = Use 'pp' for prefix of output dataset.\n" " -input dd = Alternative way to specify input dataset.\n" " -T2 = Treat the input as if it were T2-weighted, rather than\n" " T1-weighted. This processing is done simply by inverting\n" " the image contrast, processing it as if that result were\n" " T1-weighted, and then re-inverting the results.\n" " ++ This option is NOT guaranteed to be useful for anything!\n" " ++ Of course, nothing in AFNI comes with a guarantee :-)\n" " ++ If you want to be REALLY sneaky, giving this option twice\n" " will skip the second inversion step, so the result will\n" " look like a T1-weighted volume (except at the edges and\n" " near blood vessels).\n" " ++ Might be useful for skull-stripping T2-weighted datasets.\n" " ++ Don't try the '-T2 -T2' trick on FLAIR-T2-weighted datasets.\n" " The results aren't pretty!\n" " -GM = Also scale to unifize 'gray matter' = lower intensity voxels\n" " (to aid in registering images from different scanners).\n" " ++ This option is recommended for use with 3dQwarp when\n" " aligning 2 T1-weighted volumes, in order to make the\n" " WM-GM contrast about the same for the datasets, even\n" " if they don't come from the same scanner/pulse-sequence.\n" " ++ Note that standardizing the contrasts with 3dUnifize will help\n" " 3dQwarp match the source dataset to the base dataset. If you\n" " later want the original source dataset to be warped, you can\n" " do so using the 3dNwarpApply program.\n" " -Urad rr = Sets the radius (in voxels) of the ball used for the sneaky trick.\n" " ++ Default value is %.1f, and should be changed proportionally\n" " if the dataset voxel size differs significantly from 1 mm.\n" " -ssave ss = Save the scale factor used at each voxel into a dataset 'ss'.\n" " ++ This is the white matter scale factor, and does not include\n" " the factor from the '-GM' option (if that was included).\n" " ++ The input dataset is multiplied by the '-ssave' image\n" " (voxel-wise) to get the WM-unifized image.\n" " ++ Another volume (with the same grid dimensions) could be\n" " scaled the same way using 3dcalc, if that is needed.\n" " -quiet = Don't print the fun fun fun progress messages (but whyyyy?).\n" " ++ For the curious, the codes used are:\n" " A = Automask\n" " D = Duplo down (process a half-size volume)\n" " V = Voxel-wise histograms to get local scale factors\n" " U = duplo Up (convert local scale factors to full-size volume)\n" " W = multiply by White matter factors\n" " G = multiply by Gray matter factors [cf the -GM option]\n" " I = contrast inversion [cf the -T2 option]\n" " ++ 'Duplo down' means to scale the input volume to be half the\n" " grid size in each direction for speed when computing the\n" " voxel-wise histograms. The sub-sampling is done using the\n" " median of the central voxel value and its 6 nearest neighbors.\n" "\n" "------------------------------------------\n" "Special options for Jedi AFNI Masters ONLY:\n" "------------------------------------------\n" " -rbt R b t = Specify the 3 parameters for the algorithm, as 3 numbers\n" " following the '-rbt':\n" " R = radius; same as given by option '-Urad' [default=%.1f]\n" " b = bottom percentile of normalizing data range [default=%.1f]\n" " r = top percentile of normalizing data range [default=%.1f]\n" "\n" " -T2up uu = Set the upper percentile point used for T2-T1 inversion.\n" " The default value is 98.5 (for no good reason), and 'uu' is\n" " allowed to be anything between 90 and 100 (inclusive).\n" " ++ The histogram of the data is built, and the uu-th percentile\n" " point value is called 'U'. The contrast inversion is simply\n" " given by output_value = max( 0 , U - input_value ).\n" "\n" " -clfrac cc = Set the automask 'clip level fraction' to 'cc', which\n" " must be a number between 0.1 and 0.9.\n" " A small 'cc' means to make the initial threshold\n" " for clipping (a la 3dClipLevel) smaller, which\n" " will tend to make the mask larger. [default=0.1]\n" " ++ [22 May 2013] The previous version of this program used a\n" " clip level fraction of 0.5, which proved to be too large\n" " for some users, who had images with very strong shading issues.\n" " Thus, the default value for this parameter was lowered to 0.1.\n" " ++ [24 May 2016] The default value for this parameter was\n" " raised to 0.2, since the lower value often left a lot of\n" " noise outside the head on non-3dSkullStrip-ed datasets.\n" " You can still manually set -clfrac to 0.1 if you need to\n" " correct for very large shading artifacts.\n" " ++ If the results of 3dUnifize have a lot of noise outside the head,\n" " then using '-clfrac 0.5' value will probably help.\n" #ifndef USE_ALL_VALS "\n" " -useall = The 'old' way of operating was to use all dataset values\n" " in the local WM histogram. The 'new' way [May 2016] is to\n" " only use positive values. If you want to use the 'old' way,\n" " then this option is what you want.\n" #endif "\n" "-- Feb 2013 - by Obi-Wan Unifobi\n" #ifdef USE_OMP "-- This code uses OpenMP to speed up the slowest part (voxel-wise histograms).\n" #endif , Uprad , Uprad , Upbot , Uptop ) ; printf("\n" "----------------------------------------------------------------------------\n" "HOW IT WORKS (Ziad's sneaky trick is revealed at last! And more.)\n" "----------------------------------------------------------------------------\n" "The basic idea is that white matter in T1-weighted images is reasonably\n" "uniform in intensity, at least when averaged over 'large-ish' regions.\n" "\n" "The first step is to create a local white matter intensity volume.\n" "Around each voxel (inside the volume 'automask'), the ball of values\n" "within a fixed radius (default=18.3 voxels) is extracted and these\n" "numbers are sorted. The values in the high-intensity range of the\n" "histogram (default=70%% to 80%%) are averaged. The result from this\n" "step is a smooth 3D map of the 'white matter intensity' (WMI).\n" "\n" " [The parameters of the above process can be altered with the '-rbt' option.]\n" " [For speed, the WMI map is produced on an image that is half-size in all ]\n" " [directions ('Duplo down'), and then is expanded back to the full-size ]\n" " [volume ('Duplo up'). The automask procedure can be somewhat controlled ]\n" " [via the '-clfrac' option. The default setting is designed to deal with ]\n" " [heavily shaded images, where the WMI varies by a factor of 5 or more over ]\n" " [the image volume. ]\n" "\n" "The second step is to scale the value at every voxel location x in the input\n" "volume by the factor 1000/WMI(x), so that the 'white matter intensity' is\n" "now uniform-ized to be 1000 everywhere. (This is Ziad's 'trick'; it is easy,\n" "works well, and doesn't require fitting some spatial model to the data: the\n" "data provides its own model.)\n" "\n" "If the '-GM' option is used, then this scaled volume is further processed\n" "to make the lower intensity values (presumably gray matter) have a contrast\n" "similar to that from a collection of 3 Tesla MP-RAGE images that were\n" "acquired at the NIH. (This procedure is not Ziad's fault, and should be\n" "blamed on the reclusive Obi-Wan Unifobi.)\n" "\n" "From the WM-uniform-ized volume, the median of all values larger than 1000\n" "is computed; call this value P. P-1000 represents the upward dispersion\n" "of the high-intensity (white matter) voxels in the volume. This value is\n" "'reflected' below 1000 to Q = 1000 - 2*(P-1000), and Q is taken to be the\n" "upper bound for gray matter voxel intensities. A lower bound for gray\n" "matter voxel values is estimated via the 'clip fraction' algorithm as\n" "implemented in program 3dClipLevel; call this lower bound R. The median\n" "of all values between R and Q is computed; call this value G, which is taken\n" "to be a 'typical' gray matter voxel instensity. Then the values z in the\n" "entire volume are linearly scaled by the formula\n" " z_out = (1000-666)/(1000-G) * (z_in-1000) + 1000\n" "so that the WM uniform-ized intensity of 1000 remains at 1000, and the gray\n" "matter median intensity of G is mapped to 666. (Values z_out that end up\n" "negative are set to 0; as a result, some of CSF might end up as 0.)\n" "The value 666 was chosen because it gave results visually comparable to\n" "various NIH-generated 3 Tesla T1-weighted datasets. (Any suggestions that\n" "this value was chosen for other reasons will be treated as 'beastly'.)\n" "\n" "To recap: the WM uniform-ization process provides a linear scaling factor\n" "that varies for each voxel ('local'), while the GM normalization process\n" "uses a global linear scaling. The GM process is optional, and is simply\n" "designed to make the various T1-weighted images look similar.\n" "\n" "-----** CAVEAT **-----\n" "This procedure was primarily developed to aid in 3D registration, especially\n" "when using 3dQwarp, so that the registration algorithms are trying to match\n" "images that are alike. It is *NOT* intended to be used for quantification\n" "purposes, such as Voxel Based Morphometry! That would better be done via\n" "the 3dSeg program, which is far more complicated.\n" "----------------------------------------------------------------------------\n" ) ; PRINT_COMPILE_DATE ; exit(0) ; } mainENTRY("3dUnifize main"); machdep(); AFNI_logger("3dUnifize",argc,argv); PRINT_VERSION("3dUnifize") ; ct = NI_clock_time() ; /*-- scan command line --*/ THD_automask_set_clipfrac(0.1f) ; /* 22 May 2013 */ THD_automask_extclip(1) ; /* 19 Dec 2014 */ iarg = 1 ; while( iarg < argc && argv[iarg][0] == '-' ){ if( strcmp(argv[iarg],"-clfrac") == 0 || strcmp(argv[iarg],"-mfrac") == 0 ){ /* 22 May 2013 */ if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ; clfrac = (float)strtod( argv[iarg] , NULL ) ; if( clfrac < 0.1f || clfrac > 0.9f ) ERROR_exit("-clfrac value %f is illegal!",clfrac) ; THD_automask_set_clipfrac(clfrac) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-prefix") == 0 ){ if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ; prefix = argv[iarg] ; if( !THD_filename_ok(prefix) ) ERROR_exit("Illegal value after -prefix!") ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-ssave") == 0 ){ if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ; sspref = strdup(argv[iarg]) ; if( !THD_filename_ok(sspref) ) ERROR_exit("Illegal value after -ssave!") ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-input") == 0 || strcmp(argv[iarg],"-inset") == 0 ){ if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ; if( inset != NULL ) ERROR_exit("Can't use '%s' twice" ,argv[iarg-1]) ; inset = THD_open_dataset( argv[iarg] ) ; CHECK_OPEN_ERROR(inset,argv[iarg]) ; iarg++ ; continue ; } if( strcmp(argv[iarg],"-Urad") == 0 ){ if( ++iarg >= argc ) ERROR_exit("Need argument after '%s'",argv[iarg-1]) ; Uprad = (float)strtod(argv[iarg],NULL) ; if( Uprad < 5.0f || Uprad > 40.0f ) ERROR_exit("Illegal value %f after option -Urad",Uprad) ; iarg++ ; continue ; } #ifndef USE_ALL_VALS if( strcmp(argv[iarg],"-useall") == 0 ){ /* 17 May 2016 */ USE_ALL_VALS = 1 ; iarg++ ; continue ; } #else if( strcmp(argv[iarg],"-useall") == 0 ){ WARNING_message("-useall option is disabled in this version") ; iarg++ ; continue ; } #endif if( strcmp(argv[iarg],"-param") == 0 || /*--- HIDDEN OPTION ---*/ strcmp(argv[iarg],"-rbt" ) == 0 ){ if( ++iarg >= argc-2 ) ERROR_exit("Need 3 arguments (R pb pt) after '%s'",argv[iarg-1]) ; Uprad = (float)strtod(argv[iarg++],NULL) ; Upbot = (float)strtod(argv[iarg++],NULL) ; Uptop = (float)strtod(argv[iarg++],NULL) ; if( Uprad < 5.0f || Uprad > 40.0f || Upbot < 30.0f || Upbot > 80.0f || Uptop <= Upbot || Uptop > 90.0f ) ERROR_exit("Illegal values (R pb pt) after '%s'",argv[iarg-4]) ; continue ; } if( strcasecmp(argv[iarg],"-GM") == 0 ){ do_GM++ ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-T2") == 0 ){ /* 18 Dec 2014 */ do_T2++ ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-T2up") == 0 ){ /* 18 Dec 2014 */ T2_uperc = (float)strtod( argv[++iarg] , NULL ) ; if( T2_uperc < 90.0f || T2_uperc > 100.0f ) ERROR_exit("-T2up value is out of range 90..100 :-(") ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-quiet") == 0 ){ verb = 0 ; iarg++ ; continue ; } if( strcasecmp(argv[iarg],"-verb") == 0 ){ verb++ ; iarg++ ; continue ; } ERROR_exit("Unknown option: %s\n",argv[iarg]); } /* read input dataset, if not already there */ if( inset == NULL ){ if( iarg >= argc ) ERROR_exit("No dataset name on command line?\n") ; inset = THD_open_dataset( argv[iarg] ) ; CHECK_OPEN_ERROR(inset,argv[iarg]) ; } if( verb ) fprintf(stderr," + Pre-processing: ") ; /* load input from disk */ DSET_load( inset ) ; CHECK_LOAD_ERROR(inset) ; if( DSET_NVALS(inset) > 1 ) WARNING_message("Only processing sub-brick #0 (out of %d)",DSET_NVALS(inset)) ; /* make a float copy for processing */ imin = mri_to_float( DSET_BRICK(inset,0) ) ; DSET_unload(inset) ; if( imin == NULL ) ERROR_exit("Can't copy input dataset brick?!") ; #if 0 THD_cliplevel_search(imin) ; exit(0) ; /* experimentation only */ #endif THD_automask_set_clipfrac(clfrac) ; /* invert T2? */ if( do_T2 ){ if( verb ) fprintf(stderr,"I") ; T2_mask = mri_automask_image(imin) ; mri_invertcontrast_inplace( imin , T2_uperc , T2_mask ) ; } /* do the actual work */ imout = mri_WMunifize(imin) ; /* local WM scaling */ free(imin) ; if( sspref != NULL && sclim != NULL ){ /* 25 Jun 2013 */ STATUS("output -ssave") ; outset = EDIT_empty_copy( inset ) ; EDIT_dset_items( outset , ADN_prefix , sspref , ADN_nvals , 1 , ADN_ntt , 0 , ADN_none ) ; EDIT_substitute_brick( outset , 0 , MRI_float , MRI_FLOAT_PTR(sclim) ) ; tross_Copy_History( inset , outset ) ; tross_Make_History( "3dUnifize" , argc,argv , outset ) ; DSET_write(outset) ; outset = NULL ; } if( sclim != NULL ){ mri_free(sclim) ; sclim = NULL ; } if( imout == NULL ){ /* this is bad-ositiness */ if( verb ) fprintf(stderr,"\n") ; ERROR_exit("Can't compute Unifize-d dataset for some reason :-(") ; } if( do_GM ) mri_GMunifize(imout) ; /* global GM scaling */ if( do_T2 == 1 ){ /* re-invert T2? */ if( verb ) fprintf(stderr,"I") ; mri_invertcontrast_inplace( imout , T2_uperc , T2_mask ) ; } else if( do_T2 == 2 ){ /* don't re-invert, but clip off bright edges */ mri_clipedges_inplace( imout , PKVAL*1.111f , PKVAL*1.055f ) ; } if( verb ) fprintf(stderr,"\n") ; /* create output dataset, and write it into the historical record */ outset = EDIT_empty_copy( inset ) ; EDIT_dset_items( outset , ADN_prefix , prefix , ADN_nvals , 1 , ADN_ntt , 0 , ADN_none ) ; EDIT_substitute_brick( outset , 0 , MRI_float , MRI_FLOAT_PTR(imout) ) ; tross_Copy_History( inset , outset ) ; tross_Make_History( "3dUnifize" , argc,argv , outset ) ; DSET_write(outset) ; WROTE_DSET(outset) ; DSET_delete(outset) ; DSET_delete(inset) ; /* vamoose the ranch */ if( verb ){ double cput = COX_cpu_time() ; if( cput > 0.05 ) INFO_message("===== CPU time = %.1f sec Elapsed = %.1f\n", COX_cpu_time() , 0.001*(NI_clock_time()-ct) ) ; else INFO_message("===== Elapsed = %.1f sec\n", 0.001*(NI_clock_time()-ct) ) ; } exit(0) ; }