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 "ui_ImageViewer.h"
6#include "ImageViewer.h"
7
9{
11 : QModuleWidget(Owner, parent),
12 ui(std::make_unique<Ui::ImageViewer>()),
13 HistogramContextMenu(new QMenu(this)), HistogramLinLogActionGroup(new QActionGroup(this)),
14 HistogramGraph(nullptr),
15 GraphicsView(nullptr), GraphicsPixmapItem(nullptr), GraphicsScene(new QGraphicsScene(this))
16 {
17 ui->setupUi(this);
18
19 ui->action_Zoom_fit->setChecked(true);
20
21 HistogramLinAction = HistogramLinLogActionGroup->addAction("&Linear");
22 HistogramLinAction->setCheckable(true);
23 HistogramLinAction->setChecked(true);
24 HistogramLogAction = HistogramLinLogActionGroup->addAction("Lo&garithmic");
25 HistogramLogAction->setCheckable(true);
27 HistogramContextMenu->addSeparator();
28 HistogramBWAction = HistogramContextMenu->addAction("Show &intensity");
29 HistogramBWAction->setCheckable(true);
30 HistogramBWAction->setChecked(true);
31 HistogramColorAction = HistogramContextMenu->addAction("Show &colors");
32 HistogramColorAction->setCheckable(true);
33
34 // Graph to display histogram
35 connect(ui->Histogram, &QQuickWidget::statusChanged, [this](QQuickWidget::Status Status) {
36 if (Status == QQuickWidget::Status::Ready && ui->Histogram->rootObject())
37 {
38 auto BackendVariant = ui->Histogram->rootObject()->property("backend");
39 HistogramGraph = BackendVariant.isValid() ? BackendVariant.value<DynExpQuick::DynExpLineGraphBackend*>() : nullptr;
40
41 if (HistogramGraph)
42 {
43 HistogramGraph->InsertSeries("I", QColorConstants::White);
44 HistogramGraph->InsertSeries("R", QColorConstants::Red);
45 HistogramGraph->InsertSeries("G", QColorConstants::Green);
46 HistogramGraph->InsertSeries("B", QColorConstants::Blue);
47 }
48 }
49 });
50 ui->Histogram->loadFromModule("Modules.DynExpQuick", "QDynExpLineGraph");
51 HistogramPlotInfo.XUnit = DynExp::Units::UnitType::UnitlessInt;
52 HistogramPlotInfo.XLabel = "pixel value";
53
54 GraphicsView = new Util::MarkerGraphicsView(ui->MainSplitter);
55 GraphicsView->setObjectName(QString::fromUtf8("Image"));
56 QSizePolicy ImageSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
57 ImageSizePolicy.setHorizontalStretch(1);
58 ImageSizePolicy.setVerticalStretch(0);
59 ImageSizePolicy.setHeightForWidth(GraphicsView->sizePolicy().hasHeightForWidth());
60 GraphicsView->setSizePolicy(ImageSizePolicy);
61 GraphicsView->setMinimumSize(QSize(400, 300));
62 GraphicsView->viewport()->setProperty("cursor", QVariant(QCursor(Qt::CrossCursor)));
63 GraphicsView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
64
65 GraphicsPixmapItem = GraphicsScene->addPixmap(Pixmap);
66 GraphicsView->setScene(GraphicsScene);
67 GraphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
68 GraphicsView->viewport()->installEventFilter(this);
69 GraphicsView->viewport()->setMouseTracking(true);
70 }
71
72 void ImageViewerWidget::SetImage(const QImage& NewImage) noexcept
73 {
74 Pixmap = QPixmap::fromImage(NewImage); // deep copy
75 Pixmap.detach(); // just to be sure...
76 }
77
78 void ImageViewerWidget::SetImageViewEnabled(bool Enable)
79 {
80 if (GraphicsView)
81 GraphicsView->setEnabled(Enable);
82 }
83
84 void ImageViewerWidget::SetIntensityHistogram(Util::ImageHistogramType&& NewIntensityHistogram) noexcept
85 {
86 IntensityHistogram = std::move(NewIntensityHistogram);
87 }
88
89 void ImageViewerWidget::SetRGBHistogram(Util::ImageRGBHistogramType&& NewRGBHistogram) noexcept
90 {
91 RGBHistogram = std::move(NewRGBHistogram);
92 }
93
94 auto ImageViewerWidget::GetComputeHistogram() const noexcept
95 {
97
98 if (!ui->ExposureTimeGroupBox->isVisible() || ui->ExposureTimeGroupBox->visibleRegion().isEmpty())
99 return CHT::NoHistogram;
100
101 if (HistogramBWAction->isChecked())
102 return HistogramColorAction->isChecked() ? CHT::IntensityAndRGBHistogram : CHT::IntensityHistogram;
103 if (HistogramColorAction->isChecked())
104 return CHT::RGBHistogram;
105
106 return CHT::NoHistogram;
107 }
108
109 void ImageViewerWidget::UpdateScene()
110 {
111 if (GraphicsView->isVisible() && !GraphicsView->visibleRegion().isEmpty() && !Pixmap.isNull())
112 {
113 GraphicsPixmapItem->setPixmap(Pixmap);
114 GraphicsScene->update();
115
116 OnZoomFitClicked(ui->action_Zoom_fit->isChecked());
117 }
118
119 ui->ImageGeometry->setText(QString::number(Pixmap.width()) + " x " + QString::number(Pixmap.height()));
120 }
121
122 void ImageViewerWidget::UpdateHistogram()
123 {
124 if (ui->ExposureTimeGroupBox->isVisible() && !ui->ExposureTimeGroupBox->visibleRegion().isEmpty())
125 UpdateHistogramImpl();
126 }
127
128 bool ImageViewerWidget::eventFilter(QObject* obj, QEvent* event)
129 {
130 if (GraphicsView->viewport())
131 if (event->type() == QEvent::MouseMove)
132 {
133 OnImageMouseMove(static_cast<QMouseEvent*>(event));
134
135 return true;
136 }
137
138 return QObject::eventFilter(obj, event);
139 }
140
141 void ImageViewerWidget::resizeEvent(QResizeEvent* event)
142 {
143 OnZoomFitClicked(ui->action_Zoom_fit->isChecked());
144 }
145
146 void ImageViewerWidget::UpdateHistogramImpl()
147 {
149 auto ComputeHistogram = GetComputeHistogram();
150
151 bool LogPlot = HistogramLogAction->isChecked();
152 QList<QPointF> SeriesI, SeriesR, SeriesG, SeriesB;
153 for (int i = 0; i < static_cast<int>(IntensityHistogram.size()); ++i)
154 {
155 if (ComputeHistogram == CHT::IntensityHistogram ||
156 ComputeHistogram == CHT::IntensityAndRGBHistogram)
157 {
158 // Add .1 in log case to avoid NaN if value is 0.
159 SeriesI << QPointF(i, (LogPlot ? std::log10(IntensityHistogram[i] + .1) : IntensityHistogram[i]));
160 }
161
162 if (ComputeHistogram == CHT::RGBHistogram ||
163 ComputeHistogram == CHT::IntensityAndRGBHistogram)
164 {
165 // Add .1 in log case to avoid NaN if value is 0.
166 SeriesR << QPointF(i, (LogPlot ? std::log10(std::get<0>(RGBHistogram)[i] + .1) : std::get<0>(RGBHistogram)[i]));
167 SeriesG << QPointF(i, (LogPlot ? std::log10(std::get<1>(RGBHistogram)[i] + .1) : std::get<1>(RGBHistogram)[i]));
168 SeriesB << QPointF(i, (LogPlot ? std::log10(std::get<2>(RGBHistogram)[i] + .1) : std::get<2>(RGBHistogram)[i]));
169 }
170 }
171
172 HistogramPlotInfo.Reset();
173 ProcessedSeriesI.clear();
174 ProcessedSeriesR.clear();
175 ProcessedSeriesG.clear();
176 ProcessedSeriesB.clear();
177 if (ComputeHistogram == CHT::IntensityHistogram ||
178 ComputeHistogram == CHT::IntensityAndRGBHistogram)
179 {
180 HistogramPlotInfo.ProcessSamples(SeriesI, ProcessedSeriesI, 0);
181 }
182
183 if (ComputeHistogram == CHT::RGBHistogram ||
184 ComputeHistogram == CHT::IntensityAndRGBHistogram)
185 {
186 HistogramPlotInfo.ProcessSamples(SeriesR, ProcessedSeriesR, 1);
187 HistogramPlotInfo.ProcessSamples(SeriesG, ProcessedSeriesG, 2);
188 HistogramPlotInfo.ProcessSamples(SeriesB, ProcessedSeriesB, 3);
189 }
190
192 HistogramPlotInfo.YLabel = QString(LogPlot ? "log. " : "") + "counts";
193 HistogramPlotInfo.AdjustAxesLimits();
194
195 HistogramGraph->UpdateSeries(0, ProcessedSeriesI, HistogramPlotInfo);
196 HistogramGraph->UpdateSeries(1, ProcessedSeriesR, HistogramPlotInfo);
197 HistogramGraph->UpdateSeries(2, ProcessedSeriesG, HistogramPlotInfo);
198 HistogramGraph->UpdateSeries(3, ProcessedSeriesB, HistogramPlotInfo);
199 HistogramGraph->UpdateData(HistogramPlotInfo);
200 }
201
202 void ImageViewerWidget::OnHistogramContextMenuRequested(const QPoint& Position)
203 {
204 HistogramContextMenu->exec(ui->Histogram->mapToGlobal(Position));
205 }
206
207 void ImageViewerWidget::OnSaveImageClicked()
208 {
209 auto Filename = Util::PromptSaveFilePathModule(this, "Save image", ".png", "Portable Network Graphics image (*.png)");
210 if (Filename.isEmpty())
211 return;
212
213 SaveImageFilename = Filename;
214 }
215
216 void ImageViewerWidget::OnZoomResetClicked()
217 {
218 ui->action_Zoom_fit->setChecked(false);
219 GraphicsView->ZoomReset();
220 }
221
222 void ImageViewerWidget::OnZoomInClicked()
223 {
224 ui->action_Zoom_fit->setChecked(false);
225 GraphicsView->ZoomIn();
226 }
227
228 void ImageViewerWidget::OnZoomOutClicked()
229 {
230 ui->action_Zoom_fit->setChecked(false);
231 GraphicsView->ZoomOut();
232 }
233
234 void ImageViewerWidget::OnZoomFitClicked(bool Checked)
235 {
236 if (Checked)
237 GraphicsView->fitInView(GraphicsPixmapItem, Qt::AspectRatioMode::KeepAspectRatio);
238 }
239
240 void ImageViewerWidget::OnImageMouseMove(QMouseEvent* Event)
241 {
242 auto LocalPoint = GraphicsView->mapFromGlobal(Event->globalPosition().toPoint());
243
244 if (!GraphicsView->items(LocalPoint).empty())
245 {
246 auto Point = GraphicsView->mapToScene(LocalPoint).toPoint();
247
248 ui->CursorPosition->setText("X:" + QString::number(Point.x()) + ", Y:" + QString::number(Point.y()));
249 }
250 }
251
252 void ImageViewerData::ResetImpl(dispatch_tag<QModuleDataBase>)
253 {
254 Init();
255 }
256
262 double ImageViewerData::CalcBrennerGradientFromImage() const
263 {
264 if (CurrentImage.isNull())
265 return std::numeric_limits<double>::quiet_NaN();
266
267 const auto GrayImage = CurrentImage.convertToFormat(QImage::Format::Format_Grayscale8);
268 auto RawImage = GrayImage.constBits();
269
270 double BrennerGradient = .0;
271 for (int y = 0; y < GrayImage.height(); ++y)
272 {
273 for (int x = 0; x < GrayImage.width() - 2; ++x)
274 {
275 BrennerGradient += std::pow((static_cast<double>(*RawImage) - static_cast<double>(*(RawImage + 2))) / 255.0, 2);
276
277 ++RawImage;
278 }
279
280 RawImage += 2;
281 }
282
283 // Multiply by 1000 for convenience since we are normally dealing with very small gradients.
284 return BrennerGradient / GrayImage.width() / GrayImage.height() * 1e3;
285 }
286
287 void ImageViewerData::Init()
288 {
289 UIInitialized = false;
290
291 CameraModes.clear();
293 TimeType MinExposureTime = TimeType();
294 TimeType MaxExposureTime = TimeType();
295 TimeType CurrentExposureTime = TimeType();
296 CurrentFPS = 0.f;
298 AutoSaveFilename.clear();
299
300 CurrentImage = QImage();
301 HasImageChanged = false;
302 ImageCapturingPaused = false;
303 CaptureAfterPause = false;
304
305 IntensityHistogram = {};
306 RGBHistogram = {};
307 }
308
309 ImageViewer::ImageViewer(const std::thread::id OwnerThreadID, DynExp::ParamsBasePtrType&& Params)
310 : QModuleBase(OwnerThreadID, std::move(Params)),
311 StateMachine(ReadyState,
312 AutofocusInitState, AutofocusGotoSampleState, AutofocusWaitBeforeCaptureState,
313 AutofocusWaitForImageState, AutofocusStepState, AutofocusFinishedState),
314 PauseUpdatingUI(std::make_shared<std::atomic<bool>>(false))
315 {
316 }
317
321
322 void ImageViewer::OnSaveImage(DynExp::ModuleInstance* Instance, QString Filename) const
323 {
324 QImage Image;
325 {
326 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
327 Image = ModuleData->CurrentImage.copy();
328 } // ModuleData unlocked here for heavy save operation.
329
330 if (!Image.save(Filename))
331 Util::EventLog().Log("[ImageViewer] Saving image as \"" + Filename.toStdString() + "\" to file failed.", Util::ErrorType::Error);
332 }
333
335 {
336 try
337 {
338 StateMachine.Invoke(*this, Instance);
339
341 }
342 catch (const Util::TimeoutException& e)
343 {
344 if (NumFailedUpdateAttempts++ >= 3)
345 Instance.GetOwner().SetWarning(e);
346 }
347
349 }
350
364
365 std::unique_ptr<DynExp::QModuleWidget> ImageViewer::MakeUIWidget()
366 {
367 auto Widget = std::make_unique<ImageViewerWidget>(*this);
368
369 Connect(Widget->ui->CBCameraMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageViewer::OnCameraModeChanged);
370 Connect(Widget->ui->ExposureTime, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageViewer::OnExposureTimeChanged);
371 Connect(Widget->ui->action_Capture_Frame, &QAction::triggered, this, &ImageViewer::OnCaptureSingle);
372 Connect(Widget->ui->action_Capture_continuously, &QAction::triggered, this, &ImageViewer::OnCaptureContinuously);
373 Connect(Widget->ui->action_Autofocus, &QAction::triggered, this, &ImageViewer::OnAutofocusClicked);
374
375 return Widget;
376 }
377
378 void ImageViewer::UpdateUIChild(const ModuleBase::ModuleDataGetterType& ModuleDataGetter)
379 {
381
382 if (*PauseUpdatingUI)
383 return;
384
385 auto Widget = GetWidget<ImageViewerWidget>();
386 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(ModuleDataGetter());
387
388 // ModuleData is locked now. This syncs writing CameraData's settings with OnAutofocus().
389 const bool Ready = IsReadyState();
390 const bool Autofocusing = IsAutofocusingState();
391
392 if (!ModuleData->UIInitialized)
393 {
394 Widget->ui->action_Autofocus->setEnabled(ModuleData->Focus.valid());
395
396 if (ModuleData->CameraModes.empty())
397 Widget->ui->CameraModeGroupBox->setVisible(false);
398 else
399 {
400 for (const auto& Mode : ModuleData->CameraModes)
401 {
402 Widget->ui->CBCameraMode->insertItem(Widget->ui->CBCameraMode->count(), QString::fromStdString(Mode));
403 Widget->ui->CBCameraMode->setItemData(Widget->ui->CBCameraMode->count() - 1, QString::fromStdString(Mode), Qt::ToolTipRole);
404 }
405
406 // Triggers QComboBox::currentIndexChanged().
407 Widget->ui->CBCameraMode->setCurrentIndex(0);
408 }
409
410 ModuleData->UIInitialized = true;
411 }
412
413 Widget->ui->action_Save_Image->setEnabled(Ready);
414 Widget->ui->action_Capture_Frame->setEnabled(Ready);
415 Widget->ui->action_Capture_continuously->setEnabled(Ready);
416 Widget->ui->CameraModeGroupBox->setEnabled(Ready);
417 Widget->ui->ImageModifiersGroupBox->setEnabled(Ready);
418 Widget->ui->Histogram->setEnabled(Ready);
419 Widget->SetImageViewEnabled(Ready);
420
421 Widget->ui->ExposureTimeGroupBox->setEnabled(Ready && ModuleData->Camera->CanSetExposureTime());
422 Widget->ui->ExposureTimeMinValue->setText("min. " + QString::number(ModuleData->MinExposureTime.count()) + " ms");
423 Widget->ui->ExposureTimeMaxValue->setText("max. " + QString::number(ModuleData->MaxExposureTime.count()) + " ms");
424 Widget->ui->ExposureTime->setMinimum(ModuleData->MinExposureTime.count());
425 Widget->ui->ExposureTime->setMaximum(ModuleData->MaxExposureTime.count());
426
427 if (!Widget->ui->ExposureTime->hasFocus())
428 {
429 const QSignalBlocker Blocker(Widget->ui->ExposureTime);
430 Widget->ui->ExposureTime->setValue(Util::NumToT<int>(ModuleData->CurrentExposureTime.count()));
431 }
432
433 if (!Autofocusing && !ModuleData->ImageCapturingPaused)
434 {
436 if (Widget->ui->ImageModifiersGroupBox->isChecked())
437 {
438 // Values from controls ranging from -10 to 10.
439 ImageTransformation.BrightnessFactor = Widget->ui->ImageModifiersBrightness->value() / 10.f;
440 ImageTransformation.ContrastFactor = std::pow(10, Widget->ui->ImageModifiersContrast->value() / 10.f);
441 ImageTransformation.IsEnabled = true;
442 }
443
444 {
445 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
446 CameraData->SetImageTransformation(ImageTransformation);
447 } // CameraData unlocked here.
448 }
449
450 ModuleData->ComputeHistogram = Widget->GetComputeHistogram();
451
452 if (!ModuleData->CurrentImage.isNull() && ModuleData->HasImageChanged)
453 {
454 // SetImage creates a deep copy of CurrentImage.
455 Widget->SetImage(ModuleData->CurrentImage);
456 ModuleData->HasImageChanged = false;
457
458 if (ModuleData->ComputeHistogram == CHT::IntensityHistogram ||
459 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
460 Widget->SetIntensityHistogram(std::move(ModuleData->IntensityHistogram));
461 if (ModuleData->ComputeHistogram == CHT::RGBHistogram ||
462 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
463 Widget->SetRGBHistogram(std::move(ModuleData->RGBHistogram));
464
465 Widget->UpdateScene();
466 }
467
468 Widget->UpdateHistogram();
469
470 if (Ready && !Widget->GetSaveImageFilename().isEmpty())
471 {
472 ModuleData->Camera->StopCapturing();
473 MakeAndEnqueueEvent(this, &ImageViewer::OnSaveImage, Widget->GetSaveImageFilename());
474
475 Widget->ResetSaveImageFilename();
476 }
477
478 Widget->ui->action_Capture_continuously->setChecked(ModuleData->CapturingState == DynExpInstr::CameraData::CapturingStateType::CapturingContinuously);
479 Widget->ui->action_Autofocus->setChecked(Autofocusing);
480
481 if (Autofocusing)
482 Widget->ui->CurrentFPS->setText("Autofocusing...");
484 Widget->ui->CurrentFPS->setText("Capturing frame...");
486 {
487#ifdef DYNEXP_DEBUG
488 Widget->ui->CurrentFPS->setText("Capturing... (FPS: " + QString::number(ModuleData->CurrentFPS, 'f', 1) +
489 + ", Brenner gradient: " + QString::number(ModuleData->CalcBrennerGradientFromImage(), 'f', 3) + ")");
490#else
491 Widget->ui->CurrentFPS->setText("Capturing... (FPS: " + QString::number(ModuleData->CurrentFPS, 'f', 1) + ")");
492#endif // DYNEXP_DEBUG
493 }
494 else
495 Widget->ui->CurrentFPS->setText("Stopped");
496 }
497
499 {
500 const auto CurrentState = StateMachine.GetCurrentState()->GetState();
501
502 return CurrentState == StateType::Ready;
503 }
504
506 {
507 const auto CurrentState = StateMachine.GetCurrentState()->GetState();
508
509 return CurrentState == StateType::AutofocusInit ||
510 CurrentState == StateType::AutofocusGotoSample ||
512 CurrentState == StateType::AutofocusWaitForImage ||
513 CurrentState == StateType::AutofocusStep ||
514 CurrentState == StateType::AutofocusFinished;
515 }
516
518 {
519 if (ModuleData->Communicator.valid())
520 ModuleData->Communicator->PostEvent(*this, Event);
521
522 if (ModuleData->CaptureAfterPause)
523 ModuleData->Camera->StartCapturing();
524 }
525
527 {
534
535 auto ModuleParams = DynExp::dynamic_Params_cast<ImageViewer>(Instance->ParamsGetter());
536 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
537
538 Instance->LockObject(ModuleParams->Camera, ModuleData->Camera);
539 if (ModuleParams->Focus.ContainsID())
540 {
541 Instance->LockObject(ModuleParams->Focus, ModuleData->Focus);
542
543 AutofocusParams.MinVoltage = ModuleData->Focus->GetUserMinValue();
544 AutofocusParams.MaxVoltage = ModuleData->Focus->GetUserMaxValue();
545 }
546 if (ModuleParams->Communicator.ContainsID())
547 Instance->LockObject(ModuleParams->Communicator, ModuleData->Communicator);
548
549 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
550 ModuleData->CameraModes = CameraData->GetCameraModes();
551 }
552
554 {
555 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
556
557 Instance->UnlockObject(ModuleData->Camera);
558 Instance->UnlockObject(ModuleData->Focus);
559 Instance->UnlockObject(ModuleData->Communicator);
560
567 }
568
570 {
571 if (Index < 0)
572 return;
573
574 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
575
576 *PauseUpdatingUI = true;
577 ModuleData->Camera->SetCameraMode(Util::NumToT<size_t>(Index),
579 *Pause = false;
580 }
581 ));
582 }
583
585 {
586 try
587 {
588 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
589
590 if (ModuleData->Camera->CanSetExposureTime())
591 {
592 *PauseUpdatingUI = true;
593 ModuleData->Camera->SetExposureTime(std::chrono::milliseconds(Value),
595 *Pause = false;
596 }
597 ));
598 }
599 }
600 catch ([[maybe_unused]] const Util::TimeoutException& e)
601 {
602 // Swallow since it is likely that this exception occurs here when the user
603 // very quickly scrolls through the range of exposure times. It may take
604 // time to transfer the new exposure time to the camera.
605 }
606 }
607
609 {
610 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
611 ModuleData->AutoSaveFilename.clear();
612 ModuleData->ImageCapturingPaused = false;
613 ModuleData->CaptureAfterPause = false;
614
615 ModuleData->Camera->CaptureSingle();
616 }
617
619 {
620 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
621 ModuleData->AutoSaveFilename.clear();
622 ModuleData->ImageCapturingPaused = false;
623 ModuleData->CaptureAfterPause = false;
624
625 if (Checked)
626 ModuleData->Camera->StartCapturing();
627 else
628 ModuleData->Camera->StopCapturing();
629 }
630
631 void ImageViewer::OnSetFilename(DynExp::ModuleInstance* Instance, const std::string& SaveFilename) const
632 {
633 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
634
635 OnStop(Instance);
636 ModuleData->AutoSaveFilename = SaveFilename + ".png";
637 }
638
640 {
641 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
642 // Keep ModuleData->AutoSaveFilename to save the image.
643 ModuleData->ImageCapturingPaused = false;
644 ModuleData->CaptureAfterPause = false;
645
646 ModuleData->Camera->CaptureSingle();
647 }
648
650 {
651 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
652
653 if (IsReadyState())
654 OnCaptureContinuously(Instance, false);
655 else
656 {
657 StateMachine.SetCurrentState(StateType::Ready);
658 ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
659 }
660 }
661
662 void ImageViewer::OnPauseImageCapturing(DynExp::ModuleInstance* Instance, bool ResetImageTransformation) const
663 {
664 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
665
666 {
667 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
668
669 ModuleData->ImageCapturingPaused = true;
670 ModuleData->CaptureAfterPause = CameraData->IsCapturingContinuously();
671
672 if (ResetImageTransformation)
673 CameraData->SetImageTransformation({});
674 } // CameraData unlocked here.
675
676 if (ModuleData->Communicator.valid())
677 ModuleData->Communicator->PostEvent(*this, ImageCapturingPausedEvent{});
678 }
679
681 {
682 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
683
684 if (!ModuleData->ImageCapturingPaused)
685 return;
686 ModuleData->ImageCapturingPaused = false;
687
688 if (ModuleData->CaptureAfterPause)
689 ModuleData->Camera->StartCapturing();
690
691 if (ModuleData->Communicator.valid())
692 ModuleData->Communicator->PostEvent(*this, ImageCapturingResumedEvent{});
693 }
694
695 void ImageViewer::OnAutofocusClicked(DynExp::ModuleInstance* Instance, bool Checked) const
696 {
697 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
698
699 if (!ModuleData->Focus.valid())
700 return;
701
702 if (Checked)
703 OnAutofocus(Instance);
704 else
705 {
706 if (ModuleData->Communicator.valid())
707 ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
708
709 StateMachine.SetCurrentState(StateType::Ready);
710 }
711 }
712
713 void ImageViewer::OnAutofocus(DynExp::ModuleInstance* Instance, bool ResetImageTransformation) const
714 {
716
717 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
718
719 if (!ModuleData->Focus.valid())
720 {
721 if (ModuleData->Communicator.valid())
722 ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
723
724 return;
725 }
726
727 {
728 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
729
730 if (ResetImageTransformation)
731 CameraData->SetImageTransformation({});
732
733 ModuleData->CaptureAfterPause = CameraData->IsCapturingContinuously();
734 } // CameraData unlocked here.
735
736 ModuleData->Camera->StopCapturingSync();
737
738 {
739 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
740 CameraData->SetComputeHistogram(CHT::NoHistogram);
741 } // CameraData unlocked here.
742
743 StateMachine.SetCurrentState(StateType::AutofocusInit);
744 }
745
747 {
749
750 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
751 bool ImageAvailable = false;
752
753 {
754 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
755
756 ModuleData->CapturingState = CameraData->GetCapturingState();
757 ModuleData->MinExposureTime = CameraData->GetMinExposureTime();
758 ModuleData->MaxExposureTime = CameraData->GetMaxExposureTime();
759 ModuleData->CurrentExposureTime = CameraData->GetExposureTime();
760 ModuleData->CurrentFPS = CameraData->GetCurrentFPS();
761
762 CameraData->SetComputeHistogram(ModuleData->ComputeHistogram);
763
764 ImageAvailable = CameraData->IsImageAvailbale() && !ModuleData->ImageCapturingPaused;
765 if (ImageAvailable)
766 {
767 ModuleData->CurrentImage = CameraData->GetImage();
768 ModuleData->HasImageChanged = true;
769
770 if (ModuleData->ComputeHistogram == CHT::IntensityHistogram ||
771 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
772 ModuleData->IntensityHistogram = CameraData->GetIntensityHistogram();
773 if (ModuleData->ComputeHistogram == CHT::RGBHistogram ||
774 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
775 ModuleData->RGBHistogram = CameraData->GetRGBHistogram();
776 }
777 } // CameraData unlocked here.
778
779 if (ImageAvailable && !ModuleData->AutoSaveFilename.empty())
780 {
781 QImage Image = ModuleData->CurrentImage.copy();
782 if (!Image.save(QString::fromStdString(ModuleData->AutoSaveFilename)))
783 Util::EventLog().Log("[ImageViewer] Saving image as \"" + ModuleData->AutoSaveFilename + "\" to file failed.", Util::ErrorType::Error);
784
785 if (ModuleData->Communicator.valid())
786 ModuleData->Communicator->PostEvent(*this, FinishedEvent{});
787
788 ModuleData->AutoSaveFilename.clear();
789 }
790
791 return StateType::Ready;
792 }
793
795 {
796 {
797 auto ModuleParams = DynExp::dynamic_Params_cast<ImageViewer>(Instance.ParamsGetter());
798
799 AutofocusParams.NumSteps = ModuleParams->AutofocusNumSteps;
800 AutofocusParams.WaitTimeBeforeCapture = std::chrono::milliseconds(ModuleParams->AutofocusFocusChangeTime);
801 } // ModuleParams unlocked here.
802
803 AutofocusResults = {};
805
806 AutofocusSamples.clear();
807 for (double Voltage = AutofocusParams.MinVoltage; Voltage <= AutofocusParams.MaxVoltage; Voltage += AutofocusParams.GetVoltageIncrement())
808 AutofocusSamples.emplace_back(Voltage, std::numeric_limits<double>::quiet_NaN());
810
812 }
813
815 {
818 else
819 {
820 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
821 ModuleData->Focus->SetSync(AutofocusCurrentSample->Voltage);
822
823 AutofocusWaitingEndTimePoint = std::chrono::system_clock::now() + AutofocusParams.WaitTimeBeforeCapture;
824
826 }
827 }
828
830 {
831 if (std::chrono::system_clock::now() >= AutofocusWaitingEndTimePoint)
832 {
833 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
834 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
835
836 CameraData->ClearImage();
837 ModuleData->Camera->CaptureSingle();
838
840 }
841
843 }
844
846 {
847 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
848 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
849
850 if (CameraData->IsImageAvailbale())
851 {
852 ModuleData->CurrentExposureTime = CameraData->GetExposureTime();
853 ModuleData->CurrentImage = CameraData->GetImage();
854 ModuleData->HasImageChanged = true;
855
856 // Employing Brenner gradient as the figure of merit to maximize for autofocusing
857 // in the fine mode and the image's variance in the coarse mode.
858 // (S. Yazdanfar et al. Opt. Expres 16 (12), 8670-8677 (2008)).
859 AutofocusCurrentSample->Result = ModuleData->CalcBrennerGradientFromImage();
861
863 }
864
866 }
867
869 {
870 double OptimalVoltage = std::max_element(AutofocusSamples.cbegin(), AutofocusSamples.cend(), [](const auto& a, const auto& b) {
871 return a.Result < b.Result;
872 })->Voltage;
873
875 {
877
878 AutofocusSamples.clear();
879 for (double Voltage = std::max(AutofocusParams.MinVoltage, OptimalVoltage - AutofocusParams.GetVoltageIncrement());
880 Voltage <= std::min(AutofocusParams.MaxVoltage, OptimalVoltage + AutofocusParams.GetVoltageIncrement());
881 Voltage += AutofocusParams.GetVoltageIncrement(true))
882 AutofocusSamples.emplace_back(Voltage, std::numeric_limits<double>::quiet_NaN());
884
886 }
887 else
888 {
889 AutofocusResults.Success = OptimalVoltage > AutofocusSamples.front().Voltage + AutofocusParams.GetVoltageIncrement(true) / 2.0 &&
890 OptimalVoltage < AutofocusSamples.back().Voltage - AutofocusParams.GetVoltageIncrement(true) / 2.0;
892 AutofocusResults.FocusVoltage = OptimalVoltage;
893 }
894
896 }
897
909}
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
This event signals that an action (like a measurement) started by a TriggerEvent has been completed.
DynExpInstr::CameraData::TimeType TimeType
ImageViewerWidget(ImageViewer &Owner, QModuleWidget *parent=nullptr)
std::unique_ptr< Ui::ImageViewer > ui
Definition ImageViewer.h:62
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...
void OnSetFilename(DynExp::ModuleInstance *Instance, const std::string &SaveFilename) const
void OnStop(DynExp::ModuleInstance *Instance) const
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
void OnTrigger(DynExp::ModuleInstance *Instance) 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, ItemIDType CommunicatorID=ItemIDNotSet)
Registers/Subscribes module Listener to the event with the event function EventFunc....
Definition Module.h:1263
static void Deregister(const ModuleBase &Listener)
Deregisters/unsubscribes module Listener from the event, regardless of the inter-module communicator ...
Definition Module.h:1272
const std::unique_ptr< ModuleDataType > ModuleData
Module data belonging to this ModuleBase instance.
Definition Module.h:789
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:828
Refer to ParamsBase::dispatch_tag.
Definition Module.h:192
Defines data for a thread belonging to a ModuleBase instance. Refer to RunnableInstance.
Definition Module.h:841
const ModuleBase::ModuleDataGetterType ModuleDataGetter
Getter for module's data. Refer to ModuleBase::ModuleDataGetterType.
Definition Module.h:873
Refer to ParamsBase::dispatch_tag.
Definition Object.h:2026
QModuleWidget * Widget
User interface widget belonging to the module.
Definition Module.h:1816
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:1825
const Object::ParamsGetterType ParamsGetter
Invoke to obtain the parameters (derived from ParamsBase) of Owner.
Definition Object.h:3718
void UnlockObject(LinkedObjectWrapperContainer< ObjectT > &ObjectWrapperContainer)
Unlocks an Object instance stored in the LinkedObjectWrapperContainer ObjectWrapperContainer....
Definition Object.h:3617
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:3601
const auto & GetOwner() const noexcept
Returns Owner.
Definition Object.h:3564
std::function< void(const TaskBase *, ExceptionContainer &)> FuncType
Type of the owned callback function. The function receives a pointer to the task the CallbackType ins...
Definition Instrument.h:986
Base class for all tasks being processed by instruments. The class must not contain public virtual fu...
Definition Instrument.h:929
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:317
Implements a QGraphicsView the user can interact with to insert graphical markers....
Definition QtUtil.h:379
Pointer to lock a class derived from ISynchronizedPointerLockable for synchronizing between threads....
Definition Util.h:171
Thrown when an operation timed out before it could be completed, especially used for locking shared d...
Definition Exception.h:262
@ Unitless
Unitless scalar number.
@ UnitlessInt
Unitless scalar integer number.
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:517
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