Relation between vtkImageData WL and vtkImageActor WL

What is the relation between Window Level from vtkImageData and Window Level from vtkImageActor ?

vtkImageProperty class has 2 members:

  double ColorWindow;
  double ColorLevel;

and they are initialized with 255 and 127.5 in constructor.

double dActorWindow = myImageActor->GetProperty()->GetColorWindow();
double dActorLevel = myImageActor->GetProperty()->GetColorLevel();

Fine.

vtkImageData window level values are taken from DICOM tags.

Let say that I have a DICOM series that has 80 as window and 30 as level. The pipeline put the data from vtkImageData into vtkImageActor in order to display 2d image.

When I display the initial window level value, in order to display the original WL, for vtkImageActor window 255 I should display the value as 80, as vtkImageData said. When user change WL from the mouse, it change it on vtkImageActor. Let say that user increase with 5 window color. Because the vtkImageActor 255 means 80, when user increase vtkImageActor window color by 5, I display 85, right ? I hope you understand it:

vtkImageData window color: 80 -> vtkImageActor window color: 255 -> display: 80;
user increase vtkImageActor with 5.
vtkImageActor window color: 300 -> display: 85;

Well, I guess this approach is not correct, because with this calculation, when I have on my vtkImageActor W/L 400/50, my image looks more diffuse and darker like other 2d dicom viewers at the same WL. And I suspect that this calculation is wrong.

I can get here some image samples if need it.

My question is, how to convert correctly vtkImageActor window level regarding original vtkImageData ?

How you compute/translate your vtkImageActor WL on your display ? Just a little hint/ help mean a lot for me !!! Thank you.

Is there any example of how to setup WL on vtkImageActor ? I’ve found nothing on VTK examples …

I think I have to include in my pipeline vtkImageMapToWindowLevelColors object. But I don’t know how. And I have seeked on internet, but I’ve found nothing …

can you tell me how to include this object in my pipeline ?

Thank you.

I’ve found an example here: https://lorensen.github.io/VTKExamples/site/Cxx/ImageProcessing/IdealHighPass/

But after I put my vtkImageMapToWindowLevelColors into my pipeline, the WL values are still like before install this object in my pipeline.

@flaviu2 All the window width/level values (for vtkImageActor and vtkImageMapToWindowLevelColors) should be in the range of the data type of your vtkImageData. You shouldn’t have the discrepancy as you mentioned.

Could you post your pipeline code?

Thank you @sankhesh, you are so kind ! I’ll prepare for you my pipeline listing. I’ll come back a little bit later. See you !

Flaviu.

Here is my pipeline:

Input data: vtkDICOMReader -> vtkImageReslice (named as m_pResliceAxial)

View data: vtkImageActor -> vtkImageMapToColors -> vtkImageReslice

where vtkImageMapToColors, named as m_pColorAxial is defined as:

// Create a greyscale lookup m_pLUTAxial
m_pLUTAxial->SetValueRange(0.0, 1.0); // from black to white
m_pLUTAxial->SetSaturationRange(0.0, 0.0); // no color saturation
m_pLUTAxial->SetRange(dLevel - 0.5 * dWindow, dLevel + 0.5 * dWindow); // image intensity range
m_pLUTAxial->SetHueRange(0, 1);
m_pLUTAxial->SetRampToLinear();
m_pLUTAxial->Build();
m_pColorAxial->SetLookupTable(m_pLUTAxial);
m_pColorAxial->SetInputConnection(m_pResliceAxial->GetOutputPort());
m_pColorAxial->SetOutputFormatToLuminance();

and

m_pImageActor->GetMapper()->SetInputConnection(m_pColorAxial->GetOutputPort());

where dWindow and dLevel is taken from DICOM tags (in this case is 80/35).

Is ok the presentation ? I mean is understandable ? Please tell me if I should give more details.

I guess I identified the problem, but I don’t know how to solve it. The problem seem to be at setting vtkLookupTable for vtkImageMapToColors:

m_pLUTAxial->SetValueRange(0.0, 1.0); // from black to white
m_pLUTAxial->SetSaturationRange(0.0, 0.0); // no color saturation
m_pLUTAxial->SetRange(dLevel - 0.5 * dWindow, dLevel + 0.5 * dWindow); // image intensity range
m_pLUTAxial->SetHueRange(0, 1);
m_pLUTAxial->SetRampToLinear();
m_pLUTAxial->Build();
m_pColorAxial->SetOutputFormatToLuminance();
m_pColorAxial->SetLookupTable(m_pLUTAxial);
m_pColorAxial->SetInputConnection(m_pResliceAxial->GetOutputPort());

where dWindow and dLevel is coming from DICOM series. (In this case is 80/35).

If I would not setup LUT for vtkImageMapToColors, I mean comment this line:

m_pColorAxial->SetLookupTable(m_pLUTAxial);

the WL is correct any any WL value, but instead, I got this error:

