Detecting peak value on realtime data stream - c++

I'm using QCustomPlot to read and display realtime value from an IMU. This is how I set the realtimeDataSlot:
void Settings::realtimeDataSlot(double x_acceleration_g, double y_acceleration_g, double z_acceleration_g, double z_acceleration_gnew)
{
static QTime time(QTime::currentTime());
// calculate two new data points:
double key = time.elapsed()/1000.0; // time elapsed since start of demo, in seconds
static double lastPointKey = 0;
if (key-lastPointKey > 0.02) // at most add point every 20 ms
{
// add data to lines:
ui->customPlot->graph(0)->addData(key, x_acceleration_g); // X axis
ui->customPlot->graph(1)->addData(key, y_acceleration_g); // Y axis
ui->customPlot->graph(2)->addData(key, z_acceleration_g); // Z axis
ui->customPlot->graph(3)->addData(key, z_acceleration_gnew);
lastPointKey = key;
}
// make key axis range scroll with the data (at a constant range size of 8):
ui->customPlot->xAxis->setRange(key, 8, Qt::AlignRight);
ui->customPlot->replot();
// calculate frames per second:
static double lastFpsKey;
static int frameCount;
++frameCount;
if (key-lastFpsKey >2) // average fps over 2 seconds
{
ui->statusbar->showMessage(
QString("%1 FPS, Total Data points: %2")
.arg(frameCount/(key-lastFpsKey), 0, 'f', 0)
.arg(ui->customPlot->graph(0)->data()->size()+ui->customPlot->graph(1)->data()->size())
, 0);
lastFpsKey = key;
frameCount = 0;
}
}
which shows me as follows:
As a next step, I need to detect the peaks in any axis, say for example in the above figure in the Y axis there are peak values which I need to detect and count. Can somebody show me a way to do this?
Thanks in advance
EDIT
I marked in the peaks following figure:
I define peak as the figure that value (positive values) more than 0.25 g at high rate.

How to do it for the y-axis:
you define a window size on the x-axis, say 5 x-values of 100
let that window move from start to end of the x-axis, which means: for the first measure, look at the x-values number 0,1,2,3,4 and for the second measure, look at the x-values number 1,2,3,4,5 and so on
for each window measure: determine the maximum y-value in that window, and increase a score counter for the appropriate x-value.
after the complete move of the window from start to end you need to find the x-values with the highest score counters.
The size of your window gives you the number of peaks.
Also do the same for the minimum values to find the negative peaks.
Take care at the start and end of the graph.

Related

QWT axis force major tick at zero

For a better unserstanding what I've done and what I've want to achive:
I want to set up a QwtPlot with a time Axis to display a signal (Voltage over time). I wrote a own implementation for the QwtScaleDraw so i can add an offset (lets say 1000µs) and change the "zero" value of the time axis.
No my system measures a event after 123µs plus this offset. So i want to display only this part of the plot with setInterval(1000, 1123) . But for better unserstanding of the plot I want to add some time before and after this event. Lets say 10% of the event length
10% of 123µs is 12.3µs => setInterval(987.7, 1135.3)
The axis show now a timespan from -12.3µs to 135.3µs.
NOW THE QUESTION: How could I force the axis to show a major tick at 0µs?
Do I have to use the QwtScaleEngine to calculate my ticks or is it a job for the QwtScaleDraw or is there already a function inside the Axis I've only missed?
EDIT:
I think the problem is my Offset. I substract this value direct from the original value inside the Function QwtText label(qreal value) const of my own implementation of QwtScaleDraw. So the zero to display is in this example the value 1000. Maybe its better so set the offset a other way?
I found a solution:
Create a own class inherit QwtLinearScaleEngine. Add a new member variable offset and add getter/setter. Overwrite the function divideScale and test if the given interval contains your offset. If not or when offset is null, use normal function of QwtLinearScaleEngine to calculate the divide scale.
When the interval contains your offset, go from that offset back (substract) with the given step size to the lower bound. Now you can go from this value up to the upper bound and add every value as a tick.
QwtScaleDiv QwtLinearScaleEngineEx::divideScale(double x1, double x2, int maxMajorSteps, int maxMinorSteps, double stepSize) const
{
QwtScaleDiv div = QwtLinearScaleEngine::divideScale(x1, x2, maxMajorSteps, maxMinorSteps, stepSize);
QVector<QwtScaleDiv::TickType> tickTypes = QVector<QwtScaleDiv::TickType>() << QwtScaleDiv::MajorTick << QwtScaleDiv::MediumTick << QwtScaleDiv::MinorTick;
QList<double> ticksByType[QwtScaleDiv::NTickTypes];
if(offset() == 0 || !div.contains(offset())){
for(auto type : tickTypes){
ticksByType[type] = div.ticks(type);
}
}else{
for(auto type : tickTypes){
auto ticks = div.ticks(type);
if(ticks.length() > 1){
stepSize = qAbs(ticks[1]-ticks[0]);
for( double i=offset()-(stepSize*floor((offset()-div.lowerBound())/stepSize));
i<div.upperBound();
i+=stepSize )
{
ticksByType[type].append(i);
}
}
}
}
return QwtScaleDiv(QwtInterval(x1, x2), ticksByType);
}

