vtkPNGReader does not read png files as grayscale

I want to visualize PNG files in a directory using volume rendering, but the vtkColorTransferFunction and vtkPiecewiseFunction classes did not work with vtkVolumeProperty. As far as I understand, this issue arises because the voxel values are in RGB format. However, I know that it’s possible to convert the RGB format to grayscale, but in this case, the PNG files are already in grayscale.

Hello,

As far as I understand, you need to store scalar values in the data set. Whatever RGB values in there will be interpreted as scalars. So, since you didn’t post the code you’re using, I guess you need to do some conversion after PNG read and while setting the values in vtkImageData object. Since your PNGs are already grayscale, R==G==B, so I guess you only need to read one of these channels and load into your vtkImageData and set the color scale to 0 (min) and 255 (max). Again, all this is guessing. I believe you could share the part of the code involved into loading the PNGs and volume rendering so we can reach a more accurate diagnosis.

best,

PC

Thank you, Paulo. Yes, as you mentioned, the PNGs are already grayscale. Below, I am sharing the code where I read the PNGs and transfer them to vtkImageData.

vtkSmartPointer<vtkImageAppend> imageStack = vtkSmartPointer<vtkImageAppend>::New();
imageStack->SetAppendAxis(2);

for (const string& filePath : pngFiles) {
	vtkSmartPointer<vtkPNGReader> reader = vtkSmartPointer<vtkPNGReader>::New();
    reader->SetNumberOfScalarComponents(1);
	reader->SetFileName(filePath.c_str());
	reader->Update();

	imageStack->AddInputConnection(reader->GetOutputPort());
}

imageStack->Update();
imageStack->GetOutput()->SetSpacing(0.5, 0.5, 0.5);

Hello,

Thank you, but I think we need everything after that and up to setting up a vtkVolume object.

best,

PC

Of course, I do. But vtkColorTransferFunction and vtkPiecewiseFunction not working. I think this is because three channels are being used instead of a single channel.

int main()
{
	string path = "C:\\Users\\fatil\\OneDrive\\Belgeler\\Dicoms\\snow_png";
	vector<string> pngFiles = getBMPFiles(path);

	if (pngFiles.empty()) {
		std::cerr << "Error" << std::endl;
		return -1;
	}

	vtkSmartPointer<vtkImageAppend> imageStack = vtkSmartPointer<vtkImageAppend>::New();
	imageStack->SetAppendAxis(2);

	for (const string& filePath : pngFiles) {
		vtkSmartPointer<vtkPNGReader> reader = vtkSmartPointer<vtkPNGReader>::New();
		reader->SetNumberOfScalarComponents(1);
		reader->SetFileName(filePath.c_str());
		reader->Update();

		imageStack->AddInputConnection(reader->GetOutputPort());
	}

	imageStack->Update();
	imageStack->GetOutput()->SetSpacing(0.5, 0.5, 0.5);

	vtkSmartPointer<vtkSmartVolumeMapper> mapper = vtkSmartPointer<vtkSmartVolumeMapper>::New();
	mapper->SetInputConnection(imageStack->GetOutputPort());
	
	vtkSmartPointer<vtkColorTransferFunction> color = vtkSmartPointer<vtkColorTransferFunction>::New();
	color->AddRGBPoint(0, 1, 0, 0);
	color->AddRGBPoint(255, 1, 1, 1);
	
	vtkSmartPointer<vtkPiecewiseFunction> opacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
	opacity->AddPoint(0, 0);
	opacity->AddPoint(255, 0.1);
	
	vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
	volume->SetMapper(mapper);
	volume->GetProperty()->SetColor(color);
	volume->GetProperty()->SetScalarOpacity(opacity);

	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
	renderer->AddVolume(volume);
	renderer->SetBackground(0.1, 0.2, 0.4);

	vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
	renderWindow->AddRenderer(renderer);

	vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
	renderWindowInteractor->SetRenderWindow(renderWindow);

	renderWindow->Render();
	renderWindowInteractor->Start();

	return 0;
}

Try to modify your code to this:

int main()
{
	string path = "C:\\Users\\fatil\\OneDrive\\Belgeler\\Dicoms\\snow_png";
	vector<string> pngFiles = getBMPFiles(path);

	if (pngFiles.empty()) {
		std::cerr << "Error" << std::endl;
		return -1;
	}

	vtkSmartPointer<vtkImageAppend> imageStack = vtkSmartPointer<vtkImageAppend>::New();
	imageStack->SetAppendAxis(2);

	for (const string& filePath : pngFiles) {
		vtkSmartPointer<vtkPNGReader> reader = vtkSmartPointer<vtkPNGReader>::New();
		reader->SetNumberOfScalarComponents(1);
		reader->SetFileName(filePath.c_str());
		reader->Update();

		imageStack->AddInputConnection(reader->GetOutputPort());
	}

	imageStack->Update();
	imageStack->GetOutput()->SetSpacing(0.5, 0.5, 0.5);

	vtkSmartPointer<vtkSmartVolumeMapper> mapper = vtkSmartPointer<vtkSmartVolumeMapper>::New();
	mapper->SetInputConnection(imageStack->GetOutputPort());
    /*add*/ mapper->SetBlendModeToComposite(); // composite first
    /*add*/ mapper->Update();

	vtkSmartPointer<vtkColorTransferFunction> color = vtkSmartPointer<vtkColorTransferFunction>::New();
    /*add*/ color->SetColorSpaceToRGB();
	color->AddRGBPoint(0, 1, 0, 0);
	color->AddRGBPoint(255, 1, 1, 1);
	
	vtkSmartPointer<vtkPiecewiseFunction> opacity = vtkSmartPointer<vtkPiecewiseFunction>::New();
    /* Set both to 1.0 to make sure it works before trying to set translucency. */
	opacity->AddPoint(0, 0);
	opacity->AddPoint(255, 0.1);

    /** new block of code to add **/	
    //this object instructs VTK on how to render the volume.
    vtkSmartPointer<vtkVolumeProperty> volumeProperty = vtkSmartPointer<vtkVolumeProperty>::New();
    volumeProperty->ShadeOff();
    volumeProperty->SetColor(color);
    volumeProperty->SetScalarOpacity(opacity);
    //volumeProperty->SetInterpolationType(VTK_LINEAR_INTERPOLATION);
    volumeProperty->SetInterpolationType(VTK_NEAREST_INTERPOLATION);

	vtkSmartPointer<vtkVolume> volume = vtkSmartPointer<vtkVolume>::New();
	volume->SetMapper(mapper);
	/*remove*/volume->GetProperty()->SetColor(color);
	/*remove*/volume->GetProperty()->SetScalarOpacity(opacity);
    /*add*/volume->SetProperty( volumeProperty );

	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
	renderer->AddVolume(volume); // if it still doesn't work, try AddActor(volume) instead.
	renderer->SetBackground(0.1, 0.2, 0.4);

	vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
	renderWindow->AddRenderer(renderer);

	vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
	renderWindowInteractor->SetRenderWindow(renderWindow);

	renderWindow->Render();
	renderWindowInteractor->Start();

	return 0;
}

Thanks, Paulo. It works for vtkColorTransferFunction, but not for vtkPiecewiseFunction. I’ve decided to add an alpha channel to vtkImageData for opacity. This way, I won’t need both vtkColorTransferFunction and vtkPiecewiseFunction. Thanks again. Your last message was really helpful.

That’s how I successfully render volumes in my project, including opacity/translucense. If you manage to make it work as desired without those two, please, share your solution here.