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 HistogramBarSetI(nullptr), HistogramBarSetR(nullptr), HistogramBarSetG(nullptr), HistogramBarSetB(nullptr),
15 HistogramBarSeries(new QBarSeries(this)), HistogramChart(nullptr), HistogramXAxis(new QValueAxis(this)), HistogramYAxis(new QValueAxis(this)),
16 GraphicsView(nullptr), GraphicsPixmapItem(nullptr), GraphicsScene(new QGraphicsScene(this))
17 {
18 ui->setupUi(this);
19
20 ui->action_Zoom_fit->setChecked(true);
21
22 HistogramLinAction = HistogramLinLogActionGroup->addAction("&Linear");
23 HistogramLinAction->setCheckable(true);
24 HistogramLinAction->setChecked(true);
25 HistogramLogAction = HistogramLinLogActionGroup->addAction("Lo&garithmic");
26 HistogramLogAction->setCheckable(true);
28 HistogramContextMenu->addSeparator();
29 HistogramBWAction = HistogramContextMenu->addAction("Show &intensity");
30 HistogramBWAction->setCheckable(true);
31 HistogramBWAction->setChecked(true);
32 HistogramColorAction = HistogramContextMenu->addAction("Show &colors");
33 HistogramColorAction->setCheckable(true);
34
35 HistogramChart = new QChart();
36 ui->Histogram->setChart(HistogramChart); // Takes ownership of HistogramChart.
37 ui->Histogram->setRenderHint(QPainter::Antialiasing);
40 HistogramChart->legend()->setVisible(false);
41 HistogramXAxis->setTitleText("pixel value");
42 HistogramXAxis->setLabelFormat("%d");
43 HistogramXAxis->setRange(0, 255);
44 HistogramXAxis->setTickCount(255 / 85 + 1);
45 HistogramChart->addAxis(HistogramXAxis, Qt::AlignBottom);
46 HistogramYAxis->setTitleText("counts");
47 HistogramYAxis->setLabelFormat("%.0e");
48 HistogramYAxis->setRange(0, 1);
49 HistogramChart->addAxis(HistogramYAxis, Qt::AlignLeft);
50
51 GraphicsView = new Util::MarkerGraphicsView(ui->MainSplitter);
52 GraphicsView->setObjectName(QString::fromUtf8("Image"));
53 QSizePolicy ImageSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
54 ImageSizePolicy.setHorizontalStretch(1);
55 ImageSizePolicy.setVerticalStretch(0);
56 ImageSizePolicy.setHeightForWidth(GraphicsView->sizePolicy().hasHeightForWidth());
57 GraphicsView->setSizePolicy(ImageSizePolicy);
58 GraphicsView->setMinimumSize(QSize(400, 300));
59 GraphicsView->viewport()->setProperty("cursor", QVariant(QCursor(Qt::CrossCursor)));
60 GraphicsView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
61
63 GraphicsView->setScene(GraphicsScene);
64 GraphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
65 GraphicsView->viewport()->installEventFilter(this);
66 GraphicsView->viewport()->setMouseTracking(true);
67 }
68
69 void ImageViewerWidget::SetImage(const QImage& NewImage) noexcept
70 {
71 Pixmap = QPixmap::fromImage(NewImage); // deep copy
72 Pixmap.detach(); // just to be sure...
73 }
74
76 {
77 if (GraphicsView)
78 GraphicsView->setEnabled(Enable);
79 }
80
82 {
83 IntensityHistogram = std::move(NewIntensityHistogram);
84 }
85
87 {
88 RGBHistogram = std::move(NewRGBHistogram);
89 }
90
92 {
94
95 if (!ui->ExposureTimeGroupBox->isVisible() || ui->ExposureTimeGroupBox->visibleRegion().isEmpty())
96 return CHT::NoHistogram;
97
98 if (HistogramBWAction->isChecked())
99 return HistogramColorAction->isChecked() ? CHT::IntensityAndRGBHistogram : CHT::IntensityHistogram;
100 if (HistogramColorAction->isChecked())
101 return CHT::RGBHistogram;
102
103 return CHT::NoHistogram;
104 }
105
107 {
108 if (GraphicsView->isVisible() && !GraphicsView->visibleRegion().isEmpty() && !Pixmap.isNull())
109 {
110 GraphicsPixmapItem->setPixmap(Pixmap);
111 GraphicsScene->update();
112
113 OnZoomFitClicked(ui->action_Zoom_fit->isChecked());
114 }
115
116 if (ui->ExposureTimeGroupBox->isVisible() && !ui->ExposureTimeGroupBox->visibleRegion().isEmpty())
118
119 ui->ImageGeometry->setText(QString::number(Pixmap.width()) + " x " + QString::number(Pixmap.height()));
120 }
121
122 bool ImageViewerWidget::eventFilter(QObject* obj, QEvent* event)
123 {
124 if (GraphicsView->viewport())
125 if (event->type() == QEvent::MouseMove)
126 {
127 OnImageMouseMove(static_cast<QMouseEvent*>(event));
128
129 return true;
130 }
131
132 return QObject::eventFilter(obj, event);
133 }
134
135 void ImageViewerWidget::resizeEvent(QResizeEvent* event)
136 {
137 OnZoomFitClicked(ui->action_Zoom_fit->isChecked());
138 }
139
141 {
143
144 auto ComputeHistogram = GetComputeHistogram();
145 if (ComputeHistogram == CHT::NoHistogram || IntensityHistogram.size() >= std::numeric_limits<int>::max())
146 return;
147
148 // Remove all existing data series.
151 HistogramBarSetI = nullptr;
154 HistogramBarSetR = nullptr;
157 HistogramBarSetG = nullptr;
160 HistogramBarSetB = nullptr;
161
162 // Some removals failed. Do not continue in order to avoid memory leaks.
164 return;
165
166 HistogramChart->removeSeries(HistogramBarSeries);
167
168 // Create new data series.
169 if (ComputeHistogram == CHT::IntensityHistogram ||
170 ComputeHistogram == CHT::IntensityAndRGBHistogram)
171 {
172 HistogramBarSetI = new QBarSet("I", this);
173 HistogramBarSetI->setColor(Qt::white);
174 HistogramBarSetI->setBorderColor(Qt::white);
175 }
176
177 if (ComputeHistogram == CHT::RGBHistogram ||
178 ComputeHistogram == CHT::IntensityAndRGBHistogram)
179 {
180 HistogramBarSetR = new QBarSet("R", this);
181 HistogramBarSetG = new QBarSet("G", this);
182 HistogramBarSetB = new QBarSet("B", this);
183 HistogramBarSetR->setColor(Qt::red);
184 HistogramBarSetR->setBorderColor(Qt::red);
185 HistogramBarSetG->setColor(Qt::green);
186 HistogramBarSetG->setBorderColor(Qt::green);
187 HistogramBarSetB->setColor(Qt::blue);
188 HistogramBarSetB->setBorderColor(Qt::blue);
189 }
190
191 bool LogPlot = HistogramLogAction->isChecked();
192 qreal MaxValue = 1;
193 for (int i = 0; i < static_cast<int>(IntensityHistogram.size()); ++i)
194 {
196 {
197 // Add .1 in log case to avoid NaN if value is 0.
198 *HistogramBarSetI << (LogPlot ? std::log10(IntensityHistogram[i] + .1) : IntensityHistogram[i]);
199
200 MaxValue = std::max(MaxValue, HistogramBarSetI->at(i));
201 }
202
204 {
205 // Add .1 in log case to avoid NaN if value is 0.
206 *HistogramBarSetR << (LogPlot ? std::log10(std::get<0>(RGBHistogram)[i] + .1) : std::get<0>(RGBHistogram)[i]);
207 *HistogramBarSetG << (LogPlot ? std::log10(std::get<1>(RGBHistogram)[i] + .1) : std::get<1>(RGBHistogram)[i]);
208 *HistogramBarSetB << (LogPlot ? std::log10(std::get<2>(RGBHistogram)[i] + .1) : std::get<2>(RGBHistogram)[i]);
209
210 MaxValue = std::max({ MaxValue, HistogramBarSetR->at(i), HistogramBarSetG->at(i), HistogramBarSetB->at(i) });
211 }
212 }
213
218
220 HistogramYAxis->setMax(MaxValue);
223 }
224
226 {
227 HistogramContextMenu->exec(ui->Histogram->mapToGlobal(Position));
228 }
229
231 {
232 auto Filename = Util::PromptSaveFilePathModule(this, "Save image", ".png", "Portable Network Graphics image (*.png)");
233 if (Filename.isEmpty())
234 return;
235
236 SaveImageFilename = Filename;
237 }
238
240 {
241 ui->action_Zoom_fit->setChecked(false);
243 }
244
246 {
247 ui->action_Zoom_fit->setChecked(false);
249 }
250
252 {
253 ui->action_Zoom_fit->setChecked(false);
255 }
256
258 {
259 if (Checked)
260 GraphicsView->fitInView(GraphicsPixmapItem, Qt::AspectRatioMode::KeepAspectRatio);
261 }
262
263 void ImageViewerWidget::OnImageMouseMove(QMouseEvent* Event)
264 {
265 auto LocalPoint = GraphicsView->mapFromGlobal(Event->globalPosition().toPoint());
266
267 if (!GraphicsView->items(LocalPoint).empty())
268 {
269 auto Point = GraphicsView->mapToScene(LocalPoint).toPoint();
270
271 ui->CursorPosition->setText("X:" + QString::number(Point.x()) + ", Y:" + QString::number(Point.y()));
272 }
273 }
274
279
286 {
287 if (CurrentImage.isNull())
288 return std::numeric_limits<double>::quiet_NaN();
289
290 const auto GrayImage = CurrentImage.convertToFormat(QImage::Format::Format_Grayscale8);
291 auto RawImage = GrayImage.constBits();
292
293 double BrennerGradient = .0;
294 for (int y = 0; y < GrayImage.height(); ++y)
295 {
296 for (int x = 0; x < GrayImage.width() - 2; ++x)
297 {
298 BrennerGradient += std::pow((static_cast<double>(*RawImage) - static_cast<double>(*(RawImage + 2))) / 255.0, 2);
299
300 ++RawImage;
301 }
302
303 RawImage += 2;
304 }
305
306 // Multiply by 1000 for convenience since we are normally dealing with very small gradients.
307 return BrennerGradient / GrayImage.width() / GrayImage.height() * 1e3;
308 }
309
331
332 ImageViewer::ImageViewer(const std::thread::id OwnerThreadID, DynExp::ParamsBasePtrType&& Params)
333 : QModuleBase(OwnerThreadID, std::move(Params)),
334 StateMachine(ReadyState,
335 AutofocusInitState, AutofocusGotoSampleState, AutofocusWaitBeforeCaptureState,
336 AutofocusWaitForImageState, AutofocusStepState, AutofocusFinishedState),
337 PauseUpdatingUI(std::make_shared<std::atomic<bool>>(false))
338 {
339 }
340
344
345 void ImageViewer::OnSaveImage(DynExp::ModuleInstance* Instance, QString Filename) const
346 {
347 QImage Image;
348 {
349 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
350 Image = ModuleData->CurrentImage.copy();
351 } // ModuleData unlocked here for heavy save operation.
352
353 if (!Image.save(Filename))
354 Util::EventLog().Log("[ImageViewer] Saving image as \"" + Filename.toStdString() + "\" to file failed.", Util::ErrorType::Error);
355 }
356
358 {
359 try
360 {
361 StateMachine.Invoke(*this, Instance);
362
364 }
365 catch (const Util::TimeoutException& e)
366 {
367 if (NumFailedUpdateAttempts++ >= 3)
368 Instance.GetOwner().SetWarning(e);
369 }
370
372 }
373
387
388 std::unique_ptr<DynExp::QModuleWidget> ImageViewer::MakeUIWidget()
389 {
390 auto Widget = std::make_unique<ImageViewerWidget>(*this);
391
392 Connect(Widget->ui->CBCameraMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ImageViewer::OnCameraModeChanged);
393 Connect(Widget->ui->ExposureTime, QOverload<int>::of(&QSpinBox::valueChanged), this, &ImageViewer::OnExposureTimeChanged);
394 Connect(Widget->ui->action_Capture_Frame, &QAction::triggered, this, &ImageViewer::OnCaptureSingle);
395 Connect(Widget->ui->action_Capture_continuously, &QAction::triggered, this, &ImageViewer::OnCaptureContinuously);
396 Connect(Widget->ui->action_Autofocus, &QAction::triggered, this, &ImageViewer::OnAutofocusClicked);
397
398 return Widget;
399 }
400
401 void ImageViewer::UpdateUIChild(const ModuleBase::ModuleDataGetterType& ModuleDataGetter)
402 {
404
405 if (*PauseUpdatingUI)
406 return;
407
408 auto Widget = GetWidget<ImageViewerWidget>();
409 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(ModuleDataGetter());
410
411 // ModuleData is locked now. This syncs writing CameraData's settings with OnAutofocus().
412 const bool Ready = IsReadyState();
413 const bool Autofocusing = IsAutofocusingState();
414
415 if (!ModuleData->UIInitialized)
416 {
417 Widget->ui->action_Autofocus->setEnabled(ModuleData->Focus.valid());
418
419 if (ModuleData->CameraModes.empty())
420 Widget->ui->CameraModeGroupBox->setVisible(false);
421 else
422 {
423 for (const auto& Mode : ModuleData->CameraModes)
424 {
425 Widget->ui->CBCameraMode->insertItem(Widget->ui->CBCameraMode->count(), QString::fromStdString(Mode));
426 Widget->ui->CBCameraMode->setItemData(Widget->ui->CBCameraMode->count() - 1, QString::fromStdString(Mode), Qt::ToolTipRole);
427 }
428
429 // Triggers QComboBox::currentIndexChanged().
430 Widget->ui->CBCameraMode->setCurrentIndex(0);
431 }
432
433 ModuleData->UIInitialized = true;
434 }
435
436 Widget->ui->action_Save_Image->setEnabled(Ready);
437 Widget->ui->action_Capture_Frame->setEnabled(Ready);
438 Widget->ui->action_Capture_continuously->setEnabled(Ready);
439 Widget->ui->CameraModeGroupBox->setEnabled(Ready);
440 Widget->ui->ImageModifiersGroupBox->setEnabled(Ready);
441 Widget->ui->Histogram->setEnabled(Ready);
442 Widget->SetImageViewEnabled(Ready);
443
444 Widget->ui->ExposureTimeGroupBox->setEnabled(Ready && ModuleData->Camera->CanSetExposureTime());
445 Widget->ui->ExposureTimeMinValue->setText("min. " + QString::number(ModuleData->MinExposureTime.count()) + " ms");
446 Widget->ui->ExposureTimeMaxValue->setText("max. " + QString::number(ModuleData->MaxExposureTime.count()) + " ms");
447 Widget->ui->ExposureTime->setMinimum(ModuleData->MinExposureTime.count());
448 Widget->ui->ExposureTime->setMaximum(ModuleData->MaxExposureTime.count());
449
450 if (!Widget->ui->ExposureTime->hasFocus())
451 {
452 const QSignalBlocker Blocker(Widget->ui->ExposureTime);
453 Widget->ui->ExposureTime->setValue(Util::NumToT<int>(ModuleData->CurrentExposureTime.count()));
454 }
455
456 if (!Autofocusing && !ModuleData->ImageCapturingPaused)
457 {
459 if (Widget->ui->ImageModifiersGroupBox->isChecked())
460 {
461 // Values from controls ranging from -10 to 10.
462 ImageTransformation.BrightnessFactor = Widget->ui->ImageModifiersBrightness->value() / 10.f;
463 ImageTransformation.ContrastFactor = std::pow(10, Widget->ui->ImageModifiersContrast->value() / 10.f);
464 ImageTransformation.IsEnabled = true;
465 }
466
467 {
468 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
469 CameraData->SetImageTransformation(ImageTransformation);
470 } // CameraData unlocked here.
471 }
472
473 ModuleData->ComputeHistogram = Widget->GetComputeHistogram();
474
475 if (!ModuleData->CurrentImage.isNull() && ModuleData->HasImageChanged)
476 {
477 // SetImage creates a deep copy of CurrentImage.
478 Widget->SetImage(ModuleData->CurrentImage);
479 ModuleData->HasImageChanged = false;
480
481 if (ModuleData->ComputeHistogram == CHT::IntensityHistogram ||
482 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
483 Widget->SetIntensityHistogram(std::move(ModuleData->IntensityHistogram));
484 if (ModuleData->ComputeHistogram == CHT::RGBHistogram ||
485 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
486 Widget->SetRGBHistogram(std::move(ModuleData->RGBHistogram));
487
488 Widget->UpdateScene();
489 }
490
491 if (Ready && !Widget->GetSaveImageFilename().isEmpty())
492 {
493 ModuleData->Camera->StopCapturing();
494 MakeAndEnqueueEvent(this, &ImageViewer::OnSaveImage, Widget->GetSaveImageFilename());
495
496 Widget->ResetSaveImageFilename();
497 }
498
499 Widget->ui->action_Capture_continuously->setChecked(ModuleData->CapturingState == DynExpInstr::CameraData::CapturingStateType::CapturingContinuously);
500 Widget->ui->action_Autofocus->setChecked(Autofocusing);
501
502 if (Autofocusing)
503 Widget->ui->CurrentFPS->setText("Autofocusing...");
505 Widget->ui->CurrentFPS->setText("Capturing frame...");
507 {
508#ifdef DYNEXP_DEBUG
509 Widget->ui->CurrentFPS->setText("Capturing... (FPS: " + QString::number(ModuleData->CurrentFPS, 'f', 1) +
510 + ", Brenner gradient: " + QString::number(ModuleData->CalcBrennerGradientFromImage(), 'f', 3) + ")");
511#else
512 Widget->ui->CurrentFPS->setText("Capturing... (FPS: " + QString::number(ModuleData->CurrentFPS, 'f', 1) + ")");
513#endif // DYNEXP_DEBUG
514 }
515 else
516 Widget->ui->CurrentFPS->setText("Stopped");
517 }
518
520 {
521 const auto CurrentState = StateMachine.GetCurrentState()->GetState();
522
523 return CurrentState == StateType::Ready;
524 }
525
527 {
528 const auto CurrentState = StateMachine.GetCurrentState()->GetState();
529
530 return CurrentState == StateType::AutofocusInit ||
531 CurrentState == StateType::AutofocusGotoSample ||
533 CurrentState == StateType::AutofocusWaitForImage ||
534 CurrentState == StateType::AutofocusStep ||
535 CurrentState == StateType::AutofocusFinished;
536 }
537
539 {
540 if (ModuleData->Communicator.valid())
541 ModuleData->Communicator->PostEvent(*this, Event);
542
543 if (ModuleData->CaptureAfterPause)
544 ModuleData->Camera->StartCapturing();
545 }
546
548 {
555
556 auto ModuleParams = DynExp::dynamic_Params_cast<ImageViewer>(Instance->ParamsGetter());
557 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
558
559 Instance->LockObject(ModuleParams->Camera, ModuleData->Camera);
560 if (ModuleParams->Focus.ContainsID())
561 {
562 Instance->LockObject(ModuleParams->Focus, ModuleData->Focus);
563
564 AutofocusParams.MinVoltage = ModuleData->Focus->GetUserMinValue();
565 AutofocusParams.MaxVoltage = ModuleData->Focus->GetUserMaxValue();
566 }
567 if (ModuleParams->Communicator.ContainsID())
568 Instance->LockObject(ModuleParams->Communicator, ModuleData->Communicator);
569
570 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
571 ModuleData->CameraModes = CameraData->GetCameraModes();
572 }
573
575 {
576 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
577
578 Instance->UnlockObject(ModuleData->Camera);
579 Instance->UnlockObject(ModuleData->Focus);
580 Instance->UnlockObject(ModuleData->Communicator);
581
588 }
589
591 {
592 if (Index < 0)
593 return;
594
595 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
596
597 *PauseUpdatingUI = true;
598 ModuleData->Camera->SetCameraMode(Util::NumToT<size_t>(Index),
600 *Pause = false;
601 }
602 ));
603 }
604
606 {
607 try
608 {
609 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
610
611 if (ModuleData->Camera->CanSetExposureTime())
612 {
613 *PauseUpdatingUI = true;
614 ModuleData->Camera->SetExposureTime(std::chrono::milliseconds(Value),
616 *Pause = false;
617 }
618 ));
619 }
620 }
621 catch ([[maybe_unused]] const Util::TimeoutException& e)
622 {
623 // Swallow since it is likely that this exception occurs here when the user
624 // very quickly scrolls through the range of exposure times. It may take
625 // time to transfer the new exposure time to the camera.
626 }
627 }
628
630 {
631 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
632 ModuleData->AutoSaveFilename.clear();
633 ModuleData->ImageCapturingPaused = false;
634 ModuleData->CaptureAfterPause = false;
635
636 ModuleData->Camera->CaptureSingle();
637 }
638
640 {
641 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
642 ModuleData->AutoSaveFilename.clear();
643 ModuleData->ImageCapturingPaused = false;
644 ModuleData->CaptureAfterPause = false;
645
646 if (Checked)
647 ModuleData->Camera->StartCapturing();
648 else
649 ModuleData->Camera->StopCapturing();
650 }
651
652 void ImageViewer::OnSetFilename(DynExp::ModuleInstance* Instance, const std::string& SaveFilename) const
653 {
654 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
655
656 OnStop(Instance);
657 ModuleData->AutoSaveFilename = SaveFilename + ".png";
658 }
659
661 {
662 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
663 // Keep ModuleData->AutoSaveFilename to save the image.
664 ModuleData->ImageCapturingPaused = false;
665 ModuleData->CaptureAfterPause = false;
666
667 ModuleData->Camera->CaptureSingle();
668 }
669
671 {
672 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
673
674 if (IsReadyState())
675 OnCaptureContinuously(Instance, false);
676 else
677 {
678 StateMachine.SetCurrentState(StateType::Ready);
679 ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
680 }
681 }
682
683 void ImageViewer::OnPauseImageCapturing(DynExp::ModuleInstance* Instance, bool ResetImageTransformation) const
684 {
685 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
686
687 {
688 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
689
690 ModuleData->ImageCapturingPaused = true;
691 ModuleData->CaptureAfterPause = CameraData->IsCapturingContinuously();
692
693 if (ResetImageTransformation)
694 CameraData->SetImageTransformation({});
695 } // CameraData unlocked here.
696
697 if (ModuleData->Communicator.valid())
698 ModuleData->Communicator->PostEvent(*this, ImageCapturingPausedEvent{});
699 }
700
702 {
703 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
704
705 if (!ModuleData->ImageCapturingPaused)
706 return;
707 ModuleData->ImageCapturingPaused = false;
708
709 if (ModuleData->CaptureAfterPause)
710 ModuleData->Camera->StartCapturing();
711
712 if (ModuleData->Communicator.valid())
713 ModuleData->Communicator->PostEvent(*this, ImageCapturingResumedEvent{});
714 }
715
716 void ImageViewer::OnAutofocusClicked(DynExp::ModuleInstance* Instance, bool Checked) const
717 {
718 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
719
720 if (!ModuleData->Focus.valid())
721 return;
722
723 if (Checked)
724 OnAutofocus(Instance);
725 else
726 {
727 if (ModuleData->Communicator.valid())
728 ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
729
730 StateMachine.SetCurrentState(StateType::Ready);
731 }
732 }
733
734 void ImageViewer::OnAutofocus(DynExp::ModuleInstance* Instance, bool ResetImageTransformation) const
735 {
737
738 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance->ModuleDataGetter());
739
740 if (!ModuleData->Focus.valid())
741 {
742 if (ModuleData->Communicator.valid())
743 ModuleData->Communicator->PostEvent(*this, FinishedAutofocusEvent{ false });
744
745 return;
746 }
747
748 {
749 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
750
751 if (ResetImageTransformation)
752 CameraData->SetImageTransformation({});
753
754 ModuleData->CaptureAfterPause = CameraData->IsCapturingContinuously();
755 } // CameraData unlocked here.
756
757 ModuleData->Camera->StopCapturingSync();
758
759 {
760 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
761 CameraData->SetComputeHistogram(CHT::NoHistogram);
762 } // CameraData unlocked here.
763
764 StateMachine.SetCurrentState(StateType::AutofocusInit);
765 }
766
768 {
770
771 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
772 bool ImageAvailable = false;
773
774 {
775 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
776
777 ModuleData->CapturingState = CameraData->GetCapturingState();
778 ModuleData->MinExposureTime = CameraData->GetMinExposureTime();
779 ModuleData->MaxExposureTime = CameraData->GetMaxExposureTime();
780 ModuleData->CurrentExposureTime = CameraData->GetExposureTime();
781 ModuleData->CurrentFPS = CameraData->GetCurrentFPS();
782
783 CameraData->SetComputeHistogram(ModuleData->ComputeHistogram);
784
785 ImageAvailable = CameraData->IsImageAvailbale() && !ModuleData->ImageCapturingPaused;
786 if (ImageAvailable)
787 {
788 ModuleData->CurrentImage = CameraData->GetImage();
789 ModuleData->HasImageChanged = true;
790
791 if (ModuleData->ComputeHistogram == CHT::IntensityHistogram ||
792 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
793 ModuleData->IntensityHistogram = CameraData->GetIntensityHistogram();
794 if (ModuleData->ComputeHistogram == CHT::RGBHistogram ||
795 ModuleData->ComputeHistogram == CHT::IntensityAndRGBHistogram)
796 ModuleData->RGBHistogram = CameraData->GetRGBHistogram();
797 }
798 } // CameraData unlocked here.
799
800 if (ImageAvailable && !ModuleData->AutoSaveFilename.empty())
801 {
802 QImage Image = ModuleData->CurrentImage.copy();
803 if (!Image.save(QString::fromStdString(ModuleData->AutoSaveFilename)))
804 Util::EventLog().Log("[ImageViewer] Saving image as \"" + ModuleData->AutoSaveFilename + "\" to file failed.", Util::ErrorType::Error);
805
806 if (ModuleData->Communicator.valid())
807 ModuleData->Communicator->PostEvent(*this, FinishedEvent{});
808
809 ModuleData->AutoSaveFilename.clear();
810 }
811
812 return StateType::Ready;
813 }
814
816 {
817 {
818 auto ModuleParams = DynExp::dynamic_Params_cast<ImageViewer>(Instance.ParamsGetter());
819
820 AutofocusParams.NumSteps = ModuleParams->AutofocusNumSteps;
821 AutofocusParams.WaitTimeBeforeCapture = std::chrono::milliseconds(ModuleParams->AutofocusFocusChangeTime);
822 } // ModuleParams unlocked here.
823
824 AutofocusResults = {};
826
827 AutofocusSamples.clear();
828 for (double Voltage = AutofocusParams.MinVoltage; Voltage <= AutofocusParams.MaxVoltage; Voltage += AutofocusParams.GetVoltageIncrement())
829 AutofocusSamples.emplace_back(Voltage, std::numeric_limits<double>::quiet_NaN());
831
833 }
834
836 {
839 else
840 {
841 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
842 ModuleData->Focus->SetSync(AutofocusCurrentSample->Voltage);
843
844 AutofocusWaitingEndTimePoint = std::chrono::system_clock::now() + AutofocusParams.WaitTimeBeforeCapture;
845
847 }
848 }
849
851 {
852 if (std::chrono::system_clock::now() >= AutofocusWaitingEndTimePoint)
853 {
854 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
855 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
856
857 CameraData->ClearImage();
858 ModuleData->Camera->CaptureSingle();
859
861 }
862
864 }
865
867 {
868 auto ModuleData = DynExp::dynamic_ModuleData_cast<ImageViewer>(Instance.ModuleDataGetter());
869 auto CameraData = DynExp::dynamic_InstrumentData_cast<DynExpInstr::Camera>(ModuleData->Camera->GetInstrumentData());
870
871 if (CameraData->IsImageAvailbale())
872 {
873 ModuleData->CurrentExposureTime = CameraData->GetExposureTime();
874 ModuleData->CurrentImage = CameraData->GetImage();
875 ModuleData->HasImageChanged = true;
876
877 // Employing Brenner gradient as the figure of merit to maximize for autofocusing
878 // in the fine mode and the image's variance in the coarse mode.
879 // (S. Yazdanfar et al. Opt. Expres 16 (12), 8670-8677 (2008)).
880 AutofocusCurrentSample->Result = ModuleData->CalcBrennerGradientFromImage();
882
884 }
885
887 }
888
890 {
891 double OptimalVoltage = std::max_element(AutofocusSamples.cbegin(), AutofocusSamples.cend(), [](const auto& a, const auto& b) {
892 return a.Result < b.Result;
893 })->Voltage;
894
896 {
898
899 AutofocusSamples.clear();
900 for (double Voltage = std::max(AutofocusParams.MinVoltage, OptimalVoltage - AutofocusParams.GetVoltageIncrement());
901 Voltage <= std::min(AutofocusParams.MaxVoltage, OptimalVoltage + AutofocusParams.GetVoltageIncrement());
902 Voltage += AutofocusParams.GetVoltageIncrement(true))
903 AutofocusSamples.emplace_back(Voltage, std::numeric_limits<double>::quiet_NaN());
905
907 }
908 else
909 {
910 AutofocusResults.Success = OptimalVoltage > AutofocusSamples.front().Voltage + AutofocusParams.GetVoltageIncrement(true) / 2.0 &&
911 OptimalVoltage < AutofocusSamples.back().Voltage - AutofocusParams.GetVoltageIncrement(true) / 2.0;
913 AutofocusResults.FocusVoltage = OptimalVoltage;
914 }
915
917 }
918
930}
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.
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:91
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:85
bool eventFilter(QObject *obj, QEvent *event) override
void SetIntensityHistogram(Util::ImageHistogramType &&NewIntensityHistogram) noexcept
std::unique_ptr< Ui::ImageViewer > ui
Definition ImageViewer.h:61
void SetRGBHistogram(Util::ImageRGBHistogramType &&NewRGBHistogram) noexcept
Util::ImageHistogramType IntensityHistogram
Definition ImageViewer.h:90
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:1262
static void Deregister(const ModuleBase &Listener)
Deregisters/unsubscribes module Listener from the event, regardless of the inter-module communicator ...
Definition Module.h:1271
const std::unique_ptr< ModuleDataType > ModuleData
Module data belonging to this ModuleBase instance.
Definition Module.h:788
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:827
Refer to ParamsBase::dispatch_tag.
Definition Module.h:191
Defines data for a thread belonging to a ModuleBase instance. Refer to RunnableInstance.
Definition Module.h:840
const ModuleBase::ModuleDataGetterType ModuleDataGetter
Getter for module's data. Refer to ModuleBase::ModuleDataGetterType.
Definition Module.h:872
Refer to ParamsBase::dispatch_tag.
Definition Object.h:2018
QModuleWidget * Widget
User interface widget belonging to the module.
Definition Module.h:1807
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:1816
const Object::ParamsGetterType ParamsGetter
Invoke to obtain the parameters (derived from ParamsBase) of Owner.
Definition Object.h:3710
void UnlockObject(LinkedObjectWrapperContainer< ObjectT > &ObjectWrapperContainer)
Unlocks an Object instance stored in the LinkedObjectWrapperContainer ObjectWrapperContainer....
Definition Object.h:3609
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:3593
const auto & GetOwner() const noexcept
Returns Owner.
Definition Object.h:3556
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
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:262
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: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