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

  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.

=========================================================================*/
#include "vtkDICOMReader.h"

#include "vtkCharArray.h"
#include "vtkDICOMCollector.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkShortArray.h"
#include "vtkUnsignedCharArray.h"
#include "vtkUnsignedShortArray.h"
#include "vtkEventForwarderCommand.h"
#include "vtkMedicalImageProperties.h"
#include "vtkStringArray.h"
#include "vtkExecutive.h"
#include "vtkStreamingDemandDrivenPipeline.h"

#include <time.h>

#include <vtksys/stl/map>

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkDICOMReader);
vtkCxxRevisionMacro(vtkDICOMReader, "$Revision: 1.76 $");

//----------------------------------------------------------------------------
vtkDICOMReader::vtkDICOMReader()
{
  this->FileDimensionality = 3;
  this->DICOMCollector = vtkDICOMCollector::New();

  // Let's forward event from our helper class

  this->CollectorEventForwarder = vtkEventForwarderCommand::New();
  this->CollectorEventForwarder->SetTarget(this);
  this->DICOMCollector->AddObserver(
    vtkCommand::StartEvent, this->CollectorEventForwarder);
  this->DICOMCollector->AddObserver(
    vtkCommand::ProgressEvent, this->CollectorEventForwarder);
  this->DICOMCollector->AddObserver(
    vtkCommand::EndEvent, this->CollectorEventForwarder);
}

//----------------------------------------------------------------------------
vtkDICOMReader::~vtkDICOMReader()
{
  if (this->CollectorEventForwarder)
    {
    if (this->DICOMCollector)
      {
      this->DICOMCollector->RemoveObserver(this->CollectorEventForwarder);
      }
    this->CollectorEventForwarder->Delete();
    this->CollectorEventForwarder = NULL;
    }

  if (this->DICOMCollector)
    {
    this->DICOMCollector->Delete();
    this->DICOMCollector = NULL;
    }
}

//----------------------------------------------------------------------------
void vtkDICOMReader::SetFileName(const char *name)
{
  this->Superclass::SetFileName(name);
  this->DICOMCollector->SetFileName(name);
}

//----------------------------------------------------------------------------
void vtkDICOMReader::SetFileNames(vtkStringArray *filenames)
{
  this->DICOMCollector->SetFileNames(filenames);
  this->Superclass::SetFileName(this->DICOMCollector->GetFileName());
}

//----------------------------------------------------------------------------
int vtkDICOMReader::GetMedicalProperties()
{
  vtkMedicalImageProperties *hinfo =
    this->DICOMCollector->GetCurrentImageMedicalProperties();

  vtkMedicalImageProperties *med_prop = this->GetMedicalImageProperties();
  if (med_prop)
    {
    med_prop->Clear();
    }

  if (!hinfo)
    {
    return 0;
    }

  med_prop->DeepCopy(hinfo);

  return 1;
}

