Subversion Repositories seema-scanner

Rev

Rev 251 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 jakw 1
#include "SMScanner.h"
225 jakw 2
 
1 jakw 3
#include "ui_SMScanner.h"
4
 
4 jakw 5
#include <QMetaObject>
36 jakw 6
#include <QFileDialog>
67 jakw 7
#include <QMessageBox>
141 jakw 8
#include <QProgressDialog>
244 jakw 9
#include <QDesktopServices>
251 - 10
#include <QCollator>
4 jakw 11
 
44 jakw 12
#include <pcl/io/pcd_io.h>
13
#include <pcl/io/ascii_io.h>
14
#include <pcl/io/ply_io.h>
15
#include <pcl/io/png_io.h>
16
#include <pcl/io/vtk_io.h>
17
#include <vtkPolyDataWriter.h>
18
#include <pcl/conversions.h>
19
 
139 jakw 20
#include "cvtools.h"
21
 
27 jakw 22
SMScanner::SMScanner(QWidget *parent) :QMainWindow(parent), ui(new Ui::SMScanner),
67 jakw 23
                                        calibrationReviewMode(false), captureReviewMode(false), lastCaptureId(-1){
4 jakw 24
 
27 jakw 25
    // Register metatypes
26
    qRegisterMetaType<cv::Mat>("cv::Mat");
27
    qRegisterMetaType< std::vector<cv::Mat> >("std::vector<cv::Mat>");
30 jakw 28
    qRegisterMetaType< std::vector<float> >("std::vector<float>");
27 jakw 29
    qRegisterMetaType<SMCalibrationSet>("SMCalibrationSet");
30
    qRegisterMetaType< std::vector<SMCalibrationSet> >("std::vector<SMCalibrationSet>");
31
    qRegisterMetaType<SMFrameSequence>("SMFrameSequence");
42 jakw 32
    qRegisterMetaType< std::vector<SMFrameSequence> >("std::vector<SMFrameSequence>");
45 jakw 33
    qRegisterMetaType<pcl::PointCloud<pcl::PointXYZRGB>::Ptr>("pcl::PointCloud<pcl::PointXYZRGB>::Ptr");
42 jakw 34
    qRegisterMetaType<SMPointCloud>("SMPointCloud");
208 flgw 35
    qRegisterMetaType< std::vector<SMPointCloud> >("std::vector<SMPointCloud>");
27 jakw 36
 
42 jakw 37
    // Setup ui (requires stream operators registered)
38
    ui->setupUi(this);
39
 
4 jakw 40
    // Restore geometry
41
    this->restoreGeometry(settings.value("geometry/mainwindow").toByteArray());
42
    this->restoreState(settings.value("state/mainwindow").toByteArray());
43
 
255 - 44
    // Set up export thread
45
    exportWorker = new SMExportWorker();
46
    exportWorkerThread = new QThread(this);
47
    exportWorkerThread->setObjectName("exportWorkerThread");
48
    exportWorker->moveToThread(exportWorkerThread);
49
    exportWorkerThread->start();
50
 
51
    // Connections
52
    connect(this, SIGNAL(exportFrameSequence(QString, SMFrameSequence)), exportWorker, SLOT(exportFrameSequence(QString, SMFrameSequence)));
53
    connect(exportWorker, SIGNAL(progressUpdate(int)), this, SLOT(onExportProgress(int)));
54
    connect(exportWorker, SIGNAL(aborted()), this, SLOT(onAbortedExport()));
55
    connect(exportWorker, SIGNAL(finished(int)), this, SLOT(onFinishedExport(int)));
56
 
57
    // Start export timer
58
    exportTimer = new QTimer(this);
59
    connect(exportTimer, SIGNAL(timeout()), exportWorker, SLOT(work()));
60
    exportTimer->start(1000);
61
 
225 jakw 62
    // Set up capture thread
4 jakw 63
    captureWorker = new SMCaptureWorker;
64
    captureWorkerThread = new QThread(this);
65
    captureWorkerThread->setObjectName("captureWorkerThread");
66
    captureWorker->moveToThread(captureWorkerThread);
67
    captureWorkerThread->start();
68
 
69
    // Connections
23 jakw 70
    connect(captureWorker, SIGNAL(newFrame(unsigned int, cv::Mat)), this, SLOT(onReceiveFrame(unsigned int, cv::Mat)));
27 jakw 71
    connect(captureWorker, SIGNAL(newCalibrationSet(SMCalibrationSet)), this, SLOT(onReceiveCalibrationSet(SMCalibrationSet)));
72
    connect(captureWorker, SIGNAL(newFrameSequence(SMFrameSequence)), this, SLOT(onReceiveFrameSequence(SMFrameSequence)));
30 jakw 73
    connect(captureWorker, SIGNAL(rotatedTo(float)), this, SLOT(onReceiveRotatedTo(float)));
4 jakw 74
 
75
    // Start capturing
76
    QMetaObject::invokeMethod(captureWorker, "setup");
77
    QMetaObject::invokeMethod(captureWorker, "doWork");
78
 
225 jakw 79
    // Set up calibration thread
80
    calibrationWorker = new SMCalibrationWorker;
81
    calibrationWorkerThread = new QThread(this);
82
    calibrationWorkerThread->setObjectName("calibrationWorkerThread");
83
    calibrationWorker->moveToThread(calibrationWorkerThread);
84
    calibrationWorkerThread->start();
148 jakw 85
 
225 jakw 86
    // Connections
87
    connect(calibrationWorker, SIGNAL(newCheckerboardResult(int, SMCalibrationSet)), this, SLOT(onReceiveCheckerboardResult(int, SMCalibrationSet)));
88
    connect(calibrationWorker, SIGNAL(done()), this, SLOT(onReceiveCalibrationDone()));
89
    connect(calibrationWorker, SIGNAL(done()), ui->pointCloudWidget, SLOT(updateCalibrationParameters()));
90
//    connect(calibrationWorker, SIGNAL(done()), calibrationWorkerThread, SLOT(quit()));
91
//    connect(calibrationWorker, SIGNAL(done()), calibrationWorker, SLOT(deleteLater()));
92
    connect(calibrationWorker, SIGNAL(logMessage(QString)), &logDialog, SLOT(showLogMessage(QString)));
148 jakw 93
 
225 jakw 94
    // Set up reconstruction thread
95
    reconstructionWorker = new SMReconstructionWorker;
96
    reconstructionWorkerThread = new QThread(this);
97
    reconstructionWorkerThread->setObjectName("reconstructionWorkerThread");
98
    reconstructionWorker->moveToThread(reconstructionWorkerThread);
99
    reconstructionWorkerThread->start();
100
 
101
    // Connections
102
    connect(reconstructionWorker, SIGNAL(newPointCloud(SMPointCloud)), this, SLOT(onReceivePointCloud(SMPointCloud)));
103
    connect(reconstructionWorker, SIGNAL(done()), reconstructionWorkerThread, SLOT(quit()));
104
    connect(reconstructionWorker, SIGNAL(done()), reconstructionWorker, SLOT(deleteLater()));
105
 
1 jakw 106
}
107
 
