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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
// .NAME vtkDICOMCollector - Consider a DICOM file and collect related slices.
// .SECTION Description
// vtkDICOMCollector reads a DICOM file and collect related slices that are
// part of the same volume.

#ifndef __vtkDICOMCollector_h
#define __vtkDICOMCollector_h

#include "vtkObject.h"

#include "vtkKWCommonProConfigure.h" // KWCommonPro_USE_GDCM is defined here:
#ifdef KWCommonPro_USE_GDCM
#define KWCommonPro_USE_GDCM_FOR_HEADER 0
#define KWCommonPro_USE_GDCM_FOR_IMAGE 1
#else
#define KWCommonPro_USE_GDCM_FOR_HEADER 0
#define KWCommonPro_USE_GDCM_FOR_IMAGE 0
#endif

typedef void *DCM_OBJECT;

class vtkDICOMCollectorInternals;
class vtkVolumeSliceIndexesInternals;
class vtkMedicalImageProperties;
class vtkUnsignedShortArray;
class vtkStringArray;
class vtkDICOMCollectorOptions;

class VTK_EXPORT vtkDICOMCollector : public vtkObject
{
public:
  static vtkDICOMCollector *New();
  vtkTypeRevisionMacro(vtkDICOMCollector, vtkObject);
  virtual void PrintSelf(ostream& os, vtkIndent indent);
  
  // Description:
  // Specify file name for the image file to query the series/volume it is
  // part of. It is also possible ito specify the list of filenames explicitly:
  // this will bypass the collecting routine.
  virtual const char *GetFileName();
  virtual const char *GetNthFileName(int i);
  virtual void SetFileName(const char *);
  virtual void SetFileNames(vtkStringArray *);
  virtual int GetNumberOfFileNames();

  //BTX

  class VTK_EXPORT ImageInfo
  {
  public:
    unsigned short SamplesPerPixel;
    unsigned short Rows;
    unsigned short Columns;
    unsigned short Planes;
    unsigned short BitsAllocated;
    unsigned short BitsStored;
    unsigned short HighBit;
    enum {
      PixelRepresentationUnsigned = 0,
      PixelRepresentationSigned = 1
    };
    unsigned short PixelRepresentation; // 0 unsigned, 1 two-complements
    double Spacing[3];
    double Position[3];
    double Orientation[9];
    int    AcquisitionNumber;
    short  PixelPaddingValue;
    bool   PixelPaddingValueTagFound;
    double RescaleIntercept;
    double RescaleSlope;
    unsigned int NumberOfFrames;
    unsigned int InstanceNumber;  // Type 2

    int IsRelatedTo(ImageInfo&, int consider_acq);
    
    ImageInfo();
    ~ImageInfo();

    void  SetSOPInstanceUID(const char *arg);
    const char* GetSOPInstanceUID() { return this->SOPInstanceUID; };
    void  SetSeriesInstanceUID(const char *arg);
    const char* GetSeriesInstanceUID() { return this->SeriesInstanceUID; };
    void  SetStudyInstanceUID(const char *arg);
    const char* GetStudyInstanceUID() { return this->StudyInstanceUID; };
    void  SetTransferSyntaxUID(const char *arg);
    const char* GetTransferSyntaxUID() { return this->TransferSyntaxUID; };

    double *GetOrientationRow()    { return this->Orientation; }
    double *GetOrientationColumn() { return this->Orientation+3; }
    double *GetOrientationImage()  { return this->Orientation+6; }
   
    void PrintSelf(ostream& os, vtkIndent indent);

  protected:
    char  *SOPInstanceUID;
    char  *SeriesInstanceUID;
    char  *StudyInstanceUID;
    char  *TransferSyntaxUID;
  };

  // Description:
  // Return 1 if a file can be opened as a DICOM file and 
  // basic image information can be retrieved (see GetCurrentImageInfo())
  // Return 0 otherwise.
  // If filename is NULL, the FileName ivar will be used.
  virtual int CanReadFile(const char *filename = 0);

  // Description:
  // Get the image info and image header info of the DICOM image corresponding
  // to the current FileName. It does not collect any related images/slices.
  // Return NULL on error.
  virtual ImageInfo* GetCurrentImageInfo();
  virtual ImageInfo* GetSelectedImageInfo();
  virtual void SelectVolume(int vol_idx);
  virtual vtkMedicalImageProperties* GetCurrentImageMedicalProperties();

