/*=============================================================================

  NifTK: A software platform for medical image computing.

  Copyright (c) University College London (UCL). All rights reserved.

  This software is distributed WITHOUT ANY WARRANTY; without even
  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.

  See LICENSE.txt in the top level directory for details.

  =============================================================================*/

/*!
 * \file niftkBreastDensityFromMRIs.cxx
 * \page niftkBreastDensityFromMRIs
 * \section niftkBreastDensityFromMRIsSummary niftkBreastDensityFromMRIs
 *
 * Compute breast density for directories of DICOM MR images
 *
 */

#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>
#include <iterator>

#include <QProcess>
#include <QString>
#include <QDebug>

#include <boost/filesystem/path.hpp>
#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/stream.hpp>

#include <niftkFileHelper.h>
#include <niftkConversionUtils.h>
#include <niftkCSVRow.h>
#include <niftkEnvironmentHelper.h>

#include <itkCommandLineHelper.h>
#include <itkImage.h>
#include <itkImageFileReader.h>
#include <itkImageSeriesReader.h>
#include <itkImageFileWriter.h>
#include <itkNifTKImageIOFactory.h>
#include <itkWriteImage.h>
#include <itkReadImage.h>
#include <itkConversionUtils.h>
#include <itkImageRegionIterator.h>
#include <itkImageRegionIteratorWithIndex.h>
#include <itkInvertIntensityBetweenMaxAndMinImageFilter.h>
#include <itkMaskImageFilter.h>
#include <itkCastImageFilter.h>
#include <itkOrientImageFilter.h>
#include <itkSampleImageFilter.h>
#include <itkBinaryShapeBasedSuperSamplingFilter.h>
#include <itkIsImageBinary.h>
#include <itkRescaleIntensityImageFilter.h>

//#define LINK_TO_SEG_EM

#ifdef LINK_TO_SEG_EM
#include <_seg_EM.h>
#endif

#include <niftkBreastDensityFromMRIsGivenMaskAndImageCLP.h>

#define SegPrecisionTYPE float

namespace fs = boost::filesystem;

namespace bio = boost::iostreams;
using bio::tee_device;
using bio::stream;

typedef itk::MetaDataDictionary DictionaryType;
typedef itk::MetaDataObject< std::string > MetaDataStringType;

typedef float PixelType;
const unsigned int   Dimension = 3;

typedef itk::Image< PixelType, Dimension > ImageType;
typedef itk::Image< PixelType, 4 > ImageType4D;

// -------------------------------------------------------------------------
// CreateFilename()
// -------------------------------------------------------------------------

std::string CreateFilename( std::string fileOne,
                            std::string fileTwo,
                            std::string description,
                            std::string suffix )
{
  std::string fileOneWithoutSuffix;
  niftk::ExtractImageFileSuffix( fileOne, fileOneWithoutSuffix );

  std::string fileTwoWithoutSuffix;
  niftk::ExtractImageFileSuffix( fileTwo, fileTwoWithoutSuffix );

  return fileOneWithoutSuffix + "_" + description + "_" + fileTwoWithoutSuffix + suffix;
};

// -------------------------------------------------------------------------
// PrintOrientationInfo()
// -------------------------------------------------------------------------

void PrintOrientationInfo( ImageType::Pointer image )
{
  itk::SpatialOrientationAdapter adaptor;
  ImageType::DirectionType direction;

  for (unsigned int i = 0; i < Dimension; i++)
  {
    for (unsigned int j = 0; j < Dimension; j++)
    {
      direction[i][j] = image->GetDirection()[i][j];
    }
  }

  std::cout << "Image direction: " << std::endl
	    << direction;

  std::cout << "ITK orientation: "
	    << itk::ConvertSpatialOrientationToString(adaptor.FromDirectionCosines(direction))
	    << std::endl;
}

// -------------------------------------------------------------------------
// GetOrientation()
// -------------------------------------------------------------------------

itk::SpatialOrientation::ValidCoordinateOrientationFlags GetOrientation( ImageType::Pointer image )
{
  ImageType::DirectionType direction;
  itk::SpatialOrientationAdapter adaptor;

  for (unsigned int i = 0; i < Dimension; i++)
  {
    for (unsigned int j = 0; j < Dimension; j++)
    {
      direction[i][j] = image->GetDirection()[i][j];
    }
  }

  return adaptor.FromDirectionCosines(direction);
}

// -------------------------------------------------------------------------
// ReorientateImage()
// -------------------------------------------------------------------------

ImageType::Pointer ReorientateImage( ImageType::Pointer image,
                                     itk::SpatialOrientation::ValidCoordinateOrientationFlags desiredOrientation )
{
  std::cout << std::endl << "Input image:" << std::endl;
  //image->Print( std::cout );
  PrintOrientationInfo( image );

  std::cout << "Desired orientation: " << itk::ConvertSpatialOrientationToString( desiredOrientation )
            << std::endl;

  typedef itk::OrientImageFilter<ImageType,ImageType> OrientImageFilterType;
  OrientImageFilterType::Pointer orienter = OrientImageFilterType::New();

  orienter->UseImageDirectionOn();
  orienter->SetDesiredCoordinateOrientation( desiredOrientation );
  orienter->SetInput( image );

  orienter->Update();

  typedef OrientImageFilterType::FlipAxesArrayType FlipAxesArrayType;
  typedef OrientImageFilterType::PermuteOrderArrayType PermuteOrderArrayType;

  FlipAxesArrayType flipAxes = orienter->GetFlipAxes();
  PermuteOrderArrayType permuteAxes = orienter->GetPermuteOrder();

  std::cout << std::endl
            << "Permute Axes: " << permuteAxes << std::endl
            << "Flip Axes: "    << flipAxes << std::endl;

  ImageType::Pointer reorientatedImage = orienter->GetOutput();
  //reorientatedImage->DisconnectPipeline();

  std::cout << std::endl << "Output image:" << std::endl;
  //reorientatedImage->Print( std::cout );
  PrintOrientationInfo( reorientatedImage );

  return reorientatedImage;
};

// -------------------------------------------------------------------------
// ReorientateImage()
// -------------------------------------------------------------------------

ImageType::Pointer ReorientateImage( ImageType::Pointer image, ImageType::Pointer reference )
{
  itk::SpatialOrientation::ValidCoordinateOrientationFlags desiredOrientation;

  std::cout << std::endl << "Reference image:" << std::endl;
  //reference->Print( std::cout );
  PrintOrientationInfo( reference );

  desiredOrientation = GetOrientation( reference );

  return ReorientateImage( image, desiredOrientation );
}

// -------------------------------------------------------------------------
// class InputParameters
// -------------------------------------------------------------------------

class InputParameters
{

public:

  bool flgVerbose;
  bool flgDebug;
  bool flgCompression;
  bool flgOverwrite;
  bool flgFatIsBright;

  std::string dirInput;
  std::string dirOutput;

  std::string fileLog;
  std::string fileOutputCSV;

  std::string fileMaskPattern;
  std::string fileImagePattern;

  std::string dirMask;
  std::string dirImage;

  std::string dirSubData;
  std::string dirPrefix;

  std::string progSegEM;

  QStringList argsSegEM;

  std::ofstream *foutLog;
  std::ofstream *foutOutputCSV;
  std::ostream *newCout;

  typedef tee_device<std::ostream, std::ofstream> TeeDevice;
  typedef stream<TeeDevice> TeeStream;

  TeeDevice *teeDevice;
  TeeStream *teeStream;