23 jakw 108
void SMScanner::onReceiveFrame(unsigned int camId, cv::Mat frame){
4 jakw 109
 
23 jakw 110
    if(camId == 0){
25 jakw 111
        if(!calibrationReviewMode)
112
            ui->calibrationCamera0Widget->showImageCV(frame);
27 jakw 113
        if(!captureReviewMode)
114
            ui->captureCamera0Widget->showImageCV(frame);
23 jakw 115
    } else if(camId == 1){
25 jakw 116
        if(!calibrationReviewMode)
117
            ui->calibrationCamera1Widget->showImageCV(frame);
27 jakw 118
        if(!captureReviewMode)
119
            ui->captureCamera1Widget->showImageCV(frame);
23 jakw 120
    }
121
}
4 jakw 122
 
23 jakw 123
void SMScanner::on_actionPreferences_triggered(){
4 jakw 124
 
74 jakw 125
    connect(&preferenceDialog, SIGNAL(accepted()), this, SLOT(onPreferencesChanged()));
73 jakw 126
 
4 jakw 127
    preferenceDialog.show();
128
}
129
 
73 jakw 130
void SMScanner::onPreferencesChanged(){
131
 
225 jakw 132
    // Stop capture worker
133
//    connect(captureWorker, SIGNAL(finished()), captureWorker, SLOT(deleteLater()));
134
//    connect(captureWorker, SIGNAL(finished()), captureWorkerThread, SLOT(quit()));
137 jakw 135
    QMetaObject::invokeMethod(captureWorker, "stopWork");
225 jakw 136
//    captureWorkerThread->quit();
137
//    captureWorkerThread->wait();
73 jakw 138
 
225 jakw 139
//    // Restart capture worker
140
//    captureWorker = new SMCaptureWorker;
141
//    captureWorkerThread = new QThread(this);
142
//    captureWorkerThread->setObjectName("captureWorkerThread");
143
//    captureWorker->moveToThread(captureWorkerThread);
144
//    captureWorkerThread->start();
73 jakw 145
 
225 jakw 146
//    connect(captureWorker, SIGNAL(newFrame(unsigned int, cv::Mat)), this, SLOT(onReceiveFrame(unsigned int, cv::Mat)));
147
//    connect(captureWorker, SIGNAL(newCalibrationSet(SMCalibrationSet)), this, SLOT(onReceiveCalibrationSet(SMCalibrationSet)));
148
//    connect(captureWorker, SIGNAL(newFrameSequence(SMFrameSequence)), this, SLOT(onReceiveFrameSequence(SMFrameSequence)));
149
//    connect(captureWorker, SIGNAL(rotatedTo(float)), this, SLOT(onReceiveRotatedTo(float)));
74 jakw 150
 
137 jakw 151
    QMetaObject::invokeMethod(captureWorker, "setup");
152
    QMetaObject::invokeMethod(captureWorker, "doWork");
74 jakw 153
 
73 jakw 154
}
155
 
4 jakw 156
void SMScanner::closeEvent(QCloseEvent *event){
157
 
225 jakw 158
    // Close dialogs
159
    preferenceDialog.close();
160
    logDialog.close();
161
 
23 jakw 162
    // Stop capturing thread
163
    connect(captureWorker, SIGNAL(finished()), captureWorker, SLOT(deleteLater()));
164
    connect(captureWorker, SIGNAL(finished()), captureWorkerThread, SLOT(quit()));
165
    QMetaObject::invokeMethod(captureWorker, "stopWork");
166
    captureWorkerThread->quit();
167
    captureWorkerThread->wait();
168
 
4 jakw 169
    // Save window geometry
170
    settings.setValue("geometry/mainwindow", this->saveGeometry());
171
    settings.setValue("state/mainwindow", this->saveState());
172
 
173
    event->accept();
174
 
175
}
176
 
2 jakw 177
SMScanner::~SMScanner(){
1 jakw 178
    delete ui;
179
}
4 jakw 180
 
23 jakw 181
void SMScanner::on_singleCalibrationButton_clicked(){
182
 
25 jakw 183
    // If in review mode, go back to aquisition mode
184
    if(calibrationReviewMode){
26 jakw 185
        ui->calibrationListWidget->clearSelection();
25 jakw 186
        ui->singleCalibrationButton->setText("Single Aquisition");
187
        ui->batchCalibrationButton->setText("Batch Aquisition");
188
        calibrationReviewMode = false;
189
        return;
190
    }
191
 
27 jakw 192
    QMetaObject::invokeMethod(captureWorker, "acquireCalibrationSet", Q_ARG(float, -1.0));
23 jakw 193
 
194
}
195
 
30 jakw 196
 
197
void SMScanner::on_batchCalibrationButton_clicked(){
198
 
199
    // If in review mode, go back to aquisition mode
200
    if(calibrationReviewMode){
201
        ui->calibrationListWidget->clearSelection();
202
        ui->singleCalibrationButton->setText("Single Aquisition");
203
        ui->batchCalibrationButton->setText("Batch Aquisition");
204
        calibrationReviewMode = false;
205
        return;
206
    }
207
 
208
    // Construct vector of angles
209
    int angleStart = ui->calibrationBatchStartSpinBox->value();
210
    int angleEnd = ui->calibrationBatchEndSpinBox->value();
211
    int angleStep = ui->calibrationBatchStepSpinBox->value();
212
 
213
    if(angleStart > angleEnd)
214
        angleEnd += 360;
215
 
216
    std::vector<float> angles;
217
    for(int i=angleStart; i<=angleEnd; i+=angleStep)
255 - 218
       angles.push_back(i % 360);
30 jakw 219
 
220
    QMetaObject::invokeMethod(captureWorker, "acquireCalibrationSets", Q_ARG(std::vector<float>, angles));
221
 
222
    std::cout << "Aquiring sets at ";
75 jakw 223
    for(unsigned int i=0; i<angles.size(); i++)
30 jakw 224
        std::cout << angles[i] << " ";
225
    std::cout << " degrees" <<std::endl;
226
}
227
 
