How to use the vtkFrenetSerretFrame to make a dental panorama, also known as CPR?

Hello every one,
This is my first post on this forum, Hope you guys can give me some advice,
Below are my questions here
I was refer to VTK Journal --A Spline-Driven Image Slicer wish to implement Dental panorama Image, also known as CPR? with C++ VTK + QT, and I was plugin vtkFrenetSerretFrame function to calculate FSTangents and FSNormal polylines but in next step ,how can I calculate and generate the Dental panorama Image? Please anyone with relevant experience give me some advice, thanks a lot

Hi James,
I am trying to apply the vtkFrenetSerretFrame class in the c++ vtk qt program, and the function seems to work well in the codes line, but it fails when I run the program. Do you have any tips for me to apply the class?
Kind Regards,

HI Yuxuan

Have you build the vtk remote module and checked SplineDrivenImageSlicer option First?

In my experience, apply the vtkFrenetSerretFrame class have needs to include the correct version of SplineDrivenImageSlicer.dll (depending on the VTK version you use), otherwise it will crash at runtime even if the codes compilation is successful.

I hope the above can help you. If you have any latest progress, please let me know.

Maybe have a look at Slicer’s curved planar reformat

Hope it helps

I have used the for defining movement trajectories of ultrasound probes on organs and defining scan trajectories for an intra-oral scanner. I can recommend defining your own version of vtkOrientedGlyphContourRepresentation to get also the intermediate points, when moving on a smooth surface. I have used TNB in for a couple of years on different platforms and not had any issues with SplineDrivenImageSlicer.

Hi James,
Thanks for your prompt reply! I have included the vtkFrenetSerretFrame class before building the remote module since I am still figuring out using the cmake for developing. The Frenet class works well now and generates the required matrix.
Thanks again!
Kind regards.

I have added my extension here, which is quite convenient for working with TNB. You have a callback for drawing the TNB frame.

#pragma once

#include "vtkOrientedGlyphContourRepresentation.h"
#include "vtkInteractionWidgetsModule.h" // For export macro

class vtkProperty;
class vtkActor;
class vtkGlyph3DMapper;
class vtkPolyData;
class vtkProperty;
class vtkCallbackCommand;
class vtkTNB;
class vtkCellLocator;
class vtkArrowSource;
class vtkTransform;

struct TNBPipeLineData;

class VTKINTERACTIONWIDGETS_EXPORT vtkOrientedGlyphContourRepresentationEx
  : public vtkOrientedGlyphContourRepresentation
{
public:
  static vtkOrientedGlyphContourRepresentationEx* New();
  vtkTypeMacro(vtkOrientedGlyphContourRepresentationEx, vtkOrientedGlyphContourRepresentation)
  void PrintSelf(std::ostream& os, vtkIndent indent) override;

  vtkSetMacro(ArrowScale, double);
  vtkGetMacro(ArrowScale, double);

  vtkSetMacro(ReductionFactor, double);
  vtkGetMacro(ReductionFactor, double);

  /**
   * Get the intermediate points in this contour as a vtkPolyData
   */
  void GetIntermediatePolyData(vtkPolyData* poly);

  void BuildRepresentation() override;

  ///@{
  /**
   */
  vtkGetObjectMacro(NormalsProperty, vtkProperty);
  vtkGetObjectMacro(BinormalsProperty, vtkProperty);
  vtkGetObjectMacro(TangentsProperty, vtkProperty);
  ///@}

  ///@{
  /**
   * Methods to make this class behave as a vtkProp.
   */
  void GetActors(vtkPropCollection*) override;
  void ReleaseGraphicsResources(vtkWindow*) override;
  int RenderOverlay(vtkViewport* viewport) override;
  int RenderOpaqueGeometry(vtkViewport* viewport) override;
  int RenderTranslucentPolygonalGeometry(vtkViewport* viewport) override;
  vtkTypeBool HasTranslucentPolygonalGeometry() override;
  ///@}

  void TNBCallback(vtkObject* caller, int ev);

  void TNBCallback(vtkObject* caller, const char* evString);

  void SetPolyData(vtkPolyData* polyData);

  void GetTransformAtIndex(vtkIdType index, vtkTransform* transform);

  class VTKINTERACTIONWIDGETS_EXPORT vtkInternals;

protected:
  double ArrowScale;
  double ReductionFactor;
  vtkActor* NormalsActor;
  vtkActor* TangentsActor;
  vtkActor* BinormalsActor;
  vtkPolyData* Normals;
  vtkPolyData* Tangents;
  vtkPolyData* Binormals;
  vtkGlyph3DMapper* NormalsMapper;
  vtkGlyph3DMapper* TangentsMapper;
  vtkGlyph3DMapper* BinormalsMapper;
  vtkProperty* NormalsProperty;
  vtkProperty* BinormalsProperty;
  vtkProperty* TangentsProperty;
  vtkTNB* Frame;
  vtkCellLocator* CellLocator;
  vtkPolyData* PolyData;
  vtkArrowSource* ArrowSource;

  vtkInternals* Internals;
  friend class vtkInternals;

private:
  vtkOrientedGlyphContourRepresentationEx();
  ~vtkOrientedGlyphContourRepresentationEx();
};

