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

  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 "vtkKWImageMapToWindowLevelColors.h"

#include "vtkColorTransferFunction.h"
#include "vtkImageData.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkInformationVector.h"
#include "vtkInformation.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkDataArray.h"

vtkCxxRevisionMacro(vtkKWImageMapToWindowLevelColors, "$Revision: 1.43 $");
vtkStandardNewMacro(vtkKWImageMapToWindowLevelColors);

//----------------------------------------------------------------------------
vtkKWImageMapToWindowLevelColors::vtkKWImageMapToWindowLevelColors()
{
  int i;
  for (i = 0; i < 4; i++)
    {
    this->LookupTables[i] = NULL;
    this->Weights[i] = 0;
    }
  this->Weights[0] = 1;
  
  this->IndependentComponents = 1;
  this->UseOpacityModulation  = 0;
  this->DisplayChannels = 
    vtkKWImageMapToWindowLevelColors::DISPLAY_INTENSITIES;

  this->MinimumUpdateExtent[0] = 0;
  this->MinimumUpdateExtent[1] = -1;
  this->MinimumUpdateExtent[2] = 0;
  this->MinimumUpdateExtent[3] = -1;
  this->MinimumUpdateExtent[4] = 0;
  this->MinimumUpdateExtent[5] = -1;
}

//----------------------------------------------------------------------------
vtkKWImageMapToWindowLevelColors::~vtkKWImageMapToWindowLevelColors()
{
  int i;
  for (i = 0; i < 4; i++)
    {
    this->SetLookupTable(i, NULL);
    }
}