Create a plot graphs with c++ visual studio

The Y axis will be scaled automatically, depending on the values coming fallowing function.
void mouseHandleCordinate(double val){
// include graph function.
}
So I want to create the plot chart against the time. X axis represent time and Y represent the value coming above function. How I create above graph function.
Always pass the data into void mouseHandleCordinate(double val) function.
As example:
val >>> 2.1,3,1,6,7,5.5,0,9,5,6,7,3.6,2,5,6,7,8,1,2,3,4 >> represent double val
Time>>> 21,20,19,18,17,......., 4,3,2,1 second
not sure I got what you asking but looks like you want to create continuous function following table of sampled points like:
const int N=21; // number of samples
const double t0=21.0,t1=1.0; // start,end times
const double val[N]={ 2.1,3,1,6,7,5.5,0,9,5,6,7,3.6,2,5,6,7,8,1,2,3,4 };
double f(double t) // nearest
{
t = (t-t0)/(t1-t0); // time scaled to <0,1>
t*= (N-1); // time scaled to <0,N) .. index in table
int ix=t; // convert to closest index in val[]
if ((ix<0)||(ix>=N)) return 0.0; // handle undefined times
return val[ix]; // return closest point in val[]
}
This will give you the nearest neighbor style function value. If you need something better use linear or cubic or better interpolation for example:
double f(double t) // linear
{
t = (t-t0)/(t1-t0); // time scaled to <0,1>
t*= (N-1); // time scaled to <0,N) .. index in table
int ix=t; // convert to closest index in val[]
if ((ix<0)||(ix>=N)) return 0.0; // handle undefined times
if (ix==N-1) return val[ix]; // return closest point in val[] if on edge
// linear interpolation
t = t-floor(t); // distance of time between ix and ix+1 points scaled to <0,1>
return val[ix]+(val[ix+1]-val[ix])*t; // return linear interpolated value
}
Now to your problem:
void mouseHandleCordinate(double mx) // mouse x coordinate in [pixels] I assume
{
double t,x,y,x0,y0
// plot
x=0;
y=f(view_start_time)
for (t=view_start_time;t<=view_end_time;t+=(view_end_time-view_start_time)/view_size_in_pixels,x0=x,x++)
{
y0=y; y=f(t);
// render line x0,y0,x,y
}
// mouse highlight
t = view_start_time+((view_end_time-view_start_time)*mx/view_size_in_pixels);
x = mx;
y = f(t);
// render point x,y ... for example with circle r = 16 pixels
}
where:
view_start_time is time of the left most pixel in your plot view
view_end_time is time of the right most pixel in your plot view
view_size_in_pixels is the x resolution in [pixels] of your plot view
[Notes]
I code the stuff directly in SO editor so there may be typos ...
Hoping you are calling the mouseHandleCordinate in some Paint event instead of on mouse movement which should only schedule repaint order... Also you should add y view scale in similar manner to x scale I used ...
For more info see:
How can i produce multi point linear interpolation?

Plot an audio waveform in C++/Qt