  InputParameters( TCLAP::CmdLine &commandLine,

                   bool verbose,
                   bool compression,
                   bool debug,
                   bool overwrite,
                   bool fatIsBright,

                   std::string subDirMask,
                   std::string fileMask,
                   std::string subDirImage,
                   std::string fileImage,

                   std::string subDirData,
                   std::string prefix,
                   std::string dInput,

                   std::string logfile,
                   std::string csvfile,

                   std::string segEM,
                   QStringList aSegEM ) {

    std::stringstream message;

    flgVerbose = verbose;
    flgDebug = debug;
    flgCompression = compression;
    flgOverwrite = overwrite;
    flgFatIsBright = fatIsBright;

    dirMask  = subDirMask;
    fileMaskPattern = fileMask;
    dirImage = subDirImage;
    fileImagePattern = fileImage;

    dirSubData = subDirData;
    dirPrefix  = prefix;
    dirInput   = dInput;

    fileLog = logfile;
    fileOutputCSV = csvfile;

    progSegEM       = segEM;
    argsSegEM       = aSegEM;

    if ( fileLog.length() > 0 )
    {
      foutLog = new std::ofstream( fileLog.c_str() );

      if ((! *foutLog) || foutLog->bad()) {
        message << "Could not open file: " << fileLog << std::endl;
        PrintErrorAndExit( message );
      }

      newCout = new std::ostream( std::cout.rdbuf() );

      teeDevice = new TeeDevice( *newCout, *foutLog);
      teeStream = new TeeStream( *teeDevice );

      std::cout.rdbuf( teeStream->rdbuf() );
      std::cerr.rdbuf( teeStream->rdbuf() );
    }
    else
    {
      foutLog = 0;
      newCout = 0;
      teeDevice = 0;
      teeStream = 0;
    }

    if ( fileOutputCSV.length() > 0 )
    {
      foutOutputCSV = new std::ofstream( fileOutputCSV.c_str() );

      if ((! *foutOutputCSV) || foutOutputCSV->bad()) {
        message << "Could not open file: " << fileOutputCSV << std::endl;
        PrintErrorAndExit( message );
      }
    }
    else
    {
      foutOutputCSV = 0;
    }

    if ( dirInput.length() == 0 )
    {
      commandLine.getOutput()->usage( commandLine );
      message << "The input directory must be specified" << std::endl;
      PrintErrorAndExit( message );
    }

  }

  ~InputParameters() {
#if 0
    if ( teeStream )
    {
      teeStream->flush();
      teeStream->close();
      delete teeStream;
    }

    if ( teeDevice )
    {
      delete teeDevice;
    }

    if ( foutLog )
    {
      foutLog->close();
      delete foutLog;
    }

    if ( foutOutputCSV )
    {
      foutOutputCSV->close();
      delete foutOutputCSV;
    }

    if ( newCout )
    {
      delete newCout;
    }
#endif
  }

  void Print(void) {

    std::stringstream message;

    message << std::endl
            << "Examining directory: " << dirInput << std::endl
            << std::endl
            << std::boolalpha
            << "Verbose output?: "             << flgVerbose        << std::endl
            << "Compress images?: "            << flgCompression    << std::endl
            << "Debugging output?: "           << flgDebug          << std::endl
            << "Overwrite previous results?: " << flgOverwrite      << std::endl
            << "Fat is bright?: "              << flgFatIsBright    << std::endl
            << std::noboolalpha
            << std::endl
            << "Input mask sub-directory: " << dirMask << std::endl
            << "Input mask file name pattern: " << fileMaskPattern << std::endl
            << "Input image sub-directory: " << dirImage << std::endl
            << "Input image file name pattern: " << fileMaskPattern << std::endl
            << "Output data sub-directory: " << dirSubData << std::endl
            << "Study directory prefix: " << dirPrefix << std::endl
            << std::endl
            << "Output log file: " << fileLog << std::endl
            << "Output csv file: " << fileOutputCSV << std::endl
            << std::endl
            << "Segmentation executable: "
            << progSegEM       << " " << argsSegEM.join(" ").toStdString() << std::endl
            << std::endl;

    PrintMessage( message );
  }

  void PrintMessage( std::stringstream &message ) {

    std::cout << message.str();
    message.str( "" );
    if ( teeStream )
    {
      teeStream->flush();
    }
  }

  void PrintError( std::stringstream &message ) {

    std::cerr << "ERROR: " << message.str();
    message.str( "" );
    if ( teeStream )
    {
      teeStream->flush();
    }
  }

  void PrintErrorAndExit( std::stringstream &message ) {

    PrintError( message );

    exit( EXIT_FAILURE );
  }

  void PrintWarning( std::stringstream &message ) {

    std::cerr << "WARNING: " << message.str();
    message.str( "" );
    if ( teeStream )
    {
      teeStream->flush();
    }
  }

  bool ReadImageFromFile( std::string fileInput,
                          std::string description, ImageType::Pointer &image ) {

    std::stringstream message;

    if ( fileInput.find( ".nii" ) == std::string::npos )
    {
      return false;
    }

    if ( itk::ReadImageFromFile< ImageType >( fileInput, image ) )
    {
      message << std::endl << "Read " << description << " from file: " << fileInput << std::endl;
      PrintMessage( message );
    }
    else if ( itk::ReadImageFromFile< ImageType >( fileInput + ".gz", image ) )
    {
      message << std::endl << "Read " << description << " from file: " << fileInput << std::endl;
      PrintMessage( message );
    }
    else
    {
      return false;
    }

    itk::SpatialOrientation::ValidCoordinateOrientationFlags orientation;

    orientation = GetOrientation( image );

    if ( orientation !=  itk::SpatialOrientation::ITK_COORDINATE_ORIENTATION_RAI )
    {
      image = ReorientateImage( image, itk::SpatialOrientation::ITK_COORDINATE_ORIENTATION_RAI );
    }

    return true;
  }

  bool ReadImageFromFile( std::string dirInput, std::string filename,
                          std::string description, ImageType::Pointer &image ) {

    std::string fileInput = niftk::ConcatenatePath( dirInput, filename );

    return ReadImageFromFile( fileInput, description, image );
  }

  void WriteImageToFile( std::string filename,
                         std::string description, ImageType::Pointer image,
                         bool flgConcatenatePath=true ) {

    std::stringstream message;
    std::string fileOutput;

    if ( flgConcatenatePath )
    {
      fileOutput = niftk::ConcatenatePath( dirOutput, filename );
    }
    else
    {
      fileOutput = filename;
    }

    message << std::endl << "Writing " << description << " to file: "
            << fileOutput << std::endl;
    PrintMessage( message );

    if ( itk::IsImageBinary< ImageType >( image ) )
    {
      typedef unsigned char OutputPixelType;
      typedef itk::Image< OutputPixelType, ImageType::ImageDimension> OutputImageType;

      typedef itk::RescaleIntensityImageFilter< ImageType, OutputImageType > CastFilterType;

      CastFilterType::Pointer caster = CastFilterType::New();

      caster->SetInput( image );
      caster->SetOutputMinimum(   0 );
      caster->SetOutputMaximum( 255 );
      caster->Update();

      OutputImageType::Pointer imOut = caster->GetOutput();

      itk::WriteImageToFile< OutputImageType >( fileOutput, imOut );
    }
    else
    {
      itk::WriteImageToFile< ImageType >( fileOutput, image );
    }
  }

  void DeleteFile( std::string filename ) {

    std::stringstream message;
    std::string filePath = niftk::ConcatenatePath( dirOutput, filename );

    if ( ! niftk::FileIsRegular( filePath ) )
    {
      return;
    }

    if ( niftk::FileDelete( filePath ) )
    {
      message << std::endl << "Deleted file: " << filePath << std::endl;
      PrintMessage( message );
    }
    else
    {
      message << std::endl << "Failed to delete file: " << filePath << std::endl;
      PrintWarning( message );
    }
  }

};

// -------------------------------------------------------------------------
// SplitStringIntoCommandAndArguments()
// -------------------------------------------------------------------------

std::string SplitStringIntoCommandAndArguments( std::string inString,
                                                QStringList &arguments )
{
  std::string command;

  std::stringstream ssString( inString );

  std::istream_iterator< std::string > itStringStream( ssString );
  std::istream_iterator< std::string > itEnd;

  command = *itStringStream;
  itStringStream++;

  for (; itStringStream != itEnd; itStringStream++)
  {
    arguments << (*itStringStream).c_str();
  }

  return command;
};

// -------------------------------------------------------------------------
// ResampleImageToIsotropicVoxels()
// -------------------------------------------------------------------------

