RTXI  3.0.0
The Real-Time eXperiment Interface Reference Manual
scope.cpp
Go to the documentation of this file.
1 /*
2  The Real-Time eXperiment Interface (RTXI)
3  Copyright (C) 2011 Georgia Institute of Technology, University of Utah,
4  Weill Cornell Medical College
5 
6  This program is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <QTimer>
21 #include <algorithm>
22 #include <cmath>
23 #include <mutex>
24 
25 #include "scope.hpp"
26 
27 #include <qwt_abstract_scale_draw.h>
28 #include <qwt_global.h>
29 #include <qwt_plot_legenditem.h>
30 #include <qwt_scale_map.h>
31 #include <stdlib.h>
32 
33 #include "rt.hpp"
34 
36 {
37  setRenderHint(QwtPlotItem::RenderAntialiased);
38  const QColor color(Qt::black);
39  setTextPen(color);
40 }
41 
43  : QwtPlotCanvas(plot)
44 {
45  setPaintAttribute(QwtPlotCanvas::BackingStore, false);
46  if (QwtPainter::isX11GraphicsSystem()) {
47  if (testPaintAttribute(QwtPlotCanvas::BackingStore)) {
48  setAttribute(Qt::WA_PaintOnScreen, true);
49  setAttribute(Qt::WA_NoSystemBackground, true);
50  }
51  }
52  setupPalette();
53 }
54 
55 void Oscilloscope::Canvas::setupPalette()
56 {
57  QPalette pal = palette();
58  QLinearGradient gradient;
59  gradient.setCoordinateMode(QGradient::StretchToDeviceMode);
60  gradient.setColorAt(1.0, QColor(Qt::white));
61  pal.setBrush(QPalette::Window, QBrush(gradient));
62  pal.setColor(QPalette::WindowText, Qt::green);
63  setPalette(pal);
64 }
65 
66 // Scope constructor; inherits from QwtPlot
68  : QwtPlot(parent)
69  , d_directPainter(new QwtPlotDirectPainter())
70  , grid(new QwtPlotGrid())
71  , origin(new QwtPlotMarker())
72  , scaleMapY(new QwtScaleMap())
73  , scaleMapX(new QwtScaleMap())
74  , legendItem(new LegendItem())
75  , timer(new QTimer(this))
76 {
77  // Initialize director
78  plotLayout()->setAlignCanvasToScales(true);
79  setAutoReplot(false);
80  setAutoDelete(true);
81 
82  // Set scope canvas
83  setCanvas(new Oscilloscope::Canvas(nullptr));
84 
85  // Setup grid
86  this->grid->setPen(Qt::gray, 0, Qt::DotLine);
87  this->grid->attach(this);
88 
89  // Set division limits on the scope
90  setAxisMaxMajor(QwtPlot::xBottom, static_cast<int>(divX));
91  setAxisMaxMajor(QwtPlot::yLeft, static_cast<int>(divY));
92 
93  // Disable axes
94  enableAxis(QwtPlot::yLeft, false);
95  enableAxis(QwtPlot::xBottom, false);
96 
97  // Statically set y interval
98  setAxisScale(QwtPlot::yLeft, -1.0, 1.0);
99  setAxisScale(QwtPlot::xBottom, 0.0, 1000.0);
100 
101  // Disable autoscaling
102  setAxisAutoScale(QwtPlot::yLeft, false);
103  setAxisAutoScale(QwtPlot::xBottom, false);
104 
105  // Set origin markers
106  this->origin->setLineStyle(QwtPlotMarker::Cross);
107  this->origin->setValue(500.0, 0.0);
108  this->origin->setLinePen(Qt::gray, 2.0, Qt::DashLine);
109  this->origin->attach(this);
110 
111  // Setup scaling map
112  this->scaleMapY->setPaintInterval(-1.0, 1.0);
113  this->scaleMapX->setPaintInterval(0.0, 1000.0);
114 
115  // Create and attach legend
116  this->legendItem->attach(this);
117  this->legendItem->setMaxColumns(1);
118 
119 // Here we have a problem. Plot legends in QWT are
120 // aligned using setAlignment in pre 6.2, but the
121 // function does not exist >= 6.2 and is instead
122 // replaced by setAlignmentInCanvas. This needs a bit
123 // of preprocessor to fix.
124 #if QWT_VERSION >= 0X060200
125  this->legendItem->setAlignmentInCanvas(
126  static_cast<Qt::Alignment>(Qt::AlignTop | Qt::AlignRight));
127 #else
128  this->legendItem->setAlignment(
129  static_cast<Qt::Alignment>(Qt::AlignTop | Qt::AlignRight));
130 #endif
131 
132  this->legendItem->setBorderRadius(8);
133  this->legendItem->setMargin(4);
134  this->legendItem->setSpacing(2);
135  this->legendItem->setItemMargin(0);
136  this->legendItem->setBackgroundBrush(QBrush(QColor(225, 225, 225)));
137  this->legendItem->attach(this);
138 
139  // Update scope background/scales/axes
140  replot();
141 
142  resize(sizeHint());
143  // Timer controls refresh rate of scope
144  this->timer->setTimerType(Qt::PreciseTimer);
145  QObject::connect(timer, SIGNAL(timeout()), this, SLOT(process_data()));
146  this->timer->start(static_cast<int>(this->refresh));
147 }
148 
150 {
151  delete d_directPainter;
152  delete scaleMapX;
153  delete scaleMapY;
154 }
155 
156 // Returns pause status of scope
158 {
159  return isPaused;
160 }
161 
163 {
164  this->isPaused.store(value);
165 }
166 
168  RT::OS::Fifo* fifo)
169 {
170  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
171  auto iter = std::find_if(this->channels.begin(),
172  this->channels.end(),
173  [&](const scope_channel& chan)
174  { return chan.endpoint == probeInfo; });
175  if (iter != this->channels.end()) {
176  return;
177  }
179  chan.curve = new QwtPlotCurve;
180  chan.endpoint = probeInfo;
181  chan.timebuffer.assign(this->buffer_size, 0);
182  chan.xbuffer.assign(this->buffer_size, 0.0);
183  chan.ybuffer.assign(this->buffer_size, 0.0);
184  chan.xtransformed.assign(this->buffer_size, 0.0);
185  chan.ytransformed.assign(this->buffer_size, 0.0);
186  chan.scale = 1;
187  chan.offset = 0;
188  chan.data_indx = 0;
189  auto pen = QPen();
190  pen.setColor(Oscilloscope::penColors[0]);
191  pen.setStyle(Oscilloscope::penStyles[0]);
192  chan.fifo = fifo;
193  chan.curve->setPen(pen);
194  chan.curve->attach(this);
195  this->channels.push_back(chan);
196 }
197 
199 {
200  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
201  auto iter = std::find_if(this->channels.begin(),
202  this->channels.end(),
203  [&](const scope_channel& chan)
204  { return chan.endpoint == probeInfo; });
205  return iter != this->channels.end();
206 }
207 
209 {
210  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
211  auto iter = std::find_if(this->channels.begin(),
212  this->channels.end(),
213  [&](const scope_channel& chan)
214  { return chan.endpoint == probeInfo; });
215  if (iter == this->channels.end()) {
216  return;
217  }
218  iter->curve->detach();
219  delete iter->curve;
220  iter->curve = nullptr;
221  channels.erase(iter);
222  replot();
223 }
224 
226 {
227  std::vector<IO::endpoint> all_block_endpoints;
228  std::shared_lock<std::shared_mutex> read_lock(this->m_channel_mutex);
229  for (auto& channel : channels) {
230  if (channel.endpoint.block == block) {
231  all_block_endpoints.push_back(channel.endpoint);
232  }
233  }
234  read_lock.unlock();
235  for (const auto& endpoint : all_block_endpoints) {
236  this->removeChannel(endpoint);
237  }
238 }
239 
240 void Oscilloscope::Scope::resizeEvent(QResizeEvent* event)
241 {
242  this->d_directPainter->reset();
243  QwtPlot::resizeEvent(event);
244 }
245 
247 {
248  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
249  return channels.size();
250 }
251 
253 {
254  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
255  for (auto& chan : this->channels) {
256  chan.timebuffer.assign(this->buffer_size, 0);
257  chan.xbuffer.assign(this->buffer_size, 0);
258  chan.ybuffer.assign(this->buffer_size, 0);
259  chan.xtransformed.assign(this->buffer_size, 0);
260  chan.ytransformed.assign(this->buffer_size, 0);
261  chan.data_indx = 0;
262  }
263 }
264 
266 {
267  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
268  this->buffer_size.store(size);
269  this->sample_buffer.assign(buffer_size, {});
270  for (auto& chan : this->channels) {
271  chan.timebuffer.assign(this->buffer_size, 0);
272  chan.xbuffer.assign(this->buffer_size, 0);
273  chan.ybuffer.assign(this->buffer_size, 0);
274  chan.xtransformed.assign(this->buffer_size, 0);
275  chan.ytransformed.assign(this->buffer_size, 0);
276  chan.data_indx = 0;
277  }
278 }
279 
281 {
282  return this->buffer_size.load();
283 }
284 
286 {
287  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
288  return horizontal_scale_ns;
289 }
290 
291 void Oscilloscope::Scope::setDivT(int64_t value)
292 {
293  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
294  horizontal_scale_ns = value;
295  if (value >= 1000000000) {
296  dtLabel = "s";
297  } else if (value >= 1000000) {
298  dtLabel = "ms";
299  } else if (value >= 1000) {
300  dtLabel = "µs";
301  } else {
302  dtLabel = "ns";
303  }
304 }
305 
307 {
308  return static_cast<size_t>(divX);
309 }
310 
312 {
313  return static_cast<size_t>(divY);
314 }
315 
317 {
318  return refresh;
319 }
320 
322 {
323  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
324  refresh = r;
325  timer->setInterval(static_cast<int>(refresh));
326 }
327 
329 {
330  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
331  auto chan_loc = std::find_if(this->channels.begin(),
332  this->channels.end(),
333  [&](const Oscilloscope::scope_channel& chann)
334  { return chann.endpoint == endpoint; });
335  if (chan_loc == channels.end()) {
336  return;
337  }
338  chan_loc->scale = scale;
339 }
340 
342 {
343  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
344  auto chan_loc = std::find_if(this->channels.begin(),
345  this->channels.end(),
346  [&](const Oscilloscope::scope_channel& chann)
347  { return chann.endpoint == endpoint; });
348  if (chan_loc == channels.end()) {
349  return 1.0;
350  }
351  return chan_loc->scale;
352 }
353 
355 {
356  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
357  auto chan_loc = std::find_if(this->channels.begin(),
358  this->channels.end(),
359  [&](const Oscilloscope::scope_channel& chann)
360  { return chann.endpoint == endpoint; });
361  if (chan_loc == channels.end()) {
362  return;
363  }
364  chan_loc->offset = offset;
365 }
366 
368 {
369  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
370  auto chan_loc = std::find_if(this->channels.begin(),
371  this->channels.end(),
372  [&](const Oscilloscope::scope_channel& chann)
373  { return chann.endpoint == endpoint; });
374  if (chan_loc == channels.end()) {
375  return 0.0;
376  }
377  return chan_loc->offset;
378 }
379 
381  const QString& label)
382 {
383  const std::unique_lock<std::shared_mutex> lock(this->m_channel_mutex);
384  auto chan_loc = std::find_if(this->channels.begin(),
385  this->channels.end(),
386  [&](const Oscilloscope::scope_channel& chann)
387  { return chann.endpoint == endpoint; });
388  if (chan_loc == channels.end()) {
389  return;
390  }
391  chan_loc->curve->setTitle(label);
392 }
393 
395 {
396  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
397  auto chan_loc = std::find_if(this->channels.begin(),
398  this->channels.end(),
399  [&](const Oscilloscope::scope_channel& chann)
400  { return chann.endpoint == endpoint; });
401  if (chan_loc == channels.end()) {
403  }
404  return chan_loc->curve->pen().color();
405 }
406 
408 {
409  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
410  auto chan_loc = std::find_if(this->channels.begin(),
411  this->channels.end(),
412  [&](const Oscilloscope::scope_channel& chann)
413  { return chann.endpoint == endpoint; });
414  if (chan_loc == channels.end()) {
416  }
417  return chan_loc->curve->pen().style();
418 }
419 
421 {
422  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
423  auto chan_loc = std::find_if(this->channels.begin(),
424  this->channels.end(),
425  [&](const Oscilloscope::scope_channel& chann)
426  { return chann.endpoint == endpoint; });
427  if (chan_loc == channels.end()) {
428  return 1;
429  }
430  return chan_loc->curve->pen().width();
431 }
432 
434 {
435  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
436  auto chan_loc = std::find_if(this->channels.begin(),
437  this->channels.end(),
438  [&](const Oscilloscope::scope_channel& chann)
439  { return chann.endpoint == endpoint; });
440  if (chan_loc != channels.end()) {
441  chan_loc->curve->setPen(pen);
442  }
443 }
444 
446 {
447  this->m_trigger_info.threshold = threshold;
448 }
449 
451 {
452  return this->m_trigger_info.threshold;
453 }
454 
455 // Draw data on the scope
457 {
458  if (isPaused.load() || this->channels.empty()) {
459  return;
460  }
461  int64_t max_time = 0;
462  int64_t local_max_time = 0;
463  for (const auto& chan : this->channels) {
464  local_max_time =
465  *std::max_element(chan.timebuffer.begin(), chan.timebuffer.end());
466  if (local_max_time > max_time) {
467  max_time = local_max_time;
468  }
469  }
470  const int64_t min_time = (max_time - horizontal_scale_ns * divX);
471  // Set X scale map is same for all channels
472  const auto max_window_time = static_cast<double>(max_time - min_time);
473  const double min_window_time = 0.0;
474  scaleMapX->setScaleInterval(min_window_time, max_window_time);
475  size_t ringbuffer_index = 0;
476  for (auto& channel : this->channels) {
477  channel.xtransformed.assign(this->buffer_size, 0);
478  channel.ytransformed.assign(this->buffer_size, 0);
479  scaleMapY->setScaleInterval(-channel.scale * static_cast<double>(divY) / 2,
480  channel.scale * static_cast<double>(divY) / 2);
481  for (size_t i = 0; i < channel.xbuffer.size(); i++) {
482  ringbuffer_index = (i + channel.data_indx) % this->buffer_size;
483  channel.xbuffer[i] =
484  static_cast<double>(channel.timebuffer[i] - min_time);
485  // Thanks to qwt's interface we need the x axis points to be sorted
486  // and scaled. Shenanigans alert
487  channel.xtransformed[i] =
488  scaleMapX->transform(channel.xbuffer[ringbuffer_index]);
489  channel.ytransformed[i] =
490  scaleMapY->transform(channel.ybuffer[ringbuffer_index]);
491  }
492  // TODO this should not happen each iteration, instead build into channel
493  // Append data to curve
494  // Makes deep copy - which is not optimal
495  // TODO: change to pointer based method
496  channel.curve->setSamples(channel.xtransformed.data(),
497  channel.ytransformed.data(),
498  static_cast<int>(channel.ytransformed.size()));
499  }
500  // Update plot
501  replot();
502 }
503 
504 // TODO: look into SIMD for optimize move and calculation of data
506 {
507  const std::shared_lock<std::shared_mutex> lock(this->m_channel_mutex);
508  int64_t bytes = 0;
509  size_t sample_count = 0;
510  size_t array_indx = 0;
511  const size_t sample_capacity_bytes =
512  sample_buffer.size() * sizeof(Oscilloscope::sample);
513  for (auto& channel : this->channels) {
514  // Read as many samples as possible in chunks of buffer size or less.
515  // overwrite old samples from previous write if available
516  while (
517  bytes = channel.fifo->read(sample_buffer.data(), sample_capacity_bytes),
518  bytes > 0)
519  {
520  sample_count = static_cast<size_t>(bytes) / sizeof(Oscilloscope::sample);
521  for (size_t i = 0; i < sample_count; i++) {
522  array_indx = (i + channel.data_indx) % this->buffer_size;
523  channel.timebuffer[array_indx] = sample_buffer[i].time;
524  channel.ybuffer[array_indx] = sample_buffer[i].value;
525  }
526  channel.data_indx =
527  (channel.data_indx + sample_count) % this->buffer_size;
528  };
529 
530  // zero out so the buffer so it doesn't spill over to the next channel
531  sample_buffer.assign(this->buffer_size, {0, 0.0});
532  }
533  this->drawCurves();
534 }
Definition: io.hpp:79
Canvas(QwtPlot *plot)
Definition: scope.cpp:42
void setDataSize(size_t size)
Definition: scope.cpp:265
double getChannelScale(IO::endpoint endpoint)
Definition: scope.cpp:341
void removeChannel(IO::endpoint probeInfo)
Definition: scope.cpp:208
void setTriggerThreshold(double threshold)
Definition: scope.cpp:445
void setChannelLabel(IO::endpoint endpoint, const QString &label)
Definition: scope.cpp:380
void setChannelPen(IO::endpoint endpoint, const QPen &pen)
Definition: scope.cpp:433
int getChannelWidth(IO::endpoint endpoint)
Definition: scope.cpp:420
int64_t getDivT()
Definition: scope.cpp:285
double getChannelOffset(IO::endpoint endpoint)
Definition: scope.cpp:367
QColor getChannelColor(IO::endpoint endpoint)
Definition: scope.cpp:394
bool paused() const
Definition: scope.cpp:157
~Scope() override
Definition: scope.cpp:149
void setChannelOffset(IO::endpoint endpoint, double offset)
Definition: scope.cpp:354
void process_data()
Definition: scope.cpp:505
double getTriggerThreshold() const
Definition: scope.cpp:450
size_t getDataSize() const
Definition: scope.cpp:280
size_t getChannelCount()
Definition: scope.cpp:246
Scope(const Scope &)=delete
void setRefresh(size_t r)
Definition: scope.cpp:321
void createChannel(IO::endpoint probeInfo, RT::OS::Fifo *fifo)
Definition: scope.cpp:167
Qt::PenStyle getChannelStyle(IO::endpoint endpoint)
Definition: scope.cpp:407
size_t getRefresh() const
Definition: scope.cpp:316
size_t getDivX() const
Definition: scope.cpp:306
void resizeEvent(QResizeEvent *event) override
Definition: scope.cpp:240
void setDivT(int64_t value)
Definition: scope.cpp:291
void removeBlockChannels(IO::Block *block)
Definition: scope.cpp:225
void setPause(bool value)
Definition: scope.cpp:162
bool channelRegistered(IO::endpoint probeInfo)
Definition: scope.cpp:198
size_t getDivY() const
Definition: scope.cpp:311
void setChannelScale(IO::endpoint endpoint, double scale)
Definition: scope.cpp:328
struct IO::endpoint endpoint
struct Oscilloscope::sample sample
constexpr std::array< Qt::PenStyle, 5 > penStyles
Definition: scope.hpp:132
const std::array< QColor, 7 > penColors
Definition: scope.hpp:121
QwtPlotCurve * curve
Definition: scope.hpp:104
std::vector< double > ytransformed
Definition: scope.hpp:102
std::vector< double > ybuffer
Definition: scope.hpp:100
std::vector< int64_t > timebuffer
Definition: scope.hpp:98
std::vector< double > xtransformed
Definition: scope.hpp:101
std::vector< double > xbuffer
Definition: scope.hpp:99
RT::OS::Fifo * fifo
Definition: scope.hpp:95
IO::endpoint endpoint
Definition: scope.hpp:94