linzhang.robot
linzhang.robot

Reputation: 379

VTK - update vtkPolyData in renderWindow without blocking interaction

I use VTK to display a point cloud and update it in a loop. The windows can be interacted by mouse for the first time I display the cloud, if I want to update the cloud, I have to press 'q' and the Window will display updated cloud. However, although the cloud is updated and shown in the window correctly, I lost control of the window (i.e. cannot use mouse to move or rotate the point cloud in the window).

Following is my minimum code that recreates the issue. I have a Visualization class to handle this, its initialization list:

points(vtkSmartPointer<vtkPoints>::New())
, vertices(vtkSmartPointer<vtkCellArray>::New())
, pointsPolyData(vtkSmartPointer<vtkPolyData>::New())
, mapper(vtkSmartPointer<vtkPolyDataMapper>::New())
, actor(vtkSmartPointer<vtkActor>::New())
, renderer(vtkSmartPointer<vtkRenderer>::New())
, renderWindow(vtkSmartPointer<vtkRenderWindow>::New())
, renderWindowInteractor(vtkSmartPointer<vtkRenderWindowInteractor>::New())

Here is the init function:

void Visualization::init(const cv::Mat &point_cloud){
noPoints = point_cloud.cols * point_cloud.rows;

// Preallocate memory
points->SetNumberOfPoints(noPoints);

// Copy
int element_stripe = sizeof(float) * point_cloud.channels();
for (unsigned i = 0; i < noPoints; i++)
{

    float x = point_cloud.at<cv::Vec3f>(i)[0];
    float y = point_cloud.at<cv::Vec3f>(i)[1];
    float z = point_cloud.at<cv::Vec3f>(i)[2];

    vtkIdType pid[1] = {i};
    points->SetPoint(i, x, y, z);
    vertices->InsertNextCell(1, pid);
}

// Push data to polydata
pointsPolyData->SetPoints(points);
pointsPolyData->SetVerts(vertices);


// Set visualization pipeline
mapper->SetInputData(pointsPolyData);
actor->SetMapper(mapper);
actor->GetProperty()->SetPointSize(2);
renderWindow->AddRenderer(renderer);
renderer->SetBackground(.3,.6,.3);
renderWindowInteractor->SetRenderWindow(renderWindow);

renderer->AddActor(actor);
isPipelineInit = true;

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

Here is the display function:

void Visualization::display(const cv::Mat &point_cloud){
if(!isPipelineInit)
    init(point_cloud);
else
{
    // Copy
    int element_stripe = sizeof(float) * point_cloud.channels();
    for (unsigned i = 0; i < noPoints; i++)
    {
        points->SetPoint(i, (float*)(point_cloud.data + i*element_stripe));
    }
    pointsPolyData->Modified();
    mapper->Update();

    renderWindowInteractor->GetRenderWindow()->Render();
}
}

I also have a function that run an infinite loop in a thread:

void Visualization::run()
{
    while (1)   // infinite while loop
    {
            // Update m_clouds and display
            display(m_clouds);
    }
}

UPDATE: As mirni remind me that 'q' key essentially quit current renderWindow, what I see later on is actually from the display function rather then the init. In the display I didn't call renderWindowInteractor->Start() and therefore I cannot interact with the window. However, the renderWindowInteractor->Start() stuck my current thread and therefore I cannot continue my program and update my vtkPolyData.

I guess the question then becomes: How shall I display and update at the same time? Shall I do it in different thread, so one for display and another for update?

Thanks!

Upvotes: 3

Views: 6528

Answers (1)

mirni
mirni

Reputation: 695

The answer to your question is: You just update the data and call vtkRenderWindow::Render() method. VTK rendering pipeline discovers by itself that data has changed and that it needs to update.

Your code really needs to be redesigned -- it makes more sense to update the visualization when the data changed (only once), rather than keep probing the data "has anything changed?" (every millisecond).

Also, normally you don't need a separate thread for VTK visualization pipeline. Keep it simple:

  • Don't make the visualization object a thread. (What if you have 100 objects? Will you create 100 separate threads one for each of then?).
  • Get rid of the infinite loop.
  • Rename the display(...) method to
void Visualization::update(const cv::Mat &point_cloud);

make it public and call it from outside whenever data changes by any means that fit your scenario (callback? Qt signal? message? ...). Make sure to keep the vtkRenderWindow::Render() call at the end.

  • You may want to consider copying the data from cv::Mat struct directly, using the raw pointer to the point data exposed by vtkPoints::GetVoidPointer(int) and do a memcopy instead of the for loop, but don't do this until you have things in place and your measurements show the need for optimization. (Premature optimization is the root...)

  • vtkRenderWindowInteractor::Start() starts an event loop and hijacks the thread as you found out. Instead of Start() just call Initialize() to enable interactor but let the control flow go on.

HTH, Miro

Upvotes: 6

Related Questions