//----------------------------------------------------------------------------
int vtkDICOMReader::RequestInformation(vtkInformation *request,
                                       vtkInformationVector **inputVector,
                                       vtkInformationVector *outputVector)
{
  // Get current image info

  vtkDICOMCollector::ImageInfo *info =
    this->DICOMCollector->GetCurrentImageInfo();
  if (!info)
    {
    return 0;
    }

  // Collect all slices (we need the slice spacing)

  if (!this->DICOMCollector->CollectAllSlices())
    {
    return 0;
    }

  // Get the dicom header info and fill the medical fields of the superclass

  this->GetMedicalProperties();

  vtkMedicalImageProperties *med_prop = this->GetMedicalImageProperties();
  if (med_prop && med_prop->GetGantryTiltAsDouble() != 0.0)
    {
    vtkWarningMacro(
      "DICOM file [" << (this->FileName ? this->FileName : "")
      << "]\n => images appear to have a gantry tilt which cannot be used "
      "for building a volume.");
    return 0;
    }

  // Use the info from the current image and the slices collection

  this->SetNumberOfScalarComponents(info->SamplesPerPixel);

  // Since we are going to apply the rescale slope and intercept, make
  // an educated guess on what kind of scalar type will be able to
  // store the current data type range once it has been scaled and shifted.

  // Note: if we are dealing with a CT and "bits allocated" = 16 and 
  // and "bits stored" = 16, then it is likely that the "bits stored" should
  // really be 12, not 16. In that case, we will try to sample some of
  // the slices to find the real scalar range. Doing so should enable us
  // to fit a slice in 16 bits (unsigned short) instead of 32 bits (int),
  // saving half of the space.

  double min, max;

  if (info->PixelRepresentation == 
      vtkDICOMCollector::ImageInfo::PixelRepresentationUnsigned)
    {
    min = 0;
    max = pow(2.0, info->BitsStored);
    }
  else
    {
    min = -pow(2.0, info->BitsStored - 1);
    max = pow(2.0, info->BitsStored - 1) - 1;
    }

  if (info->BitsAllocated == 16 && 
      info->BitsStored > 12 &&
      info->SamplesPerPixel == 1 &&
      this->GetModality() && !strcmp(this->GetModality(), "CT"))
    {
    min = VTK_DOUBLE_MAX;
    max = VTK_DOUBLE_MIN;

    // Note that we are retrieving an estimate of the scalar range, using
    // only up to 5 slices, every 3 pixels
    //
    // First get a crude estimate of the min and max. Then if it is CT, and
    // we think that there is a circular reconstruction perimeter to be
    // discarded, get a crude estimate of the pixel value outside the perimeter
    // to be replaced. Then setting the replace value to -1024, redo and get
    // the min and max. (which may be *and is often* less than -1024). The
    // reason for doing this twice is that we won't assume any circular perimeter
    // unless the data conforms to a certain scalar range and then if true, 
    // we need to find the min after its been replaced with HS air = -1024.
    //
    // yeah.. little twisted, but if you have questions ask me.
    //

    this->DICOMCollector->GetSlicesScalarRange(
      0, this->DICOMCollector->GetNumberOfCollectedSlices() - 1, 
      &min, &max, 5, 3);

    // 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. The below is to take care of this stuff.

    if ((min < -1024 || max > (4095-1024)) && !info->PixelPaddingValueTagFound
        && info->Rows == info->Columns )
      {
      double radius;
      double tmp1 = VTK_DOUBLE_MAX, tmp2 = VTK_DOUBLE_MIN;
      this->DICOMCollector->GetSlicesInscribedCircle( 0, 
          this->DICOMCollector->GetNumberOfCollectedSlices() - 1, 
          &tmp1, &tmp2, &radius, 4);
      min = (min < tmp1) ? min : tmp1;
      max = (max > tmp2) ? max : tmp2;
      }

    if (info->PixelPaddingValueTagFound || 
        this->DICOMCollector->PixelPaddingValueTagAssumed )
      {
      min = VTK_DOUBLE_MAX;
      max = VTK_DOUBLE_MIN;
      this->DICOMCollector->GetSlicesScalarRange(
        0, this->DICOMCollector->GetNumberOfCollectedSlices() - 1, 
        &min, &max, 8, 3);
      
      if ((min*info->RescaleSlope + info->RescaleIntercept) > -1024)
        {
        
        }
      //if ( this->DICOMCollector->PixelPaddingValueTagAssumed )
      //  {
      //  vtkWarningMacro( 
      //    " A CT circular reconstruction circle has been scheduled for removal.\n" <<
      //    "The value outside was estimated to be " << 
      //    ((this->DICOMCollector->PixelPaddingValue)
      //     *info->RescaleSlope + info->RescaleIntercept) << "\n and will be replaced with "
      //    << min );
      //  }
      //else if ( info->PixelPaddingValueTagFound )
      //  {
      //  vtkWarningMacro(
      //   "PixelPaddingValueTag 0x0028|0x0120 was found \n" << 
      //    "The value outside was estimated to be " << 
      //    info->PixelPaddingValue*info->RescaleSlope + info->RescaleIntercept 
      //    << "\n and will be replaced with -1024"
      //    << min );
      //  }
      }

    min = VTK_DOUBLE_MAX;
    max = VTK_DOUBLE_MIN;

    // Note that we are retrieving an estimate of the scalar range, using
    // only up to 15 slices, every 3 pixels

    this->DICOMCollector->GetSlicesScalarRange(
      0, this->DICOMCollector->GetNumberOfCollectedSlices() - 1, 
      &min, &max, 15, 3);

    }

  // tempmin and tempmax are the min/max in the BitsStored space. 
  // Now let's see in the Pixel Value space how much is the min/max, and pick
  // the largest data type space

  vtkstd::map< int, int > types_ordered;
  types_ordered[VTK_BIT] = 0;
  types_ordered[VTK_UNSIGNED_CHAR] = 1;
  types_ordered[VTK_SIGNED_CHAR] = 2;
  types_ordered[VTK_CHAR] = 3;
  types_ordered[VTK_UNSIGNED_SHORT] = 4;
  types_ordered[VTK_SHORT] = 5;
  types_ordered[VTK_UNSIGNED_INT] = 6;
  types_ordered[VTK_INT] = 7;
  types_ordered[VTK_UNSIGNED_LONG] = 8;
  types_ordered[VTK_LONG] = 9;
#if defined(VTK_TYPE_USE_LONG_LONG)
  types_ordered[VTK_UNSIGNED_LONG_LONG] = 10;
  types_ordered[VTK_LONG_LONG] = 11;
#endif
#if defined(VTK_TYPE_CONVERT_UI64_TO_DOUBLE)
  types_ordered[VTK_UNSIGNED___INT64] = 12;
#endif
#if defined(VTK_TYPE_USE___INT64)
  types_ordered[VTK___INT64] = 13;
#endif
  types_ordered[VTK_FLOAT] = 14;
  types_ordered[VTK_DOUBLE] = 15;

  int slice_idx;
  int nb_of_slice = this->DICOMCollector->GetNumberOfCollectedSlices();
  int new_type = VTK_BIT;
  for (slice_idx = 0; slice_idx < nb_of_slice; slice_idx++)
    {
    vtkDICOMCollector::ImageInfo *slice_info = 
      this->DICOMCollector->GetSliceImageInfo(slice_idx);
    if (slice_info)
      {
      int potential_type = vtkMath::GetScalarTypeFittingRange(
        min, max, slice_info->RescaleSlope, slice_info->RescaleIntercept);
      if (types_ordered[potential_type] > types_ordered[new_type])
        {
        new_type = potential_type;
        }
      }
    }
  // if new_type = VTK_LONG_LONG vtkImageReslice/vtkKWHistogram will not work
  if ( new_type == VTK_LONG_LONG )
    {
    vtkErrorMacro( "Unhandled case.");
    return 0;
    }

  // Now let's update the number of output ports of this reader:
  this->SetNumberOfOutputPorts(this->DICOMCollector->GetNumberOfVolumes());
  // RequestInformation is too late for vtkStreamingDemandDrivenPipeline
  // so let's allocate the output ourselves
  for(int o = 0; o < this->DICOMCollector->GetNumberOfVolumes(); ++o)
    {
    if( !this->GetOutput(o) )
      {
      // Tell the executive to set a new output
      vtkImageData *img = vtkImageData::New();
      this->GetExecutive()->SetOutputData(o, img );
      img->Delete();
      }
    }

  // Loop over all volumes and setup all the infos:
  for(int volume = 0; volume < this->DICOMCollector->GetNumberOfVolumes(); ++volume)
    {
    this->DICOMCollector->SelectVolume( volume );
    vtkDICOMCollector::ImageInfo *sel_info =
      this->DICOMCollector->GetSelectedImageInfo();
    // Dimensions/Spacing on disk

    int dims_on_disk[3];
    dims_on_disk[0] = sel_info->Columns;
    dims_on_disk[1] = sel_info->Rows;
    dims_on_disk[2] =
      this->DICOMCollector->GetNumberOfCollectedSlicesForVolume(volume) * info->Planes;

    float spcs_on_disk[3];
    spcs_on_disk[0] = sel_info->Spacing[0];
    spcs_on_disk[1] = sel_info->Spacing[1];
    spcs_on_disk[2] = this->DICOMCollector->GetSpacing(volume);

    // Get the orientation permutations

    int idx[3];
    if (!this->DICOMCollector->GetOrientationPermutations(idx))
      {
      return 0;
      }

    // Dimensions/Spacing in mem once the slice/volume has been re-oriented 
    // to patient coordinate space (then RAS)

    int dims[3];
    double spcs[3];
    for (int i = 0; i < 3; i++)
      {
      dims[idx[i]] = dims_on_disk[i];
      spcs[idx[i]] = spcs_on_disk[i];
      }
  vtkInformation *outInfo = outputVector->GetInformationObject(volume);
  outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(),
               0, dims[0]-1, 0, dims[1]-1, 0, dims[2]-1);

  if (new_type >= 0)
    {
    vtkDataObject::SetPointDataActiveScalarInfo(outInfo, new_type, 1);
    }
  outInfo->Set(vtkDataObject::SPACING(), spcs, 3);

  double origin[3];
  if (this->DICOMCollector->ComputeImageOrigin(origin))
    {
    outInfo->Set(vtkDataObject::ORIGIN(), origin, 3);
    }
  // Else I would set the origin to 0, but for some reason, 0.125 is chosen
  // as the preferred number in vtkKWOpenWizard .. ??
  }

  // DO NOT call the super class RequestInformation. In particular vtkImageReader2
  // will try to overide anything you set with its own vars: DataOrigin, DataSpacing
  // DataExtent and DataScalarType on the information vector
  return 1;
}

