| | | 1 | | // Copyright 2025 Digital Holography Foundation |
| | | 2 | | // |
| | | 3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
| | | 4 | | // you may not use this file except in compliance with the License. |
| | | 5 | | // You may obtain a copy of the License at |
| | | 6 | | // |
| | | 7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
| | | 8 | | // |
| | | 9 | | // Unless required by applicable law or agreed to in writing, software |
| | | 10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
| | | 11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| | | 12 | | // See the License for the specific language governing permissions and |
| | | 13 | | // limitations under the License. |
| | | 14 | | |
| | | 15 | | #include "ui/main_window.hh" |
| | | 16 | | |
| | | 17 | | #include <QAction> |
| | | 18 | | #include <QCheckBox> |
| | | 19 | | #include <QCloseEvent> |
| | | 20 | | #include <QComboBox> |
| | | 21 | | #include <QDateTime> |
| | | 22 | | #include <QDesktopServices> |
| | | 23 | | #include <QDialog> |
| | | 24 | | #include <QDialogButtonBox> |
| | | 25 | | #include <QDir> |
| | | 26 | | #include <QDirIterator> |
| | | 27 | | #include <QDoubleSpinBox> |
| | | 28 | | #include <QFile> |
| | | 29 | | #include <QFileDialog> |
| | | 30 | | #include <QFileInfoList> |
| | | 31 | | #include <QFormLayout> |
| | | 32 | | #include <QFrame> |
| | | 33 | | #include <QHBoxLayout> |
| | | 34 | | #include <QKeyEvent> |
| | | 35 | | #include <QKeySequence> |
| | | 36 | | #include <QLabel> |
| | | 37 | | #include <QLineEdit> |
| | | 38 | | #include <QMenuBar> |
| | | 39 | | #include <QMessageBox> |
| | | 40 | | #include <QProgressBar> |
| | | 41 | | #include <QPushButton> |
| | | 42 | | #include <QRegularExpression> |
| | | 43 | | #include <QScrollArea> |
| | | 44 | | #include <QScrollBar> |
| | | 45 | | #include <QSettings> |
| | | 46 | | #include <QSignalBlocker> |
| | | 47 | | #include <QSizePolicy> |
| | | 48 | | #include <QSlider> |
| | | 49 | | #include <QSpinBox> |
| | | 50 | | #include <QSplitter> |
| | | 51 | | #include <QStandardPaths> |
| | | 52 | | #include <QStringList> |
| | | 53 | | #include <QStyle> |
| | | 54 | | #include <QThread> |
| | | 55 | | #include <QTimer> |
| | | 56 | | #include <QToolButton> |
| | | 57 | | #include <QVBoxLayout> |
| | | 58 | | #include <algorithm> |
| | | 59 | | #include <array> |
| | | 60 | | #include <cmath> |
| | | 61 | | #include <filesystem> |
| | | 62 | | #include <fstream> |
| | | 63 | | #include <functional> |
| | | 64 | | #include <limits> |
| | | 65 | | #include <optional> |
| | | 66 | | |
| | | 67 | | #include "bug.hh" |
| | | 68 | | #include "holofile/holofile.hh" |
| | | 69 | | #include "logger.hh" |
| | | 70 | | #include "settings_loader.hh" |
| | | 71 | | #include "ui/update_checker.hh" |
| | | 72 | | #include "ui/graph_visualizer_widget.hh" |
| | | 73 | | #include "ui/visualization_workspace.hh" |
| | | 74 | | #include "ui/widgets/selected_widget_settings_panel.hh" |
| | | 75 | | #include "ui/widgets/tensor_display_widget.hh" |
| | | 76 | | #include "ui/widgets/zernike_history_widget.hh" |
| | | 77 | | |
| | | 78 | | namespace { |
| | | 79 | | |
| | | 80 | | constexpr int kLargeSpinMax = 1024 * 1024; |
| | | 81 | | constexpr int kTopBarHeight = 30; |
| | | 82 | | constexpr auto kDefaultSamplingFrequency = 37'000.0; |
| | | 83 | | |
| | 0 | 84 | | QSpinBox *create_spin_box(QWidget *parent, int minimum, int maximum, int value) { |
| | 0 | 85 | | auto *spin_box = new QSpinBox(parent); |
| | 0 | 86 | | spin_box->setRange(minimum, maximum); |
| | 0 | 87 | | spin_box->setValue(value); |
| | 0 | 88 | | return spin_box; |
| | 0 | 89 | | } |
| | | 90 | | |
| | | 91 | | QDoubleSpinBox *create_double_spin_box(QWidget *parent, double minimum, double maximum, double step, |
| | 0 | 92 | | double value, int decimals = 2) { |
| | 0 | 93 | | auto *spin_box = new QDoubleSpinBox(parent); |
| | 0 | 94 | | spin_box->setRange(minimum, maximum); |
| | 0 | 95 | | spin_box->setSingleStep(step); |
| | 0 | 96 | | spin_box->setDecimals(decimals); |
| | 0 | 97 | | spin_box->setValue(value); |
| | 0 | 98 | | return spin_box; |
| | 0 | 99 | | } |
| | | 100 | | |
| | 0 | 101 | | QComboBox *create_combo_box(QWidget *parent, const QStringList &items) { |
| | 0 | 102 | | auto *combo_box = new QComboBox(parent); |
| | 0 | 103 | | combo_box->addItems(items); |
| | 0 | 104 | | return combo_box; |
| | 0 | 105 | | } |
| | | 106 | | |
| | 0 | 107 | | QLabel *create_result_label(QWidget *parent) { |
| | 0 | 108 | | auto *label = new QLabel("-", parent); |
| | 0 | 109 | | label->setTextInteractionFlags(Qt::TextSelectableByMouse); |
| | 0 | 110 | | return label; |
| | 0 | 111 | | } |
| | | 112 | | |
| | 0 | 113 | | QString format_decimal(double value, int decimals = 6) { |
| | 0 | 114 | | QString text = QString::number(value, 'f', decimals); |
| | 0 | 115 | | while (text.contains('.') && text.endsWith('0')) { |
| | 0 | 116 | | text.chop(1); |
| | 0 | 117 | | } |
| | 0 | 118 | | if (text.endsWith('.')) { |
| | 0 | 119 | | text.chop(1); |
| | | 120 | | } |
| | 0 | 121 | | return text; |
| | 0 | 122 | | } |
| | | 123 | | |
| | 0 | 124 | | QString format_frequency(double value) { return QString("%1 Hz").arg(format_decimal(value, 3)); } |
| | | 125 | | |
| | | 126 | | struct FieldBinding { |
| | | 127 | | holovibes::pipeline::SettingsField field; |
| | | 128 | | QWidget *widget; |
| | | 129 | | }; |
| | | 130 | | |
| | 0 | 131 | | QString to_qstring(holovibes::pipeline::ValidationSeverity severity) { |
| | 0 | 132 | | switch (severity) { |
| | | 133 | | case holovibes::pipeline::ValidationSeverity::Warning: |
| | 0 | 134 | | return "Warning"; |
| | | 135 | | case holovibes::pipeline::ValidationSeverity::Error: |
| | 0 | 136 | | return "Error"; |
| | | 137 | | } |
| | | 138 | | |
| | 0 | 139 | | HOLOVIBES_UNREACHABLE(); |
| | 0 | 140 | | } |
| | | 141 | | |
| | | 142 | | QString build_field_tooltip(const holovibes::pipeline::FieldHelp &help, |
| | 0 | 143 | | std::span<const holovibes::pipeline::ValidationIssue *const> issues) { |
| | 0 | 144 | | QStringList lines; |
| | 0 | 145 | | lines << QString::fromStdString(help.title); |
| | 0 | 146 | | lines << ""; |
| | 0 | 147 | | lines << QString::fromStdString(help.description); |
| | | 148 | | |
| | 0 | 149 | | if (!help.constraints.empty()) { |
| | 0 | 150 | | lines << ""; |
| | 0 | 151 | | lines << "Constraints:"; |
| | 0 | 152 | | for (const auto *constraint : help.constraints) { |
| | 0 | 153 | | lines << QString("- %1").arg(constraint); |
| | 0 | 154 | | } |
| | | 155 | | } |
| | | 156 | | |
| | 0 | 157 | | if (!issues.empty()) { |
| | 0 | 158 | | lines << ""; |
| | 0 | 159 | | lines << "Current issues:"; |
| | 0 | 160 | | for (const auto *issue : issues) { |
| | 0 | 161 | | lines << QString("- %1: %2") |
| | | 162 | | .arg(to_qstring(issue->severity), QString::fromStdString(issue->message)); |
| | 0 | 163 | | } |
| | | 164 | | } |
| | | 165 | | |
| | 0 | 166 | | return lines.join('\n'); |
| | 0 | 167 | | } |
| | | 168 | | |
| | 0 | 169 | | void restore_combo_text(QSettings &settings, const char *key, QComboBox *combo_box) { |
| | 0 | 170 | | const QString text = settings.value(key).toString(); |
| | 0 | 171 | | if (text.isEmpty()) { |
| | 0 | 172 | | return; |
| | | 173 | | } |
| | | 174 | | |
| | 0 | 175 | | const int index = combo_box->findText(text); |
| | 0 | 176 | | if (index >= 0) { |
| | 0 | 177 | | combo_box->setCurrentIndex(index); |
| | | 178 | | } |
| | 0 | 179 | | } |
| | | 180 | | |
| | | 181 | | class FftFrequencyToolDialog : public QDialog { |
| | | 182 | | public: |
| | | 183 | | using ApplyHandler = std::function<void(int, int)>; |
| | | 184 | | |
| | 0 | 185 | | FftFrequencyToolDialog(double sampling_frequency_hz, int window_size, double start_frequency_hz, |
| | | 186 | | double end_frequency_hz, const QString &time_transform, |
| | | 187 | | ApplyHandler apply_handler, QWidget *parent) |
| | 0 | 188 | | : QDialog(parent), apply_handler_(std::move(apply_handler)) { |
| | 0 | 189 | | setWindowTitle(tr("FFT Frequency Range to Bins")); |
| | 0 | 190 | | setMinimumWidth(420); |
| | | 191 | | |
| | 0 | 192 | | auto *dialog_layout = new QVBoxLayout(this); |
| | | 193 | | |
| | 0 | 194 | | auto *input_form = new QFormLayout(); |
| | 0 | 195 | | input_form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); |
| | | 196 | | |
| | 0 | 197 | | sampling_frequency_spin_ = |
| | | 198 | | create_double_spin_box(this, 0.000001, 1.0e12, 100.0, sampling_frequency_hz, 6); |
| | 0 | 199 | | sampling_frequency_spin_->setSuffix(" Hz"); |
| | | 200 | | |
| | 0 | 201 | | sampling_frequency_spin_->setToolTip(tr("The sampling frequency of the input signal. This is " |
| | | 202 | | "always set to the input sampling frequency.")); |
| | 0 | 203 | | sampling_frequency_spin_->setEnabled(false); |
| | | 204 | | |
| | 0 | 205 | | input_form->addRow(tr("Sampling frequency"), sampling_frequency_spin_); |
| | | 206 | | |
| | 0 | 207 | | window_size_spin_ = create_spin_box(this, 1, std::numeric_limits<int>::max(), window_size); |
| | 0 | 208 | | input_form->addRow(tr("Window size"), window_size_spin_); |
| | 0 | 209 | | window_size_spin_->setToolTip( |
| | | 210 | | tr("The number of samples in the FFT window. This is always set to the " |
| | | 211 | | "time window.")); |
| | 0 | 212 | | window_size_spin_->setEnabled(false); |
| | | 213 | | |
| | 0 | 214 | | start_frequency_spin_ = |
| | | 215 | | create_double_spin_box(this, -1.0e12, 1.0e12, 1.0, start_frequency_hz, 2); |
| | 0 | 216 | | start_frequency_spin_->setSuffix(" Hz"); |
| | 0 | 217 | | input_form->addRow(tr("F0"), start_frequency_spin_); |
| | | 218 | | |
| | 0 | 219 | | end_frequency_spin_ = create_double_spin_box(this, -1.0e12, 1.0e12, 1.0, end_frequency_hz, 2); |
| | 0 | 220 | | end_frequency_spin_->setSuffix(" Hz"); |
| | 0 | 221 | | input_form->addRow(tr("F1"), end_frequency_spin_); |
| | | 222 | | |
| | 0 | 223 | | transform_combo_ = create_combo_box(this, QStringList{"RFFT", "FFT"}); |
| | 0 | 224 | | if (time_transform == "FFT") { |
| | 0 | 225 | | transform_combo_->setCurrentText("FFT"); |
| | | 226 | | } |
| | 0 | 227 | | input_form->addRow(tr("Transform"), transform_combo_); |
| | | 228 | | |
| | 0 | 229 | | dialog_layout->addLayout(input_form); |
| | | 230 | | |
| | 0 | 231 | | auto *result_form = new QFormLayout(); |
| | 0 | 232 | | result_form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); |
| | | 233 | | |
| | 0 | 234 | | bin_spacing_value_ = create_result_label(this); |
| | 0 | 235 | | fractional_bin_value_ = create_result_label(this); |
| | 0 | 236 | | pipeline_start_value_ = create_result_label(this); |
| | 0 | 237 | | pipeline_width_value_ = create_result_label(this); |
| | 0 | 238 | | pipeline_end_value_ = create_result_label(this); |
| | 0 | 239 | | full_fft_bin_value_ = create_result_label(this); |
| | 0 | 240 | | bin_frequency_value_ = create_result_label(this); |
| | 0 | 241 | | frequency_error_value_ = create_result_label(this); |
| | | 242 | | |
| | 0 | 243 | | result_form->addRow(tr("Bin spacing"), bin_spacing_value_); |
| | 0 | 244 | | result_form->addRow(tr("Fractional bins"), fractional_bin_value_); |
| | 0 | 245 | | result_form->addRow(tr("Pipeline Z start"), pipeline_start_value_); |
| | 0 | 246 | | result_form->addRow(tr("Pipeline Z width"), pipeline_width_value_); |
| | 0 | 247 | | result_form->addRow(tr("Pipeline Z end"), pipeline_end_value_); |
| | 0 | 248 | | result_form->addRow(tr("Full FFT bins"), full_fft_bin_value_); |
| | 0 | 249 | | result_form->addRow(tr("Selected frequency range"), bin_frequency_value_); |
| | 0 | 250 | | result_form->addRow(tr("Endpoint error"), frequency_error_value_); |
| | 0 | 251 | | dialog_layout->addLayout(result_form); |
| | | 252 | | |
| | 0 | 253 | | message_label_ = new QLabel(this); |
| | 0 | 254 | | message_label_->setWordWrap(true); |
| | 0 | 255 | | dialog_layout->addWidget(message_label_); |
| | | 256 | | |
| | 0 | 257 | | auto_apply_check_ = new QCheckBox(tr("Auto write Z range to settings"), this); |
| | 0 | 258 | | auto_apply_check_->setToolTip( |
| | | 259 | | tr("When enabled, valid calculator results are written to View Z and Width immediately.")); |
| | 0 | 260 | | dialog_layout->addWidget(auto_apply_check_); |
| | | 261 | | |
| | 0 | 262 | | auto *button_box = new QDialogButtonBox(QDialogButtonBox::Close, this); |
| | 0 | 263 | | apply_button_ = button_box->addButton(tr("Set Z Range"), QDialogButtonBox::ActionRole); |
| | 0 | 264 | | apply_button_->setToolTip(tr("Writes the calculated Z start and width to the View settings.")); |
| | 0 | 265 | | dialog_layout->addWidget(button_box); |
| | | 266 | | |
| | 0 | 267 | | connect(sampling_frequency_spin_, qOverload<double>(&QDoubleSpinBox::valueChanged), this, |
| | | 268 | | [this](double) { update_result(); }); |
| | 0 | 269 | | connect(window_size_spin_, qOverload<int>(&QSpinBox::valueChanged), this, |
| | | 270 | | [this](int) { update_result(); }); |
| | 0 | 271 | | connect(start_frequency_spin_, qOverload<double>(&QDoubleSpinBox::valueChanged), this, |
| | | 272 | | [this](double) { update_result(); }); |
| | 0 | 273 | | connect(end_frequency_spin_, qOverload<double>(&QDoubleSpinBox::valueChanged), this, |
| | | 274 | | [this](double) { update_result(); }); |
| | 0 | 275 | | connect(transform_combo_, qOverload<int>(&QComboBox::currentIndexChanged), this, |
| | | 276 | | [this](int) { update_result(); }); |
| | 0 | 277 | | connect(auto_apply_check_, &QCheckBox::toggled, this, [this](bool checked) { |
| | | 278 | | if (checked && result_valid_) { |
| | | 279 | | apply_result(); |
| | | 280 | | } |
| | | 281 | | }); |
| | 0 | 282 | | connect(apply_button_, &QPushButton::clicked, this, [this]() { apply_result(); }); |
| | 0 | 283 | | connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); |
| | | 284 | | |
| | 0 | 285 | | update_result(); |
| | 0 | 286 | | } |
| | | 287 | | |
| | | 288 | | private: |
| | 0 | 289 | | bool is_fft_transform() const { return transform_combo_->currentText() == "FFT"; } |
| | | 290 | | |
| | 0 | 291 | | void apply_result() { |
| | 0 | 292 | | if (result_valid_ && apply_handler_) { |
| | 0 | 293 | | apply_handler_(pipeline_bin_start_, pipeline_bin_width_); |
| | | 294 | | } |
| | 0 | 295 | | } |
| | | 296 | | |
| | 0 | 297 | | void update_result() { |
| | 0 | 298 | | const double sampling_frequency_hz = sampling_frequency_spin_->value(); |
| | 0 | 299 | | const int window_size = window_size_spin_->value(); |
| | 0 | 300 | | const double start_frequency_hz = start_frequency_spin_->value(); |
| | 0 | 301 | | const double end_frequency_hz = end_frequency_spin_->value(); |
| | 0 | 302 | | const double bin_spacing_hz = sampling_frequency_hz / window_size; |
| | 0 | 303 | | const double nyquist_hz = sampling_frequency_hz / 2.0; |
| | 0 | 304 | | const double range_start_hz = |
| | | 305 | | std::min(std::abs(start_frequency_hz), std::abs(end_frequency_hz)); |
| | 0 | 306 | | const double range_end_hz = std::max(std::abs(start_frequency_hz), std::abs(end_frequency_hz)); |
| | 0 | 307 | | const int max_positive_bin = window_size / 2; |
| | 0 | 308 | | const int max_pipeline_end = max_positive_bin + 1; |
| | | 309 | | |
| | 0 | 310 | | const bool endpoints_in_range = range_end_hz <= nyquist_hz; |
| | 0 | 311 | | const auto rounded_start = std::llround(range_start_hz / bin_spacing_hz); |
| | 0 | 312 | | const auto rounded_end = std::llround(range_end_hz / bin_spacing_hz); |
| | | 313 | | |
| | 0 | 314 | | pipeline_bin_start_ = |
| | | 315 | | static_cast<int>(std::clamp(rounded_start, 0LL, static_cast<long long>(max_pipeline_end))); |
| | 0 | 316 | | pipeline_bin_end_ = |
| | | 317 | | static_cast<int>(std::clamp(rounded_end, 0LL, static_cast<long long>(max_pipeline_end))); |
| | 0 | 318 | | if (pipeline_bin_end_ <= pipeline_bin_start_) { |
| | 0 | 319 | | pipeline_bin_end_ = std::min(max_pipeline_end, pipeline_bin_start_ + 1); |
| | | 320 | | } |
| | 0 | 321 | | pipeline_bin_width_ = pipeline_bin_end_ - pipeline_bin_start_; |
| | | 322 | | |
| | 0 | 323 | | result_valid_ = endpoints_in_range && pipeline_bin_width_ > 0; |
| | 0 | 324 | | apply_button_->setEnabled(result_valid_); |
| | | 325 | | |
| | 0 | 326 | | const bool fft_transform = is_fft_transform(); |
| | 0 | 327 | | const double selected_start_hz = pipeline_bin_start_ * bin_spacing_hz; |
| | 0 | 328 | | const double selected_end_hz = pipeline_bin_end_ * bin_spacing_hz; |
| | 0 | 329 | | const double start_endpoint_err = selected_start_hz - range_start_hz; |
| | 0 | 330 | | const double end_endpoint_err = selected_end_hz - range_end_hz; |
| | 0 | 331 | | const QString full_fft_bins_text = fft_transform && result_valid_ |
| | | 332 | | ? QString("[%1, %2) and [%3, %4)") |
| | | 333 | | .arg(pipeline_bin_start_) |
| | | 334 | | .arg(pipeline_bin_end_) |
| | | 335 | | .arg(window_size - pipeline_bin_end_) |
| | | 336 | | .arg(window_size - pipeline_bin_start_) |
| | | 337 | | : tr("N/A"); |
| | | 338 | | |
| | 0 | 339 | | bin_spacing_value_->setText(format_frequency(bin_spacing_hz)); |
| | 0 | 340 | | fractional_bin_value_->setText(QString("%1 to %2") |
| | | 341 | | .arg(format_decimal(range_start_hz / bin_spacing_hz, 6), |
| | | 342 | | format_decimal(range_end_hz / bin_spacing_hz, 6))); |
| | 0 | 343 | | pipeline_start_value_->setText(result_valid_ ? QString::number(pipeline_bin_start_) : "-"); |
| | 0 | 344 | | pipeline_width_value_->setText(result_valid_ ? QString::number(pipeline_bin_width_) : "-"); |
| | 0 | 345 | | pipeline_end_value_->setText(result_valid_ ? QString::number(pipeline_bin_end_) : "-"); |
| | 0 | 346 | | full_fft_bin_value_->setText(full_fft_bins_text); |
| | 0 | 347 | | bin_frequency_value_->setText(result_valid_ ? QString("[%1, %2)") |
| | | 348 | | .arg(format_frequency(selected_start_hz), |
| | | 349 | | format_frequency(selected_end_hz)) |
| | | 350 | | : "-"); |
| | 0 | 351 | | frequency_error_value_->setText( |
| | | 352 | | result_valid_ ? QString("%1, %2").arg(format_frequency(start_endpoint_err), |
| | | 353 | | format_frequency(end_endpoint_err)) |
| | | 354 | | : "-"); |
| | | 355 | | |
| | 0 | 356 | | if (!endpoints_in_range) { |
| | 0 | 357 | | message_label_->setText(tr("Frequency range is outside the Nyquist range of +/- %1.") |
| | | 358 | | .arg(format_frequency(nyquist_hz))); |
| | 0 | 359 | | } else if (!fft_transform && (start_frequency_hz < 0.0 || end_frequency_hz < 0.0)) { |
| | 0 | 360 | | message_label_->setText(tr( |
| | | 361 | | "RFFT exposes non-negative frequencies; negative inputs are converted to magnitudes.")); |
| | 0 | 362 | | } else if (fft_transform) { |
| | 0 | 363 | | message_label_->setText( |
| | | 364 | | tr("The pipeline stores the positive range and mirrors the negative FFT range.")); |
| | 0 | 365 | | } else { |
| | 0 | 366 | | message_label_->clear(); |
| | | 367 | | } |
| | | 368 | | |
| | 0 | 369 | | if (auto_apply_check_->isChecked() && result_valid_) { |
| | 0 | 370 | | apply_result(); |
| | | 371 | | } |
| | 0 | 372 | | } |
| | | 373 | | |
| | 0 | 374 | | QDoubleSpinBox *sampling_frequency_spin_ = nullptr; |
| | 0 | 375 | | QSpinBox *window_size_spin_ = nullptr; |
| | 0 | 376 | | QDoubleSpinBox *start_frequency_spin_ = nullptr; |
| | 0 | 377 | | QDoubleSpinBox *end_frequency_spin_ = nullptr; |
| | 0 | 378 | | QComboBox *transform_combo_ = nullptr; |
| | 0 | 379 | | QLabel *bin_spacing_value_ = nullptr; |
| | 0 | 380 | | QLabel *fractional_bin_value_ = nullptr; |
| | 0 | 381 | | QLabel *pipeline_start_value_ = nullptr; |
| | 0 | 382 | | QLabel *pipeline_width_value_ = nullptr; |
| | 0 | 383 | | QLabel *pipeline_end_value_ = nullptr; |
| | 0 | 384 | | QLabel *full_fft_bin_value_ = nullptr; |
| | 0 | 385 | | QLabel *bin_frequency_value_ = nullptr; |
| | 0 | 386 | | QLabel *frequency_error_value_ = nullptr; |
| | 0 | 387 | | QLabel *message_label_ = nullptr; |
| | 0 | 388 | | QCheckBox *auto_apply_check_ = nullptr; |
| | 0 | 389 | | QPushButton *apply_button_ = nullptr; |
| | | 390 | | ApplyHandler apply_handler_; |
| | 0 | 391 | | int pipeline_bin_start_ = 0; |
| | 0 | 392 | | int pipeline_bin_width_ = 1; |
| | 0 | 393 | | int pipeline_bin_end_ = 1; |
| | 0 | 394 | | bool result_valid_ = false; |
| | | 395 | | }; |
| | | 396 | | |
| | | 397 | | class PreferencesDialog : public QDialog { |
| | | 398 | | public: |
| | | 399 | | using GraphSpecDumpPreferences = holoflow::core::GraphSpecDumpPreferences; |
| | | 400 | | using GraphCompiledDumpPreferences = holoflow::runtime::GraphCompiledDumpPreferences; |
| | | 401 | | // TODO : find the right import |
| | | 402 | | |
| | 0 | 403 | | PreferencesDialog(QWidget *parent, holovibes::pipeline::Manager &manager) |
| | 0 | 404 | | : QDialog(parent), manager_(manager) { |
| | 0 | 405 | | setWindowTitle(tr("Preferences")); |
| | 0 | 406 | | setMinimumWidth(400); |
| | | 407 | | |
| | 0 | 408 | | auto *dialog_layout = new QVBoxLayout(this); |
| | | 409 | | |
| | 0 | 410 | | auto *splitter = new QSplitter(Qt::Horizontal, this); |
| | | 411 | | |
| | | 412 | | // Dump preferences |
| | 0 | 413 | | auto *graph_spec_dump_group_box = |
| | | 414 | | setup_graph_spec_dump_preferences(manager_.get_graph_spec_dump_preferences()); |
| | 0 | 415 | | splitter->addWidget(graph_spec_dump_group_box); |
| | | 416 | | |
| | 0 | 417 | | auto *graph_compiled_dump_group_box = |
| | | 418 | | setup_graph_compiled_dump_preferences(manager_.get_graph_compiled_dump_preferences()); |
| | 0 | 419 | | splitter->addWidget(graph_compiled_dump_group_box); |
| | | 420 | | |
| | 0 | 421 | | dialog_layout->addWidget(splitter); |
| | | 422 | | // Apply / Close |
| | 0 | 423 | | auto *button_box = new QDialogButtonBox(QDialogButtonBox::Close, this); |
| | 0 | 424 | | apply_button_ = button_box->addButton(tr("Apply"), QDialogButtonBox::ActionRole); |
| | 0 | 425 | | apply_button_->setDisabled(true); |
| | 0 | 426 | | dialog_layout->addWidget(button_box); |
| | | 427 | | |
| | 0 | 428 | | connect(apply_button_, &QPushButton::clicked, this, [this]() { update_preferences(); }); |
| | 0 | 429 | | connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); |
| | 0 | 430 | | connect_signals(); |
| | 0 | 431 | | } |
| | | 432 | | |
| | | 433 | | private: |
| | | 434 | | QGroupBox * |
| | 0 | 435 | | setup_graph_spec_dump_preferences(const GraphSpecDumpPreferences &graph_spec_dump_preferences) { |
| | 0 | 436 | | auto *group_box = new QGroupBox(tr("Graph Dump Spec"), this); |
| | 0 | 437 | | auto *input_form = new QFormLayout(); |
| | 0 | 438 | | input_form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); |
| | | 439 | | |
| | 0 | 440 | | graph_spec_dump_preferences_widgets_.rankdir_combo_ = |
| | | 441 | | create_combo_box(this, QStringList{"LR", "TB"}); |
| | 0 | 442 | | if (graph_spec_dump_preferences.rankdir == GraphSpecDumpPreferences::Rankdir::TopToBottom) { |
| | 0 | 443 | | graph_spec_dump_preferences_widgets_.rankdir_combo_->setCurrentText("TB"); |
| | | 444 | | } |
| | | 445 | | |
| | 0 | 446 | | input_form->addRow(tr("Graph rankdir"), graph_spec_dump_preferences_widgets_.rankdir_combo_); |
| | | 447 | | |
| | 0 | 448 | | graph_spec_dump_preferences_widgets_.floating_point_precision_spin_ = |
| | | 449 | | create_spin_box(this, 0, 17, graph_spec_dump_preferences.floating_point_precision); |
| | 0 | 450 | | graph_spec_dump_preferences_widgets_.floating_point_precision_spin_->setToolTip( |
| | | 451 | | tr("Number of digits after the decimal point in scientific notation.")); |
| | 0 | 452 | | input_form->addRow(tr("Floating-point precision"), |
| | | 453 | | graph_spec_dump_preferences_widgets_.floating_point_precision_spin_); |
| | | 454 | | |
| | 0 | 455 | | graph_spec_dump_preferences_widgets_.node_name_checkbox_ = new QCheckBox(this); |
| | 0 | 456 | | graph_spec_dump_preferences_widgets_.node_name_checkbox_->setChecked( |
| | | 457 | | graph_spec_dump_preferences.dump_node_name); |
| | 0 | 458 | | graph_spec_dump_preferences_widgets_.node_name_checkbox_->setToolTip( |
| | | 459 | | tr("Include node names in the graph dump.")); |
| | 0 | 460 | | input_form->addRow(tr("Node names"), graph_spec_dump_preferences_widgets_.node_name_checkbox_); |
| | | 461 | | |
| | 0 | 462 | | graph_spec_dump_preferences_widgets_.node_kind_checkbox_ = new QCheckBox(this); |
| | 0 | 463 | | graph_spec_dump_preferences_widgets_.node_kind_checkbox_->setChecked( |
| | | 464 | | graph_spec_dump_preferences.dump_node_kind); |
| | 0 | 465 | | graph_spec_dump_preferences_widgets_.node_kind_checkbox_->setToolTip( |
| | | 466 | | tr("Include node kinds in the graph dump.")); |
| | 0 | 467 | | input_form->addRow(tr("Node kinds"), graph_spec_dump_preferences_widgets_.node_kind_checkbox_); |
| | | 468 | | |
| | 0 | 469 | | graph_spec_dump_preferences_widgets_.node_settings_checkbox_ = new QCheckBox(this); |
| | 0 | 470 | | graph_spec_dump_preferences_widgets_.node_settings_checkbox_->setChecked( |
| | | 471 | | graph_spec_dump_preferences.dump_node_settings); |
| | 0 | 472 | | graph_spec_dump_preferences_widgets_.node_settings_checkbox_->setToolTip( |
| | | 473 | | tr("Include node settings in the graph dump.")); |
| | 0 | 474 | | input_form->addRow(tr("Node settings"), |
| | | 475 | | graph_spec_dump_preferences_widgets_.node_settings_checkbox_); |
| | | 476 | | |
| | 0 | 477 | | graph_spec_dump_preferences_widgets_.edge_indices_checkbox_ = new QCheckBox(this); |
| | 0 | 478 | | graph_spec_dump_preferences_widgets_.edge_indices_checkbox_->setChecked( |
| | | 479 | | graph_spec_dump_preferences.dump_edge_indices); |
| | 0 | 480 | | graph_spec_dump_preferences_widgets_.edge_indices_checkbox_->setToolTip( |
| | | 481 | | tr("Include edge indices in the graph dump.")); |
| | 0 | 482 | | input_form->addRow(tr("Edge indices"), |
| | | 483 | | graph_spec_dump_preferences_widgets_.edge_indices_checkbox_); |
| | | 484 | | |
| | 0 | 485 | | group_box->setLayout(input_form); |
| | 0 | 486 | | return group_box; |
| | 0 | 487 | | } |
| | | 488 | | |
| | | 489 | | // TODO add compiled graph preferences args |
| | | 490 | | QGroupBox *setup_graph_compiled_dump_preferences( |
| | 0 | 491 | | const GraphCompiledDumpPreferences &graph_compiled_dump_preferences) { |
| | 0 | 492 | | auto *group_box = new QGroupBox(tr("Graph Compiled Spec"), this); |
| | 0 | 493 | | auto *input_form = new QFormLayout(); |
| | 0 | 494 | | input_form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); |
| | | 495 | | |
| | 0 | 496 | | graph_compiled_dump_preferences_widgets_.rankdir_combo_ = |
| | | 497 | | create_combo_box(this, QStringList{"LR", "TB"}); |
| | 0 | 498 | | if (graph_compiled_dump_preferences.rankdir == |
| | | 499 | | GraphCompiledDumpPreferences::Rankdir::TopToBottom) { |
| | 0 | 500 | | graph_compiled_dump_preferences_widgets_.rankdir_combo_->setCurrentText("TB"); |
| | | 501 | | } |
| | | 502 | | |
| | 0 | 503 | | graph_compiled_dump_preferences_widgets_.rankdir_combo_->setToolTip( |
| | | 504 | | tr("Direction used by the normal compiled-graph layout.")); |
| | 0 | 505 | | input_form->addRow(tr("Rank direction"), |
| | | 506 | | graph_compiled_dump_preferences_widgets_.rankdir_combo_); |
| | | 507 | | |
| | 0 | 508 | | graph_compiled_dump_preferences_widgets_.layout_combo_ = |
| | | 509 | | create_combo_box(this, |
| | | 510 | | QStringList{tr("Normal"), tr("Stairs"), tr("Block"), tr("Snake")}); |
| | 0 | 511 | | graph_compiled_dump_preferences_widgets_.layout_combo_->setCurrentIndex( |
| | | 512 | | static_cast<int>(graph_compiled_dump_preferences.layout)); |
| | 0 | 513 | | graph_compiled_dump_preferences_widgets_.layout_combo_->setToolTip( |
| | | 514 | | tr("Arrange sections normally, as horizontal stairs, as a left-aligned block, or in " |
| | | 515 | | "alternating snake rows.")); |
| | 0 | 516 | | graph_compiled_dump_preferences_widgets_.rankdir_combo_->setEnabled( |
| | | 517 | | graph_compiled_dump_preferences.layout == GraphCompiledDumpPreferences::Layout::Normal); |
| | 0 | 518 | | input_form->addRow(tr("Section layout"), |
| | | 519 | | graph_compiled_dump_preferences_widgets_.layout_combo_); |
| | | 520 | | |
| | 0 | 521 | | graph_compiled_dump_preferences_widgets_.floating_point_precision_spin_ = |
| | | 522 | | create_spin_box(this, 0, 17, graph_compiled_dump_preferences.floating_point_precision); |
| | 0 | 523 | | graph_compiled_dump_preferences_widgets_.floating_point_precision_spin_->setToolTip( |
| | | 524 | | tr("Number of digits after the decimal point in scientific notation.")); |
| | 0 | 525 | | input_form->addRow(tr("Floating-point precision"), |
| | | 526 | | graph_compiled_dump_preferences_widgets_.floating_point_precision_spin_); |
| | | 527 | | |
| | 0 | 528 | | graph_compiled_dump_preferences_widgets_.node_name_checkbox_ = new QCheckBox(this); |
| | 0 | 529 | | graph_compiled_dump_preferences_widgets_.node_name_checkbox_->setChecked( |
| | | 530 | | graph_compiled_dump_preferences.dump_node_name); |
| | 0 | 531 | | graph_compiled_dump_preferences_widgets_.node_name_checkbox_->setToolTip( |
| | | 532 | | tr("Include node names in the compiled graph.")); |
| | 0 | 533 | | input_form->addRow(tr("Node names"), |
| | | 534 | | graph_compiled_dump_preferences_widgets_.node_name_checkbox_); |
| | | 535 | | |
| | 0 | 536 | | graph_compiled_dump_preferences_widgets_.node_kind_checkbox_ = new QCheckBox(this); |
| | 0 | 537 | | graph_compiled_dump_preferences_widgets_.node_kind_checkbox_->setChecked( |
| | | 538 | | graph_compiled_dump_preferences.dump_node_kind); |
| | 0 | 539 | | graph_compiled_dump_preferences_widgets_.node_kind_checkbox_->setToolTip( |
| | | 540 | | tr("Include node kinds in the compiled graph.")); |
| | 0 | 541 | | input_form->addRow(tr("Node kinds"), |
| | | 542 | | graph_compiled_dump_preferences_widgets_.node_kind_checkbox_); |
| | | 543 | | |
| | 0 | 544 | | graph_compiled_dump_preferences_widgets_.node_settings_checkbox_ = new QCheckBox(this); |
| | 0 | 545 | | graph_compiled_dump_preferences_widgets_.node_settings_checkbox_->setChecked( |
| | | 546 | | graph_compiled_dump_preferences.dump_node_settings); |
| | 0 | 547 | | graph_compiled_dump_preferences_widgets_.node_settings_checkbox_->setToolTip( |
| | | 548 | | tr("Include node settings in the compiled graph.")); |
| | 0 | 549 | | input_form->addRow(tr("Node settings"), |
| | | 550 | | graph_compiled_dump_preferences_widgets_.node_settings_checkbox_); |
| | | 551 | | |
| | 0 | 552 | | graph_compiled_dump_preferences_widgets_.node_in_out_tids_ = new QCheckBox(this); |
| | 0 | 553 | | graph_compiled_dump_preferences_widgets_.node_in_out_tids_->setChecked( |
| | | 554 | | graph_compiled_dump_preferences.dump_node_in_out_tids); |
| | 0 | 555 | | graph_compiled_dump_preferences_widgets_.node_in_out_tids_->setToolTip( |
| | | 556 | | tr("Include node input and output TIDs in the compiled graph.")); |
| | 0 | 557 | | input_form->addRow(tr("Node I/O TIDs"), |
| | | 558 | | graph_compiled_dump_preferences_widgets_.node_in_out_tids_); |
| | | 559 | | |
| | 0 | 560 | | graph_compiled_dump_preferences_widgets_.edge_indices_checkbox_ = new QCheckBox(this); |
| | 0 | 561 | | graph_compiled_dump_preferences_widgets_.edge_indices_checkbox_->setChecked( |
| | | 562 | | graph_compiled_dump_preferences.dump_edge_indices); |
| | 0 | 563 | | graph_compiled_dump_preferences_widgets_.edge_indices_checkbox_->setToolTip( |
| | | 564 | | tr("Include edge indices in the compiled graph.")); |
| | 0 | 565 | | input_form->addRow(tr("Edge indices"), |
| | | 566 | | graph_compiled_dump_preferences_widgets_.edge_indices_checkbox_); |
| | | 567 | | |
| | 0 | 568 | | graph_compiled_dump_preferences_widgets_.edge_desc_checkbox_ = new QCheckBox(this); |
| | 0 | 569 | | graph_compiled_dump_preferences_widgets_.edge_desc_checkbox_->setChecked( |
| | | 570 | | graph_compiled_dump_preferences.dump_edge_descriptions); |
| | 0 | 571 | | graph_compiled_dump_preferences_widgets_.edge_desc_checkbox_->setToolTip( |
| | | 572 | | tr("Include edge descriptions in the compiled graph.")); |
| | 0 | 573 | | input_form->addRow(tr("Edge descriptions"), |
| | | 574 | | graph_compiled_dump_preferences_widgets_.edge_desc_checkbox_); |
| | | 575 | | |
| | 0 | 576 | | graph_compiled_dump_preferences_widgets_.section_toggle_checkbox_ = new QCheckBox(this); |
| | 0 | 577 | | graph_compiled_dump_preferences_widgets_.section_toggle_checkbox_->setChecked( |
| | | 578 | | graph_compiled_dump_preferences.dump_section_info); |
| | 0 | 579 | | graph_compiled_dump_preferences_widgets_.section_toggle_checkbox_->setToolTip( |
| | | 580 | | tr("Include section information in the compiled graph.")); |
| | 0 | 581 | | input_form->addRow(tr("Section info"), |
| | | 582 | | graph_compiled_dump_preferences_widgets_.section_toggle_checkbox_); |
| | | 583 | | |
| | 0 | 584 | | graph_compiled_dump_preferences_widgets_.section_stream_addr_checkbox_ = new QCheckBox(this); |
| | 0 | 585 | | graph_compiled_dump_preferences_widgets_.section_stream_addr_checkbox_->setChecked( |
| | | 586 | | graph_compiled_dump_preferences.dump_section_stream_addr); |
| | 0 | 587 | | graph_compiled_dump_preferences_widgets_.section_stream_addr_checkbox_->setToolTip( |
| | | 588 | | tr("Include section stream addresses in the compiled graph.")); |
| | 0 | 589 | | input_form->addRow(tr("Section stream addresses"), |
| | | 590 | | graph_compiled_dump_preferences_widgets_.section_stream_addr_checkbox_); |
| | | 591 | | |
| | 0 | 592 | | graph_compiled_dump_preferences_widgets_.resources_toggle_checkbox_ = new QCheckBox(this); |
| | 0 | 593 | | graph_compiled_dump_preferences_widgets_.resources_toggle_checkbox_->setChecked( |
| | | 594 | | graph_compiled_dump_preferences.dump_resource_info); |
| | 0 | 595 | | graph_compiled_dump_preferences_widgets_.resources_toggle_checkbox_->setToolTip( |
| | | 596 | | tr("Include resource information in the compiled graph.")); |
| | 0 | 597 | | input_form->addRow(tr("Resource info"), |
| | | 598 | | graph_compiled_dump_preferences_widgets_.resources_toggle_checkbox_); |
| | | 599 | | |
| | 0 | 600 | | group_box->setLayout(input_form); |
| | 0 | 601 | | return group_box; |
| | 0 | 602 | | } |
| | | 603 | | |
| | 0 | 604 | | void update_preferences() { |
| | | 605 | | // const auto &specs_ = nullptr; |
| | | 606 | | // holoflow::core::to_dot(specs_); |
| | 0 | 607 | | apply_button_->setEnabled(false); |
| | 0 | 608 | | auto graph_spec_dump_preferences = GraphSpecDumpPreferences{ |
| | | 609 | | .rankdir = graph_spec_dump_preferences_widgets_.rankdir_combo_->currentText() == "LR" |
| | | 610 | | ? GraphSpecDumpPreferences::Rankdir::LeftToRight |
| | | 611 | | : GraphSpecDumpPreferences::Rankdir::TopToBottom, |
| | | 612 | | |
| | | 613 | | .floating_point_precision = |
| | | 614 | | graph_spec_dump_preferences_widgets_.floating_point_precision_spin_->value(), |
| | | 615 | | |
| | | 616 | | .dump_node_name = graph_spec_dump_preferences_widgets_.node_name_checkbox_->isChecked(), |
| | | 617 | | .dump_node_kind = graph_spec_dump_preferences_widgets_.node_kind_checkbox_->isChecked(), |
| | | 618 | | .dump_node_settings = |
| | | 619 | | graph_spec_dump_preferences_widgets_.node_settings_checkbox_->isChecked(), |
| | | 620 | | .dump_edge_indices = |
| | | 621 | | graph_spec_dump_preferences_widgets_.edge_indices_checkbox_->isChecked()}; |
| | | 622 | | |
| | 0 | 623 | | auto graph_compiled_dump_preferences = GraphCompiledDumpPreferences{ |
| | | 624 | | .rankdir = graph_compiled_dump_preferences_widgets_.rankdir_combo_->currentText() == "LR" |
| | | 625 | | ? GraphCompiledDumpPreferences::Rankdir::LeftToRight |
| | | 626 | | : GraphCompiledDumpPreferences::Rankdir::TopToBottom, |
| | | 627 | | .layout = static_cast<GraphCompiledDumpPreferences::Layout>( |
| | | 628 | | graph_compiled_dump_preferences_widgets_.layout_combo_->currentIndex()), |
| | | 629 | | |
| | | 630 | | .floating_point_precision = |
| | | 631 | | graph_compiled_dump_preferences_widgets_.floating_point_precision_spin_->value(), |
| | | 632 | | |
| | | 633 | | .dump_node_name = graph_compiled_dump_preferences_widgets_.node_name_checkbox_->isChecked(), |
| | | 634 | | .dump_node_kind = graph_compiled_dump_preferences_widgets_.node_kind_checkbox_->isChecked(), |
| | | 635 | | .dump_node_settings = |
| | | 636 | | graph_compiled_dump_preferences_widgets_.node_settings_checkbox_->isChecked(), |
| | | 637 | | .dump_node_in_out_tids = |
| | | 638 | | graph_compiled_dump_preferences_widgets_.node_in_out_tids_->isChecked(), |
| | | 639 | | .dump_edge_indices = |
| | | 640 | | graph_compiled_dump_preferences_widgets_.edge_indices_checkbox_->isChecked(), |
| | | 641 | | .dump_edge_descriptions = |
| | | 642 | | graph_compiled_dump_preferences_widgets_.edge_desc_checkbox_->isChecked(), |
| | | 643 | | .dump_section_info = |
| | | 644 | | graph_compiled_dump_preferences_widgets_.section_toggle_checkbox_->isChecked(), |
| | | 645 | | .dump_section_stream_addr = |
| | | 646 | | graph_compiled_dump_preferences_widgets_.section_stream_addr_checkbox_->isChecked(), |
| | | 647 | | .dump_resource_info = |
| | | 648 | | graph_compiled_dump_preferences_widgets_.resources_toggle_checkbox_->isChecked()}; |
| | | 649 | | |
| | 0 | 650 | | manager_.update_graph_spec_dump_preferences(graph_spec_dump_preferences); |
| | 0 | 651 | | manager_.update_graph_compiled_dump_preferences(graph_compiled_dump_preferences); |
| | 0 | 652 | | } |
| | | 653 | | |
| | 0 | 654 | | void connect_signals() { |
| | 0 | 655 | | connect(graph_spec_dump_preferences_widgets_.rankdir_combo_, |
| | | 656 | | qOverload<int>(&QComboBox::currentIndexChanged), this, |
| | | 657 | | [this](int) { apply_button_->setEnabled(true); }); |
| | 0 | 658 | | connect(graph_spec_dump_preferences_widgets_.floating_point_precision_spin_, |
| | | 659 | | qOverload<int>(&QSpinBox::valueChanged), this, |
| | | 660 | | [this](int) { apply_button_->setEnabled(true); }); |
| | 0 | 661 | | connect(graph_spec_dump_preferences_widgets_.node_name_checkbox_, &QCheckBox::toggled, this, |
| | | 662 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 663 | | connect(graph_spec_dump_preferences_widgets_.node_kind_checkbox_, &QCheckBox::toggled, this, |
| | | 664 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 665 | | connect(graph_spec_dump_preferences_widgets_.node_settings_checkbox_, &QCheckBox::toggled, this, |
| | | 666 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 667 | | connect(graph_spec_dump_preferences_widgets_.edge_indices_checkbox_, &QCheckBox::toggled, this, |
| | | 668 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | | 669 | | |
| | 0 | 670 | | connect(graph_compiled_dump_preferences_widgets_.rankdir_combo_, |
| | | 671 | | qOverload<int>(&QComboBox::currentIndexChanged), this, |
| | | 672 | | [this](int) { apply_button_->setEnabled(true); }); |
| | 0 | 673 | | connect(graph_compiled_dump_preferences_widgets_.layout_combo_, |
| | | 674 | | qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) { |
| | | 675 | | graph_compiled_dump_preferences_widgets_.rankdir_combo_->setEnabled( |
| | | 676 | | index == static_cast<int>(GraphCompiledDumpPreferences::Layout::Normal)); |
| | | 677 | | apply_button_->setEnabled(true); |
| | | 678 | | }); |
| | 0 | 679 | | connect(graph_compiled_dump_preferences_widgets_.floating_point_precision_spin_, |
| | | 680 | | qOverload<int>(&QSpinBox::valueChanged), this, |
| | | 681 | | [this](int) { apply_button_->setEnabled(true); }); |
| | 0 | 682 | | connect(graph_compiled_dump_preferences_widgets_.node_name_checkbox_, &QCheckBox::toggled, this, |
| | | 683 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 684 | | connect(graph_compiled_dump_preferences_widgets_.node_kind_checkbox_, &QCheckBox::toggled, this, |
| | | 685 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 686 | | connect(graph_compiled_dump_preferences_widgets_.node_settings_checkbox_, &QCheckBox::toggled, |
| | | 687 | | this, [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 688 | | connect(graph_compiled_dump_preferences_widgets_.node_in_out_tids_, &QCheckBox::toggled, this, |
| | | 689 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 690 | | connect(graph_compiled_dump_preferences_widgets_.edge_indices_checkbox_, &QCheckBox::toggled, |
| | | 691 | | this, [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 692 | | connect(graph_compiled_dump_preferences_widgets_.edge_desc_checkbox_, &QCheckBox::toggled, this, |
| | | 693 | | [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 694 | | connect(graph_compiled_dump_preferences_widgets_.section_toggle_checkbox_, &QCheckBox::toggled, |
| | | 695 | | this, [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 696 | | connect(graph_compiled_dump_preferences_widgets_.section_stream_addr_checkbox_, |
| | | 697 | | &QCheckBox::toggled, this, [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 698 | | connect(graph_compiled_dump_preferences_widgets_.resources_toggle_checkbox_, |
| | | 699 | | &QCheckBox::toggled, this, [this](bool) { apply_button_->setEnabled(true); }); |
| | 0 | 700 | | } |
| | | 701 | | |
| | | 702 | | holovibes::pipeline::Manager &manager_; |
| | | 703 | | |
| | 0 | 704 | | QPushButton *apply_button_ = nullptr; |
| | | 705 | | |
| | | 706 | | struct GraphSpecDumpPreferencesWidgets { |
| | | 707 | | // dump preferences |
| | | 708 | | // rankdir: LR | TB |
| | | 709 | | QComboBox *rankdir_combo_ = nullptr; |
| | | 710 | | QSpinBox *floating_point_precision_spin_ = nullptr; |
| | | 711 | | // Nodes |
| | | 712 | | QCheckBox *node_name_checkbox_ = nullptr; |
| | | 713 | | QCheckBox *node_kind_checkbox_ = nullptr; |
| | | 714 | | QCheckBox *node_settings_checkbox_ = nullptr; |
| | | 715 | | // Edges |
| | | 716 | | QCheckBox *edge_indices_checkbox_ = nullptr; |
| | | 717 | | }; |
| | | 718 | | GraphSpecDumpPreferencesWidgets graph_spec_dump_preferences_widgets_; |
| | | 719 | | |
| | | 720 | | struct GraphCompiledDumpPreferencesWidgets { |
| | | 721 | | // dump preferences |
| | | 722 | | // rankdir: LR | TB |
| | | 723 | | QComboBox *rankdir_combo_ = nullptr; |
| | | 724 | | QComboBox *layout_combo_ = nullptr; |
| | | 725 | | QSpinBox *floating_point_precision_spin_ = nullptr; |
| | | 726 | | QCheckBox *node_name_checkbox_ = nullptr; |
| | | 727 | | QCheckBox *node_kind_checkbox_ = nullptr; |
| | | 728 | | QCheckBox *node_settings_checkbox_ = nullptr; |
| | | 729 | | QCheckBox *node_in_out_tids_ = nullptr; |
| | | 730 | | // Edges |
| | | 731 | | QCheckBox *edge_indices_checkbox_ = nullptr; |
| | | 732 | | QCheckBox *edge_desc_checkbox_ = nullptr; |
| | | 733 | | |
| | | 734 | | // Section |
| | | 735 | | QCheckBox *section_toggle_checkbox_ = nullptr; |
| | | 736 | | QCheckBox *section_stream_addr_checkbox_ = nullptr; |
| | | 737 | | |
| | | 738 | | // Resources |
| | | 739 | | QCheckBox *resources_toggle_checkbox_ = nullptr; |
| | | 740 | | }; |
| | | 741 | | GraphCompiledDumpPreferencesWidgets graph_compiled_dump_preferences_widgets_; |
| | | 742 | | }; |
| | | 743 | | |
| | | 744 | | } // namespace |
| | | 745 | | |
| | | 746 | | namespace holovibes::ui { |
| | | 747 | | |
| | | 748 | | MainWindow::MainWindow(QWidget *parent) |
| | 0 | 749 | | : QMainWindow(parent), session_id_(QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss")) { |
| | 0 | 750 | | setup_main_layout(); |
| | 0 | 751 | | initialize_display_widgets(); |
| | 0 | 752 | | restore_persistent_state(); |
| | 0 | 753 | | configure_unsupported_features(); |
| | 0 | 754 | | initialize_pipeline_manager(); |
| | | 755 | | |
| | 0 | 756 | | connect_manager_signals(); |
| | 0 | 757 | | connect_import_controls(); |
| | 0 | 758 | | connect_export_controls(); |
| | 0 | 759 | | setup_validation_connections(); |
| | 0 | 760 | | setup_update_connections(); |
| | | 761 | | |
| | 0 | 762 | | validate_inputs(); |
| | 0 | 763 | | refresh_command_bar(); |
| | 0 | 764 | | configure_window(); |
| | 0 | 765 | | check_for_updates(); |
| | 0 | 766 | | } |
| | | 767 | | |
| | 0 | 768 | | void MainWindow::setup_main_layout() { |
| | 0 | 769 | | auto *central_widget = new QWidget(this); |
| | 0 | 770 | | setCentralWidget(central_widget); |
| | | 771 | | |
| | 0 | 772 | | auto *main_layout = new QVBoxLayout(central_widget); |
| | 0 | 773 | | main_layout->setContentsMargins(8, 6, 8, 8); |
| | 0 | 774 | | main_layout->setSpacing(4); |
| | | 775 | | |
| | 0 | 776 | | auto *session_bar = new QFrame(central_widget); |
| | 0 | 777 | | session_bar->setObjectName("sessionBar"); |
| | 0 | 778 | | session_bar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); |
| | 0 | 779 | | session_bar->setFixedHeight(kTopBarHeight); |
| | 0 | 780 | | auto *session_layout = new QHBoxLayout(session_bar); |
| | 0 | 781 | | session_layout->setContentsMargins(8, 2, 8, 2); |
| | 0 | 782 | | session_layout->setSpacing(6); |
| | | 783 | | |
| | 0 | 784 | | auto *patient_label = new QLabel("Patient", session_bar); |
| | 0 | 785 | | patient_line_edit_ = new QLineEdit(session_bar); |
| | 0 | 786 | | patient_line_edit_->setObjectName("patientField"); |
| | 0 | 787 | | patient_line_edit_->setText("PATIENT"); |
| | 0 | 788 | | patient_line_edit_->setPlaceholderText("PATIENT"); |
| | 0 | 789 | | patient_line_edit_->setMinimumWidth(180); |
| | 0 | 790 | | patient_line_edit_->setMaximumWidth(260); |
| | | 791 | | |
| | 0 | 792 | | auto *eye_label = new QLabel("Eye", session_bar); |
| | 0 | 793 | | eye_side_combo_ = create_combo_box(session_bar, QStringList{"NA", "OD", "OS", "OU"}); |
| | 0 | 794 | | eye_side_combo_->setObjectName("eyeSideField"); |
| | 0 | 795 | | eye_side_combo_->setToolTip( |
| | | 796 | | "Eye side used in recording file names. OD: right eye, OS: left eye, OU: both eyes, NA: not " |
| | | 797 | | "specified."); |
| | | 798 | | |
| | 0 | 799 | | auto *session_label = new QLabel("Session", session_bar); |
| | 0 | 800 | | session_value_label_ = new QLabel(session_id_, session_bar); |
| | 0 | 801 | | session_value_label_->setObjectName("sessionValue"); |
| | | 802 | | |
| | 0 | 803 | | auto *next_acquisition_label = new QLabel("Next Acquisition", session_bar); |
| | 0 | 804 | | acquisition_value_label_ = new QLabel(acquisition_label(next_acquisition_id_), session_bar); |
| | 0 | 805 | | acquisition_value_label_->setObjectName("sessionValue"); |
| | | 806 | | |
| | 0 | 807 | | session_layout->addWidget(patient_label); |
| | 0 | 808 | | session_layout->addWidget(patient_line_edit_); |
| | 0 | 809 | | session_layout->addSpacing(8); |
| | 0 | 810 | | session_layout->addWidget(eye_label); |
| | 0 | 811 | | session_layout->addWidget(eye_side_combo_); |
| | 0 | 812 | | session_layout->addSpacing(16); |
| | 0 | 813 | | session_layout->addWidget(session_label); |
| | 0 | 814 | | session_layout->addWidget(session_value_label_); |
| | 0 | 815 | | session_layout->addSpacing(16); |
| | 0 | 816 | | session_layout->addWidget(next_acquisition_label); |
| | 0 | 817 | | session_layout->addWidget(acquisition_value_label_); |
| | 0 | 818 | | session_layout->addStretch(1); |
| | | 819 | | |
| | 0 | 820 | | update_indicator_ = new QToolButton(session_bar); |
| | 0 | 821 | | update_indicator_->setObjectName("updateIndicator"); |
| | 0 | 822 | | update_indicator_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); |
| | 0 | 823 | | update_indicator_->setIcon( |
| | | 824 | | QIcon(QStringLiteral(":/resources/holovibes/assets/download_update.svg"))); |
| | 0 | 825 | | update_indicator_->setIconSize(QSize(14, 14)); |
| | 0 | 826 | | update_indicator_->setCursor(Qt::PointingHandCursor); |
| | 0 | 827 | | update_indicator_->setVisible(false); |
| | 0 | 828 | | update_indicator_->setToolTip(tr("Open the Holoflow release page")); |
| | 0 | 829 | | session_layout->addWidget(update_indicator_); |
| | 0 | 830 | | main_layout->addWidget(session_bar, 0); |
| | | 831 | | |
| | 0 | 832 | | render_widget_ = new ImageRenderingWidget(this); |
| | 0 | 833 | | view_widget_ = new ViewWidget(this); |
| | 0 | 834 | | import_widget_ = new ImportWidget(this); |
| | 0 | 835 | | export_widget_ = new ExportWidget(this); |
| | | 836 | | |
| | 0 | 837 | | auto *command_bar = new QFrame(central_widget); |
| | 0 | 838 | | command_bar->setObjectName("commandBar"); |
| | 0 | 839 | | command_bar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); |
| | 0 | 840 | | command_bar->setFixedHeight(kTopBarHeight); |
| | 0 | 841 | | auto *command_layout = new QHBoxLayout(command_bar); |
| | 0 | 842 | | command_layout->setContentsMargins(8, 2, 8, 2); |
| | 0 | 843 | | command_layout->setSpacing(6); |
| | | 844 | | |
| | 0 | 845 | | start_command_button_ = new QPushButton("Start", command_bar); |
| | 0 | 846 | | stop_command_button_ = new QPushButton("Stop", command_bar); |
| | 0 | 847 | | record_command_button_ = new QPushButton("Record", command_bar); |
| | 0 | 848 | | stop_record_command_button_ = new QPushButton("Stop Rec", command_bar); |
| | 0 | 849 | | start_command_button_->setObjectName("primaryCommand"); |
| | 0 | 850 | | record_command_button_->setObjectName("recordCommand"); |
| | | 851 | | |
| | 0 | 852 | | command_layout->addWidget(start_command_button_); |
| | 0 | 853 | | command_layout->addWidget(stop_command_button_); |
| | 0 | 854 | | command_layout->addSpacing(8); |
| | 0 | 855 | | command_layout->addWidget(record_command_button_); |
| | 0 | 856 | | command_layout->addWidget(stop_record_command_button_); |
| | 0 | 857 | | command_layout->addSpacing(16); |
| | | 858 | | |
| | | 859 | | auto add_status_field = [&](const QString &label, QLabel *&value_label) { |
| | | 860 | | auto *name_label = new QLabel(label, command_bar); |
| | | 861 | | name_label->setObjectName("commandLabel"); |
| | | 862 | | value_label = new QLabel(command_bar); |
| | | 863 | | value_label->setObjectName("commandValue"); |
| | | 864 | | command_layout->addWidget(name_label); |
| | | 865 | | command_layout->addWidget(value_label); |
| | | 866 | | command_layout->addSpacing(12); |
| | 0 | 867 | | }; |
| | | 868 | | |
| | 0 | 869 | | add_status_field("Status", pipeline_status_label_); |
| | 0 | 870 | | add_status_field("Source", source_status_label_); |
| | 0 | 871 | | add_status_field("View", view_status_label_); |
| | 0 | 872 | | add_status_field("FPS", fps_status_label_); |
| | 0 | 873 | | add_status_field("Recording", recording_status_label_); |
| | 0 | 874 | | command_layout->addStretch(1); |
| | 0 | 875 | | main_layout->addWidget(command_bar, 0); |
| | | 876 | | |
| | 0 | 877 | | connect(start_command_button_, &QPushButton::clicked, this, &MainWindow::on_import_start_clicked); |
| | 0 | 878 | | connect(stop_command_button_, &QPushButton::clicked, this, &MainWindow::on_import_stop_clicked); |
| | 0 | 879 | | connect(record_command_button_, &QPushButton::clicked, this, |
| | | 880 | | &MainWindow::on_export_record_clicked); |
| | 0 | 881 | | connect(stop_record_command_button_, &QPushButton::clicked, this, |
| | | 882 | | &MainWindow::on_export_stop_clicked); |
| | 0 | 883 | | connect(update_indicator_, &QToolButton::clicked, this, [this]() { |
| | | 884 | | if (available_update_url_.isValid()) { |
| | | 885 | | QDesktopServices::openUrl(available_update_url_); |
| | | 886 | | } |
| | | 887 | | }); |
| | | 888 | | |
| | 0 | 889 | | auto *content_row = new QWidget(central_widget); |
| | 0 | 890 | | auto *content_layout = new QHBoxLayout(content_row); |
| | 0 | 891 | | content_layout->setContentsMargins(0, 0, 0, 0); |
| | 0 | 892 | | content_layout->setSpacing(8); |
| | 0 | 893 | | main_layout->addWidget(content_row, 1); |
| | | 894 | | |
| | 0 | 895 | | auto *controls_content = new QWidget(content_row); |
| | 0 | 896 | | controls_content->setObjectName("controlsContent"); |
| | 0 | 897 | | controls_content->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); |
| | 0 | 898 | | auto *controls_layout = new QHBoxLayout(controls_content); |
| | 0 | 899 | | controls_layout->setContentsMargins(0, 0, 0, 0); |
| | 0 | 900 | | controls_layout->setSpacing(12); |
| | | 901 | | |
| | 0 | 902 | | auto *acquisition_column = new QWidget(controls_content); |
| | 0 | 903 | | acquisition_column->setObjectName("controlsColumn"); |
| | 0 | 904 | | auto *acquisition_layout = new QVBoxLayout(acquisition_column); |
| | 0 | 905 | | acquisition_layout->setContentsMargins(0, 0, 0, 0); |
| | 0 | 906 | | acquisition_layout->setSpacing(12); |
| | 0 | 907 | | acquisition_layout->addWidget(import_widget_); |
| | 0 | 908 | | acquisition_layout->addWidget(export_widget_); |
| | 0 | 909 | | acquisition_layout->addWidget(view_widget_); |
| | 0 | 910 | | acquisition_layout->addWidget(view_widget_->post_processing_group()); |
| | 0 | 911 | | acquisition_layout->addStretch(1); |
| | | 912 | | |
| | 0 | 913 | | auto *processing_column = new QWidget(controls_content); |
| | 0 | 914 | | processing_column->setObjectName("controlsColumn"); |
| | 0 | 915 | | auto *processing_layout = new QVBoxLayout(processing_column); |
| | 0 | 916 | | processing_layout->setContentsMargins(0, 0, 0, 0); |
| | 0 | 917 | | processing_layout->setSpacing(12); |
| | 0 | 918 | | processing_layout->addWidget(render_widget_); |
| | 0 | 919 | | processing_layout->addWidget(render_widget_->autofocus_widget()); |
| | 0 | 920 | | processing_layout->addStretch(1); |
| | | 921 | | |
| | 0 | 922 | | auto *controls_divider = new QFrame(controls_content); |
| | 0 | 923 | | controls_divider->setObjectName("controlsColumnDivider"); |
| | 0 | 924 | | controls_divider->setFixedWidth(1); |
| | 0 | 925 | | controls_divider->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); |
| | | 926 | | |
| | 0 | 927 | | controls_layout->addWidget(acquisition_column); |
| | 0 | 928 | | controls_layout->addWidget(controls_divider); |
| | 0 | 929 | | controls_layout->addWidget(processing_column); |
| | | 930 | | |
| | 0 | 931 | | auto *controls_scroll = new QScrollArea(content_row); |
| | 0 | 932 | | controls_scroll->setObjectName("controlsScrollArea"); |
| | 0 | 933 | | controls_scroll->viewport()->setObjectName("controlsScrollViewport"); |
| | 0 | 934 | | controls_scroll->setWidgetResizable(true); |
| | 0 | 935 | | controls_scroll->setFrameShape(QFrame::NoFrame); |
| | 0 | 936 | | controls_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
| | 0 | 937 | | controls_scroll->setWidget(controls_content); |
| | 0 | 938 | | const int controls_width = |
| | | 939 | | std::max(controls_content->sizeHint().width() + |
| | | 940 | | controls_scroll->verticalScrollBar()->sizeHint().width() + 12, |
| | | 941 | | 430); |
| | 0 | 942 | | controls_scroll->setFixedWidth(controls_width); |
| | 0 | 943 | | controls_scroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); |
| | | 944 | | |
| | 0 | 945 | | display_workspace_ = new VisualizationWorkspace(content_row); |
| | | 946 | | |
| | 0 | 947 | | right_sidebar_ = new QWidget(content_row); |
| | 0 | 948 | | right_sidebar_->setObjectName("rightSidebar"); |
| | 0 | 949 | | right_sidebar_->setMinimumWidth(280); |
| | 0 | 950 | | right_sidebar_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); |
| | 0 | 951 | | auto *right_sidebar_layout = new QVBoxLayout(right_sidebar_); |
| | 0 | 952 | | right_sidebar_layout->setContentsMargins(0, 0, 0, 0); |
| | 0 | 953 | | right_sidebar_layout->setSpacing(8); |
| | | 954 | | |
| | 0 | 955 | | monitor_widget_ = new SystemMonitorWidget(right_sidebar_); |
| | 0 | 956 | | monitor_widget_->setObjectName("systemMonitorPanel"); |
| | 0 | 957 | | monitor_widget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); |
| | 0 | 958 | | selected_widget_settings_panel_ = new SelectedWidgetSettingsPanel(right_sidebar_); |
| | 0 | 959 | | right_sidebar_layout->addWidget(monitor_widget_); |
| | 0 | 960 | | right_sidebar_layout->addWidget(selected_widget_settings_panel_, 1); |
| | | 961 | | |
| | 0 | 962 | | const int right_sidebar_width = std::max(280, right_sidebar_->sizeHint().width()); |
| | 0 | 963 | | right_sidebar_->setFixedWidth(right_sidebar_width); |
| | | 964 | | |
| | 0 | 965 | | content_layout->addWidget(controls_scroll, 0); |
| | 0 | 966 | | content_layout->addWidget(display_workspace_, 1); |
| | 0 | 967 | | content_layout->addWidget(right_sidebar_, 0); |
| | | 968 | | |
| | 0 | 969 | | connect(patient_line_edit_, &QLineEdit::textChanged, this, |
| | | 970 | | &MainWindow::update_recording_path_preview); |
| | 0 | 971 | | connect(eye_side_combo_, qOverload<int>(&QComboBox::currentIndexChanged), this, |
| | | 972 | | [this](int) { update_recording_path_preview(); }); |
| | 0 | 973 | | } |
| | | 974 | | |
| | 0 | 975 | | void MainWindow::refresh_visualization_availability() { |
| | 0 | 976 | | if (display_workspace_ == nullptr || view_widget_ == nullptr || render_widget_ == nullptr) { |
| | 0 | 977 | | return; |
| | | 978 | | } |
| | | 979 | | |
| | 0 | 980 | | auto *autofocus = render_widget_->autofocus_widget(); |
| | 0 | 981 | | const bool autofocus_enabled = autofocus->is_enabled(); |
| | 0 | 982 | | const bool has_zernike_order = |
| | | 983 | | autofocus->is_z2_enabled() || autofocus->is_z3_enabled() || autofocus->is_z4_enabled() || |
| | | 984 | | autofocus->is_z5_enabled() || autofocus->is_z6_enabled() || autofocus->is_z7_enabled() || |
| | | 985 | | autofocus->is_z8_enabled() || autofocus->is_z9_enabled() || autofocus->is_z10_enabled(); |
| | 0 | 986 | | const bool single_reference_slopes = !autofocus->use_graph_laplacian(); |
| | | 987 | | |
| | 0 | 988 | | const auto autofocus_message = tr("Enable Auto Focus to generate this visualization."); |
| | 0 | 989 | | const auto zernike_order_message = |
| | | 990 | | tr("Select at least one Zernike coefficient to generate the metrics plot."); |
| | 0 | 991 | | const auto xcorr_message = |
| | | 992 | | tr("Cross-correlation is available only in Single reference slope recovery mode."); |
| | 0 | 993 | | const auto spectrum_message = |
| | | 994 | | tr("Spectrum visualizations are not generated by the current pipeline."); |
| | 0 | 995 | | const auto cuts_message = |
| | | 996 | | tr("3D cut visualizations are not currently supported by the pipeline."); |
| | | 997 | | |
| | 0 | 998 | | display_workspace_->set_visualization_content_expectations({ |
| | | 999 | | {"xy_processed", {true, {}}}, |
| | | 1000 | | {"zernike_a4", |
| | | 1001 | | {autofocus_enabled && has_zernike_order, |
| | | 1002 | | autofocus_enabled ? zernike_order_message : autofocus_message}}, |
| | | 1003 | | {"zernike_phase", {autofocus_enabled, autofocus_message}}, |
| | | 1004 | | {"shack_hartmann", {autofocus_enabled, autofocus_message}}, |
| | | 1005 | | {"shack_hartmann_xcorr", |
| | | 1006 | | {autofocus_enabled && single_reference_slopes, |
| | | 1007 | | autofocus_enabled ? xcorr_message : autofocus_message}}, |
| | | 1008 | | {"xy_raw", {true, {}}}, |
| | | 1009 | | {"raw_spectrum", {false, spectrum_message}}, |
| | | 1010 | | {"processed_spectrum", {false, spectrum_message}}, |
| | | 1011 | | {"xz_processed", {false, cuts_message}}, |
| | | 1012 | | {"yz_processed", {false, cuts_message}}, |
| | | 1013 | | {"pipeline_graph", {true, {}}}, |
| | | 1014 | | }); |
| | 0 | 1015 | | } |
| | | 1016 | | |
| | 0 | 1017 | | void MainWindow::select_configurable_widget(ZernikeHistoryWidget *widget) { |
| | 0 | 1018 | | if (widget == nullptr) { |
| | 0 | 1019 | | selected_widget_settings_panel_->clear_selection(); |
| | 0 | 1020 | | return; |
| | | 1021 | | } |
| | | 1022 | | |
| | 0 | 1023 | | display_workspace_->select_visualization("zernike_a4"); |
| | 0 | 1024 | | } |
| | | 1025 | | |
| | 0 | 1026 | | void MainWindow::on_selected_visualization_changed(const QString &visualization_id) { |
| | 0 | 1027 | | if (visualization_id == "zernike_a4") { |
| | 0 | 1028 | | selected_widget_settings_panel_->set_selected_widget(zernike_history_widget_); |
| | 0 | 1029 | | } else if (visualization_id.isEmpty()) { |
| | 0 | 1030 | | selected_widget_settings_panel_->clear_selection(); |
| | 0 | 1031 | | } else { |
| | 0 | 1032 | | selected_widget_settings_panel_->show_no_configurable_settings(); |
| | | 1033 | | } |
| | 0 | 1034 | | } |
| | | 1035 | | |
| | 0 | 1036 | | QString MainWindow::sanitize_recording_token(const QString &value) const { |
| | 0 | 1037 | | QString token = value.trimmed(); |
| | 0 | 1038 | | if (token.isEmpty()) { |
| | 0 | 1039 | | token = "PATIENT"; |
| | | 1040 | | } |
| | | 1041 | | |
| | 0 | 1042 | | token.replace(QRegularExpression(R"(\s+)"), "_"); |
| | 0 | 1043 | | token.remove(QRegularExpression(R"([^A-Za-z0-9_-])")); |
| | 0 | 1044 | | token = token.left(64); |
| | | 1045 | | |
| | 0 | 1046 | | if (token.isEmpty()) { |
| | 0 | 1047 | | token = "PATIENT"; |
| | | 1048 | | } |
| | | 1049 | | |
| | 0 | 1050 | | return token; |
| | 0 | 1051 | | } |
| | | 1052 | | |
| | 0 | 1053 | | QString MainWindow::recording_file_name(int acquisition_id) const { |
| | 0 | 1054 | | const QString patient = sanitize_recording_token(patient_line_edit_->text()); |
| | 0 | 1055 | | const QString eye = sanitize_recording_token(eye_side_combo_->currentText()); |
| | 0 | 1056 | | return QString("%1_%2_%3_%4.holo") |
| | | 1057 | | .arg(patient, eye, session_id_, acquisition_label(acquisition_id)); |
| | 0 | 1058 | | } |
| | | 1059 | | |
| | 0 | 1060 | | QString MainWindow::acquisition_label(int acquisition_id) const { |
| | 0 | 1061 | | return QString("AQ%1").arg(std::max(acquisition_id, 1), 3, 10, QChar('0')); |
| | 0 | 1062 | | } |
| | | 1063 | | |
| | 0 | 1064 | | void MainWindow::update_acquisition_label() { |
| | 0 | 1065 | | if (acquisition_value_label_ != nullptr) { |
| | 0 | 1066 | | acquisition_value_label_->setText(acquisition_label(next_acquisition_id_)); |
| | | 1067 | | } |
| | 0 | 1068 | | } |
| | | 1069 | | |
| | 0 | 1070 | | void MainWindow::update_recording_path_preview() { |
| | 0 | 1071 | | if (export_widget_ == nullptr) { |
| | 0 | 1072 | | return; |
| | | 1073 | | } |
| | | 1074 | | |
| | 0 | 1075 | | QFileInfo info(export_widget_->get_file_path()); |
| | 0 | 1076 | | QString directory = info.absolutePath(); |
| | 0 | 1077 | | if (directory.isEmpty() || directory == ".") { |
| | 0 | 1078 | | directory = QDir::currentPath(); |
| | | 1079 | | } |
| | | 1080 | | |
| | 0 | 1081 | | export_widget_->set_file_path( |
| | | 1082 | | QDir(directory).filePath(recording_file_name(next_acquisition_id_))); |
| | 0 | 1083 | | update_acquisition_label(); |
| | 0 | 1084 | | refresh_command_bar(); |
| | 0 | 1085 | | } |
| | | 1086 | | |
| | 0 | 1087 | | void MainWindow::set_status_label(QLabel *label, const QString &text, const char *tone) { |
| | 0 | 1088 | | if (label == nullptr) { |
| | 0 | 1089 | | return; |
| | | 1090 | | } |
| | | 1091 | | |
| | 0 | 1092 | | label->setText(text); |
| | 0 | 1093 | | label->setProperty("statusTone", QString::fromLatin1(tone)); |
| | 0 | 1094 | | label->style()->unpolish(label); |
| | 0 | 1095 | | label->style()->polish(label); |
| | 0 | 1096 | | label->update(); |
| | 0 | 1097 | | } |
| | | 1098 | | |
| | 0 | 1099 | | void MainWindow::configure_unsupported_features() { |
| | 0 | 1100 | | if (view_widget_ == nullptr || render_widget_ == nullptr) { |
| | 0 | 1101 | | return; |
| | | 1102 | | } |
| | | 1103 | | |
| | 0 | 1104 | | const QString registration_tooltip = |
| | | 1105 | | tr("Visible for planned support. The current pipeline does not support registration yet."); |
| | 0 | 1106 | | const QString convolution_tooltip = |
| | | 1107 | | tr("Visible for planned support. The current pipeline does not support convolution kernels " |
| | | 1108 | | "yet."); |
| | | 1109 | | |
| | | 1110 | | { |
| | 0 | 1111 | | QSignalBlocker blocker(view_widget_->registration_check()); |
| | 0 | 1112 | | view_widget_->set_registration_enabled(false); |
| | 0 | 1113 | | } |
| | 0 | 1114 | | view_widget_->registration_check()->setEnabled(false); |
| | 0 | 1115 | | view_widget_->registration_check()->setToolTip(registration_tooltip); |
| | 0 | 1116 | | view_widget_->registration_radius()->setEnabled(false); |
| | 0 | 1117 | | view_widget_->registration_radius()->setToolTip(registration_tooltip); |
| | | 1118 | | |
| | | 1119 | | { |
| | 0 | 1120 | | QSignalBlocker blocker(render_widget_->convolution_combo()); |
| | 0 | 1121 | | render_widget_->set_convolution(QStringLiteral("None")); |
| | 0 | 1122 | | } |
| | | 1123 | | { |
| | 0 | 1124 | | QSignalBlocker blocker(render_widget_->convolution_divide_check()); |
| | 0 | 1125 | | render_widget_->set_convolution_divide(false); |
| | 0 | 1126 | | } |
| | 0 | 1127 | | render_widget_->convolution_combo()->setEnabled(false); |
| | 0 | 1128 | | render_widget_->convolution_combo()->setToolTip(convolution_tooltip); |
| | 0 | 1129 | | render_widget_->convolution_divide_check()->setEnabled(false); |
| | 0 | 1130 | | render_widget_->convolution_divide_check()->setToolTip(convolution_tooltip); |
| | 0 | 1131 | | refresh_visualization_availability(); |
| | 0 | 1132 | | } |
| | | 1133 | | |
| | 0 | 1134 | | void MainWindow::refresh_command_bar() { |
| | | 1135 | | if (start_command_button_ == nullptr || import_widget_ == nullptr || render_widget_ == nullptr || |
| | 0 | 1136 | | view_widget_ == nullptr || export_widget_ == nullptr) { |
| | 0 | 1137 | | return; |
| | | 1138 | | } |
| | | 1139 | | |
| | 0 | 1140 | | const QString primary_action = pipeline_running_ ? QStringLiteral("Pause") |
| | | 1141 | | : pipeline_paused_ ? QStringLiteral("Resume") |
| | | 1142 | | : QStringLiteral("Start"); |
| | 0 | 1143 | | import_widget_->start_button()->setText(primary_action); |
| | 0 | 1144 | | start_command_button_->setText(primary_action); |
| | 0 | 1145 | | start_command_button_->setEnabled(import_widget_->start_button()->isEnabled()); |
| | 0 | 1146 | | stop_command_button_->setEnabled(import_widget_->stop_button()->isEnabled()); |
| | 0 | 1147 | | record_command_button_->setEnabled(export_widget_->record_button()->isEnabled()); |
| | 0 | 1148 | | stop_record_command_button_->setEnabled(export_widget_->stop_button()->isEnabled()); |
| | | 1149 | | |
| | 0 | 1150 | | if (pause_requested_) { |
| | 0 | 1151 | | set_status_label(pipeline_status_label_, "Pausing", "warning"); |
| | 0 | 1152 | | } else if (resume_in_progress_) { |
| | 0 | 1153 | | set_status_label(pipeline_status_label_, "Resuming", "warning"); |
| | 0 | 1154 | | } else if (update_in_progress_) { |
| | 0 | 1155 | | set_status_label(pipeline_status_label_, "Updating", "warning"); |
| | 0 | 1156 | | } else if (pipeline_running_) { |
| | 0 | 1157 | | set_status_label(pipeline_status_label_, "Live", "success"); |
| | 0 | 1158 | | } else if (pipeline_paused_) { |
| | 0 | 1159 | | set_status_label(pipeline_status_label_, "Paused", "warning"); |
| | 0 | 1160 | | } else { |
| | 0 | 1161 | | set_status_label(pipeline_status_label_, "Idle", "muted"); |
| | | 1162 | | } |
| | | 1163 | | |
| | 0 | 1164 | | QString source_text; |
| | 0 | 1165 | | if (import_widget_->is_camera_mode()) { |
| | 0 | 1166 | | source_text = import_widget_->get_camera_type(); |
| | 0 | 1167 | | } else { |
| | 0 | 1168 | | const QFileInfo file_info(import_widget_->get_file_path()); |
| | 0 | 1169 | | source_text = file_info.fileName().isEmpty() ? QStringLiteral("File") : file_info.fileName(); |
| | 0 | 1170 | | } |
| | 0 | 1171 | | source_status_label_->setText(source_text); |
| | | 1172 | | |
| | 0 | 1173 | | view_status_label_->setText( |
| | | 1174 | | QString("%1 %2").arg(render_widget_->get_image_mode(), view_widget_->get_image_type())); |
| | | 1175 | | |
| | 0 | 1176 | | if (!pipeline_running_) { |
| | 0 | 1177 | | fps_status_label_->setText("--"); |
| | | 1178 | | } |
| | | 1179 | | |
| | 0 | 1180 | | if (export_in_progress_) { |
| | 0 | 1181 | | set_status_label(recording_status_label_, "Recording", "danger"); |
| | 0 | 1182 | | } else if (export_widget_->isChecked()) { |
| | 0 | 1183 | | set_status_label(recording_status_label_, "Armed", "warning"); |
| | 0 | 1184 | | } else { |
| | 0 | 1185 | | set_status_label(recording_status_label_, "Off", "muted"); |
| | | 1186 | | } |
| | 0 | 1187 | | } |
| | | 1188 | | |
| | 0 | 1189 | | void MainWindow::save_persistent_state() { |
| | 0 | 1190 | | QSettings settings; |
| | | 1191 | | |
| | 0 | 1192 | | settings.beginGroup("main_window"); |
| | 0 | 1193 | | settings.setValue("geometry", saveGeometry()); |
| | 0 | 1194 | | settings.endGroup(); |
| | | 1195 | | |
| | 0 | 1196 | | settings.beginGroup("session"); |
| | 0 | 1197 | | settings.setValue("patient", patient_line_edit_->text()); |
| | 0 | 1198 | | settings.setValue("eye_side", eye_side_combo_->currentText()); |
| | 0 | 1199 | | settings.endGroup(); |
| | | 1200 | | |
| | 0 | 1201 | | settings.beginGroup("import"); |
| | 0 | 1202 | | settings.setValue("camera_mode", import_widget_->is_camera_mode()); |
| | 0 | 1203 | | settings.setValue("file_path", import_widget_->get_file_path()); |
| | 0 | 1204 | | settings.setValue("fps_limit", import_widget_->get_fps_limit().value_or(0)); |
| | 0 | 1205 | | settings.setValue("sampling_frequency_hz", import_widget_->get_sampling_frequency_hz()); |
| | 0 | 1206 | | settings.setValue("start_index", import_widget_->get_start_index()); |
| | 0 | 1207 | | settings.setValue("end_index", import_widget_->get_end_index()); |
| | 0 | 1208 | | settings.setValue("load_method", import_widget_->get_load_method()); |
| | 0 | 1209 | | settings.setValue("camera_type", import_widget_->get_camera_type()); |
| | 0 | 1210 | | settings.setValue("camera_config", import_widget_->get_camera_config()); |
| | 0 | 1211 | | settings.endGroup(); |
| | | 1212 | | |
| | 0 | 1213 | | settings.beginGroup("rendering"); |
| | 0 | 1214 | | settings.setValue("image_mode", render_widget_->get_image_mode()); |
| | 0 | 1215 | | settings.setValue("batch_size", render_widget_->get_batch_size()); |
| | 0 | 1216 | | settings.setValue("time_stride", render_widget_->get_time_stride()); |
| | 0 | 1217 | | settings.setValue("filter_2d", render_widget_->is_filter_2d_enabled()); |
| | 0 | 1218 | | settings.setValue("filter_inner", render_widget_->get_filter_inner()); |
| | 0 | 1219 | | settings.setValue("filter_outer", render_widget_->get_filter_outer()); |
| | 0 | 1220 | | settings.setValue("space_transform", render_widget_->get_space_transform()); |
| | 0 | 1221 | | settings.setValue("time_transform", render_widget_->get_time_transform()); |
| | 0 | 1222 | | settings.setValue("time_window", render_widget_->get_time_window()); |
| | 0 | 1223 | | settings.setValue("lambda_nm", render_widget_->get_lambda()); |
| | 0 | 1224 | | settings.setValue("focus_mm", render_widget_->get_focus()); |
| | 0 | 1225 | | settings.setValue("asp_padding_enabled", render_widget_->is_asp_padding_enabled()); |
| | 0 | 1226 | | settings.setValue("asp_padded_width", render_widget_->get_asp_padded_width()); |
| | 0 | 1227 | | settings.setValue("asp_padded_height", render_widget_->get_asp_padded_height()); |
| | 0 | 1228 | | settings.setValue("convolution", render_widget_->get_convolution()); |
| | 0 | 1229 | | settings.setValue("convolution_divide", render_widget_->is_convolution_divide()); |
| | 0 | 1230 | | settings.endGroup(); |
| | | 1231 | | |
| | 0 | 1232 | | settings.beginGroup("view"); |
| | 0 | 1233 | | settings.setValue("image_type", view_widget_->get_image_type()); |
| | 0 | 1234 | | settings.setValue("z_origin", view_widget_->get_z_origin()); |
| | 0 | 1235 | | settings.setValue("z_width", view_widget_->get_z_width()); |
| | 0 | 1236 | | settings.setValue("accumulation", view_widget_->get_accumulation()); |
| | 0 | 1237 | | settings.setValue("flatfield", view_widget_->is_flatfield_enabled()); |
| | 0 | 1238 | | settings.setValue("flatfield_cutoff_period_um", view_widget_->get_flatfield_cutoff_period_um()); |
| | 0 | 1239 | | settings.setValue("range_start", view_widget_->get_range_start()); |
| | 0 | 1240 | | settings.setValue("range_end", view_widget_->get_range_end()); |
| | 0 | 1241 | | settings.setValue("registration", view_widget_->is_registration_enabled()); |
| | 0 | 1242 | | settings.setValue("registration_radius", view_widget_->get_registration_radius()); |
| | 0 | 1243 | | settings.setValue("reticle", view_widget_->is_reticle_enabled()); |
| | 0 | 1244 | | settings.setValue("reticle_radius", view_widget_->get_reticle_radius()); |
| | 0 | 1245 | | settings.setValue("pct", view_widget_->is_pct_enabled()); |
| | 0 | 1246 | | settings.setValue("pct_radius", view_widget_->get_pct_radius()); |
| | 0 | 1247 | | settings.endGroup(); |
| | | 1248 | | |
| | 0 | 1249 | | settings.beginGroup("export"); |
| | 0 | 1250 | | settings.setValue("enabled", export_widget_->isChecked()); |
| | 0 | 1251 | | settings.setValue("image_type", export_widget_->get_image_type()); |
| | 0 | 1252 | | settings.setValue("file_path", export_widget_->get_file_path()); |
| | 0 | 1253 | | settings.setValue("tag", export_widget_->get_tag()); |
| | 0 | 1254 | | settings.setValue("frame_count_enabled", export_widget_->is_frame_count_enabled()); |
| | 0 | 1255 | | settings.setValue("frame_count", export_widget_->get_frame_count()); |
| | 0 | 1256 | | settings.endGroup(); |
| | | 1257 | | |
| | 0 | 1258 | | auto *autofocus = render_widget_->autofocus_widget(); |
| | 0 | 1259 | | settings.beginGroup("autofocus"); |
| | 0 | 1260 | | settings.setValue("enabled", autofocus->is_enabled()); |
| | 0 | 1261 | | settings.setValue("nb_subaps", autofocus->get_nb_subaps()); |
| | 0 | 1262 | | settings.setValue("nb_iter", autofocus->get_nb_iter()); |
| | 0 | 1263 | | settings.setValue("skip_subapertures_outside_pupil", |
| | | 1264 | | autofocus->skip_subapertures_outside_pupil()); |
| | 0 | 1265 | | settings.setValue("use_graph_laplacian", autofocus->use_graph_laplacian()); |
| | 0 | 1266 | | settings.setValue("z2_enabled", autofocus->is_z2_enabled()); |
| | 0 | 1267 | | settings.setValue("z3_enabled", autofocus->is_z3_enabled()); |
| | 0 | 1268 | | settings.setValue("z4_enabled", autofocus->is_z4_enabled()); |
| | 0 | 1269 | | settings.setValue("z5_enabled", autofocus->is_z5_enabled()); |
| | 0 | 1270 | | settings.setValue("z6_enabled", autofocus->is_z6_enabled()); |
| | 0 | 1271 | | settings.setValue("z7_enabled", autofocus->is_z7_enabled()); |
| | 0 | 1272 | | settings.setValue("z8_enabled", autofocus->is_z8_enabled()); |
| | 0 | 1273 | | settings.setValue("z9_enabled", autofocus->is_z9_enabled()); |
| | 0 | 1274 | | settings.setValue("z10_enabled", autofocus->is_z10_enabled()); |
| | 0 | 1275 | | settings.endGroup(); |
| | | 1276 | | |
| | 0 | 1277 | | settings.beginGroup("zernike_history_display"); |
| | 0 | 1278 | | const auto display_settings = zernike_history_widget_->display_settings(); |
| | 0 | 1279 | | settings.setValue("time_window_seconds", display_settings.time_window_seconds); |
| | 0 | 1280 | | settings.setValue("y_scaling_mode", static_cast<int>(display_settings.y_scaling_mode)); |
| | 0 | 1281 | | settings.setValue("manual_y_minimum", display_settings.manual_y_minimum); |
| | 0 | 1282 | | settings.setValue("manual_y_maximum", display_settings.manual_y_maximum); |
| | 0 | 1283 | | settings.setValue("show_statistics", display_settings.show_statistics); |
| | 0 | 1284 | | settings.endGroup(); |
| | | 1285 | | |
| | 0 | 1286 | | display_workspace_->save_persistent_state(settings); |
| | | 1287 | | |
| | 0 | 1288 | | settings.sync(); |
| | 0 | 1289 | | } |
| | | 1290 | | |
| | 0 | 1291 | | void MainWindow::restore_persistent_state() { |
| | 0 | 1292 | | QSettings settings; |
| | | 1293 | | |
| | 0 | 1294 | | settings.beginGroup("main_window"); |
| | 0 | 1295 | | if (settings.contains("geometry")) { |
| | 0 | 1296 | | geometry_restored_ = restoreGeometry(settings.value("geometry").toByteArray()); |
| | | 1297 | | } |
| | 0 | 1298 | | settings.endGroup(); |
| | | 1299 | | |
| | 0 | 1300 | | display_workspace_->restore_persistent_state(settings); |
| | 0 | 1301 | | std::optional<bool> legacy_raw_view; |
| | 0 | 1302 | | std::optional<bool> legacy_raw_spectrum; |
| | 0 | 1303 | | std::optional<bool> legacy_processed_spectrum; |
| | 0 | 1304 | | std::optional<bool> legacy_3d_cuts; |
| | 0 | 1305 | | std::optional<bool> legacy_zernike_metrics; |
| | 0 | 1306 | | std::optional<bool> legacy_zernike_phase; |
| | 0 | 1307 | | std::optional<bool> legacy_shack_hartmann; |
| | 0 | 1308 | | std::optional<bool> legacy_shack_hartmann_xcorr; |
| | | 1309 | | |
| | 0 | 1310 | | settings.beginGroup("session"); |
| | 0 | 1311 | | patient_line_edit_->setText(settings.value("patient", patient_line_edit_->text()).toString()); |
| | 0 | 1312 | | restore_combo_text(settings, "eye_side", eye_side_combo_); |
| | 0 | 1313 | | settings.endGroup(); |
| | | 1314 | | |
| | 0 | 1315 | | settings.beginGroup("import"); |
| | 0 | 1316 | | import_widget_->set_camera_mode( |
| | | 1317 | | settings.value("camera_mode", import_widget_->is_camera_mode()).toBool()); |
| | 0 | 1318 | | import_widget_->set_file_path( |
| | | 1319 | | settings.value("file_path", import_widget_->get_file_path()).toString()); |
| | 0 | 1320 | | import_widget_->set_fps_limit(settings.value("fps_limit", 0).toInt()); |
| | 0 | 1321 | | import_widget_->set_start_index( |
| | | 1322 | | settings.value("start_index", import_widget_->get_start_index()).toInt()); |
| | 0 | 1323 | | import_widget_->set_end_index( |
| | | 1324 | | settings.value("end_index", import_widget_->get_end_index()).toInt()); |
| | 0 | 1325 | | restore_combo_text(settings, "load_method", import_widget_->load_method_combo()); |
| | 0 | 1326 | | restore_combo_text(settings, "camera_type", import_widget_->camera_combo()); |
| | 0 | 1327 | | restore_combo_text(settings, "camera_config", import_widget_->camera_config_combo()); |
| | 0 | 1328 | | import_widget_->set_sampling_frequency_hz( |
| | | 1329 | | settings.value("sampling_frequency_hz", import_widget_->get_sampling_frequency_hz()) |
| | | 1330 | | .toDouble()); |
| | 0 | 1331 | | settings.endGroup(); |
| | | 1332 | | |
| | 0 | 1333 | | settings.beginGroup("rendering"); |
| | 0 | 1334 | | restore_combo_text(settings, "image_mode", render_widget_->image_combo()); |
| | 0 | 1335 | | render_widget_->set_batch_size( |
| | | 1336 | | settings.value("batch_size", render_widget_->get_batch_size()).toInt()); |
| | 0 | 1337 | | render_widget_->set_time_stride( |
| | | 1338 | | settings.value("time_stride", render_widget_->get_time_stride()).toInt()); |
| | 0 | 1339 | | render_widget_->set_filter_2d_enabled( |
| | | 1340 | | settings.value("filter_2d", render_widget_->is_filter_2d_enabled()).toBool()); |
| | 0 | 1341 | | render_widget_->set_filter_inner( |
| | | 1342 | | settings.value("filter_inner", render_widget_->get_filter_inner()).toInt()); |
| | 0 | 1343 | | render_widget_->set_filter_outer( |
| | | 1344 | | settings.value("filter_outer", render_widget_->get_filter_outer()).toInt()); |
| | 0 | 1345 | | restore_combo_text(settings, "space_transform", render_widget_->space_transform_combo()); |
| | 0 | 1346 | | restore_combo_text(settings, "time_transform", render_widget_->time_transform_combo()); |
| | 0 | 1347 | | render_widget_->set_time_window( |
| | | 1348 | | settings.value("time_window", render_widget_->get_time_window()).toInt()); |
| | 0 | 1349 | | render_widget_->set_lambda(settings.value("lambda_nm", render_widget_->get_lambda()).toInt()); |
| | 0 | 1350 | | render_widget_->set_focus(settings.value("focus_mm", render_widget_->get_focus()).toInt()); |
| | 0 | 1351 | | render_widget_->set_asp_padding_enabled( |
| | | 1352 | | settings.value("asp_padding_enabled", render_widget_->is_asp_padding_enabled()).toBool()); |
| | 0 | 1353 | | render_widget_->set_asp_padded_width( |
| | | 1354 | | settings.value("asp_padded_width", render_widget_->get_asp_padded_width()).toInt()); |
| | 0 | 1355 | | render_widget_->set_asp_padded_height( |
| | | 1356 | | settings.value("asp_padded_height", render_widget_->get_asp_padded_height()).toInt()); |
| | 0 | 1357 | | restore_combo_text(settings, "convolution", render_widget_->convolution_combo()); |
| | 0 | 1358 | | render_widget_->set_convolution_divide( |
| | | 1359 | | settings.value("convolution_divide", render_widget_->is_convolution_divide()).toBool()); |
| | 0 | 1360 | | settings.endGroup(); |
| | | 1361 | | |
| | 0 | 1362 | | settings.beginGroup("view"); |
| | 0 | 1363 | | restore_combo_text(settings, "image_type", view_widget_->image_type_combo()); |
| | 0 | 1364 | | if (settings.contains("cuts_3d")) { |
| | 0 | 1365 | | legacy_3d_cuts = settings.value("cuts_3d").toBool(); |
| | | 1366 | | } |
| | 0 | 1367 | | if (settings.contains("raw_view")) { |
| | 0 | 1368 | | legacy_raw_view = settings.value("raw_view").toBool(); |
| | | 1369 | | } |
| | 0 | 1370 | | if (settings.contains("raw_spectrum")) { |
| | 0 | 1371 | | legacy_raw_spectrum = settings.value("raw_spectrum").toBool(); |
| | | 1372 | | } |
| | 0 | 1373 | | if (settings.contains("processed_spectrum")) { |
| | 0 | 1374 | | legacy_processed_spectrum = settings.value("processed_spectrum").toBool(); |
| | | 1375 | | } |
| | 0 | 1376 | | view_widget_->set_z_origin(settings.value("z_origin", view_widget_->get_z_origin()).toInt()); |
| | 0 | 1377 | | view_widget_->set_z_width(settings.value("z_width", view_widget_->get_z_width()).toInt()); |
| | 0 | 1378 | | view_widget_->set_accumulation( |
| | | 1379 | | settings.value("accumulation", view_widget_->get_accumulation()).toInt()); |
| | 0 | 1380 | | view_widget_->set_flatfield_enabled( |
| | | 1381 | | settings.value("flatfield", view_widget_->is_flatfield_enabled()).toBool()); |
| | 0 | 1382 | | view_widget_->set_flatfield_cutoff_period_um( |
| | | 1383 | | settings.value("flatfield_cutoff_period_um", view_widget_->get_flatfield_cutoff_period_um()) |
| | | 1384 | | .toDouble()); |
| | 0 | 1385 | | view_widget_->range_start_spin()->setValue( |
| | | 1386 | | settings.value("range_start", view_widget_->get_range_start()).toInt()); |
| | 0 | 1387 | | view_widget_->range_end_spin()->setValue( |
| | | 1388 | | settings.value("range_end", view_widget_->get_range_end()).toInt()); |
| | 0 | 1389 | | view_widget_->set_registration_enabled( |
| | | 1390 | | settings.value("registration", view_widget_->is_registration_enabled()).toBool()); |
| | 0 | 1391 | | view_widget_->registration_radius()->setValue( |
| | | 1392 | | settings.value("registration_radius", view_widget_->get_registration_radius()).toDouble()); |
| | 0 | 1393 | | view_widget_->reticle_check()->setChecked( |
| | | 1394 | | settings.value("reticle", view_widget_->is_reticle_enabled()).toBool()); |
| | 0 | 1395 | | view_widget_->reticle_radius()->setValue( |
| | | 1396 | | settings.value("reticle_radius", view_widget_->get_reticle_radius()).toDouble()); |
| | 0 | 1397 | | view_widget_->set_pct_enabled(settings.value("pct", view_widget_->is_pct_enabled()).toBool()); |
| | 0 | 1398 | | view_widget_->set_pct_radius( |
| | | 1399 | | settings.value("pct_radius", view_widget_->get_pct_radius()).toDouble()); |
| | 0 | 1400 | | settings.endGroup(); |
| | | 1401 | | |
| | 0 | 1402 | | settings.beginGroup("export"); |
| | 0 | 1403 | | export_widget_->setChecked(settings.value("enabled", export_widget_->isChecked()).toBool()); |
| | 0 | 1404 | | restore_combo_text(settings, "image_type", export_widget_->image_type_combo()); |
| | 0 | 1405 | | export_widget_->set_file_path( |
| | | 1406 | | settings.value("file_path", export_widget_->get_file_path()).toString()); |
| | 0 | 1407 | | restore_combo_text(settings, "tag", export_widget_->tag_combo()); |
| | 0 | 1408 | | export_widget_->frames_check()->setChecked( |
| | | 1409 | | settings.value("frame_count_enabled", export_widget_->is_frame_count_enabled()).toBool()); |
| | 0 | 1410 | | export_widget_->set_frame_count( |
| | | 1411 | | settings.value("frame_count", export_widget_->get_frame_count()).toInt()); |
| | 0 | 1412 | | settings.endGroup(); |
| | | 1413 | | |
| | 0 | 1414 | | auto *autofocus = render_widget_->autofocus_widget(); |
| | 0 | 1415 | | settings.beginGroup("autofocus"); |
| | 0 | 1416 | | autofocus->nb_subaps_spin()->setValue( |
| | | 1417 | | settings.value("nb_subaps", autofocus->get_nb_subaps()).toInt()); |
| | 0 | 1418 | | autofocus->nb_iter_spin()->setValue(settings.value("nb_iter", autofocus->get_nb_iter()).toInt()); |
| | 0 | 1419 | | autofocus->set_skip_subapertures_outside_pupil( |
| | | 1420 | | settings |
| | | 1421 | | .value("skip_subapertures_outside_pupil", autofocus->skip_subapertures_outside_pupil()) |
| | | 1422 | | .toBool()); |
| | 0 | 1423 | | autofocus->set_use_graph_laplacian( |
| | | 1424 | | settings.value("use_graph_laplacian", autofocus->use_graph_laplacian()).toBool()); |
| | 0 | 1425 | | autofocus->set_z2_enabled(settings.value("z2_enabled", autofocus->is_z2_enabled()).toBool()); |
| | 0 | 1426 | | autofocus->set_z3_enabled(settings.value("z3_enabled", autofocus->is_z3_enabled()).toBool()); |
| | 0 | 1427 | | autofocus->set_z4_enabled(settings.value("z4_enabled", autofocus->is_z4_enabled()).toBool()); |
| | 0 | 1428 | | autofocus->set_z5_enabled(settings.value("z5_enabled", autofocus->is_z5_enabled()).toBool()); |
| | 0 | 1429 | | autofocus->set_z6_enabled(settings.value("z6_enabled", autofocus->is_z6_enabled()).toBool()); |
| | 0 | 1430 | | autofocus->set_z7_enabled(settings.value("z7_enabled", autofocus->is_z7_enabled()).toBool()); |
| | 0 | 1431 | | autofocus->set_z8_enabled(settings.value("z8_enabled", autofocus->is_z8_enabled()).toBool()); |
| | 0 | 1432 | | autofocus->set_z9_enabled(settings.value("z9_enabled", autofocus->is_z9_enabled()).toBool()); |
| | 0 | 1433 | | autofocus->set_z10_enabled(settings.value("z10_enabled", autofocus->is_z10_enabled()).toBool()); |
| | 0 | 1434 | | if (settings.contains("show_zernike_metrics")) { |
| | 0 | 1435 | | legacy_zernike_metrics = settings.value("show_zernike_metrics").toBool(); |
| | | 1436 | | } |
| | 0 | 1437 | | if (settings.contains("show_phase")) { |
| | 0 | 1438 | | legacy_zernike_phase = settings.value("show_phase").toBool(); |
| | | 1439 | | } |
| | 0 | 1440 | | if (settings.contains("show_shack_hartmann")) { |
| | 0 | 1441 | | legacy_shack_hartmann = settings.value("show_shack_hartmann").toBool(); |
| | | 1442 | | } |
| | 0 | 1443 | | if (settings.contains("show_xcorr")) { |
| | 0 | 1444 | | legacy_shack_hartmann_xcorr = settings.value("show_xcorr").toBool(); |
| | | 1445 | | } |
| | 0 | 1446 | | autofocus->set_enabled(settings.value("enabled", autofocus->is_enabled()).toBool()); |
| | 0 | 1447 | | settings.endGroup(); |
| | | 1448 | | |
| | 0 | 1449 | | settings.beginGroup("zernike_history_display"); |
| | 0 | 1450 | | auto display_settings = zernike_history_widget_->display_settings(); |
| | 0 | 1451 | | display_settings.time_window_seconds = |
| | | 1452 | | settings.value("time_window_seconds", display_settings.time_window_seconds).toDouble(); |
| | 0 | 1453 | | const int scaling_mode = |
| | | 1454 | | settings.value("y_scaling_mode", static_cast<int>(display_settings.y_scaling_mode)).toInt(); |
| | 0 | 1455 | | if (scaling_mode >= static_cast<int>(YAxisScalingMode::VisibleWindow) && |
| | | 1456 | | scaling_mode <= static_cast<int>(YAxisScalingMode::Manual)) { |
| | 0 | 1457 | | display_settings.y_scaling_mode = static_cast<YAxisScalingMode>(scaling_mode); |
| | | 1458 | | } |
| | 0 | 1459 | | display_settings.manual_y_minimum = |
| | | 1460 | | settings.value("manual_y_minimum", display_settings.manual_y_minimum).toDouble(); |
| | 0 | 1461 | | display_settings.manual_y_maximum = |
| | | 1462 | | settings.value("manual_y_maximum", display_settings.manual_y_maximum).toDouble(); |
| | 0 | 1463 | | display_settings.show_statistics = |
| | | 1464 | | settings.value("show_statistics", display_settings.show_statistics).toBool(); |
| | 0 | 1465 | | zernike_history_widget_->set_display_settings(display_settings); |
| | 0 | 1466 | | signal_plot_time_window_seconds_ = |
| | | 1467 | | zernike_history_widget_->display_settings().time_window_seconds; |
| | 0 | 1468 | | settings.endGroup(); |
| | | 1469 | | |
| | | 1470 | | const auto migrate_legacy_view_preference = [this, &settings](const QString &id, |
| | | 1471 | | std::optional<bool> legacy_value) { |
| | | 1472 | | if (!legacy_value.has_value()) { |
| | | 1473 | | return; |
| | | 1474 | | } |
| | | 1475 | | |
| | | 1476 | | const QString workspace_key = |
| | | 1477 | | QStringLiteral("visualization_workspace/view_preferences/%1").arg(id); |
| | | 1478 | | if (!settings.contains(workspace_key)) { |
| | | 1479 | | display_workspace_->set_visualization_enabled(id, *legacy_value); |
| | | 1480 | | } |
| | 0 | 1481 | | }; |
| | 0 | 1482 | | migrate_legacy_view_preference(QStringLiteral("xy_raw"), legacy_raw_view); |
| | 0 | 1483 | | migrate_legacy_view_preference(QStringLiteral("raw_spectrum"), legacy_raw_spectrum); |
| | 0 | 1484 | | migrate_legacy_view_preference(QStringLiteral("processed_spectrum"), legacy_processed_spectrum); |
| | 0 | 1485 | | migrate_legacy_view_preference(QStringLiteral("xz_processed"), legacy_3d_cuts); |
| | 0 | 1486 | | migrate_legacy_view_preference(QStringLiteral("yz_processed"), legacy_3d_cuts); |
| | 0 | 1487 | | migrate_legacy_view_preference(QStringLiteral("zernike_a4"), legacy_zernike_metrics); |
| | 0 | 1488 | | migrate_legacy_view_preference(QStringLiteral("zernike_phase"), legacy_zernike_phase); |
| | 0 | 1489 | | migrate_legacy_view_preference(QStringLiteral("shack_hartmann"), legacy_shack_hartmann); |
| | 0 | 1490 | | migrate_legacy_view_preference(QStringLiteral("shack_hartmann_xcorr"), |
| | | 1491 | | legacy_shack_hartmann_xcorr); |
| | | 1492 | | |
| | 0 | 1493 | | update_recording_path_preview(); |
| | 0 | 1494 | | refresh_visualization_availability(); |
| | 0 | 1495 | | display_workspace_->set_pipeline_running(false); |
| | 0 | 1496 | | } |
| | | 1497 | | |
| | 0 | 1498 | | void MainWindow::initialize_display_widgets() { |
| | 0 | 1499 | | xy_raw_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1500 | | xy_processed_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1501 | | xz_processed_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1502 | | yz_processed_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1503 | | raw_spectrum_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1504 | | processed_spectrum_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1505 | | shack_hartmann_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1506 | | shack_hartmann_xcorr_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1507 | | zernike_phase_widget_ = new TensorDisplayWidget(display_workspace_); |
| | 0 | 1508 | | zernike_history_widget_ = new ZernikeHistoryWidget(display_workspace_); |
| | 0 | 1509 | | graph_visualizer_widget_ = new GraphVisualizerWidget(display_workspace_); |
| | | 1510 | | |
| | 0 | 1511 | | zernike_phase_widget_->set_colormap(Colormap::Twilight); |
| | 0 | 1512 | | zernike_phase_widget_->set_value_range(0.0f, 2 * static_cast<float>(M_PI)); |
| | | 1513 | | |
| | 0 | 1514 | | display_workspace_->register_visualization( |
| | | 1515 | | {"xy_processed", "XY Processed", xy_processed_widget_, {}, DockPlacement::Root}); |
| | 0 | 1516 | | display_workspace_->register_visualization({"zernike_a4", "Zernike Metrics", |
| | | 1517 | | zernike_history_widget_, "xy_processed", |
| | | 1518 | | DockPlacement::Right}); |
| | 0 | 1519 | | display_workspace_->register_visualization({"zernike_phase", "Zernike Phase", |
| | | 1520 | | zernike_phase_widget_, "xy_processed", |
| | | 1521 | | DockPlacement::Below, false}); |
| | 0 | 1522 | | display_workspace_->register_visualization({"shack_hartmann", "Shack Hartmann", |
| | | 1523 | | shack_hartmann_widget_, "zernike_phase", |
| | | 1524 | | DockPlacement::Right, false}); |
| | 0 | 1525 | | display_workspace_->register_visualization({"shack_hartmann_xcorr", "Shack Hartmann XCorr", |
| | | 1526 | | shack_hartmann_xcorr_widget_, "shack_hartmann", |
| | | 1527 | | DockPlacement::Right, false}); |
| | | 1528 | | |
| | | 1529 | | // Optional current displays use the same registry and visibility model. They default to tabs so |
| | | 1530 | | // enabling one does not disturb the requested five-display hierarchy. |
| | 0 | 1531 | | display_workspace_->register_visualization( |
| | | 1532 | | {"xy_raw", "XY Raw", xy_raw_widget_, "xy_processed", DockPlacement::Tab, false}); |
| | 0 | 1533 | | display_workspace_->register_visualization({"raw_spectrum", "Raw Spectrum", raw_spectrum_widget_, |
| | | 1534 | | "zernike_phase", DockPlacement::Tab, false}); |
| | 0 | 1535 | | display_workspace_->register_visualization({"processed_spectrum", "Processed Spectrum", |
| | | 1536 | | processed_spectrum_widget_, "raw_spectrum", |
| | | 1537 | | DockPlacement::Tab, false}); |
| | 0 | 1538 | | display_workspace_->register_visualization({"xz_processed", "XZ Processed", xz_processed_widget_, |
| | | 1539 | | "xy_processed", DockPlacement::Tab, false}); |
| | 0 | 1540 | | display_workspace_->register_visualization({"yz_processed", "YZ Processed", yz_processed_widget_, |
| | | 1541 | | "xy_processed", DockPlacement::Tab, false}); |
| | 0 | 1542 | | display_workspace_->register_visualization({"pipeline_graph", "Pipeline Graph", |
| | | 1543 | | graph_visualizer_widget_, "xy_processed", |
| | | 1544 | | DockPlacement::Tab, false}); |
| | 0 | 1545 | | display_workspace_->finalize_registration(); |
| | | 1546 | | |
| | 0 | 1547 | | connect(display_workspace_, &VisualizationWorkspace::selected_visualization_changed, this, |
| | | 1548 | | &MainWindow::on_selected_visualization_changed); |
| | 0 | 1549 | | connect(zernike_history_widget_, &ZernikeHistoryWidget::configuration_requested, this, |
| | | 1550 | | &MainWindow::select_configurable_widget); |
| | 0 | 1551 | | connect(zernike_history_widget_, &ZernikeHistoryWidget::display_settings_changed, this, |
| | | 1552 | | [this](const ZernikeHistoryDisplaySettings &settings) { |
| | | 1553 | | // Axis policy is GUI-only. Keeping this out of update_if_running avoids rebuilding or |
| | | 1554 | | // restarting the processing pipeline for display changes. |
| | | 1555 | | signal_plot_time_window_seconds_ = settings.time_window_seconds; |
| | | 1556 | | }); |
| | 0 | 1557 | | connect(graph_visualizer_widget_, &GraphVisualizerWidget::reload_requested, this, |
| | | 1558 | | &MainWindow::show_pipeline_graph); |
| | 0 | 1559 | | connect(display_workspace_, &VisualizationWorkspace::visualization_preferences_changed, this, |
| | | 1560 | | &MainWindow::refresh_visualization_availability); |
| | | 1561 | | |
| | 0 | 1562 | | connect(render_widget_, &ImageRenderingWidget::settings_changed, this, |
| | | 1563 | | &MainWindow::refresh_visualization_availability); |
| | | 1564 | | |
| | 0 | 1565 | | connect(view_widget_, &ViewWidget::reticle_toggled, this, [this](bool checked) { |
| | | 1566 | | xy_processed_widget_->set_reticle_enabled(checked); |
| | | 1567 | | if (checked) { |
| | | 1568 | | xy_processed_widget_->set_reticle_radius(view_widget_->get_reticle_radius()); |
| | | 1569 | | } |
| | | 1570 | | }); |
| | 0 | 1571 | | connect(view_widget_, &ViewWidget::reticle_radius_changed, this, [this](double value) { |
| | | 1572 | | if (view_widget_->is_reticle_enabled()) { |
| | | 1573 | | xy_processed_widget_->set_reticle_radius(value); |
| | | 1574 | | } |
| | | 1575 | | }); |
| | 0 | 1576 | | } |
| | | 1577 | | |
| | 0 | 1578 | | void MainWindow::initialize_pipeline_manager() { |
| | 0 | 1579 | | pipeline_manager_ = new pipeline::Manager( |
| | | 1580 | | render_widget_->autofocus_widget(), xy_processed_widget_, xz_processed_widget_, |
| | | 1581 | | yz_processed_widget_, xy_raw_widget_, raw_spectrum_widget_, processed_spectrum_widget_, |
| | | 1582 | | shack_hartmann_widget_, shack_hartmann_xcorr_widget_, zernike_phase_widget_, |
| | | 1583 | | zernike_history_widget_); |
| | 0 | 1584 | | pipeline_manager_thread_ = new QThread(this); |
| | 0 | 1585 | | pipeline_manager_->moveToThread(pipeline_manager_thread_); |
| | 0 | 1586 | | pipeline_manager_thread_->start(); |
| | 0 | 1587 | | } |
| | | 1588 | | |
| | 0 | 1589 | | void MainWindow::connect_manager_signals() { |
| | 0 | 1590 | | connect(pipeline_manager_, &pipeline::Manager::start_pipeline_success, this, |
| | | 1591 | | &MainWindow::on_start_pipeline_success); |
| | | 1592 | | |
| | 0 | 1593 | | connect(pipeline_manager_, &pipeline::Manager::start_pipeline_failure, this, |
| | | 1594 | | &MainWindow::on_start_pipeline_failure); |
| | | 1595 | | |
| | 0 | 1596 | | connect(pipeline_manager_, &pipeline::Manager::stop_pipeline_success, this, |
| | | 1597 | | &MainWindow::on_stop_pipeline_success); |
| | | 1598 | | |
| | 0 | 1599 | | connect(pipeline_manager_, &pipeline::Manager::stop_pipeline_failure, this, |
| | | 1600 | | &MainWindow::on_stop_pipeline_failure); |
| | | 1601 | | |
| | 0 | 1602 | | connect(pipeline_manager_, &pipeline::Manager::resume_pipeline_success, this, |
| | | 1603 | | &MainWindow::on_resume_pipeline_success); |
| | | 1604 | | |
| | 0 | 1605 | | connect(pipeline_manager_, &pipeline::Manager::resume_pipeline_failure, this, |
| | | 1606 | | &MainWindow::on_resume_pipeline_failure); |
| | | 1607 | | |
| | 0 | 1608 | | connect(pipeline_manager_, &pipeline::Manager::update_pipeline_success, this, |
| | | 1609 | | &MainWindow::on_update_pipeline_success); |
| | | 1610 | | |
| | 0 | 1611 | | connect(pipeline_manager_, &pipeline::Manager::update_pipeline_failure, this, |
| | | 1612 | | &MainWindow::on_update_pipeline_failure); |
| | | 1613 | | |
| | 0 | 1614 | | connect(pipeline_manager_, &pipeline::Manager::metrics_updated, this, |
| | | 1615 | | &MainWindow::on_metrics_updated, Qt::QueuedConnection); |
| | | 1616 | | |
| | 0 | 1617 | | connect(pipeline_manager_, &pipeline::Manager::raw_record_started_success, this, |
| | | 1618 | | &MainWindow::on_raw_record_started_success, Qt::QueuedConnection); |
| | | 1619 | | |
| | 0 | 1620 | | connect(pipeline_manager_, &pipeline::Manager::raw_record_started_failure, this, |
| | | 1621 | | &MainWindow::on_raw_record_started_failure, Qt::QueuedConnection); |
| | | 1622 | | |
| | 0 | 1623 | | connect(pipeline_manager_, &pipeline::Manager::raw_record_stopped_success, this, |
| | | 1624 | | &MainWindow::on_raw_record_stopped_success, Qt::QueuedConnection); |
| | | 1625 | | |
| | 0 | 1626 | | connect(pipeline_manager_, &pipeline::Manager::raw_record_stopped_failure, this, |
| | | 1627 | | &MainWindow::on_raw_record_stopped_failure, Qt::QueuedConnection); |
| | | 1628 | | |
| | 0 | 1629 | | connect(pipeline_manager_, &pipeline::Manager::graph_visualization_ready, this, |
| | | 1630 | | [this](const QString &dot) { |
| | | 1631 | | if (graph_visualizer_widget_ != nullptr) { |
| | | 1632 | | graph_visualizer_widget_->render_dot(dot); |
| | | 1633 | | } |
| | | 1634 | | }); |
| | 0 | 1635 | | connect(pipeline_manager_, &pipeline::Manager::graph_visualization_failed, this, |
| | | 1636 | | [this](const QString &error) { |
| | | 1637 | | if (graph_visualizer_widget_ != nullptr) { |
| | | 1638 | | graph_visualizer_widget_->show_error(error); |
| | | 1639 | | } |
| | | 1640 | | }); |
| | | 1641 | | |
| | 0 | 1642 | | } |
| | | 1643 | | |
| | 0 | 1644 | | void MainWindow::connect_import_controls() { |
| | 0 | 1645 | | connect(import_widget_, &ImportWidget::start_clicked, this, &MainWindow::on_import_start_clicked); |
| | 0 | 1646 | | connect(import_widget_, &ImportWidget::stop_clicked, this, &MainWindow::on_import_stop_clicked); |
| | 0 | 1647 | | connect(import_widget_, &ImportWidget::browse_clicked, this, [=]() { |
| | | 1648 | | QString file = QFileDialog::getOpenFileName(this, tr("Select File")); |
| | | 1649 | | if (file.isEmpty()) { |
| | | 1650 | | return; |
| | | 1651 | | } |
| | | 1652 | | try { |
| | | 1653 | | auto reader = holofile::Reader(file.toStdString()); |
| | | 1654 | | auto frame_count = reader.header().frame_count; |
| | | 1655 | | |
| | | 1656 | | import_widget_->set_file_path(file); |
| | | 1657 | | import_widget_->set_end_index_range(0, static_cast<int>(frame_count)); |
| | | 1658 | | import_widget_->set_end_index(static_cast<int>(frame_count)); |
| | | 1659 | | import_widget_->set_start_index(0); |
| | | 1660 | | |
| | | 1661 | | if (reader.footer().has_value()) { |
| | | 1662 | | auto footer = reader.footer().value(); |
| | | 1663 | | pipeline::Settings prev_settings = get_pipeline_settings(); |
| | | 1664 | | prev_settings.view_type = pipeline::ViewType::PROCESSED; |
| | | 1665 | | pipeline::Settings new_settings = |
| | | 1666 | | pipeline::old_json_to_settings(footer.pipeline_settings, prev_settings); |
| | | 1667 | | set_pipeline_settings(new_settings); |
| | | 1668 | | } |
| | | 1669 | | } catch (std::exception &e) { |
| | | 1670 | | logger()->error("failed to open \"{}\": \"{}\"", file.toStdString(), e.what()); |
| | | 1671 | | } |
| | | 1672 | | }); |
| | 0 | 1673 | | } |
| | | 1674 | | |
| | 0 | 1675 | | void MainWindow::connect_export_controls() { |
| | | 1676 | | |
| | 0 | 1677 | | connect(export_widget_, &ExportWidget::record_clicked, this, |
| | | 1678 | | &MainWindow::on_export_record_clicked); |
| | 0 | 1679 | | connect(export_widget_, &ExportWidget::stop_clicked, this, &MainWindow::on_export_stop_clicked); |
| | 0 | 1680 | | connect(export_widget_, &ExportWidget::browse_clicked, this, [=]() { |
| | | 1681 | | QFileInfo current_info(export_widget_->get_file_path()); |
| | | 1682 | | QString initial_directory = current_info.absolutePath(); |
| | | 1683 | | if (initial_directory.isEmpty() || initial_directory == ".") { |
| | | 1684 | | initial_directory = QDir::currentPath(); |
| | | 1685 | | } |
| | | 1686 | | |
| | | 1687 | | QString directory = QFileDialog::getExistingDirectory(this, tr("Select Recording Directory"), |
| | | 1688 | | initial_directory); |
| | | 1689 | | if (!directory.isEmpty()) { |
| | | 1690 | | export_widget_->set_file_path( |
| | | 1691 | | QDir(directory).filePath(recording_file_name(next_acquisition_id_))); |
| | | 1692 | | } |
| | | 1693 | | }); |
| | 0 | 1694 | | } |
| | | 1695 | | |
| | 0 | 1696 | | void MainWindow::configure_window() { |
| | 0 | 1697 | | setWindowTitle("Holovibes"); |
| | | 1698 | | |
| | 0 | 1699 | | auto *file_menu = menuBar()->addMenu(tr("&File")); |
| | 0 | 1700 | | auto *preferences = file_menu->addAction(tr("Preferences")); |
| | 0 | 1701 | | preferences->setShortcut(QKeySequence(QStringLiteral("Ctrl+Alt+P"))); |
| | 0 | 1702 | | preferences->setShortcutContext(Qt::ApplicationShortcut); |
| | 0 | 1703 | | connect(preferences, &QAction::triggered, this, &MainWindow::show_preferences); |
| | | 1704 | | |
| | 0 | 1705 | | auto *view_menu = menuBar()->addMenu(tr("&View")); |
| | 0 | 1706 | | display_workspace_->populate_view_menu(view_menu); |
| | 0 | 1707 | | view_menu->addSeparator(); |
| | 0 | 1708 | | auto *debug_menu = view_menu->addMenu(tr("Debug")); |
| | 0 | 1709 | | auto *graph_visualizer_action = debug_menu->addAction(tr("Open Pipeline Graph...")); |
| | 0 | 1710 | | connect(graph_visualizer_action, &QAction::triggered, this, &MainWindow::show_pipeline_graph); |
| | 0 | 1711 | | auto *open_dot_action = debug_menu->addAction(tr("Open DOT File...")); |
| | 0 | 1712 | | connect(open_dot_action, &QAction::triggered, this, &MainWindow::open_dot_file); |
| | | 1713 | | |
| | 0 | 1714 | | auto *tools_menu = menuBar()->addMenu(tr("&Tools")); |
| | 0 | 1715 | | auto *fft_tool_action = tools_menu->addAction(tr("FFT Frequency Range to Bins...")); |
| | 0 | 1716 | | fft_tool_action->setShortcut(QKeySequence(QStringLiteral("Ctrl+Alt+F"))); |
| | 0 | 1717 | | fft_tool_action->setShortcutContext(Qt::ApplicationShortcut); |
| | 0 | 1718 | | connect(fft_tool_action, &QAction::triggered, this, &MainWindow::show_fft_frequency_tool); |
| | | 1719 | | |
| | 0 | 1720 | | const QSize minimum_size(960, 640); |
| | 0 | 1721 | | const QSize default_size = minimum_size.expandedTo(QSize(1280, 800)); |
| | 0 | 1722 | | setMinimumSize(minimum_size); |
| | 0 | 1723 | | if (!geometry_restored_) { |
| | 0 | 1724 | | resize(default_size); |
| | | 1725 | | } |
| | 0 | 1726 | | } |
| | | 1727 | | |
| | 0 | 1728 | | void MainWindow::check_for_updates() { |
| | 0 | 1729 | | update_checker_ = new UpdateChecker(QCoreApplication::applicationVersion(), this); |
| | 0 | 1730 | | connect(update_checker_, &UpdateChecker::update_available, this, |
| | | 1731 | | [this](const QString &version, const QUrl &page_url) { |
| | | 1732 | | available_update_url_ = page_url; |
| | | 1733 | | update_indicator_->setText(tr("Update available v%1").arg(version)); |
| | | 1734 | | update_indicator_->setVisible(true); |
| | | 1735 | | }); |
| | 0 | 1736 | | QTimer::singleShot(0, update_checker_, &UpdateChecker::start); |
| | 0 | 1737 | | } |
| | | 1738 | | |
| | 0 | 1739 | | void MainWindow::show_pipeline_graph() { |
| | 0 | 1740 | | graph_visualizer_widget_->set_reload_enabled(true); |
| | 0 | 1741 | | display_workspace_->set_visualization_title(QStringLiteral("pipeline_graph"), |
| | | 1742 | | tr("Pipeline Graph")); |
| | 0 | 1743 | | display_workspace_->set_visualization_enabled(QStringLiteral("pipeline_graph"), true); |
| | 0 | 1744 | | display_workspace_->select_visualization(QStringLiteral("pipeline_graph")); |
| | | 1745 | | |
| | 0 | 1746 | | auto request = [manager = pipeline_manager_]() { manager->request_compiled_graph_visualization(); }; |
| | 0 | 1747 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, std::move(request), |
| | | 1748 | | Qt::QueuedConnection)); |
| | 0 | 1749 | | } |
| | | 1750 | | |
| | 0 | 1751 | | void MainWindow::open_dot_file() { |
| | 0 | 1752 | | const QString path = QFileDialog::getOpenFileName( |
| | | 1753 | | this, tr("Open Graphviz DOT File"), {}, tr("Graphviz DOT Files (*.dot);;All Files (*)")); |
| | 0 | 1754 | | if (path.isEmpty()) { |
| | 0 | 1755 | | return; |
| | | 1756 | | } |
| | | 1757 | | |
| | 0 | 1758 | | QFile dot_file(path); |
| | 0 | 1759 | | if (!dot_file.open(QIODevice::ReadOnly | QIODevice::Text)) { |
| | 0 | 1760 | | QMessageBox::warning(this, tr("Open Graphviz DOT File"), |
| | | 1761 | | tr("Could not open '%1': %2").arg(path, dot_file.errorString())); |
| | 0 | 1762 | | return; |
| | | 1763 | | } |
| | | 1764 | | |
| | 0 | 1765 | | display_workspace_->set_visualization_title( |
| | | 1766 | | QStringLiteral("pipeline_graph"), tr("Pipeline Graph — %1").arg(QFileInfo(path).fileName())); |
| | 0 | 1767 | | graph_visualizer_widget_->set_reload_enabled(false); |
| | 0 | 1768 | | display_workspace_->set_visualization_enabled(QStringLiteral("pipeline_graph"), true); |
| | 0 | 1769 | | display_workspace_->select_visualization(QStringLiteral("pipeline_graph")); |
| | 0 | 1770 | | graph_visualizer_widget_->render_dot(QString::fromUtf8(dot_file.readAll())); |
| | 0 | 1771 | | } |
| | | 1772 | | |
| | 0 | 1773 | | void MainWindow::show_fft_frequency_tool() { |
| | 0 | 1774 | | const double sampling_frequency_hz = import_widget_->get_sampling_frequency_hz(); |
| | 0 | 1775 | | const int window_size = std::max(1, render_widget_->get_time_window()); |
| | 0 | 1776 | | const double bin_spacing_hz = sampling_frequency_hz / static_cast<double>(window_size); |
| | 0 | 1777 | | const double start_frequency_hz = view_widget_->get_z_origin() * bin_spacing_hz; |
| | 0 | 1778 | | const double end_frequency_hz = |
| | | 1779 | | (view_widget_->get_z_origin() + view_widget_->get_z_width()) * bin_spacing_hz; |
| | | 1780 | | |
| | 0 | 1781 | | FftFrequencyToolDialog dialog( |
| | | 1782 | | sampling_frequency_hz, window_size, start_frequency_hz, end_frequency_hz, |
| | | 1783 | | render_widget_->get_time_transform(), |
| | | 1784 | | [this](int z_start, int z_width) { |
| | | 1785 | | if (view_widget_ != nullptr) { |
| | | 1786 | | view_widget_->set_z_origin(z_start); |
| | | 1787 | | view_widget_->set_z_width(z_width); |
| | | 1788 | | } |
| | | 1789 | | }, |
| | | 1790 | | this); |
| | 0 | 1791 | | dialog.exec(); |
| | 0 | 1792 | | } |
| | | 1793 | | |
| | 0 | 1794 | | void MainWindow::show_preferences() { |
| | 0 | 1795 | | PreferencesDialog dialog(this, *pipeline_manager_); |
| | 0 | 1796 | | dialog.exec(); |
| | 0 | 1797 | | } |
| | | 1798 | | |
| | 0 | 1799 | | std::filesystem::path MainWindow::makeRecordingPath(const QString &userText) { |
| | | 1800 | | namespace fs = std::filesystem; |
| | | 1801 | | |
| | 0 | 1802 | | QFileInfo info(userText); |
| | | 1803 | | |
| | 0 | 1804 | | fs::path dir = info.absolutePath().isEmpty() ? fs::current_path() |
| | | 1805 | | : fs::path(info.absolutePath().toStdString()); |
| | | 1806 | | |
| | 0 | 1807 | | int acquisition_id = next_acquisition_id_; |
| | 0 | 1808 | | QString finalFileName = recording_file_name(acquisition_id); |
| | 0 | 1809 | | fs::path candidate = dir / finalFileName.toStdString(); |
| | | 1810 | | |
| | | 1811 | | // Add suffix if needed |
| | 0 | 1812 | | while (fs::exists(candidate)) { |
| | 0 | 1813 | | ++acquisition_id; |
| | 0 | 1814 | | finalFileName = recording_file_name(acquisition_id); |
| | 0 | 1815 | | candidate = dir / finalFileName.toStdString(); |
| | 0 | 1816 | | } |
| | | 1817 | | |
| | 0 | 1818 | | pending_recording_acquisition_id_ = acquisition_id; |
| | 0 | 1819 | | QSignalBlocker blocker(export_widget_->file_line_edit()); |
| | 0 | 1820 | | export_widget_->set_file_path(QString::fromStdString(candidate.string())); |
| | | 1821 | | |
| | 0 | 1822 | | logger()->info("[MainWindow::makeRecordingPath] Generated recording path: {}", |
| | | 1823 | | candidate.string()); |
| | | 1824 | | |
| | 0 | 1825 | | return candidate; |
| | 0 | 1826 | | } |
| | | 1827 | | |
| | 0 | 1828 | | void MainWindow::show_pipeline_error_popup(const QString &message) { |
| | 0 | 1829 | | QMessageBox msgBox(this); |
| | 0 | 1830 | | msgBox.setIcon(QMessageBox::Critical); |
| | 0 | 1831 | | msgBox.setWindowTitle(tr("Pipeline Error")); |
| | 0 | 1832 | | msgBox.setText(message); |
| | 0 | 1833 | | msgBox.setStandardButtons(QMessageBox::Ok); |
| | 0 | 1834 | | msgBox.setDefaultButton(QMessageBox::Ok); |
| | 0 | 1835 | | msgBox.exec(); |
| | 0 | 1836 | | } |
| | | 1837 | | |
| | 0 | 1838 | | void MainWindow::on_start_pipeline_success() { |
| | 0 | 1839 | | logger()->info("[MainWindow::on_start_pipeline_success]"); |
| | 0 | 1840 | | pipeline_running_ = true; |
| | 0 | 1841 | | pipeline_paused_ = false; |
| | 0 | 1842 | | pause_requested_ = false; |
| | 0 | 1843 | | resume_in_progress_ = false; |
| | 0 | 1844 | | paused_settings_dirty_ = false; |
| | 0 | 1845 | | import_widget_->set_start_enabled(true); |
| | 0 | 1846 | | import_widget_->set_stop_enabled(true); |
| | 0 | 1847 | | export_widget_->set_record_enabled(!export_in_progress_ && export_widget_->isChecked()); |
| | 0 | 1848 | | export_widget_->set_stop_enabled(export_in_progress_); |
| | | 1849 | | |
| | 0 | 1850 | | auto dims = guess_source_dims(); |
| | 0 | 1851 | | xy_raw_widget_->set_fixed_aspect(dims); |
| | 0 | 1852 | | dims = processed_display_dims(dims); |
| | 0 | 1853 | | view_widget_->set_xy_extent(dims.width(), dims.height()); |
| | 0 | 1854 | | xy_processed_widget_->set_fixed_aspect(dims); |
| | 0 | 1855 | | xy_raw_widget_->set_fixed_aspect(guess_source_dims()); |
| | 0 | 1856 | | shack_hartmann_widget_->set_fixed_aspect(dims); |
| | 0 | 1857 | | shack_hartmann_xcorr_widget_->set_fixed_aspect(dims); |
| | 0 | 1858 | | zernike_phase_widget_->set_fixed_aspect(guess_source_dims()); |
| | | 1859 | | |
| | 0 | 1860 | | auto *autofocus_widget = render_widget_->autofocus_widget(); |
| | 0 | 1861 | | if (autofocus_widget->is_enabled()) { |
| | 0 | 1862 | | const bool has_enabled_zernike = |
| | | 1863 | | autofocus_widget->is_z2_enabled() || autofocus_widget->is_z3_enabled() || |
| | | 1864 | | autofocus_widget->is_z4_enabled() || autofocus_widget->is_z5_enabled() || |
| | | 1865 | | autofocus_widget->is_z6_enabled() || autofocus_widget->is_z7_enabled() || |
| | | 1866 | | autofocus_widget->is_z8_enabled() || autofocus_widget->is_z9_enabled() || |
| | | 1867 | | autofocus_widget->is_z10_enabled(); |
| | 0 | 1868 | | if (!has_enabled_zernike) { |
| | 0 | 1869 | | autofocus_widget->reset_zernike_values(); |
| | | 1870 | | } |
| | 0 | 1871 | | } else { |
| | 0 | 1872 | | autofocus_widget->reset_zernike_values(); |
| | | 1873 | | } |
| | 0 | 1874 | | refresh_visualization_availability(); |
| | 0 | 1875 | | display_workspace_->set_pipeline_running(true); |
| | 0 | 1876 | | refresh_command_bar(); |
| | 0 | 1877 | | } |
| | | 1878 | | |
| | 0 | 1879 | | void MainWindow::on_start_pipeline_failure(const QString &error) { |
| | 0 | 1880 | | logger()->error("[MainWindow::on_start_pipeline_failure]"); |
| | 0 | 1881 | | pipeline_running_ = false; |
| | 0 | 1882 | | pipeline_paused_ = false; |
| | 0 | 1883 | | pause_requested_ = false; |
| | 0 | 1884 | | resume_in_progress_ = false; |
| | 0 | 1885 | | paused_settings_dirty_ = false; |
| | 0 | 1886 | | export_in_progress_ = false; |
| | 0 | 1887 | | import_widget_->set_start_enabled(true); |
| | 0 | 1888 | | import_widget_->set_stop_enabled(false); |
| | 0 | 1889 | | export_widget_->set_record_enabled(false); |
| | 0 | 1890 | | export_widget_->set_stop_enabled(false); |
| | 0 | 1891 | | display_workspace_->set_pipeline_running(false); |
| | 0 | 1892 | | refresh_command_bar(); |
| | | 1893 | | |
| | 0 | 1894 | | show_pipeline_error_popup(tr("An error occurred while starting the pipeline:\n%1").arg(error)); |
| | 0 | 1895 | | } |
| | | 1896 | | |
| | 0 | 1897 | | void MainWindow::on_stop_pipeline_success() { |
| | 0 | 1898 | | logger()->info("[MainWindow::on_stop_pipeline_success]"); |
| | 0 | 1899 | | if (pause_requested_) { |
| | 0 | 1900 | | pipeline_running_ = false; |
| | 0 | 1901 | | pipeline_paused_ = true; |
| | 0 | 1902 | | pause_requested_ = false; |
| | 0 | 1903 | | export_in_progress_ = false; |
| | 0 | 1904 | | import_widget_->set_start_enabled(true); |
| | 0 | 1905 | | import_widget_->set_stop_enabled(true); |
| | 0 | 1906 | | export_widget_->set_record_enabled(false); |
| | 0 | 1907 | | export_widget_->set_stop_enabled(false); |
| | 0 | 1908 | | on_metrics_updated(0.0); |
| | 0 | 1909 | | refresh_command_bar(); |
| | 0 | 1910 | | return; |
| | | 1911 | | } |
| | | 1912 | | |
| | 0 | 1913 | | reset_pipeline_ui_after_stop(); |
| | 0 | 1914 | | } |
| | | 1915 | | |
| | 0 | 1916 | | void MainWindow::on_stop_pipeline_failure(const QString &error) { |
| | 0 | 1917 | | logger()->error("[MainWindow::on_stop_pipeline_failure]"); |
| | 0 | 1918 | | if (pause_requested_) { |
| | 0 | 1919 | | pipeline_running_ = false; |
| | 0 | 1920 | | pipeline_paused_ = true; |
| | 0 | 1921 | | pause_requested_ = false; |
| | 0 | 1922 | | export_in_progress_ = false; |
| | 0 | 1923 | | import_widget_->set_start_enabled(true); |
| | 0 | 1924 | | import_widget_->set_stop_enabled(true); |
| | 0 | 1925 | | export_widget_->set_record_enabled(false); |
| | 0 | 1926 | | export_widget_->set_stop_enabled(false); |
| | 0 | 1927 | | on_metrics_updated(0.0); |
| | 0 | 1928 | | refresh_command_bar(); |
| | 0 | 1929 | | } else { |
| | 0 | 1930 | | reset_pipeline_ui_after_stop(); |
| | | 1931 | | } |
| | | 1932 | | |
| | 0 | 1933 | | show_pipeline_error_popup(error); |
| | 0 | 1934 | | } |
| | | 1935 | | |
| | 0 | 1936 | | void MainWindow::on_resume_pipeline_success() { |
| | 0 | 1937 | | logger()->info("[MainWindow::on_resume_pipeline_success]"); |
| | 0 | 1938 | | pipeline_running_ = true; |
| | 0 | 1939 | | pipeline_paused_ = false; |
| | 0 | 1940 | | resume_in_progress_ = false; |
| | 0 | 1941 | | import_widget_->set_start_enabled(true); |
| | 0 | 1942 | | import_widget_->set_stop_enabled(true); |
| | 0 | 1943 | | export_widget_->set_record_enabled(!export_in_progress_ && export_widget_->isChecked()); |
| | 0 | 1944 | | export_widget_->set_stop_enabled(export_in_progress_); |
| | 0 | 1945 | | refresh_visualization_availability(); |
| | 0 | 1946 | | display_workspace_->set_pipeline_running(true); |
| | 0 | 1947 | | refresh_command_bar(); |
| | 0 | 1948 | | } |
| | | 1949 | | |
| | 0 | 1950 | | void MainWindow::on_resume_pipeline_failure(const QString &error) { |
| | 0 | 1951 | | logger()->error("[MainWindow::on_resume_pipeline_failure]"); |
| | 0 | 1952 | | pipeline_running_ = false; |
| | 0 | 1953 | | pipeline_paused_ = true; |
| | 0 | 1954 | | resume_in_progress_ = false; |
| | 0 | 1955 | | export_in_progress_ = false; |
| | 0 | 1956 | | import_widget_->set_start_enabled(true); |
| | 0 | 1957 | | import_widget_->set_stop_enabled(true); |
| | 0 | 1958 | | export_widget_->set_record_enabled(false); |
| | 0 | 1959 | | export_widget_->set_stop_enabled(false); |
| | 0 | 1960 | | refresh_command_bar(); |
| | | 1961 | | |
| | 0 | 1962 | | show_pipeline_error_popup(tr("An error occurred while resuming the pipeline:\n%1").arg(error)); |
| | 0 | 1963 | | } |
| | | 1964 | | |
| | 0 | 1965 | | void MainWindow::reset_pipeline_ui_after_stop() { |
| | 0 | 1966 | | pipeline_running_ = false; |
| | 0 | 1967 | | pipeline_paused_ = false; |
| | 0 | 1968 | | pause_requested_ = false; |
| | 0 | 1969 | | resume_in_progress_ = false; |
| | 0 | 1970 | | paused_settings_dirty_ = false; |
| | 0 | 1971 | | export_in_progress_ = false; |
| | 0 | 1972 | | import_widget_->set_start_enabled(true); |
| | 0 | 1973 | | import_widget_->set_stop_enabled(false); |
| | 0 | 1974 | | export_widget_->set_record_enabled(false); |
| | 0 | 1975 | | export_widget_->set_stop_enabled(false); |
| | 0 | 1976 | | on_metrics_updated(0.0); |
| | | 1977 | | |
| | 0 | 1978 | | display_workspace_->set_pipeline_running(false); |
| | 0 | 1979 | | render_widget_->autofocus_widget()->reset_zernike_values(); |
| | 0 | 1980 | | refresh_command_bar(); |
| | 0 | 1981 | | } |
| | | 1982 | | |
| | 0 | 1983 | | void MainWindow::on_metrics_updated(double input_fps) { |
| | 0 | 1984 | | if (input_fps < 0.0) { |
| | 0 | 1985 | | input_fps = 0.0; |
| | | 1986 | | } |
| | 0 | 1987 | | last_input_fps_ = input_fps; |
| | | 1988 | | |
| | 0 | 1989 | | int fps = static_cast<int>(input_fps); |
| | | 1990 | | |
| | 0 | 1991 | | const QString text = QString("%1 fps").arg(fps, 6, 10, QChar('0')); |
| | | 1992 | | |
| | 0 | 1993 | | if (fps_status_label_ != nullptr) { |
| | 0 | 1994 | | fps_status_label_->setText(text); |
| | | 1995 | | } |
| | 0 | 1996 | | monitor_widget_->set_input_throughput_fps(text); |
| | | 1997 | | |
| | 0 | 1998 | | monitor_widget_->set_gpu_load("N/A"); |
| | 0 | 1999 | | monitor_widget_->set_cpu_load("N/A"); |
| | 0 | 2000 | | monitor_widget_->set_input_throughput_bytes("N/A"); |
| | 0 | 2001 | | monitor_widget_->set_cpu_throughput("N/A"); |
| | 0 | 2002 | | monitor_widget_->set_gpu_throughput("N/A"); |
| | 0 | 2003 | | monitor_widget_->set_ram_usage("N/A"); |
| | 0 | 2004 | | monitor_widget_->set_vram_usage("N/A"); |
| | 0 | 2005 | | monitor_widget_->set_dropped_frames("N/A"); |
| | 0 | 2006 | | monitor_widget_->set_pipeline_latency("N/A"); |
| | 0 | 2007 | | refresh_command_bar(); |
| | 0 | 2008 | | } |
| | | 2009 | | |
| | 0 | 2010 | | void MainWindow::on_update_pipeline_success() { |
| | 0 | 2011 | | logger()->info("[MainWindow::on_update_pipeline_success]"); |
| | 0 | 2012 | | update_in_progress_ = false; |
| | 0 | 2013 | | import_widget_->set_start_enabled(pipeline_running_); |
| | 0 | 2014 | | export_widget_->set_record_enabled(!export_in_progress_ && export_widget_->isChecked()); |
| | 0 | 2015 | | export_widget_->set_stop_enabled(export_in_progress_); |
| | | 2016 | | |
| | 0 | 2017 | | auto dims = guess_source_dims(); |
| | 0 | 2018 | | xy_raw_widget_->set_fixed_aspect(dims); |
| | 0 | 2019 | | dims = processed_display_dims(dims); |
| | 0 | 2020 | | view_widget_->set_xy_extent(dims.width(), dims.height()); |
| | 0 | 2021 | | xy_processed_widget_->set_fixed_aspect(dims); |
| | | 2022 | | |
| | 0 | 2023 | | shack_hartmann_widget_->set_fixed_aspect(dims); |
| | 0 | 2024 | | shack_hartmann_xcorr_widget_->set_fixed_aspect(dims); |
| | 0 | 2025 | | zernike_phase_widget_->set_fixed_aspect(guess_source_dims()); |
| | | 2026 | | |
| | 0 | 2027 | | auto *autofocus_widget = render_widget_->autofocus_widget(); |
| | 0 | 2028 | | if (autofocus_widget->is_enabled()) { |
| | 0 | 2029 | | const bool has_enabled_zernike = |
| | | 2030 | | autofocus_widget->is_z2_enabled() || autofocus_widget->is_z3_enabled() || |
| | | 2031 | | autofocus_widget->is_z4_enabled() || autofocus_widget->is_z5_enabled() || |
| | | 2032 | | autofocus_widget->is_z6_enabled() || autofocus_widget->is_z7_enabled() || |
| | | 2033 | | autofocus_widget->is_z8_enabled() || autofocus_widget->is_z9_enabled() || |
| | | 2034 | | autofocus_widget->is_z10_enabled(); |
| | 0 | 2035 | | if (!has_enabled_zernike) { |
| | 0 | 2036 | | autofocus_widget->reset_zernike_values(); |
| | | 2037 | | } |
| | 0 | 2038 | | } else { |
| | 0 | 2039 | | autofocus_widget->reset_zernike_values(); |
| | | 2040 | | } |
| | 0 | 2041 | | refresh_visualization_availability(); |
| | 0 | 2042 | | display_workspace_->set_pipeline_running(true); |
| | 0 | 2043 | | refresh_command_bar(); |
| | 0 | 2044 | | } |
| | | 2045 | | |
| | 0 | 2046 | | void MainWindow::on_update_pipeline_failure(const QString &error) { |
| | 0 | 2047 | | logger()->error("[MainWindow::on_update_pipeline_failure]"); |
| | 0 | 2048 | | pipeline_running_ = false; |
| | 0 | 2049 | | pipeline_paused_ = false; |
| | 0 | 2050 | | pause_requested_ = false; |
| | 0 | 2051 | | resume_in_progress_ = false; |
| | 0 | 2052 | | update_in_progress_ = false; |
| | 0 | 2053 | | paused_settings_dirty_ = false; |
| | 0 | 2054 | | import_widget_->set_start_enabled(true); |
| | 0 | 2055 | | import_widget_->set_stop_enabled(false); |
| | 0 | 2056 | | export_in_progress_ = false; |
| | 0 | 2057 | | export_widget_->set_record_enabled(false); |
| | 0 | 2058 | | export_widget_->set_stop_enabled(false); |
| | 0 | 2059 | | display_workspace_->set_pipeline_running(false); |
| | 0 | 2060 | | refresh_command_bar(); |
| | | 2061 | | |
| | 0 | 2062 | | show_pipeline_error_popup(tr("An error occurred while updating the pipeline:\n%1").arg(error)); |
| | 0 | 2063 | | } |
| | | 2064 | | |
| | 0 | 2065 | | void MainWindow::closeEvent(QCloseEvent *event) { |
| | 0 | 2066 | | save_persistent_state(); |
| | | 2067 | | |
| | 0 | 2068 | | if (pipeline_manager_ && pipeline_running_) { |
| | 0 | 2069 | | pipeline_manager_->stop_pipeline(); |
| | | 2070 | | } |
| | | 2071 | | |
| | 0 | 2072 | | if (pipeline_manager_thread_) { |
| | 0 | 2073 | | pipeline_manager_thread_->quit(); |
| | 0 | 2074 | | pipeline_manager_thread_->wait(); |
| | | 2075 | | } |
| | | 2076 | | |
| | 0 | 2077 | | QMainWindow::closeEvent(event); |
| | 0 | 2078 | | QCoreApplication::quit(); |
| | 0 | 2079 | | } |
| | | 2080 | | |
| | 0 | 2081 | | void MainWindow::on_import_start_clicked() { |
| | 0 | 2082 | | if (pipeline_running_) { |
| | 0 | 2083 | | pause_requested_ = true; |
| | 0 | 2084 | | import_widget_->set_start_enabled(false); |
| | 0 | 2085 | | import_widget_->set_stop_enabled(false); |
| | 0 | 2086 | | refresh_command_bar(); |
| | 0 | 2087 | | auto pause = [manager = pipeline_manager_]() { manager->stop_pipeline(); }; |
| | 0 | 2088 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, pause, Qt::QueuedConnection)); |
| | 0 | 2089 | | return; |
| | | 2090 | | } |
| | | 2091 | | |
| | 0 | 2092 | | if (pipeline_paused_ && !paused_settings_dirty_) { |
| | 0 | 2093 | | resume_in_progress_ = true; |
| | 0 | 2094 | | import_widget_->set_start_enabled(false); |
| | 0 | 2095 | | import_widget_->set_stop_enabled(false); |
| | 0 | 2096 | | refresh_command_bar(); |
| | 0 | 2097 | | auto resume = [manager = pipeline_manager_]() { manager->resume_pipeline(); }; |
| | 0 | 2098 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, resume, Qt::QueuedConnection)); |
| | 0 | 2099 | | return; |
| | | 2100 | | } |
| | | 2101 | | |
| | 0 | 2102 | | if (!validate_inputs()) { |
| | 0 | 2103 | | return; |
| | | 2104 | | } |
| | | 2105 | | |
| | 0 | 2106 | | pipeline_paused_ = false; |
| | 0 | 2107 | | paused_settings_dirty_ = false; |
| | 0 | 2108 | | import_widget_->set_start_enabled(false); |
| | 0 | 2109 | | import_widget_->set_stop_enabled(false); |
| | 0 | 2110 | | display_workspace_->reset_visualization_content(); |
| | 0 | 2111 | | refresh_command_bar(); |
| | 0 | 2112 | | pipeline::Settings settings = get_pipeline_settings(); |
| | 0 | 2113 | | auto update = [=]() { pipeline_manager_->update_pipeline(settings); }; |
| | 0 | 2114 | | auto start = [=]() { pipeline_manager_->start_pipeline(); }; |
| | 0 | 2115 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, update, Qt::QueuedConnection)); |
| | 0 | 2116 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, start, Qt::QueuedConnection)); |
| | 0 | 2117 | | } |
| | | 2118 | | |
| | 0 | 2119 | | void MainWindow::on_import_stop_clicked() { |
| | 0 | 2120 | | if (pipeline_paused_) { |
| | 0 | 2121 | | reset_pipeline_ui_after_stop(); |
| | 0 | 2122 | | return; |
| | | 2123 | | } |
| | | 2124 | | |
| | 0 | 2125 | | HOLOVIBES_CHECK(pipeline_running_); |
| | 0 | 2126 | | pause_requested_ = false; |
| | 0 | 2127 | | import_widget_->set_start_enabled(false); |
| | 0 | 2128 | | import_widget_->set_stop_enabled(false); |
| | 0 | 2129 | | refresh_command_bar(); |
| | 0 | 2130 | | auto stop = [=]() { pipeline_manager_->stop_pipeline(); }; |
| | 0 | 2131 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, stop, Qt::QueuedConnection)); |
| | 0 | 2132 | | } |
| | | 2133 | | |
| | 0 | 2134 | | void MainWindow::on_export_record_clicked() { |
| | 0 | 2135 | | if (export_in_progress_) { |
| | 0 | 2136 | | logger()->warn("[MainWindow::on_export_record_clicked] Recording already in progress"); |
| | 0 | 2137 | | return; |
| | | 2138 | | } |
| | 0 | 2139 | | if (!pipeline_running_) { |
| | 0 | 2140 | | logger()->warn("[MainWindow::on_export_record_clicked] Pipeline is not running"); |
| | 0 | 2141 | | return; |
| | | 2142 | | } |
| | | 2143 | | |
| | 0 | 2144 | | if (!validate_inputs()) { |
| | 0 | 2145 | | logger()->warn("[MainWindow::on_export_record_clicked] Validation failed"); |
| | 0 | 2146 | | export_widget_->set_stop_enabled(pipeline_running_); |
| | 0 | 2147 | | return; |
| | | 2148 | | } |
| | | 2149 | | |
| | 0 | 2150 | | export_widget_->set_record_enabled(false); |
| | 0 | 2151 | | export_widget_->set_stop_enabled(false); |
| | 0 | 2152 | | refresh_command_bar(); |
| | | 2153 | | |
| | 0 | 2154 | | std::filesystem::path record_path = makeRecordingPath(export_widget_->get_file_path()); |
| | 0 | 2155 | | std::optional<size_t> frame_count; |
| | 0 | 2156 | | if (export_widget_->is_frame_count_enabled()) { |
| | 0 | 2157 | | frame_count = static_cast<size_t>(export_widget_->get_frame_count()); |
| | | 2158 | | } |
| | | 2159 | | |
| | 0 | 2160 | | auto start = [mgr = pipeline_manager_, record_path]() { mgr->start_raw_record(record_path); }; |
| | 0 | 2161 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, start, Qt::QueuedConnection)); |
| | 0 | 2162 | | } |
| | | 2163 | | |
| | 0 | 2164 | | void MainWindow::on_export_stop_clicked() { |
| | 0 | 2165 | | if (!export_in_progress_) { |
| | 0 | 2166 | | logger()->warn("[MainWindow::on_export_stop_clicked] No active recording to stop"); |
| | 0 | 2167 | | return; |
| | | 2168 | | } |
| | | 2169 | | |
| | 0 | 2170 | | export_widget_->set_record_enabled(false); |
| | 0 | 2171 | | refresh_command_bar(); |
| | 0 | 2172 | | auto stop = [mgr = pipeline_manager_]() { mgr->stop_raw_record(); }; |
| | 0 | 2173 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, stop, Qt::QueuedConnection)); |
| | 0 | 2174 | | } |
| | | 2175 | | |
| | 0 | 2176 | | void MainWindow::on_raw_record_started_success() { |
| | 0 | 2177 | | logger()->info("[MainWindow::on_raw_record_started_success]"); |
| | 0 | 2178 | | export_in_progress_ = true; |
| | 0 | 2179 | | export_widget_->set_stop_enabled(true); |
| | 0 | 2180 | | export_widget_->set_record_enabled(false); |
| | 0 | 2181 | | refresh_command_bar(); |
| | 0 | 2182 | | } |
| | | 2183 | | |
| | 0 | 2184 | | void MainWindow::on_raw_record_started_failure(const QString &error) { |
| | 0 | 2185 | | logger()->error("[MainWindow::on_raw_record_started_failure]"); |
| | 0 | 2186 | | export_in_progress_ = false; |
| | 0 | 2187 | | pending_recording_acquisition_id_.reset(); |
| | 0 | 2188 | | export_widget_->set_record_enabled(pipeline_running_ && export_widget_->isChecked()); |
| | 0 | 2189 | | export_widget_->set_stop_enabled(false); |
| | 0 | 2190 | | refresh_command_bar(); |
| | | 2191 | | |
| | 0 | 2192 | | show_pipeline_error_popup(tr("An error occurred while starting raw recording:\n%1").arg(error)); |
| | 0 | 2193 | | } |
| | | 2194 | | |
| | 0 | 2195 | | void MainWindow::on_raw_record_stopped_success() { |
| | 0 | 2196 | | logger()->info("[MainWindow::on_raw_record_stopped_success]"); |
| | 0 | 2197 | | export_in_progress_ = false; |
| | 0 | 2198 | | if (pending_recording_acquisition_id_.has_value()) { |
| | 0 | 2199 | | next_acquisition_id_ = std::max(next_acquisition_id_, *pending_recording_acquisition_id_ + 1); |
| | 0 | 2200 | | pending_recording_acquisition_id_.reset(); |
| | 0 | 2201 | | update_recording_path_preview(); |
| | | 2202 | | } |
| | 0 | 2203 | | export_widget_->set_record_enabled(pipeline_running_ && export_widget_->isChecked()); |
| | 0 | 2204 | | export_widget_->set_stop_enabled(false); |
| | 0 | 2205 | | refresh_command_bar(); |
| | 0 | 2206 | | } |
| | | 2207 | | |
| | 0 | 2208 | | void MainWindow::on_raw_record_stopped_failure(const QString &error) { |
| | 0 | 2209 | | logger()->error("[MainWindow::on_raw_record_stopped_failure]"); |
| | 0 | 2210 | | export_widget_->set_stop_enabled(pipeline_running_); |
| | 0 | 2211 | | refresh_command_bar(); |
| | 0 | 2212 | | show_pipeline_error_popup(tr("An error occurred while stopping raw recording:\n%1").arg(error)); |
| | 0 | 2213 | | } |
| | | 2214 | | |
| | 0 | 2215 | | bool MainWindow::validate_inputs() { |
| | 0 | 2216 | | import_widget_->clear_validation_styles(); |
| | 0 | 2217 | | export_widget_->clear_validation_styles(); |
| | 0 | 2218 | | render_widget_->clear_validation_styles(); |
| | 0 | 2219 | | view_widget_->clear_validation_styles(); |
| | 0 | 2220 | | render_widget_->autofocus_widget()->clear_validation_styles(); |
| | 0 | 2221 | | configure_unsupported_features(); |
| | | 2222 | | |
| | 0 | 2223 | | pipeline::Settings settings = get_pipeline_settings(); |
| | 0 | 2224 | | const auto context = build_validation_context(settings); |
| | 0 | 2225 | | QSize display_dims(context.source_width.value_or(1), context.source_height.value_or(1)); |
| | | 2226 | | if (settings.view_type != pipeline::ViewType::RAW && |
| | 0 | 2227 | | settings.spacial_method == pipeline::SpacialMethod::ANGULAR_SPECTRUM && |
| | | 2228 | | settings.asp_padding_enabled) { |
| | 0 | 2229 | | display_dims = QSize(settings.asp_padded_width, settings.asp_padded_height); |
| | | 2230 | | } |
| | 0 | 2231 | | view_widget_->set_xy_extent(display_dims.width(), display_dims.height()); |
| | 0 | 2232 | | const auto result = pipeline::validate_settings(settings, context); |
| | 0 | 2233 | | refresh_validation_tooltips(result); |
| | 0 | 2234 | | apply_validation_result(result); |
| | 0 | 2235 | | configure_unsupported_features(); |
| | 0 | 2236 | | return result.ok(); |
| | 0 | 2237 | | } |
| | | 2238 | | |
| | 0 | 2239 | | void MainWindow::apply_validation_result(const pipeline::ValidationResult &result) { |
| | | 2240 | | using pipeline::SettingsField; |
| | | 2241 | | |
| | 0 | 2242 | | for (const auto &issue : result.issues) { |
| | 0 | 2243 | | for (const auto field : issue.fields) { |
| | 0 | 2244 | | switch (field) { |
| | | 2245 | | case SettingsField::LoadPath: |
| | 0 | 2246 | | import_widget_->mark_file_invalid(); |
| | 0 | 2247 | | break; |
| | | 2248 | | case SettingsField::CameraConfigPath: |
| | 0 | 2249 | | import_widget_->mark_camera_config_invalid(); |
| | 0 | 2250 | | break; |
| | | 2251 | | case SettingsField::InputSamplingFrequency: |
| | 0 | 2252 | | import_widget_->mark_sampling_frequency_invalid(); |
| | 0 | 2253 | | break; |
| | | 2254 | | case SettingsField::LoadBegin: |
| | 0 | 2255 | | import_widget_->mark_start_index_invalid(); |
| | 0 | 2256 | | break; |
| | | 2257 | | case SettingsField::LoadEnd: |
| | 0 | 2258 | | import_widget_->mark_end_index_invalid(); |
| | 0 | 2259 | | break; |
| | | 2260 | | case SettingsField::LoadBatch: |
| | 0 | 2261 | | render_widget_->mark_batch_size_invalid(); |
| | 0 | 2262 | | break; |
| | | 2263 | | case SettingsField::Filter2D: |
| | 0 | 2264 | | render_widget_->mark_filter_2d_invalid(); |
| | 0 | 2265 | | break; |
| | | 2266 | | case SettingsField::Filter2DInnerRadius: |
| | 0 | 2267 | | render_widget_->mark_filter_inner_invalid(); |
| | 0 | 2268 | | break; |
| | | 2269 | | case SettingsField::Filter2DOuterRadius: |
| | 0 | 2270 | | render_widget_->mark_filter_outer_invalid(); |
| | 0 | 2271 | | break; |
| | | 2272 | | case SettingsField::SpacialMethod: |
| | 0 | 2273 | | render_widget_->mark_space_transform_invalid(); |
| | 0 | 2274 | | break; |
| | | 2275 | | case SettingsField::AspPadding: |
| | 0 | 2276 | | render_widget_->mark_asp_padding_invalid(); |
| | 0 | 2277 | | break; |
| | | 2278 | | case SettingsField::TimeMethod: |
| | 0 | 2279 | | render_widget_->mark_time_transform_invalid(); |
| | 0 | 2280 | | break; |
| | | 2281 | | case SettingsField::TimeWindow: |
| | 0 | 2282 | | render_widget_->mark_time_window_invalid(); |
| | 0 | 2283 | | break; |
| | | 2284 | | case SettingsField::TimeStride: |
| | 0 | 2285 | | render_widget_->mark_time_stride_invalid(); |
| | 0 | 2286 | | break; |
| | | 2287 | | case SettingsField::TimeZBegin: |
| | 0 | 2288 | | view_widget_->mark_z_invalid(); |
| | 0 | 2289 | | break; |
| | | 2290 | | case SettingsField::TimeZEnd: |
| | 0 | 2291 | | view_widget_->mark_z_width_invalid(); |
| | 0 | 2292 | | break; |
| | | 2293 | | case SettingsField::View3DCuts: |
| | | 2294 | | break; |
| | | 2295 | | case SettingsField::PpAccumulation: |
| | 0 | 2296 | | view_widget_->mark_accumulation_invalid(); |
| | 0 | 2297 | | break; |
| | | 2298 | | case SettingsField::PpFlatfieldCutoffPeriod: |
| | 0 | 2299 | | view_widget_->mark_flatfield_cutoff_period_invalid(); |
| | 0 | 2300 | | break; |
| | | 2301 | | case SettingsField::PpConvolution: |
| | 0 | 2302 | | render_widget_->mark_convolution_invalid(); |
| | 0 | 2303 | | break; |
| | | 2304 | | case SettingsField::PpRegistration: |
| | 0 | 2305 | | view_widget_->mark_registration_invalid(); |
| | 0 | 2306 | | break; |
| | | 2307 | | case SettingsField::RecordingPath: |
| | 0 | 2308 | | export_widget_->mark_file_invalid(); |
| | 0 | 2309 | | break; |
| | | 2310 | | case SettingsField::RecordingCount: |
| | 0 | 2311 | | export_widget_->mark_frames_invalid(); |
| | 0 | 2312 | | break; |
| | | 2313 | | case SettingsField::AutofocusNbSubaps: |
| | 0 | 2314 | | render_widget_->autofocus_widget()->mark_nb_subaps_invalid(); |
| | 0 | 2315 | | break; |
| | | 2316 | | case SettingsField::AutofocusNbIter: |
| | 0 | 2317 | | render_widget_->autofocus_widget()->mark_nb_iter_invalid(); |
| | | 2318 | | break; |
| | | 2319 | | } |
| | 0 | 2320 | | } |
| | 0 | 2321 | | } |
| | 0 | 2322 | | } |
| | | 2323 | | |
| | 0 | 2324 | | void MainWindow::refresh_validation_tooltips(const pipeline::ValidationResult &result) { |
| | | 2325 | | using pipeline::SettingsField; |
| | | 2326 | | |
| | 0 | 2327 | | const std::array bindings = { |
| | | 2328 | | FieldBinding{SettingsField::LoadPath, import_widget_->file_line_edit()}, |
| | | 2329 | | FieldBinding{SettingsField::CameraConfigPath, import_widget_->camera_config_combo()}, |
| | | 2330 | | FieldBinding{SettingsField::InputSamplingFrequency, |
| | | 2331 | | import_widget_->sampling_frequency_spin()}, |
| | | 2332 | | FieldBinding{SettingsField::LoadBegin, import_widget_->start_index_spin()}, |
| | | 2333 | | FieldBinding{SettingsField::LoadEnd, import_widget_->end_index_spin()}, |
| | | 2334 | | FieldBinding{SettingsField::LoadBatch, render_widget_->batch_size_spin()}, |
| | | 2335 | | FieldBinding{SettingsField::Filter2D, render_widget_->filter_2d_check()}, |
| | | 2336 | | FieldBinding{SettingsField::Filter2DInnerRadius, render_widget_->filter_2d_inner_spin()}, |
| | | 2337 | | FieldBinding{SettingsField::Filter2DOuterRadius, render_widget_->filter_2d_outer_spin()}, |
| | | 2338 | | FieldBinding{SettingsField::SpacialMethod, render_widget_->space_transform_combo()}, |
| | | 2339 | | FieldBinding{SettingsField::AspPadding, render_widget_->asp_padding_check()}, |
| | | 2340 | | FieldBinding{SettingsField::AspPadding, render_widget_->asp_padded_width_spin()}, |
| | | 2341 | | FieldBinding{SettingsField::AspPadding, render_widget_->asp_padded_height_spin()}, |
| | | 2342 | | FieldBinding{SettingsField::TimeMethod, render_widget_->time_transform_combo()}, |
| | | 2343 | | FieldBinding{SettingsField::TimeWindow, render_widget_->time_window_spin()}, |
| | | 2344 | | FieldBinding{SettingsField::TimeStride, render_widget_->time_stride_spin()}, |
| | | 2345 | | FieldBinding{SettingsField::TimeZBegin, view_widget_->z_spin()}, |
| | | 2346 | | FieldBinding{SettingsField::TimeZEnd, view_widget_->z_width_spin()}, |
| | | 2347 | | FieldBinding{SettingsField::PpAccumulation, view_widget_->accumulation_spin()}, |
| | | 2348 | | FieldBinding{SettingsField::PpFlatfieldCutoffPeriod, |
| | | 2349 | | view_widget_->flatfield_cutoff_period_um()}, |
| | | 2350 | | FieldBinding{SettingsField::PpConvolution, render_widget_->convolution_combo()}, |
| | | 2351 | | FieldBinding{SettingsField::PpRegistration, view_widget_->registration_check()}, |
| | | 2352 | | FieldBinding{SettingsField::RecordingPath, export_widget_->file_line_edit()}, |
| | | 2353 | | FieldBinding{SettingsField::RecordingCount, export_widget_->frames_spin()}, |
| | | 2354 | | FieldBinding{SettingsField::AutofocusNbSubaps, |
| | | 2355 | | render_widget_->autofocus_widget()->nb_subaps_spin()}, |
| | | 2356 | | FieldBinding{SettingsField::AutofocusNbIter, |
| | | 2357 | | render_widget_->autofocus_widget()->nb_iter_spin()}, |
| | | 2358 | | }; |
| | | 2359 | | |
| | 0 | 2360 | | for (const auto &binding : bindings) { |
| | 0 | 2361 | | const auto issues = result.issues_for(binding.field); |
| | 0 | 2362 | | const auto &help = pipeline::get_field_help(binding.field); |
| | 0 | 2363 | | const auto tooltip = build_field_tooltip(help, std::span{issues}); |
| | 0 | 2364 | | binding.widget->setToolTip(tooltip); |
| | 0 | 2365 | | } |
| | 0 | 2366 | | } |
| | | 2367 | | |
| | | 2368 | | pipeline::ValidationContext |
| | 0 | 2369 | | MainWindow::build_validation_context(const pipeline::Settings &settings) const { |
| | 0 | 2370 | | pipeline::ValidationContext context; |
| | | 2371 | | |
| | 0 | 2372 | | if (settings.import_source == pipeline::ImportSource::HOLOFILE) { |
| | 0 | 2373 | | context.load_path_exists = |
| | | 2374 | | !settings.load_path.empty() && std::filesystem::exists(settings.load_path); |
| | | 2375 | | |
| | 0 | 2376 | | if (context.load_path_exists) { |
| | | 2377 | | try { |
| | 0 | 2378 | | auto header = holofile::Reader(settings.load_path.string()).header(); |
| | 0 | 2379 | | context.source_width = static_cast<int>(header.frame_width); |
| | 0 | 2380 | | context.source_height = static_cast<int>(header.frame_height); |
| | 0 | 2381 | | context.source_frame_count = static_cast<int>(header.frame_count); |
| | 0 | 2382 | | } catch (...) { |
| | 0 | 2383 | | context.load_path_exists = false; |
| | 0 | 2384 | | } |
| | | 2385 | | } |
| | 0 | 2386 | | } else { |
| | 0 | 2387 | | context.camera_config_exists = !settings.camera_config_path.empty() && |
| | | 2388 | | std::filesystem::exists(settings.camera_config_path); |
| | | 2389 | | |
| | 0 | 2390 | | if (context.camera_config_exists) { |
| | | 2391 | | try { |
| | 0 | 2392 | | std::ifstream cfg_file(settings.camera_config_path); |
| | 0 | 2393 | | auto cfg_json = nlohmann::json::parse(cfg_file); |
| | 0 | 2394 | | const auto &cfg = cfg_json.contains("s711") ? cfg_json.at("s711") : cfg_json.at("s710"); |
| | | 2395 | | |
| | 0 | 2396 | | context.source_width = cfg.at("Width").get<int>(); |
| | 0 | 2397 | | context.source_height = cfg.at("Height").get<int>(); |
| | 0 | 2398 | | context.camera_config_valid = true; |
| | 0 | 2399 | | } catch (...) { |
| | 0 | 2400 | | context.camera_config_valid = false; |
| | 0 | 2401 | | } |
| | | 2402 | | } |
| | | 2403 | | } |
| | | 2404 | | |
| | 0 | 2405 | | if (settings.recording_method != pipeline::RecordingMethod::NONE) { |
| | 0 | 2406 | | context.recording_path_error = pipeline::validate_recording_path(settings.recording_path); |
| | | 2407 | | } |
| | | 2408 | | |
| | 0 | 2409 | | return context; |
| | 0 | 2410 | | } |
| | | 2411 | | |
| | 0 | 2412 | | void MainWindow::setup_validation_connections() { |
| | 0 | 2413 | | bool (MainWindow::*cb)() = &MainWindow::validate_inputs; |
| | | 2414 | | |
| | | 2415 | | // Connect to widget signals |
| | 0 | 2416 | | connect(import_widget_, &ImportWidget::settings_changed, this, cb); |
| | 0 | 2417 | | connect(export_widget_, &ExportWidget::settings_changed, this, cb); |
| | 0 | 2418 | | connect(render_widget_, &ImageRenderingWidget::settings_changed, this, cb); |
| | 0 | 2419 | | connect(view_widget_, &ViewWidget::settings_changed, this, cb); |
| | 0 | 2420 | | connect(display_workspace_, &VisualizationWorkspace::visualization_preferences_changed, this, cb); |
| | | 2421 | | |
| | 0 | 2422 | | connect(import_widget_, &ImportWidget::settings_changed, this, &MainWindow::refresh_command_bar); |
| | 0 | 2423 | | connect(export_widget_, &ExportWidget::settings_changed, this, &MainWindow::refresh_command_bar); |
| | 0 | 2424 | | connect(render_widget_, &ImageRenderingWidget::settings_changed, this, |
| | | 2425 | | &MainWindow::refresh_command_bar); |
| | 0 | 2426 | | connect(view_widget_, &ViewWidget::settings_changed, this, &MainWindow::refresh_command_bar); |
| | 0 | 2427 | | connect(display_workspace_, &VisualizationWorkspace::visualization_preferences_changed, this, |
| | | 2428 | | &MainWindow::refresh_command_bar); |
| | 0 | 2429 | | } |
| | | 2430 | | |
| | 0 | 2431 | | void MainWindow::setup_update_connections() { |
| | 0 | 2432 | | void (MainWindow::*cb)() = &MainWindow::update_if_running; |
| | | 2433 | | |
| | 0 | 2434 | | connect(import_widget_, &ImportWidget::settings_changed, this, cb); |
| | 0 | 2435 | | connect(export_widget_, &ExportWidget::settings_changed, this, cb); |
| | 0 | 2436 | | connect(render_widget_, &ImageRenderingWidget::settings_changed, this, cb); |
| | 0 | 2437 | | connect(view_widget_, &ViewWidget::settings_changed, this, cb); |
| | 0 | 2438 | | connect(display_workspace_, &VisualizationWorkspace::visualization_preferences_changed, this, cb); |
| | 0 | 2439 | | } |
| | | 2440 | | |
| | 0 | 2441 | | void MainWindow::update_if_running() { |
| | 0 | 2442 | | if (pipeline_paused_) { |
| | 0 | 2443 | | paused_settings_dirty_ = true; |
| | 0 | 2444 | | return; |
| | | 2445 | | } |
| | | 2446 | | |
| | 0 | 2447 | | if (!pipeline_manager_ || !pipeline_running_) { |
| | 0 | 2448 | | return; |
| | | 2449 | | } |
| | | 2450 | | |
| | 0 | 2451 | | if (update_in_progress_) { |
| | 0 | 2452 | | return; |
| | | 2453 | | } |
| | | 2454 | | |
| | 0 | 2455 | | if (!validate_inputs()) { |
| | 0 | 2456 | | return; |
| | | 2457 | | } |
| | | 2458 | | |
| | 0 | 2459 | | display_workspace_->reset_visualization_content(); |
| | 0 | 2460 | | update_in_progress_ = true; |
| | 0 | 2461 | | import_widget_->set_start_enabled(false); |
| | 0 | 2462 | | refresh_command_bar(); |
| | 0 | 2463 | | pipeline::Settings settings = get_pipeline_settings(); |
| | 0 | 2464 | | auto update = [=]() { pipeline_manager_->update_pipeline(settings); }; |
| | 0 | 2465 | | HOLOVIBES_CHECK(QMetaObject::invokeMethod(pipeline_manager_, update, Qt::QueuedConnection)); |
| | 0 | 2466 | | } |
| | | 2467 | | |
| | 0 | 2468 | | QSize MainWindow::guess_source_dims() { |
| | 0 | 2469 | | if (!import_widget_->is_camera_mode()) { |
| | 0 | 2470 | | auto header = holofile::Reader(import_widget_->get_file_path().toStdString()).header(); |
| | 0 | 2471 | | int src_width = header.frame_width; |
| | 0 | 2472 | | int src_height = header.frame_height; |
| | 0 | 2473 | | return QSize(src_width, src_height); |
| | | 2474 | | } |
| | | 2475 | | |
| | 0 | 2476 | | else if (import_widget_->is_camera_mode()) { |
| | 0 | 2477 | | auto path = get_selected_camera_config_path(); |
| | 0 | 2478 | | auto cfg_file = std::ifstream(path); |
| | 0 | 2479 | | if (!cfg_file.is_open()) { |
| | 0 | 2480 | | throw std::runtime_error(std::format("Could not open camera config file: {}", path)); |
| | | 2481 | | } |
| | | 2482 | | |
| | | 2483 | | // FIXME: This needs to be properly handeled for more camera support |
| | 0 | 2484 | | auto cfg_json = nlohmann::json::parse(cfg_file); |
| | 0 | 2485 | | auto cfg = cfg_json.contains("s711") ? cfg_json.at("s711") : cfg_json.at("s710"); |
| | 0 | 2486 | | int src_width = cfg.at("Width").get<int>(); |
| | 0 | 2487 | | int src_height = cfg.at("Height").get<int>(); |
| | 0 | 2488 | | return QSize(src_width, src_height); |
| | | 2489 | | } |
| | | 2490 | | |
| | 0 | 2491 | | HOLOVIBES_UNIMPLEMENTED(); |
| | 0 | 2492 | | } |
| | | 2493 | | |
| | 0 | 2494 | | QSize MainWindow::processed_display_dims(QSize source_dims) const { |
| | 0 | 2495 | | if (render_widget_->get_image_mode() == "Raw") { |
| | 0 | 2496 | | return source_dims; |
| | | 2497 | | } |
| | 0 | 2498 | | if (render_widget_->get_space_transform() == "Fresnel Diffraction") { |
| | 0 | 2499 | | return QSize(source_dims.width(), source_dims.width()); |
| | | 2500 | | } |
| | 0 | 2501 | | if (render_widget_->get_space_transform() == "Angular Spectrum" && |
| | | 2502 | | render_widget_->is_asp_padding_enabled()) { |
| | 0 | 2503 | | return QSize(render_widget_->get_asp_padded_width(), render_widget_->get_asp_padded_height()); |
| | | 2504 | | } |
| | 0 | 2505 | | return source_dims; |
| | 0 | 2506 | | } |
| | | 2507 | | |
| | 0 | 2508 | | pipeline::Settings MainWindow::get_pipeline_settings() { |
| | | 2509 | | using namespace holovibes::pipeline; |
| | | 2510 | | |
| | 0 | 2511 | | Settings s; |
| | | 2512 | | |
| | | 2513 | | // Advanced Settings |
| | | 2514 | | { |
| | 0 | 2515 | | s.cpu_in_size = 4096; |
| | 0 | 2516 | | s.gpu_in_size = 1024; |
| | 0 | 2517 | | s.cpu_rec_size = 4096; |
| | 0 | 2518 | | s.cpu_out_size = 64; |
| | 0 | 2519 | | s.gpu_out_size = 64; |
| | | 2520 | | } |
| | | 2521 | | |
| | | 2522 | | // Import Settings |
| | | 2523 | | { |
| | 0 | 2524 | | std::map<std::string, LoadMethod> method_from_str{ |
| | | 2525 | | {"Read Live", LoadMethod::READ_LIVE}, |
| | | 2526 | | {"Load in CPU RAM", LoadMethod::LOAD_IN_CPU}, |
| | | 2527 | | {"Load in GPU RAM", LoadMethod::LOAD_IN_GPU}, |
| | | 2528 | | }; |
| | 0 | 2529 | | std::map<std::string, ImportSource> source_from_str{ |
| | | 2530 | | {"Ametek S710 Euresys Coaxlink Octo", ImportSource::AMETEK_S710_EURESYS_COAXLINK_OCTO}, |
| | | 2531 | | {"Ametek S711 Euresys Coaxlink QSFP+", ImportSource::AMETEK_S711_EURESYS_COAXLINK_QSFP}, |
| | | 2532 | | }; |
| | 0 | 2533 | | s.input_sampling_frequency_hz = import_widget_->get_sampling_frequency_hz(); |
| | | 2534 | | |
| | 0 | 2535 | | if (!import_widget_->is_camera_mode()) { |
| | 0 | 2536 | | s.import_source = ImportSource::HOLOFILE; |
| | 0 | 2537 | | s.load_path = import_widget_->get_file_path().toStdString(); |
| | 0 | 2538 | | s.load_begin = static_cast<size_t>(import_widget_->get_start_index()); |
| | 0 | 2539 | | s.load_end = static_cast<size_t>(import_widget_->get_end_index()); |
| | 0 | 2540 | | s.load_fps_limit = import_widget_->get_fps_limit(); |
| | 0 | 2541 | | QString method = import_widget_->get_load_method(); |
| | 0 | 2542 | | s.load_method = method_from_str.at(method.toStdString()); |
| | 0 | 2543 | | if (render_widget_->get_time_transform() == "Principal Component Analysis") { |
| | 0 | 2544 | | s.load_batch = 32; |
| | 0 | 2545 | | } else { |
| | 0 | 2546 | | s.load_batch = render_widget_->get_batch_size(); |
| | | 2547 | | } |
| | 0 | 2548 | | } else { |
| | 0 | 2549 | | QString source = import_widget_->get_camera_type(); |
| | 0 | 2550 | | s.import_source = source_from_str.at(source.toStdString()); |
| | 0 | 2551 | | s.camera_config_path = get_selected_camera_config_path(); |
| | 0 | 2552 | | s.load_batch = 1; |
| | 0 | 2553 | | s.load_fps_limit = std::nullopt; |
| | | 2554 | | |
| | 0 | 2555 | | std::ifstream cfg_file(s.camera_config_path); |
| | 0 | 2556 | | if (cfg_file.is_open()) { |
| | | 2557 | | try { |
| | 0 | 2558 | | auto cfg_json = nlohmann::json::parse(cfg_file); |
| | 0 | 2559 | | s.load_batch = cfg_json.contains("s711") |
| | | 2560 | | ? cfg_json.at("s711").at("BufferPartCount").get<int>() |
| | | 2561 | | : cfg_json.at("s710").at("BufferPartCount").get<int>(); |
| | 0 | 2562 | | } catch (...) { |
| | 0 | 2563 | | } |
| | | 2564 | | } |
| | 0 | 2565 | | } |
| | 0 | 2566 | | } |
| | | 2567 | | |
| | | 2568 | | // Image Rendering Settings |
| | | 2569 | | { |
| | 0 | 2570 | | s.view_type = render_widget_->get_image_mode() == "Raw" ? ViewType::RAW : ViewType::PROCESSED; |
| | | 2571 | | |
| | 0 | 2572 | | std::map<std::string, SpacialMethod> method_from_str{ |
| | | 2573 | | {"None", SpacialMethod::NONE}, |
| | | 2574 | | {"Fresnel Diffraction", SpacialMethod::FRESNEL_DIFFRACTION}, |
| | | 2575 | | {"Angular Spectrum", SpacialMethod::ANGULAR_SPECTRUM}, |
| | | 2576 | | }; |
| | 0 | 2577 | | QString method = render_widget_->get_space_transform(); |
| | 0 | 2578 | | s.spacial_method = method_from_str.at(method.toStdString()); |
| | 0 | 2579 | | s.spacial_lambda = static_cast<float>(render_widget_->get_lambda()) * 1e-9f; |
| | 0 | 2580 | | s.spacial_z = static_cast<float>(render_widget_->get_focus()) * 1e-3f; |
| | 0 | 2581 | | s.spacial_pixel_size = 20e-6f; // TODO: get from camera |
| | 0 | 2582 | | s.asp_padding_enabled = render_widget_->is_asp_padding_enabled(); |
| | 0 | 2583 | | s.asp_padded_width = render_widget_->get_asp_padded_width(); |
| | 0 | 2584 | | s.asp_padded_height = render_widget_->get_asp_padded_height(); |
| | 0 | 2585 | | } |
| | | 2586 | | { |
| | 0 | 2587 | | s.filter_2d = render_widget_->is_filter_2d_enabled(); |
| | 0 | 2588 | | s.filter_r_inner = render_widget_->get_filter_inner(); |
| | 0 | 2589 | | s.filter_r_outer = render_widget_->get_filter_outer(); |
| | 0 | 2590 | | s.filter_smooth_inner = 0; |
| | 0 | 2591 | | s.filter_smooth_outer = 1; |
| | | 2592 | | } |
| | | 2593 | | { |
| | 0 | 2594 | | std::map<std::string, TimeMethod> method_from_str{ |
| | | 2595 | | {"None", TimeMethod::NONE}, |
| | | 2596 | | {"Principal Component Analysis", TimeMethod::PRINCIPAL_COMPONENT_ANALYSIS}, |
| | | 2597 | | {"RFFT", TimeMethod::RFFT}, |
| | | 2598 | | {"FFT", TimeMethod::FFT}, |
| | | 2599 | | }; |
| | 0 | 2600 | | s.time_window = render_widget_->get_time_window(); |
| | 0 | 2601 | | s.time_stride = render_widget_->get_time_stride(); |
| | 0 | 2602 | | s.time_accumulation = 4; // TODO: expose in UI |
| | 0 | 2603 | | QString method = render_widget_->get_time_transform(); |
| | 0 | 2604 | | s.time_method = method_from_str.at(method.toStdString()); |
| | 0 | 2605 | | s.time_x_begin = view_widget_->get_x_origin(); |
| | 0 | 2606 | | s.time_x_end = s.time_x_begin + view_widget_->get_x_width(); |
| | 0 | 2607 | | s.time_y_begin = view_widget_->get_y_origin(); |
| | 0 | 2608 | | s.time_y_end = s.time_y_begin + view_widget_->get_y_width(); |
| | 0 | 2609 | | s.time_z_begin = view_widget_->get_z_origin(); |
| | 0 | 2610 | | s.time_z_end = s.time_z_begin + view_widget_->get_z_width(); |
| | 0 | 2611 | | } |
| | | 2612 | | |
| | | 2613 | | // View Settings |
| | | 2614 | | { |
| | 0 | 2615 | | std::map<std::string, MomentType> moment_from_str{ |
| | | 2616 | | {"M0", MomentType::M0}, |
| | | 2617 | | {"M1", MomentType::M1}, |
| | | 2618 | | {"M2", MomentType::M2}, |
| | | 2619 | | }; |
| | | 2620 | | const auto view_enabled = [this](const QString &id, bool fallback) { |
| | | 2621 | | return display_workspace_ == nullptr ? fallback |
| | | 2622 | | : display_workspace_->is_visualization_enabled(id); |
| | 0 | 2623 | | }; |
| | | 2624 | | |
| | 0 | 2625 | | s.view_3d_cuts = false; |
| | 0 | 2626 | | s.raw_view = view_enabled(QStringLiteral("xy_raw"), false); |
| | 0 | 2627 | | s.moment_type = moment_from_str.at(view_widget_->get_image_type().toStdString()); |
| | 0 | 2628 | | s.view_raw_spectrum = view_enabled(QStringLiteral("raw_spectrum"), false); |
| | 0 | 2629 | | s.view_processed_spectrum = view_enabled(QStringLiteral("processed_spectrum"), false); |
| | 0 | 2630 | | s.view_zernike_metrics = view_enabled(QStringLiteral("zernike_a4"), true); |
| | 0 | 2631 | | s.view_zernike_phase = view_enabled(QStringLiteral("zernike_phase"), false); |
| | 0 | 2632 | | s.view_shack_hartmann = view_enabled(QStringLiteral("shack_hartmann"), false); |
| | 0 | 2633 | | s.view_shack_hartmann_xcorr = view_enabled(QStringLiteral("shack_hartmann_xcorr"), false); |
| | 0 | 2634 | | } |
| | | 2635 | | |
| | | 2636 | | // Post-processing Settings |
| | | 2637 | | { |
| | 0 | 2638 | | QString appDataBase = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); |
| | 0 | 2639 | | QString appDataPath = appDataBase + "/" + QCoreApplication::applicationVersion(); |
| | 0 | 2640 | | QString convolutionsKernelsPath = appDataPath + "/" + "convolution_kernels/"; |
| | 0 | 2641 | | std::string kernel_path = convolutionsKernelsPath.toStdString() + |
| | | 2642 | | render_widget_->get_convolution().toStdString() + ".json"; |
| | | 2643 | | |
| | 0 | 2644 | | s.pp_fps = 60; |
| | 0 | 2645 | | s.pp_fft_shift = s.spacial_method == SpacialMethod::FRESNEL_DIFFRACTION; |
| | 0 | 2646 | | s.pp_flatfield = view_widget_->is_flatfield_enabled(); |
| | 0 | 2647 | | s.pp_flatfield_cutoff_period_m = |
| | | 2648 | | static_cast<float>(view_widget_->get_flatfield_cutoff_period_um() * 1e-6); |
| | 0 | 2649 | | s.pp_accumulation = static_cast<size_t>(view_widget_->get_accumulation()); |
| | 0 | 2650 | | s.pp_convolution = render_widget_->get_convolution() != "None"; |
| | 0 | 2651 | | s.pp_convolution_path = kernel_path; |
| | 0 | 2652 | | s.pp_convolution_divide = render_widget_->is_convolution_divide(); |
| | 0 | 2653 | | s.pp_pctclip = view_widget_->is_pct_enabled(); |
| | 0 | 2654 | | s.pp_pctclip_lower = 0.02f; |
| | 0 | 2655 | | s.pp_pctclip_upper = 99.98f; |
| | 0 | 2656 | | s.pp_pctclip_radius = view_widget_->get_pct_radius(); |
| | 0 | 2657 | | s.pp_registration = view_widget_->is_registration_enabled(); |
| | 0 | 2658 | | s.pp_registration_radius = view_widget_->get_registration_radius(); |
| | 0 | 2659 | | } |
| | | 2660 | | |
| | | 2661 | | // Recording Settings |
| | | 2662 | | { |
| | 0 | 2663 | | if (export_widget_->isChecked()) { |
| | | 2664 | | |
| | 0 | 2665 | | s.recording_method = export_widget_->get_image_type() == "Raw Image" |
| | | 2666 | | ? RecordingMethod::RAW |
| | | 2667 | | : RecordingMethod::PROCESSED; |
| | 0 | 2668 | | } else { |
| | 0 | 2669 | | s.recording_method = RecordingMethod::NONE; |
| | | 2670 | | } |
| | 0 | 2671 | | s.recording_path = export_widget_->get_file_path().toStdString(); |
| | 0 | 2672 | | s.recording_count = export_widget_->get_frame_count(); |
| | | 2673 | | } |
| | | 2674 | | |
| | | 2675 | | // Auto-Focus Settings |
| | | 2676 | | { |
| | 0 | 2677 | | s.autofocus_enabled = render_widget_->autofocus_widget()->is_enabled(); |
| | 0 | 2678 | | s.autofocus_nb_subaps = render_widget_->autofocus_widget()->get_nb_subaps(); |
| | 0 | 2679 | | s.autofocus_nb_iter = render_widget_->autofocus_widget()->get_nb_iter(); |
| | 0 | 2680 | | s.autofocus_zernike_orders = std::vector<int>(); |
| | 0 | 2681 | | s.autofocus_skip_subapertures_outside_pupil = |
| | | 2682 | | render_widget_->autofocus_widget()->skip_subapertures_outside_pupil(); |
| | 0 | 2683 | | s.autofocus_use_graph_laplacian = render_widget_->autofocus_widget()->use_graph_laplacian(); |
| | | 2684 | | |
| | 0 | 2685 | | if (render_widget_->autofocus_widget()->is_z2_enabled()) { |
| | 0 | 2686 | | s.autofocus_zernike_orders.push_back(2); |
| | | 2687 | | } |
| | | 2688 | | |
| | 0 | 2689 | | if (render_widget_->autofocus_widget()->is_z3_enabled()) { |
| | 0 | 2690 | | s.autofocus_zernike_orders.push_back(3); |
| | | 2691 | | } |
| | | 2692 | | |
| | 0 | 2693 | | if (render_widget_->autofocus_widget()->is_z4_enabled()) { |
| | 0 | 2694 | | s.autofocus_zernike_orders.push_back(4); |
| | | 2695 | | } |
| | | 2696 | | |
| | 0 | 2697 | | if (render_widget_->autofocus_widget()->is_z5_enabled()) { |
| | 0 | 2698 | | s.autofocus_zernike_orders.push_back(5); |
| | | 2699 | | } |
| | | 2700 | | |
| | 0 | 2701 | | if (render_widget_->autofocus_widget()->is_z6_enabled()) { |
| | 0 | 2702 | | s.autofocus_zernike_orders.push_back(6); |
| | | 2703 | | } |
| | | 2704 | | |
| | 0 | 2705 | | if (render_widget_->autofocus_widget()->is_z7_enabled()) { |
| | 0 | 2706 | | s.autofocus_zernike_orders.push_back(7); |
| | | 2707 | | } |
| | | 2708 | | |
| | 0 | 2709 | | if (render_widget_->autofocus_widget()->is_z8_enabled()) { |
| | 0 | 2710 | | s.autofocus_zernike_orders.push_back(8); |
| | | 2711 | | } |
| | | 2712 | | |
| | 0 | 2713 | | if (render_widget_->autofocus_widget()->is_z9_enabled()) { |
| | 0 | 2714 | | s.autofocus_zernike_orders.push_back(9); |
| | | 2715 | | } |
| | | 2716 | | |
| | 0 | 2717 | | if (render_widget_->autofocus_widget()->is_z10_enabled()) { |
| | 0 | 2718 | | s.autofocus_zernike_orders.push_back(10); |
| | | 2719 | | } |
| | | 2720 | | |
| | 0 | 2721 | | s.signal_plot_time_window_seconds = signal_plot_time_window_seconds_; |
| | | 2722 | | } |
| | | 2723 | | |
| | 0 | 2724 | | return s; |
| | 0 | 2725 | | } |
| | | 2726 | | |
| | 0 | 2727 | | void MainWindow::set_pipeline_settings(const pipeline::Settings &s) { |
| | | 2728 | | using namespace holovibes::pipeline; |
| | | 2729 | | |
| | 0 | 2730 | | signal_plot_time_window_seconds_ = s.signal_plot_time_window_seconds; |
| | | 2731 | | |
| | | 2732 | | // --- Advanced Settings --- |
| | | 2733 | | { |
| | | 2734 | | // (none exposed in UI) |
| | | 2735 | | } |
| | | 2736 | | |
| | | 2737 | | // --- Import Settings --- |
| | | 2738 | | { |
| | | 2739 | | // Source: camera or file |
| | 0 | 2740 | | if (s.import_source == ImportSource::HOLOFILE) { |
| | 0 | 2741 | | import_widget_->set_camera_mode(false); |
| | 0 | 2742 | | import_widget_->set_file_path(QString::fromStdString(s.load_path.string())); |
| | 0 | 2743 | | import_widget_->set_fps_limit(s.load_fps_limit); |
| | 0 | 2744 | | import_widget_->set_start_index(static_cast<int>(s.load_begin)); |
| | 0 | 2745 | | import_widget_->set_end_index(static_cast<int>(s.load_end)); |
| | | 2746 | | |
| | 0 | 2747 | | QString method; |
| | 0 | 2748 | | switch (s.load_method) { |
| | | 2749 | | case LoadMethod::READ_LIVE: |
| | 0 | 2750 | | method = "Read Live"; |
| | 0 | 2751 | | break; |
| | | 2752 | | case LoadMethod::LOAD_IN_CPU: |
| | 0 | 2753 | | method = "Load in CPU RAM"; |
| | 0 | 2754 | | break; |
| | | 2755 | | case LoadMethod::LOAD_IN_GPU: |
| | 0 | 2756 | | method = "Load in GPU RAM"; |
| | 0 | 2757 | | break; |
| | | 2758 | | default: |
| | 0 | 2759 | | method = "Read Live"; |
| | | 2760 | | break; |
| | | 2761 | | } |
| | 0 | 2762 | | import_widget_->set_load_method(method); |
| | 0 | 2763 | | } else { |
| | 0 | 2764 | | import_widget_->set_camera_mode(true); |
| | 0 | 2765 | | import_widget_->set_fps_limit(std::nullopt); |
| | | 2766 | | |
| | 0 | 2767 | | QString source; |
| | 0 | 2768 | | switch (s.import_source) { |
| | | 2769 | | case ImportSource::AMETEK_S710_EURESYS_COAXLINK_OCTO: |
| | 0 | 2770 | | source = "Ametek S710 Euresys Coaxlink Octo"; |
| | 0 | 2771 | | break; |
| | | 2772 | | case ImportSource::AMETEK_S711_EURESYS_COAXLINK_QSFP: |
| | 0 | 2773 | | source = "Ametek S711 Euresys Coaxlink QSFP+"; |
| | 0 | 2774 | | break; |
| | | 2775 | | default: |
| | 0 | 2776 | | source = "Ametek S710 Euresys Coaxlink Octo"; |
| | | 2777 | | break; |
| | | 2778 | | } |
| | 0 | 2779 | | import_widget_->set_camera_type(source); |
| | 0 | 2780 | | } |
| | 0 | 2781 | | import_widget_->set_sampling_frequency_hz(s.input_sampling_frequency_hz); |
| | | 2782 | | } |
| | | 2783 | | |
| | | 2784 | | // --- Image Rendering Settings --- |
| | | 2785 | | { |
| | 0 | 2786 | | QString method; |
| | 0 | 2787 | | switch (s.spacial_method) { |
| | | 2788 | | case SpacialMethod::FRESNEL_DIFFRACTION: |
| | 0 | 2789 | | method = "Fresnel Diffraction"; |
| | 0 | 2790 | | break; |
| | | 2791 | | case SpacialMethod::ANGULAR_SPECTRUM: |
| | 0 | 2792 | | method = "Angular Spectrum"; |
| | 0 | 2793 | | break; |
| | | 2794 | | default: |
| | 0 | 2795 | | method = "None"; |
| | | 2796 | | break; |
| | | 2797 | | } |
| | 0 | 2798 | | render_widget_->set_batch_size(s.load_batch); |
| | 0 | 2799 | | render_widget_->set_space_transform(method); |
| | 0 | 2800 | | render_widget_->set_lambda(s.spacial_lambda * 1e9); // nm |
| | 0 | 2801 | | render_widget_->set_focus(s.spacial_z * 1e3); // mm |
| | 0 | 2802 | | render_widget_->set_asp_padding_enabled(s.asp_padding_enabled); |
| | 0 | 2803 | | render_widget_->set_asp_padded_width(s.asp_padded_width); |
| | 0 | 2804 | | render_widget_->set_asp_padded_height(s.asp_padded_height); |
| | 0 | 2805 | | } |
| | | 2806 | | { |
| | 0 | 2807 | | render_widget_->set_filter_2d_enabled(s.filter_2d); |
| | 0 | 2808 | | render_widget_->set_filter_inner(s.filter_r_inner); |
| | 0 | 2809 | | render_widget_->set_filter_outer(s.filter_r_outer); |
| | | 2810 | | // smooth_inner/smooth_outer omitted since not exposed in UI |
| | | 2811 | | } |
| | | 2812 | | { |
| | 0 | 2813 | | QString method; |
| | 0 | 2814 | | switch (s.time_method) { |
| | | 2815 | | case TimeMethod::PRINCIPAL_COMPONENT_ANALYSIS: |
| | 0 | 2816 | | method = "Principal Component Analysis"; |
| | 0 | 2817 | | break; |
| | | 2818 | | case TimeMethod::RFFT: |
| | 0 | 2819 | | method = "RFFT"; |
| | 0 | 2820 | | break; |
| | | 2821 | | case TimeMethod::FFT: |
| | 0 | 2822 | | method = "FFT"; |
| | 0 | 2823 | | break; |
| | | 2824 | | default: |
| | 0 | 2825 | | method = "None"; |
| | | 2826 | | break; |
| | | 2827 | | } |
| | 0 | 2828 | | render_widget_->set_time_transform(method); |
| | 0 | 2829 | | render_widget_->set_time_window(static_cast<int>(s.time_window)); |
| | 0 | 2830 | | render_widget_->set_time_stride(static_cast<int>(s.time_stride)); |
| | | 2831 | | |
| | 0 | 2832 | | view_widget_->set_z_origin(static_cast<int>(s.time_z_begin)); |
| | 0 | 2833 | | view_widget_->set_z_width(static_cast<int>(s.time_z_end - s.time_z_begin)); |
| | 0 | 2834 | | } |
| | | 2835 | | |
| | | 2836 | | // --- View Settings --- |
| | | 2837 | | { |
| | 0 | 2838 | | if (display_workspace_ != nullptr) { |
| | 0 | 2839 | | display_workspace_->set_visualization_enabled(QStringLiteral("xy_raw"), s.raw_view); |
| | 0 | 2840 | | display_workspace_->set_visualization_enabled(QStringLiteral("raw_spectrum"), |
| | | 2841 | | s.view_raw_spectrum); |
| | 0 | 2842 | | display_workspace_->set_visualization_enabled(QStringLiteral("processed_spectrum"), |
| | | 2843 | | s.view_processed_spectrum); |
| | 0 | 2844 | | display_workspace_->set_visualization_enabled(QStringLiteral("zernike_a4"), |
| | | 2845 | | s.view_zernike_metrics); |
| | 0 | 2846 | | display_workspace_->set_visualization_enabled(QStringLiteral("zernike_phase"), |
| | | 2847 | | s.view_zernike_phase); |
| | 0 | 2848 | | display_workspace_->set_visualization_enabled(QStringLiteral("shack_hartmann"), |
| | | 2849 | | s.view_shack_hartmann); |
| | 0 | 2850 | | display_workspace_->set_visualization_enabled(QStringLiteral("shack_hartmann_xcorr"), |
| | | 2851 | | s.view_shack_hartmann_xcorr); |
| | 0 | 2852 | | display_workspace_->set_visualization_enabled(QStringLiteral("xz_processed"), s.view_3d_cuts); |
| | 0 | 2853 | | display_workspace_->set_visualization_enabled(QStringLiteral("yz_processed"), s.view_3d_cuts); |
| | | 2854 | | } |
| | | 2855 | | } |
| | | 2856 | | |
| | | 2857 | | // --- Post-processing Settings --- |
| | | 2858 | | { |
| | 0 | 2859 | | view_widget_->set_flatfield_enabled(s.pp_flatfield); |
| | 0 | 2860 | | view_widget_->set_flatfield_cutoff_period_um(s.pp_flatfield_cutoff_period_m * 1e6); |
| | 0 | 2861 | | view_widget_->set_accumulation(static_cast<int>(s.pp_accumulation)); |
| | 0 | 2862 | | render_widget_->set_convolution_divide(s.pp_convolution_divide); |
| | | 2863 | | |
| | | 2864 | | // Select convolution kernel name from path |
| | 0 | 2865 | | QString convName = "None"; |
| | 0 | 2866 | | if (s.pp_convolution && !s.pp_convolution_path.empty()) { |
| | 0 | 2867 | | auto fileName = QFileInfo(QString::fromStdString(s.pp_convolution_path)).baseName(); |
| | 0 | 2868 | | convName = fileName; |
| | 0 | 2869 | | } |
| | 0 | 2870 | | render_widget_->set_convolution(convName); |
| | | 2871 | | |
| | 0 | 2872 | | view_widget_->set_pct_radius(s.pp_pctclip_radius); |
| | 0 | 2873 | | view_widget_->set_pct_enabled(s.pp_pctclip); |
| | 0 | 2874 | | view_widget_->set_registration_enabled(s.pp_registration); |
| | 0 | 2875 | | view_widget_->set_registration_radius(s.pp_registration_radius); |
| | 0 | 2876 | | } |
| | | 2877 | | |
| | | 2878 | | // --- Recording Settings --- |
| | | 2879 | | { |
| | 0 | 2880 | | export_widget_->set_file_path(QString::fromStdString(s.recording_path.string())); |
| | 0 | 2881 | | export_widget_->set_frame_count(static_cast<int>(s.recording_count)); |
| | | 2882 | | // recording_method not exposed (always RAW in get_pipeline_settings) |
| | | 2883 | | } |
| | | 2884 | | |
| | | 2885 | | // --- Auto-Focus Settings --- |
| | | 2886 | | { |
| | 0 | 2887 | | auto *autofocus = render_widget_->autofocus_widget(); |
| | 0 | 2888 | | autofocus->set_enabled(s.autofocus_enabled); |
| | 0 | 2889 | | autofocus->nb_subaps_spin()->setValue(s.autofocus_nb_subaps); |
| | 0 | 2890 | | autofocus->nb_iter_spin()->setValue(s.autofocus_nb_iter); |
| | 0 | 2891 | | autofocus->set_skip_subapertures_outside_pupil(s.autofocus_skip_subapertures_outside_pupil); |
| | 0 | 2892 | | autofocus->set_use_graph_laplacian(s.autofocus_use_graph_laplacian); |
| | | 2893 | | } |
| | | 2894 | | |
| | 0 | 2895 | | configure_unsupported_features(); |
| | 0 | 2896 | | } |
| | | 2897 | | |
| | 0 | 2898 | | std::string MainWindow::get_selected_camera_config_path() { |
| | 0 | 2899 | | QString appDataBase = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); |
| | 0 | 2900 | | QString appDataPath = appDataBase + "/" + QCoreApplication::applicationVersion(); |
| | 0 | 2901 | | QString cameraConfigPath = appDataPath + "/" + "camera_configs/"; |
| | 0 | 2902 | | std::string config_path = |
| | | 2903 | | cameraConfigPath.toStdString() + import_widget_->get_camera_config().toStdString() + ".json"; |
| | 0 | 2904 | | return config_path; |
| | 0 | 2905 | | } |
| | | 2906 | | |
| | | 2907 | | } // namespace holovibes::ui |