#include "mrilib.h" #include "mri_threshX.c" static int verb=1 ; void mri_apply_mask( MRI_IMAGE *inim , MRI_IMAGE *bim ) ; /* prototype */ /*----------------------------------------------------------------------------*/ /*! Threshold for upper tail probability of N(0,1) = 1-inverseCDF(pval) */ #if 0 #undef PSMALL #define PSMALL 1.e-15 double zthresh( double pval ) { if( pval <= 0.0 ) pval = PSMALL ; else if( pval >= 1.0 ) pval = 1.0 - PSMALL ; return qginv(pval) ; /* mri_stats.c */ } #endif /*! Convert zthresh to pval, given these: zthr_1sid = zthresh( pthr ) zthr_2sid = zthresh( 0.5*pthr ) */ double ztop( double z , int sid ) { double pp = qg(z) ; if( sid == 2 ) pp *= 2.0 ; return pp ; } /*----------------------------------------------------------------------------*/ int main( int argc , char *argv[] ) { THD_3dim_dataset *mset=NULL , *iset=NULL ; NI_element *mel=NULL ; /* Sep 2018 */ char *prefix = "mthresh.nii" ; int nopt , ii , tindex=-1 , ival,nval,nvox ; int nnlev=0,nnsid=0,nzthr=0 ; float *zthr=NULL ; MRI_IMAGE *fim , *bfim ; int nhits=0 , do_nozero=0 ; int do_hpow0=0,do_hpow1=0,do_hpow2=0, nhpow=0,hpow=0, hstart; int do_maskonly=0 , do_pos=-1 ; MRI_IMARR *cimar0=NULL , *cimar1=NULL , *cimar2=NULL ; MRI_IMARR *allmask=NULL ; char *prefix_allmask=NULL , *case_allmask=NULL ; float *melvec=NULL ; /*----- help, I'm trapped in an instance of vi and can't get out -----*/ if( argc < 3 || strcasecmp(argv[1],"-help") == 0 ){ printf("\n" "Program to apply a multi-threshold (mthresh) dataset\n" "to an input dataset.\n" "\n" "Usage:\n" " 3dMultiThresh OPTIONS\n" "\n" "OPTIONS (in any order)\n" "----------------------\n" "\n" " -mthresh mmm = Multi-threshold dataset from 3dXClustSim\n" " (usually via running '3dttest++ -ETAC').\n" " *OR*\n" " globalETAC.mthresh.*.niml threshold file\n" " -input ddd = Dataset to threshold.\n" " -1tindex iii = Index (sub-brick) on which to threshold.\n" " -signed +/- = If the .mthresh.nii file from 3dXClustSim\n" " was created using 1-sided thresholding,\n" " this option tells which sign to choose when\n" " doing voxel-wise thresholding: + or -.\n" " ++ If the .mthresh.nii file was created using\n" " 2-sided thresholding, this option is ignored.\n" " -pos = Same as '-signed +'\n" " -neg = Same as '-signed -'\n" " -prefix ppp = prefix for output dataset\n" " ++ Can be 'NULL' to get no output dataset\n" " -maskonly = Instead of outputting a thresholded version\n" " of the input dataset, just output a 0/1 mask\n" " dataset of voxels that survive the process.\n" " -allmask qqq = Write out a multi-volume dataset with prefix 'qqq'\n" " where each volume is the binary mask of voxels that\n" " pass ONE of the tests. This dataset can be used\n" " to see which tests mattered where in the brain.\n" " ++ To be more clear, there will be one sub-brick for\n" " each p-value threshold coded in the '-mthresh'\n" " dataset (e.g., p=0.0100 and p=0.0001).\n" " ++ In each sub-brick, the value will be between\n" " 0 and 7, and is the sum of these:\n" " 1 == hpow=0 was declared 'active'\n" " 2 == hpow=1 was declared 'active'\n" " 4 == hpow=2 was declared 'active'\n" " Of course, an hpow value will only be tested\n" " if it is so encoded in the '-mthresh' dataset.\n" " -nozero = This option prevents the output of a\n" " dataset if it would be all zero\n" " -quiet = Turn off progress report messages\n" "\n" "The number of surviving voxels will be written to stdout.\n" "It can be captured in a csh script by a command such as\n" " set nhits = `3dMultiThresh OPTIONS`\n" "\n" "Meant to be used in conjunction with program 3dXClustSim,\n" "which is in turn meant to be used with program 3dttest++ -- RWCox\n\n" ) ; exit(0) ; } /*----- scan thru options -----*/ mainENTRY("3dMultiThresh"); machdep(); PRINT_VERSION("3dMultiThresh") ; nopt = 1 ; while( nopt < argc && argv[nopt][0] == '-' ){ /*-----*/ if( strcasecmp(argv[nopt],"-signed") == 0 ){ /* 26 Apr 2017 */ if( ++nopt >= argc ) ERROR_exit("need 1 argument after option '%s'",argv[nopt-1]) ; if( argv[nopt][0] == '+' ) do_pos = 1 ; else if( argv[nopt][0] == '-' ) do_pos = 0 ; else ERROR_exit("option '%s %s' doesn't mean anything :(",argv[nopt-1],argv[nopt]) ; nopt++ ; continue ; } /*-----*/ if( strncasecmp(argv[nopt],"-pos",4) == 0 ){ /* 26 Apr 2017 */ do_pos = 1 ; nopt++ ; continue ; } if( strncasecmp(argv[nopt],"-neg",4) == 0 ){ /* 26 Apr 2017 */ do_pos = 0 ; nopt++ ; continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-maskonly") == 0 ){ do_maskonly = 1 ; nopt++ ; continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-nozero") == 0 ){ do_nozero = 1 ; nopt++ ; continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-quiet") == 0 ){ verb = 0 ; nopt++ ; continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-prefix") == 0 ){ if( ++nopt >= argc ) ERROR_exit("need 1 argument after option '%s'",argv[nopt-1]) ; prefix = strdup(argv[nopt]) ; if( !THD_filename_ok(prefix) ) ERROR_exit("-prefix '%s' is illegal (and funny looking)",prefix) ; nopt++ ; continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-allmask") == 0 ){ if( ++nopt >= argc ) ERROR_exit("need 1 argument after option '%s'",argv[nopt-1]) ; prefix_allmask = strdup(argv[nopt]) ; if( !THD_filename_ok(prefix_allmask) ) ERROR_exit("-allmask '%s' is illegal (and funny looking)",prefix_allmask) ; nopt++ ; if( nopt < argc && argv[nopt][0] != '-' ){ case_allmask = strdup(argv[nopt++]) ; if( strlen(case_allmask) > 9 ) case_allmask[9] = '\0' ; } continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-input") == 0 ){ if( iset != NULL ) ERROR_exit("you can't use '-input' twice!") ; if( ++nopt >= argc ) ERROR_exit("need 1 argument after option '%s'",argv[nopt-1]) ; iset = THD_open_dataset(argv[nopt]) ; if( iset == NULL ) ERROR_exit("can't open -input dataset '%s'",argv[nopt]) ; DSET_load(iset) ; CHECK_LOAD_ERROR(iset) ; nopt++ ; continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-1tindex") == 0 ){ if( ++nopt >= argc ) ERROR_exit("need 1 argument after option '%s'",argv[nopt-1]) ; tindex = (int)strtod(argv[nopt],NULL) ; nopt++ ; continue ; } /*-----*/ if( strcasecmp(argv[nopt],"-mthresh") == 0 ){ int nbad=0 ; if( mset != NULL || mel != NULL ) ERROR_exit("you can't use '-mthresh' twice!") ; if( ++nopt >= argc ) ERROR_exit("need 1 argument after option '%s'",argv[nopt-1]) ; if( !STRING_HAS_SUFFIX(argv[nopt],".niml") ){ /*--- read cluster-FOM dataset ---*/ ATR_float *atr ; float *afl ; mset = THD_open_dataset(argv[nopt]) ; if( mset == NULL ) ERROR_exit("can't open -mthresh dataset '%s'",argv[nopt]) ; DSET_load(mset) ; CHECK_LOAD_ERROR(mset) ; atr = THD_find_float_atr( mset->dblk , "MULTI_THRESHOLDS" ) ; if( atr == NULL ) ERROR_exit("-mthresh dataset does not have any MULTI_THRESHOLDS attribute :(") ; if( atr->nfl < 5 ) ERROR_exit("-mthresh dataset MULTI_THRESHOLDS attribute is too short [%d] :(",atr->nfl) ; afl = atr->fl ; nnlev = (int)afl[0] ; nnsid = (int)afl[1] ; nzthr = (int)afl[2] ; hpow = (int)afl[3] ; zthr = (float *)malloc(sizeof(float)*nzthr) ; for( ii=0 ; ii < nzthr ; ii++ ) zthr[ii] = afl[4+ii] ; if( atr->nfl > 4+nzthr ){ /* 21 Sep 2017 */ int mc = (int)afl[4+nzthr] ; if( mc > 1 ) set_Xmin_clust(mc) ; } do_hpow0 = (hpow & 1) != 0 ; do_hpow1 = (hpow & 2) != 0 ; do_hpow2 = (hpow & 4) != 0 ; nhpow = do_hpow0 + do_hpow1 + do_hpow2 ; if( verb ){ INFO_message("-mthresh dataset parameters") ; ININFO_message(" clustering NN=%d thresholding=%d-sided %d thresholds %d hpows", nnlev,nnsid,nzthr,nhpow ) ; } if( nnlev < 1 || nnlev > 3 ){ ERROR_message("illegal nnlev=%d",nnlev); nbad++; } if( nnsid < 1 || nnsid > 2 ){ ERROR_message("illegal sided=%d",nnsid); nbad++; } if( nzthr < 1 ){ ERROR_message("illegal nzthr=%d",nzthr); nbad++; } if( nhpow < 1 ){ ERROR_message("illegal nhpow=%d",nhpow); nbad++; } if( nbad ) ERROR_exit("Can't continue after the above problems") ; } else { /*--- NIML element == global cluster-FOM threshold [Sep 2018] ---*/ char *atr ; int nv0=0, nv1=0 ; mel = NI_read_element_fromfile(argv[nopt]) ; if( mel == NULL ) ERROR_exit("Cannot read valid data from %s",argv[nopt]) ; if( mel->vec_num != 1 || mel->vec_typ[0] != NI_FLOAT || mel->vec_rank != 2 ){ ERROR_message("badly formed data element in %s",argv[nopt]) ; nbad++ ; } else { nv0 = mel->vec_axis_len[0] ; /* should be nzthr */ nv1 = mel->vec_axis_len[1] ; /* should be nhpow */ melvec = (float *)mel->vec[0] ; /* list of thresholds */ } atr = NI_get_attribute(mel,"NNlev") ; if( atr == NULL || !isdigit(*atr) ){ ERROR_message("data element in %s is missing NNlev",argv[nopt]) ; nbad++ ; } else { nnlev = (int)rintf(strtod(atr,NULL)) ; } atr = NI_get_attribute(mel,"NNsid") ; if( atr == NULL || !isdigit(*atr) ){ ERROR_message("data element in %s is missing NNsid",argv[nopt]) ; nbad++ ; } else { nnsid = (int)rintf(strtod(atr,NULL)) ; } atr = NI_get_attribute(mel,"FOM_list") ; if( atr == NULL ){ ERROR_message("data element in %s is missing FOM_list",argv[nopt]) ; nbad++ ; } else { do_hpow0 = ( strstr(atr,"hpow0") != NULL ) ; do_hpow1 = ( strstr(atr,"hpow1") != NULL ) ; do_hpow2 = ( strstr(atr,"hpow2") != NULL ) ; nhpow = do_hpow0 + do_hpow1 + do_hpow2 ; if( nhpow != nv1 ){ ERROR_message("data element in %s mismatch in FOM numbers",argv[nopt]) ; nbad++ ; } } atr = NI_get_attribute(mel,"zthr_list") ; if( atr == NULL ){ ERROR_message("data element in %s is missing zthr_list",argv[nopt]) ; nbad++ ; } else { NI_float_array *gfar ; gfar = NI_decode_float_list(atr,",") ; if( gfar == NULL || gfar->num != nv0 ){ ERROR_message("data element in %s has bad zthr_list",argv[nopt]) ; nbad++ ; } else { zthr = gfar->ar ; nzthr = nv0 ; } } atr = NI_get_attribute(mel,"min_clust") ; if( atr != NULL && isdigit(*atr) ){ int mc = (int)rintf(strtod(atr,NULL)) ; if( mc > 1 ) set_Xmin_clust(mc) ; } if( verb ){ INFO_message("-mthresh global parameters") ; ININFO_message(" clustering NN=%d thresholding=%d-sided %d thresholds %d hpows", nnlev,nnsid,nzthr,nhpow ) ; } if( nnlev < 1 || nnlev > 3 ){ ERROR_message("illegal nnlev=%d",nnlev); nbad++; } if( nnsid < 1 || nnsid > 2 ){ ERROR_message("illegal sided=%d",nnsid); nbad++; } if( nzthr < 1 ){ ERROR_message("illegal nzthr=%d",nzthr); nbad++; } if( nhpow < 1 ){ ERROR_message("illegal nhpow=%d",nhpow); nbad++; } if( nbad ) ERROR_exit("Can't continue after the above problems") ; } /*-- end of processing NIML element --*/ nopt++ ; continue ; } /*-----*/ ERROR_exit("Unknown option '%s'",argv[nopt]) ; } /*------- check for errors -------*/ if( iset == NULL ) ERROR_exit("-input is a mandatory 'option'") ; if( mset == NULL && mel == NULL ) ERROR_exit("-mthresh is a mandatory 'option'") ; /*-- check sign-age [26 Apr 2017] --*/ if( nnsid == 1 ){ if( do_pos < 0 ){ INFO_message("1-sided thresholding: default is positive") ; do_pos = 1 ; } } else if( do_pos >= 0 ){ INFO_message("2-sided thresholding: ignoring %s sign option", (do_pos) ? "+" : "-" ) ; do_pos = 1 ; } /*-- change signs to threshold on the negative side? --*/ if( do_pos == 0 ){ for( ival=0 ; ival < nzthr ; ival++ ) zthr[ival] = -zthr[ival] ; } /*----- do some real work (oog) -----*/ /* find the threshold sub-brick, if not forced upon us */ nval = DSET_NVALS(iset) ; nvox = DSET_NVOX(iset) ; /*-- if tindex not given, find something appropriate --*/ if( tindex < 0 || tindex >= nval ){ for( ival=0 ; ival < nval ; ival++ ){ if( DSET_BRICK_STATCODE(iset,ival) == FUNC_ZT_TYPE ) break ; } if( ival >= nval ){ for( ival=0 ; ival < nval ; ival++ ){ if( DSET_BRICK_STATCODE(iset,ival) == FUNC_TT_TYPE ) break ; } } tindex = (ival < nval) ? ival : 0 ; if( verb ) INFO_message("threshold default index set to %d",ival) ; } /*-- get the threshold brick image --*/ fim = THD_extract_float_brick( tindex , iset ) ; if( fim == NULL ) ERROR_exit("Can't get sub-brick %d to threshold",tindex) ; /*-- preparations for thresholding, then thresholding --*/ mri_multi_threshold_setup() ; if( mset != NULL ){ /*--- spatially variable cluster-FOM thresholds ---*/ /* build the arrays of FOM threshold volumes */ hstart = 0 ; if( do_hpow0 ){ INIT_IMARR(cimar0) ; for( ival=0 ; ival < nzthr ; ival++ ) ADDTO_IMARR(cimar0,DSET_BRICK(mset,hstart+ival*nhpow)) ; hstart++ ; } if( do_hpow1 ){ INIT_IMARR(cimar1) ; for( ival=0 ; ival < nzthr ; ival++ ) ADDTO_IMARR(cimar1,DSET_BRICK(mset,hstart+ival*nhpow)) ; hstart++ ; } if( do_hpow2 ){ INIT_IMARR(cimar2) ; for( ival=0 ; ival < nzthr ; ival++ ) ADDTO_IMARR(cimar2,DSET_BRICK(mset,hstart+ival*nhpow)) ; hstart++ ; } /* get the mask of values above the multi-thresholds */ bfim = mri_multi_threshold_Xcluster_cimar( fim , nzthr , zthr , nnsid , nnlev , cimar0 , cimar1 , cimar2 , XTHRESH_OUTPUT_MASK , &nhits , (prefix_allmask != NULL) ? &allmask : NULL ) ; /* don't need mthresh dataset no more */ DSET_unload(mset) ; FREE_IMARR(cimar0) ; FREE_IMARR(cimar1) ; FREE_IMARR(cimar2) ; } else { /*--- spatially constant cluster-FOM thresholds [Sep 2018] ---*/ float *fth0=NULL , *fth1=NULL , *fth2=NULL , *zqq=melvec ; /* get pointers into the global FOM-threshold array */ if( do_hpow0 ){ fth0 = zqq ; zqq += nzthr ; } if( do_hpow1 ){ fth1 = zqq ; zqq += nzthr ; } if( do_hpow2 ){ fth2 = zqq ; zqq += nzthr ; } /* get the mask of values above the multi-thresholds */ bfim = mri_multi_threshold_Xcluster_fomth( fim , nzthr , zthr , nnsid , nnlev , fth0 , fth1 , fth2 , XTHRESH_OUTPUT_MASK , &nhits , (prefix_allmask != NULL) ? &allmask : NULL ) ; } mri_multi_threshold_unsetup() ; /*--- nothing survived? ---*/ if( bfim == NULL && do_nozero ){ printf("0\n") ; exit(0) ; } /*--- apply the mask and produce the output dataset ---*/ if( strcmp(prefix,"NULL") != 0 ){ THD_3dim_dataset *oset ; if( bfim == NULL ){ /* make all-zero mask */ bfim = mri_new_conforming( fim , MRI_byte ) ; memset(MRI_BYTE_PTR(bfim),0,sizeof(byte)*bfim->nvox) ; } if( !do_maskonly ){ /* output a real dataset */ oset = EDIT_full_copy(iset,prefix) ; DSET_unload(iset) ; tross_Copy_History( iset , oset ) ; tross_Make_History( "3dMultiThresh" , argc,argv , oset ) ; THD_copy_datablock_auxdata( iset->dblk , oset->dblk ) ; THD_copy_labeltable_atr(oset->dblk,iset->dblk); for( ival=0 ; ival < nval ; ival++ ){ mri_apply_mask( DSET_BRICK(oset,ival) , bfim ) ; } DSET_write(oset) ; if( verb ) WROTE_DSET(oset) ; DSET_delete(oset) ; } else { /* maskonly output [21 Apr 2017] */ DSET_unload(iset) ; oset = EDIT_empty_copy(iset) ; tross_Copy_History( iset , oset ) ; tross_Make_History( "3dMultiThresh" , argc,argv , oset ) ; EDIT_dset_items( oset , ADN_nvals,1 , ADN_prefix,prefix , ADN_none ) ; EDIT_substitute_brick( oset , 0 , MRI_byte , MRI_BYTE_PTR(bfim) ) ; DSET_write(oset) ; if( verb ) WROTE_DSET(oset) ; } } /*--- output allmask = mask for each sub-test (zthr) ---*/ if( prefix_allmask != NULL && allmask != NULL ){ THD_3dim_dataset *oset; int nvv; MRI_IMAGE *qfim; char lab[64]; if( bfim != NULL ) mri_free(bfim) ; bfim = mri_new_conforming( fim , MRI_byte ) ; memset(MRI_BYTE_PTR(bfim),0,sizeof(byte)*bfim->nvox) ; DSET_unload(iset) ; oset = EDIT_empty_copy(iset) ; nvv = IMARR_COUNT(allmask) ; EDIT_dset_items( oset , ADN_nvals , nvv , ADN_prefix , prefix_allmask , ADN_none ) ; for( ival=0 ; ival < nvv ; ival++ ){ qfim = IMARR_SUBIM(allmask,ival) ; if( qfim == NULL ) qfim = bfim ; EDIT_substitute_brick( oset , ival , MRI_byte , MRI_BYTE_PTR(qfim) ) ; if( case_allmask == NULL ) sprintf(lab,"p=%.4f", ztop(zthr[ival],nnsid) ) ; else sprintf(lab,"%s:p=%.4f", case_allmask , ztop(zthr[ival],nnsid) ) ; EDIT_BRICK_LABEL(oset,ival,lab) ; } DSET_write(oset) ; if( verb ) WROTE_DSET(oset) ; } mri_free(bfim) ; printf("%d\n",nhits) ; exit(0) ; } /*----------------------------------------------------------------------------*/ void mri_apply_mask( MRI_IMAGE *inim , MRI_IMAGE *bim ) { byte *msk ; int ii,nvox ; ENTRY("mri_apply_mask") ; if( inim == NULL || bim == NULL || bim->kind != MRI_byte ) EXRETURN ; msk = MRI_BYTE_PTR(bim) ; nvox = inim->nvox ; if( bim->nvox != nvox ) EXRETURN ; switch( inim->kind ){ default: ERROR_message("Can't handle data of type %s",MRI_TYPE_name[inim->kind]) ; break ; case MRI_byte:{ byte *iar = MRI_BYTE_PTR(inim) ; for( ii=0 ; ii < nvox ; ii++ ) if( msk[ii] == 0 ) iar[ii] = 0 ; } break ; case MRI_short:{ short *iar = MRI_SHORT_PTR(inim) ; for( ii=0 ; ii < nvox ; ii++ ) if( msk[ii] == 0 ) iar[ii] = 0 ; } break ; case MRI_float:{ float *iar = MRI_FLOAT_PTR(inim) ; for( ii=0 ; ii < nvox ; ii++ ) if( msk[ii] == 0 ) iar[ii] = 0.0f ; } break ; } EXRETURN ; }