Annoying "feature" of the BivariateManager

If the BivariateManager plugin is part of a ParaView project and the user loads a state file, he will often have to choose the “Use filenames from state” option, if e.g. he wants to open a state file from somebody else that comes together with other files in a package.

With the version that I am currently using (13.1), this will result in four warning messages about PNG files that could not be found in that location, but were loaded from some plugin location instead. This has no negative consequences, but it is highly confusing for the user because he has of course no idea what “Bremm.png” etc. is actually good for!?

Already in the source code of pqBivariateManager.cxx the solution of that problem is pointed out:

// Nice to have : textures names can be retrieved from the ressources directly.

Right - that’s it! Only that it is slightly more important than just “nice to have”, and I do not want my customers to always have to deal with these useless messages, but still I do not want to drop the option to use bivariate representations for whatever data they may have!

So right now I am trying to realize this job in my own version of the ParaView code. The point where I am struggling is at the end of pqBivariateManager.cxx: Here the programmer did a whole lot of actions that should not be required if the files would really be inside a resource file, like:

  • find the path where the files are stored
  • read the files
  • generate some “proxy” for them

Now in order to drop all this stuff, I would need to find out where these proxies with the PNG files are actually being used - and here I am failing! Just “grepping” through the source code, I do not find one single location where the bitmaps are actually being used!

Could it be that I can simply drop all this bitmap stuff entirely without losing any important functionality!?? This would of course be the easiest solution! Otherwise I can of course load them with the common “Qt style” method like “:/pqBivariateRepresentations/Recourses/Bremm.png” etc. - but I would have to know where such code is indeed required!

Actually my guess is that generating these proxies has also the adverse effect of writing them into all state files, which finally results in the above annoying warning messages…

Hi @cobo !

First, thanks for using the bivariate plugin!

Your analysis is perfectly correct! This nice to have have far reaching impact that were no identified. Storing this files into Qt resources file would definitely fix the issue.

It should not be that hard to do, but its not trivial either. Please open an issue on https://gitlab.kitware.com/paraview/paraview/-/issues

Ok, right now I would prefer to code a solution and only then make it some “issue with solution proposal”! The point is that

  • I want it now and in “my current” ParaView version, not at some time in the future when possibly somebody else finds time
  • Of course I could contribute code through the gitlab interface, but honestly I realized that the related “bureaucracy” is above my skills level in the git world…

This said, this is the actual state of my art relating such a self-coded solution. The construction site that I opened is in pqBivariateManager.cxx.

Loading the four PNG is no big magic, and once this is done, I can address them as follows:

namespace
{
  // Nice to have : textures names can be retrieved from the ressources directly.
  //const std::string TEXTURE_FILES[4]{ "Bremm.png", "Schumann.png", "Steiger.png", "Teulingfig2.png" };
  const std::string TEXTURE_FILES[4]
  {":/pqBivariateRepresentations/Resources/Bremm.png",
   ":/pqBivariateRepresentations/Resources/Schumann.png",
   ":/pqBivariateRepresentations/Resources/Steiger.png",
   ":/pqBivariateRepresentations/Resources/Teulingfig2.png"
  };
}

But at the end of the code I am struggling in the function

void pqBivariateManager::generateTextureProxies(pqServer* server)

My understanding is that instead of passing the full path and filenames, I would have to ensure that in vtkNetworkImageSource:UpdateImage() the mode “ReadFromMemory” is chosen, instead of “ReadFromFile”. And that of course for this purpose I need to provide the image data somehow as a vtkImageData object. And this has to happen in the above generate… function - right?

Ok, if now the variable textureFile contains one of the four images as a std::string in “Qt resource format”, I was generating such vtkImageData thing with the following code (which however I could not test yet - except that I could see that at least “something” was being read from the resources…):

if(textureFile[0] == ':') // or something more sophisticated...

  QPixmap pix(textureFile.c_str());
  QImage img = pix.toImage();

  vtkNew<vtkQImageToImageSource> qimageToImageSource;
  qimageToImageSource->SetQImage(&img);
  qimageToImageSource->Update();

  vtkNew<vtkImageData> image;
  image->DeepCopy(qimageToImageSource->GetOutput());

  // TEST: see if we read "something" from the resources - and see if at least it is
  //         different for the four bitmaps (result: yes, it is!)
  std::cout << "this is a Qt resource" << std::endl;
  std::cout << "<" << textureFile << ">" << std::endl;
  std::cout << "this is a vtkImageData pointer " << image.Get() << std::endl;
  std::cout << "- ref count " << image->GetReferenceCount() << std::endl;
  image->PrintSelf(std::cout, vtkIndent(2));
  unsigned char const* buf = static_cast<unsigned char const*>(image->GetScalarPointer());
  std::cout << "buf < " << std::setw(2) << std::setfill('0') << std::hex << (int)buf[0]
            << " " << std::setw(2) << std::setfill('0') << std::hex << (int)buf[1]
            << " " << std::setw(2) << std::setfill('0') << std::hex << (int)buf[2] << " >" << std::endl;

  ......
}

Now what I see is how the “trick” is done for passing a filename - at the very end of the function. And I understand that I need to generate a “Trivial Producer” with my vtkImageData information, probably in a somewhat similar way.