228
 
23 jakw 229
void SMScanner::on_calibrationRotationDial_sliderReleased(){
27 jakw 230
 
23 jakw 231
    float angle = ui->calibrationRotationDial->value();
232
    std::cout << "Rotation stage target: " << angle << std::endl;
233
    QMetaObject::invokeMethod(captureWorker, "rotateTo", Q_ARG(float, angle));
27 jakw 234
 
159 jakw 235
    ui->calibrationRotationSpinBox->setValue(angle);
236
 
237
    ui->captureRotationDial->setValue(angle);
238
    ui->captureRotationSpinBox->setValue(angle);
23 jakw 239
}
24 jakw 240
 
159 jakw 241
 
242
void SMScanner::on_calibrationRotationSpinBox_editingFinished(){
243
 
244
    float angle = ui->calibrationRotationSpinBox->value();
245
    std::cout << "Rotation stage target: " << angle << std::endl;
246
    QMetaObject::invokeMethod(captureWorker, "rotateTo", Q_ARG(float, angle));
247
 
248
    ui->calibrationRotationDial->setValue(angle);
249
 
250
    ui->captureRotationSpinBox->setValue(angle);
251
    ui->captureRotationDial->setValue(angle);
252
}
253
 
27 jakw 254
void SMScanner::onReceiveCalibrationSet(SMCalibrationSet calibrationSet){
123 jakw 255
 
225 jakw 256
    // Send to checkerboard detection on calibration thread
257
 
249 jakw 258
    if(settings.value("calibration/method").toString() == "Charuco")
250 jakw 259
        QMetaObject::invokeMethod(calibrationWorker, "checkerboardDetectionCharuco", Q_ARG(SMCalibrationSet, calibrationSet));
249 jakw 260
    else
261
        QMetaObject::invokeMethod(calibrationWorker, "checkerboardDetection", Q_ARG(SMCalibrationSet, calibrationSet));
262
 
23 jakw 263
}
25 jakw 264
 
225 jakw 265
void SMScanner::on_calibrateCamerasButton_clicked(){
25 jakw 266
 
29 jakw 267
    // disable ui elements
225 jakw 268
    ui->calibrateCamerasButton->setEnabled(false);
269
    ui->calibrateRotationStageButton->setEnabled(false);
29 jakw 270
    ui->calibrationFrame->setEnabled(false);
27 jakw 271
 
225 jakw 272
    // if none items are selected, we will use all available items
273
    if(ui->calibrationListWidget->selectedItems().empty())
274
        ui->calibrationListWidget->selectAll();
275
 
276
    // set selected flags
75 jakw 277
    for(unsigned int i=0; i<calibrationData.size(); i++){
225 jakw 278
        if(ui->calibrationListWidget->item(i)->isSelected())
279
            calibrationData[i].selected = true;
280
        else
281
            calibrationData[i].selected = false;
27 jakw 282
    }
283
 
225 jakw 284
    logDialog.show();
27 jakw 285
 
225 jakw 286
    // start calibration
249 jakw 287
    QSettings settings;
288
    if(settings.value("calibration/method").toString() == "Charuco")
289
        QMetaObject::invokeMethod(calibrationWorker, "cameraCalibrationCharuco", Q_ARG(std::vector<SMCalibrationSet>, calibrationData));
290
    else
291
        QMetaObject::invokeMethod(calibrationWorker, "cameraCalibration", Q_ARG(std::vector<SMCalibrationSet>, calibrationData));
27 jakw 292
 
249 jakw 293
 
27 jakw 294
}
295
 
255 - 296
void SMScanner::onReceiveCheckerboardResult(int, SMCalibrationSet calibrationSet){
30 jakw 297
 
232 jakw 298
    int id = ui->calibrationListWidget->count();
299
    calibrationSet.id = id;
30 jakw 300
 
232 jakw 301
    calibrationData.push_back(calibrationSet);
302
 
303
    // Add identifier to list
304
    QListWidgetItem* item = new QListWidgetItem(QString("Calibration Set %1 -- %2 deg").arg(id).arg(calibrationSet.rotationAngle), ui->calibrationListWidget);
305
    ui->calibrationListWidget->addItem(item);
306
    item->setSelected(true);
307
    ui->calibrationListWidget->setCurrentItem(item);
308
 
309
    // Enable calibration button
310
    if(calibrationData.size() >= 2){
311
        ui->calibrateCamerasButton->setEnabled(true);
312
        ui->calibrateRotationStageButton->setEnabled(true);
313
    }
314
 
315
    //ui->calibrationListWidget->setCurrentRow(idx);
27 jakw 316
}
317
 
225 jakw 318
void SMScanner::onReceiveCalibrationDone(){
27 jakw 319
 
320
    std::cout << "Calibration done!" << std::endl;
225 jakw 321
    ui->calibrateCamerasButton->setEnabled(true);
322
    ui->calibrateRotationStageButton->setEnabled(true);
29 jakw 323
    ui->calibrationFrame->setEnabled(true);
27 jakw 324
 
325
}
326
 
327
void SMScanner::on_calibrationListWidget_itemSelectionChanged(){
328
 
329
    // if selection is just cleared
330
    if(ui->calibrationListWidget->selectedItems().empty())
331
        return;
332
 
25 jakw 333
    calibrationReviewMode = true;
334
    ui->singleCalibrationButton->setText("Live View");
335
    ui->batchCalibrationButton->setText("Live View");
336
 
225 jakw 337
    int currentRow = ui->calibrationListWidget->currentRow();
338
    if(currentRow < 0)
339
        return;
340
 
341
    assert(currentRow < (int)calibrationData.size());
342
 
343
    // if checkerboard results are available, show them, otherwise show the frame
222 flgw 344
    if(calibrationData[currentRow].frame0Result.empty())
345
        ui->calibrationCamera0Widget->showImageCV(calibrationData[currentRow].frame0);
346
    else
25 jakw 347
        ui->calibrationCamera0Widget->showImageCV(calibrationData[currentRow].frame0Result);
222 flgw 348
 
349
    if(calibrationData[currentRow].frame1Result.empty())
350
        ui->calibrationCamera1Widget->showImageCV(calibrationData[currentRow].frame1);
25 jakw 351
    else
352
        ui->calibrationCamera1Widget->showImageCV(calibrationData[currentRow].frame1Result);
27 jakw 353
 
25 jakw 354
}
355
 