//----------------------------------------------------------------------------
template <class T, class S>
void vtkDICOMReaderExecuteDataTemplate2(
  vtkDICOMReader *self, T *vtkNotUsed(dummy), S *slice_start,
  vtkImageData *output)
{
  // Get info about slices

  vtkDICOMCollector::ImageInfo *info = 
    self->GetDICOMCollector()->GetSelectedImageInfo();
    //self->GetDICOMCollector()->GetCurrentImageInfo();
  if (!info)
    {
    return;
    }

  // Get the orientation increments/offset

  int incs[3];
  long offset = 0;
  if (!self->GetDICOMCollector()->GetOrientationIncrements(incs, &offset))
    {
    return;
    }

  // Get a pointer to the ouput volume
  
  vtkDataArray *volume_scalars = 
    output->GetPointData()->GetScalars();
  T *vol_ptr = (T *)volume_scalars->GetVoidPointer(0) + offset;

  // Process each slice and put it in the volume at the right place

  int current_vol = self->GetDICOMCollector()->GetCurrentVolume();
  int num_slices = self->GetDICOMCollector()->GetNumberOfCollectedSlicesForVolume(current_vol);
  int i, j, k, slice_idx;

  int same_type = sizeof(S) == sizeof(T);
  int same_row_struct =
    same_type && incs[0] == 1 && info->SamplesPerPixel == 1;
  int same_plane_struct =
    same_row_struct && incs[1] == info->Rows;

  long plane_size = info->Rows * info->Columns;

  int idx_slice_start = self->GetDICOMCollector()->GetStartSliceForVolume(current_vol);
  int idx_slice_end   = self->GetDICOMCollector()->GetEndSliceForVolume(current_vol);
  for (slice_idx = idx_slice_start; slice_idx <= idx_slice_end; slice_idx++)
    {
    vtkDICOMCollector::ImageInfo *slice_info = 
      self->GetDICOMCollector()->GetSliceImageInfo(slice_idx);
    if (slice_info)
      {
      int same_per_pixel = 
        slice_info->RescaleSlope == 1.0 && slice_info->RescaleIntercept == 0.0;
      S *slice_ptr = slice_start;
      if (self->GetDICOMCollector()->GetSliceImageData(
            slice_idx, (unsigned char *)slice_ptr))
        {
        T *plane_ptr = vol_ptr;

        for (k = 0; k < info->Planes; k++)
          {
          T *row_ptr = plane_ptr;

          // Try to avoid the multiplication and addition (scale/intercept)
          // if the copy per pixel is the same

          if (same_per_pixel)
            {
            // If a single plane has the same structure in both source
            // and destination, use faster memcpy

            if (same_plane_struct)
              {
              memcpy(row_ptr, slice_ptr, plane_size * sizeof(S));
              slice_ptr += plane_size;
              }
            else
              {
              for (j = 0; j < info->Rows; j++)
                {
                T *col_ptr = row_ptr;

                // If a single row has the same structure in both source
                // and destination, use faster memcpy

                if (same_row_struct)
                  {
                  memcpy(col_ptr, slice_ptr, info->Columns * sizeof(S));
                  slice_ptr += info->Columns;
                  }
                else
                  {
                  for (i = 0; i < info->Columns; i++)
                    {
                    *col_ptr = (T)(*slice_ptr);
                    col_ptr += incs[0];
                    slice_ptr += info->SamplesPerPixel;
                    }
                  }
                row_ptr += incs[1];
                }
              }
            }

          // The most general (slowest) case

          else
            {
            for (j = 0; j < info->Rows; j++)
              {
              T *col_ptr = row_ptr;
              for (i = 0; i < info->Columns; i++)
                {
                *col_ptr = 
                  (T)((double)(*slice_ptr) * (double)slice_info->RescaleSlope 
                      + (double)slice_info->RescaleIntercept);
                col_ptr += incs[0];
                slice_ptr += info->SamplesPerPixel;
                }
              row_ptr += incs[1];
              }
            }
          plane_ptr += incs[2];
          }
        }
      }
    vol_ptr += incs[2];
    self->UpdateProgress(0.2 + (float)(slice_idx) * (0.6 / (float)num_slices));
    }
}