and the code here

#include <vtkCallbackCommand.h>
#include <vtkCellArray.h>
#include <vtkOrientedGlyphContourRepresentationEx.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>

// Option for interpolation, coarse/fine, show/hide, orientation for an index

// Test TNB Observer
#include <vtkArrowSource.h>
#include <vtkCellData.h>
#include <vtkCellLocator.h>
#include <vtkContourWidget.h>
#include <vtkGlyph3DMapper.h>
#include <vtkMaskPoints.h>
#include <vtkMatrix4x4.h>
#include <vtkPointData.h>
#include <vtkPointPlacer.h>
#include <vtkPolyDataCollection.h>
#include <vtkPolygonalSurfaceContourLineInterpolator.h>
#include <vtkPolygonalSurfacePointPlacer.h> // Is this used?
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkSurfacePathInterpolator.h> // Using Dijkstra
#include <vtkTNB.h>
#include <vtkTransform.h>
#include <vtkVector.h>

vtkStandardNewMacro(vtkOrientedGlyphContourRepresentationEx);

class vtkOrientedGlyphContourRepresentationEx::vtkInternals
{
public:
  vtkInternals(vtkOrientedGlyphContourRepresentationEx* self)
    : Self(self)
  {
  }
  vtkOrientedGlyphContourRepresentationEx* Self;
  void Execute(vtkObject* caller, unsigned long vtkNotUsed(evId))
  {
    vtkContourWidget* cw = vtkContourWidget::SafeDownCast(caller);
    if (cw)
    {
      auto rep = vtkOrientedGlyphContourRepresentationEx::SafeDownCast(cw->GetRepresentation());
      if (rep)
      {
        // Cell normals are required!!!
        vtkPolyData* surfacePolyData = vtkPolyData::SafeDownCast(Self->PolyData);
        if (!surfacePolyData)
        {
          std::cerr << "No polydata associated with representation\n";
          return;
        }

        if (!surfacePolyData->GetCellData()->GetNormals())
        {
          std::cerr << "No cell normals defined for polydata\n";
          return;
        }
        vtkNew<vtkPolyData> pd;
        rep->GetIntermediatePolyData(pd);
        if (pd->GetNumberOfPoints() < 2)
        {
          return;
        }

        // Reset orientation
        Self->Frame->SetViewUp(0.0);
        Self->Frame->SetInputData(pd);

        // Input either masked or not
        vtkPolyDataAlgorithm* pipeLineInput;

        vtkNew<vtkMaskPoints> maskPoints;
        maskPoints->SetInputConnection(Self->Frame->GetOutputPort());

        int everyNth = Self->ReductionFactor == 0.0 ? 1.0 : int(1.0 / Self->ReductionFactor);

        maskPoints->SetOnRatio(everyNth);
        maskPoints->Update();

        // Masked input - every nth point
        pipeLineInput = maskPoints.GetPointer();

        // Tangent, normal and binormal for the first point.
        double *pNormal, *pTangent, *pBiNormal;

        pNormal = pipeLineInput->GetOutput()->GetPointData()->GetArray("FSNormals")->GetTuple(0);
        pBiNormal =
          pipeLineInput->GetOutput()->GetPointData()->GetArray("FSBinormals")->GetTuple(0);
        pTangent = pipeLineInput->GetOutput()->GetPointData()->GetArray("FSTangents")->GetTuple(0);

        double cellNormal[3];

        // Locate a normal on the surface near the first point
        vtkIdType cellId;
        int subId;
        double closestPoint[3];
        double distance = 0.0;

        Self->CellLocator->FindClosestPoint(pd->GetPoint(0), closestPoint, cellId, subId, distance);
        surfacePolyData->GetCellData()->GetNormals()->GetTuple(cellId, cellNormal);

        // Project cellNormal vector into the NxB plane and adjust N to align with cellNormal
        double UdotT = vtkMath::Dot(cellNormal, pTangent);
        for (int i = 0; i < 3; i++)
        {
          cellNormal[i] = cellNormal[i] - UdotT * pTangent[i];
        }

        vtkVector3d cellNormalMinusT = vtkVector3d(cellNormal);
        cellNormalMinusT.Normalize();

        double UdotN = vtkMath::Dot(cellNormalMinusT.GetData(), pNormal);
        double UdotB = vtkMath::Dot(cellNormalMinusT.GetData(), pBiNormal);

        // Angle in radians used for adjustment
        double angleAdjust = std::atan2(UdotB, UdotN);
        Self->Frame->SetViewUp(angleAdjust);

        pipeLineInput->SetInputConnection(Self->Frame->GetOutputPort());

        pipeLineInput->Update();

        // Normals
        pipeLineInput->GetOutput()->GetPointData()->SetActiveVectors("FSNormals");
        Self->Normals->DeepCopy(pipeLineInput->GetOutput());

        Self->NormalsMapper->SetInputData(Self->Normals);
        Self->NormalsMapper->SetScaleFactor(Self->ArrowScale);
        Self->NormalsMapper->SetSourceConnection(Self->ArrowSource->GetOutputPort());
        Self->NormalsMapper->Update();

        // Tangents
        pipeLineInput->GetOutput()->GetPointData()->SetActiveVectors("FSTangents");
        Self->Tangents->DeepCopy(pipeLineInput->GetOutput());
        Self->TangentsMapper->SetInputData(Self->Tangents);
        Self->TangentsMapper->SetScaleFactor(Self->ArrowScale);
        Self->TangentsMapper->SetSourceConnection(Self->ArrowSource->GetOutputPort());
        Self->TangentsMapper->Update();

        // Bi-normals
        pipeLineInput->GetOutput()->GetPointData()->SetActiveVectors("FSBinormals");
        Self->Binormals->DeepCopy(pipeLineInput->GetOutput());
        Self->BinormalsMapper->SetInputData(pipeLineInput->GetOutput());
        Self->BinormalsMapper->SetScaleFactor(Self->ArrowScale);
        Self->BinormalsMapper->SetSourceConnection(Self->ArrowSource->GetOutputPort());
        Self->BinormalsMapper->Update();
      }
    }
  }

private:
  double ReductionFactor;
};