159 jakw 356
void SMScanner::on_captureRotationDial_sliderReleased(){
357
 
358
    float angle = ui->captureRotationDial->value();
359
    std::cout << "Rotation stage target: " << angle << std::endl;
360
    QMetaObject::invokeMethod(captureWorker, "rotateTo", Q_ARG(float, angle));
361
 
362
    ui->captureRotationSpinBox->setValue(ui->captureRotationDial->value());
363
 
364
    ui->calibrationRotationDial->setValue(ui->captureRotationDial->value());
365
    ui->calibrationRotationSpinBox->setValue(ui->captureRotationDial->value());
366
}
367
 
368
void SMScanner::on_captureRotationSpinBox_editingFinished(){
369
 
370
    float angle = ui->captureRotationSpinBox->value();
371
    std::cout << "Rotation stage target: " << angle << std::endl;
372
    QMetaObject::invokeMethod(captureWorker, "rotateTo", Q_ARG(float, angle));
373
 
374
    ui->captureRotationDial->setValue(angle);
375
 
376
    ui->calibrationRotationSpinBox->setValue(angle);
377
    ui->calibrationRotationDial->setValue(angle);
378
}
379
 
27 jakw 380
void SMScanner::on_singleCaptureButton_clicked(){
381
 
382
    // If in review mode, go back to aquisition mode
383
    if(captureReviewMode){
384
        ui->captureTreeWidget->clearSelection();
385
        ui->singleCaptureButton->setText("Single Aquisition");
386
        ui->batchCaptureButton->setText("Batch Aquisition");
387
        captureReviewMode = false;
388
        return;
25 jakw 389
    }
390
 
27 jakw 391
    QMetaObject::invokeMethod(captureWorker, "acquireFrameSequence", Q_ARG(float, -1.0));
25 jakw 392
 
393
}
26 jakw 394
 
27 jakw 395
 
30 jakw 396
void SMScanner::on_batchCaptureButton_clicked(){
397
 
398
    // If in review mode, go back to aquisition mode
399
    if(captureReviewMode){
400
        ui->captureTreeWidget->clearSelection();
401
        ui->singleCaptureButton->setText("Single Aquisition");
402
        ui->batchCaptureButton->setText("Batch Aquisition");
403
        captureReviewMode = false;
404
        return;
405
    }
406
 
407
    // Construct vector of angles
255 - 408
    int repeatsteps = ui->captureBatchStepRepeatSpinBox->value();
409
    int repeatsequence = ui->captureBatchSequenceRepeatSpinBox->value();
30 jakw 410
    int angleStart = ui->captureBatchStartSpinBox->value();
411
    int angleEnd = ui->captureBatchEndSpinBox->value();
412
    int angleStep = ui->captureBatchStepSpinBox->value();
413
 
414
    if(angleStart > angleEnd)
415
        angleEnd += 360;
416
 
417
    std::vector<float> angles;
255 - 418
    for(int i=angleStart; i<=angleEnd; i+=angleStep) {
30 jakw 419
        angles.push_back(i % 360);
255 - 420
        for(int j=1; j<repeatsteps; ++j)
421
            angles.push_back(-1);
422
    }
423
    for(int l=1; l<repeatsequence; ++l) {
424
        for(int i=angleStart; i<=angleEnd; i+=angleStep) {
425
            angles.push_back(i % 360);
426
            for(int j=1; j<repeatsteps; ++j)
427
                angles.push_back(-1);
428
        }
429
    }
30 jakw 430
 
431
    std::cout << "Aquiring sequences at ";
75 jakw 432
    for(unsigned int i=0; i<angles.size(); i++)
30 jakw 433
        std::cout << angles[i] << " ";
434
    std::cout << " degrees" <<std::endl;
435
 
436
    QMetaObject::invokeMethod(captureWorker, "acquireFrameSequences", Q_ARG(std::vector<float>, angles));
437
}
438
 
27 jakw 439
void SMScanner::onReceiveFrameSequence(SMFrameSequence frameSequence){
225 jakw 440
 
207 flgw 441
    frameSequence.id = ++lastCaptureId;
27 jakw 442
 
443
    // Add identifier to list
444
    QTreeWidgetItem* item = new QTreeWidgetItem(ui->captureTreeWidget);
207 flgw 445
    item->setText(0, QString("Frame Sequence %1 -- %2 deg").arg(frameSequence.id).arg(frameSequence.rotationAngle));
139 jakw 446
    //item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
225 jakw 447
    item->setData(0, Qt::UserRole, QPoint(frameSequence.id, -1));
139 jakw 448
    //item->setCheckState(0, Qt::Checked);
27 jakw 449
    //ui->captureTreeWidget->addItem(item);
450
 
103 jakw 451
    for(unsigned int i=0; i<frameSequence.frames0.size(); i++){
27 jakw 452
        QTreeWidgetItem* subItem = new QTreeWidgetItem(item);
453
        subItem->setText(0, QString("frames %1").arg(i));
207 flgw 454
        subItem->setData(0, Qt::UserRole, QPoint(frameSequence.id, i));
27 jakw 455
    }
41 jakw 456
 
225 jakw 457
    // Indicate that the current item is currently reconstructing
458
    item->setTextColor(0, QColor(128, 128, 128));
459
    item->setIcon(0, QIcon::fromTheme("system-run"));
460
 
461
    // Reconstruct the frame sequence
245 jakw 462
    QMetaObject::invokeMethod(reconstructionWorker, "reconstructPointCloud", Q_ARG(SMFrameSequence, frameSequence));
225 jakw 463
    frameSequence.reconstructed = true;
464
 
465
    captureData.push_back(frameSequence);
466
 
27 jakw 467
}
468
 
