DynExp
Highly flexible laboratory automation for dynamically changing experiments.
Loading...
Searching...
No Matches
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);
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())
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.
149 HistogramBarSetI = nullptr;
152 HistogramBarSetR = nullptr;
155 HistogramBarSetG = nullptr;
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 {
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
216
218 HistogramYAxis->setMax(MaxValue);
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);
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
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
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
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
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
517 {
518 const auto CurrentState = StateMachine.GetCurrentState()->GetState();
519
520 return CurrentState == StateType::Ready;
521 }
522
524 {
525 const auto CurrentState = StateMachine.GetCurrentState()->GetState();
526
527 return CurrentState == StateType::AutofocusInit ||
528 CurrentState == StateType::AutofocusGotoSample ||
530 CurrentState == StateType::AutofocusWaitForImage ||
531 CurrentState == StateType::AutofocusStep ||
532 CurrentState == StateType::AutofocusFinished;
533 }
534
536 {
537 if (ModuleData->Communicator.valid())
538 ModuleData->Communicator->PostEvent(*this, Event);
539
540 if (ModuleData->CaptureAfterPause)
541 ModuleData->Camera->StartCapturing();
542 }
543
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
DynExpInstr::CameraData::CameraModesType CameraModes
DynExpInstr::CameraData::ComputeHistogramType ComputeHistogram
DynExpInstr::CameraData::CapturingStateType CapturingState
Util::ImageHistogramType IntensityHistogram
Util::ImageRGBHistogramType RGBHistogram
Definition ImageViewer.h:86
void OnHistogramContextMenuRequested(const QPoint &Position)
ImageViewerWidget(ImageViewer &Owner, QModuleWidget *parent=nullptr)
void SetImage(const QImage &NewImage) noexcept
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
void SetRGBHistogram(Util::ImageRGBHistogramType &&NewRGBHistogram) noexcept
Util::ImageHistogramType IntensityHistogram
Definition ImageViewer.h:85
StateType AutofocusStepStateFunc(DynExp::ModuleInstance &Instance)
StateType AutofocusInitStateFunc(DynExp::ModuleInstance &Instance)
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
StateType AutofocusFinishedStateFunc(DynExp::ModuleInstance &Instance)
void OnCaptureSingle(DynExp::ModuleInstance *Instance, bool) const
std::unique_ptr< DynExp::QModuleWidget > MakeUIWidget() override final
Used by InitUI() as a factory function for the module's user interface widget. Create the widget here...
void OnInit(DynExp::ModuleInstance *Instance) const override final
This event is triggered right before the module thread starts. Override it to lock instruments this m...
Util::DynExpErrorCodes::DynExpErrorCodes ModuleMainLoop(DynExp::ModuleInstance &Instance) override final
Module main loop. The function is executed periodically by the module thread. Also refer to GetMainLo...
void OnCaptureContinuously(DynExp::ModuleInstance *Instance, bool Checked) const
StateType ReadyStateFunc(DynExp::ModuleInstance &Instance)
StateType AutofocusGotoSampleStateFunc(DynExp::ModuleInstance &Instance)
void OnExposureTimeChanged(DynExp::ModuleInstance *Instance, int Value) const
void OnExit(DynExp::ModuleInstance *Instance) const override final
This event is triggered right before the module thread terminates (not due to an exception,...
StateType AutofocusWaitForImageStateFunc(DynExp::ModuleInstance &Instance)
std::vector< AutofocusSampleType >::iterator AutofocusCurrentSample
std::vector< AutofocusSampleType > AutofocusSamples
StateType AutofocusWaitBeforeCaptureStateFunc(DynExp::ModuleInstance &Instance)
void OnAutofocusClicked(DynExp::ModuleInstance *Instance, bool Checked) const
void UpdateUIChild(const ModuleBase::ModuleDataGetterType &ModuleDataGetter) override final
const std::shared_ptr< std::atomic< bool > > PauseUpdatingUI
Util::StateMachine< StateMachineStateType > StateMachine
void ResetImpl(dispatch_tag< QModuleBase >) override final
void OnCameraModeChanged(DynExp::ModuleInstance *Instance, int Index) const
void OnSaveImage(DynExp::ModuleInstance *Instance, QString Filename) const
std::chrono::system_clock::time_point AutofocusWaitingEndTimePoint
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
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
constexpr auto GetVoltageIncrement(bool Fine=false) const noexcept
DynExpInstr::AnalogOutData::SampleStreamType::SampleType MaxVoltage