  // Description:
  // Get the orientation permutations that can be used to map the 
  // slice(s) extent to the patient coordinate system (then RAS).
  // For example, if idx[0] = 2, the columns dimension (x) of the slice should 
  // be mapped to the slice dimension (z) of the volume.
  // Index range from 0 to 2 (3 dimensions).
  // Return 1 on success, 0 on error
  virtual int GetOrientationPermutations(int idx[3]);

  // Description:
  // Get the increments that can be used to map the pixels of a slice(s) 
  // to the patient coordinate system (then RAS).
  // While the slice is processed through normal increments, those increments
  // will be used to march through the volume and assign the pixel to its
  // correct location. The 'offset' pointer must be first added to the 
  // beginning of the volume array since increments can be negative.
  // Return 1 on success, 0 on error
  virtual int GetOrientationIncrements(int incs[3], long *offset);

  // Description:
  // Collect all slices related to the current FileName.
  // Return the total number of slices collected.
  // Note that this method emits vtkCommand::StartEvent, 
  // vtkCommand::ProgressEvent and vtkCommand::EndEvent events.
  // A call to ClearCollectedSlices will clear the slices collected so far,
  // forcing a potential re-collect the next time some info is requested
  // (this should be used for example if you set ExploreDirectory to Off
  // on the vtkDICOMCollectorOptions object associated to this instance).
  virtual int CollectAllSlices();
  virtual int GetNumberOfCollectedSlices();
  virtual void ClearCollectedSlices();

  // Description:
  // Usually those function simply return 0 and size-1 for the start and end
  // in a DICOM series. Unless you are in the case of a SCOUT series or a 
  // DWI/DTI case we need to consider each sub volume separately.
  // WARNING: GetEndSliceForVolume returns the last slice, so your loop should
  // be: for ( i = start; i <= end; ++i )
  virtual int GetNumberOfCollectedSlicesForVolume(int volumeidx);
  virtual int GetNumberOfVolumes();
  virtual int GetStartSliceForVolume(int volumeidx );
  virtual int GetEndSliceForVolume(int volumeidx );
  virtual double GetSpacing(int volumeidx);

  // Description:
  // Usually this function simply returns 0. Since there is most of the time
  // only one volume per series. Whereas in the case of a SCOUT or DWI/DTI you
  // can have multiple sub volumes so you may want to check which of those sub
  // volume the user selected using a single filename. See ExploreDirectory to
  // remove this behavior in the SCOUT case
  vtkGetMacro(CurrentVolume, int);

  // Description:
  // Get the slice spacing of the set of slices once they have been collected.
  // (CollectAllSlices() is called automatically).
  // Default to 1.0 if only one file was loaded
  // WARNING: I need to deprecate this function otherwise it collides with 
  // GetSpacing
  virtual double GetSliceSpacing();

  // Description:
  // Get the filename, image info and image header info of a given slice
  // among the collected slices. (CollectAllSlices() is called automatically).
  // Return NULL on error.
  virtual const char *GetSliceFileName(int slice_idx);
  virtual ImageInfo* GetSliceImageInfo(int slice_idx);
  virtual vtkMedicalImageProperties* GetSliceImageMedicalProperties(int slice_idx);

  // Description:
  // Get the image data of a given slice among the collected slices
  // (CollectAllSlices() is called automatically) and copy it to 'data'.
  // If shift_mask is 1, data is automatically masked and shifted so that
  // the pixels are "cleaned" and shifted to start at bit 0.
  // Return 1 on success, 0 on error
  virtual int GetSliceImageData(int slice_idx, void *data, int shift_mask = 1);

  // Description:
  // Get the Pixel padding value extents for a certain 'y' co-ordinate at 
  // the slice 'slice_idx'. For now, it is assumed that all slices have the
  // same extents. So nothing is done with the last argument. If there are
  // The extents indicate the maximum possible extents that are allowed. The
  // pixel values at both extents are within the image and not one of the padded
  // pixels. The extents are just calculated for the first slice. (It is assumed
  // that there is no gantry tilt)
  // 
  // \code
  //   vtkUnsignedShortArray * arr = 
  //                  dicomCollector->GetSlicePixelPaddingValueExtents();
  //   unsigned short padExt[2];
  //   arr->GetTupleValue(10,padExt); // extents on row 10.
  // \endcode
  virtual vtkUnsignedShortArray 
    * const GetSlicePixelPaddingValueExtents( int slice_idx=0 ) const;