bool ResampleImageToIsotropicVoxels( ImageType::Pointer &image, InputParameters &args )
{
  std::stringstream message;

  double factor[Dimension];

  ImageType::SpacingType spacing = image->GetSpacing();

  // Calculate the minimum spacing

  double minSpacing = std::numeric_limits<double>::max();

  for (unsigned int j = 0; j < ImageType::ImageDimension; j++)
  {
    if ( spacing[j] < minSpacing )
    {
      minSpacing = spacing[j];
    }
  }

  // Calculate the subsampling factors

  bool flgSamplingRequired = false;

  for (unsigned int j = 0; j < ImageType::ImageDimension; j++)
  {
    factor[j] = minSpacing/spacing[j];

    if ( factor[j] < 0.8 )
    {
      flgSamplingRequired = true;
    }
  }

  // Run the sampling filter

  if ( itk::IsImageBinary< ImageType >(image) )
  {
    typedef itk::BinaryShapeBasedSuperSamplingFilter< ImageType, ImageType > SampleImageFilterType;

    SampleImageFilterType::Pointer sampler = SampleImageFilterType::New();

    sampler->SetIsotropicVoxels( true );

    sampler->SetInput( image );

    sampler->VerboseOn();

    message << "Computing sampled binary image" << std::endl;
    args.PrintMessage( message );

    sampler->Update();

    ImageType::Pointer sampledImage = sampler->GetOutput();
    sampledImage->DisconnectPipeline();

    image = sampledImage;
  }
  else
  {
    typedef itk::SampleImageFilter< ImageType, ImageType > SampleImageFilterType;

    SampleImageFilterType::Pointer sampler = SampleImageFilterType::New();

    sampler->SetIsotropicVoxels( true );
    sampler->SetInterpolationType( itk::NEAREST );

    sampler->SetInput( image );

    sampler->VerboseOn();

    message << "Computing sampled image" << std::endl;
    args.PrintMessage( message );

    sampler->Update();

    ImageType::Pointer sampledImage = sampler->GetOutput();
    sampledImage->DisconnectPipeline();

    image = sampledImage;
  }

  return true;
};

// -------------------------------------------------------------------------
// NaiveParenchymaSegmentation()
// -------------------------------------------------------------------------

void NaiveParenchymaSegmentation( InputParameters &args,

                                  ImageType::Pointer &imSegmentedBreastMask,
                                  ImageType::Pointer &image,

                                  bool flgFatIsBright,

                                  float &nLeftVoxels,
                                  float &nRightVoxels,

                                  float &totalDensity,
                                  float &leftDensity,
                                  float &rightDensity,

                                  std::string fileOutputParenchyma )
{
  float minFraction = 0.02;

  std::stringstream message;

  ImageType::Pointer imParenchyma = 0;

  typedef itk::ImageDuplicator< ImageType > DuplicatorType;

  DuplicatorType::Pointer duplicator = DuplicatorType::New();

  duplicator->SetInputImage( image );
  duplicator->Update();

  imParenchyma = duplicator->GetOutput();
  imParenchyma->DisconnectPipeline();

  imParenchyma->FillBuffer( 0. );

  nLeftVoxels = 0;
  nRightVoxels = 0;

  totalDensity = 0.;
  leftDensity = 0.;
  rightDensity = 0.;

  float meanOfHighProbIntensities = 0.;
  float meanOfLowProbIntensities = 0.;

  float nHighProbIntensities = 0.;
  float nLowProbIntensities = 0.;

  itk::ImageRegionIteratorWithIndex< ImageType >
    itMask( imSegmentedBreastMask, imSegmentedBreastMask->GetLargestPossibleRegion() );

  itk::ImageRegionIterator< ImageType >
    itSegmentation( imParenchyma, imParenchyma->GetLargestPossibleRegion() );

  itk::ImageRegionConstIterator< ImageType >
    itImage( image, image->GetLargestPossibleRegion() );

  // Compute the range of intensities inside the mask

  float minIntensity = std::numeric_limits< float >::max();
  float maxIntensity = -std::numeric_limits< float >::max();

  for ( itMask.GoToBegin(), itImage.GoToBegin();
        ! itMask.IsAtEnd();
        ++itMask, ++itImage )
  {
    if ( itMask.Get() )
    {
      if ( itImage.Get() > maxIntensity )
      {
        maxIntensity = itImage.Get();
      }

      if ( itImage.Get() < minIntensity )
      {
        minIntensity = itImage.Get();
      }
    }
  }

  message  << std::endl
           << "Range of " << " is from: "
           << minIntensity << " to: " << maxIntensity << std::endl;
  args.PrintMessage( message );

  // Compute 1st and 99th percentiles of the image from the image histogram

  unsigned int nBins = static_cast<unsigned int>( maxIntensity - minIntensity + 0.5 ) + 1;

  itk::Array< float > histogram( nBins );

  histogram.Fill( 0 );

  float nPixels = 0;
  float flIntensity;

  for ( itImage.GoToBegin(), itMask.GoToBegin();
        ! itImage.IsAtEnd();
        ++itImage, ++itMask )
  {
    if ( itMask.Get() )
    {
      flIntensity = itImage.Get() - minIntensity;

      if ( flIntensity < 0. )
      {
        flIntensity = 0.;
      }

      if ( flIntensity > static_cast<float>( nBins - 1 ) )
      {
        flIntensity = static_cast<float>( nBins - 1 );
      }

      nPixels++;
      histogram[ static_cast<unsigned int>( flIntensity ) ] += 1.;
    }
  }

  float sumProbability = 0.;
  unsigned int intensity;

  float pLowerBound = 0.;
  float pUpperBound = 0.;

  bool flgLowerBoundFound = false;
  bool flgUpperBoundFound = false;

  for ( intensity=0; intensity<nBins; intensity++ )
  {
    histogram[ intensity ] /= nPixels;
    sumProbability += histogram[ intensity ];

    if ( ( ! flgLowerBoundFound ) && ( sumProbability >= minFraction ) )
    {
      pLowerBound = intensity;
      flgLowerBoundFound = true;
    }

    if ( ( ! flgUpperBoundFound ) && ( sumProbability >= (1. - minFraction) ) )
    {
      pUpperBound = intensity;
      flgUpperBoundFound = true;
    }

    if ( args.flgDebug )
    {
      std::cout << std::setw( 18 ) << intensity << " "
                << std::setw( 18 ) << histogram[ intensity ]  << " "
                << std::setw( 18 ) << sumProbability << std::endl;
    }
  }

  message << std::endl
          << "Density lower bound: " << pLowerBound
          << " ( " << minFraction*100. << "% )" << std::endl
          << " upper bound: " << pUpperBound
          << " ( " << (1. - minFraction)*100. << "% )" << std::endl;
  args.PrintMessage( message );

  // Compute the density

  ImageType::SpacingType spacing = imParenchyma->GetSpacing();

  float voxelVolume = spacing[0]*spacing[1]*spacing[2];

  ImageType::RegionType region;
  region = imSegmentedBreastMask->GetLargestPossibleRegion();

  ImageType::SizeType lateralSize;
  lateralSize = region.GetSize();
  lateralSize[0] = lateralSize[0]/2;

  ImageType::IndexType idx;

  for ( itMask.GoToBegin(), itSegmentation.GoToBegin(), itImage.GoToBegin();
        ! itMask.IsAtEnd();
        ++itMask, ++itSegmentation, ++itImage )
  {
    if ( itMask.Get() )
    {
      idx = itMask.GetIndex();

      flIntensity = ( itImage.Get() - pLowerBound )/( pUpperBound - pLowerBound );

      if ( flIntensity < 0. )
      {
        itSegmentation.Set( 0. );
      }
      else if ( flIntensity > 1. )
      {
        itSegmentation.Set( 1. );
      }
      else
      {
        itSegmentation.Set( flIntensity );
      }

      //std::cout << idx << " " << itImage.Get() << " -> " << flIntensity << std::endl;

      // Left breast

      if ( idx[0] < (int) lateralSize[0] )
      {
        nLeftVoxels++;
        leftDensity += flIntensity;
      }

      // Right breast

      else
      {
        nRightVoxels++;
        rightDensity += flIntensity;
      }

      // Both breasts

      totalDensity += flIntensity;

      // Ensure we have the polarity correct by calculating the
      // mean intensities of each class

      if ( flIntensity > 0.5 )
      {
        meanOfHighProbIntensities += itImage.Get();
        nHighProbIntensities++;
      }
      else
      {
        meanOfLowProbIntensities += itImage.Get();
        nLowProbIntensities++;
      }
    }
  }

  leftDensity /= nLeftVoxels;
  rightDensity /= nRightVoxels;
  totalDensity /= ( nLeftVoxels + nRightVoxels);

  // Calculate the mean intensities of each class

  if ( nHighProbIntensities > 0. )
  {
    meanOfHighProbIntensities /= nHighProbIntensities;
  }

  if ( nLowProbIntensities > 0. )
  {
    meanOfLowProbIntensities /= nLowProbIntensities;
  }

  message  << std::endl
           << "Mean intensity of high probability class = " << meanOfHighProbIntensities
           << " ( " << nHighProbIntensities << " voxels )" << std::endl
           << "Mean intensity of low probability class = " << meanOfLowProbIntensities
           << " ( " << nLowProbIntensities << " voxels )" << std::endl;
  args.PrintMessage( message );

  // Fat should be high intensity in the T2 image so if the dense
  // region (high prob) has a high intensity then it is probably fat
  // and we need to invert the density, whereas the opposite is true
  // for the fat-saturated T1w VIBE image.

  if ( (     flgFatIsBright   && ( meanOfHighProbIntensities > meanOfLowProbIntensities ) ) ||
       ( ( ! flgFatIsBright ) && ( meanOfHighProbIntensities < meanOfLowProbIntensities ) ) )
  {
    message << "Inverting the density estimation" << std::endl;
    args.PrintWarning( message );

    leftDensity  = 1. - leftDensity;
    rightDensity = 1. - rightDensity;
    totalDensity = 1. - totalDensity;

    typedef itk::InvertIntensityBetweenMaxAndMinImageFilter< ImageType > InvertFilterType;
    InvertFilterType::Pointer invertFilter = InvertFilterType::New();
    invertFilter->SetInput( imParenchyma );

    typedef itk::MaskImageFilter< ImageType, ImageType > MaskFilterType;
    MaskFilterType::Pointer maskFilter = MaskFilterType::New();

    maskFilter->SetInput( invertFilter->GetOutput() );
    maskFilter->SetMaskImage( imSegmentedBreastMask );
    maskFilter->Update();

    ImageType::Pointer imInverted = maskFilter->GetOutput();
    imInverted->DisconnectPipeline();
    imParenchyma = imInverted;
  }

  std::string fileOut = niftk::ModifyImageFileSuffix( fileOutputParenchyma,
                                                      std::string( "_Naive.nii" ) );

  if ( args.flgCompression ) fileOut.append( ".gz" );

  args.WriteImageToFile( fileOut,
                         std::string( "naive parenchyma image"), imParenchyma );

  float leftBreastVolume = nLeftVoxels*voxelVolume;
  float rightBreastVolume = nRightVoxels*voxelVolume;

  message << "Naive - Number of left breast voxels: " << nLeftVoxels << std::endl
          << "Naive - Volume of left breast: " << leftBreastVolume << " mm^3" << std::endl
          << "Naive - Density of left breast (fraction of glandular tissue): " << leftDensity
          << std::endl << std::endl

          << "Naive - Number of right breast voxels: " << nRightVoxels << std::endl
          << "Naive - Volume of right breast: " << rightBreastVolume << " mm^3" << std::endl
          << "Naive - Density of right breast (fraction of glandular tissue): " << rightDensity
          << std::endl << std::endl

          << "Naive - Total number of breast voxels: "
          << nLeftVoxels + nRightVoxels << std::endl
          << "Naive - Total volume of both breasts: "
          << leftBreastVolume + rightBreastVolume << " mm^3" << std::endl
          << "Naive - Combined density of both breasts (fraction of glandular tissue): "
          << totalDensity << std::endl << std::endl;

  args.PrintMessage( message );
};

