Jump to content

Programming Reference:VisualizationContainerDemo Signal Processing: Difference between revisions

From BCI2000 Wiki
Mellinger (talk | contribs)
Created page with "==Location== <tt>src/contrib/SignalProcessing/VisualizationContainerDemo</tt> ==Synopsis== The ''VisualizationContainerDemo'' signal processing module demonstrates how to mai..."
 
Mellinger (talk | contribs)
 
(9 intermediate revisions by the same user not shown)
Line 16: Line 16:


==Implementation==
==Implementation==
Each visualization window is implemented as a ''VisualizationObject'' that contains a ''Visualization'' 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, the result is sent to the Operator window inside a ''GenericSignal'' object. As both computation effort and the amount of data is limited, and to keep the example code simple, no multithreading is involved in this demo. (For an example that involves multithreading, see [[Programming Reference:ComplexVisualizationDemo Signal Processing]].
Each visualization window is implemented as a ''VisualizationObject'' that contains a ''Visualization'' 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, the result is sent to the Operator window inside a ''GenericSignal'' object. As both computation effort and the amount of data is limited, and to keep the example code simple, no multithreading is involved in this demo. (For an example that involves multithreading, see [[Programming Reference:ComplexVisualizationDemo Signal Processing]].)


===Declaration of internal variables===
===Declaration of internal variables===
Line 26: Line 26:
struct VisualizationContainerDemoFilter::Private
struct VisualizationContainerDemoFilter::Private
{
{
    GenericVisualization mContainerVis;


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


  class VisualizationObject;
    ~Private()
  std::vector<VisualizationObject*> mVisualizations;
    {
        destroyVisualizations();
    }
    // Creates visualization objects for pairs of channels.
    void createVisualizations(const SignalProperties &);
    // Destroys all visualization objects.
    void destroyVisualizations();
    // Sets visualizations to their initial state.
    void resetVisualizations();
    // Computes data values, and updates visualization windows.
    void updateVisualizations(const GenericSignal &);


  ~Private() { destroyVisualizations(); }
    class VisualizationObject
  // 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;
       public:
      double result;
        VisualizationObject(const std::string &visID);
      // run() is called whenever a new signal arrives.
        ~VisualizationObject();
      void run(const GenericSignal&);
        void setTitle(const std::string &);
    } mComputation;
        void configureSignal(const SignalProperties&);
        void setPosition(int row, int col);
        void reset();
        void update(const GenericSignal &);


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


    MemberCall<void(VisualizationObject*)> mCallAsyncUpdate;
      private:
 
        GenericVisualization mVis;
    const Private* p;
        GenericSignal mSignal;
    WorkerThread mWorker;
        PhysicalUnit mSampleUnit, mValueUnit;
    BitmapVisualization mVis;
        std::string mTitle;
    std::string mTitle, mInfo;
        int mRow, mCol;
    GUI::Rect mPosition;
     };
     GUI::GraphDisplay mImage;
    PieShape* mpShape;
    TextField* mpValueField, *mpInfoField;
  };
};
};
</syntaxhighlight>
</syntaxhighlight>


===<tt>VisualizationObject::update()</tt>===
===<tt>VisualizationObject::update()</tt>===
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).
This function computes the correlation matrix element, and sends it to the visualization display wrapped into a ''GenericSignal'' object.
<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
void
void
ComplexVisualizationDemoFilter::Private::VisualizationObject::update(const GenericSignal& Input)
VisualizationContainerDemoFilter::Private::VisualizationObject::update(const GenericSignal& Input)
{
{
  mComputation.run(Input);
    mComputation.run(Input);
  mWorker.Run(mCallAsyncUpdate); // will fail silently if worker still busy
    mSignal(0, 0) = mComputation.result;
    mVis.Send(mSignal);
}
}
</syntaxhighlight>
</syntaxhighlight>


===<tt>VisualizationObject::reset()</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 signal to the Operator
<syntaxhighlight lang="cpp">
(this will enforce creation of the visualization display on the Operator side).
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);
}
</syntaxhighlight>
 
