From 84f782e8d23cb95569ac24098515949809d4d855 Mon Sep 17 00:00:00 2001
From: Taylor Hanayik <hanayik@gmail.com>
Date: Fri, 16 Jun 2023 11:11:33 +0100
Subject: [PATCH] add --json option and associated logic

---
 .gitignore  |    1 +
 fslstats.cc | 1263 +++++++++++++++++++++++++++++++++++----------------
 2 files changed, 867 insertions(+), 397 deletions(-)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..600d2d3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.vscode
\ No newline at end of file
diff --git a/fslstats.cc b/fslstats.cc
index 03514a9..83a9873 100644
--- a/fslstats.cc
+++ b/fslstats.cc
@@ -15,16 +15,18 @@
 #include "newimage/newimageall.h"
 #include "newimage/costfns.h"
 
-
 using namespace std;
 using namespace NEWMAT;
 using namespace NEWIMAGE;
 
-int print_usage(const string& progname) {
-  cout << "Usage: fslstats [preoptions] <input> [options]" << endl << endl;
+int print_usage(const string &progname)
+{
+  cout << "Usage: fslstats [preoptions] <input> [options]" << endl
+       << endl;
   cout << "preoption -t will give a separate output line for each 3D volume of a 4D timeseries" << endl;
   cout << "preoption -K < indexMask > will generate seperate n submasks from indexMask, for indexvalues 1..n where n is the maximum index value in indexMask, and generate statistics for each submask" << endl;
-  cout << "Note - options are applied in order, e.g. -M -l 10 -M will report the non-zero mean, apply a threshold and then report the new nonzero mean" << endl << endl;
+  cout << "Note - options are applied in order, e.g. -M -l 10 -M will report the non-zero mean, apply a threshold and then report the new nonzero mean" << endl
+       << endl;
   cout << "-l <lthresh> : set lower threshold" << endl;
   cout << "-u <uthresh> : set upper threshold" << endl;
   cout << "-r           : output <robust min intensity> <robust max intensity>" << endl;
@@ -44,12 +46,16 @@ int print_usage(const string& progname) {
   cout << "-C           : output centre-of-gravity (cog) in voxel coordinates" << endl;
   cout << "-p <n>       : output nth percentile (n between 0 and 100)" << endl;
   cout << "-P <n>       : output nth percentile (for nonzero voxels)" << endl;
-  cout << "-a           : use absolute values of all image intensities"<< endl;
+  cout << "-a           : use absolute values of all image intensities" << endl;
   cout << "-n           : treat NaN or Inf as zero for subsequent stats" << endl;
   cout << "-k <mask>    : use the specified image (filename) for masking - overrides lower and upper thresholds" << endl;
   cout << "-d <image>   : take the difference between the base image and the image specified here" << endl;
   cout << "-h <nbins>   : output a histogram (for the thresholded/masked voxels only) with nbins" << endl;
-  cout << "-H <nbins> <min> <max>   : output a histogram (for the thresholded/masked voxels only) with nbins and histogram limits of min and max" << endl << endl;
+  cout << "-H <nbins> <min> <max>   : output a histogram (for the thresholded/masked voxels only) with nbins and histogram limits of min and max" << endl;
+  // add --json output format option
+  // keys will correspond to the options above
+  cout << "--json       : output in JSON format" << endl
+       << endl;
   cout << "Note - thresholds are not inclusive ie lthresh<allowed<uthresh" << endl;
   return 1;
 }
@@ -57,461 +63,924 @@ int print_usage(const string& progname) {
 // Some specialised nonzero functions just for speedup
 //  (it avoids generating masks when not absolutely necessary)
 
-long nonzerocount(const volume4D<float>& vol)
+long nonzerocount(const volume4D<float> &vol)
 {
-  long totn=0;
-  for(volume4D<float>::fast_const_iterator it=vol.fbegin(), end=vol.fend(); it<end; it++ )
-    if ( (*it) != 0 )
+  long totn = 0;
+  for (volume4D<float>::fast_const_iterator it = vol.fbegin(), end = vol.fend(); it < end; it++)
+    if ((*it) != 0)
       totn++;
   return totn;
 }
 
-double nonzeromean(const volume4D<float>& vol)
+double nonzeromean(const volume4D<float> &vol)
 {
-  double total=0.0;
-  long int totn=0;
-  for(volume4D<float>::fast_const_iterator it=vol.fbegin(), end=vol.fend(); it<end; it++ )
-    if ( (*it) != 0 ) {
-      total+=(double)(*it);
+  double total = 0.0;
+  long int totn = 0;
+  for (volume4D<float>::fast_const_iterator it = vol.fbegin(), end = vol.fend(); it < end; it++)
+    if ((*it) != 0)
+    {
+      total += (double)(*it);
       totn++;
     }
-  if (totn>0)
-    total/=totn;
+  if (totn > 0)
+    total /= totn;
   return total;
 }
 
-double nonzerostddev(const volume4D<float>& vol)
+double nonzerostddev(const volume4D<float> &vol)
 {
-  double totv=0.0, totvv=0.0;
-  long int totn=0;
-  for(volume4D<float>::fast_const_iterator it=vol.fbegin(), end=vol.fend(); it<end; it++ )
-    if ( (*it) != 0 ) {
-      totvv+=(double)((*it)*(*it));
-      totv+=(double)(*it);
+  double totv = 0.0, totvv = 0.0;
+  long int totn = 0;
+  for (volume4D<float>::fast_const_iterator it = vol.fbegin(), end = vol.fend(); it < end; it++)
+    if ((*it) != 0)
+    {
+      totvv += (double)((*it) * (*it));
+      totv += (double)(*it);
       totn++;
     }
-  double var=0.0;
-  if (totn>1) {
-    double meanval = totv/totn;
-    var = (totvv - totn*meanval*meanval)/(totn-1);
+  double var = 0.0;
+  if (totn > 1)
+  {
+    double meanval = totv / totn;
+    var = (totvv - totn * meanval * meanval) / (totn - 1);
   }
   return std::sqrt(var);
 }
 
-template<class M>
+template <class M>
 int generateNonZeroMask(const M &mask, volume4D<float> &masknz, const volume4D<float> &input)
 {
-  masknz = binarise(input,0.0f,0.0f,inclusive,true)*mask;
+  masknz = binarise(input, 0.0f, 0.0f, inclusive, true) * mask;
   return 0;
 }
 
-int generate_masks(volume4D<float>& mask, volume4D<float>& masknz, const volume4D<float>& input, const float& lthr, const float& uthr)
+int generate_masks(volume4D<float> &mask, volume4D<float> &masknz, const volume4D<float> &input, const float &lthr, const float &uthr)
+{
+  mask = binarise(input, lthr, uthr, exclusive);
+  return generateNonZeroMask(mask, masknz, input);
+}
+
+// print function that can handle outputting in JSON format or not
+void print_value(const string &key, const string  &value, string &json, const bool jsonOutput)
 {
-  mask = binarise(input,lthr,uthr,exclusive);
-  return generateNonZeroMask(mask,masknz,input);
+  // append the key and value to the json string
+  if (jsonOutput)
+  {
+    json.append("\"" + key + "\": \"" + value + "\", ");
+  }
+  else
+  {
+    cout << value << " ";
+  }
 }
 
-int fmrib_main_float(int argc, char* argv[],const bool timeseriesMode, const string& indexMaskName)
+
+int fmrib_main_float(int argc, char *argv[], const bool timeseriesMode, const string &indexMaskName)
 {
+  // determine if the user wants to output in JSON format
+  bool jsonOutput = false;
+  for (int i = 1; i < argc; i++)
+  {
+    if (strcmp(argv[i], "--json") == 0)
+    {
+      jsonOutput = true;
+      break;
+    }
+  }
+  // if JSON output is requested, then we need to store the output as a string
+  // and then output it at the end via cout
+  // don't forget to close the json at the end with a "}"
+  string json = "";
+  
   cout.setf(ios::dec);
   cout.setf(ios::fixed, ios::floatfield);
   cout.setf(ios::left, ios::adjustfield);
   cout.precision(6);
   volume<float> vol, inputMaster;
   volume<int> indexMask;
-  if ( timeseriesMode || indexMaskName != "" )
-    read_volume4D(inputMaster,argv[1]);
+  if (timeseriesMode || indexMaskName != "")
+    read_volume4D(inputMaster, argv[1]);
   else
-    read_volume4D(vol,argv[1]);
-  volume<float> & indexMaster = (timeseriesMode ) ? vol : inputMaster;
-  if ( indexMaskName != "" )
-    read_volume4D(indexMask,indexMaskName);
+    read_volume4D(vol, argv[1]);
+  volume<float> &indexMaster = (timeseriesMode) ? vol : inputMaster;
+  if (indexMaskName != "")
+    read_volume4D(indexMask, indexMaskName);
   int nTimepoints((timeseriesMode) ? inputMaster.tsize() : 1), nIndices((indexMaskName != "") ? indexMask.max() : 1);
-  for ( int timepoint=0; timepoint < nTimepoints ; timepoint++ ) {
-   for ( int index=1; index <= nIndices; index++ ) {
-    if ( timeseriesMode )
-      vol=inputMaster[timepoint];
-    volume<float> mask, masknz;
-    float lthr(-numeric_limits<float>::max());
-    float uthr(numeric_limits<float>::max());
-    if ( indexMask.nvoxels() ) {
-      if ( indexMask.dimensionality() > 3 ) {
-	cerr << "Index mask must be 3D" << endl;
-        return 3;
-      }
-      copyconvert(indexMask,mask);
-      mask.binarise(index-1,index+1,exclusive);
-      vol=indexMaster*mask;
-      if (mask.max() < 1) {
-    		cout << "missing label: " << index << " in mask" << endl;
-    		continue;
-    	}
-      generateNonZeroMask(mask,masknz,vol);
-    }
-    int narg(2);
-
-  while (narg<argc) {
-    string sarg(argv[narg]);
-    if (sarg=="-n") {
-      for (int t=0; t<vol.tsize(); t++)
-        for (int z=0; z<vol.zsize(); z++)
-          for (int y=0; y<vol.ysize(); y++)
-            for (int x=0; x<vol.xsize(); x++)
-              if (!isfinite((double)vol(x,y,z,t)))
-	        vol(x,y,z,t)=0;
-    } else if (sarg=="-m") {
-      if (mask.nvoxels()>0) cout <<  vol.mean(mask) << " ";
-      else cout << vol.mean() << " ";
-    } else if (sarg=="-M") {
-      if (masknz.nvoxels()>0) cout << vol.mean(masknz) << " ";
-      else {
-	double nzmean=0;
-	nzmean = nonzeromean(vol);
-	cout << nzmean << " ";
-      }
-    } else if (sarg=="-X") {
-      ColumnVector coord(4);
-      vector<int64_t> iCoords;
-      vol.min(mask,iCoords);
-      coord << (Real)iCoords[7] << (Real)iCoords[8] << (Real)iCoords[9] << 1.0;
-      coord = vol.niftivox2newimagevox_mat().i() * coord;
-      cout << MISCMATHS::round(coord(1)) << " " <<
-	MISCMATHS::round(coord(2)) << " " << MISCMATHS::round(coord(3)) << " ";
-    } else if (sarg=="-x") {
-      ColumnVector coord(4);
-      vector<int64_t> iCoords;
-      vol.min(mask,iCoords);
-      coord << (Real)iCoords[0] << (Real)iCoords[1] << (Real)iCoords[2] << 1.0;
-      coord = vol.niftivox2newimagevox_mat().i() * coord;
-      cout << MISCMATHS::round(coord(1)) << " " <<
-	MISCMATHS::round(coord(2)) << " " << MISCMATHS::round(coord(3)) << " ";
-    } else if (sarg=="-w") {
-	if (masknz.nvoxels()<1) { //Need to generate non-zeromask
-	  generate_masks(mask,masknz,vol,lthr,uthr);
-	  vol*=mask;
-	}
-	int xmin=masknz.xsize()-1,xmax(0),ymin=masknz.ysize()-1,ymax(0),zmin=masknz.zsize()-1,zmax(0),tmin=masknz.tsize()-1,tmax(0);
 
-      for(int t=0;t<masknz.tsize();t++)
-	for(int z=0;z<masknz.zsize();z++)
-	  for(int y=0;y<masknz.ysize();y++)
-	    for(int x=0;x<masknz.xsize();x++)
-	      if (masknz(x,y,z,t)>0.5) {
-		if (x<xmin) xmin=x;
-		if (x>xmax) xmax=x;
-		if (y<ymin) ymin=y;
-		if (y>ymax) ymax=y;
-		if (z<zmin) zmin=z;
-		if (z>zmax) zmax=z;
-		if (t<tmin) tmin=t;
-		if (t>tmax) tmax=t;
-	      }
-      // change voxel coords from newimage to nifti convention for output
-      ColumnVector v(4);
-      v << xmin << ymin << zmin << 1.0;
-      v = masknz.niftivox2newimagevox_mat().i() * v;
-      xmin = MISCMATHS::round(v(1));
-      ymin = MISCMATHS::round(v(2));
-      zmin = MISCMATHS::round(v(3));
-      v << xmax << ymax << zmax << 1.0;
-      v = masknz.niftivox2newimagevox_mat().i() * v;
-      xmax = MISCMATHS::round(v(1));
-      ymax = MISCMATHS::round(v(2));
-      zmax = MISCMATHS::round(v(3));
-      if (xmin>xmax) { int tmp=xmax;  xmax=xmin;  xmin=tmp; }
-      if (ymin>ymax) { int tmp=ymax;  ymax=ymin;  ymin=tmp; }
-      if (zmin>zmax) { int tmp=zmax;  zmax=zmin;  zmin=tmp; }
-      // now output nifti coords
-      cout << xmin << " " << 1+xmax-xmin << " " << ymin << " " << 1+ymax-ymin << " " << zmin << " " << 1+zmax-zmin << " " << tmin << " " << 1+tmax-tmin << " ";
-      } else if (sarg=="-e") {
-	if (mask.nvoxels()<1) {
-	  generate_masks(mask,masknz,vol,lthr,uthr);
-	  vol*=mask;
-	}
-      ColumnVector hist;
-      int nbins=1000;
-      double entropy=0;
-      hist = vol.histogram(nbins,mask);
-      double ntot = hist.Sum();
-      for (int j=1; j<=nbins; j++) {
-	if (hist(j)>0) {
-	  entropy -= (hist(j)/ntot) * log(hist(j)/ntot);
-	}
-      }
-      entropy /= log((double) nbins);
-      cout << entropy << " ";
-      } else if (sarg=="-E") {
-      ColumnVector hist;
-      int nbins=1000;
-      double entropy=0;
-      if (mask.nvoxels()<1) {
-	generate_masks(mask,masknz,vol,lthr,uthr);
-	vol*=mask;
-      }
-      hist = vol.histogram(nbins,masknz);
-      double ntot = hist.Sum();
-      for (int j=1; j<=nbins; j++) {
-	if (hist(j)>0) {
-	  entropy -= (hist(j)/ntot) * log(hist(j)/ntot);
-	}
-      }
-      entropy /= log((double) nbins);
-      cout << entropy << " ";
-    } else if (sarg=="-k") {
-      narg++;
-      volume<float> mask2;
-      if (narg>=argc) {
-	cerr << "Must specify an argument to -k" << endl;
-	exit(2);
-      }
-      read_volume4D(mask,argv[narg]);
-      if ( !samesize(mask,vol,3) || mask.tsize() > vol.tsize() ) {
-	cerr << "Mask and image must be the same size" << endl;
-	exit(3);
-      }
-      if (timeseriesMode && mask.tsize() != 1 ) { mask2=mask[timepoint]; }
-      if ( mask.tsize() != vol.tsize() && mask.tsize() != 1)
-	while (mask.tsize() < vol.tsize() ) { // copy the last max volume until the correct size is reached
-   	  mask.addvolume(mask[mask.tsize()-1]);
-        }
-      mask.binarise(0.5);
-      if (mask.tsize()!=1) {
-	generateNonZeroMask(mask,masknz,vol);
-	vol*=mask;
-      }
-      else {
-	generateNonZeroMask(mask[0],masknz,vol);
-	vol*=mask[0];
-      }
-    } else if (sarg=="-d") {
-      narg++;
-      if (narg>=argc) {
-	cerr << "Must specify an argument to -d" << endl;
-	exit(2);
-      }
-      volume4D<float> image2,image3;
-      read_volume4D(image2,argv[narg]);
-      if (!samesize(image2[0],vol[0])) {
-	cerr << "Image used with -d must be the same size as the base image" << endl;
-	exit(3);
-      }
-      if (timeseriesMode) { image3=image2[timepoint]; }
-      if ( image2.tsize() > vol.tsize() ) {
-	cerr << "Image must be the same size as the base image" << endl;
-	exit(3);
-      }
-      if ( image2.tsize() != vol.tsize() && image2.tsize() != 1) {
-	// copy the last max volume until the correct size is reached
-	while (image2.tsize() < vol.tsize() ) {
-	  image2.addvolume(image2[image2.maxt()]);
-	}
-      }
-      // now substract the new image from the base image
-      vol -= image2;
-     } else if (sarg=="-l") {
-      narg++;
-      if (narg<argc) {
-        lthr = atof(argv[narg]);
-      } else {
-	cerr << "Must specify an argument to -l" << endl;
-	exit(2);
-      }
-      generate_masks(mask,masknz,vol,lthr,uthr);
-      vol*=mask;
-    } else if (sarg=="-u") {
-      narg++;
-      if (narg<argc) {
-        uthr = atof(argv[narg]);
-      } else {
-	cerr << "Must specify an argument to -u" << endl;
-	exit(2);
-      }
-      generate_masks(mask,masknz,vol,lthr,uthr);
-      vol*=mask;
-    } else if (sarg=="-a") {
-      vol = abs(vol);
-    } else if (sarg=="-v") {
-      if (mask.nvoxels()>0) {
-        long int nvox = mask.sum();
-        if (mask.tsize() == 1) nvox = nvox * vol.tsize();
-	cout << (long int) nvox << " "
-	     << nvox * vol.xdim() * vol.ydim() * vol.zdim() << " ";
-      } else {
-	cout << (long int) vol.nvoxels() * vol.tsize() << " "
-	     << vol.nvoxels() * vol.tsize() * vol.xdim() * vol.ydim() * vol.zdim() << " ";
-      }
-    } else if (sarg=="-V") {
-      if (masknz.nvoxels()>0) {
-	cout << (long int) masknz.sum() << " "
-	     << masknz.sum() * vol.xdim() * vol.ydim() * vol.zdim() << " ";
-      } else {
-	long int nzvox;
-	nzvox = nonzerocount(vol);
-	cout << nzvox << " "
-	     << nzvox * vol.xdim() * vol.ydim() * vol.zdim() << " ";
-      }
-    } else if (sarg=="-D") {
-	// hidden debug option!
-      cout << vol.sum() << " ";
-    } else if (sarg=="-s") {
-	if (mask.nvoxels()>0) cout << vol.stddev(mask) << " ";
-	else cout << vol.stddev() << " ";
-    } else if (sarg=="-S") {
-      if (masknz.nvoxels()>0) {
-	cout << vol.stddev(masknz) << " ";
-      } else {
-	cout << nonzerostddev(vol) << " ";
-      }
-    } else if (sarg=="-r") {
-      vector<float> limits(vol.robustlimits(mask));
-      cout << limits[0] << " " << limits[1] << " ";
-    } else if (sarg=="-R") {
-      cout << vol.min(mask) << " " << vol.max(mask) << " ";
-    } else if (sarg=="-c") {
-	ColumnVector cog(4);
-	// convert from fsl mm to voxel to nifti sform coord
-	cog.SubMatrix(1,3,1,1) = vol.cog();
-	cog(4) = 1.0;
-	cog = vol.newimagevox2mm_mat() * cog;
-	cout << cog(1) << " " << cog(2) << " " << cog(3) << " " ;
-    } else if (sarg=="-C") {
-    ColumnVector cog(4);
-	// convert from fsl mm to fsl voxel coord to nifti voxel coord
-	cog.SubMatrix(1,3,1,1) = vol.cog();
-	cog(4) = 1.0;
-	cog = vol.niftivox2newimagevox_mat().i() * cog;
-	cout << cog(1) << " " << cog(2) << " " << cog(3) << " " ;
-    } else if (sarg=="-p") {
-      float n;
-      narg++;
-      if (narg<argc) {
-        n = atof(argv[narg]);
-      } else {
-	cerr << "Must specify an argument to -p" << endl;
-	exit(2);
-      }
-      if ( (n<0) || (n>100) ) {
-    	cerr << "Percentile must be between 0 and 100" << endl;
-    	exit(1);
-      }
-      if (mask.nvoxels()>0) cout << vol.percentile((float) n/100.0, mask) << " ";
-      else cout << vol.percentile((float) n/100.0) << " ";
-    } else if (sarg=="-P") {
-      float n;
-      narg++;
-      if (narg<argc) {
-        n = atof(argv[narg]);
-      } else {
-	cerr << "Must specify an argument to -P" << endl;
-	exit(2);
-      }
-      if ( (n<0) || (n>100) ) {
-    	cerr << "Percentile must be between 0 and 100" << endl;
-    	exit(1);
-      }
-      if (mask.nvoxels()<1) {
-	generate_masks(mask,masknz,vol,lthr,uthr);
-	vol*=mask;
-      }
-      cout << vol.percentile((float) n/100.0,masknz) << " ";
-    } else if (sarg=="-h") {
-      float n;
-      narg++;
-      if (narg<argc) {
-        n = atof(argv[narg]);
-      } else {
-	cerr << "Must specify the number of bins" << endl;
-	exit(2);
-      }
-      int nbins = (int) n;
-      if (nbins<1) {
-    	cerr << "Must specify at least 1 bin" << endl;
-    	exit(1);
-      }
-      if (mask.nvoxels()>0) {
-	cout << vol.histogram(nbins,vol.min(),vol.max(),mask) << " ";
+  for (int timepoint = 0; timepoint < nTimepoints; timepoint++)
+  {
+    if (jsonOutput)
+    {
+      if (timepoint == 0){
+        json.append("{\"volumes\": [");
       } else {
-	cout << vol.histogram(nbins,vol.min(),vol.max()) << " ";
+        json.append(", ");
       }
-   } else if (sarg=="-H") {
-      float n;
-      narg++;
-      if (narg<argc) {
-        n = atof(argv[narg]);
-      } else {
-	cerr << "Must specify the number of bins" << endl;
-	exit(2);
+    }
+    for (int index = 1; index <= nIndices; index++)
+    {
+      // if json mode is enabled
+      if (jsonOutput)
+      {
+        if (index == 1){
+          json.append("{\"indices\": [{");
+        } else {
+          json.append(", {");
+        }
       }
-      int nbins = (int) n;
-      if (nbins<1) {
-    	cerr << "Must specify at least 1 bin" << endl;
-    	exit(1);
+      if (timeseriesMode)
+        vol = inputMaster[timepoint];
+      volume<float> mask, masknz;
+      float lthr(-numeric_limits<float>::max());
+      float uthr(numeric_limits<float>::max());
+      if (indexMask.nvoxels())
+      {
+        if (indexMask.dimensionality() > 3)
+        {
+          cerr << "Index mask must be 3D" << endl;
+          return 3;
+        }
+        copyconvert(indexMask, mask);
+        mask.binarise(index - 1, index + 1, exclusive);
+        vol = indexMaster * mask;
+        if (mask.max() < 1)
+        {
+          cout << "missing label: " << index << " in mask" << endl;
+          continue;
+        }
+        generateNonZeroMask(mask, masknz, vol);
       }
-      float min=0;
-      narg++;
-      if (narg<argc) {
-        min = atof(argv[narg]);
-      } else {
-	cerr << "Must specify the histogram minimum intensity" << endl;
-	exit(2);
+      int narg(2);
+      bool last = false;
+      while (narg < argc)
+      {
+
+        last = !(narg < argc - 1);
+        string sarg(argv[narg]);
+        if (sarg == "-n")
+        {
+          for (int t = 0; t < vol.tsize(); t++)
+            for (int z = 0; z < vol.zsize(); z++)
+              for (int y = 0; y < vol.ysize(); y++)
+                for (int x = 0; x < vol.xsize(); x++)
+                  if (!isfinite((double)vol(x, y, z, t)))
+                    vol(x, y, z, t) = 0;
+        }
+        else if (sarg == "-m")
+        {
+          if (mask.nvoxels() > 0){
+            string val = to_string(vol.mean(mask));
+            print_value(sarg, val, json, jsonOutput);
+          } else {
+            string val = to_string(vol.mean());
+            print_value(sarg, val, json, jsonOutput);
+          }
+        }
+        else if (sarg == "-M")
+        {
+          if (masknz.nvoxels() > 0)
+            print_value(sarg, to_string(vol.mean(masknz)), json, jsonOutput);
+          else
+          {
+            double nzmean = 0;
+            nzmean = nonzeromean(vol);
+            string val = to_string(nzmean);
+            print_value(sarg, val, json, jsonOutput);
+          }
+        }
+        else if (sarg == "-X")
+        {
+          ColumnVector coord(4);
+          vector<int64_t> iCoords;
+          vol.min(mask, iCoords);
+          coord << (Real)iCoords[7] << (Real)iCoords[8] << (Real)iCoords[9] << 1.0;
+          coord = vol.niftivox2newimagevox_mat().i() * coord;
+          //cout << MISCMATHS::round(coord(1)) << " " << MISCMATHS::round(coord(2)) << " " << MISCMATHS::round(coord(3)) << " ";
+          auto coord1 = MISCMATHS::round(coord(1));
+          auto coord2 = MISCMATHS::round(coord(2));
+          auto coord3 = MISCMATHS::round(coord(3));
+          string coordString = "";
+          coordString.append(to_string(coord1));
+          coordString.append(" ");
+          coordString.append(to_string(coord2));
+          coordString.append(" ");
+          coordString.append(to_string(coord3));
+          print_value(
+            sarg,
+            coordString,
+            json,
+            jsonOutput);
+          
+        }
+        else if (sarg == "-x")
+        {
+          ColumnVector coord(4);
+          vector<int64_t> iCoords;
+          vol.min(mask, iCoords);
+          coord << (Real)iCoords[0] << (Real)iCoords[1] << (Real)iCoords[2] << 1.0;
+          coord = vol.niftivox2newimagevox_mat().i() * coord;
+          //cout << MISCMATHS::round(coord(1)) << " " << MISCMATHS::round(coord(2)) << " " << MISCMATHS::round(coord(3)) << " ";
+          auto coord1 = MISCMATHS::round(coord(1));
+          auto coord2 = MISCMATHS::round(coord(2));
+          auto coord3 = MISCMATHS::round(coord(3));
+          string coordString = "";
+          coordString.append(to_string(coord1));
+          coordString.append(" ");
+          coordString.append(to_string(coord2));
+          coordString.append(" ");
+          coordString.append(to_string(coord3));
+          print_value(
+            sarg,
+            coordString,
+            json,
+            jsonOutput);
+        }
+        else if (sarg == "-w")
+        {
+          if (masknz.nvoxels() < 1)
+          { // Need to generate non-zeromask
+            generate_masks(mask, masknz, vol, lthr, uthr);
+            vol *= mask;
+          }
+          int xmin = masknz.xsize() - 1, xmax(0), ymin = masknz.ysize() - 1, ymax(0), zmin = masknz.zsize() - 1, zmax(0), tmin = masknz.tsize() - 1, tmax(0);
+
+          for (int t = 0; t < masknz.tsize(); t++)
+            for (int z = 0; z < masknz.zsize(); z++)
+              for (int y = 0; y < masknz.ysize(); y++)
+                for (int x = 0; x < masknz.xsize(); x++)
+                  if (masknz(x, y, z, t) > 0.5)
+                  {
+                    if (x < xmin)
+                      xmin = x;
+                    if (x > xmax)
+                      xmax = x;
+                    if (y < ymin)
+                      ymin = y;
+                    if (y > ymax)
+                      ymax = y;
+                    if (z < zmin)
+                      zmin = z;
+                    if (z > zmax)
+                      zmax = z;
+                    if (t < tmin)
+                      tmin = t;
+                    if (t > tmax)
+                      tmax = t;
+                  }
+          // change voxel coords from newimage to nifti convention for output
+          ColumnVector v(4);
+          v << xmin << ymin << zmin << 1.0;
+          v = masknz.niftivox2newimagevox_mat().i() * v;
+          xmin = MISCMATHS::round(v(1));
+          ymin = MISCMATHS::round(v(2));
+          zmin = MISCMATHS::round(v(3));
+          v << xmax << ymax << zmax << 1.0;
+          v = masknz.niftivox2newimagevox_mat().i() * v;
+          xmax = MISCMATHS::round(v(1));
+          ymax = MISCMATHS::round(v(2));
+          zmax = MISCMATHS::round(v(3));
+          if (xmin > xmax)
+          {
+            int tmp = xmax;
+            xmax = xmin;
+            xmin = tmp;
+          }
+          if (ymin > ymax)
+          {
+            int tmp = ymax;
+            ymax = ymin;
+            ymin = tmp;
+          }
+          if (zmin > zmax)
+          {
+            int tmp = zmax;
+            zmax = zmin;
+            zmin = tmp;
+          }
+          // now output nifti coords
+          string coords = "";
+          coords.append(to_string(xmin));
+          coords.append(" ");
+          coords.append(to_string(1 + xmax - xmin));
+          coords.append(" ");
+          coords.append(to_string(ymin));
+          coords.append(" ");
+          coords.append(to_string(1 + ymax - ymin));
+          coords.append(" ");
+          coords.append(to_string(zmin));
+          coords.append(" ");
+          coords.append(to_string(1 + zmax - zmin));
+          coords.append(" ");
+          coords.append(to_string(tmin));
+          coords.append(" ");
+          coords.append(to_string(1 + tmax - tmin));
+          print_value(
+            sarg,
+            coords,
+            json,
+            jsonOutput);
+        }
+        else if (sarg == "-e")
+        {
+          if (mask.nvoxels() < 1)
+          {
+            generate_masks(mask, masknz, vol, lthr, uthr);
+            vol *= mask;
+          }
+          ColumnVector hist;
+          int nbins = 1000;
+          double entropy = 0;
+          hist = vol.histogram(nbins, mask);
+          double ntot = hist.Sum();
+          for (int j = 1; j <= nbins; j++)
+          {
+            if (hist(j) > 0)
+            {
+              entropy -= (hist(j) / ntot) * log(hist(j) / ntot);
+            }
+          }
+          entropy /= log((double)nbins);
+          print_value(
+            sarg,
+            to_string(entropy),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-E")
+        {
+          ColumnVector hist;
+          int nbins = 1000;
+          double entropy = 0;
+          if (mask.nvoxels() < 1)
+          {
+            generate_masks(mask, masknz, vol, lthr, uthr);
+            vol *= mask;
+          }
+          hist = vol.histogram(nbins, masknz);
+          double ntot = hist.Sum();
+          for (int j = 1; j <= nbins; j++)
+          {
+            if (hist(j) > 0)
+            {
+              entropy -= (hist(j) / ntot) * log(hist(j) / ntot);
+            }
+          }
+          entropy /= log((double)nbins);
+          print_value(
+            sarg,
+            to_string(entropy),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-k")
+        {
+          narg++;
+          volume<float> mask2;
+          if (narg >= argc)
+          {
+            cerr << "Must specify an argument to -k" << endl;
+            exit(2);
+          }
+          read_volume4D(mask, argv[narg]);
+          if (!samesize(mask, vol, 3) || mask.tsize() > vol.tsize())
+          {
+            cerr << "Mask and image must be the same size" << endl;
+            exit(3);
+          }
+          if (timeseriesMode && mask.tsize() != 1)
+          {
+            mask2 = mask[timepoint];
+          }
+          if (mask.tsize() != vol.tsize() && mask.tsize() != 1)
+            while (mask.tsize() < vol.tsize())
+            { // copy the last max volume until the correct size is reached
+              mask.addvolume(mask[mask.tsize() - 1]);
+            }
+          mask.binarise(0.5);
+          if (mask.tsize() != 1)
+          {
+            generateNonZeroMask(mask, masknz, vol);
+            vol *= mask;
+          }
+          else
+          {
+            generateNonZeroMask(mask[0], masknz, vol);
+            vol *= mask[0];
+          }
+        }
+        else if (sarg == "-d")
+        {
+          narg++;
+          if (narg >= argc)
+          {
+            cerr << "Must specify an argument to -d" << endl;
+            exit(2);
+          }
+          volume4D<float> image2, image3;
+          read_volume4D(image2, argv[narg]);
+          if (!samesize(image2[0], vol[0]))
+          {
+            cerr << "Image used with -d must be the same size as the base image" << endl;
+            exit(3);
+          }
+          if (timeseriesMode)
+          {
+            image3 = image2[timepoint];
+          }
+          if (image2.tsize() > vol.tsize())
+          {
+            cerr << "Image must be the same size as the base image" << endl;
+            exit(3);
+          }
+          if (image2.tsize() != vol.tsize() && image2.tsize() != 1)
+          {
+            // copy the last max volume until the correct size is reached
+            while (image2.tsize() < vol.tsize())
+            {
+              image2.addvolume(image2[image2.maxt()]);
+            }
+          }
+          // now substract the new image from the base image
+          vol -= image2;
+        }
+        else if (sarg == "-l")
+        {
+          narg++;
+          if (narg < argc)
+          {
+            lthr = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify an argument to -l" << endl;
+            exit(2);
+          }
+          generate_masks(mask, masknz, vol, lthr, uthr);
+          vol *= mask;
+        }
+        else if (sarg == "-u")
+        {
+          narg++;
+          if (narg < argc)
+          {
+            uthr = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify an argument to -u" << endl;
+            exit(2);
+          }
+          generate_masks(mask, masknz, vol, lthr, uthr);
+          vol *= mask;
+        }
+        else if (sarg == "-a")
+        {
+          vol = abs(vol);
+        }
+        else if (sarg == "-v")
+        {
+          if (mask.nvoxels() > 0)
+          {
+            long int nvox = mask.sum();
+            if (mask.tsize() == 1)
+              nvox = nvox * vol.tsize();
+            
+            string nvox_str = "";
+            nvox_str.append(to_string(nvox));
+            nvox_str.append(" ");
+            nvox_str.append(to_string(nvox * vol.xdim() * vol.ydim() * vol.zdim()));
+            print_value(
+              sarg,
+              nvox_str,
+              json,
+              jsonOutput
+            );
+          }
+          else
+          {
+            string nvox_str = "";
+            nvox_str.append(to_string((long int)vol.nvoxels() * vol.tsize()));
+            nvox_str.append(" ");
+            nvox_str.append(to_string(vol.nvoxels() * vol.tsize() * vol.xdim() * vol.ydim() * vol.zdim()));
+            print_value(
+              sarg,
+              nvox_str,
+              json,
+              jsonOutput);
+          }
+        }
+        else if (sarg == "-V")
+        {
+          if (masknz.nvoxels() > 0)
+          {
+            string nvox_str = "";
+            nvox_str.append(to_string((long int)masknz.sum()));
+            nvox_str.append(" ");
+            nvox_str.append(to_string(masknz.sum() * vol.xdim() * vol.ydim() * vol.zdim()));
+            print_value(
+              sarg,
+              nvox_str,
+              json,
+              jsonOutput);
+          }
+          else
+          {
+            long int nzvox;
+            nzvox = nonzerocount(vol);
+            string nzvox_str = "";
+            nzvox_str.append(to_string(nzvox));
+            nzvox_str.append(" ");
+            nzvox_str.append(to_string(nzvox * vol.xdim() * vol.ydim() * vol.zdim()));
+            print_value(
+              sarg,
+              nzvox_str,
+              json,
+              jsonOutput);
+          }
+        }
+        else if (sarg == "-D")
+        {
+          // hidden debug option!
+          print_value(
+            sarg,
+            to_string(vol.sum()),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-s")
+        {
+          if (mask.nvoxels() > 0)
+            print_value(
+              sarg,
+              to_string(vol.stddev(mask)),
+              json,
+              jsonOutput
+            );
+          else
+            print_value(
+              sarg,
+              to_string(vol.stddev()),
+              json,
+              jsonOutput
+            );
+        }
+        else if (sarg == "-S")
+        {
+          if (masknz.nvoxels() > 0)
+          {
+            print_value(
+              sarg,
+              to_string(vol.stddev(masknz)),
+              json,
+              jsonOutput
+            );
+          }
+          else
+          {
+            print_value(
+              sarg,
+              to_string(nonzerostddev(vol)),
+              json,
+              jsonOutput
+            );
+          }
+        }
+        else if (sarg == "-r")
+        {
+          vector<float> limits(vol.robustlimits(mask));
+          print_value(
+            sarg,
+            to_string(limits[0]).append(" ").append(to_string(limits[1])),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-R")
+        {
+          print_value(
+            sarg,
+            to_string(vol.min(mask)).append(" ").append(to_string(vol.max(mask))),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-c")
+        {
+          ColumnVector cog(4);
+          cog.SubMatrix(1, 3, 1, 1) = vol.cog();
+          cog(4) = 1.0;
+          cog = vol.newimagevox2mm_mat() * cog;
+          print_value(
+            sarg,
+            to_string(cog(1)).append(" ").append(to_string(cog(2))).append(" ").append(to_string(cog(3))),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-C")
+        {
+          ColumnVector cog(4);
+          // convert from fsl mm to fsl voxel coord to nifti voxel coord
+          cog.SubMatrix(1, 3, 1, 1) = vol.cog();
+          cog(4) = 1.0;
+          cog = vol.niftivox2newimagevox_mat().i() * cog;
+          print_value(
+            sarg,
+            to_string(cog(1)).append(" ").append(to_string(cog(2))).append(" ").append(to_string(cog(3))),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-p")
+        {
+          float n;
+          narg++;
+          if (narg < argc)
+          {
+            n = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify an argument to -p" << endl;
+            exit(2);
+          }
+          if ((n < 0) || (n > 100))
+          {
+            cerr << "Percentile must be between 0 and 100" << endl;
+            exit(1);
+          }
+          if (mask.nvoxels() > 0)
+            print_value(
+              sarg,
+              to_string(vol.percentile((float)n / 100.0, mask)),
+              json,
+              jsonOutput
+            );
+          else
+            print_value(
+              sarg,
+              to_string(vol.percentile((float)n / 100.0)),
+              json,
+              jsonOutput
+            );
+        }
+        else if (sarg == "-P")
+        {
+          float n;
+          narg++;
+          if (narg < argc)
+          {
+            n = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify an argument to -P" << endl;
+            exit(2);
+          }
+          if ((n < 0) || (n > 100))
+          {
+            cerr << "Percentile must be between 0 and 100" << endl;
+            exit(1);
+          }
+          if (mask.nvoxels() < 1)
+          {
+            generate_masks(mask, masknz, vol, lthr, uthr);
+            vol *= mask;
+          }
+          print_value(
+            sarg,
+            to_string(vol.percentile((float)n / 100.0, masknz)),
+            json,
+            jsonOutput
+          );
+        }
+        else if (sarg == "-h")
+        {
+          float n;
+          narg++;
+          if (narg < argc)
+          {
+            n = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify the number of bins" << endl;
+            exit(2);
+          }
+          int nbins = (int)n;
+          if (nbins < 1)
+          {
+            cerr << "Must specify at least 1 bin" << endl;
+            exit(1);
+          }
+          if (mask.nvoxels() > 0)
+          {
+            auto hist = vol.histogram(nbins, vol.min(), vol.max(), mask);
+            // loop over histogram values and and build a string to print
+            string hist_str = "";
+            for (int i = 0; i < hist.Nrows(); i++)
+            {
+              hist_str += to_string(hist(i+1));
+              if (i < hist.Nrows() - 1)
+              {
+                hist_str += " ";
+              }
+            }
+            print_value(
+              sarg,
+              hist_str,
+              json,
+              jsonOutput
+            );
+          }
+          else
+          {
+            auto hist = vol.histogram(nbins, vol.min(), vol.max());
+            string hist_str = "";
+            for (int i = 0; i < hist.Nrows(); i++)
+            {
+              hist_str += to_string(hist(i+1));
+              if (i < hist.Nrows() - 1)
+              {
+                hist_str += " ";
+              }
+            }
+            print_value(
+              sarg,
+              hist_str,
+              json,
+              jsonOutput
+            );
+          }
+        }
+        else if (sarg == "-H")
+        {
+          float n;
+          narg++;
+          if (narg < argc)
+          {
+            n = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify the number of bins" << endl;
+            exit(2);
+          }
+          int nbins = (int)n;
+          if (nbins < 1)
+          {
+            cerr << "Must specify at least 1 bin" << endl;
+            exit(1);
+          }
+          float min = 0;
+          narg++;
+          if (narg < argc)
+          {
+            min = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify the histogram minimum intensity" << endl;
+            exit(2);
+          }
+          float max = 0;
+          narg++;
+          if (narg < argc)
+          {
+            max = atof(argv[narg]);
+          }
+          else
+          {
+            cerr << "Must specify the histogram maximum intensity" << endl;
+            exit(2);
+          }
+          if (mask.nvoxels() > 0)
+          {
+            auto hist = vol.histogram(nbins, min, max, mask);
+            string hist_str = "";
+            for (int i = 0; i < hist.Nrows(); i++)
+            {
+              hist_str += to_string(hist(i+1));
+              if (i < hist.Nrows() - 1)
+              {
+                hist_str += " ";
+              }
+            }
+            print_value(
+              sarg,
+              hist_str,
+              json,
+              jsonOutput
+            );
+          }
+          else
+          {
+            auto hist = vol.histogram(nbins, min, max);
+            string hist_str = "";
+            for (int i = 0; i < hist.Nrows(); i++)
+            {
+              hist_str += to_string(hist(i+1));
+              if (i < hist.Nrows() - 1)
+              {
+                hist_str += " ";
+              }
+            }
+            print_value(
+              sarg,
+              hist_str,
+              json,
+              jsonOutput
+            );
+          }
+        }
+        else
+        {
+          // only print cerr if not the --json option
+          if (sarg != "--json")
+          {
+            cerr << "Unrecognised option: " << sarg << endl;
+            exit(3);
+          } else if (sarg == "--json" && last) {
+              // remove the trailing comma and space
+              json.pop_back();
+              json.pop_back();
+          }
+        }
+
+        narg++;
       }
-      float max=0;
-      narg++;
-      if (narg<argc) {
-        max = atof(argv[narg]);
-      } else {
-	cerr << "Must specify the histogram maximum intensity" << endl;
-	exit(2);
+      // if json mode is enabled
+      if (jsonOutput)
+      {
+        // close the json object
+        json.append("}");
       }
-      if (mask.nvoxels()>0) {
-	cout << vol.histogram(nbins,min,max,mask) << " ";
-      } else {
-	cout << vol.histogram(nbins,min,max) << " ";
+      if (index < nIndices && !jsonOutput)
+      {
+        cout << endl;
       }
-    } else {
-	cerr << "Unrecognised option: " << sarg << endl;
-	exit(3);
+      // cout << endl;
+    } // end nIndices
+    // if json mode is enabled
+    if (jsonOutput)
+    {
+      // close the json object
+      json.append("]}");
     }
-
-    narg++;
-  }
-  cout << endl;
-   }
-
+    if (timepoint < nTimepoints - 1 && !jsonOutput)
+    {
+      cout << endl;
+    }
+    // cout << endl;
+  } // end timepoints
+  if (jsonOutput)
+  {
+    // close the json object
+    json.append("]}");
+    cout << json;
+  } else {
+    cout << endl;
   }
   return 0;
 }
 
-
-
-int main(int argc,char *argv[])
+int main(int argc, char *argv[])
 {
 
   Tracer tr("main");
   string progname(argv[0]);
   bool timeseriesMode(false);
   string indexMask("");
-  while ( argc > 2 && ( string(argv[1])=="-t" || string(argv[1]) =="-K" ) ) {
-    if ( string(argv[1])=="-t" )
-      timeseriesMode=true;
-    if ( string(argv[1])=="-K" ) {
-      indexMask=string(argv[2]);
+  while (argc > 2 && (string(argv[1]) == "-t" || string(argv[1]) == "-K"))
+  {
+    if (string(argv[1]) == "-t")
+      timeseriesMode = true;
+    if (string(argv[1]) == "-K")
+    {
+      indexMask = string(argv[2]);
       argv++;
       argc--;
     }
     argv++;
     argc--;
   }
-  if (argc < 3 )
+  if (argc < 3)
     return print_usage(progname);
-  try {
-    return fmrib_main_float(argc,argv,timeseriesMode,indexMask);
-  } catch(std::exception &e) {
+  try
+  {
+    return fmrib_main_float(argc, argv, timeseriesMode, indexMask);
+  }
+  catch (std::exception &e)
+  {
     cerr << e.what() << endl;
-  } catch (...) {
+  }
+  catch (...)
+  {
     // do nothing - just exit without garbage message
   }
 
   return -1;
-
 }
-- 
GitLab