Algorithm interface (non-pipelined)

Hi Developers

I am making some algorithms, which are meant for assignment to a filter. More precisely registration algorithms, which I will use in homemade vtkContinuousRegistrationFilter inheriting from vtkTemporalAlgorithm.

I would them to implement UpdateProgress, CheckAbort and a SetSource(vtkPointSet*) and SetTarget(vtkPointSet*). The containing filter vtkContinousRegistrationFilter should use the internal algorithm for its execution.

Would the right approach be to inherit vtkAlgorithm and set the number of input and output ports to zero or would you prefer another design?

After execution of the internal registration algorithm, the output is just a homogeneous transform.

Thanks in advance
Jens

Are you trying to embed a vtkAlgorithm inside vtkContinuonsRegistration? I would appreciate a bit more details on the design you are trying to come up with, it is not very clear to me right now.

There is a new base algorithm called vtkForEach (in master) which could potentially help better than vtkTemporalAlgorithm for your specific use case. There is a discourse discussion about it there. Don’t hesitate to interact in the thread if it is of interest.

Thanks for reaching out. Yes, I have created a virtual subclass of vtkPointSetAlgorithm implementing a SetSource and a SetTarget. It contains threaded versions for finding correspondences using either closest point or rays. I am working on multiple classes implementing this interface for ICP(point-to-point, point-to-plane, plane-to-plane), the possibility for using Gauss-Newton or Levenberg-Marquardt optimizers. It is a spare-time project I am making to convince my employer that VTK may be a good framework for product, but it is kind of a long shot.

Right now, I only support vtkPolyData and a new polydata with a smaller memory footprint, where the triangulation is stored in cell and point data. For the subclass I am using the possibility to cancel and provide progress. The vtkContinuousRegistration has a method SetRegistrationAlgorithm. The next months, I will only work on the registration and not so much on the real-time part.

I have a feature branch, where some of this is outlined (only the idea). I am primarily working on a (right now non-public) stand-alone project made roughly like the vtkDICOM was - hence I use the VTK pipeline.

I am not sure I understand why you want a vtkAlgorithm with zero input and output ports.

Let me summarize what I understand from what you described: You want to have a class of filters that do iterative geometry processing on point clouds (such as ICP and such). You are thinking about doing it considering your dataset as a temporal dataset, having something like output_{t+1} = filter(output_t) with output_0 = input, until convergence, and you want to be able to see intermediary results at will.

The way I would design that would be by having a subclass of vtkTemporalAlgorithm embedded inside a larger filter. It sounds to me that you want to have something like:

class vtkContinuousRegistrationFilter
{
public:
  // SetTarget SetSource etc.

  // subclasses need to define when we converged to the solution
  virtual bool Converged() = 0;

protected:
  vtkSmartPointer<vtkAlgorithm> Implementation;
};

int vtkContinuousRegistrationFilter::RequestData(/* ... */)
{
  vtkPointSet* target = GetTargetSomehow();
  vtkPointSet* source = GetSourceSomehow();

  this->Implementation->SetInputSource(source);
  this->Implementation->SetInputTarget(target);

  int iteration = 0;
  while (!this->Converged())
  {
    this->Implementation->UpdateTimeStep(iteration++);
  }

  // Deal with output

  return 1;
}

Say you create a subclass vtkMyFilter, and here is how vtkMyFilter.cxx would look like:

namespace
{
class vtkImplementation : public vtkTemporalAlgorithm<vtkPolyData>
{
  // Implementation
  // Need to cache previous output to generate next output
};
} // anonymous namespace

vtkMyFilter::vtkMyFilter()
  : Implementation(vtkSmartPointer<vtkImplementation>::New())
{}

bool vtkMyFilter::Converged() { /* some criterion */ }

That is not what I want (or not entirely).

Two weeks a go, I uploaded a branch of a preliminary vtkContinuousRegistrationFilter that does work in the case the input is from a vtkFileSeriesReader providing temporal data downstream. The branch is called temporalRegistration and I have included some file series readers to ensure that the example, TestContinousPointRegistration works. All registrations are implicit and since the vtkPointSet does not have an orientation and position, I have decided to keep the results of the registrations in FieldData.

I have a temporal pipeline either live or with feed from a vtkFileSeriesReader. The vtkContinousRegistrationFilter will register an input to the previous dataset. In the feature branch, the filter simple returns the first dataset and keeps a reference for registration of the next input. Soon, it will continue executing instead until two successive inputs has been registered to one-another and return the first of the input and keep a shallow copy of the second for continuous registration. Further down the pipeline, I update a signed distance field (in-progress) and extract a larger mesh. The vtkContinuousRegistrationFilter will at a later time own a reference to a larger mesh and be able to register to this mesh using RANSAC.

The only reason why I introduce a vtkContinuousRegistration is to isolate the temporal aspects for continuity and allow the possibility for changing the registration algorithm and also the “resnap” algorithm, which could be RANSAC. This is a typical pipeline for solving a SLAM problem.

For real-time application, what I hope to be able to do is the following.

Insert a dataset into the pipeline
Update at the end of the pipeline, if only one dataset I will get an empty output
Insert another dataset into the pipeline
Update at the end of the pipeline. I vtkTemporalDataSetCache with length 2 is at the beginning of the pipeline, so now I can get an output.

I was considering to have a module in the beginning which exposed a TIME_RANGE downstream and set the UPDATE_TIME_STEP() at the end before each update. I guess with the new vtkTemporalAlgorithm, I will need to run it in real-time mode where I request each temporal update - also in the case of knowing all time steps in advance. I will figure it out when I get to this. Right now, I am working on the registration algorithms and doing some experiments with FLANN, since I am not really satisfied with the performance of the VTK locators. I managed to get an almost linear speed-up by using SMPTools, where the correspondences are made using by a modified vtkImplicitPolyDataDistance, where I can get both the closest point and an interpolated normal - the SharedEvaluate is not public at moment.

Can you describe what the internal algorithm would be? I am having trouble fully understanding what you are intending to do. Maybe you could give a coarse execution trace of an example?

Sure, you could also run the test in my remote branch. It does exactly what I want except that here all time-steps are known beforehand. It is a little difficult without implementing everything.

RealTimeSource -> TemporalCache(2) -> ContRegistration   

contReg->Update():

Internals:

  • Search upstream and register two consecutive temporal datasets to one another. If a LastTarget exist, try instead to register to this, but only if the new data data is at time i and the old last target is at time i-1. If this fails, we need to reset the internals, since we only interested in having a chain of datasets each registered a previous dataset at one time step earlier.
  • If registration succeeds, store the most recent dataset as a LastTarget and return the current data with a transformation relative to the first Source. I am concatenating transforms internally to fulfill this.

Further down the pipeline, I will update a truncated signed distance field and every now and then, I can extract a mesh. In the image, I have shown my application doing exactly this - except this is not made with a temporal pipeline, but a bunch of callbacks and some global variables.