// -------------------------------------------------------------------------
// main()
// -------------------------------------------------------------------------

int main( int argc, char *argv[] )
{
  itk::NifTKImageIOFactory::Initialize();

  bool flgVeryFirstRow = true;

  float progress = 0.;
  float iDirectory = 0.;
  float nDirectories;

  std::stringstream message;

  fs::path pathExecutable( argv[0] );
  fs::path dirExecutables = pathExecutable.parent_path();

  // Validate command line args
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~

  PARSE_ARGS;

  // Extract any arguments from the input commands

  QStringList argsSegEM;
  std::string progSegEM = SplitStringIntoCommandAndArguments( comSegEM, argsSegEM );

  InputParameters args( commandLine,

                        flgVerbose,
                        flgCompression,
                        flgDebug,
                        flgOverwrite,
                        flgFatIsBright,

                        dirMask,
                        fileMaskPattern,
                        dirImage,
                        fileImagePattern,

                        dirSubData,
                        dirPrefix,
                        dirInput,

                        fileLog,
                        fileOutputCSV,

                        progSegEM,
                        argsSegEM );

  // Can we find the seg_EM executable or verify the path?
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  if ( ! niftk::FileIsRegular( args.progSegEM ) )
  {
    std::string fileSearchSegEM = niftk::ConcatenatePath( dirExecutables.string(),
                                                          args.progSegEM );

    if ( niftk::FileIsRegular( fileSearchSegEM ) )
    {
      args.progSegEM = fileSearchSegEM;
    }
  }

  args.Print();

  std::cout  << std::endl << "<filter-progress>" << std::endl
             << 0. << std::endl
             << "</filter-progress>" << std::endl << std::endl;

  // Get the list of files in the directory
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  std::string dirFullPath;
  std::string dirBaseName;

  std::string dirMaskFullPath;
  std::string dirImageFullPath;

  std::vector< std::string > directoryNames;
  std::vector< std::string >::iterator iterDirectoryNames;

  std::vector< std::string > fileMaskNames;
  std::vector< std::string >::iterator iterFileMaskNames;

  std::vector< std::string > fileImageNames;
  std::vector< std::string >::iterator iterFileImageNames;

  directoryNames = niftk::GetDirectoriesInDirectory( args.dirInput );

  nDirectories = directoryNames.size();

  for ( iterDirectoryNames = directoryNames.begin();
	iterDirectoryNames < directoryNames.end();
	++iterDirectoryNames, iDirectory += 1. )
  {

    dirFullPath = *iterDirectoryNames;
    dirBaseName = niftk::Basename( dirFullPath );

    if ( ! (dirBaseName.compare( 0, args.dirPrefix.length(), args.dirPrefix ) == 0) )
    {
      message << std::endl << "Skipping directory: " << dirFullPath << std::endl;
      args.PrintMessage( message );
      continue;
    }

    message << std::endl << "Directory: " << dirFullPath << std::endl;
    args.PrintMessage( message );

    // The mask directory

    if ( args.dirMask.length() > 0 )
    {
      dirMaskFullPath = niftk::ConcatenatePath( dirFullPath, args.dirMask );
    }
    else
    {
      dirMaskFullPath = dirFullPath;
    }

    // The image directory

    if ( args.dirImage.length() > 0 )
    {
      dirImageFullPath = niftk::ConcatenatePath( dirFullPath, args.dirImage );
    }
    else
    {
      dirImageFullPath = dirFullPath;
    }

    // The output directory

    if ( dirSubData.length() > 0 )
    {
      args.dirOutput = niftk::ConcatenatePath( dirFullPath, args.dirSubData );
    }
    else
    {
      args.dirOutput = dirFullPath;
    }

    if ( ! niftk::DirectoryExists( args.dirOutput ) )
    {
      niftk::CreateDirAndParents( args.dirOutput );

      message << "Creating output directory: " << args.dirOutput << std::endl;
      args.PrintMessage( message );
    }

    // For each mask image
    // ~~~~~~~~~~~~~~~~~~~

    fileMaskNames = niftk::GetFilesInDirectory( dirMaskFullPath );

    float iMask = 0.;
    float nMasks = fileMaskNames.size();

    for ( iterFileMaskNames = fileMaskNames.begin();
          iterFileMaskNames < fileMaskNames.end();
          ++iterFileMaskNames, iMask += 1. )
    {

      std::string fileMaskFullPath = *iterFileMaskNames;
      std::string fileMaskBasename = niftk::Basename( fileMaskFullPath );

      if ( fileMaskBasename.find( args.fileMaskPattern ) == std::string::npos )
      {
        if ( args.flgDebug )
        {
          message << std::endl << "Skipping non-mask file: " << fileMaskBasename
                  << std::endl;
          args.PrintMessage( message );
        }
        continue;
      }

      // Read the mask image

      ImageType::Pointer imMask = 0;

      if ( ! args.ReadImageFromFile( fileMaskFullPath,
                                     std::string( "mask image" ),
                                     imMask ) )
      {
        if ( args.flgDebug )
        {
          message << std::endl << "Cannot read mask: " << fileMaskFullPath << std::endl << std::endl;
          args.PrintMessage( message );
        }
        continue;
      }

      if ( ResampleImageToIsotropicVoxels( imMask, args ) )
      {
        fileMaskBasename = niftk::ModifyImageFileSuffix( fileMaskBasename,
                                                         std::string( "_Isotropic.nii" ) );

        fileMaskFullPath = niftk::ConcatenatePath( args.dirOutput, fileMaskBasename );

        if ( flgCompression )
        {
          fileMaskFullPath.append( ".gz" );
        }

        args.WriteImageToFile( fileMaskFullPath,
                               std::string( "isotropic mask image" ),
                               imMask, false );
      }

      // For each image
      // ~~~~~~~~~~~~~~

      fileImageNames = niftk::GetFilesInDirectory( dirImageFullPath );

      float iImage = 0.;
      float nImages = fileImageNames.size();

      for ( iterFileImageNames = fileImageNames.begin();
            iterFileImageNames < fileImageNames.end();
            ++iterFileImageNames, iImage += 1. )
      {

        std::string fileImageFullPath = *iterFileImageNames;
        std::string fileImageBasename = niftk::Basename( fileImageFullPath );

        if ( fileImageBasename.find( args.fileImagePattern ) == std::string::npos )
        {
          if ( args.flgDebug )
          {
            message << std::endl << "Skipping non-image file: " << fileImageBasename
                    << std::endl;
            args.PrintMessage( message );
          }
          continue;
        }

        // Read the image

        ImageType::Pointer imInput = 0;

        if ( ! args.ReadImageFromFile( fileImageFullPath,
                                       std::string( "input image" ),
                                       imInput ) )
        {
          if ( args.flgDebug )
          {
            message << std::endl << "Cannot read image: " << fileImageFullPath
                    << std::endl;
            args.PrintMessage( message );
          }
          continue;
        }

        if ( ResampleImageToIsotropicVoxels( imInput, args ) )
        {
          fileImageBasename = niftk::ModifyImageFileSuffix( fileImageBasename,
                                                            std::string( "_Isotropic.nii" ) );

          fileImageFullPath = niftk::ConcatenatePath( args.dirOutput, fileImageBasename );

          if ( flgCompression )
          {
            fileImageFullPath.append( ".gz" );
          }

          args.WriteImageToFile( fileImageFullPath,
                                 std::string( "isotropic image" ),
                                 imInput, false );
        }

        try
        {

          // If the CSV file has already been generated then read it
          // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

          std::string fileDensityMeasurements = CreateFilename( fileImageBasename,
                                                                fileMaskBasename,
                                                                std::string( "WithMask" ),
                                                                std::string( ".csv" ) );

          std::string fileInputDensityMeasurements
            =  niftk::ConcatenatePath( args.dirOutput, fileDensityMeasurements );

          if ( ! args.flgOverwrite )
          {
            if ( niftk::FileIsRegular( fileInputDensityMeasurements ) )
            {
              std::ifstream fin( fileInputDensityMeasurements.c_str() );

              if ((! fin) || fin.bad())
              {
                message << "ERROR: Could not open file: " << fileDensityMeasurements << std::endl;
                args.PrintError( message );
                continue;
              }

              message << std::endl << "Reading CSV file: " << fileInputDensityMeasurements << std::endl;
              args.PrintMessage( message );

              niftk::CSVRow csvRow;

              bool flgFirstRowOfThisFile = true;

              while( fin >> csvRow )
              {
                message << "" << csvRow << std::endl;
                args.PrintMessage( message );

                if ( flgFirstRowOfThisFile )
                {
                  if ( flgVeryFirstRow )    // Include the title row?
                  {
                    *args.foutOutputCSV << csvRow << std::endl;
                    flgVeryFirstRow = false;
                  }
                  flgFirstRowOfThisFile = false;
                }
                else
                {
                  *args.foutOutputCSV << csvRow << std::endl;
                }
              }

              continue;
            }
            else
            {
              message << "Density measurements: " << fileInputDensityMeasurements
                      << " not found" << std::endl;
              args.PrintMessage( message );
            }
          }

          progress = ( iDirectory + ( iMask + ( iImage + 0.3 )/nImages )/nMasks )/nDirectories;
          std::cout  << std::endl << "<filter-progress>" << std::endl
                     << progress << std::endl
                     << "</filter-progress>" << std::endl << std::endl;

          // Segment the parenchyma
          // ~~~~~~~~~~~~~~~~~~~~~~

          ImageType::Pointer imParenchyma = 0;

          std::string fileOutputParenchyma = CreateFilename( fileImageBasename,
                                                             fileMaskBasename,
                                                             std::string( "WithMask" ),
                                                             std::string( "_Parenchyma.nii" ) );

          if ( args.flgOverwrite ||
               ( ! args.ReadImageFromFile( args.dirOutput,
                                           fileOutputParenchyma,
                                           "breast parenchyma", imParenchyma ) ) )
          {

            // QProcess call to seg_EM

            QStringList argumentsNiftySeg = args.argsSegEM;

            argumentsNiftySeg
              << "-in"   << fileImageFullPath.c_str()
              << "-mask" << fileMaskFullPath.c_str()
              << "-out"  << niftk::ConcatenatePath( args.dirOutput, fileOutputParenchyma ).c_str();

            message << std::endl << "Executing parenchyma segmentation (QProcess): "
                    << std::endl << " " << args.progSegEM;
            for(int i=0;i<argumentsNiftySeg.size();i++)
            {
              message << " " << argumentsNiftySeg[i].toStdString();
            }
            message << std::endl << std::endl;
            args.PrintMessage( message );

            QProcess callSegEM;
            QString outSegEM;

            callSegEM.setProcessChannelMode( QProcess::MergedChannels );
            callSegEM.start( args.progSegEM.c_str(), argumentsNiftySeg );

            bool flgFinished = callSegEM.waitForFinished( 3600000 ); // Wait one hour

            outSegEM = callSegEM.readAllStandardOutput();

            message << "" << outSegEM.toStdString();

            if ( ! flgFinished )
            {
              message << "ERROR: Could not execute: " << args.progSegEM << " ( "
                      << callSegEM.errorString().toStdString() << " )" << std::endl;
              args.PrintMessage( message );

              continue;
            }

            args.PrintMessage( message );

            callSegEM.close();

            args.ReadImageFromFile( args.dirOutput,
                                    fileOutputParenchyma,
                                    "breast parenchyma", imParenchyma );
          }

          progress = ( iDirectory + ( iMask + ( iImage + 0.6 )/nImages )/nMasks )/nDirectories;
          std::cout  << std::endl << "<filter-progress>" << std::endl
                     << progress << std::endl
                     << "</filter-progress>" << std::endl << std::endl;

          // Compute the breast density
          // ~~~~~~~~~~~~~~~~~~~~~~~~~~

          if ( imParenchyma && imMask )
          {
            float nLeftVoxels = 0;
            float nRightVoxels = 0;

            float totalDensity = 0.;
            float leftDensity = 0.;
            float rightDensity = 0.;

            float meanOfHighProbIntensities = 0.;
            float meanOfLowProbIntensities = 0.;

            float nHighProbIntensities = 0.;
            float nLowProbIntensities = 0.;

            itk::ImageRegionIteratorWithIndex< ImageType >
              maskIterator( imMask, imMask->GetLargestPossibleRegion() );

            itk::ImageRegionConstIterator< ImageType >
              segmIterator( imParenchyma, imParenchyma->GetLargestPossibleRegion() );

            itk::ImageRegionConstIterator< ImageType >
              imIterator( imInput, imInput->GetLargestPossibleRegion() );

            ImageType::SpacingType spacing = imParenchyma->GetSpacing();

            float voxelVolume = spacing[0]*spacing[1]*spacing[2];

            ImageType::RegionType region;
            region = imMask->GetLargestPossibleRegion();

            ImageType::SizeType lateralSize;
            lateralSize = region.GetSize();
            lateralSize[0] = lateralSize[0]/2;

            ImageType::IndexType idx;

            for ( maskIterator.GoToBegin(), segmIterator.GoToBegin(), imIterator.GoToBegin();
                  ! maskIterator.IsAtEnd();
                  ++maskIterator, ++segmIterator, ++imIterator )
            {
              if ( maskIterator.Get() )
              {
                idx = maskIterator.GetIndex();

                // Left breast

                if ( idx[0] < (int) lateralSize[0] )
                {
                  nLeftVoxels++;
                  leftDensity += segmIterator.Get();
                }

                // Right breast

                else
                {
                  nRightVoxels++;
                  rightDensity += segmIterator.Get();
                }

                // Both breasts

                totalDensity += segmIterator.Get();

                // Ensure we have the polarity correct by calculating the
                // mean intensities of each class

                if ( segmIterator.Get() > 0.5 )
                {
                  meanOfHighProbIntensities += imIterator.Get();
                  nHighProbIntensities++;
                }
                else
                {
                  meanOfLowProbIntensities += imIterator.Get();
                  nLowProbIntensities++;
                }
              }
            }

            leftDensity /= nLeftVoxels;
            rightDensity /= nRightVoxels;
            totalDensity /= ( nLeftVoxels + nRightVoxels);

            // Calculate the mean intensities of each class

            if ( nHighProbIntensities > 0. )
            {
              meanOfHighProbIntensities /= nHighProbIntensities;
            }

            if ( nLowProbIntensities > 0. )
            {
              meanOfLowProbIntensities /= nLowProbIntensities;
            }

            message  << std::endl
                     << "Mean intensity of high probability class = " << meanOfHighProbIntensities
                     << " ( " << nHighProbIntensities << " voxels )" << std::endl
                     << "Mean intensity of low probability class = " << meanOfLowProbIntensities
                     << " ( " << nLowProbIntensities << " voxels )" << std::endl;
            args.PrintMessage( message );

            // Fat should be high intensity in the T2 image so if the
            // dense region (high prob) has a high intensity then it is
            // probably fat and we need to invert the density

            if ( (     args.flgFatIsBright   && ( meanOfHighProbIntensities > meanOfLowProbIntensities ) ) ||
                 ( ( ! args.flgFatIsBright ) && ( meanOfHighProbIntensities < meanOfLowProbIntensities ) ) )
            {
              message << std::endl << "Inverting the density estimation" << std::endl;
              args.PrintWarning( message );

              leftDensity  = 1. - leftDensity;
              rightDensity = 1. - rightDensity;
              totalDensity = 1. - totalDensity;

              itk::ImageRegionIterator< ImageType >
              imIterator( imParenchyma, imParenchyma->GetLargestPossibleRegion() );

              itk::ImageRegionConstIterator< ImageType >
              maskIterator( imMask, imMask->GetLargestPossibleRegion() );

              for ( maskIterator.GoToBegin(), imIterator.GoToBegin();
                    ! maskIterator.IsAtEnd();
                    ++maskIterator, ++imIterator )
              {
                if ( maskIterator.Get() )
                {
                  imIterator.Set( 1. - imIterator.Get() );
                }
              }

              args.WriteImageToFile( fileOutputParenchyma,
                                     std::string( "inverted parenchyma image" ),
                                     imParenchyma );
            }

            float leftBreastVolume = nLeftVoxels*voxelVolume;
            float rightBreastVolume = nRightVoxels*voxelVolume;

            message << "Number of left breast voxels: " << nLeftVoxels << std::endl
                    << "Volume of left breast: " << leftBreastVolume << " mm^3" << std::endl
                    << "Density of left breast (fraction of glandular tissue): " << leftDensity
                    << std::endl << std::endl

                    << "Number of right breast voxels: " << nRightVoxels << std::endl
                    << "Volume of right breast: " << rightBreastVolume << " mm^3" << std::endl
                    << "Density of right breast (fraction of glandular tissue): " << rightDensity
                    << std::endl << std::endl

                    << "Total number of breast voxels: "
                    << nLeftVoxels + nRightVoxels << std::endl
                    << "Total volume of both breasts: "
                    << leftBreastVolume + rightBreastVolume << " mm^3" << std::endl
                    << "Combined density of both breasts (fraction of glandular tissue): "
                    << totalDensity << std::endl << std::endl;
            args.PrintMessage( message );

            // Compute a naive value of the breast density

            float nLeftVoxelsNaive;
            float nRightVoxelsNaive;

            float totalDensityNaive;
            float leftDensityNaive;
            float rightDensityNaive;

            NaiveParenchymaSegmentation( args,
                                         imMask, imInput,
                                         flgFatIsBright,
                                         nLeftVoxelsNaive, nRightVoxelsNaive,
                                         totalDensityNaive, leftDensityNaive, rightDensityNaive,
                                         fileOutputParenchyma );

            if ( fileDensityMeasurements.length() != 0 )
            {
              std::string fileOutputDensityMeasurements
                = niftk::ConcatenatePath( args.dirOutput, fileDensityMeasurements );

              std::ofstream fout( fileOutputDensityMeasurements.c_str() );

              fout.precision(16);

              if ((! fout) || fout.bad())
              {
                message << "ERROR: Could not open file: " << fileDensityMeasurements << std::endl;
                args.PrintError( message );
                continue;
              }

              fout << "Study ID, "
                   << "Image, "
                   << "Mask, "

                   << "Number of left breast voxels, "
                   << "Volume of left breast (mm^3), "
                   << "Density of left breast (fraction of glandular tissue), "

                   << "Number of right breast voxels, "
                   << "Volume of right breast (mm^3), "
                   << "Density of right breast (fraction of glandular tissue), "

                   << "Total number of breast voxels, "
                   << "Total volume of both breasts (mm^3), "
                   << "Combined density of both breasts (fraction of glandular tissue), "

                   << "Naive density of left breast (fraction of glandular tissue), "
                   << "Naive density of right breast (fraction of glandular tissue), "
                   << "Naive combined density of both breasts (fraction of glandular tissue)"
                   << std::endl;

              fout << dirBaseName << ", "
                   << fileImageBasename << ", "
                   << fileMaskBasename << ", "

                   << nLeftVoxels << ", "
                   << leftBreastVolume << ", "
                   << leftDensity << ", "

                   << nRightVoxels << ", "
                   << rightBreastVolume << ", "
                   << rightDensity << ", "

                   << nLeftVoxels + nRightVoxels << ", "
                   << leftBreastVolume + rightBreastVolume << ", "
                   << totalDensity << ", "

                   << leftDensityNaive << ", "
                   << rightDensityNaive << ", "
                   << totalDensityNaive

                   << std::endl;

              fout.close();

              message  << "Density measurements written to file: "
                       << fileOutputDensityMeasurements  << std::endl << std::endl;
              args.PrintMessage( message );
            }

            // Write the data to the main collated csv file

            if ( args.foutOutputCSV )
            {
              if ( flgVeryFirstRow )    // Include the title row?
              {
                *args.foutOutputCSV << "Study ID, "
                                    << "Image, "
                                    << "Mask, "

                                    << "Number of left breast voxels, "
                                    << "Volume of left breast (mm^3), "
                                    << "Density of left breast (fraction of glandular tissue), "

                                    << "Number of right breast voxels, "
                                    << "Volume of right breast (mm^3), "
                                    << "Density of right breast (fraction of glandular tissue), "

                                    << "Total number of breast voxels, "
                                    << "Total volume of both breasts (mm^3), "
                                    << "Combined density of both breasts (fraction of glandular tissue), "

                                    << "Naive density of left breast (fraction of glandular tissue), "
                                    << "Naive density of right breast (fraction of glandular tissue), "
                                    << "Naive combined density of both breasts (fraction of glandular tissue)"

                                    << std::endl;
                flgVeryFirstRow = false;
              }

              *args.foutOutputCSV << dirBaseName << ", "
                                  << fileImageBasename << ", "
                                  << fileMaskBasename << ", "

                                  << nLeftVoxels << ", "
                                  << leftBreastVolume << ", "
                                  << leftDensity << ", "

                                  << nRightVoxels << ", "
                                  << rightBreastVolume << ", "
                                  << rightDensity << ", "

                                  << nLeftVoxels + nRightVoxels << ", "
                                  << leftBreastVolume + rightBreastVolume << ", "
                                  << totalDensity  << ", "

                                  << leftDensityNaive << ", "
                                  << rightDensityNaive << ", "
                                  << totalDensityNaive

                                  << std::endl;
            }
            else
            {
              message << "Collated csv data file: " << fileOutputCSV
                      << " is not open, data will not be written." << std::endl;
              args.PrintWarning( message );
            }
          }

          progress = ( iDirectory + ( iMask + ( iImage + 0.9 )/nImages )/nMasks )/nDirectories;
          std::cout  << std::endl << "<filter-progress>" << std::endl
                     << progress << std::endl
                     << "</filter-progress>" << std::endl << std::endl;

        }
        catch (itk::ExceptionObject &ex)
        {
          message << ex << std::endl;
          args.PrintError( message );
          continue;
        }

        progress = ( iDirectory + ( iMask + iImage/nImages )/nMasks )/nDirectories;
        std::cout  << std::endl << "<filter-progress>" << std::endl
                   << progress << std::endl
                   << "</filter-progress>" << std::endl << std::endl;
      }
    }
  }

  progress = iDirectory/nDirectories;
  std::cout  << std::endl << "<filter-progress>" << std::endl
             << progress << std::endl
             << "</filter-progress>" << std::endl << std::endl;

  return EXIT_SUCCESS;
}