469
void SMScanner::on_captureTreeWidget_itemSelectionChanged(){
470
 
471
    // if selection is just cleared
472
    if(ui->captureTreeWidget->selectedItems().empty())
473
        return;
474
 
475
    QTreeWidgetItem *item = ui->captureTreeWidget->currentItem();
476
 
477
    captureReviewMode = true;
478
 
479
    ui->singleCaptureButton->setText("Live View");
480
    ui->batchCaptureButton->setText("Live View");
481
 
482
    QPoint idx = item->data(0, Qt::UserRole).toPoint();
483
 
226 jakw 484
    if( idx.y() != -1 && idx.x()>=0 && idx.x()<(int)captureData.size()
485
            && idx.y()<(int)captureData[idx.x()].frames0.size()
486
            && idx.y()<(int)captureData[idx.x()].frames1.size() ){
207 flgw 487
        // TODO idx.x() can & WILL be out of bounds
103 jakw 488
        ui->captureCamera0Widget->showImageCV(captureData[idx.x()].frames0[idx.y()]);
489
        ui->captureCamera1Widget->showImageCV(captureData[idx.x()].frames1[idx.y()]);
27 jakw 490
    }
225 jakw 491
 
27 jakw 492
//     std::cout << "on_captureTreeWidget_itemSelectionChanged" << std::endl;
493
}
30 jakw 494
 
495
 
496
void SMScanner::onReceiveRotatedTo(float angle){
497
 
498
    // update ui with new position
499
    ui->calibrationRotationDial->setValue(angle);
500
    ui->captureRotationDial->setValue(angle);
501
 
159 jakw 502
    ui->calibrationRotationSpinBox->setValue(angle);
503
    ui->captureRotationSpinBox->setValue(angle);
30 jakw 504
}
36 jakw 505
 
506
void SMScanner::on_actionExport_Sets_triggered(){
507
 
143 jakw 508
    QString dirName = QFileDialog::getExistingDirectory(this, "Export calibration sets", QString());
509
 
510
    QProgressDialog progressDialog("Exporting Sets...", "Cancel", 0, 100, this);
511
    progressDialog.setWindowModality(Qt::WindowModal);
512
    progressDialog.setMinimumDuration(1000);
513
 
514
    progressDialog.show();
112 jakw 515
    cv::Mat frameBGR;
75 jakw 516
    for(unsigned int i=0; i<calibrationData.size(); i++){
36 jakw 517
        QString fileName = QString("%1/frame0_%2.png").arg(dirName).arg(i);
136 jakw 518
 
143 jakw 519
        progressDialog.setValue(100.0*i/calibrationData.size());
520
 
136 jakw 521
        // Convert to BGR
522
        if(calibrationData[i].frame0.channels() == 1)
523
            cv::cvtColor(calibrationData[i].frame0, frameBGR, CV_BayerBG2BGR);
524
        else
525
            cv::cvtColor(calibrationData[i].frame1, frameBGR, CV_RGB2BGR);
526
 
36 jakw 527
        cv::imwrite(fileName.toStdString(), frameBGR);
528
        fileName = QString("%1/frame1_%2.png").arg(dirName).arg(i);
136 jakw 529
 
530
        // Convert to BGR
531
        if(calibrationData[i].frame1.channels() == 1)
532
            cv::cvtColor(calibrationData[i].frame1, frameBGR, CV_BayerBG2BGR);
533
        else
534
            cv::cvtColor(calibrationData[i].frame1, frameBGR, CV_RGB2BGR);
535
 
36 jakw 536
        cv::imwrite(fileName.toStdString(), frameBGR);
137 jakw 537
 
538
        // Necessary to prevent pileup of video frame signals
112 jakw 539
        QCoreApplication::processEvents();
143 jakw 540
 
541
        if(progressDialog.wasCanceled())
542
            return;
36 jakw 543
    }
544
 
545
}
546
 
547
void SMScanner::on_actionExport_Sequences_triggered(){
548
 
143 jakw 549
    QString dirName = QFileDialog::getExistingDirectory(this, "Export frame sequences", QString());
550
 
551
    QProgressDialog progressDialog("Exporting Sequences...", "Cancel", 0, 100, this);
142 jakw 552
    progressDialog.setWindowModality(Qt::WindowModal);
141 jakw 553
    progressDialog.setMinimumDuration(1000);
554
 
142 jakw 555
    progressDialog.show();
141 jakw 556
 
112 jakw 557
    cv::Mat frameBGR;
103 jakw 558
    for(unsigned int i=0; i<captureData.size(); i++){
255 - 559
        emit exportFrameSequence(dirName, captureData[i]);
103 jakw 560
    }
36 jakw 561
}
41 jakw 562
 
115 jakw 563
void SMScanner::on_actionExport_White_Frames_triggered(){
564
 
143 jakw 565
    QString dirName = QFileDialog::getExistingDirectory(this, "Export frame sequences", QString());
566
 
567
    QProgressDialog progressDialog("Exporting White Frames...", "Cancel", 0, 100, this);
568
    progressDialog.setWindowModality(Qt::WindowModal);
569
    progressDialog.setMinimumDuration(1000);
570
 
115 jakw 571
    cv::Mat frameBGR;
572
    for(unsigned int i=0; i<captureData.size(); i++){
143 jakw 573
 
574
        progressDialog.setValue(100.0*i/captureData.size());
575
 
115 jakw 576
        QString seqDirName = QString("%1/sequence_%2").arg(dirName).arg(i);
577
        if(!QDir().mkdir(seqDirName))
578
            std::cerr << "Could not create directory " << seqDirName.toStdString() << std::endl;
579
 
580
        QString fileName = QString("%1/frames0_0.png").arg(seqDirName);
581
        // Convert Bayer to rgb (png needs BGR order)
582
        cv::cvtColor(captureData[i].frames0[0], frameBGR, CV_BayerBG2BGR);
583
        cv::imwrite(fileName.toStdString(), frameBGR);
128 jakw 584
        // Necessary to prevent memory pileup
115 jakw 585
        QCoreApplication::processEvents();
586
 
587
        fileName = QString("%1/frames1_0.png").arg(seqDirName);
588
        // Convert Bayer to rgb (png needs BGR order)
589
        cv::cvtColor(captureData[i].frames1[0], frameBGR, CV_BayerBG2BGR);
590
        cv::imwrite(fileName.toStdString(), frameBGR);
128 jakw 591
        // Necessary to prevent memory pileup
115 jakw 592
        QCoreApplication::processEvents();
143 jakw 593
        if(progressDialog.wasCanceled())
594
            return;
115 jakw 595
    }
596
 
597
}
598
 
41 jakw 599
 