//----------------------------------------------------------------------------
template <class T>
void vtkDICOMReaderExecuteDataTemplate(vtkDICOMReader *self, 
                                       T *vtkNotUsed(dummy),
                                       T *vtkNotUsed(dummy2),
                                       vtkImageData *output)
{
  // Get info about slices

  vtkDICOMCollector::ImageInfo *info = 
    self->GetDICOMCollector()->GetSelectedImageInfo();
    //self->GetDICOMCollector()->GetCurrentImageInfo();
  if (!info)
    {
    return;
    }

  // Allocate one slice that will be read from disk

  vtkDataArray *slice_scalars = NULL;

  if (info->BitsAllocated <= 8)
    {
    if (info->PixelRepresentation == 
        vtkDICOMCollector::ImageInfo::PixelRepresentationUnsigned)
      {
      slice_scalars = vtkUnsignedCharArray::New();
      }
    else
      {
      slice_scalars = vtkCharArray::New();
      }
    }
  else if (info->BitsAllocated <= 16)
    {
    if (info->PixelRepresentation ==
        vtkDICOMCollector::ImageInfo::PixelRepresentationUnsigned)
      {
      slice_scalars = vtkUnsignedShortArray::New();
      }
    else
      {
      slice_scalars = vtkShortArray::New();
      }
    }
  else
    {
    // Should not happen. ACR NEMA for a while had a notion of 24 bits for RGB.
    return;
    }

  slice_scalars->SetNumberOfComponents(info->SamplesPerPixel);
  slice_scalars->SetNumberOfTuples(info->Rows * info->Columns * info->Planes);

  // Process each slice and put it in the volume at the right place
  // 4 cases here (8 or 16 bits, signed/unsigned)

  if (info->BitsAllocated <= 8) 
    {
    if (info->PixelRepresentation == 
        vtkDICOMCollector::ImageInfo::PixelRepresentationUnsigned)
      {
      vtkDICOMReaderExecuteDataTemplate2(
        self, static_cast<T*>(0), 
        static_cast<unsigned char*>(slice_scalars->GetVoidPointer(0)),
        output);
      }
    else
      {
      vtkDICOMReaderExecuteDataTemplate2(
        self, static_cast<T*>(0), 
        static_cast<char*>(slice_scalars->GetVoidPointer(0)),
        output);
      }
    }
  else if (info->BitsAllocated <= 16) 
    {
    if (info->PixelRepresentation == 
        vtkDICOMCollector::ImageInfo::PixelRepresentationUnsigned)
      {
      vtkDICOMReaderExecuteDataTemplate2(
        self, static_cast<T*>(0), 
        static_cast<unsigned short*>(slice_scalars->GetVoidPointer(0)),
        output);
      }
    else
      {
      vtkDICOMReaderExecuteDataTemplate2(
        self, static_cast<T*>(0), 
        static_cast<short*>(slice_scalars->GetVoidPointer(0)),
        output);
      }
    }

  slice_scalars->Delete();
}