// Copyright Tom Westerhout (c) 2018
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//
//     * Neither the name of Tom Westerhout nor the names of other
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "diffusion.hpp"
#include "hamiltonian.hpp"
#include "quantum_state.hpp"
#include <boost/exception/get_error_info.hpp>
#include <boost/optional.hpp>
#include <boost/program_options.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <optional>

namespace po = boost::program_options;

using IStreamPtr = std::unique_ptr<std::istream, void (*)(std::istream*)>;
using OStreamPtr = std::unique_ptr<std::ostream, void (*)(std::ostream*)>;

namespace {
auto parse_options(int argc, char** argv, IStreamPtr& input_file,
    OStreamPtr& output_file, std::string& hamiltonian_file_name, double& lambda,
    std::size_t& iterations, std::size_t& soft_max,
    boost::optional<std::size_t>& hard_max) -> bool
{
    std::string                  input_file_name;
    boost::optional<std::string> output_file_name;
    po::options_description      cmdline_options{"Command-line options"};
    // clang-format off
    cmdline_options.add_options()
        ("help", "Produce the help message.")
        ("input-file", po::value(&input_file_name)->required(),
            "File containing the initial quantum state. '-' can be used to "
            "indicate that the initial state should be read from the standard input.")
        ("output-file,o", po::value(&output_file_name),
            "Where to save the final quantum state.")
        ("hamiltonian,H", po::value(&hamiltonian_file_name)->required(),
            "The file containing the Hamiltonian specification.")
        ("lambda,L", po::value(&lambda)->default_value(1.0),
            "Value of Λ in the diffusion operator (H - Λ).")
        ("iterations,n", po::value(&iterations)->default_value(1.0),
            "Number of application of (H - Λ) to perform.")
        ("max", po::value(&soft_max)->default_value(1000),
            "Maximum number of elements to keep after each application of (H - Λ).")
        ("hard-max", po::value(&hard_max),
            "Initial number of buckets in the hash table. This parameter should "
            "be chosen carefully, because too low a value will result in a lot of "
            "rehashing, but too high a value will use more memory and result in "
            "more cache misses.")
    ;
    // clang-format on
    po::positional_options_description positional;
    positional.add("input-file", 1);

    po::variables_map vm;
    store(po::command_line_parser(argc, argv)
              .options(cmdline_options)
              .positional(positional)
              .run(),
        vm);
    if (vm.count("help")) {
        std::cout << cmdline_options << '\n';
        return false;
    }
    po::notify(vm);

    if (input_file_name == "-") {
        input_file = IStreamPtr{std::addressof(std::cin), [](auto*) {}};
    }
    else if (!std::filesystem::exists({input_file_name})) {
        throw std::runtime_error{
            "Input file '" + input_file_name + "' does not exist."};
    }
    else {
        input_file = IStreamPtr{new std::ifstream{input_file_name},
            [](auto* p) { std::default_delete<std::istream>{}(p); }};
        if (!*input_file) {
            throw std::runtime_error{
                "Could not open '" + input_file_name + "' for reading."};
        }
    }

    if (!output_file_name) {
        output_file = OStreamPtr{std::addressof(std::cout), [](auto*) {}};
    }
    else {
        // Issue #1: Prevent the user from overwriting the input file.
        if (std::filesystem::exists({*output_file_name})
            && input_file_name != "-"
            && std::filesystem::equivalent(
                   {*output_file_name}, {input_file_name})) {
            throw std::runtime_error{"Input file '" + input_file_name
                                     + "' and output file '" + *output_file_name
                                     + "' are the same."};
        }
        output_file = OStreamPtr{new std::ofstream{*output_file_name},
            [](auto* p) { std::default_delete<std::ostream>{}(p); }};
        if (!*output_file) {
            throw std::runtime_error{
                "Could not open '" + *output_file_name + "' for writing."};
        }
    }
    return true;
}

auto read_hamiltonian(std::string const& hamiltonian_file_name) -> Hamiltonian
{
    if (!std::filesystem::exists({hamiltonian_file_name})) {
        throw std::runtime_error{"Hamiltonian specification file '"
                                 + hamiltonian_file_name + "' does not exist."};
    }
    std::ifstream hamiltonian_file{hamiltonian_file_name};
    if (!hamiltonian_file) {
        throw std::runtime_error{
            "Could not open '" + hamiltonian_file_name + "' for reading."};
    }

    Heisenberg hamiltonian;
    if (!(hamiltonian_file >> hamiltonian) && !hamiltonian_file.eof()) {
        throw std::runtime_error{"Failed to parse the Hamiltonian."};
    }
    return {std::move(hamiltonian)};
}
} // namespace