148 jakw 600
void SMScanner::onReceivePointCloud(SMPointCloud smCloud){
82 jakw 601
 
225 jakw 602
    pointCloudData.push_back(smCloud);
41 jakw 603
 
42 jakw 604
    // Add identifier to list
207 flgw 605
    QListWidgetItem* item = new QListWidgetItem(QString("Point Cloud %1 -- %2 deg").arg(smCloud.id).arg(smCloud.rotationAngle));
42 jakw 606
    item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
207 flgw 607
    item->setData(Qt::UserRole, QVariant(smCloud.id));
42 jakw 608
    item->setCheckState(Qt::Checked);
44 jakw 609
 
42 jakw 610
    ui->pointCloudsListWidget->addItem(item);
207 flgw 611
    ui->pointCloudWidget->addPointCloud(smCloud, smCloud.id);
208 flgw 612
 
225 jakw 613
    // Indicate completed reconstruction in captureTreeWidget
614
    for(int i=0; i<ui->captureTreeWidget->topLevelItemCount(); i++){
615
        QTreeWidgetItem *captureItem = ui->captureTreeWidget->topLevelItem(i);
616
        if(captureItem->data(0, Qt::UserRole).toPoint() == QPoint(smCloud.id, -1)){
617
            captureItem->setTextColor(0, QColor(0, 0, 0));
618
            captureItem->setIcon(0, QIcon());
619
        }
620
   }
621
 
255 - 622
    if (ui->exportCheckBox->isChecked()) {
623
        QString path = ui->exportPathLineEdit->text();
624
        for(unsigned int i = 0; i < captureData.size(); i++) {
625
            if (captureData[i].id == smCloud.id) {
626
                emit exportFrameSequence(path, captureData[i]);
627
                break;
628
            }
629
        }
630
    }
41 jakw 631
}
42 jakw 632
 
255 - 633
void SMScanner::onExportProgress(int progress) {
634
    ui->exportProgressBar->setEnabled(true);
635
    ui->abortExportPushButton->setEnabled(true);
636
    ui->exportProgressBar->setValue(progress);
637
}
638
 
639
void SMScanner::onAbortedExport() {
640
    ui->exportProgressBar->setEnabled(false);
641
    ui->abortExportPushButton->setEnabled(false);
642
    ui->exportProgressBar->setValue(100);
643
}
644
 
645
void SMScanner::onFinishedExport(int id) {
646
    if (ui->deleteExportCheckBox->isChecked()) {
647
        for(unsigned int i = 0; i < captureData.size(); i++) {
648
            if (captureData[i].id == id) {
649
                captureData.erase(captureData.begin()+i);
650
                ui->captureTreeWidget->takeTopLevelItem(i);
651
                break;
652
            }
653
        }
654
    }
655
    onAbortedExport();
656
}
657
 
44 jakw 658
void SMScanner::on_actionExport_Point_Clouds_triggered(){
659
 
660
    QString directory = QFileDialog::getExistingDirectory(this, "Export Point Clouds", QString());
661
 
662
//    //  Non native file dialog
663
//    QFileDialog saveDirectoryDialog(this, "Export Point Clouds", QString(), "*.pcd;;*.ply;;*.vtk;;*.png;;*.txt");
664
//    saveDirectoryDialog.setDefaultSuffix("ply");
665
//    saveDirectoryDialog.setFileMode(QFileDialog::Directory);
666
//    saveDirectoryDialog.exec();
667
//    QString directory = saveDirectoryDialog.directory().path();
668
//    QString type = saveDirectoryDialog.selectedNameFilter();
669
 
144 jakw 670
    QProgressDialog progressDialog("Exporting Point Clouds...", "Cancel", 0, 100, this);
143 jakw 671
    progressDialog.setWindowModality(Qt::WindowModal);
672
    progressDialog.setMinimumDuration(1000);
673
 
44 jakw 674
    // save point clouds in ply format
75 jakw 675
    for(unsigned int i=0; i<pointCloudData.size(); i++){
143 jakw 676
 
677
        progressDialog.setValue(100.0*i/pointCloudData.size());
207 flgw 678
    if(pointCloudData[i].id != -1){
255 - 679
        QString fileName = QString("%1/pointcloud_%2.ply").arg(directory).arg(pointCloudData[i].id);
128 jakw 680
        pcl::PointCloud<pcl::PointXYZRGBNormal>::Ptr pointCloudPCL(pointCloudData[i].pointCloud);
44 jakw 681
        //pcl::io::savePLYFileBinary(fileName.toStdString(), *pointCloudPCL);
682
        pcl::PLYWriter w;
683
        // Write to ply in binary without camera
128 jakw 684
        w.write<pcl::PointXYZRGBNormal> (fileName.toStdString(), *pointCloudPCL, true, false);
207 flgw 685
    }
143 jakw 686
        QCoreApplication::processEvents();
687
        if(progressDialog.wasCanceled())
688
            return;
44 jakw 689
    }
690
 
691
    // save meshlab aln project file
692
    std::ofstream s(QString("%1/alignment.aln").arg(directory).toLocal8Bit());
693
    s << pointCloudData.size() << std::endl;
75 jakw 694
    for(unsigned int i=0; i<pointCloudData.size(); i++){
207 flgw 695
        if(pointCloudData[i].id != -1){
696
        QString fileName = QString("pointcloud_%1.ply").arg(pointCloudData[i].id);
44 jakw 697
        s << fileName.toStdString() << std::endl << "#" << std::endl;
698
        cv::Mat Tr = cv::Mat::eye(4, 4, CV_32F);
699
        cv::Mat(pointCloudData[i].R.t()).copyTo(Tr.colRange(0, 3).rowRange(0, 3));
700
        cv::Mat(-pointCloudData[i].R.t()*pointCloudData[i].T).copyTo(Tr.col(3).rowRange(0, 3));
701
        for(int j=0; j<Tr.rows; j++){
702
            for(int k=0; k<Tr.cols; k++){
703
                s << Tr.at<float>(j,k) << " ";
704
            }
705
            s << std::endl;
706
        }
207 flgw 707
        }
44 jakw 708
    }
709
    s << "0" << std::flush;
710
    s.close();
711
}
712
 
713
void SMScanner::on_pointCloudsListWidget_itemChanged(QListWidgetItem *item){
714
 
715
    int id = item->data(Qt::UserRole).toInt();
255 - 716
    // assert((int)pointCloudData.size()>id);
225 jakw 717
 
207 flgw 718
    if(item->checkState() == Qt::Checked){
719
        id = std::min(int(pointCloudData.size())-1,std::max(0,id));// clamp range
44 jakw 720
        ui->pointCloudWidget->addPointCloud(pointCloudData[id], id);
207 flgw 721
    }
44 jakw 722
    else
723
        ui->pointCloudWidget->removePointCloud(id);
724
 
725
}
726
 