ERROR: In D:\VTK\VTK-8.2.0\Imaging\Core\vtkImageMapToColors.cxx, line 154
vtkImageMapToColors (00000233F446F670): RequestInformation: No LookupTable was set but input data is not VTK_UNSIGNED_CHAR, therefore input can't be passed through!

So, how can I overcome this issue ? I am working on this from long time … :frowning:

Setting the output format to a single channel would mean that the image mapper would also do a color lookup. If you want the vtkImageMapToWindowLevelColors to be your final color lookup, set the output format to RGB (or RGBA if you want translucency in the mapped colors).

Here is the modification:

m_pLUTAxial->SetValueRange(0.0, 1.0); // from black to white
m_pLUTAxial->SetSaturationRange(0.0, 0.0); // no color saturation
m_pLUTAxial->SetRange(dLevel - 0.5 * dWindow, dLevel + 0.5 * dWindow); // image intensity range
m_pLUTAxial->SetHueRange(0, 1);
m_pLUTAxial->SetRampToLinear();
m_pLUTAxial->Build();
m_pColorAxial->SetOutputFormatToRGB(); // <-- modification
m_pColorAxial->SetLookupTable(m_pLUTAxial);
m_pColorAxial->SetInputConnection(m_pResliceAxial->GetOutputPort());

“where dWindow and dLevel is taken from DICOM tags (in this case is 80/35).”

These values are taken:

vtkDICOMMetaData* pMeta = m_pDICOMReader->GetMetaData();
const vtkDICOMValue& dWindow = pMeta->GetAttributeValue(DC::WindowWidth);
const vtkDICOMValue& dLevel = pMeta->GetAttributeValue(DC::WindowCenter);

Seem to behave the same way. At the WL like in dicom series, 80/35, my image look the same like in Radiant. But if I modify WL at 400/50, my image looks whiter than Radiant look at 400/50 WL.

The lookup table could be saturating. What is the range you set on the lookup table? For testing, remove the following line:

If I commented that line I got the following warning/error:

ERROR: In D:\VTK\VTK-8.2.0\Imaging\Core\vtkImageMapToColors.cxx, line 154
vtkImageMapToColors (000001FC9C3FE1D0): RequestInformation: No LookupTable was set but input data is not VTK_UNSIGNED_CHAR, therefore input can't be passed through!

I guess I cannot get out those line, would I ?

I see. In that case, could you set the range of the lookup table to be the scalar range of the image data and not derived from the window and level tags?

I have tried:

double* dRange = m_pDICOMReader->GetOutput()->GetScalarRange();

and

m_pLUTAxial->SetValueRange(0.0, 1.0); // from black to white
m_pLUTAxial->SetSaturationRange(0.0, 0.0); // no color saturation
m_pLUTAxial->SetRange(dRange); // image intensity range
m_pLUTAxial->SetHueRange(0, 1);
m_pLUTAxial->SetRampToLinear();
m_pLUTAxial->Build();
m_pColorAxial->SetOutputFormatToRGBA();
m_pColorAxial->SetLookupTable(m_pLUTAxial);
m_pColorAxial->SetInputConnection(m_pResliceAxial->GetOutputPort());

The image are darker than in Radiant at the original WL, 80/35.

dRange has following values: 0.0 and 3913.0

I am not sure what the difference could be. One suggestion - try loading your data in Slicer and see if the image looks different with the same window/level values. Slicer uses VTK for visualization and the pipeline is similar. If the image looks right in Slicer, maybe that could give you a clue as to what could be wrong with your code.

Is there a sure thing: if I didn’t apply LUT to my vtkImageMapToColors (m_pColorAxial), my image looks and behave just like Radiant or 3dslicer. Once I included vtkLookupTable (m_pLUTAxial), my image doesn’t look and behave exactly like Radiant or 3dslicer. And this issue drive me crazy for days …

That means an issue with your lookup table. See the documentation here: https://vtk.org/doc/nightly/html/classvtkImageMapToWindowLevelColors.html#details

I don’t have vtkImageMapToWindowLevelColors into my pipeline … There is 2 questions: how should include it in my pipeline ? and, if I would include it, it solve my problem ?

Typically, the simplest image mapping pipeline would be something like this:

vtkNew<vtkImageMapToWindowLevelColors> colors;
colors->SetInputConnection(reader->GetOutputPort());
// No need to set lookup table, unless you want a custom lookup table
// No need to set range, unless you want a limited range to be mapped
colors->SetWindow(window);
colors->SetLevel(level);
vtkNew<vtkImageActor> actor;
actor->GetMapper()->SetInputConnection(colors->GetOutputPort());

For a detailed example, see https://lorensen.github.io/VTKExamples/site/Cxx/Images/Transparency/.
You can replace vtkImageMapToColors with vtkImageMapToWindowLevelColors in that example to control window width/level of the displayed image.

1 Like