I have an university assignement which consists in displaying the waveform of an audio file using C++/Qt. We should be able to modify the scale that we use to display it (expressed in audio samples per screen pixel).
So far, I am able to:
open the audio file
read the samples
plot the samples at a given scale
To plot the samples at a given scale, I have tried two strategies. Let assume that N is the value of the scale:
for i going from 0 to the width of my window, plot the i * Nth audio sample at the screen pixel i. This is very fast and constant in time because we always access the same amount of audio data points.
However, it does not represent the waveform correctly, as we use the value of only 1 point to represent N points.
for i going from 0 to N * width, plot the ith audio sample at the screen position i / (N * width) and let Qt figure out how to represent that correctly on physical screen pixels.
That plots very beautiful waveforms but it takes hell a lot of time to access data. For instance, if I want to display 500 samples per pixel and the width of my window is 100px, I have to access 50 000 points, which are then plotted by Qt as 100 physical points (pixels).
So, how can I get a correct plot of my audio data, which can be calculated fast? Should I calculate the average of N samples for each physical pixel? Should I do some curve fitting?
In other words, what kind of operation is involved when Qt/Matplotlib/Matlab/etc plot thousands of data point to a very limited amount of physical pixels?
Just because I do know how to do it and I already asked something similar on stackoverflow I'll reference this. I'll provide code later.
Drawing Waveforms is a real problem. I tried to figure this out for more than a half of a year!
To sum this up:
According to the Audacity Documentation:
The waveform view uses two shades of blue, one darker and one lighter.
The dark blue part of the waveform displays the tallest peak in the area that pixel represents. At default zoom level Audacity will
display many samples within that pixel width, so this pixel represents
the value of the loudest sample in the group.
The light blue part of the waveform displays the average RMS (Root Mean Square) value for the same group of samples. This is a rough
guide to how loud this area might sound, but there is no way to
extract or use this RMS part of the waveform separately.
So you simply try to get the important information out of a chunk of data. If you do this over and over you'll have multiple stages which can be used for drawing.
I'll provide some code here, please bear with me it's in development:
template<typename T>
class CacheHandler {
public:
std::vector<T> data;
vector2d<T> min, max, rms;
CacheHandler(std::vector<T>& data) throw(std::exception);
void addData(std::vector<T>& samples);
/*
irreversible removes data.
Fails if end index is greater than data length
*/
void removeData(int endIndex);
void removeData(int startIndex, int endIndex);
};
using this:
template<typename T>
inline WaveformPane::CacheHandler<T>::CacheHandler(std::vector<T>& data, int sampleSizeInBits) throw(std::exception)
{
this->data = data;
this->sampleSizeInBits = sampleSizeInBits;
int N = log(data.size()) / log(2);
rms.resize(N); min.resize(N); max.resize(N);
rms[0] = calcRMSSegments(data, 2);
min[0] = getMinPitchSegments(data, 2);
max[0] = getMaxPitchSegments(data, 2);
for (int i = 1; i < N; i++) {
rms[i] = calcRMSSegments(rms[i - 1], 2);
min[i] = getMinPitchSegments(min[i - 1], 2);
max[i] = getMaxPitchSegments(max[i - 1], 2);
}
}
What I'd suggest is something like this:
Given totalNumSamples audio samples in your audio file, and widgetWidth pixels of width in your display widget, you can calculate which samples are to be represented by each pixel:
// Given an x value (in pixels), returns the appropriate corresponding
// offset into the audio-samples array that represents the
// first sample that should be included in that pixel.
int GetFirstSampleIndexForPixel(int x, int widgetWidth, int totalNumSamples)
{
return (totalNumSamples*x)/widgetWidth;
}
virtual void paintEvent(QPaintEvent * e)
{
QPainter p(this);
for (int x=0; x<widgetWidth; x++)
{
const int firstSampleIndexForPixel = GetFirstSampleIndexForPixel(x, widgetWidth, totalNumSamples);
const int lastSampleIndexForPixel = GetFirstSampleIndexForPixel(x+1, widgetWidth, totalNumSamples)-1;
const int largestSampleValueForPixel = GetMaximumSampleValueInRange(firstSampleIndexForPixel, lastSampleIndexForPixel);
const int smallestSampleValueForPixel = GetMinimumSampleValueInRange(firstSampleIndexForPixel, lastSampleIndexForPixel);
// draw a vertical line spanning all sample values that are contained in this pixel
p.drawLine(x, GetYValueForSampleValue(largestSampleValueForPixel), x, GetYValueForSampleValue(smallestSampleValueForPixel));
}
}
Note that I didn't include source code for GetMinimumSampleValueInRange(), GetMaximumSampleValueInRange(), or GetYValueForSampleValue(), since hopefully what they do is obvious from their names, but if not, let me know and I can explain them.
Once you have the above working reasonably well (i.e. drawing a waveform that shows the entire file into your widget), you can start working on adding in zoom-and-pan functionality. Horizontal zoom can be implemented by modifying the behavior of GetFirstSampleIndexForPixel(), e.g.:
int GetFirstSampleIndexForPixel(int x, int widgetWidth, int sampleIndexAtLeftEdgeOfWidget, int sampleIndexAfterRightEdgeOfWidget)
{
int numSamplesToDisplay = sampleIndexAfterRightEdgeOfWidget-sampleIndexAtLeftEdgeOfWidget;
return sampleIndexAtLeftEdgeOfWidget+((numSamplesToDisplay*x)/widgetWidth);
}
With that, you can zoom/pan simply by passing in different values for sampleIndexAtLeftEdgeOfWidget and sampleIndexAfterRightEdgeOfWidget that together indicate the subrange of the file you want to display.

