diff --git a/smoothest.cc b/smoothest.cc index af1abd0d9d9fd63f8e71e6dbe1d5cc2ea6b9b504..1cc8fbaf9355af4c29707b7e62c92909837df691 100644 --- a/smoothest.cc +++ b/smoothest.cc @@ -1,37 +1,99 @@ // $Id$ -// COPYRIGHT -#include <getopt.h> - -#include <cstdlib> +//#include <cstdlib> #include <cmath> #include <iostream> +//#include <fstream> #include <string> +#include <map> + #include <avwio.h> -#include <cassert> +#include "options.h" +#define _GNU_SOURCE 1 #define POSIX_SOURCE 1 +Option<bool> verbose(string("-V,--verbose"), false, + string("switch on diagnostic messages"), + false, BaseOption::no_argument); +Option<bool> help(string("-h,--help"), false, + string("display this message"), + false, BaseOption::no_argument); +Option<float> dof(string("-d,--dof"), 100.0, + string("number of degrees of freedom"), + true, BaseOption::requires_argument); +Option<string> maskname(string("-m,--mask"), string("mask"), + string("brain mask volume"), + true, BaseOption::requires_argument); +Option<string> zstatname(string("-z,--zstat"), "zstat", + string("filename of zstat image (not with -d)"), + true, BaseOption::requires_argument); +Option<string> residname(string("-r,--res"), "res4d", + string("filename of `residual-fit' image (must use -d)"), + true, BaseOption::requires_argument); + +class Interpolate { +public: + Interpolate() { + lut[5] = 1.5423138; lut[6] = 1.3757105; lut[7] = 1.2842680; + lut[8] = 1.2272151; lut[9] = 1.1885232; lut[10] = 1.1606988; + lut[11] = 1.1398000; lut[12] = 1.1235677; lut[13] = 1.1106196; + lut[14] = 1.1000651; lut[15] = 1.0913060; lut[16] = 1.0839261; + lut[17] = 1.0776276; lut[18] = 1.0721920; lut[19] = 1.0674553; + lut[20] = 1.0632924; lut[25] = 1.0483053; lut[30] = 1.0390117; + lut[40] = 1.0281339; lut[50] = 1.0219834; lut[60] = 1.0180339; + lut[70] = 1.0152850; lut[80] = 1.0132621; lut[90] = 1.0117115; + lut[100] = 1.0104851; lut[150] = 1.0068808; lut[200] = 1.0051200; + lut[300] = 1.0033865; lut[500] = 1.0020191; + } + + inline float operator()(float v) { + + float retval = 0; + + map<int, float>::iterator i = lut.lower_bound(v); + + if(i != lut.end()) { + if(i != lut.begin()) { + map<int, float>::iterator j = i--; + + retval = (j->second - i->second)/(j->first - i->first)*(v - i->first) + + j->second; + } + } else { + retval = 1.0321/v + 1; + } + + retval = pow(retval, 0.5); + + return retval; + } + +private: + map<int, float> lut; +}; + +Interpolate interpolate; template<class T> class Volume { private: - unsigned int _x, _y, _z, _xy, _xyz; - T *_data; + unsigned int x_, y_, z_, xy_, xyz_; + T *data_; public: - Volume(): _x(0), _y(0), _z(0), - _xy(0), _data(NULL) {}; + Volume(): x_(0), y_(0), z_(0), + xy_(0), data_(NULL) {}; Volume(const unsigned int x, const unsigned int y, const unsigned int z, - T *data): _x(x), _y(y), _z(z) { - _xy = x * y; - _xyz = _xy * z; - _data = new T[_xy * z]; + T *data): x_(x), y_(y), z_(z) { + xy_ = x_ * y; + xyz_ = xy_ * z; + data_ = new T[xyz_]; } Volume(AVW *ap, const unsigned int v) { @@ -40,35 +102,35 @@ public: short x, y, z, M; AvwGetDim(ap, &x, &y, &z, &M); - _x = x; - _xy = x * y; - _xyz = _xy * z; - _data = new T[_xyz]; + x_ = x; + xy_ = x_ * y; + xyz_ = xy_ * z; + data_ = new T[xyz_]; - AvwReadVolumes(ap, (void *)_data, 1); + AvwReadVolumes(ap, (void *)data_, 1); } unsigned int volsize() { - return _xyz; + return xyz_; } inline T& operator () (const unsigned int x, const unsigned int y, const unsigned int z) { - return _data[(z * _xy) + (y * _x) + x]; + return data_[(z * xy_) + (y * x_) + x]; } inline T& operator () (const unsigned int n) { - return _data[n]; + return data_[n]; } inline T* buffer () { - return _data; + return data_; } ~Volume() { try { - delete [] _data; + delete [] data_; } catch(...) { // Er... panic! } @@ -81,39 +143,21 @@ inline float sqr(const T& a) return a * a; } -static int verbose = 0; - -void usage(void) -{ - cerr << endl; - cerr << "usage: smoothest -d <fp-number> -m <filename> -r <filename>" << endl; - cerr << endl; - cerr << "Compulsory:" << endl; - cerr << "-d,--dof" << "\t" << "degrees of freedom" << endl; - cerr << "-m,--mask" << "\t" << "brain mask volume" << endl; - cerr << "-r,--res" << "\t" << "4d `residual-of-fit' image" << endl; - cerr << endl; - cerr << "Optional:" << endl; - cerr << "-v,--verbose" << "\t" << "display diagnostic messages" << endl; - cerr << "-h,--help" << "\t" << "display this message" << endl; - cerr << endl; - cerr << endl; -} - +// Standardise the residual field (assuming gaussianity) unsigned long standardise(Volume<unsigned char> *mask, Volume<float> **R, short M) { unsigned long count = 0; - if( M > 2 ) { - // Standardise the residual field assuming gaussianity - for ( unsigned int i = 0; i < mask->volsize(); i++) { + for ( unsigned int i = 0; i < mask->volsize(); i++) { - if( (*mask)(i) ) { + if( (*mask)(i) ) { - count ++; + count ++; + if( M > 2 ) { + // For each voxel // calculate mean and standard deviation... double Sx = 0.0, SSx = 0.0; @@ -140,134 +184,98 @@ unsigned long standardise(Volume<unsigned char> *mask, return count; } -int main(int argc, char **argv) { - - int hflag= 0, dflag = 1, mflag = 1, rflag = 1; - - static struct option long_options[] = - { - {"dof", 1, 0, 'd'}, - {"mask", 1, 0, 'm'}, - {"verbose", 0, &verbose, 1}, - {"res", 1, 0, 'r'}, - {"help", 0, &hflag, 1}, - {0, 0, 0, 0} - }; - - float v = 0; - int c; int option_index = 0; - - string *maskname = NULL, *residname = NULL; - - while( (c = getopt_long(argc, argv, "d:m:r:hv", - long_options, &option_index)) != -1 ) - { - switch(c) - { - case 0: - break; - case 'd': - v = atof(optarg); - dflag --; - break; - case 'm': - maskname = new string(optarg); - mflag --; - break; - case 'r': - residname = new string(optarg); - rflag --; - break; - case 'v': - verbose = 1; - break; - case 'h': - hflag = 1; - break; - case '?': - break; - default: - abort(); - } - } +string title = "\ +smoothest (Version 1.0b)\nCopyright(c) 2000, University of Oxford (Dave Flitney)"; - if ( hflag | dflag | mflag | rflag ) - { - usage(); - return EXIT_FAILURE; - } +string examples = "\ +\tsmoothest -d <number> -r <filename> -m <filename>\n\ +\tsmoothest -z <filename> -m <filename>"; + +int main(unsigned int argc, char **argv) { + + OptionParser options(title, examples); + + options.add(verbose); + options.add(help); + options.add(dof); + options.add(maskname); + options.add(residname); + options.add(zstatname); + + options.parse_command_line(argc, argv); + +// if(verbose.value()) options.check_compulsory_arguments(true); + + if(help.value()) { + options.usage(); + exit(EXIT_SUCCESS); + } + + if( !((zstatname.set() && residname.unset()) || (residname.set() && zstatname.unset())) || + (zstatname.set() && (residname.set() || dof.set())) || + (residname.set() && dof.unset()) || + (maskname.unset()) ) { + options.usage(); + cerr << endl; + cerr << "***************************************************************************" << endl; + cerr << "You must specify either a zstat image OR a 4d residual image." << endl; + cerr << "If processing a zstat image then you should not set degrees of freedom." << endl; + cerr << "You must specify a mask volume image filename" << endl; + cerr << "You must specify the degrees of freedom for processing a 4d residual image." << endl; + cerr << "***************************************************************************" << endl; + cerr << endl; + exit(EXIT_FAILURE); + } - if(verbose) { - cerr << "maskname = " << *maskname << endl; - cerr << "residname = " << *residname << endl; - cerr << "dof = " << v << endl; + if(verbose.value()) { + cout << "verbose = " << verbose.value() << endl; + cout << "help = " << help.value() << endl; + cout << "dof = " << dof.value() << endl; + cout << "maskname = " << maskname.value() << endl; + cout << "residname = " << residname.value() << endl; + cout << "zstatname = " << zstatname.value() << endl; } // Read the AVW mask image (single volume) AVW *maskfile; - if((maskfile=AvwOpen(maskname->c_str(),"r"))==NULL){ - printf("Failed to open mask: %s\n",maskname->c_str()); + if((maskfile=AvwOpen(maskname.value().c_str(),"r"))==NULL){ + printf("Failed to open mask: %s\n", maskname.value().c_str()); return(1); } - delete maskname; Volume<unsigned char> *mask = new Volume<unsigned char>(maskfile, 0); - // Read the AVW residual images (array of volumes) - AVW *residfile; - if((residfile=AvwOpen(residname->c_str(),"r"))==NULL){ - printf("Failed to open residuals: %s\n",residname->c_str()); - return(1); + AVW *datafile; + if(residname.set()) { + // Read the AVW residual images (array of volumes) + if((datafile = AvwOpen(residname.value().c_str(), "r")) == NULL){ + printf("Failed to open residuals: %s\n", residname.value().c_str()); + return(EXIT_FAILURE); + } + } else { + // Read the AVW zstat image (array of one volume) + if((datafile = AvwOpen(zstatname.value().c_str(), "r")) == NULL){ + printf("Failed to open zstat volume: %s\n", zstatname.value().c_str()); + return(EXIT_FAILURE); + } } + short xdim, ydim, zdim, M; - AvwGetDim(residfile, &xdim, &ydim, &zdim, &M); - Volume<float> **R = new Volume<float> * [M]; - for ( unsigned short i = 0; i < M; i++ ) { - R[i] = new Volume<float>(residfile, i); - } - delete residname; - - unsigned long count = standardise(mask, R, M); -// if( M > 2 ) { -// // Standardise the residual field assuming gaussianity -// for ( unsigned int i = 0; i < mask->volsize(); i++) { - -// if( (*mask)(i) ) { - -// count ++; - -// // For each voxel -// // calculate mean and standard deviation... -// double Sx = 0.0, SSx = 0.0; - -// for ( unsigned short t = 0; t < M; t++ ) { -// float& R_it = (*R[t])(i); - -// Sx += R_it; -// SSx += sqr(R_it); -// } - -// float mean = Sx / M; -// float sd = sqrt( (SSx - (sqr(Sx) / M)) / (M - 1) ); - -// // ... and use them to standardise to N(0, 1). -// for ( unsigned short t = 0; t < M; t++ ) { -// float& R_it = (*R[t])(i); - -// R_it = (R_it - mean) / sd; -// } -// } -// } -// } - - if(verbose) cerr << "Masked-in voxels = " << count << endl; + AvwGetDim(datafile, &xdim, &ydim, &zdim, &M); + Volume<float> **R = new Volume<float> * [M]; // R is an array of pointers to Volume<float> objects + for ( unsigned short i = 0; i < M; i++ ) + R[i] = new Volume<float>(datafile, i); + + unsigned long mask_volume = standardise(mask, R, M); + + if(verbose.value()) cerr << "Masked-in voxels = " << mask_volume << endl; - count = 0; // Reset count for the sums + unsigned long N = 0; // Estimate the smoothness of the normalised residual field + // see TR00DF1 for mathematical description of the algorithm. enum {X = 0, Y, Z}; - float F[3] = {0, 0, 0}; - float V0 = 0; + float SSminus[3] = {0, 0, 0}, S2 = 0; for ( unsigned short z = 1; z < zdim ; z++ ) for ( unsigned short y = 1; y < ydim ; y++ ) @@ -278,63 +286,76 @@ int main(int argc, char **argv) { (*mask)(x, y-1, z) && (*mask)(x, y, z-1) ) { - count ++; + N ++; for ( unsigned short t = 0; t < M; t++ ) { Volume<float>& S_it = *R[t]; // Sum over M - - F[X] += S_it(x, y, z) * S_it(x-1, y, z); - F[Y] += S_it(x, y, z) * S_it(x, y-1, z); - F[Z] += S_it(x, y, z) * S_it(x, y, z-1); - V0 += sqr(S_it(x, y, z)); + SSminus[X] += S_it(x, y, z) * S_it(x-1, y, z); + SSminus[Y] += S_it(x, y, z) * S_it(x, y-1, z); + SSminus[Z] += S_it(x, y, z) * S_it(x, y, z-1); + + S2 += sqr(S_it(x, y, z)); } } - - if(verbose) { - cerr << "Non-edge voxels = " << count << endl; - cerr << "(v - 2)/(v - 1) = " << (v - 2)/(v - 1) << endl; - } - float norm = (v - 2) / ((v - 1) * count * M); - F[X] *= norm; - F[Y] *= norm; - F[Z] *= norm; + + float norm = 1.0/(float) N; + float v = dof.value(); // v - degrees of freedom (nu) + if(M > 1) { + if(verbose.value()) { + cerr << "Non-edge voxels = " << N << endl; + cerr << "(v - 2)/(v - 1) = " << (v - 2)/(v - 1) << endl; + } + norm = (v - 2) / ((v - 1) * N * M); + } + + SSminus[X] *= norm; + SSminus[Y] *= norm; + SSminus[Z] *= norm; - V0 *= norm; + S2 *= norm; // Convert to sigma squared - float W[3]; - W[X] = -1.0 / (4 * log(fabs(F[X]/V0))); - W[Y] = -1.0 / (4 * log(fabs(F[Y]/V0))); - W[Z] = -1.0 / (4 * log(fabs(F[Z]/V0))); - - // Convert to full width half maximum - float FWHMx, FWHMy, FWHMz; - FWHMx = sqrt(8 * log(2) * W[X]); - FWHMy = sqrt(8 * log(2) * W[Y]); - FWHMz = sqrt(8 * log(2) * W[Z]); + float sigmasq[3]; + sigmasq[X] = -1.0 / (4 * log(fabs(SSminus[X]/S2))); + sigmasq[Y] = -1.0 / (4 * log(fabs(SSminus[Y]/S2))); + sigmasq[Z] = -1.0 / (4 * log(fabs(SSminus[Z]/S2))); - cout << "FWHMx = " << FWHMx << " voxels, " - << "FWHMy = " << FWHMy << " voxels, " - << "FWHMz = " << FWHMz << " voxels" << endl; - - float Wc = pow(FWHMx * FWHMy * FWHMz, 1.0/3.0) / sqrt(4 * log(2)); + float dLh = pow(sigmasq[X] * sigmasq[Y] * sigmasq[Z], -0.5) * pow(8, -0.5); + if(M > 1) dLh *= interpolate(v); + // Convert to full width half maximum + float FWHM[3]; + FWHM[X] = sqrt(8 * log(2) * sigmasq[X]); + FWHM[Y] = sqrt(8 * log(2) * sigmasq[Y]); + FWHM[Z] = sqrt(8 * log(2) * sigmasq[Z]); + float resels = FWHM[X] * FWHM[Y] * FWHM[Z]; + + if(verbose.value()) { + cerr << "FWHMx = " << FWHM[X] << " voxels, " + << "FWHMy = " << FWHM[Y] << " voxels, " + << "FWHMz = " << FWHM[Z] << " voxels" << endl; + } + // Scale results from voxels to real world dimensions, e.g., mm. float dx, dy, dz, dtr; - AvwGetVoxDim(residfile, &dx, &dy, &dz, &dtr); - FWHMx *= dx; FWHMy *= dy; FWHMz *= dz; - - cout << "FWHMx = " << FWHMx << " mm, " - << "FWHMy = " << FWHMy << " mm, " - << "FWHMz = " << FWHMz << " mm" << endl; - - float Fc = pow(FWHMx * FWHMy * FWHMz, 1.0/3.0); - - cout << "Fc = " << Fc << endl; - cout << "Wc = " << Wc << endl; + AvwGetVoxDim(datafile, &dx, &dy, &dz, &dtr); + FWHM[X] *= dx; FWHM[Y] *= dy; FWHM[Z] *= dz; + + if(verbose.value()) { + cerr << "FWHMx = " << FWHM[X] << " mm, " + << "FWHMy = " << FWHM[Y] << " mm, " + << "FWHMz = " << FWHM[Z] << " mm" << endl; + cerr << "DLH " << dLh << " voxels^-3" << endl; + cerr << "VOLUME " << mask_volume << " voxels" << endl; + cerr << "RESELS " << resels << " voxels per resel" << endl; + } + cout << "DLH " << dLh << endl; + cout << "VOLUME " << mask_volume << endl; + cout << "RESELS " << resels << endl; + return EXIT_SUCCESS; }