int main(int argc, char** argv)
{
    try {
        IStreamPtr                   input_file{nullptr, [](auto*) {}};
        OStreamPtr                   output_file{nullptr, [](auto*) {}};
        std::string                  hamiltonian_file_name;
        double                       lambda;
        std::size_t                  iterations;
        std::size_t                  soft_max;
        boost::optional<std::size_t> hard_max;

        auto const proceed = parse_options(argc, argv, input_file, output_file,
            hamiltonian_file_name, lambda, iterations, soft_max, hard_max);
        if (!proceed) { return EXIT_SUCCESS; }

        QuantumState state{soft_max, hard_max ? *hard_max : 2 * soft_max, 1};
        *input_file >> state;
        auto const hamiltonian    = read_hamiltonian(hamiltonian_file_name);
        auto const initial_energy = energy(hamiltonian, state);
        *output_file << "# Result of evaluating (Λ - H)ⁿ|ψ₀〉for\n"
                     << "# Λ = " << lambda << '\n'
                     << "# n = " << iterations << '\n'
                     << "# E₀ = 〈ψ₀|H|ψ₀〉= " << initial_energy << '\n';
        state = diffusion_loop(lambda, hamiltonian, state, iterations);
        auto const final_energy = energy(hamiltonian, state);
        *output_file << "# => E = " << final_energy << '\n' << state;
        return EXIT_SUCCESS;
    }
    catch (std::exception const& e) {
        auto const* st = boost::get_error_info<errinfo_backtrace>(e);
        std::cerr << "Error: " << e.what() << '\n';
        if (st != nullptr) { std::cerr << "Backtrace:\n" << *st << '\n'; }
        return EXIT_FAILURE;
    }
    catch (...) {
        std::cerr << "Error: "
                  << "Unknown error occured." << '\n';
        return EXIT_FAILURE;
    }
}


