DynExp
Highly flexible laboratory automation for dynamically changing experiments.
ImageViewer.cpp
Go to the documentation of this file.
1 // This file is part of DynExp.
2 
3 #include "stdafx.h"
4 #include "moc_ImageViewer.cpp"
5 #include "ImageViewer.h"
6 
8 {
9  ImageViewerWidget::ImageViewerWidget(ImageViewer& Owner, QModuleWidget* parent)
10  : QModuleWidget(Owner, parent),
11  HistogramContextMenu(new QMenu(this)), HistogramLinLogActionGroup(new QActionGroup(this)),
12  HistogramBarSetI(nullptr), HistogramBarSetR(nullptr), HistogramBarSetG(nullptr), HistogramBarSetB(nullptr),
13  HistogramBarSeries(new QBarSeries(this)), HistogramChart(nullptr), HistogramXAxis(new QValueAxis(this)), HistogramYAxis(new QValueAxis(this)),
14  GraphicsView(nullptr), GraphicsPixmapItem(nullptr), GraphicsScene(new QGraphicsScene(this))
15  {
16  ui.setupUi(this);
17 
18  ui.action_Zoom_fit->setChecked(true);
19 
20  HistogramLinAction = HistogramLinLogActionGroup->addAction("&Linear");
21  HistogramLinAction->setCheckable(true);
22  HistogramLinAction->setChecked(true);
23  HistogramLogAction = HistogramLinLogActionGroup->addAction("Lo&garithmic");
24  HistogramLogAction->setCheckable(true);
25  HistogramContextMenu->addActions(HistogramLinLogActionGroup->actions());
26  HistogramContextMenu->addSeparator();
27  HistogramBWAction = HistogramContextMenu->addAction("Show &intensity");
28  HistogramBWAction->setCheckable(true);
29  HistogramBWAction->setChecked(true);
30  HistogramColorAction = HistogramContextMenu->addAction("Show &colors");
31  HistogramColorAction->setCheckable(true);
32 
33  HistogramChart = new QChart();
34  ui.Histogram->setChart(HistogramChart); // Takes ownership of HistogramChart.
35  ui.Histogram->setRenderHint(QPainter::Antialiasing);
38  HistogramChart->legend()->setVisible(false);
39  HistogramXAxis->setTitleText("pixel value");
40  HistogramXAxis->setLabelFormat("%d");
41  HistogramXAxis->setRange(0, 255);
42  HistogramXAxis->setTickCount(255 / 85 + 1);
43  HistogramChart->addAxis(HistogramXAxis, Qt::AlignBottom);
44  HistogramYAxis->setTitleText("counts");
45  HistogramYAxis->setLabelFormat("%.0e");
46  HistogramYAxis->setRange(0, 1);
47  HistogramChart->addAxis(HistogramYAxis, Qt::AlignLeft);
48 
49  GraphicsView = new Util::MarkerGraphicsView(ui.MainSplitter);
50  GraphicsView->setObjectName(QString::fromUtf8("Image"));
51  QSizePolicy ImageSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
52  ImageSizePolicy.setHorizontalStretch(1);
53  ImageSizePolicy.setVerticalStretch(0);
54  ImageSizePolicy.setHeightForWidth(GraphicsView->sizePolicy().hasHeightForWidth());
55  GraphicsView->setSizePolicy(ImageSizePolicy);
56  GraphicsView->setMinimumSize(QSize(400, 300));
57  GraphicsView->viewport()->setProperty("cursor", QVariant(QCursor(Qt::CrossCursor)));
58  GraphicsView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
59 
61  GraphicsView->setScene(GraphicsScene);
62  GraphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
63  GraphicsView->viewport()->installEventFilter(this);
64  GraphicsView->viewport()->setMouseTracking(true);
65  }
66 
67  void ImageViewerWidget::SetImage(const QImage& NewImage) noexcept
68  {
69  Pixmap = QPixmap::fromImage(NewImage); // deep copy
70  Pixmap.detach(); // just to be sure...
71  }
72 
74  {
75  if (GraphicsView)
76  GraphicsView->setEnabled(Enable);
77  }
78 
80  {
81  IntensityHistogram = std::move(NewIntensityHistogram);
82  }
83 
85  {
86  RGBHistogram = std::move(NewRGBHistogram);
87  }
88 
90  {
92 
93  if (!ui.ExposureTimeGroupBox->isVisible() || ui.ExposureTimeGroupBox->visibleRegion().isEmpty())
94  return CHT::NoHistogram;
95 
96  if (HistogramBWAction->isChecked())
97  return HistogramColorAction->isChecked() ? CHT::IntensityAndRGBHistogram : CHT::IntensityHistogram;
98  if (HistogramColorAction->isChecked())
99  return CHT::RGBHistogram;
100 
101  return CHT::NoHistogram;
102  }
103 
105  {
106  if (GraphicsView->isVisible() && !GraphicsView->visibleRegion().isEmpty() && !Pixmap.isNull())
107  {
108  GraphicsPixmapItem->setPixmap(Pixmap);
109  GraphicsScene->update();
110 
111  OnZoomFitClicked(ui.action_Zoom_fit->isChecked());
112  }
113 
114  if (ui.ExposureTimeGroupBox->isVisible() && !ui.ExposureTimeGroupBox->visibleRegion().isEmpty())
115  UpdateHistogram();
116 
117  ui.ImageGeometry->setText(QString::number(Pixmap.width()) + " x " + QString::number(Pixmap.height()));
118  }
119 
120  bool ImageViewerWidget::eventFilter(QObject* obj, QEvent* event)
121  {
122  if (GraphicsView->viewport())
123  if (event->type() == QEvent::MouseMove)
124  {
125  OnImageMouseMove(static_cast<QMouseEvent*>(event));
126 
127  return true;
128  }
129 
130  return QObject::eventFilter(obj, event);
131  }
132 
133  void ImageViewerWidget::resizeEvent(QResizeEvent* event)
134  {
135  OnZoomFitClicked(ui.action_Zoom_fit->isChecked());
136  }
137 
139  {
141 
142  auto ComputeHistogram = GetComputeHistogram();
143  if (ComputeHistogram == CHT::NoHistogram || IntensityHistogram.size() >= std::numeric_limits<int>::max())
144  return;
145 
146  // Remove all existing data series.
147  if (HistogramBarSetI)
149  HistogramBarSetI = nullptr;
150  if (HistogramBarSetR)
152  HistogramBarSetR = nullptr;
153  if (HistogramBarSetG)
155  HistogramBarSetG = nullptr;
156  if (HistogramBarSetB)
158  HistogramBarSetB = nullptr;
159 
160  // Some removals failed. Do not continue in order to avoid memory leaks.
162  return;
163 
164  HistogramChart->removeSeries(HistogramBarSeries);
165 
166  // Create new data series.
167  if (ComputeHistogram == CHT::IntensityHistogram ||
168  ComputeHistogram == CHT::IntensityAndRGBHistogram)
169  {
170  HistogramBarSetI = new QBarSet("I", this);
171  HistogramBarSetI->setColor(Qt::white);
172  HistogramBarSetI->setBorderColor(Qt::white);
173  }
174 
175  if (ComputeHistogram == CHT::RGBHistogram ||
176  ComputeHistogram == CHT::IntensityAndRGBHistogram)
177  {
178  HistogramBarSetR = new QBarSet("R", this);
179  HistogramBarSetG = new QBarSet("G", this);
180  HistogramBarSetB = new QBarSet("B", this);
181  HistogramBarSetR->setColor(Qt::red);
182  HistogramBarSetR->setBorderColor(Qt::red);
183  HistogramBarSetG->setColor(Qt::green);
184  HistogramBarSetG->setBorderColor(Qt::green);
185  HistogramBarSetB->setColor(Qt::blue);
186  HistogramBarSetB->setBorderColor(Qt::blue);
187  }
188 
189  bool LogPlot = HistogramLogAction->isChecked();
190  qreal MaxValue = 1;
191  for (int i = 0; i < static_cast<int>(IntensityHistogram.size()); ++i)
192  {
193  if (HistogramBarSetI)
194  {
195  // Add .1 in log case to avoid NaN if value is 0.
196  *HistogramBarSetI << (LogPlot ? std::log10(IntensityHistogram[i] + .1) : IntensityHistogram[i]);
197 
198  MaxValue = std::max(MaxValue, HistogramBarSetI->at(i));
199  }
200 
202  {
203  // Add .1 in log case to avoid NaN if value is 0.
204  *HistogramBarSetR << (LogPlot ? std::log10(std::get<0>(RGBHistogram)[i] + .1) : std::get<0>(RGBHistogram)[i]);
205  *HistogramBarSetG << (LogPlot ? std::log10(std::get<1>(RGBHistogram)[i] + .1) : std::get<1>(RGBHistogram)[i]);
206  *HistogramBarSetB << (LogPlot ? std::log10(std::get<2>(RGBHistogram)[i] + .1) : std::get<2>(RGBHistogram)[i]);
207 
208  MaxValue = std::max({ MaxValue, HistogramBarSetR->at(i), HistogramBarSetG->at(i), HistogramBarSetB->at(i) });
209  }
210  }
211 
212  if (HistogramBarSetI)
216 
218  HistogramYAxis->setMax(MaxValue);
219  HistogramBarSeries->attachAxis(HistogramXAxis);
220  HistogramBarSeries->attachAxis(HistogramYAxis);
221  }
222 
224  {
225  HistogramContextMenu->exec(ui.Histogram->mapToGlobal(Position));
226  }
227 
229  {
230  auto Filename = Util::PromptSaveFilePathModule(this, "Save image", ".png", "Portable Network Graphics image (*.png)");
231  if (Filename.isEmpty())
232  return;
233 
234  SaveImageFilename = Filename;
235  }
236 
238  {
239  ui.action_Zoom_fit->setChecked(false);
241  }
242 
244  {
245  ui.action_Zoom_fit->setChecked(false);
246  GraphicsView->ZoomIn();
247  }
248 
250  {
251  ui.action_Zoom_fit->setChecked(false);
253  }
254 
256  {
257  if (Checked)
258  GraphicsView->fitInView(GraphicsPixmapItem, Qt::AspectRatioMode::KeepAspectRatio);
259  }
260 
261  void ImageViewerWidget::OnImageMouseMove(QMouseEvent* Event)
262  {
263  auto LocalPoint = GraphicsView->mapFromGlobal(Event->globalPos());
264 
265  if (!GraphicsView->items(LocalPoint).empty())
266  {
267  auto Point = GraphicsView->mapToScene(LocalPoint).toPoint();
268 
269  ui.CursorPosition->setText("X:" + QString::number(Point.x()) + ", Y:" + QString::number(Point.y()));
270  }
271  }
272 
274  {
275  Init();
276  }
277 
284  {
285  if (CurrentImage.isNull())
286  return std::numeric_limits<double>::quiet_NaN();
287 
288  const auto GrayImage = CurrentImage.convertToFormat(QImage::Format::Format_Grayscale8);
289  auto RawImage = GrayImage.constBits();
290 
291  double BrennerGradient = .0;
292  for (int y = 0; y < GrayImage.height(); ++y)
293  {
294  for (int x = 0; x < GrayImage.width() - 2; ++x)
295  {
296  BrennerGradient += std::pow((static_cast<double>(*RawImage) - static_cast<double>(*(RawImage + 2))) / 255.0, 2);
297 
298  ++RawImage;
299  }
300 
301  RawImage += 2;
302  }
303 
304  // Multiply by 1000 for convenience since we are normally dealing with very small gradients.
305  return BrennerGradient / GrayImage.width() / GrayImage.height() * 1e3;
306  }
307 
309  {
310  UIInitialized = false;
311 
312  CameraModes.clear();
317  CurrentFPS = 0.f;
319 
320  CurrentImage = QImage();
321  HasImageChanged = false;
322  ImageCapturingPaused = false;
323  CaptureAfterPause = false;
324 
325  IntensityHistogram = {};
326  RGBHistogram = {};
327  }
328 
329  ImageViewer::ImageViewer(const std::thread::id OwnerThreadID, DynExp::ParamsBasePtrType&& Params)
330  : QModuleBase(OwnerThreadID, std::move(Params)),
331  StateMachine(ReadyState,
332  AutofocusInitState, AutofocusGotoSampleState, AutofocusWaitBeforeCaptureState,
333  AutofocusWaitForImageState, AutofocusStepState, AutofocusFinishedState),
334  PauseUpdatingUI(std::make_shared<std::atomic<bool>>(false))
335  {
336  }
337 
338  ImageViewer::~ImageViewer()
339  {
340  }
341 
342  void ImageViewer::OnSaveImage(DynExp::ModuleInstance* Instance, QString Filename) const
343  {
344  QImage Image;
345  {
346  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
347  Image = ModuleData->CurrentImage.copy();
348  } // ModuleData unlocked here for heavy save operation.
349 
350  if (!Image.save(Filename))
351  Util::EventLog().Log("Image Viewer: Saving the current image failed.", Util::ErrorType::Error);
352  }
353 
355  {
356  try
357  {
358  StateMachine.Invoke(*this, Instance);
359 
361  } // ModuleData and CameraData unlocked here.
362  catch (const Util::TimeoutException& e)
363  {
364  if (NumFailedUpdateAttempts++ >= 3)
365  Instance.GetOwner().SetWarning(e);
366  }
367 
369  }
370 
371  void ImageViewer::ResetImpl(dispatch_tag<QModuleBase>)
372  {
373  StateMachine.SetCurrentState(StateType::Ready);
374 
375  AutofocusParams = {};
376  AutofocusResults = {};
378  AutofocusSamples.clear();
380 
381  *PauseUpdatingUI = false;
383  }
384 
385  std::unique_ptr<DynExp::QModuleWidget> ImageViewer::MakeUIWidget()
386  {
387  auto Widget = std::make_unique<ImageViewerWidget>(*this);
388 
389  Connect(Widget->ui.CBCameraMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageViewer::OnCameraModeChanged);
390  Connect(Widget->ui.ExposureTime, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageViewer::OnExposureTimeChanged);
391  Connect(Widget->ui.action_Capture_Frame, &QAction::triggered, this, &ImageViewer::OnCaptureSingle);
392  Connect(Widget->ui.action_Capture_continuously, &QAction::triggered, this, &ImageViewer::OnCaptureContinuously);
393  Connect(Widget->ui.action_Autofocus, &QAction::triggered, this, &ImageViewer::OnAutofocusClicked);
394 
395  return Widget;
396  }
397 
398  void ImageViewer::UpdateUIChild(const ModuleBase::ModuleDataGetterType& ModuleDataGetter)
399  {
401 
402  if (*PauseUpdatingUI)
403  return;
404 
405  auto Widget = GetWidget<ImageViewerWidget>();
406  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(ModuleDataGetter());
407 
408  // ModuleData is locked now. This syncs writing CameraData's settings with OnAutofocus().
409  const bool Ready = IsReadyState();
410  const bool Autofocusing = IsAutofocusingState();
411 
412  if (!ModuleData->UIInitialized)
413  {
414  Widget->ui.action_Autofocus->setEnabled(ModuleData->Focus.valid());
415 
416  if (ModuleData->CameraModes.empty())
417  Widget->ui.CameraModeGroupBox->setVisible(false);
418  else
419  {
420  for (const auto& Mode : ModuleData->CameraModes)
421  {
422  Widget->ui.CBCameraMode->insertItem(Widget->ui.CBCameraMode->count(), QString::fromStdString(Mode));
423  Widget->ui.CBCameraMode->setItemData(Widget->ui.CBCameraMode->count() - 1, QString::fromStdString(Mode), Qt::ToolTipRole);
424  }
425 
426  // Triggers QComboBox::currentIndexChanged().
427  Widget->ui.CBCameraMode->setCurrentIndex(0);
428  }
429 
430  ModuleData->UIInitialized = true;
431  }
432 
433  Widget->ui.action_Save_Image->setEnabled(Ready);
434  Widget->ui.action_Capture_Frame->setEnabled(Ready);
435  Widget->ui.action_Capture_continuously->setEnabled(Ready);
436  Widget->ui.CameraModeGroupBox->setEnabled(Ready);
437  Widget->ui.ImageModifiersGroupBox->setEnabled(Ready);
438  Widget->ui.Histogram->setEnabled(Ready);
439  Widget->SetImageViewEnabled(Ready);
440 
441  Widget->ui.ExposureTimeGroupBox->setEnabled(Ready && ModuleData->Camera->CanSetExposureTime());
442  Widget->ui.ExposureTimeMinValue->setText("min. " + QString::number(ModuleData->MinExposureTime.count()) + " ms");
443  Widget->ui.ExposureTimeMaxValue->setText("max. " + QString::number(ModuleData->MaxExposureTime.count()) + " ms");
444  Widget->ui.ExposureTime->setMinimum(ModuleData->MinExposureTime.count());
445  Widget->ui.ExposureTime->setMaximum(ModuleData->MaxExposureTime.count());
446 
447  if (!Widget->ui.ExposureTime->hasFocus())
448  {
449  const QSignalBlocker Blocker(Widget->ui.ExposureTime);
450  Widget->ui.ExposureTime->setValue(Util::NumToT<int>(ModuleData->CurrentExposureTime.count()));
451  }
452 
453  if (!Autofocusing && !ModuleData->ImageCapturingPaused)
454  {
456  if (Widget->ui.ImageModifiersGroupBox->isChecked())
457  {
458  // Values from controls ranging from -10 to 10.
459  ImageTransformation.BrightnessFactor = Widget->ui.ImageModifiersBrightness->value() / 10.f;
460  ImageTransformation.ContrastFactor = std::pow(10, Widget->ui.ImageModifiersContrast->value() / 10.f);
461  ImageTransformation.IsEnabled = true;
462  }
463 
464  {
465  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
466  CameraData->SetImageTransformation(ImageTransformation);
467  } // CameraData unlocked here.
468  }
469 
470  ModuleData->ComputeHistogram = Widget->GetComputeHistogram();
471 
472  if (!ModuleData->CurrentImage.isNull() && ModuleData->HasImageChanged)
473  {
474  // SetImage creates a deep copy of CurrentImage.
475  Widget->SetImage(ModuleData->CurrentImage);
476  ModuleData->HasImageChanged = false;
477 
478  if (ModuleData->ComputeHistogram == CHT::IntensityHistogram ||
479  ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
480  Widget->SetIntensityHistogram(std::move(ModuleData->IntensityHistogram));
481  if (ModuleData->ComputeHistogram == CHT::RGBHistogram ||
482  ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
483  Widget->SetRGBHistogram(std::move(ModuleData->RGBHistogram));
484 
485  Widget->UpdateScene();
486  }
487 
488  if (Ready && !Widget->GetSaveImageFilename().isEmpty())
489  {
490  ModuleData->Camera->StopCapturing();
491  MakeAndEnqueueEvent(this, &ImageViewer::OnSaveImage, Widget->GetSaveImageFilename());
492 
493  Widget->ResetSaveImageFilename();
494  }
495 
496  Widget->ui.action_Capture_continuously->setChecked(ModuleData->CapturingState == DynExpInstr::CameraData::CapturingStateType::CapturingContinuously);
497  Widget->ui.action_Autofocus->setChecked(Autofocusing);
498 
499  if (Autofocusing)
500  Widget->ui.CurrentFPS->setText("Autofocusing...");
502  Widget->ui.CurrentFPS->setText("Capturing frame...");
504  {
505 #ifdef DYNEXP_DEBUG
506  Widget->ui.CurrentFPS->setText("Capturing... (FPS: " + QString::number(ModuleData->CurrentFPS, 'f', 1) +
507  + ", Brenner gradient: " + QString::number(ModuleData->CalcBrennerGradientFromImage(), 'f', 3) + ")");
508 #else
509  Widget->ui.CurrentFPS->setText("Capturing... (FPS: " + QString::number(ModuleData->CurrentFPS, 'f', 1) + ")");
510 #endif // DYNEXP_DEBUG
511  }
512  else
513  Widget->ui.CurrentFPS->setText("Stopped");
514  }
515 
516  bool ImageViewer::IsReadyState() const
517  {
518  const auto CurrentState = StateMachine.GetCurrentState()->GetState();
519 
520  return CurrentState == StateType::Ready;
521  }
522 
523  bool ImageViewer::IsAutofocusingState() const
524  {
525  const auto CurrentState = StateMachine.GetCurrentState()->GetState();
526 
527  return CurrentState == StateType::AutofocusInit ||
528  CurrentState == StateType::AutofocusGotoSample ||
529  CurrentState == StateType::AutofocusWaitBeforeCapture ||
530  CurrentState == StateType::AutofocusWaitForImage ||
531  CurrentState == StateType::AutofocusStep ||
532  CurrentState == StateType::AutofocusFinished;
533  }
534 
535  void ImageViewer::FinishAutofocus(Util::SynchronizedPointer<ModuleDataType>& ModuleData, const FinishedAutofocusEvent& Event) const
536  {
537  if (ModuleData->Communicator.valid())
538  ModuleData->Communicator->PostEvent(*this, Event);
539 
540  if (ModuleData->CaptureAfterPause)
541  ModuleData->Camera->StartCapturing();
542  }
543 
544  void ImageViewer::OnInit(DynExp::ModuleInstance* Instance) const
545  {
549 
550  auto ModuleParams = DynExp::dynamic_Params_cast<ImageViewer>(Instance->ParamsGetter());
551  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
552 
553  Instance->LockObject(ModuleParams->Camera, ModuleData->Camera);
554  if (ModuleParams->Focus.ContainsID())
555  {
556  Instance->LockObject(ModuleParams->Focus, ModuleData->Focus);
557 
558  AutofocusParams.MinVoltage = ModuleData->Focus->GetUserMinValue();
559  AutofocusParams.MaxVoltage = ModuleData->Focus->GetUserMaxValue();
560  }
561  if (ModuleParams->Communicator.ContainsID())
562  Instance->LockObject(ModuleParams->Communicator, ModuleData->Communicator);
563 
564  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
565  ModuleData->CameraModes = CameraData->GetCameraModes();
566  }
567 
568  void ImageViewer::OnExit(DynExp::ModuleInstance* Instance) const
569  {
570  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
571 
572  Instance->UnlockObject(ModuleData->Camera);
573  Instance->UnlockObject(ModuleData->Focus);
574  Instance->UnlockObject(ModuleData->Communicator);
575 
579  }
580 
581  void ImageViewer::OnCameraModeChanged(DynExp::ModuleInstance* Instance, int Index) const
582  {
583  if (Index < 0)
584  return;
585 
586  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
587 
588  *PauseUpdatingUI = true;
589  ModuleData->Camera->SetCameraMode(Util::NumToT<size_t>(Index),
591  *Pause = false;
592  }
593  );
594  }
595 
596  void ImageViewer::OnExposureTimeChanged(DynExp::ModuleInstance* Instance, int Value) const
597  {
598  try
599  {
600  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
601 
602  if (ModuleData->Camera->CanSetExposureTime())
603  {
604  *PauseUpdatingUI = true;
605  ModuleData->Camera->SetExposureTime(std::chrono::milliseconds(Value),
607  *Pause = false;
608  }
609  );
610  }
611  }
612  catch ([[maybe_unused]] const Util::TimeoutException& e)
613  {
614  // Swallow since it is likely that this exception occurs here when the user
615  // very quickly scrolls through the range of exposure times. It may take
616  // time to transfer the new exposure time to the camera.
617  }
618  }
619 
620  void ImageViewer::OnCaptureSingle(DynExp::ModuleInstance* Instance, bool) const
621  {
622  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
623  ModuleData->ImageCapturingPaused = false;
624  ModuleData->CaptureAfterPause = false;
625 
626  ModuleData->Camera->CaptureSingle();
627  }
628 
629  void ImageViewer::OnCaptureContinuously(DynExp::ModuleInstance* Instance, bool Checked) const
630  {
631  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
632  ModuleData->ImageCapturingPaused = false;
633  ModuleData->CaptureAfterPause = false;
634 
635  if (Checked)
636  ModuleData->Camera->StartCapturing();
637  else
638  ModuleData->Camera->StopCapturing();
639  }
640 
641  void ImageViewer::OnPauseImageCapturing(DynExp::ModuleInstance* Instance, bool ResetImageTransformation) const
642  {
643  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
644 
645  {
646  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
647 
648  ModuleData->ImageCapturingPaused = true;
649  ModuleData->CaptureAfterPause = CameraData->IsCapturingContinuously();
650 
651  if (ResetImageTransformation)
652  CameraData->SetImageTransformation({});
653  } // CameraData unlocked here.
654 
655  if (ModuleData->Communicator.valid())
656  ModuleData->Communicator->PostEvent(*this, ImageCapturingPausedEvent{});
657  }
658 
659  void ImageViewer::OnResumeImageCapturing(DynExp::ModuleInstance* Instance) const
660  {
661  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
662 
663  if (!ModuleData->ImageCapturingPaused)
664  return;
665  ModuleData->ImageCapturingPaused = false;
666 
667  if (ModuleData->CaptureAfterPause)
668  ModuleData->Camera->StartCapturing();
669 
670  if (ModuleData->Communicator.valid())
671  ModuleData->Communicator->PostEvent(*this, ImageCapturingResumedEvent{});
672  }
673 
674  void ImageViewer::OnAutofocusClicked(DynExp::ModuleInstance* Instance, bool Checked) const
675  {
676  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
677 
678  if (!ModuleData->Focus.valid())
679  return;
680 
681  if (Checked)
682  OnAutofocus(Instance);
683  else
684  {
685  if (ModuleData->Communicator.valid())
686  ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
687 
688  StateMachine.SetCurrentState(StateType::Ready);
689  }
690  }
691 
692  void ImageViewer::OnAutofocus(DynExp::ModuleInstance* Instance, bool ResetImageTransformation) const
693  {
695 
696  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
697 
698  if (!ModuleData->Focus.valid())
699  {
700  if (ModuleData->Communicator.valid())
701  ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
702 
703  return;
704  }
705 
706  {
707  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
708 
709  if (ResetImageTransformation)
710  CameraData->SetImageTransformation({});
711 
712  ModuleData->CaptureAfterPause = CameraData->IsCapturingContinuously();
713  } // CameraData unlocked here.
714 
715  ModuleData->Camera->StopCapturingSync();
716 
717  {
718  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
719  CameraData->SetComputeHistogram(CHT::NoHistogram);
720  } // CameraData unlocked here.
721 
722  StateMachine.SetCurrentState(StateType::AutofocusInit);
723  }
724 
725  StateType ImageViewer::ReadyStateFunc(DynExp::ModuleInstance& Instance)
726  {
728 
729  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
730  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
731 
732  ModuleData->CapturingState = CameraData->GetCapturingState();
733  ModuleData->MinExposureTime = CameraData->GetMinExposureTime();
734  ModuleData->MaxExposureTime = CameraData->GetMaxExposureTime();
735  ModuleData->CurrentExposureTime = CameraData->GetExposureTime();
736  ModuleData->CurrentFPS = CameraData->GetCurrentFPS();
737 
738  CameraData->SetComputeHistogram(ModuleData->ComputeHistogram);
739 
740  if (CameraData->IsImageAvailbale() && !ModuleData->ImageCapturingPaused)
741  {
742  ModuleData->CurrentImage = CameraData->GetImage();
743  ModuleData->HasImageChanged = true;
744 
745  if (ModuleData->ComputeHistogram == CHT::IntensityHistogram ||
746  ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
747  ModuleData->IntensityHistogram = CameraData->GetIntensityHistogram();
748  if (ModuleData->ComputeHistogram == CHT::RGBHistogram ||
749  ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
750  ModuleData->RGBHistogram = CameraData->GetRGBHistogram();
751  }
752 
753  return StateType::Ready;
754  }
755 
756  StateType ImageViewer::AutofocusInitStateFunc(DynExp::ModuleInstance& Instance)
757  {
758  {
759  auto ModuleParams = DynExp::dynamic_Params_cast<ImageViewer>(Instance.ParamsGetter());
760 
761  AutofocusParams.NumSteps = ModuleParams->AutofocusNumSteps;
762  AutofocusParams.WaitTimeBeforeCapture = std::chrono::milliseconds(ModuleParams->AutofocusFocusChangeTime);
763  } // ModuleParams unlocked here.
764 
765  AutofocusResults = {};
767 
768  AutofocusSamples.clear();
769  for (double Voltage = AutofocusParams.MinVoltage; Voltage <= AutofocusParams.MaxVoltage; Voltage += AutofocusParams.GetVoltageIncrement())
770  AutofocusSamples.emplace_back(Voltage, std::numeric_limits<double>::quiet_NaN());
772 
774  }
775 
776  StateType ImageViewer::AutofocusGotoSampleStateFunc(DynExp::ModuleInstance& Instance)
777  {
780  else
781  {
782  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
783  ModuleData->Focus->SetSync(AutofocusCurrentSample->Voltage);
784 
785  AutofocusWaitingEndTimePoint = std::chrono::system_clock::now() + AutofocusParams.WaitTimeBeforeCapture;
786 
788  }
789  }
790 
791  StateType ImageViewer::AutofocusWaitBeforeCaptureStateFunc(DynExp::ModuleInstance& Instance)
792  {
793  if (std::chrono::system_clock::now() >= AutofocusWaitingEndTimePoint)
794  {
795  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
796  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
797 
798  CameraData->ClearImage();
799  ModuleData->Camera->CaptureSingle();
800 
802  }
803 
805  }
806 
807  StateType ImageViewer::AutofocusWaitForImageStateFunc(DynExp::ModuleInstance& Instance)
808  {
809  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
810  auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
811 
812  if (CameraData->IsImageAvailbale())
813  {
814  ModuleData->CurrentExposureTime = CameraData->GetExposureTime();
815  ModuleData->CurrentImage = CameraData->GetImage();
816  ModuleData->HasImageChanged = true;
817 
818  // Employing Brenner gradient as the figure of merit to maximize for autofocusing
819  // in the fine mode and the image's variance in the coarse mode.
820  // (S. Yazdanfar et al. Opt. Expres 16 (12), 8670-8677 (2008)).
821  AutofocusCurrentSample->Result = ModuleData->CalcBrennerGradientFromImage();
823 
825  }
826 
828  }
829 
830  StateType ImageViewer::AutofocusStepStateFunc(DynExp::ModuleInstance& Instance)
831  {
832  double OptimalVoltage = std::max_element(AutofocusSamples.cbegin(), AutofocusSamples.cend(), [](const auto& a, const auto& b) {
833  return a.Result < b.Result;
834  })->Voltage;
835 
837  {
839 
840  AutofocusSamples.clear();
841  for (double Voltage = std::max(AutofocusParams.MinVoltage, OptimalVoltage - AutofocusParams.GetVoltageIncrement());
842  Voltage <= std::min(AutofocusParams.MaxVoltage, OptimalVoltage + AutofocusParams.GetVoltageIncrement());
843  Voltage += AutofocusParams.GetVoltageIncrement(true))
844  AutofocusSamples.emplace_back(Voltage, std::numeric_limits<double>::quiet_NaN());
846 
848  }
849  else
850  {
851  AutofocusResults.Success = OptimalVoltage > AutofocusSamples.front().Voltage + AutofocusParams.GetVoltageIncrement(true) / 2.0 &&
852  OptimalVoltage < AutofocusSamples.back().Voltage - AutofocusParams.GetVoltageIncrement(true) / 2.0;
854  AutofocusResults.FocusVoltage = OptimalVoltage;
855  }
856 
858  }
859 
860  StateType ImageViewer::AutofocusFinishedStateFunc(DynExp::ModuleInstance& Instance)
861  {
862  auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
863 
865  ModuleData->Focus->SetSync(AutofocusResults.FocusVoltage);
866 
868 
869  return StateType::Ready;
870  }
871 }
Implementation of a module to display images recorded by camera instruments.
@ CapturingSingle
The camera is caturing a single image and will stop afterwards.
@ CapturingContinuously
The camera is capturing one image after the other.
@ Stopped
The camera is not capturing.
float BrightnessFactor
Factor to enhance the image brightness (between -1 and 1).
Definition: Camera.h:86
bool IsEnabled
Determines whether the image transformation is to be applied (enabled).
Definition: Camera.h:96
ComputeHistogramType
Type indicating whether histograms should be computed for newly captured images.
Definition: Camera.h:115
@ NoHistogram
Histogram computation is disabled.
float ContrastFactor
Factor to enhance the image contrast. Valid interval is (0, Inf).
Definition: Camera.h:91
Type describing an image transformation.
Definition: Camera.h:82
Util::ImageRGBHistogramType RGBHistogram
Definition: ImageViewer.h:130
void ResetImpl(dispatch_tag< QModuleDataBase >) override final
double CalcBrennerGradientFromImage() const
Calculates the Brenner gradient of CurrentImage. Refer to J. F. Brenner et al. J. Histochem....
DynExpInstr::CameraData::TimeType TimeType
Definition: ImageViewer.h:103
DynExpInstr::CameraData::CameraModesType CameraModes
Definition: ImageViewer.h:116
DynExpInstr::CameraData::ComputeHistogramType ComputeHistogram
Definition: ImageViewer.h:122
DynExpInstr::CameraData::CapturingStateType CapturingState
Definition: ImageViewer.h:117
Util::ImageHistogramType IntensityHistogram
Definition: ImageViewer.h:129
Util::ImageRGBHistogramType RGBHistogram
Definition: ImageViewer.h:86
void OnHistogramContextMenuRequested(const QPoint &Position)
ImageViewerWidget(ImageViewer &Owner, QModuleWidget *parent=nullptr)
Definition: ImageViewer.cpp:9
void SetImage(const QImage &NewImage) noexcept
Definition: ImageViewer.cpp:67
virtual void resizeEvent(QResizeEvent *event) override
Util::MarkerGraphicsView * GraphicsView
Definition: ImageViewer.h:80
bool eventFilter(QObject *obj, QEvent *event) override
void SetIntensityHistogram(Util::ImageHistogramType &&NewIntensityHistogram) noexcept
Definition: ImageViewer.cpp:79
void SetRGBHistogram(Util::ImageRGBHistogramType &&NewRGBHistogram) noexcept
Definition: ImageViewer.cpp:84
Util::ImageHistogramType IntensityHistogram
Definition: ImageViewer.h:85
void FinishAutofocus(Util::SynchronizedPointer< ModuleDataType > &ModuleData, const FinishedAutofocusEvent &Event) const
void OnResumeImageCapturing(DynExp::ModuleInstance *Instance) const
void OnAutofocus(DynExp::ModuleInstance *Instance, bool ResetImageTransformation=false) const
void OnCaptureSingle(DynExp::ModuleInstance *Instance, bool) const
void OnCaptureContinuously(DynExp::ModuleInstance *Instance, bool Checked) const
void OnExposureTimeChanged(DynExp::ModuleInstance *Instance, int Value) const
std::vector< AutofocusSampleType >::iterator AutofocusCurrentSample
Definition: ImageViewer.h:283
std::vector< AutofocusSampleType > AutofocusSamples
Definition: ImageViewer.h:282
void OnAutofocusClicked(DynExp::ModuleInstance *Instance, bool Checked) const
AutofocusResultsType AutofocusResults
Definition: ImageViewer.h:280
const std::shared_ptr< std::atomic< bool > > PauseUpdatingUI
Definition: ImageViewer.h:286
Util::StateMachine< StateMachineStateType > StateMachine
Definition: ImageViewer.h:276
void OnCameraModeChanged(DynExp::ModuleInstance *Instance, int Index) const
void OnSaveImage(DynExp::ModuleInstance *Instance, QString Filename) const
std::chrono::system_clock::time_point AutofocusWaitingEndTimePoint
Definition: ImageViewer.h:284
void OnPauseImageCapturing(DynExp::ModuleInstance *Instance, bool ResetImageTransformation=false) const
Wrapper holding a pointer to an exception and providing functionality for accessing it....
Definition: Instrument.h:86
static void Register(const ModuleBase &Listener, CallableT EventFunc)
Registers/Subscribes module Listener to the event with the event function EventFunc....
Definition: Module.h:1028
static void Deregister(const ModuleBase &Listener)
Deregisters/unsubscribes module Listener from the event. Indirectly calls ModuleBase::RemoveRegistere...
Definition: Module.h:1034
const std::unique_ptr< ModuleDataType > ModuleData
Module data belonging to this ModuleBase instance.
Definition: Module.h:743
void MakeAndEnqueueEvent(ReceiverType *Receiver, EventType EventFuncPtr, ArgsTs &&...Args) const
Calls MakeEvent() to construct a new event and subsequently enqueues the event into the module's even...
Definition: Module.h:780
Refer to ParamsBase::dispatch_tag.
Definition: Module.h:189
Defines data for a thread belonging to a ModuleBase instance. Refer to RunnableInstance.
Definition: Module.h:793
const ModuleBase::ModuleDataGetterType ModuleDataGetter
Getter for module's data. Refer to ModuleBase::ModuleDataGetterType.
Definition: Module.h:825
Refer to ParamsBase::dispatch_tag.
Definition: Object.h:2018
QModuleWidget * Widget
User interface widget belonging to the module.
Definition: Module.h:1491
void Connect(SenderType *Sender, SignalType Signal, ReceiverType *Receiver, EventType Event)
Uses Qt's connect mechanism to connect a QObject's signal to a DynExp module's event....
Definition: Module.h:1500
const Object::ParamsGetterType ParamsGetter
Invoke to obtain the parameters (derived from ParamsBase) of Owner.
Definition: Object.h:3671
void UnlockObject(LinkedObjectWrapperContainer< ObjectT > &ObjectWrapperContainer)
Unlocks an Object instance stored in the LinkedObjectWrapperContainer ObjectWrapperContainer....
Definition: Object.h:3570
void LockObject(const ParamsBase::Param< ObjectLink< ObjectT >> &LinkParam, LinkedObjectWrapperContainer< ObjectT > &ObjectWrapperContainer, std::chrono::milliseconds Timeout=ObjectLinkBase::LockObjectTimeoutDefault)
Locks an Object instance referenced by a parameter LinkParam of type ParamsBase::Param< ObjectLink< O...
Definition: Object.h:3554
const auto & GetOwner() const noexcept
Returns Owner.
Definition: Object.h:3524
Base class for all tasks being processed by instruments. The class must not contain public virtual fu...
Definition: Instrument.h:892
void Log(const std::string &Message, const ErrorType Type=ErrorType::Info, const size_t Line=0, const std::string &Function="", const std::string &File="", const int ErrorCode=0, const std::stacktrace &Trace={}) noexcept
Logs an event from information specified manually.
Definition: Util.cpp:309
Implements a QGraphicsView the user can interact with to insert graphical markers....
Definition: QtUtil.h:379
void ZoomReset()
Resets the zoom.
Definition: QtUtil.cpp:522
void ZoomIn()
Zooms in one step.
Definition: QtUtil.cpp:512
void ZoomOut()
Zooms out one step.
Definition: QtUtil.cpp:517
Pointer to lock a class derived from ISynchronizedPointerLockable for synchronizing between threads....
Definition: Util.h:170
Thrown when an operation timed out before it could be completed, especially used for locking shared d...
Definition: Exception.h:261
const QColor green("lime")
const QColor blue(42, 130, 218)
constexpr auto DefaultQChartTheme
std::unique_ptr< ParamsBase > ParamsBasePtrType
Alias for a pointer to the parameter system base class ParamsBase.
Definition: Object.h:1807
DynExpErrorCodes
DynExp's error codes
Definition: Exception.h:22
EventLogger & EventLog()
This function holds a static EventLogger instance and returns a reference to it. DynExp uses only one...
Definition: Util.cpp:509
std::array< unsigned long long, 256 > ImageHistogramType
Alias which represents a histogram as a std::array with 256 numeric bins. The lowest (highest) index ...
Definition: QtUtil.h:244
std::tuple< ImageHistogramType, ImageHistogramType, ImageHistogramType > ImageRGBHistogramType
Alias which represents a RGB histogram as a std::tuple of three ImageHistogramType elements....
Definition: QtUtil.h:250
QString PromptSaveFilePathModule(DynExp::QModuleWidget *Parent, const QString &Title, const QString &DefaultSuffix, const QString &NameFilter)
Works as PromptOpenFilePath() but asks the user to select a single file which does not need to exist....
Definition: QtUtil.cpp:143
Accumulates include statements to provide a precompiled header.
DynExpInstr::AnalogOutData::SampleStreamType::SampleType MinVoltage
Definition: ImageViewer.h:216
constexpr auto GetVoltageIncrement(bool Fine=false) const noexcept
Definition: ImageViewer.h:222
DynExpInstr::AnalogOutData::SampleStreamType::SampleType MaxVoltage
Definition: ImageViewer.h:217