//----------------------------------------------------------------------------
int vtkKWImageMapToWindowLevelColors::RequestData(
  vtkInformation *request,
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{
  vtkInformation *outInfo = outputVector->GetInformationObject(0);
  vtkImageData *outData = vtkImageData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkImageData *inData = vtkImageData::SafeDownCast(
    inInfo->Get(vtkDataObject::DATA_OBJECT()));

  // Let's try to preallocate the CLUT entries.
  // This filter is multithreaded, thus no modifications can be made to our
  // own Ivars in any threaded part. Now the problem is that the threaded
  // code uses vtkColorTransferFunction::MapScalarsThroughTable2 to map its
  // input through CLUTs. At this point, a CLUT might allocate or extend
  // a RGB(A) table with a given number of entries depending on the input
  // data type (unsigned char/short only). This seems to cause a problem
  // because the allocation/deallocation occurs while being in multithread
  // context. Let's try to solve that by forcing the CLUT to preallocate
  // its entries right now, before going into threaded code.

  if (inData->GetScalarType() == VTK_UNSIGNED_CHAR ||
      inData->GetScalarType() == VTK_UNSIGNED_SHORT)
    {
    for (int i = 0; i < inData->GetNumberOfScalarComponents(); i++)
      {
      vtkColorTransferFunction *ctf =
        vtkColorTransferFunction::SafeDownCast(this->GetLookupTable(i));
      if (ctf)
        {
        int min = (int)inData->GetScalarTypeMin();
        int max = (int)inData->GetScalarTypeMax();
        ctf->GetTable(min, max, max - min + 1);
        }
      }
    }

  // Ken - what the heck does this do?
  // always grow output's update extent to match input's
  //outInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT,
  //             UpdateExtent(inData->GetUpdateExtent());
  
  // skip up to TIA for threaded execute
  return this->vtkThreadedImageAlgorithm::RequestData(request, inputVector,
                                                      outputVector);
}

//----------------------------------------------------------------------------
int vtkKWImageMapToWindowLevelColors::RequestInformation(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **vtkNotUsed(inputVector),
  vtkInformationVector *outputVector)
{
  vtkInformation *outInfo = outputVector->GetInformationObject(0);
  vtkDataObject::SetPointDataActiveScalarInfo(outInfo, VTK_UNSIGNED_CHAR, 3);

  return 1;
}

/* 
 * This templated routine calculates effective lower and upper limits 
 * for a window of values of type T, lower and upper. 
 */
template <class T>
void vtkKWImageMapToWindowLevelClamps ( double range[2], float w, 
                                        float l, T& lower, T& upper, 
                                        unsigned char &lower_val, 
                                        unsigned char &upper_val)
{
  double f_lower, f_upper, f_lower_val, f_upper_val;
  double adjustedLower, adjustedUpper;

  f_lower = l - fabs(w) / 2.0;
  f_upper = f_lower + fabs(w);

  // Set the correct lower value
  if ( f_lower <= range[1])
    {
    if (f_lower >= range[0])
      {
      lower = (T) f_lower;
      adjustedLower = f_lower;
      }
    else
      {
      lower = (T) range[0];
      adjustedLower = range[0];
      }
    }
  else
    {
    lower = (T) range[1];
    adjustedLower = range[1];
    }
  
  
  // Set the correct upper value
  if ( f_upper >= range[0])
    {
    if (f_upper <= range[1])
      {
      upper = (T) f_upper;
      adjustedUpper = f_upper;
      }
    else
      {
      upper = (T) range[1];
      adjustedUpper = range[1];
      }
    }
  else
    {
    upper = (T) range [0];
    adjustedUpper = range [0];
    }
  
  // now compute the lower and upper values
  if (w > 0) // not possible for window to be 0.
    {
    f_lower_val = 255.0*(adjustedLower - f_lower)/w;
    f_upper_val = 255.0*(adjustedUpper - f_lower)/w;
    }
  else
    {
    f_lower_val = 255.0 + 255.0*(adjustedLower - f_lower)/w;
    f_upper_val = 255.0 + 255.0*(adjustedUpper - f_lower)/w;
    }
  
  if (f_upper_val > 255) 
    {
    upper_val = 255;
    }
  else if (f_upper_val < 0)
    {
    upper_val = 0;
    }
  else
    {
    upper_val = (unsigned char)(f_upper_val);
    }
  
  if (f_lower_val > 255) 
    {
    lower_val = 255;
    }
  else if (f_lower_val < 0)
    {
    lower_val = 0;
    }
  else
    {
    lower_val = (unsigned char)(f_lower_val);
    }  
}

/*=======================================================================
  
OK There are a number of different cases here so it warrents some
discussion. There are from one to four components of input data and the
mapping can be done on intesity, opacity, post lut intensities. Opacity
modulation can be on or off.  This gives us a bunch of combinations.

Here are the mapping type combinations:

No Opacity Modulation
1 - display and W/L intensities 
2 - display and W/L opacity channel
3 - map intensities through the LUT then W/L them
7 - display and W/L grayscale intensities

With Opacity Modulation
4 - display intensities modulated by W/L opacity
5 - display opacity modulated by W/L opacity
6 - map intensities through the LUT then modulate by W/L opacity

Mode 5 doesn't really make sense so we will not support it


Input 
Type                        
----------------------------------------------------------------
1
  1 ch 0 -> W/L
  2 ch 0 -> W/L
  3 ch 0 -> map -> W/L
  4 ch 0 * (ch 0 -> W/L)
  6 ch 0 -> map * ch 0 -> W/L

2
  1 ch 0 -> W/L
  2 ch 1 -> W/L
  3 ch 0 -> map -> W/L
  4 ch 0 * (ch 1 -> W/L)
  6 ch 0 -> map * (ch 1 -> W/L)

3
  1 ch 0,1 -> W/L
  2 ch 2 -> W/L
  3 ch 0,1 -> map -> W/L
  4 ch 0,1 * (ch 2 -> W/L)
  6 ch 0,1 -> map * (ch 2 -> W/L)

4
  1 ch 0,1,2 -> W/L
  2 ch 3 -> W/L
  3 ch 0,1,2 -> map -> W/L
  4 ch 0,1,2 * (ch 3 -> W/L)
  6 ch 0,1,2 -> map * (ch 3 -> W/L)

2I
  1 ch 0,1 -> W/L -> weight
  2 ch 0,1 -> W/L -> weight
  3 ch 0,1 -> map -> W/L -> weight
  4 ch 0,1 * (ch 0,1 -> W/L) -> weight
  6 ch 0,1 -> map * (ch 0,1 -> W/L) -> weight

3I
  1 ch 0,1,2 -> W/L -> weight
  2 ch 0,1,2 -> W/L -> weight
  3 ch 0,1,2 -> map -> W/L -> weight
  4 ch 0,1,2 * (ch 0,1,2 -> W/L) -> weight
  6 ch 0,1,2 -> map * (ch 0,1,2 -> W/L) -> weight

4I
  1 ch 0,1,2,3 -> W/L -> weight
  2 ch 0,1,2,3 -> W/L -> weight
  3 ch 0,1,2,3 -> map -> W/L -> weight
  4 ch 0,1,2,3 * (ch 0,1,2,3 -> W/L) -> weight
  6 ch 0,1,2,3 -> map * (ch 0,1,2,3 -> W/L) -> weight

  
The basic process is the following:

- identify (or compute) the display channels 
  (intensity or opacity or post LUT intensity)
- identify the channels to W/L
  case 1,2,3 this is the display channels
  case 4,6 it is the opacity channels
- perform W/L
- multiply display channels by W/L channels if required
- apply weights

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

struct vtkRowInfo
{
  int extX;
  unsigned char lower_val;
  unsigned char upper_val;
  unsigned char uc_lower_val;
  unsigned char uc_upper_val;
  unsigned char uc_lower;
  unsigned char uc_upper;
  int dataType;
  int dataTypeNum;
  int opTypeNum;
  float weights[4];
  float shift;
  float scale;
  unsigned char *tmpData;
  unsigned char *tmpData2;
  unsigned char *tmpData3;
  unsigned char *tmpData4;
  vtkScalarsToColors *lookupTable[4];
};


template <class T>
void vtkKW_WLOneChannel(int extX,
                        T *iptr,
                        int inComp,
                        unsigned char *optr,
                        T lower, T upper,
                        unsigned char lower_val, 
                        unsigned char upper_val,
                        float shift, float scale)
{
  int idxX;
  unsigned short ushort_val;
  
  for (idxX = 0; idxX < extX; idxX++)
    {
    if (*iptr <= lower)
      {
      ushort_val = lower_val;
      }
    else if (*iptr >= upper)
      {
      ushort_val = upper_val;
      }
    else
      {
      ushort_val = (unsigned char)((*iptr + shift) * scale);
      }
    *optr = (unsigned char)ushort_val;
    optr[1] = *optr;
    optr[2] = *optr;
    iptr += inComp;
    optr += 3; 
    }
}

template <class T>
void vtkKW_ModulateColor(int extX,
                         T *opacityPtr, int inComp,
                         unsigned char *optr,
                         T lower, T upper,
                         unsigned char lower_val, 
                         unsigned char upper_val, 
                         unsigned char *colors,
                         int colorComp,
                         float shift, float scale)
{
  int idxX;
  unsigned short ushort_val;
  
  for (idxX = 0; idxX < extX; idxX++)
    {
    if (*opacityPtr <= lower)
      {
      ushort_val = lower_val;
      }
    else if (*opacityPtr >= upper)
      {
      ushort_val = upper_val;
      }
    else
      {
      ushort_val = (unsigned char)((*opacityPtr + shift) * scale);
      }
    *optr = (unsigned char)((*colors  * ushort_val) >> 8);
    colors++;
    *(optr+1) =
      (unsigned char)((*colors * ushort_val) >> 8);
    colors++;
    *(optr+2) =
      (unsigned char)((*colors * ushort_val) >> 8);
    colors += colorComp - 2;
    opacityPtr += inComp;
    optr += 3; 
    }
}

template <class T>
void vtkKW_WLMultipleChannels(int extX,
                              T *iptr, int inComp,
                              unsigned char *optr,
                              T lower, T upper,
                              unsigned char lower_val, 
                              unsigned char upper_val,
                              float shift, float scale)
{
  int idxC, idxX;
  unsigned short ushort_val;
  int inWLComp = inComp > 3 ? 3 : inComp;
  for (idxX = 0; idxX < extX; idxX++)
    {
    for (idxC = 0; idxC < inWLComp; idxC++)
      {
      if (*iptr <= lower)
        {
        ushort_val = lower_val;
        }
      else if (*iptr >= upper)
        {
        ushort_val = upper_val;
        }
      else
        {
        ushort_val = (unsigned char)((*iptr + shift) * scale);
        }
      *optr = (unsigned char)ushort_val;
      optr++;
      iptr++;
      }
    for (;idxC < 3; idxC++)
      {
      *optr = 0;
      optr++;
      }
    iptr += inComp - inWLComp;
    }
}

template <class T>
void vtkKW_WLMergeMultipleChannels(vtkRowInfo *ri,
                                   T *iptr, int inComp,
                                   unsigned char *optr,
                                   T lower, T upper,
                                   unsigned char lower_val, 
                                   unsigned char upper_val,
                                   float shift, float scale)
{
  int idxC, idxX;
  unsigned short ushort_val;
  int inWLComp = inComp > 3 ? 3 : inComp;
  for (idxX = 0; idxX < ri->extX; idxX++)
    {
    for (idxC = 0; idxC < inWLComp; idxC++)
      {
      if (*iptr <= lower)
        {
        ushort_val = lower_val;
        }
      else if (*iptr >= upper)
        {
        ushort_val = upper_val;
        }
      else
        {
        ushort_val = (unsigned char)((*iptr + shift) * scale);
        }
      *optr = (unsigned char)(ushort_val*ri->weights[idxC]);
      optr++;
      iptr++;
      }
    for (;idxC < 3; idxC++)
      {
      *optr = 0;
      optr++;
      }
    iptr += inComp - inWLComp;
    }
}

template <class T>
void vtkKW_WLMergeGrayMultipleChannels(vtkRowInfo *ri,
                                       T *iptr, int inComp,
                                       unsigned char *optr,
                                       T lower, T upper,
                                       unsigned char lower_val, 
                                       unsigned char upper_val,
                                       float shift, float scale)
{
  int idxC, idxX;
  unsigned short ushort_val;
  T result;
  float factor = 1.0/inComp;
  for (idxX = 0; idxX < ri->extX; idxX++)
    {
    result = 0;
    for (idxC = 0; idxC < inComp; idxC++)
      {
      result += static_cast<T>(*iptr*ri->weights[idxC]*factor);
      iptr++;
      }
    if (result <= lower)
      {
      ushort_val = lower_val;
      }
    else if (result >= upper)
      {
      ushort_val = upper_val;
      }
    else
      {
      ushort_val = (unsigned char)((result + shift) * scale);
      }
    *optr = (unsigned char)ushort_val;
    optr[1] = *optr;
    optr[2] = *optr;
    optr += 3; 
    }
}

template <class T>
void vtkKW_WLGrayMultipleChannels(vtkRowInfo *ri,
                                  T *iptr, int inComp,
                                  unsigned char *optr,
                                  T lower, T upper,
                                  unsigned char lower_val, 
                                  unsigned char upper_val,
                                  float shift, float scale)
{
  int idxC, idxX;
  unsigned short ushort_val;
  T result;
  float factor = 1.0/inComp;
  for (idxX = 0; idxX < ri->extX; idxX++)
    {
    result = 0;
    for (idxC = 0; idxC < inComp; idxC++)
      {
      result += static_cast<T>(*iptr*factor);
      iptr++;
      }
    if (result <= lower)
      {
      ushort_val = lower_val;
      }
    else if (result >= upper)
      {
      ushort_val = upper_val;
      }
    else
      {
      ushort_val = (unsigned char)((result + shift) * scale);
      }
    *optr = (unsigned char)ushort_val;
    optr[1] = *optr;
    optr[2] = *optr;
    optr += 3; 
    }
}

void vtkKW_MergeColorsAndWL(vtkRowInfo *ri, int inComp, unsigned char *optr,
                            float shift, float scale)
{
  int idxX, idxC;
  unsigned char *iptrs[4];
  iptrs[0] = ri->tmpData;
  iptrs[1] = ri->tmpData2;
  iptrs[2] = ri->tmpData3;
  iptrs[3] = ri->tmpData4;
  
  unsigned short r,g,b;
  
  unsigned char lower = ri->uc_lower;
  unsigned char upper = ri->uc_upper;
  unsigned char lower_val = ri->uc_lower_val;
  unsigned char upper_val = ri->uc_upper_val;
  
  for (idxX = 0; idxX < ri->extX; idxX++)
    {
    r = 0;
    g = 0;
    b = 0;
    for (idxC = 0; idxC < inComp; idxC++)
      {
      r += static_cast<unsigned short>(ri->weights[idxC] * *iptrs[idxC]);
      (iptrs[idxC])++;
      g += static_cast<unsigned short>(ri->weights[idxC] * *iptrs[idxC]);
      (iptrs[idxC])++;
      b += static_cast<unsigned short>(ri->weights[idxC] * *iptrs[idxC]);
      (iptrs[idxC])++;
      }
    // now W/L
    if (r <= lower)
      {
      *optr = lower_val;
      }
    else if (r >= upper)
      {
      *optr = upper_val;
      }
    else
      {
      *optr = (unsigned char)((r + shift) * scale);
      }
    optr++;
    if (g <= lower)
      {
      *optr = lower_val;
      }
    else if (g >= upper)
      {
      *optr = upper_val;
      }
    else
      {
      *optr = (unsigned char)((g + shift) * scale);
      }
    optr++;
    if (b <= lower)
      {
      *optr = lower_val;
      }
    else if (b >= upper)
      {
      *optr = upper_val;
      }
    else
      {
      *optr = (unsigned char)((b + shift) * scale);
      }
    optr++;
    }
}

template <class T>
void vtkKW_MergeColorsAndWLOpacity(vtkRowInfo *ri, 
                                   T *opptr,
                                   T lower, T upper,
                                   int inComp,
                                   unsigned char *optr,
                                   float shift, float scale)
{
  int idxC, idxX;
  unsigned char *iptrs[4];
  iptrs[0] = ri->tmpData;
  iptrs[1] = ri->tmpData2;
  iptrs[2] = ri->tmpData3;
  iptrs[3] = ri->tmpData4;

  unsigned char lower_val = ri->lower_val;
  unsigned char upper_val = ri->upper_val;

  unsigned int r,g,b;
  unsigned short ushort_val;
  for (idxX = 0; idxX < ri->extX; idxX++)
    {
    r = 0;
    g = 0;
    b = 0;
    for (idxC = 0; idxC < inComp; idxC++)
      {
      if (*opptr <= lower)
        {
        ushort_val = lower_val;
        }
      else if (*opptr >= upper)
        {
        ushort_val = upper_val;
        }
      else
        {
        ushort_val = (unsigned char)((*opptr + shift) * scale);
        }
      opptr++;
      r += static_cast<unsigned int>(
        ushort_val*(ri->weights[idxC] * *(iptrs[idxC])));
      (iptrs[idxC])++;
      g += static_cast<unsigned int>(
        ushort_val*(ri->weights[idxC] * *(iptrs[idxC])));
      (iptrs[idxC])++;
      b += static_cast<unsigned int>(
        ushort_val*(ri->weights[idxC] * *(iptrs[idxC])));
      (iptrs[idxC])++;
      }
    // now W/L
    r = r >> 8;
    g = g >> 8;
    b = b >> 8;
    if (r > 255)
      {
      r = 255;
      }
    if (g > 255)
      {
      g = 255;
      }
    if (b > 255)
      {
      b = 255;
      }
    *optr = (unsigned char)r;
    optr++;
    *optr = (unsigned char)g;
    optr++;
    *optr = (unsigned char)b;
    optr++;
    }
}

// process one row of input data to a row of output data
template <class T>
void vtkDoARow(int operationType, T *iptr, unsigned char *outp, 
               vtkRowInfo *ri, T lower, T upper)
{
  int dispComp, inStep;
  switch (operationType)
    {
    case 11:
    case 12:
    case 21:
    case 22:
    case 32:
    case 42:
      // simple mapping of disp channel or single opacity through W/L
      dispComp = operationType & 0x2 ? ri->dataTypeNum - 1 : 0;
      vtkKW_WLOneChannel(ri->extX, iptr+dispComp,
                         ri->dataTypeNum, outp,
                         lower, upper,
                         ri->lower_val, ri->upper_val,
                         ri->shift, ri->scale);
      break;
    case 31:
    case 41:
      // color through W/L
      vtkKW_WLMultipleChannels(ri->extX, iptr,
                               ri->dataTypeNum, outp,
                               lower, upper,
                               ri->lower_val, ri->upper_val,
                               ri->shift, ri->scale);
      break;
    case 13:
    case 23:
      // map single channel through lut then W/L
      inStep = operationType/10;
      ri->lookupTable[0]->MapScalarsThroughTable2(iptr, ri->tmpData, 
                                                  ri->dataType,
                                                  ri->extX, inStep,
                                                  VTK_RGB);
      vtkKW_WLMultipleChannels(ri->extX, ri->tmpData,
                               3, outp,
                               ri->uc_lower, ri->uc_upper,
                               ri->uc_lower_val, ri->uc_upper_val,
                               ri->shift, ri->scale);      
      break;
    case 16:
    case 26:
      // map single channel through lut then * (opacity -> W/L)
      ri->lookupTable[0]->MapScalarsThroughTable2(iptr, ri->tmpData, 
                                                 ri->dataType,
                                                 ri->extX, ri->dataTypeNum,
                                                 VTK_RGB);
      vtkKW_ModulateColor(ri->extX, iptr+ri->dataTypeNum - 1,
                          ri->dataTypeNum, outp,
                          lower, upper,
                          ri->lower_val, ri->upper_val,
                          ri->tmpData, 3, ri->shift, ri->scale);
      break;
    case 51:
    case 61:
    case 71:
    case 52:
    case 62:
    case 72:
      // handle independent channles with W/L
      vtkKW_WLMergeMultipleChannels(ri, 
                                    iptr, ri->dataTypeNum-3, 
                                    outp,
                                    lower, upper,
                                    ri->lower_val, ri->upper_val,
                                    ri->shift, ri->scale);
      break;

    case 73:
      // map independent comp through LUT, then W/L then merge
      ri->lookupTable[3]->MapScalarsThroughTable2(iptr+3, ri->tmpData4, 
                                                  ri->dataType,
                                                  ri->extX, 
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
    case 63:
      ri->lookupTable[2]->MapScalarsThroughTable2(iptr+2, ri->tmpData3, 
                                                  ri->dataType,
                                                  ri->extX,
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
    case 53:
      ri->lookupTable[1]->MapScalarsThroughTable2(iptr+1, ri->tmpData2, 
                                                  ri->dataType,
                                                  ri->extX,
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
      ri->lookupTable[0]->MapScalarsThroughTable2(iptr, ri->tmpData, 
                                                  ri->dataType,
                                                  ri->extX,
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
      // now we have the data, we must merge
      vtkKW_MergeColorsAndWL(ri, ri->dataTypeNum - 3, outp, ri->shift, ri->scale);
      break;

    case 76:
      // map independent comp through LUT, then W/L then merge
      ri->lookupTable[3]->MapScalarsThroughTable2(iptr+3, ri->tmpData4, 
                                                  ri->dataType,
                                                  ri->extX,
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
    case 66:
      ri->lookupTable[2]->MapScalarsThroughTable2(iptr+2, ri->tmpData3, 
                                                  ri->dataType,
                                                  ri->extX,
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
    case 56:
      ri->lookupTable[1]->MapScalarsThroughTable2(iptr+1, ri->tmpData2, 
                                                  ri->dataType,
                                                  ri->extX,
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
      ri->lookupTable[0]->MapScalarsThroughTable2(iptr, ri->tmpData, 
                                                  ri->dataType,
                                                  ri->extX,
                                                  ri->dataTypeNum - 3, 
                                                  VTK_RGB);
      // now we have the data, we must merge
      vtkKW_MergeColorsAndWLOpacity(ri,iptr,lower, upper, ri->dataTypeNum -3, outp, 
                                    ri->shift, ri->scale);
      break;
      
    case 34:
    case 44:
      vtkKW_ModulateColor(ri->extX, 
                          iptr + ri->dataTypeNum -1, ri->dataTypeNum, 
                          outp,
                          lower, upper,
                          ri->lower_val, ri->upper_val,
                          (unsigned char *)iptr, ri->dataTypeNum, ri->shift, ri->scale);      
      break;
      
    case 57:
    case 67:
    case 77:
      vtkKW_WLMergeGrayMultipleChannels(ri, iptr, 
                                        ri->dataTypeNum-3, 
                                        outp, lower, upper,
                                        ri->lower_val, ri->upper_val,
                                        ri->shift, ri->scale);
      break;
    case 37:
    case 47:
      vtkKW_WLGrayMultipleChannels(ri, iptr, 
                                   ri->dataTypeNum, 
                                   outp, lower, upper,
                                   ri->lower_val, ri->upper_val,
                                   ri->shift, ri->scale);
      break;
    }
  
}

template <class T>
void vtkKWImageMapToWindowLevelColorsExecute(
  vtkKWImageMapToWindowLevelColors *self, 
  vtkImageData *inData, T *inPtr,
  vtkImageData *outData, 
  unsigned char *outPtr,
  int outExt[6], int id)
{
  int independent = self->GetIndependentComponents();
  int opMod = self->GetUseOpacityModulation();
  int dispCh = self->GetDisplayChannels();

  vtkRowInfo ri;
  ri.dataType = inData->GetScalarType();
  
  ri.shift =  self->GetWindow() / 2.0 - self->GetLevel();
  ri.scale = 255.0 / self->GetWindow();
  
  // set the weights
  ri.weights[0] = self->GetWeight(0);
  ri.weights[1] = self->GetWeight(1);
  ri.weights[2] = self->GetWeight(2);
  ri.weights[3] = self->GetWeight(3);
  ri.lookupTable[0] = self->GetLookupTable(0);
  ri.lookupTable[1] = self->GetLookupTable(1);
  ri.lookupTable[2] = self->GetLookupTable(2);
  ri.lookupTable[3] = self->GetLookupTable(3);
  
  // compute an index for what type of operation this will be
  // there are 7 types of data and 5 operations
  ri.dataTypeNum = inData->GetNumberOfScalarComponents();
  ri.dataTypeNum += (ri.dataTypeNum > 1 && independent) ? 3 : 0;
  // now dataTypeNum = 1 to 7 as 1 2 3 4 2I 3I 4I
  
  // compute the optype 1,2,3,4,6,7
  ri.opTypeNum = dispCh + 1;
  if (ri.opTypeNum == 4)
    {
    ri.opTypeNum = 7;
    }
  if (opMod)
    {
    ri.opTypeNum += 3;
    }
  
  // now construct a unique number
  int operationType = ri.dataTypeNum*10 + ri.opTypeNum;
 
  // find the region to loop over
  ri.extX = outExt[1] - outExt[0] + 1;
  int extY = outExt[3] - outExt[2] + 1; 
  int extZ = outExt[5] - outExt[4] + 1;

  // some operation types require temp storage
  ri.tmpData = 0;
  ri.tmpData2 = 0;
  ri.tmpData3 = 0;
  ri.tmpData4 = 0;  
  switch (operationType)
    {
    case 73: case 76:
      ri.tmpData4 = new unsigned char [ri.extX*3];
    case 63: case 66:
      ri.tmpData3 = new unsigned char [ri.extX*3];
    case 53: case 56:
      ri.tmpData2 = new unsigned char [ri.extX*3];
    case 13: case 23: case 16: case 26:
      ri.tmpData = new unsigned char [ri.extX*3];
    }
  
  unsigned long target = (unsigned long)(extZ*extY/50.0);
  target++;
  unsigned long count = 0;

  // find key values for W/L or type T
  T   lower, upper;
  double range[2];

  inData->GetPointData()->GetScalars()->GetDataTypeRange( range );

  vtkKWImageMapToWindowLevelClamps( range, self->GetWindow(), 
                                    self->GetLevel(), 
                                    lower, upper, ri.lower_val, ri.upper_val );
  range[0] = 0;
  range[1] = 255;
  vtkKWImageMapToWindowLevelClamps( range, self->GetWindow(), 
                                    self->GetLevel(), 
                                    ri.uc_lower, ri.uc_upper, 
                                    ri.uc_lower_val, ri.uc_upper_val );

  // Get increments to march through data 
  vtkIdType inIncX, inIncY, inIncZ, outIncX, outIncY, outIncZ;
  inData->GetContinuousIncrements(outExt, inIncX, inIncY, inIncZ);
  outData->GetContinuousIncrements(outExt, outIncX, outIncY, outIncZ);
  int rowLength = ri.extX*inData->GetNumberOfScalarComponents();

  // Loop through output pixels
  unsigned char *outPtr1 = outPtr;
  T *inPtr1 = inPtr;
  int idxZ, idxY;
  for (idxZ = 0; idxZ < extZ; idxZ++)
    {
    for (idxY = 0; !self->AbortExecute && idxY < extY; idxY++)
      {
      if (!id) 
        {
        if (!(count%target))
          {
          self->UpdateProgress(count/(50.0*target));
          }
        count++;
        }
      
      // a big switch statement to do the right thing for the right cases
      vtkDoARow(operationType,inPtr1,outPtr1,&ri, lower, upper);

      outPtr1 += outIncY + ri.extX*3;
      inPtr1 += inIncY + rowLength;
      }
    outPtr1 += outIncZ;
    inPtr1 += inIncZ;
    }

  // free memory
  if (ri.tmpData)
    {
    delete [] ri.tmpData;
    }
  if (ri.tmpData2)
    {
    delete [] ri.tmpData2;
    }
  if (ri.tmpData3)
    {
    delete [] ri.tmpData3;
    }
  if (ri.tmpData4)
    {
    delete [] ri.tmpData4;
    }  
}

//----------------------------------------------------------------------------
void vtkKWImageMapToWindowLevelColors::ThreadedRequestData(
  vtkInformation *vtkNotUsed(request),
  vtkInformationVector **vtkNotUsed(inputVector),
  vtkInformationVector *vtkNotUsed(outputVector),
  vtkImageData ***inData,
  vtkImageData **outData,
  int outExt[6], int id)
{
  void *inPtr = inData[0][0]->GetScalarPointerForExtent(outExt);
  void *outPtr = outData[0]->GetScalarPointerForExtent(outExt);
  
  switch (inData[0][0]->GetScalarType())
    {
    vtkTemplateMacro(vtkKWImageMapToWindowLevelColorsExecute(this, 
                      inData[0][0], (VTK_TT *)(inPtr), 
                      outData[0], (unsigned char *)(outPtr), outExt, id));
    default:
      vtkErrorMacro(<< "Execute: Unknown ScalarType");
      return;
    }
}

//----------------------------------------------------------------------------
void vtkKWImageMapToWindowLevelColors::SetLookupTable(int comp,
                                                      vtkScalarsToColors *lut)
{
  if (comp < 0 || comp >= VTK_MAX_VRCOMP)
    {
    vtkErrorMacro("Component must be in range [0, " 
                  << VTK_MAX_VRCOMP - 1 << "].");
    return;
    }
  
  vtkColorTransferFunction *func =
    vtkColorTransferFunction::SafeDownCast(lut);
  if (lut && !func)
    {
    vtkErrorMacro("Lookup table must be a vtkColorTransferFunction");
    return;
    }
  
  if (this->LookupTables[comp] == func)
    {
    return;
    }
  
  if (this->LookupTables[comp])
    {
    this->LookupTables[comp]->UnRegister(this);
    }
  
  this->LookupTables[comp] = func;
  
  if (this->LookupTables[comp])
    {
    this->LookupTables[comp]->Register(this);
    }
  
  this->Modified();
}

//----------------------------------------------------------------------------
vtkScalarsToColors* vtkKWImageMapToWindowLevelColors::GetLookupTable(int comp)
{
  if (comp < 0 || comp >= VTK_MAX_VRCOMP)
    {
    return NULL;
    }
  
  return this->LookupTables[comp];
}

//----------------------------------------------------------------------------
void vtkKWImageMapToWindowLevelColors::SetWeight(int comp, float weight)
{
  if (comp < 0 || comp >= VTK_MAX_VRCOMP)
    {
    vtkErrorMacro("Component must be in range [0, " 
                  << VTK_MAX_VRCOMP - 1 << "].");
    return;
    }
  
  if (weight < 0)
    {
    vtkErrorMacro("Weights cannot be negative");
    return;
    }
  
  if (this->Weights[comp] == weight)
    {
    return;
    }
  
  this->Weights[comp] = weight;
  this->Modified();
}

//----------------------------------------------------------------------------
float vtkKWImageMapToWindowLevelColors::GetWeight(int comp)
{
  if (comp < 0 || comp >= VTK_MAX_VRCOMP)
    {
    return 0;
    }
  
  return this->Weights[comp];
}

//----------------------------------------------------------------------------
unsigned long vtkKWImageMapToWindowLevelColors::GetMTime()
{
  unsigned long t1, t2;
  int i;
  
  t1 = this->Superclass::GetMTime();
  for (i = 0; i < 4; i++)
    {
    if (this->LookupTables[i])
      {
      t2 = this->LookupTables[i]->GetMTime();
      if (t2 > t1)
        {
        t1 = t2;
        }
      }
    }
  return t1;
}

//----------------------------------------------------------------------------
int vtkKWImageMapToWindowLevelColors::IsValidCombination(int dispCh, 
                                                         int useOpMod)
{
  // If no input, imply that all combinations are possible

  if (!this->GetInput())
    {
    return 1;
    }

  vtkImageData *inData = (vtkImageData *)(this->GetInput());
  int dataTypeNum = inData->GetNumberOfScalarComponents();
  dataTypeNum += (dataTypeNum > 1 && this->IndependentComponents) ? 3 : 0;
  // now dataTypeNum = 1 to 7 as 1 2 3 4 2I 3I 4I

  // compute the optype 1,2,3,4,6,7
   int opTypeNum = dispCh + 1;
  if (opTypeNum == 4)
    {
    opTypeNum = 7;
    }
  if (useOpMod)
    {
    opTypeNum += 3;
    }

  // now construct a unique number
  int operationType = dataTypeNum*10 + opTypeNum;
  
  // is this op valid? This code should mirror the similar switch statement
  // above but we mark some supported ops as invalid because they dont
  // really make sense. No need to confuse the user
  switch (operationType)
    {
    case 11:
    case 21:
    case 22:
    case 32:
    case 42:
    case 31:
    case 41:
    case 13:
    case 23:
    case 16:
    case 26:
    case 51:
    case 61:
    case 71:
    case 73:
    case 63:
    case 53:
    case 76:
    case 66:
    case 56:
    case 34:
    case 44:
    case 37:
    case 47:
    case 57:
    case 67:
    case 77:
      return 1;
    }
  return 0;
}

//----------------------------------------------------------------------------
int vtkKWImageMapToWindowLevelColors::GetValidCombination(int *dispCh, 
                                                          int *useOpMod)
{
  int color_disp_ch_favs[] = { 
    vtkKWImageMapToWindowLevelColors::DISPLAY_POST_LUT_INTENSITIES,
    vtkKWImageMapToWindowLevelColors::DISPLAY_OPACITY,
    vtkKWImageMapToWindowLevelColors::DISPLAY_INTENSITIES,
    vtkKWImageMapToWindowLevelColors::DISPLAY_GRAYSCALE_INTENSITIES
  };

  int grayscale_disp_ch_favs[] = { 
    vtkKWImageMapToWindowLevelColors::DISPLAY_INTENSITIES,
    vtkKWImageMapToWindowLevelColors::DISPLAY_OPACITY,
    vtkKWImageMapToWindowLevelColors::DISPLAY_POST_LUT_INTENSITIES,
    vtkKWImageMapToWindowLevelColors::DISPLAY_GRAYSCALE_INTENSITIES
  };

  vtkImageData *inData = (vtkImageData *)(this->GetInput());
  int is_color = (inData && inData->GetNumberOfScalarComponents() > 2);
  int *disp_ch_favs = is_color ? color_disp_ch_favs : grayscale_disp_ch_favs;

  for (int op_mod = this->GetUseOpacityModulationMaxValue(); 
       op_mod >= this->GetUseOpacityModulationMinValue(); op_mod--)
    {
    for (int i = 0; i <= 4; i++)
      {
      if (this->IsValidCombination(disp_ch_favs[i], op_mod))
        {
        *dispCh = disp_ch_favs[i];
        *useOpMod = op_mod;
        return 1;
        }
      }
    }
  return 0;
}

//----------------------------------------------------------------------------
void vtkKWImageMapToWindowLevelColors::SetMinimumUpdateExtent(int extent[6])
{
  int idx, modified = 0;
  
  for (idx = 0; idx < 6; ++idx)
    {
    if (this->MinimumUpdateExtent[idx] != extent[idx])
      {
      this->MinimumUpdateExtent[idx] = extent[idx];
      modified = 1;
      }
    }

  if (modified)
    {
    this->Modified();
    }
}
//----------------------------------------------------------------------------
void vtkKWImageMapToWindowLevelColors::SetMinimumUpdateExtent(
  int minX, int maxX, int minY, int maxY, int minZ, int maxZ)
{
  int extent[6];
  
  extent[0] = minX;  extent[1] = maxX;
  extent[2] = minY;  extent[3] = maxY;
  extent[4] = minZ;  extent[5] = maxZ;
  this->SetMinimumUpdateExtent(extent);
}

//----------------------------------------------------------------------------
// This method computes the input extent necessary to generate the output.
int vtkKWImageMapToWindowLevelColors::RequestUpdateExtent (
  vtkInformation * vtkNotUsed(request),
  vtkInformationVector **inputVector,
  vtkInformationVector *outputVector)
{
  // get the info objects
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  
  int outExt[6], inExt[6];
  outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), outExt);

  int *wholeExtent;
  int idx;
  
  // perform some simple checks
  wholeExtent = inInfo->Get(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT());
  
  // is the minimum set?
  if (this->MinimumUpdateExtent[1] != -1)
    {
    // enlarge the request to the minimum
    for (idx = 0; idx < 3; ++idx)
      {
      if (outExt[idx*2] < this->MinimumUpdateExtent[idx*2])
        {
        vtkErrorMacro("update extent is outside the MinimumUpdateExtent");
        }
      inExt[idx*2] = this->MinimumUpdateExtent[idx*2];
      if (outExt[idx*2+1] > this->MinimumUpdateExtent[idx*2+1])
        {
        vtkErrorMacro("update extent is outside the MinimumUpdateExtent");
        }
      inExt[idx*2+1] = this->MinimumUpdateExtent[idx*2+1];
      }
    }
  else
    {
    memcpy(inExt,outExt,6*sizeof(int));
    }
  
  // make sure the minimum isn't bigger than the maximum
  for (idx = 0; idx < 3; ++idx)
    {
    if (inExt[idx*2] < wholeExtent[idx*2])
      {
      vtkErrorMacro(
        "MinimumUpdateExtent specified is larger than the whole extent\n");
      }
    if (inExt[idx*2+1] > wholeExtent[idx*2+1])
      {
      vtkErrorMacro(
        "MinimumUpdateExtent specified is larger than the whole extent\n");
      }    
    if (inExt[idx*2] > outExt[idx*2])
      {
      vtkErrorMacro(
        "UpdateExtent requested is not in the MinimumUpdateExtent\n");
      }
    if (inExt[idx*2+1] < outExt[idx*2+1])
      {
      vtkErrorMacro(
        "UpdateExtent requested is not in the MinimumUpdateExtent\n");
      }
    }
  
  inInfo->Set(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), inExt, 6);

  return 1;
}

//----------------------------------------------------------------------------
void vtkKWImageMapToWindowLevelColors::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  
  os << indent << "IndependentComponents: " << this->IndependentComponents
     << endl;
  os << indent << "UseOpacityModulation: " << this->UseOpacityModulation
     << endl;
  os << indent << "DisplayChannels: ";
  switch (this->DisplayChannels)
    {
    case (vtkKWImageMapToWindowLevelColors::DISPLAY_INTENSITIES) :
      os << "Intensities";
      break;
    case (vtkKWImageMapToWindowLevelColors::DISPLAY_GRAYSCALE_INTENSITIES) :
      os << "Gray Scale Intensities";
      break;
    case (vtkKWImageMapToWindowLevelColors::DISPLAY_OPACITY) :
      os << "Opacity";
      break;
    case (vtkKWImageMapToWindowLevelColors::DISPLAY_POST_LUT_INTENSITIES) :
      os << "Post Lookup Table Intensities";
      break;
    }
  os << endl;
}