Display the plot values on mouse over. - Detect Scatter points

I am attempting to display the plot values of different points on my QCustomPlot in which I have a Line style of lsLine. I know i could set a mouse over signal on the QCustomPlot but that wont really help since I just need to be informed when the mouse is over my plotted line.My question is is there any way to find out if the mouse is over my scatter point. Is there a signal i could connect to that would tell me when the mouse is over a scatter point ?
Reimplement QCustomPlot::mouseMoveEvent or connect to QCustomPlot::mouseMove.
Then use axes' coordToPixel to translate (cursor) pixel coords to plot coords and search nearest points in your QCPDataMap with QMap::lowerBound(cursorX).
You can easily just connect a slot to the mouseMove signal that QCustomPlot emits. You can then use QCPAxis::pixelToCoord to find the coordinate :
connect(this, SIGNAL(mouseMove(QMouseEvent*)), this,SLOT(showPointToolTip(QMouseEvent*)));
void QCustomPlot::showPointToolTip(QMouseEvent *event)
{
int x = this->xAxis->pixelToCoord(event->pos().x());
int y = this->yAxis->pixelToCoord(event->pos().y());
setToolTip(QString("%1 , %2").arg(x).arg(y));
}
when You use datetime format (including more point per second) of X axis, then pixel to coord fails.
If you want to display coordinates between points, then this is the fastest way
maybe usefull (with connected signal QCustomplot::MouseMove)
void MainWindow::onMouseMoveGraph(QMouseEvent* evt)
{
int x = this->ui->customPlot->xAxis->pixelToCoord(evt->pos().x());
int y = this->ui->customPlot->yAxis->pixelToCoord(evt->pos().y());
qDebug()<<"pixelToCoord: "<<data.key<<data.value; //this is correct when step is greater 1 second
if (this->ui->customPlot->selectedGraphs().count()>0)
{
QCPGraph* graph = this->ui->customPlot->selectedGraphs().first();
QCPData data = graph->data()->lowerBound(x).value();
double dbottom = graph->valueAxis()->range().lower; //Yaxis bottom value
double dtop = graph->valueAxis()->range().upper; //Yaxis top value
long ptop = graph->valueAxis()->axisRect()->top(); //graph top margin
long pbottom = graph->valueAxis()->axisRect()->bottom(); //graph bottom position
// result for Y axis
double valueY = (evt->pos().y() - ptop) / (double)(pbottom - ptop)*(double)(dbottom - dtop) + dtop;
//or shortly for X-axis
double valueX = (evt->pos().x() - graph->keyAxis()->axisRect()->left()); //graph width in pixels
double ratio = (double)(graph->keyAxis()->axisRect()->right() - graph->keyAxis()->axisRect()->left()) / (double)(graph->keyAxis()->range().lower - graph->keyAxis()->range().upper); //ratio px->graph width
//and result for X-axis
valueX=-valueX / ratio + graph->keyAxis()->range().lower;
qDebug()<<"calculated:"<<valueX<<valueY;
}
}

drawing waveform - converting to DB squashes it

I have a wave file, i have a function that retrieves 2 samples per pixel then i draw lines with them. quick and painless before i deal with zooming. i can display the amplitude values no problem
that is an accurate image of the waveform. to do this i used the following code
//tempAllChannels[numOfSamples] holds amplitude data for the entire wav
//oneChannel[numOfPixels*2] will hold 2 values per pixel in display area, an average of min amp, and average of max
for(int i = 0; i < numOfSamples; i++)//loop through all samples in wave file
{
if (tempAllChannels[i] < 0) min += tempAllChannels[i];//if neg amp value, add amp value to min
if (tempAllChannels[i] >= 0) max += tempAllChannels[i];
if(i%factor==0 && i!=0) //factor is (numofsamples in wav)/(numofpixels) in display area
{
min = min/factor; //get average amp value
max = max/factor;
oneChannel[j]=max;
oneChannel[j+1]=min;
j+=2; //iterate for next time
min = 0; //reset for next time
max = 0;
}
}
and that's great but I need to display in db so quieter wave images arent ridiculously small, but when i make the following change to the above code
oneChannel[j]=10*log10(max);
oneChannel[j+1]=-10*log10(-min);
the wave image looks like this.
which isnt accurate, it looks like its being squashed. Is there something wrong with what I'm doing? I need to find a way to convert from amplitude to decibels whilst maintaining dynamics. im thinking i shouldnt be taking an average when converted to DB.
Don't convert to dB for overviews. No one does that.
Instead of finding the average over a block, you should find the max of the absolute value. By averaging, you will loose a lot of amplitude in your high frequency peaks.