0

My Eigen::MatrixXf m stores some data and I need to use this data in another array vtkFloatArray. In order to avoid copying the data I do:

vtkNew<vtkFloatArray> array;    // `vtkNew` is a smart pointer class
{
    Eigen::MatrixXf m(2, 2);
    m << 1, 2, 3, 4;
    array->SetArray(m.data(), m.size(), 1);
}
// `array` must go on living and have access to the `.data()` pointer

The problem is that the array must live much longer than eigen matrix m lives but when the execution goes out of the scope it seems that array can't get values as they are removed (eigen matrix m is deleted when out of the scope).

It seems that I can dynamically allocate eigen matrix:

vtkNew<vtkFloatArray> array;
{
    Eigen::MatrixXf* m = new Eigen::MatrixXf(2, 2);
    m << 1, 2, 3, 4;
    array->SetArray(m->data(), m->size(), 1);
}
// `array` must go on living and have access to the `.data()` pointer

Nevertheless SetArray() has some options to save and delete array I suspect that I will get memory leak if I use this approach. Probably std::shared_ptr<Eigen::MatrixXf> m could help me but I'm not sure exactly as it should count references on matrix m and vtkFloatArray references to the m.data() pointer (though I maybe mistaken) and I will also get memory leak.

If someone knows how to keep my eigen matrix alive while vtkFloatArray is alive and do not get memory leak, please explain that.

Regards

Kerim
  • 171
  • 3
  • 10
  • 1
    Terminology: *It seems that I can create eigen matrix as a pointer* This is called dynamic allocation. If `m` is out of scope, `array` should also be out of scope, so unless `vtkNew` is playing silly games off-screen, the point should be moot. Wait a second. Are you returning `array` and that's how `m` goes out of scope? If so, the `m` is a dynamic allocation trick will definitely lead to a leak. Worse, does `array` take ownership of `m`'s data? That will probably go BOOM! somewhere down the line. – user4581301 Aug 16 '21 at 22:58
  • 2
    I think what you are looking for is "Can I transfer ownership of the backing data buffer from `Matrix` to some other object?" I'm no Eigen guru, but a quick search along those lines finds nothing. – user4581301 Aug 16 '21 at 23:08
  • @user4581301 hi, thank you for comment. I edited the question. I hope it will make my issue more transparent.. And yes, probably taking the ownership of an eigen data by buffer vtk array may solve the issue – Kerim Aug 16 '21 at 23:12
  • 1
    Do you at all need the Eigen matrix? You don't in your snippet. If you do you could write a wrapper that holds both an Eigen matrix and an array and disposes of both of them in the destructor, in the right order. – Peter - Reinstate Monica Aug 16 '21 at 23:23
  • @Peter-ReinstateMonica thabk you for the comment. I read data from disk to the eigen matrix that is it. I can't read these data directly to the vtk array. But thank you for the approach, I don't like it but if I don't find anything better then probably I will use your solution :) – Kerim Aug 16 '21 at 23:27
  • 1
    You can't take away ownership of an `Eigen::Matrix` into a non-Eigen object (without hacking with Eigen's internals). Create a `vtkArray` somehow and access it via `Eigen::Map` if you must. – chtz Aug 17 '21 at 09:13
  • 1
    *"In order to avoid copying the data I do:"* That won't work. If you want to store the data into a `vtkArray` you need to copy it. – RHertel Aug 17 '21 at 10:11
  • 1
    @RHertel: As long as they have the same layout, it can work with ```Map```. – RL-S Aug 19 '21 at 07:56
  • 1
    @RL-S Did you try specifically with a `vtkPointer` / `vtkArray`? I remember that I once tried to transfer data to a `vtkArray` and did not succeed. I concluded that this was not possible without copying the data, but I might be mistaken. – RHertel Aug 19 '21 at 09:53
  • 1
    I haven't tried it myself. The [doc][1] says "Values are stored with fortran ordering", so that would be column-major as well (even though fortran is 1-based). If you create the ```vtkArray``` first, resize it correctly, and then access it via ```Eigen::Map```, it sounds like it should work. Do you remember what went wrong in your case? [1]: https://vtk.org/doc/release/6.2/html/classvtkDenseArray.html – RL-S Aug 19 '21 at 10:37
  • Thank all for the comments, I guess I try @Peter-ReinstateMonica proposition (I will try to inherit from Eigen::Matrix and set there vtkArray and everytime the array is resized it is the user responsibility to update vtkArray number of points) – Kerim Aug 26 '21 at 01:39
  • 1
    @Kerim if you find a working solution you can answer your own question for posterity. You may even get upvotes ,-). – Peter - Reinstate Monica Aug 26 '21 at 05:42

1 Answers1

0

My solution is based on @Peter-ReinstateMonica suggestion: create a wrapper class where both objects vtkFloatArray and Eigen::MatrixXf would reside and that would make possibe to control the lifetime of both these objects.

Let's call wrapper class vtkEigenFloatArray and let it be inherited from vtkFloatArray. As our wrapper class implicitely inherits vtkObject we should follow vtk's inheritance structure to make it usable within vtk (for example vtkNew<vtkEigenFloatArray>) while keeping pimp idiom (that is why you can see many vtk methods and macros within created class, this is done by analogy with vtkFloatArray source code).