// Boost.Geometry

// Copyright (c) 2018 Adeel Ahmad, Islamabad, Pakistan.

// Contributed and/or modified by Adeel Ahmad, as part of Google Summer of Code 2018 program.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_KARNEY_HPP
#define BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_KARNEY_HPP

#include <boost/geometry/strategies/geographic/distance.hpp>
#include <boost/geometry/strategies/geographic/parameters.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace distance
{

/*!
\brief The solution of the inverse problem of geodesics on latlong coordinates,
       after Karney (2011).
\ingroup distance
\tparam Spheroid The reference spheroid model
\tparam CalculationType \tparam_calculation
\author See
- Charles F.F Karney, Algorithms for geodesics, 2011
https://arxiv.org/pdf/1109.4448.pdf
*/
template
<
    typename Spheroid = srs::spheroid<double>,
    typename CalculationType = void
>
class karney
    : public strategy::distance::geographic
        <
            strategy::karney, Spheroid, CalculationType
        >
{
    typedef strategy::distance::geographic
        <
            strategy::karney, Spheroid, CalculationType
        > base_type;

public:
    inline karney()
        : base_type()
    {}

    explicit inline karney(Spheroid const& spheroid)
        : base_type(spheroid)
    {}
};

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
namespace services
{

template <typename Spheroid, typename CalculationType>
struct tag<karney<Spheroid, CalculationType> >
{
    typedef strategy_tag_distance_point_point type;
};

template <typename Spheroid, typename CalculationType, typename P1, typename P2>
struct return_type<karney<Spheroid, CalculationType>, P1, P2>
    : karney<Spheroid, CalculationType>::template calculation_type<P1, P2>
{};

template <typename Spheroid, typename CalculationType>
struct comparable_type<karney<Spheroid, CalculationType> >
{
    typedef karney<Spheroid, CalculationType> type;
};

template <typename Spheroid, typename CalculationType>
struct get_comparable<karney<Spheroid, CalculationType> >
{
    static inline karney<Spheroid, CalculationType> apply(karney<Spheroid, CalculationType> const& input)
    {
        return input;
    }
};

template <typename Spheroid, typename CalculationType, typename P1, typename P2>
struct result_from_distance<karney<Spheroid, CalculationType>, P1, P2 >
{
    template <typename T>
    static inline typename return_type<karney<Spheroid, CalculationType>, P1, P2>::type
        apply(karney<Spheroid, CalculationType> const& , T const& value)
    {
        return value;
    }
};

} // namespace services
#endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

}} // namespace strategy::distance

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_KARNEY_HPP