727
void SMScanner::on_actionExport_Parameters_triggered(){
728
 
42 jakw 729
    QString fileName = QFileDialog::getSaveFileName(this, "Export calibration parameters", QString(), "*.xml");
730
    QFileInfo info(fileName);
731
    QString type = info.suffix();
732
    if(type == ""){
733
        fileName.append(".xml");
734
    }
735
 
736
    SMCalibrationParameters calibration = settings.value("calibration/parameters").value<SMCalibrationParameters>();
737
    calibration.exportToXML(fileName);
738
}
67 jakw 739
 
740
void SMScanner::on_actionClear_Sequences_triggered(){
741
 
742
    int res = QMessageBox::question(this, "Clear Captured Sequences", "Clear all captured data?", QMessageBox::Ok, QMessageBox::Cancel);
743
 
744
    if(res == QMessageBox::Ok){
745
        captureData.clear();
746
        ui->captureTreeWidget->clear();
207 flgw 747
        lastCaptureId=-1;
67 jakw 748
    }
749
}
115 jakw 750
 
751
 
135 jakw 752
 
753
void SMScanner::on_actionImport_Parameters_triggered(){
754
 
755
    QString fileName = QFileDialog::getOpenFileName(this, "Import calibration parameters", QString(), "*.xml");
756
    QFileInfo info(fileName);
757
    QString type = info.suffix();
136 jakw 758
    if(type != "xml"){
135 jakw 759
        std::cerr << "Error: calibration parameters must be in .xml file." << std::endl;
760
        return;
761
    }
762
 
763
    SMCalibrationParameters cal;
764
    cal.importFromXML(fileName);
765
 
766
    settings.setValue("calibration/parameters",  QVariant::fromValue(cal));
767
    ui->pointCloudWidget->updateCalibrationParameters();
139 jakw 768
 
769
    std::cout << "Imported calibration parameters " << fileName.toStdString() << std::endl;
135 jakw 770
}
771
 
772
void SMScanner::on_actionImport_Sets_triggered(){
773
 
774
    QString dirName = QFileDialog::getExistingDirectory(this, "Import calibration sets", QString());
775
 
776
    QDir dir(dirName);
777
    QStringList fileNames0 = dir.entryList(QStringList("frame0_*.png"));
778
    QStringList fileNames1 = dir.entryList(QStringList("frame1_*.png"));
779
 
780
    if(fileNames0.empty() || fileNames1.empty() || fileNames0.count() != fileNames1.count()){
781
        std::cerr << "Error: could not load calibration sets. Directory must contain .png files for both cameras." << std::endl;
782
        return;
783
    }
784
 
785
    calibrationData.clear();
786
    ui->calibrationListWidget->clear();
787
 
143 jakw 788
    QProgressDialog progressDialog("Importing Sets...", "Cancel", 0, 100, this);
789
    progressDialog.setWindowModality(Qt::WindowModal);
790
    progressDialog.setMinimumDuration(1000);
791
 
167 jakw 792
    size_t nSets = fileNames0.size();
135 jakw 793
    for(unsigned int i=0; i<nSets; i++){
794
 
143 jakw 795
        progressDialog.setValue(100.0*i/nSets);
796
 
135 jakw 797
        SMCalibrationSet calibrationSet;
798
 
140 jakw 799
        QString fileName0 = QString("%1/frame0_%2.png").arg(dirName).arg(i);
139 jakw 800
        cv::Mat frame0BGR = cv::imread(fileName0.toStdString());
801
        cvtools::cvtColorBGRToBayerBG(frame0BGR, calibrationSet.frame0);
135 jakw 802
 
140 jakw 803
        QString fileName1 = QString("%1/frame1_%2.png").arg(dirName).arg(i);
139 jakw 804
        cv::Mat frame1BGR = cv::imread(fileName1.toStdString());
805
        cvtools::cvtColorBGRToBayerBG(frame1BGR, calibrationSet.frame1);
806
 
225 jakw 807
//        int id = ui->calibrationListWidget->count();
808
//        calibrationSet.id = id;
139 jakw 809
 
225 jakw 810
//        calibrationData.push_back(calibrationSet);
135 jakw 811
 
225 jakw 812
//        // Add identifier to list
813
//        QListWidgetItem* item = new QListWidgetItem(QString("Calibration Set %1 -- %2 deg").arg(id).arg(calibrationSet.rotationAngle), ui->calibrationListWidget);
814
//        item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
815
//        item->setCheckState(Qt::Checked);
816
//        ui->calibrationListWidget->addItem(item);
135 jakw 817
 
225 jakw 818
        this->onReceiveCalibrationSet(calibrationSet);
135 jakw 819
 
820
        QCoreApplication::processEvents();
143 jakw 821
        if(progressDialog.wasCanceled())
822
            return;
135 jakw 823
    }
824
 
825
    // Set enabled checkmark
225 jakw 826
    if(calibrationData.size() >= 2){
827
        ui->calibrateCamerasButton->setEnabled(true);
828
        ui->calibrateRotationStageButton->setEnabled(true);
829
    }
135 jakw 830
}
139 jakw 831
 