  // Description:
  // Get the scalar range of the data stored in the slices located
  // between 'slice_idx_start' and 'slice_idx_end' (inclusive)
  // (CollectAllSlices() is called automatically).
  // If the max_nb_slices parameter is not 0, only up to max_nb_slices
  // will be sampled between 'start' and 'end'.
  // If 'pixel_step' is set, only every 'pixel_step'-th pixel will be 
  // considered.  This is only useful when trying to retrieve a good estimate
  // of the scalar range in a fast way (given the nature of the image, 
  // processing every 2 or 3 pixel should be enough).
  // Note that this method emits vtkCommand::StartEvent, 
  // vtkCommand::ProgressEvent and vtkCommand::EndEvent events.
  // Return 1 on success, 0 on error
  virtual int GetSlicesScalarRange(
    int slice_idx_start, int slice_idx_end, 
    double *min, double *max, 
    int max_nb_slices = 0,
    int pixel_step = 1);

  // Description:
  // Convenience method to check if a file is part of that series. 
  virtual int DoesIncludeFile(const char *fname);

  //ETX

  //BTX

  class ImageSlot
  {
  public:
    ImageInfo       *Info;
    vtkMedicalImageProperties *MedicalProperties;
    
    void SetFileName(const char *);
    const char *GetFileName() { return this->FileName; };

    ImageSlot();
    ~ImageSlot();

    void PrintSelf(ostream& os, vtkIndent indent);

  protected:
    char            *FileName;
  };

  //ETX
  
  // Description:
  // Compute the origin of the DICOM image. See the source for futher 
  // documentaton. Returns 0 on failure. 1 on success.
  virtual int ComputeImageOrigin(double origin[3]);

  // Description:
  // There are CT datasets that mess up the scalar range because of data
  // outside the circular reconstruction perimeter. Scanners are supposed to
  // provide a PixelPaddingValue tag to signify this. However, not all of them
  // do. This method checks if the data is CT, with data type US/SS and if the
  // PixelPaddingValue tag isn't present returns the estimated radius of the
  // circle. 
  //
  // Only a few slices are checked, See documentation of GetSlicesScalarRange()
  // for details.
  // 
  // Radius is VTK_DOUBLE_MAX if no estimate was possible.
  virtual void GetSlicesInscribedCircle(
    int slice_idx_start, int slice_idx_end, 
    double *min, double *max,
    double *Radius, int max_nb_slices);
  
  int    PixelPaddingValueTagAssumed; // the tag wasn't found, but is assumed
                                 // Most scanners don't specify the tag to 
                                 // eliminate pixels outisde the reconstruction
                                 // perimeter.. assume the tag if necessary.
  short  PixelPaddingValue;

  // Description:
  // Any informative messages on how the reader failed 
  //BTX
  typedef enum
    {
    FailureNone                             = 0x0000, // No failure happen
    FailureNotReadable                      = 0x0001, // not a DICOM file
    FailureUnknownError                     = 0x0002,
    FailureTooLittlePixelData               = 0x0004,
    FailureImagesNotFacingTheSameDirection  = 0x0008,
    FailureGantryTilt                       = 0x0010,
    FailureMoreThanOneSamplePerPixel        = 0x0020,
    FailureMoreThanOneNumberOfFrames        = 0x0040,
    FailureNotOrthogonal                    = 0x0080,
    FailureMissingFile                      = 0x0100,
    FailureNonASCII                         = 0x0200,
    FailureNoSOPClassUID                    = 0x0400,
    FailureNoSpatialInformation             = 0x0800,
    FailureMultipleSamplesPerPixel          = 0x1000,
    FailureUnknownSeriesType                = 0x2000, 
    FailureMissingRequiredElements          = 0x4000,
    FailureCannotGetPixelDataSize           = 0x8000
    } FailureStatusTypes;
  //ETX
  vtkGetMacro(FailureStatus, int);

  // Description:
  // The DICOM options.
  // Note that a call to ClearCollectedSlices is generally useful after 
  // modifying the option's ExploreDirectory value.
  vtkGetObjectMacro(Options, vtkDICOMCollectorOptions);
  virtual void SetOptions(vtkDICOMCollectorOptions *);

protected:
  vtkDICOMCollector();
  ~vtkDICOMCollector();

  // Description:
  // Series file names
  vtkStringArray *FileNames;

  double SliceSpacing;

  //BTX

  // Description:
  // PIMPL Encapsulation for STL containers
  //BTX
  vtkDICOMCollectorInternals *Internals;
  vtkVolumeSliceIndexesInternals *SliceIndexes;
  //ETX