===<tt>VisualizationObject::asyncUpdate()</tt>===
The <tt>asyncUpdate()</tt> 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.
<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
void
void
ComplexVisualizationDemoFilter::Private::VisualizationObject::asyncUpdate()
VisualizationContainerDemoFilter::Private::VisualizationObject::reset()
{
{
  double value = mComputation.result;
    // reset position and size
 
    mVis.Send(CfgID::PlacementVis, "CNTR");
  std::ostringstream oss;
    mVis.Send(CfgID::PlacementRow, mRow + 1); // rows are one-based
  oss << std::setprecision(2) << std::fixed << value;
    mVis.Send(CfgID::PlacementCol, mCol + 1); // columns are one-based
  mpValueField->SetText(oss.str());
    mVis.Send(CfgID::Visible, true);
 
    mVis.Send(CfgID::FixedScale, true);
  // Draw a pie shape that is full angle when value == 1, and that reduces to a line when value == 0.
    mVis.Send(CfgID::MinValue, mValueUnit.RawMin());
  float angle = 270;
    mVis.Send(CfgID::MaxValue, mValueUnit.RawMax() * 1.1);
  if(value == value) // not NaN
    mVis.Send(CfgID::ShowStatusBar, false);
     angle = 360 * value;
    mVis.Send(CfgID::SampleUnit, mSampleUnit.RawToPhysical(1));
  mpShape->SetStartAngle(180 - angle/2).SetEndAngle(180 + angle/2);
    mVis.Send(CfgID::ShowYTicks, false);
  // render the image
     LabelList markers;
  mImage.Paint();
    markers.push_back(Label(mValueUnit.PhysicalToRaw("1"), "1|red|-|0.5"));
  // send bitmap data to operator
    markers.push_back(Label(mValueUnit.PhysicalToRaw("0.5"), "0.5|green|-.|0.5"));
  mVis.SendDifferenceFrame(mImage.BitmapData());
    mVis.Send(CfgID::YAxisMarkers, markers);
    mVis.Send(CfgID::WindowTitle, mTitle);
    mVis.Send(CfgID::WindowFrame, true);
    mVis.Send(GenericSignal());
}
}
</syntaxhighlight>
</syntaxhighlight>


===<tt>Process()</tt> function===
===<tt>Process()</tt> function===
The <tt>Process()</tt> function handles the test of the decimation counter, and calls <tt>updateVisualizations()</tt> if necessary. <tt>updateVisualizations()</tt>, in turn, calls <tt>VisualizationObject::update()</tt> for each visualization object contained in <tt>mVisualizations</tt>.
The <tt>Process()</tt> function calls <tt>updateVisualizations()</tt>, which, in turn, will call <tt>VisualizationObject::update()</tt> for each visualization object contained in <tt>mVisualizations</tt>.


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
void ComplexVisualizationDemoFilter::Process(
void VisualizationContainerDemoFilter::Process(
   const GenericSignal& Input,
   const GenericSignal& Input,
         GenericSignal& Output)
         GenericSignal& Output)
{
{
  Output = Input;
    Output = Input;
  if(p->mDecimationCounter == 0)
     p->updateVisualizations(Input);
     p->updateVisualizations(Input);
  ++p->mDecimationCounter %= p->mDecimation;
}
}
</syntaxhighlight>
</syntaxhighlight>


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


<syntaxhighlight lang="cpp">
<syntaxhighlight lang="cpp">
void ComplexVisualizationDemoFilter::StartRun()
void  
VisualizationContainerDemoFilter::StartRun()
{
{
  p->mDecimationCounter = 0;
    p->resetVisualizations();
  p->waitForVisualizations();
  p->resetVisualizations();
}
}


