CustusX  2023.01.05-dev+develop.0da12
An IGT application
cxPlaybackWidget.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include "cxPlaybackWidget.h"
13 
14 #include <QPainter>
15 #include <QToolTip>
16 #include <QMouseEvent>
17 #include <QLabel>
18 #include "cxTrackingService.h"
19 #include "cxHelperWidgets.h"
20 #include "cxTime.h"
21 #include "cxLogger.h"
22 
23 #include "cxTypeConversions.h"
24 #include "cxTimelineWidget.h"
25 #include "cxData.h"
27 #include "cxVideoService.h"
28 //#include "cxPlaybackUSAcquisitionVideo.h"
29 #include "cxSettings.h"
30 #include "cxPatientModelService.h"
31 
32 namespace cx
33 {
34 
35 PlaybackWidget::PlaybackWidget(TrackingServicePtr trackingService, VideoServicePtr videoService, PatientModelServicePtr patientModelService, QWidget* parent) :
36  BaseWidget(parent, "playback_widget", "Playback"),
37  mTrackingService(trackingService),
38  mVideoService(videoService),
39  mPatientModelService(patientModelService)
40 {
41  mOpen = false;
42  this->setToolTip("Replay current session");
43 
44  mTimer.reset(new PlaybackTime());
45  mTimer->initialize(QDateTime::currentDateTime(), 100000);
46  connect(mTimer.get(), SIGNAL(changed()), SLOT(timeChangedSlot()));
47 
48  QVBoxLayout* topLayout = new QVBoxLayout(this);
49 
50  mStartTimeLabel = new QLabel;
51  topLayout->addWidget(mStartTimeLabel);
52  mTotalLengthLabel = new QLabel;
53  topLayout->addWidget(mTotalLengthLabel);
54  mLabel = new QLabel;
55  topLayout->addWidget(mLabel);
56 
57  mToolTimelineWidget = new TimelineWidget(this);
58  connect(mToolTimelineWidget, SIGNAL(positionChanged()), this, SLOT(timeLineWidgetValueChangedSlot()));
59  topLayout->addWidget(mToolTimelineWidget);
60 
61  // create buttons bar
62  QHBoxLayout* playButtonsLayout = new QHBoxLayout;
63  topLayout->addLayout(playButtonsLayout);
64 
65  mOpenAction = this->createAction(this,
66  QIcon(":/icons/open_icon_library/button-red.png"),
67  "Open Playback", "",
68  SLOT(toggleOpenSlot()),
69  playButtonsLayout);
70  this->createAction(this,
71  QIcon(":/icons/open_icon_library/media-seek-backward-3.png"),
72  "Rewind", "",
73  SLOT(rewindSlot()),
74  playButtonsLayout);
75  mPlayAction = this->createAction(this,
76  QIcon(":/icons/open_icon_library/media-playback-start-3.png"),
77  "Play", "",
78  SLOT(playSlot()),
79  playButtonsLayout);
80  this->createAction(this,
81  QIcon(":/icons/open_icon_library/media-seek-forward-3.png"),
82  "Forward", "",
83  SLOT(forwardSlot()),
84  playButtonsLayout);
85  this->createAction(this,
86  QIcon(":/icons/open_icon_library/media-playback-stop-3.png"),
87  "Stop", "",
88  SLOT(stopSlot()),
89  playButtonsLayout);
90  this->createAction(this,
91  QIcon(":/icons/open_icon_library/system-run-5.png"),
92  "Details", "Details",
93  SLOT(toggleDetailsSlot()),
94  playButtonsLayout);
95 
96  mSpeedAdapter = DoubleProperty::initialize(
97  "speed",
98  "Speed",
99  "Set speed of playback, 0 is normal speed.", 0, DoubleRange(-5,5,1),0);
100  connect(mSpeedAdapter.get(), SIGNAL(changed()), this, SLOT(speedChangedSlot()));
101  playButtonsLayout->addWidget(sscCreateDataWidget(this, mSpeedAdapter));
102 
103  playButtonsLayout->addStretch();
104 
105  topLayout->addStretch();
106  this->showDetails();
107 }
108 
110 {
111 }
112 
113 void PlaybackWidget::toggleDetailsSlot()
114 {
115  settings()->setValue("playback/ShowDetails", !settings()->value("playback/ShowDetails", "true").toBool());
116  this->showDetails();
117 }
118 
119 void PlaybackWidget::showDetails()
120 {
121  bool on = settings()->value("playback/ShowDetails").toBool();
122 
123  mStartTimeLabel->setVisible(on);
124  mTotalLengthLabel->setVisible(on);
125 }
126 
127 void PlaybackWidget::timeLineWidgetValueChangedSlot()
128 {
129  mTimer->setTime(QDateTime::fromMSecsSinceEpoch(mToolTimelineWidget->getPos()));
130 }
131 
132 void PlaybackWidget::toggleOpenSlot()
133 {
134  if (mTrackingService->isPlaybackMode())
135  {
136  mTimer->stop();
137  mTrackingService->setPlaybackMode(PlaybackTimePtr());
138  mVideoService->setPlaybackMode(PlaybackTimePtr());
139  }
140  else
141  {
142  mTrackingService->setPlaybackMode(mTimer);
143  if (!mTrackingService->isPlaybackMode())
144  {
145  reportError("trackingService is not in playback mode");
146  return;
147  }
148  mVideoService->setPlaybackMode(mTimer);
149  report(QString("Started Playback with start time [%1] and end time [%2]")
150  .arg(mTimer->getStartTime().toString(timestampMilliSecondsFormatNice()))
151  .arg(mTimer->getStartTime().addMSecs(mTimer->getLength()).toString(timestampMilliSecondsFormatNice())));
152 
153  this->toolManagerInitializedSlot();
154  }
155 }
156 
157 QColor PlaybackWidget::generateRandomToolColor() const
158 {
159  std::vector<QColor> colors;
160  int s = 255;
161  int v = 192;
162  colors.push_back(QColor::fromHsv(110, s, v));
163  colors.push_back(QColor::fromHsv(80, s, v));
164  colors.push_back(QColor::fromHsv(140, s, v));
165  colors.push_back(QColor::fromHsv(95, s, v));
166  colors.push_back(QColor::fromHsv(125, s, v));
167 
168  static int gCounter = 0;
169  return colors[(gCounter++)%colors.size()];
170 }
171 
172 std::vector<TimelineEvent> PlaybackWidget::convertHistoryToEvents(ToolPtr tool)
173 {
174  std::vector<TimelineEvent> retval;
175  TimedTransformMapPtr history = tool->getPositionHistory();
176  if (!history || history->empty())
177  return retval;
178  double timeout = 200;
179  TimelineEvent currentEvent(tool->getName() + " visible", history->begin()->first);
180  currentEvent.mGroup = "tool";
181  currentEvent.mColor = this->generateRandomToolColor(); // QColor::fromHsv(110, 255, 192);
182 // std::cout << "first event start: " << currentEvent.mDescription << " " << currentEvent.mStartTime << " " << history->size() << std::endl;
183 
184  for(TimedTransformMap::iterator iter=history->begin(); iter!=history->end(); ++iter)
185  {
186  double current = iter->first;
187 
188  if (current - currentEvent.mEndTime > timeout)
189  {
190  retval.push_back(currentEvent);
191  currentEvent.mStartTime = currentEvent.mEndTime = current;
192  }
193  else
194  {
195  currentEvent.mEndTime = current;
196  }
197  }
198  if (!similar(currentEvent.mEndTime - currentEvent.mStartTime, 0))
199  retval.push_back(currentEvent);
200 
201  return retval;
202 }
203 
204 std::vector<TimelineEvent> PlaybackWidget::convertRegistrationHistoryToEvents(RegistrationHistoryPtr reg)
205 {
206  std::vector<TimelineEvent> events;
207 
208  std::vector<RegistrationTransform> tr = reg->getData();
209  for (unsigned i=0; i<tr.size(); ++i)
210  {
211  if (!tr[i].mTimestamp.isValid())
212  continue;
213 
214  QString text = QString("Registraton %1, fixed=%2").arg(tr[i].mType).arg(tr[i].mFixed);
215  if (!tr[i].mMoving.isEmpty())
216  text = QString("%1, moving=%2").arg(text).arg(tr[i].mMoving);
217 
218  events.push_back(TimelineEvent(text,
219  tr[i].mTimestamp.toMSecsSinceEpoch()));
220  }
221 
222 // std::vector<ParentSpace> ps = reg->getParentSpaces();
223 
224  return events;
225 }
226 
227 
228 bool PlaybackWidget::isInterestingTool(ToolPtr tool) const
229 {
230  return !tool->hasType(Tool::TOOL_MANUAL) && !tool->hasType(Tool::TOOL_REFERENCE);
231 }
232 
233 std::vector<TimelineEvent> PlaybackWidget::createEvents()
234 {
235  typedef std::vector<TimelineEvent> TimelineEventVector;
236 
237  // find all valid regions (i.e. time sequences with tool navigation)
238  TimelineEventVector events;
239  TrackingService::ToolMap tools = mTrackingService->getTools();
240  for (TrackingService::ToolMap::iterator iter=tools.begin(); iter!=tools.end(); ++iter)
241  {
242  if(this->isInterestingTool(iter->second))
243  {
244  TimelineEventVector current = convertHistoryToEvents(iter->second);
245  copy(current.begin(), current.end(), std::back_inserter(events));
246  }
247  }
248 
249  std::map<QString, DataPtr> data = mPatientModelService->getDatas();
250  for (std::map<QString, DataPtr>::iterator iter=data.begin(); iter!=data.end(); ++iter)
251  {
252  QString desc("loaded " + iter->second->getName());
253  if (iter->second->getAcquisitionTime().isValid())
254  {
255  double acqTime = iter->second->getAcquisitionTime().toMSecsSinceEpoch();
256  events.push_back(TimelineEvent(desc, acqTime));
257  }
258 
259  RegistrationHistoryPtr reg = iter->second->get_rMd_History();
260  TimelineEventVector current = this->convertRegistrationHistoryToEvents(reg);
261  copy(current.begin(), current.end(), std::back_inserter(events));
262  }
263 
264  RegistrationHistoryPtr reg = mPatientModelService->get_rMpr_History();
265  TimelineEventVector current = this->convertRegistrationHistoryToEvents(reg);
266  copy(current.begin(), current.end(), std::back_inserter(events));
267 
268  current = mVideoService->getPlaybackEvents();
269  copy(current.begin(), current.end(), std::back_inserter(events));
270 
271 
272  return events;
273 }
274 
278 std::pair<double,double> PlaybackWidget::findTimeRange(std::vector<TimelineEvent> events)
279 {
280  if (events.empty())
281  {
282  double now = QDateTime::currentDateTime().toMSecsSinceEpoch();
283  return std::make_pair(now, now+1000);
284  }
285 
286  std::pair<double,double> timeRange(getMilliSecondsSinceEpoch(), 0);
287 // std::pair<double,double> timeRange(events[0].mStartTime, events[0].mEndTime);
288 
289  for (unsigned i=0; i<events.size(); ++i)
290  {
291  timeRange.first = std::min(timeRange.first, events[i].mStartTime);
292  timeRange.second = std::max(timeRange.second, events[i].mEndTime);
293 // std::cout << events[i].mDescription << std::endl;
294 // std::cout << "===start " << QDateTime::fromMSecsSinceEpoch(events[i].mStartTime).toString(timestampMilliSecondsFormatNice()) << std::endl;
295 // std::cout << "=== end " << QDateTime::fromMSecsSinceEpoch(events[i].mEndTime).toString(timestampMilliSecondsFormatNice()) << std::endl;
296 // std::cout << "===start " << events[i].mStartTime << std::endl;
297 // std::cout << "=== end " << events[i].mEndTime << std::endl;
298 // std::cout << "======" << std::endl;
299  }
300 // std::cout << "======" << std::endl;
301 // std::cout << "======" << std::endl;
302 // std::cout << "======" << std::endl;
303 
304  return timeRange;
305 }
306 
307 void PlaybackWidget::toolManagerInitializedSlot()
308 {
309  if (mTrackingService->isPlaybackMode())
310  {
311  mOpenAction->setText("Close Playback");
312  mOpenAction->setIcon(QIcon(":/icons/open_icon_library/button-green.png"));
313  }
314  else
315  {
316  mOpenAction->setText("Open Playback");
317  mOpenAction->setIcon(QIcon(":/icons/open_icon_library/button-red.png"));
318  mToolTimelineWidget->setEvents(std::vector<TimelineEvent>());
319  return;
320  }
321 
322  if (mTrackingService->getState() < Tool::tsINITIALIZED)
323  return;
324 
325  std::vector<TimelineEvent> events = this->createEvents();
326  std::pair<double,double> range = this->findTimeRange(events);
327 // std::cout << "===start " << QDateTime::fromMSecsSinceEpoch(range.first).toString(timestampMilliSecondsFormatNice()) << std::endl;
328 // std::cout << "=== end " << QDateTime::fromMSecsSinceEpoch(range.second).toString(timestampMilliSecondsFormatNice()) << std::endl;
329  mTimer->initialize(QDateTime::fromMSecsSinceEpoch(range.first), range.second - range.first);
330 
331  //TODO merge into one initializer:
332  mToolTimelineWidget->setRange(range.first, range.second);
333  mToolTimelineWidget->setEvents(events);
334 
335  QString startDate = mTimer->getStartTime().toString("yyyy-MM-dd");
336  QString startTime = mTimer->getStartTime().toString("hh:mm");
337  QString endTime = mTimer->getStartTime().addMSecs(mTimer->getLength()).toString("hh:mm");
338 // QString length = this->stripLeadingZeros(QTime(0,0,0,0).addMSecs(mTimer->getLength()).toString("hh:mm:ss"));
339  QString length = this->convertMillisecsToNiceString(mTimer->getLength());
340  mStartTimeLabel->setText(
341  QString("Date:").leftJustified(15) +"" + startDate+"\n" +
342  QString("Time:").leftJustified(15) +"" + startTime + " - " + endTime + "\n" +
343  QString("Duration:").leftJustified(15)+"" + length);
344 
345  this->timeChangedSlot();
346 }
347 
351 //QString PlaybackWidget::stripLeadingZeros(QString time)
352 //{
353 // QStringList split = time.split(":");
354 // bool ok = false;
355 // while (!split.empty() && (split.front().toInt(&ok)==0) && ok)
356 // {
357 // split.pop_front();
358 // }
359 // return split.join(":");
360 //}
361 
365 QString PlaybackWidget::convertMillisecsToNiceString(qint64 length) const
366 {
367  QString retval;
368 
369  qint64 ms = length % 1000;
370  qint64 s = (length / 1000) % 60;
371  qint64 m = (length / (1000*60)) % 60;
372  qint64 h = (length / (1000*60*60));
373  QChar c = '0';
374 
375  retval = QString("%1:%2.%3").arg(m,2,10,c).arg(s,2,10,c).arg(ms,3,10,c);
376  if (h>0)
377  retval = QString("%1:%2").arg(h).arg(retval);
378 
379  return retval;
380 }
381 
382 void PlaybackWidget::speedChangedSlot()
383 {
384  double speed = mSpeedAdapter->getValue();
385  speed = pow(2,speed);
386  mTimer->setSpeed(speed);
387 }
388 
389 
390 void PlaybackWidget::timeChangedSlot()
391 {
392  QString color("green");
393  int fontSize = 4;
394  qint64 offset = mTimer->getOffset(); // SmStartTime.secsTo(QDateTime::currentDateTime());
395  QString format = QString("<font size=%1 color=%2><b>%3</b></font>").arg(fontSize).arg(color);
396 
397  QString currentTime = mTimer->getTime().toString("hh:mm:ss");
398 // QString currentOffset = this->stripLeadingZeros(QTime(0,0,0,0).addMSecs(offset).toString("hh:mm:ss.zzz"));
399  QString currentOffset = this->convertMillisecsToNiceString(offset);
400 
401  mLabel->setText(format.arg("Elapsed: "+currentOffset+" \tTime: " + currentTime));
402 
403  if (mTimer->isPlaying())
404  {
405  mPlayAction->setIcon(QIcon(":/icons/open_icon_library/media-playback-pause-3.png"));
406  mPlayAction->setText("Pause");
407  }
408  else
409  {
410  mPlayAction->setIcon(QIcon(":/icons/open_icon_library/media-playback-start-3.png"));
411  mPlayAction->setText("Play");
412  }
413 
414 // mTimeLineSlider->blockSignals(true);
416 // mTimeLineSlider->setRange(0, mTimer->getLength());
417 // mTimeLineSlider->setSingleStep(1);
418 //
419 // mTimeLineSlider->setValue(offset);
420 // mTimeLineSlider->setToolTip(QString("Current time"));
421 // mTimeLineSlider->blockSignals(false);
422 
423  mToolTimelineWidget->blockSignals(true);
424  mToolTimelineWidget->setPos(mTimer->getTime().toMSecsSinceEpoch());
425  mToolTimelineWidget->blockSignals(false);
426 }
427 
428 void PlaybackWidget::playSlot()
429 {
430  if (mTimer->isPlaying())
431  {
432  mTimer->pause();
433  }
434  else
435  {
436  mTimer->start();
437  }
438 }
439 //void PlaybackWidget::pauseSlot()
440 //{
441 // mTimer->pause();
442 //}
443 void PlaybackWidget::stopSlot()
444 {
445  mTimer->stop();
446 }
447 void PlaybackWidget::forwardSlot()
448 {
449  mTimer->forward(1000*mTimer->getSpeed());
450 }
451 void PlaybackWidget::rewindSlot()
452 {
453  mTimer->rewind(1000*mTimer->getSpeed());
454 }
455 
456 
457 } /* namespace cx */
widget for displaying a timeline for events.
PlaybackWidget(TrackingServicePtr trackingService, VideoServicePtr videoService, PatientModelServicePtr patientModelService, QWidget *parent)
boost::shared_ptr< class VideoService > VideoServicePtr
void reportError(QString msg)
Definition: cxLogger.cpp:71
boost::shared_ptr< class RegistrationHistory > RegistrationHistoryPtr
Definition: cxDataManager.h:37
boost::shared_ptr< class TrackingService > TrackingServicePtr
Description of one event in time.
Utility class for describing a bounded numeric range.
Definition: cxDoubleRange.h:32
double getMilliSecondsSinceEpoch()
Definition: cxTime.cpp:44
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Definition: cxSettings.cpp:66
Reference tool.
Definition: cxTool.h:84
void setRange(double start, double stop)
QAction * createAction(QObject *parent, QIcon iconName, QString text, QString tip, T slot, QLayout *layout=NULL, QToolButton *button=new QToolButton())
Definition: cxBaseWidget.h:129
void setPos(double pos)
boost::shared_ptr< class PlaybackTime > PlaybackTimePtr
void setValue(const QString &key, const QVariant &value)
Definition: cxSettings.cpp:58
double getPos() const
boost::shared_ptr< TimedTransformMap > TimedTransformMapPtr
Definition: cxTool.h:36
boost::shared_ptr< class PatientModelService > PatientModelServicePtr
Representation of a mouse/keyboard-controlled virtual tool.
Definition: cxTool.h:85
Settings * settings()
Shortcut for accessing the settings instance.
Definition: cxSettings.cpp:21
connected to hardware, if any, ready to use
Definition: cxTool.h:76
std::map< QString, ToolPtr > ToolMap
Interface for QWidget which handles widgets uniformly for the system.
Definition: cxBaseWidget.h:88
void report(QString msg)
Definition: cxLogger.cpp:69
bool similar(const CameraInfo &lhs, const CameraInfo &rhs, double tol)
static DoublePropertyPtr initialize(const QString &uid, QString name, QString help, double value, DoubleRange range, int decimals, QDomNode root=QDomNode())
RealScalar length() const
QWidget * sscCreateDataWidget(QWidget *parent, PropertyPtr data, QGridLayout *gridLayout, int row)
Create a widget capable of displaying the input data.
Controller for historic time, playback etc.
QString timestampMilliSecondsFormatNice()
Definition: cxTime.cpp:30
void setEvents(std::vector< TimelineEvent > events)
Namespace for all CustusX production code.
boost::shared_ptr< class Tool > ToolPtr