832
void SMScanner::on_actionImport_Sequences_triggered(){
833
 
834
    // NOTE: we do not know which algorithm was used!!!
143 jakw 835
 
139 jakw 836
    QString dirName = QFileDialog::getExistingDirectory(this, "Import captured sequences", QString());
837
 
838
    QDir dir(dirName);
839
    QStringList sequenceDirNames = dir.entryList(QStringList("sequence_*"));
840
 
251 - 841
    //If we have more than 10 sequences, natural sorting is needed as no leading zeros used in naming
842
    QCollator collator;
843
    collator.setNumericMode(true);
844
    std::sort(sequenceDirNames.begin(), sequenceDirNames.end(), collator);
845
 
139 jakw 846
    if(sequenceDirNames.empty()){
847
        std::cerr << "Error: could not find sequences." << std::endl;
848
        return;
849
    }
850
 
851
    captureData.clear();
852
    ui->captureTreeWidget->clear();
207 flgw 853
    lastCaptureId=-1;
139 jakw 854
 
143 jakw 855
    QProgressDialog progressDialog("Importing Sequences...", "Cancel", 0, 100, this);
856
    progressDialog.setWindowModality(Qt::WindowModal);
857
    progressDialog.setMinimumDuration(1000);
858
 
139 jakw 859
    for(int s=0; s<sequenceDirNames.count(); s++){
860
 
861
        QDir sequenceDir(QString("%1/%2").arg(dirName).arg(sequenceDirNames.at(s)));
862
 
863
        QStringList fileNames0 = sequenceDir.entryList(QStringList("frames0_*.png"));
864
        QStringList fileNames1 = sequenceDir.entryList(QStringList("frames1_*.png"));
865
 
866
        if(fileNames0.empty() || fileNames1.empty() || fileNames0.count() != fileNames1.count()){
867
            std::cerr << "Error: could not load sequence. Directory must contain .png files for both cameras." << std::endl;
868
            return;
869
        }
870
 
871
        int nFrames = fileNames0.size();
872
 
873
        SMFrameSequence sequence;
874
 
875
        for(int f=0; f<nFrames; f++){
876
 
143 jakw 877
            progressDialog.setValue(100.0*s/sequenceDirNames.count() + 100.0/sequenceDirNames.count()*f/nFrames);
878
 
139 jakw 879
            cv::Mat frame0BGR, frame0BayerBG, frame1BGR, frame1BayerBG;
880
 
881
            QString fileName0 = QString("%1/%2/frames0_%3.png").arg(dirName).arg(sequenceDirNames.at(s)).arg(f);
882
            frame0BGR = cv::imread(fileName0.toStdString());
883
            cvtools::cvtColorBGRToBayerBG(frame0BGR, frame0BayerBG);
884
 
885
            QString fileName1 = QString("%1/%2/frames1_%3.png").arg(dirName).arg(sequenceDirNames.at(s)).arg(f);
886
            frame1BGR = cv::imread(fileName1.toStdString());
887
            cvtools::cvtColorBGRToBayerBG(frame1BGR, frame1BayerBG);
888
 
889
            sequence.frames0.push_back(frame0BayerBG);
890
            sequence.frames1.push_back(frame1BayerBG);
891
 
892
            QCoreApplication::processEvents();
143 jakw 893
            if(progressDialog.wasCanceled())
894
                return;
139 jakw 895
        }
143 jakw 896
 
897
        // Assign whatever algorithm is configured
139 jakw 898
        sequence.codec = settings.value("algorithm").toString();
899
        sequence.rotationAngle = 0;
900
        sequence.reconstructed = false;
247 jakw 901
        sequence.shutters.push_back(16.6666);
139 jakw 902
 
903
        onReceiveFrameSequence(sequence);
904
    }
905
 
906
}
907
 
908
void SMScanner::on_actionClear_Point_Clouds_triggered(){
909
 
910
    int res = QMessageBox::question(this, "Clear Reconstructed Point Clouds", "Clear all reconstructed point clouds?", QMessageBox::Ok, QMessageBox::Cancel);
911
 
912
    if(res == QMessageBox::Ok){
913
 
914
        pointCloudData.clear();
915
        ui->pointCloudsListWidget->clear();
916
        ui->pointCloudWidget->removeAllPointClouds();
917
 
167 jakw 918
        for(unsigned int i=0; i<captureData.size(); i++)
139 jakw 919
            captureData[i].reconstructed = false;
920
    }
921
}
159 jakw 922
 
199 jakw 923
 
225 jakw 924
void SMScanner::on_actionProject_Focusing_Pattern_triggered(){
925
 
199 jakw 926
    bool projectFocusingPattern = ui->actionProject_Focusing_Pattern->isChecked();
927
    QMetaObject::invokeMethod(captureWorker, "setProjectFocusingPattern", Q_ARG(bool, projectFocusingPattern));
928
 
929
}
225 jakw 930
 
931
void SMScanner::on_calibrateRotationStageButton_clicked(){
932
 
933
    // disable ui elements
934
    ui->calibrateCamerasButton->setEnabled(false);
935
    ui->calibrateRotationStageButton->setEnabled(false);
936
    ui->calibrationFrame->setEnabled(false);
937
 
938
    // if none items are selected, we will use all available items
939
    if(ui->calibrationListWidget->selectedItems().empty())
940
        ui->calibrationListWidget->selectAll();
941
 
942
    // set selected flags
943
    for(unsigned int i=0; i<calibrationData.size(); i++){
944
        if(ui->calibrationListWidget->item(i)->isSelected())
945
            calibrationData[i].selected = true;
946
        else
947
            calibrationData[i].selected = false;
948
    }
949
 
950
    logDialog.show();
226 jakw 951
    logDialog.activateWindow();
225 jakw 952
 
953
    // start calibration
954
    QMetaObject::invokeMethod(calibrationWorker, "rotationStageCalibration", Q_ARG(std::vector<SMCalibrationSet>, calibrationData));
955
 
956
}
957
 
958
 
959
void SMScanner::on_actionView_Log_Messages_triggered()
960
{
961
     logDialog.show();
226 jakw 962
     logDialog.activateWindow();
225 jakw 963
}
227 jakw 964
 
965
void SMScanner::on_tabWidget_currentChanged(int index)
966
{
967
    if(index==0){
968
        ui->calibrationCamera0Widget->fitImage();
969
        ui->calibrationCamera1Widget->fitImage();
970
    }
971
    if(index==1){
972
        ui->captureCamera0Widget->fitImage();
973
        ui->captureCamera1Widget->fitImage();
974
    }
975
}
232 jakw 976
 
977
void SMScanner::on_calibrationListWidget_currentRowChanged(int currentRow)
978
{
979
    std::cout << "Current row: " << currentRow << std::endl;
980
    on_calibrationListWidget_itemSelectionChanged();
981
}
244 jakw 982
 
983
void SMScanner::on_actionEdit_Configuration_File_triggered()
984
{
985
    QDesktopServices::openUrl(QUrl::fromLocalFile(settings.fileName()));
986
}
255 - 987
 
988
void SMScanner::on_exportCheckBox_toggled(bool checked)
989
{
990
    ui->exportPathLineEdit->setEnabled(checked);
991
    ui->browseExportPushButton->setEnabled(checked);
992
    ui->exportedPathLabel->setEnabled(checked);
993
    ui->deleteExportCheckBox->setEnabled(checked);
994
}
995
 
996
void SMScanner::on_browseExportPushButton_clicked()
997
{
998
    QString dirName = QFileDialog::getExistingDirectory(this, "Export frame sequences", QString());
999
    ui->exportPathLineEdit->setText(dirName);
1000
}