/**************************************************************************\
 * Copyright (c) Kongsberg Oil & Gas Technologies AS
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\**************************************************************************/

#include <Inventor/engines/SoHeightMapToNormalMap.h>

#include <boost/scoped_array.hpp>

#include <Inventor/SbVec3f.h>
#include <Inventor/SbImage.h>
#include "engines/SoSubEngineP.h"

/*!
  \class SoHeightMapToNormalMap SoHeightMapToNormalMap.h Inventor/engines/SoHeightMapToNormalMap.h
  \brief Engine for computing a normal map from a height map.

  This engine will create a normal map texture from a height map texture.
  You can use it in an Inventor file like this:

  \code
  Texture2 {
    image = HeightMapToNormalMap {
      sourceImage = Texture2 { filename "HeightMap.jpg" } . image
    } . image
  }
  \endcode

  Be aware that the field connections will remain active, so both
  Texture2 nodes and the HeightMapToNormalMap engine will be kept resident
  in memory (unless you intervene manually and detach the engine) even
  though only the "outer" Texture2 node is needed. This can give quite
  a big memory use overhead.

  \ingroup coin_engines
  \COIN_CLASS_EXTENSION
  \since Coin 3.0
*/

/*!
  \enum SoHeightMapToNormalMap::NormalMapFormat
  Enumeration of available normal map formats.
*/

/*!
  \var SoHeightMapToNormalMap::NormalMapFormat SoHeightMapToNormalMap::INT8
  Encode the normals as a 3 component byte texture.
  This is the only option for now, as long as float textures are not conveniently
  supported in Coin.
*/

/*!
  \var SoMFEnum SoHeightMapToNormalMap::format
  This setting decides what kind of normal map is generated.  For now, only the
  INT8 format is available, and it is the default value.
*/

SO_ENGINE_SOURCE(SoHeightMapToNormalMap);

/*!
  \copybrief SoBase::initClass(void)
*/
void
SoHeightMapToNormalMap::initClass(void)
{
  SO_ENGINE_INTERNAL_INIT_CLASS(SoHeightMapToNormalMap);
}

/*!
  Constructor.
*/
SoHeightMapToNormalMap::SoHeightMapToNormalMap(void)
{
  SO_ENGINE_INTERNAL_CONSTRUCTOR(SoHeightMapToNormalMap);

  SO_ENGINE_ADD_INPUT(format, (INT8));

  SO_ENGINE_DEFINE_ENUM_VALUE(NormalMapFormat, INT8);
  SO_ENGINE_SET_SF_ENUM_TYPE(format, NormalMapFormat);
}

/*!
  Static function for computing a normal map from a height map.
  This function can be used directly without any engine instantiation.
*/
void
SoHeightMapToNormalMap::convert(const unsigned char * srcptr, SbVec2s size, int nc, SbImage & dst_out)
{
  float dx, dy;
  int width = size[0];
  int height = size[1];
  boost::scoped_array<unsigned char> dstarray(new unsigned char[width*height*3]);
  unsigned char * dstptr = dstarray.get();
  unsigned char red;
  SbVec3f n;

#define GET_PIXEL_RED(x_, y_) \
  srcptr[(y_)*width*nc + (x_)*nc]

  for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
      // do Y Sobel filter
      red = GET_PIXEL_RED((x-1+width) % width, (y+1) % height);
      dy  = static_cast<float>(red) / 255.0f * -1.0f;

      red = GET_PIXEL_RED(x % width, (y+1) % height);
      dy += static_cast<float>(red) / 255.0f * -2.0f;

      red = GET_PIXEL_RED((x+1) % width, (y+1) % height);
      dy += static_cast<float>(red) / 255.0f * -1.0f;

      red = GET_PIXEL_RED((x-1+width) % width, (y-1+height) % height);
      dy += static_cast<float>(red) / 255.0f *  1.0f;

      red = GET_PIXEL_RED(x % width, (y-1+height) % height);
      dy += static_cast<float>(red) / 255.0f *  2.0f;

      red = GET_PIXEL_RED((x+1) % width, (y-1+height) % height);
      dy += static_cast<float>(red) / 255.0f *  1.0f;

      // Do X Sobel filter
      red = GET_PIXEL_RED((x-1+width) % width, (y-1+height) % height);
      dx  = static_cast<float>(red) / 255.0f * -1.0f;

      red = GET_PIXEL_RED((x-1+width) % width, y % height);
      dx += static_cast<float>(red) / 255.0f * -2.0f;

      red = GET_PIXEL_RED((x-1+width) % width, (y+1) % height);
      dx += static_cast<float>(red) / 255.0f * -1.0f;

      red = GET_PIXEL_RED((x+1) % width, (y-1+height) % height);
      dx += static_cast<float>(red) / 255.0f *  1.0f;

      red = GET_PIXEL_RED((x+1) % width, y % height);
      dx += static_cast<float>(red) / 255.0f *  2.0f;

      red = GET_PIXEL_RED((x+1) % width, (y+1) % height);
      dx += static_cast<float>(red) / 255.0f *  1.0f;

      n[0] = -dx;
      n[1] = -dy;
      n[2] = 1.0f;
      (void) n.normalize();

      *dstptr++ = static_cast<unsigned char>(SbMin((n[0]+1.0f) * 128.0f, 255.0f));
      *dstptr++ = static_cast<unsigned char>(SbMin((n[1]+1.0f) * 128.0f, 255.0f));
      *dstptr++ = static_cast<unsigned char>(SbMin((n[2]+1.0f) * 128.0f, 255.0f));
    }
  }
#undef GET_PIXEL_RED
  dst_out.setValue(size, 3, dstarray.get());
}

void
SoHeightMapToNormalMap::inputChanged(SoField * which)
{
  // in case we need to override later
  inherited::inputChanged(which);
}

void
SoHeightMapToNormalMap::evaluate(void)
{
  SbVec2s size;
  int nc;
  const unsigned char * ptr =
    static_cast<const unsigned char *>(sourceImage.getValue(size, nc));

  SbImage targetimg;
  SoHeightMapToNormalMap::convert(ptr, size, nc, targetimg);

  ptr = static_cast<const unsigned char *>(targetimg.getValue(size, nc));
  SO_ENGINE_OUTPUT(image, SoSFImage, setValue(size, nc, ptr));
}