void vtkOrientedGlyphContourRepresentationEx::SetPolyData(vtkPolyData* polydata)
{
  if (this->PolyData == polydata)
  {
    return;
  }
  if (this->PolyData)
  {
    this->PolyData->UnRegister(this);
  }
  this->PolyData = polydata;

  if (this->PolyData)
  {
    this->PolyData->Register(this);
    this->CellLocator->SetDataSet(this->PolyData);
    this->CellLocator->BuildLocator();
  }
}

//------------------------------------------------------------------------------
vtkOrientedGlyphContourRepresentationEx::vtkOrientedGlyphContourRepresentationEx()
{
  this->ArrowScale = 1.0;
  this->ReductionFactor = 0.0;
  this->Normals = vtkPolyData::New();
  this->NormalsMapper = vtkGlyph3DMapper::New();
  this->NormalsActor = vtkActor::New();
  this->NormalsActor->SetMapper(this->NormalsMapper);

  this->NormalsProperty = vtkProperty::New();
  this->NormalsProperty->SetColor(1.0, 0.0, 0.0);
  this->NormalsProperty->SetRepresentationToSurface();
  this->NormalsProperty->SetAmbient(1.0);
  this->NormalsProperty->SetDiffuse(0.0);
  this->NormalsProperty->SetSpecular(0.0);
  this->NormalsProperty->SetLineWidth(1.0);

  this->NormalsActor->SetProperty(this->NormalsProperty);

  this->Tangents = vtkPolyData::New();
  this->TangentsMapper = vtkGlyph3DMapper::New();
  this->TangentsActor = vtkActor::New();
  this->TangentsActor->SetMapper(this->TangentsMapper);

  this->TangentsProperty = vtkProperty::New();
  this->TangentsProperty->SetColor(0.0, 1.0, 0.0);
  this->TangentsProperty->SetRepresentationToSurface();
  this->TangentsProperty->SetAmbient(1.0);
  this->TangentsProperty->SetDiffuse(0.0);
  this->TangentsProperty->SetSpecular(0.0);
  this->TangentsProperty->SetLineWidth(1.0);

  this->TangentsActor->SetProperty(this->TangentsProperty);

  this->Binormals = vtkPolyData::New();
  this->BinormalsMapper = vtkGlyph3DMapper::New();
  this->BinormalsActor = vtkActor::New();
  this->BinormalsActor->SetMapper(this->BinormalsMapper);

  this->BinormalsProperty = vtkProperty::New();
  this->BinormalsProperty->SetColor(0.0, 0.0, 1.0);
  this->BinormalsProperty->SetRepresentationToSurface();
  this->BinormalsProperty->SetAmbient(1.0);
  this->BinormalsProperty->SetDiffuse(0.0);
  this->BinormalsProperty->SetSpecular(0.0);
  this->BinormalsProperty->SetLineWidth(1.0);

  this->BinormalsActor->SetProperty(this->BinormalsProperty);

  this->Frame = vtkTNB::New();
  this->Frame->ConsistentNormalsOn();
  this->CellLocator = vtkCellLocator::New();
  this->PolyData = nullptr;
  this->ArrowSource = vtkArrowSource::New();
  this->ArrowSource->SetTipResolution(16);
  this->ArrowSource->SetTipLength(.3);
  this->ArrowSource->SetTipRadius(.1);

  this->Internals = new vtkInternals(this);
}