We add Eigen::MatrixXf* eigArr member variable to our vtkEigenFloatArray class.

We also add the method Eigen::MatrixXf* GetEigenArray() that returns Eigen::MatrixXf* eigArr pointer and setter void SetEigenArray(Eigen::MatrixXf* arr) to make available to use the inner Eigen::MatrixXf object from ouside created class.

It worth to note that vtkEigenFloatArray and its protected Eigen::MatrixXf* eigArr object share the same buffer. It is set every time one calls void UpdateEigenBuffer() function. Moreover everytime when one resizes Eigen::MatrixXf* eigArr from outside the created class the function void UpdateEigenBuffer() must be called to update size of vtkFloatArray-inherited class.

Also user must never externally delete Eigen::MatrixXf* eigArr as it is deleted in vtkEigenFloatArray's destructor.

The drawback of proposed solution:

When the main vtkEigenFloatArray object is deleted the pointer Eigen::MatrixXf* eigArr will not be equal to nullptr. Thus you cannot check whether Eigen::MatrixXf* eigArr is alive or not while you don't have holder vtkEigenFloatArray object.

#include <vtkNew.h>
#include <vtkFloatArray.h>
#include <Eigen/Dense>
#include <iostream>

class vtkEigenFloatArray : public vtkFloatArray
{
public:
  static vtkEigenFloatArray* New();
  vtkTypeMacro(vtkEigenFloatArray, vtkFloatArray);


  void PrintSelf(ostream& os, vtkIndent indent) override
  {
    this->RealSuperclass::PrintSelf(os, indent);
  }

  // This macro expands to the set of method declarations that
  // make up the interface of vtkAOSDataArrayTemplate, which is ignored
  // by the wrappers.
#if defined(__VTK_WRAP__) || defined(__WRAP_GCCXML__)
  vtkCreateWrappedArrayInterface(float);
#endif

  /**
   * A faster alternative to SafeDownCast for downcasting vtkAbstractArrays.
   */
  static vtkEigenFloatArray* FastDownCast(vtkAbstractArray* source)
  {
    return static_cast<vtkEigenFloatArray*>(Superclass::FastDownCast(source));
  }

  /**
   * Get the minimum data value in its native type.
   */
  static float GetDataTypeValueMin() { return VTK_FLOAT_MIN; }

  /**
   * Get the maximum data value in its native type.
   */
  static float GetDataTypeValueMax() { return VTK_FLOAT_MAX; }
  
  /**
   * Deletes previous pointer and sets new
   */
  void SetEigenArray(Eigen::MatrixXf* arr)
  {
    delete eigArr;
    eigArr = arr;
    UpdateEigenBuffer();
  }

  /**
   * Don't delete this pointer!
   */
  Eigen::MatrixXf* GetEigenArray()
  {
    return eigArr;
  }

  /**
   * Must be manually called every time Eigen::MatrixXf resized
   */
  void UpdateEigenBuffer()
  {
    this->SetArray(eigArr->data(), eigArr->size(), 1); // must be set to 1 or in destructor runtime error
  }

protected:
  vtkEigenFloatArray()
  {
    eigArr = new Eigen::MatrixXf();
  }

  ~vtkEigenFloatArray() override
  {
    delete eigArr;
    eigArr = nullptr;
  }
  
  Eigen::MatrixXf* eigArr;

private:
  typedef vtkAOSDataArrayTemplate<float> RealSuperclass;

  vtkEigenFloatArray(const vtkEigenFloatArray&) = delete;
  void operator=(const vtkEigenFloatArray&) = delete;

  friend class vtkNew<vtkEigenFloatArray>;
};

vtkStandardNewMacro(vtkEigenFloatArray);


int main(int argc, char *argv[]) {
  /* General usage */
  vtkNew<vtkEigenFloatArray> arr;
  Eigen::MatrixXf* M = arr->GetEigenArray();
  M->resize(2, 2);
  *M << 1, 2, 3, 4;
  arr->UpdateEigenBuffer();

  std::cout << "Eigen M:" << std::endl;
  std::cout << *M << std::endl;

  std::cout << "VTK array:" << std::endl;
  for(int i = 0; i < arr->GetSize(); i++)
    std::cout << arr->GetValue(i) << std::endl;

  arr->Allocate(5);
  std::cout << "Eigen M:" << std::endl;
  std::cout << *M << std::endl;

  std::cout << "VTK array:" << std::endl;
  for(int i = 0; i < arr->GetSize(); i++)
    std::cout << arr->GetValue(i) << std::endl;

  /* Test `SetEigenArray` */
  Eigen::MatrixXf* MM = new Eigen::MatrixXf();
  MM->resize(2,2);
  *MM << 11, 22, 33, 44;

  arr->SetEigenArray(MM);

  std::cout << "Eigen MM:" << std::endl;
  std::cout << *MM << std::endl;

  std::cout << "VTK array:" << std::endl;
  for(int i = 0; i < arr->GetSize(); i++)
    std::cout << arr->GetValue(i) << std::endl;

  return 0;
}
Kerim
  • 171
  • 3
  • 10
  • 1
    Please provide additional details in your answer. As it's currently written, it's hard to understand your solution. – Community Sep 02 '21 at 02:39