And this is exactly the place where I am struggling! Because this jungle of proxies, sources, server manager stuff etc. is not really easy to detangle…

This is the code for passing the file path and name:

      auto texturePath = texturesFolder + textureFile;

      // Create texture proxies. We do not use pqObjectBuilder because we need to specify
      // a custom proxy name (which is the name of the texture file, minus the extension)
      vtkSMSessionProxyManager* spxm = server->proxyManager();
      vtkSMProxy* textureProxy = spxm->NewProxy("textures", "ImageTexture");
      auto textureName = vtksys::SystemTools::GetFilenameWithoutExtension(textureFile);
      spxm->RegisterProxy("textures", textureName.c_str(), textureProxy);
      vtkSMPropertyHelper(textureProxy->GetProperty("FileName")).Set(texturePath.c_str());
      textureProxy->UpdateVTKObjects();
      textureProxy->Delete();

Indeed not a trivial part, that is why it was not done originally.

I dont spot anything wrong in the code you shared. Ideally share a branch of ParaView I can compile for myself and try out.

Against all expectations, it looks like I managed to get it done!

The ParaView version that I am using is 13.1, with all subprojects added, then the git stuff removed and generated a new git repository “flat” and “all included”. Based on that, I am adding a number of patches, for changing or adding this or that little feature. My changes for the bivariate bitmaps are all in this diff file:

bivariate_texture_resources.diff (16.7 KB)

My problem was at the end that I did not really understand what a “trivial producer” actually is and does, but that I would have to prepare one. A first idea was to go for some “brute force” solution, somehow smuggling a vtkImageData object pointer to wherever it is needed, but then I found out that the “trivial producer” does more or less exactly this!

The final hint I found in TestDistributedTrivialProducer.cxx, and there it was the following line of code:

vtkDistributedTrivialProducer::SetGlobalOutput("sphere", data);

From which I learned that you just generate some global data object with a name, which can later on be retrieved with the following code:

vtkNew<vtkDistributedTrivialProducer> producer;
producer->UpdateFromGlobal("sphere");

The code is maybe not perfect yet, maybe lacking the one or other test. And for example this is now the list of possible textures that I see in the “Display” tab of the bivariate texture representation:

So all the textures are doubled, but all “eight” are functional! No idea why, but at least I do not get the annoying error messages any more, because no references to external files occur in the state files.

Also not perfect: If I have some “Bremm” representation on the screen, save the thing into a state file and read it back in, this is how the “Texturing” section of the “Display” tab is looking:

However, in the render view I still have the “Bremm” texture representation correctly! It only is not appearing in the “Display” tab. Ok, I can live with that - assuming that not so many will use this feature anyway.

What I can still do: load an external texture PNG, from whatever location. It can be saved to a state file and read back in - and even with the “all from same location…” option it did not complain, but still loaded the PNG from the correct place…

And in the drop down list of possible textures I can now choose the default textures even 3 times:

So you see that the “solution” is really not perfect yet! But my main purpose was to get rid of the annoying warning messages - and this seems to be a success so far.

One more thing - rather in the direction of a new feature: As you know, my software and my customers are hardly ever working with “point data”, mostly just “cell data”. Now the bivariate texturing does not care about cell data, but only respects point data, so in order to use it, you need to apply the “cell data to point data” filter first.

On the other side, having some bivariate texturing option with cell data would open up many more interesting possibilities! The solution for points is clever in the sense that you open up a 2-dim color plane instead of just a color scale in 1-dim. However with cells, you can make use of the fact that cells already have a surface!

In an older software that I managed - before my ParaView days -, we also managed “block models” like e.g. this one:

Here you can see the CaO content of each volume block in the model.

But then on top of that, we wanted to visualize still other things, not continuous values, but rather kind of “markers” - and we did that by putting still a much smaller square on each one of the cell surfaces. Typically these were then black, white or some gray etc., in order not to “collide” with the base color of the blocks that were never just black - gray - white.

Other things can be thought about, like splitting each block face in some other way and even have a second continuous color scale completely independently from the first. This may turn out to be too much overloaded, but markers could still be a thing to consider - just as an idea for now.

Nice work @cobo

I looked at the .diff and that seem to be what I had in mind.
Obviouvly the next step would be to open a MR, clean the diff up and follow the review process.

It can take some time, so as you see fit.

Kitware can also take care of this process if you want.

Best,

Right: For my own purposes I consider my solution so far satisfactory, but of course I would be happy if it can also become a contribution to the overall evolution of ParaView! A very small return for the fact that I profit so much from the fact that I can use the VTK and ParaView code for my own projects just for free. But others did a lot of work for it!

But now I would indeed be glad if somebody at Kitware would do the formal quality control related integration steps - best somebody who is familiar with these processes and not in the need of being taken by the hand for every little thing… It would already start with the “stupid” question: “What is a MR”? (Maybe something like “merge request”??)

Indeed a merge request.

You can read all about the contribution process here:

https://gitlab.kitware.com/paraview/paraview/-/blob/master/Documentation/dev/git/develop.md?ref_type=heads