//------------------------------------------------------------------------------
vtkOrientedGlyphContourRepresentationEx::~vtkOrientedGlyphContourRepresentationEx()
{
  this->Normals->Delete();
  this->NormalsMapper->Delete();
  this->NormalsActor->Delete();

  this->Tangents->Delete();
  this->TangentsMapper->Delete();
  this->TangentsActor->Delete();
  this->TangentsProperty->Delete();

  this->Binormals->Delete();
  this->BinormalsMapper->Delete();
  this->BinormalsActor->Delete();
  this->BinormalsProperty->Delete();

  this->ArrowSource->Delete();
  this->Frame->Delete();
  this->CellLocator->Delete();

  this->NormalsProperty->Delete();
  this->SetPolyData(nullptr);

  if (this->Internals)
  {
    delete this->Internals;
    this->Internals = nullptr;
  }
}

//------------------------------------------------------------------------------
void vtkOrientedGlyphContourRepresentationEx::PrintSelf(std::ostream& os, vtkIndent indent)
{
  Superclass::PrintSelf(os, indent);
  if (this->NormalsProperty)
  {
    os << indent << "Normals Property: " << this->NormalsProperty << "\n";
  }
  else
  {
    os << indent << "Normals Property: (none)\n";
  }
  if (this->BinormalsProperty)
  {
    os << indent << "Binormals Property: " << this->BinormalsProperty << "\n";
  }
  else
  {
    os << indent << "Binormals Property: (none)\n";
  }
  if (this->TangentsProperty)
  {
    os << indent << "Tangents Property: " << this->TangentsProperty << "\n";
  }
  else
  {
    os << indent << "Tangents Property: (none)\n";
  }
}

// vtkCommand* vtkOrientedGlyphContourRepresentationEx::GetTNBObserver()

//------------------------------------------------------------------------------
void vtkOrientedGlyphContourRepresentationEx::GetIntermediatePolyData(vtkPolyData* poly)
{
  poly->Initialize();
  int count = this->GetNumberOfNodes();

  if (count == 0)
  {
    return;
  }

  vtkNew<vtkPoints> points;
  vtkNew<vtkCellArray> lines;

  int i, j;
  vtkIdType index = 0;

  count = 0;
  for (i = 0; i < this->GetNumberOfNodes(); i++)
  {
    count += this->GetNumberOfIntermediatePoints(i);
  }
  points->SetNumberOfPoints(count);

  vtkIdType numLines;

  if (this->ClosedLoop && count > 0)
  {
    numLines = count + 1;
  }
  else
  {
    numLines = count;
  }

  if (numLines > 0)
  {
    vtkIdType* lineIndices = new vtkIdType[numLines];

    double pos[3];
    for (i = 0; i < this->GetNumberOfNodes(); i++)
    {
      int numIntermediatePoints = this->GetNumberOfIntermediatePoints(i);

      for (j = 0; j < numIntermediatePoints; j++)
      {
        Superclass::GetIntermediatePointWorldPosition(i, j, pos);
        points->InsertPoint(index, pos);
        if (index < numLines)
        {
          lineIndices[index] = index;
        }
        index++;
      }
    }

    if (Superclass::ClosedLoop)
    {
      if (index < numLines)
        lineIndices[index] = 0;
    }

    lines->InsertNextCell(numLines, lineIndices);
    delete[] lineIndices;
  }

  poly->SetPoints(points);
  poly->SetLines(lines);
}

void vtkOrientedGlyphContourRepresentationEx::TNBCallback(vtkObject* caller, int ev)
{
  this->Internals->Execute(caller, ev);
}

