MainWindow.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /*
  2. * This file is part of the Carpe Diem Active Learning Software,
  3. * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
  4. *
  5. * For licensing information, see the LICENSE file included with this project.
  6. */
  7. #include <QMessageBox>
  8. #include <QDirIterator>
  9. #include <sstream>
  10. #include <QInputDialog>
  11. #include <QFileDialog>
  12. #include <QIcon>
  13. #include <QtConcurrent/QtConcurrent>
  14. #include <fstream>
  15. #include <random>
  16. #include "MainWindow.h"
  17. #include "ui_MainWindow.h"
  18. #include "CDUtils.h"
  19. #include "ProjectDetailsDialog.h"
  20. #include "ImportLabeledDataDialog.h"
  21. #include "SelectSegmentDialog.h"
  22. #include "SelectScoredSegmentDialog.h"
  23. #include "ProgressDialog.h"
  24. #include "LabelHypothesesDialog.h"
  25. #include "ClassManagerDialog.h"
  26. MainWindow::MainWindow(QWidget *parent) :
  27. QMainWindow(parent),
  28. ui(new Ui::MainWindow),
  29. project(this, this, this)
  30. {
  31. ui->setupUi(this);
  32. OnProjectStateUpdate();
  33. }
  34. MainWindow::~MainWindow()
  35. {
  36. delete ui;
  37. }
  38. void MainWindow::on_actionNew_triggered()
  39. {
  40. ProjectDetailsDialog d(this);
  41. if(d.exec()) {
  42. std::string architecture_filename, model_filename, project_folder, project_name;
  43. d.GetOptions(architecture_filename, model_filename, project_folder);
  44. bool result = project.New(architecture_filename, model_filename, project_name, project_folder);
  45. if(!result) {
  46. QMessageBox::critical(this, tr("Error"), tr("Could not create new project! See log for details."), QMessageBox::Ok);
  47. }
  48. }
  49. }
  50. void MainWindow::OnClassUpdate() {
  51. // Do nothing here
  52. }
  53. void MainWindow::OnProjectStateUpdate() {
  54. // Buttons
  55. if(project.state == Project::LOADED) {
  56. ui->predictMultipleButton->setEnabled(true);
  57. if(project.known_samples_->GetSampleCount() > 0 && project.update_set_->GetSampleCount() > 0) {
  58. ui->updateModelButton->setEnabled(true);
  59. } else {
  60. ui->updateModelButton->setEnabled(false);
  61. }
  62. ui->importDataButton->setEnabled(true);
  63. ui->actionImport_New_Data->setEnabled(true);
  64. ui->actionImport_New_Data_Recursively->setEnabled(true);
  65. ui->actionImport_Labeled_Data->setEnabled(true);
  66. ui->actionReview_Known_Data->setEnabled(true);
  67. ui->actionReview_Labeled_Data->setEnabled(true);
  68. ui->actionReview_New_Data->setEnabled(true);
  69. if(project.new_set_->GetSampleCount() > 0) {
  70. ui->labelDataButton->setEnabled(true);
  71. } else {
  72. ui->labelDataButton->setEnabled(false);
  73. }
  74. ui->clearPredictionsButton->setEnabled(prediction_files_.size() > 0);
  75. ui->exportCSVButton->setEnabled(prediction_files_.size() > 0);
  76. ui->actionSave->setEnabled(true);
  77. ui->actionSave_As->setEnabled(true);
  78. ui->actionNew->setEnabled(false);
  79. ui->actionLoad->setEnabled(false);
  80. ui->actionSet_Active_Learning_Policy->setEnabled(true);
  81. ui->actionManage_Classes->setEnabled(true);
  82. ui->actionSave_Snapshot->setEnabled(true);
  83. ui->actionLoad_Snapshot->setEnabled(true);
  84. } else {
  85. ui->predictMultipleButton->setEnabled(false);
  86. ui->updateModelButton->setEnabled(false);
  87. ui->importDataButton->setEnabled(false);
  88. ui->actionImport_New_Data->setEnabled(false);
  89. ui->actionImport_New_Data_Recursively->setEnabled(false);
  90. ui->actionImport_Labeled_Data->setEnabled(false);
  91. ui->actionReview_Known_Data->setEnabled(false);
  92. ui->actionReview_Labeled_Data->setEnabled(false);
  93. ui->actionReview_New_Data->setEnabled(false);
  94. ui->labelDataButton->setEnabled(false);
  95. ui->clearPredictionsButton->setEnabled(false);
  96. ui->exportCSVButton->setEnabled(false);
  97. ui->actionSave->setEnabled(false);
  98. ui->actionSave_As->setEnabled(false);
  99. ui->actionNew->setEnabled(project.state == Project::NOTHING);
  100. ui->actionLoad->setEnabled(project.state == Project::NOTHING);
  101. ui->actionSet_Active_Learning_Policy->setEnabled(false);
  102. ui->actionManage_Classes->setEnabled(false);
  103. ui->actionSave_Snapshot->setEnabled(false);
  104. ui->actionLoad_Snapshot->setEnabled(false);
  105. }
  106. // Model status
  107. if(project.state == Project::LOADED) {
  108. std::stringstream ss;
  109. ss << "Model loaded." << std::endl;
  110. // Number of known images
  111. if(project.known_samples_->GetSampleCount() > 0) {
  112. ss << "Known examples: " << project.known_samples_->GetSampleCount() << std::endl;
  113. } else {
  114. ss << "You need to import the model's known samples before updating the model." << std::endl;
  115. }
  116. if(project.new_set_->GetSampleCount() > 0) {
  117. ss << "Unlabeled examples: " << project.new_set_->GetSampleCount() << std::endl;
  118. } else {
  119. ss << "No unlabeled examples, please import unlabeled data to get started." << std::endl;
  120. }
  121. if(project.update_set_->GetSampleCount() > 0) {
  122. ss << "Ready to update: " << project.update_set_->GetSampleCount();
  123. } else {
  124. ss << "No labeled samples available, model cannot be updated.";
  125. }
  126. ui->modelStatusText->setText(QString::fromStdString(ss.str()));
  127. } else if(project.state == Project::NOTHING) {
  128. ui->modelStatusText->setText("Welcome to Carpe Diem!\nTo get started, please create a new project or load an existing project using the menu options.");
  129. } else if(project.state == Project::FAILED) {
  130. ui->modelStatusText->setText("Please restart Carpe Diem.");
  131. }
  132. // Window title
  133. if(project.state == Project::LOADED) {
  134. std::stringstream ss;
  135. ss << "Carpe Diem - " << project.project_name_;
  136. setWindowTitle(QString::fromStdString(ss.str()));
  137. } else {
  138. setWindowTitle("Carpe Diem");
  139. }
  140. }
  141. void MainWindow::on_actionLoad_YOLO_Small_VOC_triggered()
  142. {
  143. #ifdef W_OS_LINUX
  144. bool result = project.New("/home/clemens/.cn24/yolo/base_all.json", "/home/clemens/.cn24/yolo/yolo-small.CNParamX", "YOLO-Small", "/home/clemens/.cn24/proj");
  145. #else
  146. bool result = project.New("C:/Users/clemens/cn24/base_all.json", "C:/Users/clemens/cn24/yolo-small.CNParamX", "YOLO-Small", "C:/Users/clemens/cn24/proj");
  147. #endif
  148. if(!result) {
  149. QMessageBox::critical(this, tr("Error"), tr("Could not create new project! See log for details."), QMessageBox::Ok);
  150. }
  151. }
  152. void MainWindow::on_predictMultipleButton_clicked()
  153. {
  154. QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open Images to Predict"), QString(), tr("Image (*.jpg *.jpeg *.png)"));
  155. ui->progressBar->setValue(0);
  156. int old_list_count = ui->predictionList->count();
  157. for(unsigned int i = 0; i < fileNames.size(); i++) {
  158. QString fileName = fileNames[i];
  159. std::vector<Conv::BoundingBox> predictions;
  160. project.Predict(fileName.toStdString(), predictions);
  161. ui->predictionList->addItem(new QListWidgetItem(QIcon(fileName), CDUtils::FileBaseName(fileName)));
  162. prediction_boxes_.push_back(predictions);
  163. prediction_files_.push_back(fileName);
  164. int progress = (100 * (i + 1)) / fileNames.size();
  165. ui->progressBar->setValue(progress);
  166. }
  167. if(fileNames.size() > 0)
  168. ui->predictionList->setCurrentRow(old_list_count);
  169. OnProjectStateUpdate();
  170. ui->progressBar->setValue(100);
  171. ui->predictionList->focusWidget();
  172. }
  173. void MainWindow::on_predictionList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
  174. {
  175. if(project.state != Project::LOADED)
  176. return;
  177. if(current != nullptr) {
  178. int index = ui->predictionList->row(current);
  179. if(index >= 0 && index < prediction_boxes_.size()) {
  180. ui->imageDisplay->DisplayImage(prediction_files_[index].toStdString(), prediction_boxes_[index], project.class_manager_);
  181. }
  182. }
  183. }
  184. void MainWindow::on_clearPredictionsButton_clicked()
  185. {
  186. ui->predictionList->clear();
  187. prediction_boxes_.clear();
  188. prediction_files_.clear();
  189. OnProjectStateUpdate();
  190. ui->imageDisplay->ClearImage();
  191. }
  192. void MainWindow::on_importLabeledDataButton_clicked()
  193. {
  194. auto fileName = QFileDialog::getOpenFileName(this, tr("Open Labeled Dataset"), QString(), tr("Dataset (*.json)"));
  195. if(fileName.length() > 0) {
  196. ImportLabeledDataDialog d(this, fileName.toStdString(), &project);
  197. d.exec();
  198. OnProjectStateUpdate();
  199. }
  200. }
  201. void MainWindow::on_actionSave_triggered()
  202. {
  203. bool result = project.Save();
  204. if(!result) {
  205. QMessageBox::critical(this, tr("Error"), tr("Could not save project! See log for details."), QMessageBox::Ok);
  206. }
  207. }
  208. void MainWindow::on_actionLoad_triggered()
  209. {
  210. auto folderName = QFileDialog::getExistingDirectory(this, tr("Select Project Folder"));
  211. if(folderName.length() > 0) {
  212. bool result = project.Load(folderName.toStdString());
  213. if(!result) {
  214. QMessageBox::critical(this, tr("Error"), tr("Could not load project! See log for details."), QMessageBox::Ok);
  215. }
  216. }
  217. }
  218. void MainWindow::on_importDataButton_clicked()
  219. {
  220. QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open Images to Import As Unlabeled Data"), QString(), tr("Image (*.jpg *.jpeg *.png)"));
  221. std::mt19937 generator;
  222. ui->progressBar->setValue(0);
  223. int maxprogress = fileNames.size();
  224. int i = 0;
  225. while(fileNames.size() > 0) {
  226. std::uniform_int_distribution<int> dist(0, fileNames.size() - 1);
  227. int file_index = dist(generator);
  228. QString fileName = fileNames[file_index];
  229. fileNames.removeAt(file_index);
  230. bool result = project.AddSample(fileName.toStdString());
  231. if(!result) {
  232. QMessageBox::critical(this, tr("Error"), tr("Could not import unlabeled sample! See log for details."), QMessageBox::Ok);
  233. OnProjectStateUpdate();
  234. break;
  235. }
  236. int progress = (100 * (i++ + 1)) / maxprogress;
  237. ui->progressBar->setValue(progress);
  238. }
  239. OnProjectStateUpdate();
  240. ui->progressBar->setValue(100);
  241. }
  242. void MainWindow::on_actionImport_New_Data_Recursively_triggered()
  243. {
  244. auto folderName = QFileDialog::getExistingDirectory(this, tr("Select New Data Folder"));
  245. if(folderName.length() <= 0)
  246. return;
  247. QDirIterator iterator(folderName, QStringList() << "*.jpg" << "*.png", QDir::Files, QDirIterator::Subdirectories);
  248. QStringList fileNames;
  249. while(iterator.hasNext())
  250. fileNames << iterator.next();
  251. std::mt19937 generator;
  252. ui->progressBar->setValue(0);
  253. int maxprogress = fileNames.size();
  254. int i = 0;
  255. while(fileNames.size() > 0) {
  256. std::uniform_int_distribution<int> dist(0, fileNames.size() - 1);
  257. int file_index = dist(generator);
  258. QString fileName = fileNames[file_index];
  259. fileNames.removeAt(file_index);
  260. bool result = project.AddSample(fileName.toStdString());
  261. if(!result) {
  262. QMessageBox::critical(this, tr("Error"), tr("Could not import unlabeled sample! See log for details."), QMessageBox::Ok);
  263. OnProjectStateUpdate();
  264. break;
  265. }
  266. int progress = (100 * (i++ + 1)) / maxprogress;
  267. ui->progressBar->setValue(progress);
  268. }
  269. OnProjectStateUpdate();
  270. ui->progressBar->setValue(100);
  271. }
  272. void MainWindow::on_actionImport_New_Data_triggered()
  273. {
  274. on_importDataButton_clicked();
  275. }
  276. void MainWindow::on_actionImport_Labeled_Data_triggered()
  277. {
  278. on_importLabeledDataButton_clicked();
  279. }
  280. void MainWindow::AddSegmentImagesToList(Conv::Segment *segment) {
  281. if(segment != nullptr) {
  282. unsigned int sample_count = segment->GetSampleCount();
  283. if(sample_count > 100) {
  284. QMessageBox::StandardButton result = QMessageBox::information(this, tr("Information"), tr("You have chosen more than 100 images for review. Do you want to review all images? Select No to review only the first 100."), QMessageBox::Yes, QMessageBox::No);
  285. if(result == QMessageBox::No)
  286. sample_count = 100;
  287. }
  288. for(unsigned int sample = 0; sample < sample_count; sample++) {
  289. Conv::JSON& sample_json = segment->GetSample(sample);
  290. std::vector<Conv::BoundingBox> predictions;
  291. Conv::Segment::CopyDetectionMetadata(sample_json, 1, 1, *(project.class_manager_), &predictions);
  292. for(unsigned int b = 0; b < predictions.size(); b++) {
  293. // Notify the view that these coordinates are not normalized
  294. predictions[b].flag1 = true;
  295. }
  296. QString fileName = QString::fromStdString(sample_json["image_rpath"]);
  297. ui->predictionList->addItem(new QListWidgetItem(QIcon(fileName), CDUtils::FileBaseName(fileName)));
  298. prediction_boxes_.push_back(predictions);
  299. prediction_files_.push_back(fileName);
  300. }
  301. }
  302. OnProjectStateUpdate();
  303. }
  304. void MainWindow::on_actionReview_Known_Data_triggered()
  305. {
  306. SelectSegmentDialog d(this, project.known_samples_, &project);
  307. if(d.exec()) {
  308. Conv::Segment* segment = d.GetSegment();
  309. AddSegmentImagesToList(segment);
  310. }
  311. OnProjectStateUpdate();
  312. }
  313. void MainWindow::on_actionReview_Labeled_Data_triggered()
  314. {
  315. SelectSegmentDialog d(this, project.update_set_, &project);
  316. if(d.exec()) {
  317. Conv::Segment* segment = d.GetSegment();
  318. AddSegmentImagesToList(segment);
  319. }
  320. OnProjectStateUpdate();
  321. }
  322. void MainWindow::on_actionReview_New_Data_triggered()
  323. {
  324. SelectScoredSegmentDialog d(this, &project);
  325. if(d.exec()) {
  326. Conv::Segment* segment = d.GetSegment();
  327. AddSegmentImagesToList(segment);
  328. }
  329. OnProjectStateUpdate();
  330. }
  331. void MainWindow::on_updateModelButton_clicked()
  332. {
  333. QString description = tr("Updating model...");
  334. ProgressDialog d(this, &project, description);
  335. QFuture<void> future = QtConcurrent::run(&project, &Project::UpdateModel, (Project::ProjectProgressHandler*)&d);
  336. if(!d.exec(&future)) {
  337. QMessageBox::critical(this, tr("Error"), tr("Could not update model! See log for details."), QMessageBox::Ok);
  338. } else {
  339. // Move update segments to known segments
  340. while(project.update_set_->GetSegmentCount() > 0) {
  341. Conv::Segment* segment = project.update_set_->GetSegment(0);
  342. project.known_samples_->AddSegment(segment);
  343. project.update_set_->RemoveSegment(0);
  344. }
  345. project.SaveSnapshot();
  346. }
  347. OnProjectStateUpdate();
  348. }
  349. void MainWindow::on_labelDataButton_clicked()
  350. {
  351. QString description = tr("Selecting best batch from new data...");
  352. ProgressDialog d(this, &project, description);
  353. QFuture<void> future = QtConcurrent::run(&project, &Project::UpdateScores, (Project::ProjectProgressHandler*)&d);
  354. if(!d.exec(&future)) {
  355. QMessageBox::critical(this, tr("Error"), tr("Could not select best batch! See log for details."), QMessageBox::Ok);
  356. } else {
  357. // Segments are scored, select best segment
  358. Conv::Segment* best_segment =project.new_set_->GetSegment(0);
  359. Conv::datum best_score = 0;
  360. int best_segment_index = 0;
  361. for(unsigned int s = 0; s < project.new_set_->GetSegmentCount(); s++) {
  362. if(project.new_set_->GetSegment(s)->score > best_score) {
  363. best_score = project.new_set_->GetSegment(s)->score;
  364. best_segment = project.new_set_->GetSegment(s);
  365. best_segment_index = s;
  366. }
  367. }
  368. // Predict segment
  369. QString description2 = tr("Generating predictions on new data...");
  370. ProgressDialog d2(this, &project, description2);
  371. std::vector<std::vector<Conv::BoundingBox>> boxes;
  372. std::vector<std::string> filenames;
  373. // Start prediction future
  374. QFuture<void> future2 = QtConcurrent::run(&project, &Project::PredictSegment, (Project::ProjectProgressHandler*)&d2, best_segment, &filenames, &boxes);
  375. if(!d2.exec(&future2)) {
  376. QMessageBox::critical(this, tr("Error"), tr("Could not predict new samples! See log for details."), QMessageBox::Ok);
  377. } else {
  378. // Display labeling dialog
  379. LabelHypothesesDialog d3(this, boxes, filenames, project.class_manager_);
  380. if(d3.exec()) {
  381. if(d3.StoreInSegment(best_segment)) {
  382. project.new_set_->RemoveSegment(best_segment_index);
  383. project.update_set_->AddSegment(best_segment);
  384. } else {
  385. // JSON error
  386. QMessageBox::critical(this, tr("Error"), tr("Could not save labels! See log for details."), QMessageBox::Ok);
  387. }
  388. } else {
  389. // User interrupted labeling, stop
  390. QMessageBox::critical(this, tr("Error"), tr("Labeling was interrupted."), QMessageBox::Ok);
  391. }
  392. }
  393. }
  394. OnProjectStateUpdate();
  395. }
  396. void MainWindow::on_actionSet_Active_Learning_Policy_triggered()
  397. {
  398. QStringList list({"wholeimagediff", "wholeimage1vs2", "proposalsum1vs2", "proposalmax1vs2","proposalavg1vs2"});
  399. QString result = QInputDialog::getItem(this, tr("Select Active Learning Policy"), tr("Active Learning Policy:"), list, 0, false);
  400. if(result.length() > 0) {
  401. project.SetActiveLearningPolicy(result.toStdString());
  402. OnProjectStateUpdate();
  403. }
  404. }
  405. void MainWindow::on_actionSet_New_Sample_Batch_Size_triggered()
  406. {
  407. int result = QInputDialog::getInt(this, tr("Select New Sample Batch Size"), tr("New Sample Batch Size:"), 32, 1, 320);
  408. if(result > 0) {
  409. project.SetNewBatchSize(result);
  410. OnProjectStateUpdate();
  411. }
  412. }
  413. void MainWindow::on_exportCSVButton_clicked()
  414. {
  415. QString targetFile = QFileDialog::getSaveFileName(this, tr("Select a CSV File"), QString(), tr("CSV File (*.csv)"));
  416. if(targetFile.length() > 0) {
  417. std::string filename = targetFile.toStdString();
  418. std::ofstream csv_output(filename, std::ios::out);
  419. csv_output << "FileName;Class;CenterX;CenterY;Width;Height" << std::endl;
  420. ui->progressBar->setValue(0);
  421. for(unsigned int s = 0; s < prediction_boxes_.size(); s++) {
  422. std::vector<Conv::BoundingBox>& sample_boxes = prediction_boxes_[s];
  423. QImage image(prediction_files_[s]);
  424. Conv::datum width = image.width(), height = image.height();
  425. std::string filename = prediction_files_[s].toStdString();
  426. // One CSV line per bounding box
  427. for(unsigned int b = 0; b < sample_boxes.size(); b++) {
  428. Conv::BoundingBox& box = sample_boxes[b];
  429. csv_output << filename << ";";
  430. csv_output << project.class_manager_->GetClassInfoById(box.c).first << ";";
  431. if(box.flag1) {
  432. // Coordinates are not normalized, direct output
  433. csv_output << box.x << ";" << box.y << ";" << box.w << ";" << box.h;
  434. } else {
  435. // Coordinates are normalized, find out image proportions
  436. csv_output << box.x * width << ";" << box.y * height << ";" << box.w * width << ";" << box.h * height;
  437. }
  438. csv_output << std::endl;
  439. }
  440. ui->progressBar->setValue((100 * (s+1)) / prediction_boxes_.size());
  441. }
  442. ui->progressBar->setValue(0);
  443. }
  444. }
  445. void MainWindow::on_actionAbout_Carpe_Diem_triggered()
  446. {
  447. QMessageBox::information(this, tr("About Carpe Diem"), tr("Carpe Diem Version 1.0.0-alpha1\n\nCopyright (C) 2017 Clemens-Alexander Brust\nE-Mail:ikosa.de@gmail.com"), QMessageBox::Ok);
  448. }
  449. void MainWindow::on_actionManage_Classes_triggered()
  450. {
  451. ClassManagerDialog d(this, project.class_manager_, &project);
  452. d.exec();
  453. }
  454. void MainWindow::on_actionLoad_Snapshot_triggered()
  455. {
  456. QString fileName = QFileDialog::getOpenFileName(this, tr("Load Snapshot"), QString::fromStdString(project.project_folder_), tr("CN24 Model (*.CNParamX)"));
  457. if(fileName.length() > 0) {
  458. std::string file_name = fileName.toStdString();
  459. if(!project.LoadSnapshot(file_name)) {
  460. QMessageBox::warning(this, tr("Error"), tr("Could not load snapshot! See log for details."));
  461. }
  462. OnProjectStateUpdate();
  463. }
  464. }
  465. void MainWindow::on_actionSave_Snapshot_triggered()
  466. {
  467. project.SaveSnapshot();
  468. }