  // Description:
  // Retrieve info and header info for a given image
  // Return a FailureStatusTypes
  virtual int RetrieveImageInfo(ImageSlot *image, int quiet = 1);
  virtual int RetrieveImageMedicalProperties(ImageSlot *image, int quiet = 1);

  // Description:
  // Retrieve data for a given image
  // If 'min' and 'max' are not NULL, the minimum and maximum scalar range
  // will be retrieved at the same time.
  // If 'pixel_step' is set, only every 'pixel_step'-th pixel will be 
  // considered.  This is only useful when trying to retrieve a good estimate
  // of the scalar range in a fast way (given the nature of the image, 
  // processing every 2 or 3 pixel should be enough).
  // Note that the size of the buffer will remain the same, i.e. only
  // the first step-th part of the buffer will be filled with valid pixels.
  // Return a FailureStatusTypes
  virtual int RetrieveImageData(ImageSlot *image, void *data, 
                                int shift_mask = 1, 
                                int quiet = 1,
                                double *min = NULL,
                                double *max = NULL,
                                int pixel_step = 1);

  // Description:
  // Get the info and header info for a given image
  // This info is not retrieved (see above) if it has been retrieved already
  virtual ImageInfo* GetImageInfo(ImageSlot *image);
  vtkMedicalImageProperties* GetImageMedicalProperties(ImageSlot *image);

  // Description:
  // Get the image orientation. The following 2 methods populate the 
  // orientation direction cosines into "info", so the DICOMReader 
  // can orient the data correctly.
  // Check for ImageOrientation or Patient Orientation.
  // (or set to [1.0000/0.0000/0.0000/0.0000/1.0000/0.0000])
#if defined(KWCommonPro_USE_GDCM) && KWCommonPro_USE_GDCM_FOR_HEADER
  virtual int GetImageOrientation(const gdcmFile *, 
                                  vtkDICOMCollector::ImageInfo *info );
#else
  virtual int GetImageOrientation(DCM_OBJECT *object,
                                  vtkDICOMCollector::ImageInfo *info, 
                                  ImageSlot* image );
#endif

  // The image corresponding to the current FileName

  ImageSlot *CurrentImage;
  ImageSlot* GetCurrentImage();

  // This one is a little more tricky. In order not to break too much 
  // vtkDICOMCollector API we define a notion of selection. Once the image
  // is selected the operation will be done one this one (it used to be the
  // CurrentImage before)
  ImageSlot *SelectedImage;

  // Description:
  // Can the image specified in the slot be read ? Return 0 if we cannot read
  // because its an invalid DICOM image. 1 if we cannot read because we do
  // not support it. 3 if we can read it.
  virtual int CanReadImage(ImageSlot *image);

  //ETX

  virtual void DeleteCurrentImage();
  virtual void DeleteAllSlices();

  virtual void ComputeSliceSpacing(int current_vol);

#ifdef KWCommonPro_USE_CTNLIB
  virtual int OpenDicomFile(const char *filename, DCM_OBJECT **object);
#endif

  // Description:
  // Modify inplace the char* into a human readable string for person name (PN)
  virtual void RearrangeName(char *name);

  // Description:
  // Collect current image.
  // Return a FailureStatusTypes
  virtual int CollectCurrentImage();

  virtual int GetOrientationPermutationsAndIncrements(
    int idx[3], int incs[3], long *offset);

  // Calculated only if. See vtkDICOMCollector.cxx
  double CTReconstructionRadius; 

  // Description:
  // To be used internally to give meaningful failure messages.
  int FailureStatus;

  // Description:
  // the current volume selected by user input (the first filename). Usually this
  // is simply 0
  int CurrentVolume;

  // Description:
  // In some case even in a single series you can have missing slices. Thus
  // instead of hacking your way with 'most representative spacing', build
  // a volume for each consistant number of volume
  // If volume: volumeidx does not have consistant spacing, then internally
  // update the vtkVolumeSliceIndexesInternals to add as many subvolume as we
  // need NOTE: After calling this function the volumeidx might have his 
  // start/end changed
  int CheckSpacingConsistancy(unsigned int volumeidx);

  vtkDICOMCollectorOptions *Options;

private:
  vtkDICOMCollector(const vtkDICOMCollector&); // Not implemented
  void operator=(const vtkDICOMCollector&); // Not implemented

  vtkUnsignedShortArray *PaddedExtents; // An array
};

#endif