void vtkOrientedGlyphContourRepresentationEx::TNBCallback(vtkObject* caller, const char* evString)
{
  this->TNBCallback(caller, vtkCommand::GetEventIdFromString(evString));
}

void vtkOrientedGlyphContourRepresentationEx::BuildRepresentation()
{
  Superclass::BuildRepresentation();
}

int vtkOrientedGlyphContourRepresentationEx::RenderTranslucentPolygonalGeometry(
  vtkViewport* viewport)
{
  int count = Superclass::RenderTranslucentPolygonalGeometry(viewport);
  count += this->NormalsActor->RenderTranslucentPolygonalGeometry(viewport);
  count += this->BinormalsActor->RenderTranslucentPolygonalGeometry(viewport);
  count += this->TangentsActor->RenderTranslucentPolygonalGeometry(viewport);
  return count;
}

vtkTypeBool vtkOrientedGlyphContourRepresentationEx::HasTranslucentPolygonalGeometry()
{
  int result = Superclass::HasTranslucentPolygonalGeometry();
  if (this->NormalsActor->GetVisibility())
  {
    result |= this->NormalsActor->HasTranslucentPolygonalGeometry();
  }
  if (this->BinormalsActor->GetVisibility())
  {
    result |= this->BinormalsActor->HasTranslucentPolygonalGeometry();
  }
  if (this->TangentsActor->GetVisibility())
  {
    result |= this->TangentsActor->HasTranslucentPolygonalGeometry();
  }
  return result;
}

void vtkOrientedGlyphContourRepresentationEx::GetActors(vtkPropCollection* pc)
{
  Superclass::GetActors(pc);
  this->TangentsActor->GetActors(pc);
  this->NormalsActor->GetActors(pc);
  this->BinormalsActor->GetActors(pc);
}

int vtkOrientedGlyphContourRepresentationEx::RenderOverlay(vtkViewport* viewport)
{
  int count = Superclass::RenderOverlay(viewport);
  count += this->NormalsActor->RenderOverlay(viewport);
  count += this->BinormalsActor->RenderOverlay(viewport);
  count += this->TangentsActor->RenderOverlay(viewport);
  return count;
}

int vtkOrientedGlyphContourRepresentationEx::RenderOpaqueGeometry(vtkViewport* viewport)
{
  int count = Superclass::RenderOpaqueGeometry(viewport);
  count += this->NormalsActor->RenderOpaqueGeometry(viewport);
  count += this->BinormalsActor->RenderOpaqueGeometry(viewport);
  count += this->TangentsActor->RenderOpaqueGeometry(viewport);
  return count;
}

void vtkOrientedGlyphContourRepresentationEx::ReleaseGraphicsResources(vtkWindow* win)
{
  Superclass::ReleaseGraphicsResources(win);
  this->NormalsActor->ReleaseGraphicsResources(win);
  this->BinormalsActor->ReleaseGraphicsResources(win);
  this->TangentsActor->ReleaseGraphicsResources(win);
}

void vtkOrientedGlyphContourRepresentationEx::GetTransformAtIndex(
  vtkIdType index, vtkTransform* transform)
{
  vtkIdType nTransforms = this->Binormals->GetNumberOfPoints();
  if (index < nTransforms)
  {
    double* pBinormal = this->Binormals->GetPointData()->GetArray("FSBinormals")->GetTuple(index);
    double* pNormal = this->Binormals->GetPointData()->GetArray("FSNormals")->GetTuple(index);
    double* pTangent = this->Binormals->GetPointData()->GetArray("FSTangents")->GetTuple(index);
    double* pPosition = this->Binormals->GetPoint(index);

    vtkNew<vtkMatrix4x4> matrix;
    matrix->Identity();
    matrix->SetElement(0, 0, pBinormal[0]);
    matrix->SetElement(0, 1, pTangent[0]);
    matrix->SetElement(0, 2, pNormal[0]);
    matrix->SetElement(1, 0, pBinormal[1]);
    matrix->SetElement(1, 1, pTangent[1]);
    matrix->SetElement(1, 2, pNormal[1]);
    matrix->SetElement(2, 0, pBinormal[2]);
    matrix->SetElement(2, 1, pTangent[2]);
    matrix->SetElement(2, 2, pNormal[2]);
    matrix->SetElement(0, 3, pPosition[0]);
    matrix->SetElement(1, 3, pPosition[1]);
    matrix->SetElement(2, 3, pPosition[2]);

    transform->SetMatrix(matrix);
  }
}
1 Like

Hi Jens~
The code style looks elegant and I believe the extension is truly helpful for me to calculate the swept volume of the machine tool. I always learn a lot here!
Oops Happy holidays!
Thanks again Jens.
Best regards.