Jump to content

Programming Reference:ComplexVisualizationDemo Signal Processing: Difference between revisions

From BCI2000 Wiki
Mellinger (talk | contribs)
Mellinger (talk | contribs)
 
(8 intermediate revisions by the same user not shown)
Line 11: Line 11:
The ''ComplexVisualizationDemoFilter'' computes pairwise determination coefficients (squared correlation, <math>r^2</math> values) between its input channels. Determination coefficients are visualized in form of pie charts, and pie charts are sent to the operator module as bitmap visualization data.
The ''ComplexVisualizationDemoFilter'' computes pairwise determination coefficients (squared correlation, <math>r^2</math> values) between its input channels. Determination coefficients are visualized in form of pie charts, and pie charts are sent to the operator module as bitmap visualization data.


Operator visualization windows have no frames or title bars, and are arranged in form of a lower triangular matrix, with each window appearing at the place of its associated correlation matrix element:
Operator visualization windows have no frames or title bars, and are arranged in form of a triangular matrix, with each window appearing at the place of its associated correlation matrix element:
 
[[File:ComplexVisualizationDemo.PNG]]
[[File:ComplexVisualizationDemo.PNG]]


Line 38: Line 39:
   // Destroys all visualization objects.
   // Destroys all visualization objects.
   void destroyVisualizations();
   void destroyVisualizations();
   // Asynchronously sets visualizations to their initial state.
   // Resets visualizations to their initial state.
   void resetVisualizations();
   void resetVisualizations();
   // Computes data values, and asynchronously updates visualization bitmaps.
   // Computes data values, and asynchronously updates visualization bitmaps.
Line 51: Line 52:
     ~VisualizationObject();
     ~VisualizationObject();
     void setTitle(const std::string&, const std::string& info = "");
     void setTitle(const std::string&, const std::string& info = "");
     void setPosition(const GUI::Rect&);
     void setPosition(int row, int col, int rowSpan = 1, int colSpan = 1);
     void reset();
     void reset();
     void update(const GenericSignal&);
     void update(const GenericSignal&);
Line 65: Line 66:


   private:
   private:
    void asyncReset();
     void asyncUpdate();
     void asyncUpdate();


    MemberCall<void(VisualizationObject*)> mCallAsyncReset;
     MemberCall<void(VisualizationObject*)> mCallAsyncUpdate;
     MemberCall<void(VisualizationObject*)> mCallAsyncUpdate;


Line 83: Line 82:
</syntaxhighlight>
</syntaxhighlight>


===<tt>VisualizationObject::reset()</tt> and <tt>VisualizationObject::update()</tt>===
===<tt>VisualizationObject::update()</tt>===
These functions run <tt>VisualizationObject::asyncReset()</tt> and <tt>VisualizationObject::asyncUpdate()</tt> inside the object's worker thread in order to avoid interference with timing of the main BCI2000 thread.
This function runs <tt>VisualizationObject::asyncUpdate()</tt> inside the object's worker thread in order to avoid interference with timing of the main BCI2000 thread (pipeline thread).
<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
void
ComplexVisualizationDemoFilter::Private::VisualizationObject::reset()
{
  if(!mWorker.Run(mCallAsyncReset))
    throw bcierr << "cannot initialize: worker is busy or dead";
}
void
void
ComplexVisualizationDemoFilter::Private::VisualizationObject::update(const GenericSignal& Input)
ComplexVisualizationDemoFilter::Private::VisualizationObject::update(const GenericSignal& Input)
{
{
   mComputation.run(Input);
   mComputation.run(Input);
   mWorker.Run(mCallAsyncUpdate); // will fail if worker still busy
   mWorker.Run(mCallAsyncUpdate); // will fail silently if worker still busy
}
}
</syntaxhighlight>
</syntaxhighlight>