//----------------------------------------------------------------------------
int vtkDICOMReader::RequestData(vtkInformation *vtkNotUsed(request),
                                vtkInformationVector **vtkNotUsed(inputVector),
                                vtkInformationVector *outputVector)
{
  // Collect all slices

  if (!this->DICOMCollector->CollectAllSlices())
    {
    return 0;
    }

  this->UpdateProgress(0.2);

  // Make sure the output dimension is OK, and allocate its scalars

//  vtkInformation *outInfo = outputVector->GetInformationObject(0);
//  vtkImageData *output = vtkImageData::SafeDownCast(
//    outInfo->Get(vtkDataObject::DATA_OBJECT()));
//  int *dext = this->GetDataExtent();
//  output->SetDimensions(
//    dext[1] - dext[0] + 1, dext[3] - dext[2] + 1, dext[5] - dext[4] + 1);
//  output->AllocateScalars();
  for(int i = 0; i < this->GetNumberOfOutputPorts(); ++i)
  {
  // Copy/paste from vtkImageAlgorithm::AllocateScalars. Cf. "this needs to be fixed -Ken"
    vtkStreamingDemandDrivenPipeline *sddp = 
      vtkStreamingDemandDrivenPipeline::SafeDownCast(this->GetExecutive());
    if (sddp)
      {
      int extent[6];
      sddp->GetOutputInformation(i)->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(),extent);
      this->GetOutput(i)->SetExtent(extent);
      }
    this->GetOutput(i)->AllocateScalars();
  }

  //clock_t start_clock = clock();

  // Execute templates according to the output type

  for(int volume = 0; volume < this->DICOMCollector->GetNumberOfVolumes(); ++volume)
    {
    this->DICOMCollector->SelectVolume( volume );

    switch (this->GetOutput(volume)->GetScalarType())
      {
      vtkTemplateMacro(
        vtkDICOMReaderExecuteDataTemplate(this, (VTK_TT *)0, (VTK_TT *)0,
          this->GetOutput(volume)));
      }
    }

  /* 1) Pixel Stored Values vs. Measurements Values

     Rescale is an operation that is performed as part of DICOM's pixel
     processing for some types of images.  Rescale operates on stored pixel
     values, which are the pixel numbers that you get directly from an
     uncompressed DICOM image, or that you get out of the decompressor for
     a compressed DICOM image (when decompressing the image).

     Rescale is designed for modalities where the pixel values are
     meaningful measurements, not just arbitrary numbers, e.g.:
        Hounsfield units for CT, 
        Optical Density of CR, 
        Becquerels/mm, counts, or one of many other types of values for PET
        (the list could go on)

     These measurement units are not necessarily the most convenient or
     efficient for a modality to work with or store in the DICOM image. 
     Instead, the modality uses the Rescale values to show how to convert
     from stored pixel values to meaningful units.  Rescale is a linear
     transformation:

     If:
       x     is the stored pixel value
       m     is the value of Rescale Slope (0028,1053)
       b     is the value of Rescale Intercept (0028,1052)
       y     is the rescaled, meaningful value
     then:
       y = (m * x) + b
       x = (y - b) / m

     Note that if there is a rescale, then Window Width and Window Level are
     specified in rescaled units.  This means that you first apply rescale
     to the pixel value, then window/level.
     UPDATE: this is true most of the time, except, unfortunately, for PET 
     where the scale is so small that some vendors ignore it and specificy 
     window width and center in stored values :( 
     http://groups.google.com/group/comp.protocols.dicom/msg/2b05e7eed494d2a7
  */

  // clock_t end_clock = clock();
  //cout << "vtkDICOMReader - Done reading in " << ((double)(end_clock - start_clock) / (double)CLOCKS_PER_SEC) << " s." << endl;
 

  this->UpdateProgress(1.0);

  return 1;
}

//----------------------------------------------------------------------------
int vtkDICOMReader::CanReadFile(const char* fname)
{ 
  // Check if file can be read (opened as DICOM and basic image info can
  // be retrieved)

  if (!this->DICOMCollector->CanReadFile(fname))
    {
    int ft = this->DICOMCollector->GetFailureStatus();
    if (ft != vtkDICOMCollector::FailureNone && 
        !(ft & vtkDICOMCollector::FailureNotReadable ||
          ft & vtkDICOMCollector::FailureMissingFile))
      {
      return 1;
      }
    return 0;
    }

  return 3;
}

//----------------------------------------------------------------------------
void vtkDICOMReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  
  if (this->DICOMCollector)
    {
    this->DICOMCollector->PrintSelf(os, indent.GetNextIndent());
    }
}