void ComplexVisualizationDemoFilter::StopRun()
void
VisualizationContainerDemoFilter::StopRun()
{
{
  p->waitForVisualizations();
    p->resetVisualizations();
  p->resetVisualizations();
}
}
</syntaxhighlight>
</syntaxhighlight>
===BCI2000 GraphDisplay vs. QPainter rendering===
The code presented here is using the BCI2000 [[Programming Reference:GraphDisplay Class|<tt>GraphDisplay</tt>]] class.
[[Programming Reference:GraphDisplay Class|<tt>GraphDisplay</tt>]] is a layer of abstraction that allows to render shapes and text objects into
a normalized coordinate system.
Code that uses [[Programming Reference:GraphDisplay Class|<tt>GraphDisplay</tt>]] is most likely to survive breaking changes in the
drawing backend (currently Qt) and BCI2000 dependencies.
In contrast, <tt>QPainter</tt>-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, <tt>QPainter</tt>-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==
==See also==
[[Programming Reference:GraphDisplay Class]], [[Programming Reference:GenericVisualization Class]], [[Programming Reference:VisualizationDemo Signal Processing]], [[Technical Reference:Visualization Properties]]
[[Programming Reference:GraphDisplay Class]], [[Programming Reference:GenericVisualization Class]], [[Programming Reference:VisualizationDemo Signal Processing]], [[Programming Reference:ComplexVisualizationDemo 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:48, 14 June 2023

Location

src/contrib/SignalProcessing/VisualizationContainerDemo

Synopsis

The VisualizationContainerDemo signal processing module demonstrates how to maintain a number of visualization displays within a container window. It is similar to Programming Reference:ComplexVisualizationDemo Signal Processing but does not render graphics itself; rather, it maintains a group of visualization windows within a container window, and sends data to these.

Inheritance

The VisualizationContainerDemo signal processing filter derives from GenericFilter.

Function

The VisualizationContainerDemo computes pairwise determination coefficients (squared correlation, r2 values) between its input channels. Determination coefficients are sent to a group of visualizations in the Operator module.

Operator visualizations are contained in a parent window, and 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 Visualization 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, the result is sent to the Operator window inside a GenericSignal object. As both computation effort and the amount of data is limited, and to keep the example code simple, no multithreading is involved in this demo. (For an example that involves multithreading, see Programming Reference:ComplexVisualizationDemo Signal Processing.)

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 compute an r2 value, and to send it to a visualization window.

struct VisualizationContainerDemoFilter::Private
{
    GenericVisualization mContainerVis;

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

    ~Private()
    {
        destroyVisualizations();
    }
    // Creates visualization objects for pairs of channels.
    void createVisualizations(const SignalProperties &);
    // Destroys all visualization objects.
    void destroyVisualizations();
    // Sets visualizations to their initial state.
    void resetVisualizations();
    // Computes data values, and updates visualization windows.
    void updateVisualizations(const GenericSignal &);

    class VisualizationObject
    {
      public:
        VisualizationObject(const std::string &visID);
        ~VisualizationObject();
        void setTitle(const std::string &);
        void configureSignal(const SignalProperties&);
        void setPosition(int row, int col);
        void reset();
        void update(const GenericSignal &);

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

      private:
        GenericVisualization mVis;
        GenericSignal mSignal;
        PhysicalUnit mSampleUnit, mValueUnit;
        std::string mTitle;
        int mRow, mCol;
    };
};

VisualizationObject::update()

This function computes the correlation matrix element, and sends it to the visualization display wrapped into a GenericSignal object.

void
VisualizationContainerDemoFilter::Private::VisualizationObject::update(const GenericSignal& Input)
{
    mComputation.run(Input);
    mSignal(0, 0) = mComputation.result;
    mVis.Send(mSignal);
}

VisualizationObject::reset()

This function resets the visualization window's position and size before sending an empty signal to the Operator (this will enforce creation of the visualization display on the Operator side).

void
VisualizationContainerDemoFilter::Private::VisualizationObject::reset()
{
    // reset position and size
    mVis.Send(CfgID::PlacementVis, "CNTR");
    mVis.Send(CfgID::PlacementRow, mRow + 1); // rows are one-based
    mVis.Send(CfgID::PlacementCol, mCol + 1); // columns are one-based
    mVis.Send(CfgID::Visible, true);
    mVis.Send(CfgID::FixedScale, true);
    mVis.Send(CfgID::MinValue, mValueUnit.RawMin());
    mVis.Send(CfgID::MaxValue, mValueUnit.RawMax() * 1.1);
    mVis.Send(CfgID::ShowStatusBar, false);
    mVis.Send(CfgID::SampleUnit, mSampleUnit.RawToPhysical(1));
    mVis.Send(CfgID::ShowYTicks, false);
    LabelList markers;
    markers.push_back(Label(mValueUnit.PhysicalToRaw("1"), "1|red|-|0.5"));
    markers.push_back(Label(mValueUnit.PhysicalToRaw("0.5"), "0.5|green|-.|0.5"));
    mVis.Send(CfgID::YAxisMarkers, markers);
    mVis.Send(CfgID::WindowTitle, mTitle);
    mVis.Send(CfgID::WindowFrame, true);
    mVis.Send(GenericSignal());
}

Process() function

The Process() function calls updateVisualizations(), which, in turn, will call VisualizationObject::update() for each visualization object contained in mVisualizations.

void VisualizationContainerDemoFilter::Process(
  const GenericSignal& Input,
        GenericSignal& Output)
{
    Output = Input;
    p->updateVisualizations(Input);
}

StartRun() and StopRun()

These reset all visualization objects.

void 
VisualizationContainerDemoFilter::StartRun()
{
    p->resetVisualizations();
}

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

See also

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