===<tt>VisualizationObject::asyncReset()</tt>===
===<tt>VisualizationObject::reset()</tt>===
This function resets the visualization window's position and size before sending an empty reference frame to the operator.
This function resets the visualization window's position and size before sending an empty reference frame to the operator.
<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
void
void
ComplexVisualizationDemoFilter::Private::VisualizationObject::asyncReset()
ComplexVisualizationDemoFilter::Private::VisualizationObject::reset()
{
{
   // reset position and size
   // place window into a container window named "CPLX" (will be created implicitly)
  mVis.Send(CfgID::Left, mPosition.left);
   mVis.Send(CfgID::PlacementVis, "CPLX");
   mVis.Send(CfgID::Top, mPosition.top);
   mVis.Send(CfgID::PlacementRow, mRow);
   mVis.Send(CfgID::Width, mPosition.Width());
   mVis.Send(CfgID::PlacementCol, mCol);
   mVis.Send(CfgID::Height, mPosition.Height());
   mVis.Send(CfgID::WindowFrame, false); // hide title
   mVis.Send(CfgID::WindowTitle, mTitle);
  // setting CfgID::WindowFrame to false will hide the window's title bar and frame
  mVis.Send(CfgID::WindowFrame, false);
   mVis.Send(CfgID::Visible, true);
   mVis.Send(CfgID::Visible, true);


Line 211: Line 200:
The images' background color, in hexadecimal notation.
The images' background color, in hexadecimal notation.
===VisImageDecimation===
===VisImageDecimation===
A positive integer that indicates how often images are refreshed. 1 means every refresh on every signal packet.
A positive integer that indicates how often images are refreshed. 1 means refresh on every signal packet.
 
===VisMaxWindows===
===VisMaxWindows===
The maximum number of visualization windows created, or ''0'' for any number of windows.
The maximum number of visualization windows created, or ''0'' for any number of windows.


==See also==
==See also==
[[Programming Reference:GraphDisplay Class]], [[Programming Reference:GenericVisualization Class]], [[Programming Reference:VisualizationDemo Signal Processing]]
[[Programming Reference:GraphDisplay Class]], [[Programming Reference:GenericVisualization Class]], [[Programming Reference:VisualizationDemo Signal Processing]], [[Programming Reference:VisualizationContainerDemo Signal Processing]], [[Technical Reference:Visualization Properties]]


[[Category:Framework API]][[Category:Howto]][[Category:Development]]
[[Category:Framework API]][[Category:Howto]][[Category:Development]]

Latest revision as of 16:30, 14 June 2023

Location

src/contrib/SignalProcessing/ComplexVisualizationDemo

Synopsis

The ComplexVisualizationDemo signal processing module demonstrates how to send a large number of visualizations with arbitrary pixel content to the operator module. Two rendering methods are provided: Native Qt QPainter-based rendering, and BCI2000's own GraphDisplay based rendering.

Inheritance

The ComplexVisualizationDemoFilter signal processing filter derives from GenericFilter.

Function

The ComplexVisualizationDemoFilter computes pairwise determination coefficients (squared correlation, r2 values) between its input channels. Determination coefficients are visualized in form of pie charts, and pie charts are sent to the operator module as bitmap visualization data.

Operator visualization windows have no frames or title bars, and are arranged in form of a triangular matrix, with each window appearing at the place of its associated correlation matrix element:

Implementation

Each visualization window is implemented as a VisualizationObject that contains a BitmapVisualization object, a WorkerThread object, and a Computation object. Whenever a new block of data arrives, computation is done for the individual window's pair of channels in the main thread. Then, visualization bitmap data are produced and sent to the operator module inside the worker thread, to avoid blocking the main thread. Inside the worker thread, the BitmapVisualization's SendDifferenceFrame() function is called to update the visualization display in the operator module.

Declaration of internal variables

The code example uses a pointer to an internal private struct to hide implementation details from the outer header file of the filter class (PIMPL idiom).

A VisualizationObject class is declared that contains all members necessary to draw to an individual visualization window, and a WorkerThread instance that runs code to send visualization data to the operator in a separate thread in order to avoid interference with the timing of the main BCI2000 thread.

struct ComplexVisualizationDemoFilter::Private
{
  RGBColor mBackground;
  int mHeight, mWidth, mDecimation;
  int mDecimationCounter;

  class VisualizationObject;
  std::vector<VisualizationObject*> mVisualizations;

  ~Private() { destroyVisualizations(); }
  // Creates visualization objects for pairs of channels.
  void createVisualizations(const SignalProperties&, int maxWindows);
  // Destroys all visualization objects.
  void destroyVisualizations();
  // Resets visualizations to their initial state.
  void resetVisualizations();
  // Computes data values, and asynchronously updates visualization bitmaps.
  void updateVisualizations(const GenericSignal&);
  // Waits for asynchronous activity in all visualizations to terminate.
  void waitForVisualizations();

  class VisualizationObject
  {
  public:
    VisualizationObject(const Private*, const std::string& visID);
    ~VisualizationObject();
    void setTitle(const std::string&, const std::string& info = "");
    void setPosition(int row, int col, int rowSpan = 1, int colSpan = 1);
    void reset();
    void update(const GenericSignal&);
    void wait();

    struct Computation
    {
      int inputCh1, inputCh2;
      double result;
      // run() is called whenever a new signal arrives.
      void run(const GenericSignal&);
    } mComputation;

  private:
    void asyncUpdate();

    MemberCall<void(VisualizationObject*)> mCallAsyncUpdate;

    const Private* p;
    WorkerThread mWorker;
    BitmapVisualization mVis;
    std::string mTitle, mInfo;
    GUI::Rect mPosition;
    GUI::GraphDisplay mImage;
    PieShape* mpShape;
    TextField* mpValueField, *mpInfoField;
  };
};

VisualizationObject::update()

This function runs VisualizationObject::asyncUpdate() inside the object's worker thread in order to avoid interference with timing of the main BCI2000 thread (pipeline thread).

void
ComplexVisualizationDemoFilter::Private::VisualizationObject::update(const GenericSignal& Input)
{
  mComputation.run(Input);
  mWorker.Run(mCallAsyncUpdate); // will fail silently if worker still busy
}

VisualizationObject::reset()

This function resets the visualization window's position and size before sending an empty reference frame to the operator.

void
ComplexVisualizationDemoFilter::Private::VisualizationObject::reset()
{
  // place window into a container window named "CPLX" (will be created implicitly)
  mVis.Send(CfgID::PlacementVis, "CPLX");
  mVis.Send(CfgID::PlacementRow, mRow);
  mVis.Send(CfgID::PlacementCol, mCol);
  mVis.Send(CfgID::WindowFrame, false); // hide title
  mVis.Send(CfgID::Visible, true);

  // paint an empty image
  mpInfoField->SetText(mInfo);
  mpInfoField->SetVisible(true);
  mpValueField->SetVisible(false);
  mpShape->SetVisible(false);
  mImage.SetColor(p->mBackground);
  mImage.Paint();
  mVis.SendReferenceFrame(mImage.BitmapData());
  mpShape->SetVisible(true);
  mpValueField->SetVisible(true);
  mpInfoField->SetVisible(false);
}

VisualizationObject::asyncUpdate()

The asyncUpdate() function gets the computation result from the computation object, and draws a pie shape with an angle that corresponds to the result. It then sends the resulting image to the operator module as a difference frame.

void
ComplexVisualizationDemoFilter::Private::VisualizationObject::asyncUpdate()
{
  double value = mComputation.result;

  std::ostringstream oss;
  oss << std::setprecision(2) << std::fixed << value;
  mpValueField->SetText(oss.str());

  // Draw a pie shape that is full angle when value == 1, and that reduces to a line when value == 0.
  float angle = 270;
  if(value == value) // not NaN
    angle = 360 * value;
  mpShape->SetStartAngle(180 - angle/2).SetEndAngle(180 + angle/2);
  // render the image
  mImage.Paint();
  // send bitmap data to operator
  mVis.SendDifferenceFrame(mImage.BitmapData());
}

Process() function

The Process() function handles the test of the decimation counter, and calls updateVisualizations() if necessary. updateVisualizations(), in turn, calls VisualizationObject::update() for each visualization object contained in mVisualizations.

void ComplexVisualizationDemoFilter::Process(
  const GenericSignal& Input,
        GenericSignal& Output)
{
  Output = Input;
  if(p->mDecimationCounter == 0)
    p->updateVisualizations(Input);
  ++p->mDecimationCounter %= p->mDecimation;
}

StartRun() and StopRun()

These reset the decimation counter, and indirectly call VisualizationObject::reset(). The waitForVisualizations() function in StartRun() waits for all WorkerThread tasks to complete before actually re-starting visualization activity.

void ComplexVisualizationDemoFilter::StartRun()
{
  p->mDecimationCounter = 0;
  p->waitForVisualizations();
  p->resetVisualizations();
}

void ComplexVisualizationDemoFilter::StopRun()
{
  p->waitForVisualizations();
  p->resetVisualizations();
}

BCI2000 GraphDisplay vs. QPainter rendering

The code presented here is using the BCI2000 GraphDisplay class.

GraphDisplay is a layer of abstraction that allows to render shapes and text objects into a normalized coordinate system. Code that uses GraphDisplay is most likely to survive breaking changes in the drawing backend (currently Qt) and BCI2000 dependencies.

In contrast, QPainter-based rendering provides access to more complex drawing functions but suffers from a limitation in Qt which makes text rendering impossible outside the main GUI thread.

In the ComplexVisualizationDemo source code, QPainter-based rendering is available through a compiler switch.

Parameters

VisImageWidth

Native image width in pixels.

VisImageHeight

Native image height in pixels.

VisImageBackground

The images' background color, in hexadecimal notation.

VisImageDecimation

A positive integer that indicates how often images are refreshed. 1 means refresh on every signal packet.

VisMaxWindows

The maximum number of visualization windows created, or 0 for any number of windows.

See also

Programming Reference:GraphDisplay Class, Programming Reference:GenericVisualization Class, Programming Reference:VisualizationDemo Signal Processing, Programming Reference:VisualizationContainerDemo Signal Processing, Technical Reference:Visualization Properties