浏览代码

[general] Initial commit

Clemens-Alexander Brust 9 年之前
当前提交
307b73e2de

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+*.pro.user
+*.pro.user.*
+build-*/

+ 5 - 0
CONTRIBUTORS

@@ -0,0 +1,5 @@
+This program uses the Qt library for its GUI. You can find out more
+about Qt at https://www.qt.io/
+
+For other contributors, see the CONTRIBUTORS file for the CN24
+project.

+ 12 - 0
LICENSE

@@ -0,0 +1,12 @@
+Copyright (c) 2017, Clemens-Alexander Brust
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 1 - 0
README.md

@@ -0,0 +1 @@
+# Carpe Diem

+ 100 - 0
src/AnnotatedImageView.cpp

@@ -0,0 +1,100 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "AnnotatedImageView.h"
+#include <QGraphicsView>
+#include <QGraphicsSimpleTextItem>
+#include <QPixmap>
+#include <QImage>
+
+AnnotatedImageView::AnnotatedImageView(QWidget* parent) : QGraphicsView(parent)
+{
+  scene_ = new QGraphicsScene(this);
+  setScene(scene_);
+  rect_ = QRectF(0, 0, 0, 0);
+}
+
+void AnnotatedImageView::ClearImage() {
+  scene_->clear();
+  if(image_ != nullptr) {
+    delete image_;
+    image_ = nullptr;
+  }
+  rect_ = QRectF(0, 0, 0, 0);
+}
+
+void AnnotatedImageView::DisplayImage(std::string filename, std::vector<Conv::BoundingBox> bounding_boxes, Conv::ClassManager *class_manager) {
+  scene_->clear();
+  if(image_ != nullptr)
+    delete image_;
+
+  image_ = new QImage(QString::fromStdString(filename));
+  item_ = new QGraphicsPixmapItem(QPixmap::fromImage(*image_));
+  scene_->addItem(item_);
+  scene_->setSceneRect(item_->boundingRect());
+
+  Conv::datum width = image_->width(), height = image_->height();
+  Conv::datum x1 = 0, x2 = width, y1 = 0, y2 = height;
+
+
+  for(unsigned int b = 0; b < bounding_boxes.size(); b++) {
+    Conv::BoundingBox box = bounding_boxes[b];
+
+    if(!box.flag1) {
+      // Denormalize box
+      box.w *= width; box.x *= width;
+      box.h *= height; box.y *= height;
+    }
+
+    // Decenter box
+    box.x -= box.w / 2.0;
+    box.y -= box.h / 2.0;
+
+    if(box.x < x1) x1 = box.x;
+    if(box.x + box.w > x2) x2 = box.x + box.w;
+    if(box.y < y1) y1 = box.y;
+
+    // Print class label
+    QString class_name = QString::fromStdString(class_manager->GetClassInfoById(box.c).first);
+    auto* text_item = new QGraphicsSimpleTextItem(class_name);
+    if(image_->width() >= 800) {
+      text_item->setFont(QFont(tr("Helvetica"), 24));
+    } else {
+      text_item->setFont(QFont(tr("Helvetica"), 16));
+    }
+    text_item->setPos(box.x, box.y + box.h);
+    qreal text_width = text_item->boundingRect().width();
+    qreal text_height = text_item->boundingRect().height();
+    if(box.y + box.h + text_height > y2) y2 = box.y + box.h + text_height;
+
+    // Draw under class label
+    scene_->addRect(box.x, box.y + box.h, text_width, text_height, QPen(), QBrush(QColor::fromRgbF(1,1,1,0.9)));
+    scene_->addItem(text_item);
+
+    // Draw box
+    scene_->addRect(box.x, box.y, box.w, box.h,QPen(QBrush(QColor::fromRgbF(0,0,0,1)), 3.0), QBrush(QColor::fromRgbF(1,1,1,0.2)));
+
+  }
+
+  if(image_->width() >= 800) {
+    const qreal border = 35;
+    QRectF bounding_rect(x1 - border, y1 - border, (2.0 * border) + x2 - x1, (2.0 * border) + y2 - y1);
+    rect_ = bounding_rect;
+    RefitDisplay();
+  } else {
+    rect_ = QRectF(0, 0, 0, 0);
+    RefitDisplay();
+  }
+}
+
+void AnnotatedImageView::RefitDisplay() {
+  if(rect_.x() == 0 && rect_.y() == 0 && rect_.width() == 0 && rect_.height() == 0) {
+    this->resetTransform();
+  } else {
+    this->fitInView(rect_, Qt::KeepAspectRatio);
+  }
+}

+ 36 - 0
src/AnnotatedImageView.h

@@ -0,0 +1,36 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef ANNOTATEDIMAGEVIEW_H
+#define ANNOTATEDIMAGEVIEW_H
+
+#include <QGraphicsView>
+#include <QGraphicsScene>
+#include <QGraphicsPixmapItem>
+#include <QImage>
+#include <QRectF>
+#include <QResizeEvent>
+
+#include <cn24.h>
+
+class AnnotatedImageView : public QGraphicsView
+{
+public:
+  AnnotatedImageView(QWidget* parent);
+
+  void DisplayImage(std::string filename, std::vector<Conv::BoundingBox> bounding_boxes, Conv::ClassManager* class_manager);
+  void ClearImage();
+  void RefitDisplay();
+private:
+  void resizeEvent(QResizeEvent* evt) { RefitDisplay(); QGraphicsView::resizeEvent(evt); }
+  QGraphicsScene* scene_ = nullptr;
+  QGraphicsPixmapItem* item_ = nullptr;
+  QImage* image_ = nullptr;
+  QRectF rect_;
+};
+
+#endif // ANNOTATEDIMAGEVIEW_H

+ 25 - 0
src/CDUtils.cpp

@@ -0,0 +1,25 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "CDUtils.h"
+
+#include <QDir>
+
+CDUtils::CDUtils()
+{
+
+}
+
+QString CDUtils::FileBaseName(QString &path) {
+  if(path.contains('/')) {
+    return path.split('/', QString::SkipEmptyParts).last();
+  } else if(path.contains(QDir::separator())) {
+    return path.split(QDir::separator(), QString::SkipEmptyParts).last();
+  } else {
+    return path;
+  }
+}

+ 21 - 0
src/CDUtils.h

@@ -0,0 +1,21 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef CDUTILS_H
+#define CDUTILS_H
+
+#include <QString>
+#include <string>
+
+class CDUtils
+{
+public:
+  CDUtils();
+  static QString FileBaseName(QString& path);
+};
+
+#endif // CDUTILS_H

+ 108 - 0
src/ImportLabeledDataDialog.cpp

@@ -0,0 +1,108 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "ImportLabeledDataDialog.h"
+#include "ui_ImportLabeledDataDialog.h"
+
+#include <string>
+#include <fstream>
+
+ImportLabeledDataDialog::ImportLabeledDataDialog(QWidget *parent, std::string filename, Project* project) :
+  QDialog(parent), project(project),
+  ui(new Ui::ImportLabeledDataDialog)
+{
+  ui->setupUi(this);
+
+  // Load file
+  std::ifstream set_file(filename, std::ios::in);
+  if(!set_file.good()) {
+    this->reject();
+  } else {
+    Conv::JSON set_json = Conv::JSON::parse(set_file);
+    set = new Conv::SegmentSet("Labeled Data");
+    set->Deserialize(set_json);
+    UpdateLists();
+  }
+}
+
+ImportLabeledDataDialog::~ImportLabeledDataDialog()
+{
+  for(unsigned int s = 0; s < set->GetSegmentCount(); s++) {
+    Conv::Segment* segment = set->GetSegment(s);
+    set->RemoveSegment(s);
+    delete segment;
+  }
+
+  delete set;
+
+  delete ui;
+}
+
+void ImportLabeledDataDialog::UpdateLists() {
+  ui->knownDataList->clear();
+  ui->importDataList->clear();
+  ui->updateDataList->clear();
+
+  for(unsigned int s = 0; s < set->GetSegmentCount(); s++) {
+    Conv::Segment* segment = set->GetSegment(s);
+    ui->importDataList->addItem(QString::fromStdString(segment->name));
+  }
+
+  for(unsigned int s = 0; s < project->known_samples_->GetSegmentCount(); s++) {
+    Conv::Segment* segment = project->known_samples_->GetSegment(s);
+    ui->knownDataList->addItem(QString::fromStdString(segment->name));
+  }
+
+  for(unsigned int s = 0; s < project->update_set_->GetSegmentCount(); s++) {
+    Conv::Segment* segment = project->update_set_->GetSegment(s);
+    ui->updateDataList->addItem(QString::fromStdString(segment->name));
+  }
+
+  if(ui->importDataList->count() > 0) {
+    ui->importDataList->item(0)->setSelected(true);
+    ui->importDataList->setCurrentRow(0);
+  }
+
+  UpdateButtons();
+}
+
+void ImportLabeledDataDialog::UpdateButtons() {
+  ui->addToKnownButton->setEnabled(ui->importDataList->count() > 0 && ui->importDataList->selectedItems().length() > 0);
+  ui->addToUpdateButton->setEnabled(ui->importDataList->count() > 0 && ui->importDataList->selectedItems().length() > 0);
+}
+
+void ImportLabeledDataDialog::on_importDataList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
+{
+  UpdateButtons();
+}
+
+void ImportLabeledDataDialog::on_importDataList_currentRowChanged(int currentRow)
+{
+  UpdateButtons();
+}
+
+void ImportLabeledDataDialog::on_addToKnownButton_clicked()
+{
+  int segment_index = ui->importDataList->currentRow();
+  if(segment_index < 0)
+    return;
+  Conv::Segment* segment = set->GetSegment(segment_index);
+  set->RemoveSegment(segment_index);
+
+  project->known_samples_->AddSegment(segment);
+  UpdateLists();
+}
+
+void ImportLabeledDataDialog::on_addToUpdateButton_clicked()
+{
+  int segment_index = ui->importDataList->currentRow();
+  Conv::Segment* segment = set->GetSegment(segment_index);
+  set->RemoveSegment(segment_index);
+
+  project->update_set_->AddSegment(segment);
+  UpdateLists();
+}

+ 48 - 0
src/ImportLabeledDataDialog.h

@@ -0,0 +1,48 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef IMPORTLABELEDDATADIALOG_H
+#define IMPORTLABELEDDATADIALOG_H
+
+#include <QDialog>
+#include <string>
+#include <QListWidgetItem>
+
+#include <cn24.h>
+#include "Project.h"
+
+namespace Ui {
+class ImportLabeledDataDialog;
+}
+
+class ImportLabeledDataDialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+  explicit ImportLabeledDataDialog(QWidget *parent, std::string filename, Project* project);
+  ~ImportLabeledDataDialog();
+
+private slots:
+  void on_importDataList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
+
+  void on_importDataList_currentRowChanged(int currentRow);
+
+  void on_addToKnownButton_clicked();
+
+  void on_addToUpdateButton_clicked();
+
+private:
+  void UpdateLists();
+  void UpdateButtons();
+
+  Ui::ImportLabeledDataDialog *ui;
+  Conv::SegmentSet* set = nullptr;
+  Project* project = nullptr;
+};
+
+#endif // IMPORTLABELEDDATADIALOG_H

+ 124 - 0
src/ImportLabeledDataDialog.ui

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ImportLabeledDataDialog</class>
+ <widget class="QDialog" name="ImportLabeledDataDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1064</width>
+    <height>609</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Import Data</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_4">
+     <item>
+      <widget class="QGroupBox" name="groupBox">
+       <property name="title">
+        <string>Known Data</string>
+       </property>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QListWidget" name="knownDataList"/>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox_2">
+       <property name="title">
+        <string>Importing Data</string>
+       </property>
+       <layout class="QVBoxLayout" name="verticalLayout">
+        <item>
+         <widget class="QListWidget" name="importDataList"/>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_2">
+          <item>
+           <widget class="QPushButton" name="addToKnownButton">
+            <property name="text">
+             <string>&lt;&lt;&lt;
+Add To Known  Data</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="addToUpdateButton">
+            <property name="text">
+             <string>&gt;&gt;&gt;
+Add To Update Data</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox_3">
+       <property name="title">
+        <string>Update Data</string>
+       </property>
+       <layout class="QHBoxLayout" name="horizontalLayout_3">
+        <item>
+         <widget class="QListWidget" name="updateDataList"/>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ImportLabeledDataDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ImportLabeledDataDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 202 - 0
src/LabelHypothesesDialog.cpp

@@ -0,0 +1,202 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "LabelHypothesesDialog.h"
+#include "ui_LabelHypothesesDialog.h"
+
+#include <QStringList>
+#include <QInputDialog>
+#include <QImage>
+#include <QDebug>
+
+LabelHypothesesDialog::LabelHypothesesDialog(QWidget *parent, std::vector<std::vector<Conv::BoundingBox>>& prediction_boxes, std::vector<std::string>& prediction_files, Conv::ClassManager* class_manager) :
+  QDialog(parent),
+  prediction_boxes_(prediction_boxes),
+  prediction_files_(prediction_files),
+  class_manager_(class_manager),
+  ui(new Ui::LabelHypothesesDialog)
+{
+  ui->setupUi(this);
+
+  // Generate decision table
+  for(unsigned int file_index = 0; file_index < prediction_boxes_.size(); file_index++) {
+    for(unsigned int prediction_index = 0; prediction_index < prediction_boxes_[file_index].size(); prediction_index++) {
+      UserDecision decision;
+      decision.file_index = file_index;
+      decision.prediction_index = prediction_index;
+      decisions_.push_back(decision);
+    }
+  }
+  LoadNext(0);
+}
+
+LabelHypothesesDialog::~LabelHypothesesDialog()
+{
+  delete ui;
+}
+
+void LabelHypothesesDialog::LoadNext(int diff) {
+  decision_index_+= diff;
+  if(decision_index_ < 0) { decision_index_ = 0; }
+  // Check if next decision exists
+  if(decision_index_ < decisions_.size()) {
+    UserDecision& decision = decisions_[decision_index_];
+    // Load prediction
+    std::vector<Conv::BoundingBox> single_prediction;
+    single_prediction.push_back(prediction_boxes_[decision.file_index][decision.prediction_index]);
+
+    ui->imageView->DisplayImage(prediction_files_[decision.file_index], single_prediction, class_manager_);
+
+    // Set "correct" button state
+    if(single_prediction[0].c == UNKNOWN_CLASS) {
+      ui->correctButton->setEnabled(false);
+    } else {
+      ui->correctButton->setEnabled(true);
+    }
+
+    if(decision_index_ > 0) {
+      ui->backButton->setEnabled(true);
+    } else {
+      ui->backButton->setEnabled(false);
+    }
+  } else {
+    // Done with all predictions
+    accept();
+  }
+}
+
+void LabelHypothesesDialog::on_correctButton_clicked()
+{
+  UserDecision& decision = decisions_[decision_index_];
+  decision.decision = UserDecision::CORRECT;
+  LoadNext();
+}
+
+void LabelHypothesesDialog::on_wrongClassButton_clicked()
+{
+  QStringList list;
+  for(Conv::ClassManager::const_iterator it = class_manager_->begin(); it != class_manager_->end(); it++) {
+    list.push_back(QString::fromStdString(it->first));
+  }
+
+  bool ok = false;
+  std::string result = QInputDialog::getItem(this, tr("Select Class"), tr("Class:"), list, 0, true, &ok).toStdString();
+  if(result.length() > 0 && ok) {
+    unsigned int class_index = class_manager_->GetClassIdByName(result);
+    if(class_index == UNKNOWN_CLASS) {
+      class_manager_->RegisterClassByName(result, 0, 1);
+      class_index = class_manager_->GetClassIdByName(result);
+    }
+    UserDecision& decision = decisions_[decision_index_];
+    decision.decision = UserDecision::WRONG_CLASS;
+    decision.correct_class = class_index;
+    LoadNext();
+  }
+}
+
+void LabelHypothesesDialog::on_incorrectButton_clicked()
+{
+  UserDecision& decision = decisions_[decision_index_];
+  decision.decision = UserDecision::INCORRECT;
+  LoadNext();
+}
+
+bool LabelHypothesesDialog::StoreInSegment(Conv::Segment *segment) {
+  // Process boxes
+  std::vector<std::vector<Conv::BoundingBox>> processed_boxes;
+  unsigned int decision_index = 0;
+  for(unsigned int sample = 0; sample < prediction_boxes_.size(); sample++) {
+    processed_boxes.push_back({});
+    std::vector<Conv::BoundingBox>* processed_sample_boxes = &(processed_boxes[sample]);
+    std::vector<Conv::BoundingBox>* sample_boxes = &(prediction_boxes_[sample]);
+
+    for(unsigned int b = 0; b < sample_boxes->size(); b++) {
+      Conv::BoundingBox box = (*sample_boxes)[b];
+      UserDecision& decision = decisions_[decision_index++];
+      switch(decision.decision) {
+      case UserDecision::CORRECT:
+        break;
+      case UserDecision::WRONG_CLASS:
+        box.c = decision.correct_class;
+        break;
+      case UserDecision::MISSING:
+        qDebug() << "Missing decision for index " << decision_index << "(" << decision.file_index << ":" << decision.prediction_index << ")";
+        return false;
+      case UserDecision::INCORRECT:
+        continue;
+      }
+
+      processed_sample_boxes->push_back(box);
+    }
+  }
+
+  // First, do non-maximum suppression
+  for(unsigned int sample = 0; sample < processed_boxes.size(); sample++) {
+    if(processed_boxes[sample].size() > 1) {
+      std::vector<Conv::BoundingBox>* sample_boxes = &(processed_boxes[sample]);
+
+      std::sort(sample_boxes->begin(), sample_boxes->end(), Conv::BoundingBox::CompareScore);
+      for (unsigned int b1 = 0; b1 < (sample_boxes->size() - 1); b1++) {
+        Conv::BoundingBox &box1 = (*sample_boxes)[b1];
+        for (unsigned int b2 = b1 + 1; b2 < sample_boxes->size(); b2++) {
+          Conv::BoundingBox &box2 = (*sample_boxes)[b2];
+          if (box2.c == box1.c && box2.score > box1.score) {
+            if (box1.IntersectionOverUnion(&box2) > (Conv::datum) 0.5)
+              box1.score = (Conv::datum) 0;
+          }
+        }
+      }
+
+      sample_boxes->erase(std::remove_if(sample_boxes->begin(), sample_boxes->end(),
+                                         [](Conv::BoundingBox &box1) { return box1.score == (Conv::datum) 0; }),
+                          sample_boxes->end());
+    }
+  }
+
+  // Then, check file names for sanity
+  for(unsigned int sample = 0; sample < processed_boxes.size(); sample++) {
+    Conv::JSON& sample_json = segment->GetSample(sample);
+
+    std::string stored_path = sample_json["image_rpath"];
+    if(stored_path.compare(prediction_files_[sample]) != 0) {
+      LOGERROR << "Filenames don't match! " << stored_path << " vs " << prediction_files_[sample];
+      return false;
+    }
+  }
+
+  // If everything checks out, store the boxes in the segment
+  for(unsigned int sample = 0; sample < processed_boxes.size(); sample++) {
+    Conv::JSON& sample_json = segment->GetSample(sample);
+    Conv::JSON boxes_json = Conv::JSON::array();
+
+    // Load image for width and height
+    QImage image(QString::fromStdString(prediction_files_[sample]));
+    Conv::datum width = image.width(); Conv::datum height = image.height();
+
+    // Loop through all boxes and JSONify them
+    for(unsigned int box = 0; box < processed_boxes[sample].size(); box++) {
+      Conv::BoundingBox& predicted_box = processed_boxes[sample][box];
+      Conv::JSON box_json;
+      box_json["x"] = predicted_box.x * width; box_json["y"] = predicted_box.y * height;
+      box_json["w"] = predicted_box.w * width; box_json["h"] = predicted_box.h * height;
+
+      // Get class name
+      std::string box_class = class_manager_->GetClassInfoById(predicted_box.c).first;
+      box_json["class"] = box_class;
+
+      // Because coordinates are already normalized
+      boxes_json.push_back(box_json);
+    }
+    sample_json["boxes"] = boxes_json;
+  }
+  return true;
+}
+
+void LabelHypothesesDialog::on_backButton_clicked()
+{
+  LoadNext(-1);
+}

+ 66 - 0
src/LabelHypothesesDialog.h

@@ -0,0 +1,66 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef LABELHYPOTHESESDIALOG_H
+#define LABELHYPOTHESESDIALOG_H
+
+#include <QDialog>
+#include <cn24.h>
+#include <string>
+
+#include "Project.h"
+
+namespace Ui {
+class LabelHypothesesDialog;
+}
+
+class LabelHypothesesDialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+  class UserDecision {
+  public:
+    enum Decision {
+      MISSING,
+      CORRECT,
+      WRONG_CLASS,
+      INCORRECT
+    };
+    Decision decision = MISSING;
+    unsigned int correct_class;
+    unsigned int file_index;
+    unsigned int prediction_index;
+  };
+
+  explicit LabelHypothesesDialog(QWidget *parent, std::vector<std::vector<Conv::BoundingBox>>& prediction_boxes, std::vector<std::string>& prediction_files, Conv::ClassManager* class_manager);
+  ~LabelHypothesesDialog();
+
+  bool StoreInSegment(Conv::Segment* segment);
+
+private slots:
+  void on_correctButton_clicked();
+
+  void on_wrongClassButton_clicked();
+
+  void on_incorrectButton_clicked();
+
+  void on_backButton_clicked();
+
+private:
+  void LoadNext(int diff = 1);
+
+  std::vector<UserDecision> decisions_;
+  int decision_index_ = 0;
+
+  std::vector<std::vector<Conv::BoundingBox>> prediction_boxes_;
+  std::vector<std::string> prediction_files_;
+  Conv::ClassManager* class_manager_;
+  Ui::LabelHypothesesDialog *ui;
+};
+
+#endif // LABELHYPOTHESESDIALOG_H

+ 94 - 0
src/LabelHypothesesDialog.ui

@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LabelHypothesesDialog</class>
+ <widget class="QDialog" name="LabelHypothesesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>937</width>
+    <height>743</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Carpe Diem</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="AnnotatedImageView" name="imageView"/>
+   </item>
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Is this prediction correct? To be correct, the bounding box has to match the object with an intersection over union of more than 50% and the class has to match the object's class.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="backButton">
+       <property name="text">
+        <string>&lt;&lt; Back (B)</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="correctButton">
+       <property name="text">
+        <string>Correct (&amp;Y)</string>
+       </property>
+       <property name="shortcut">
+        <string>Y</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+       <property name="flat">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="wrongClassButton">
+       <property name="text">
+        <string>Wrong Class (&amp;C)</string>
+       </property>
+       <property name="shortcut">
+        <string>C</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="incorrectButton">
+       <property name="text">
+        <string>Incorrect (&amp;N)</string>
+       </property>
+       <property name="shortcut">
+        <string>N</string>
+       </property>
+       <property name="autoDefault">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>AnnotatedImageView</class>
+   <extends>QGraphicsView</extends>
+   <header>AnnotatedImageView.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

+ 460 - 0
src/MainWindow.cpp

@@ -0,0 +1,460 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include <QMessageBox>
+#include <sstream>
+#include <QInputDialog>
+#include <QFileDialog>
+#include <QIcon>
+#include <QtConcurrent/QtConcurrent>
+#include <fstream>
+#include <random>
+
+#include "MainWindow.h"
+#include "ui_MainWindow.h"
+
+#include "CDUtils.h"
+#include "ProjectDetailsDialog.h"
+#include "ImportLabeledDataDialog.h"
+#include "SelectSegmentDialog.h"
+#include "SelectScoredSegmentDialog.h"
+#include "ProgressDialog.h"
+#include "LabelHypothesesDialog.h"
+
+MainWindow::MainWindow(QWidget *parent) :
+  QMainWindow(parent),
+  ui(new Ui::MainWindow),
+  project(this, this, this)
+{
+  ui->setupUi(this);
+
+  OnProjectStateUpdate();
+}
+
+MainWindow::~MainWindow()
+{
+  delete ui;
+}
+
+void MainWindow::on_actionNew_triggered()
+{
+  ProjectDetailsDialog d(this);
+  if(d.exec()) {
+    std::string architecture_filename, model_filename, project_folder, project_name;
+    d.GetOptions(architecture_filename, model_filename, project_folder);
+    bool result = project.New(architecture_filename, model_filename, project_name, project_folder);
+    if(!result) {
+      QMessageBox::critical(this, tr("Error"), tr("Could not create new project! See log for details."), QMessageBox::Ok);
+    }
+  }
+}
+
+void MainWindow::OnClassUpdate() {
+  // Do nothing here
+}
+
+void MainWindow::OnProjectStateUpdate() {
+  // Buttons
+  if(project.state == Project::LOADED) {
+    ui->predictMultipleButton->setEnabled(true);
+    if(project.known_samples_->GetSampleCount() > 0 && project.update_set_->GetSampleCount() > 0) {
+      ui->updateModelButton->setEnabled(true);
+    } else {
+      ui->updateModelButton->setEnabled(false);
+    }
+    ui->importDataButton->setEnabled(true);
+    ui->actionImport_New_Data->setEnabled(true);
+    ui->actionImport_Labeled_Data->setEnabled(true);
+    ui->actionReview_Known_Data->setEnabled(true);
+    ui->actionReview_Labeled_Data->setEnabled(true);
+    ui->actionReview_New_Data->setEnabled(true);
+    if(project.new_set_->GetSampleCount() > 0) {
+      ui->labelDataButton->setEnabled(true);
+    } else {
+      ui->labelDataButton->setEnabled(false);
+    }
+    ui->clearPredictionsButton->setEnabled(prediction_files_.size() > 0);
+    ui->exportCSVButton->setEnabled(prediction_files_.size() > 0);
+    ui->actionSave->setEnabled(true);
+    ui->actionSave_As->setEnabled(true);
+    ui->actionNew->setEnabled(false);
+    ui->actionLoad->setEnabled(false);
+    ui->actionSet_Active_Learning_Policy->setEnabled(true);
+  } else {
+    ui->predictMultipleButton->setEnabled(false);
+    ui->updateModelButton->setEnabled(false);
+    ui->importDataButton->setEnabled(false);
+    ui->actionImport_New_Data->setEnabled(false);
+    ui->actionImport_Labeled_Data->setEnabled(false);
+    ui->actionReview_Known_Data->setEnabled(false);
+    ui->actionReview_Labeled_Data->setEnabled(false);
+    ui->actionReview_New_Data->setEnabled(false);
+    ui->labelDataButton->setEnabled(false);
+    ui->clearPredictionsButton->setEnabled(false);
+    ui->exportCSVButton->setEnabled(false);
+    ui->actionSave->setEnabled(false);
+    ui->actionSave_As->setEnabled(false);
+    ui->actionNew->setEnabled(project.state == Project::NOTHING);
+    ui->actionLoad->setEnabled(project.state == Project::NOTHING);
+    ui->actionSet_Active_Learning_Policy->setEnabled(false);
+  }
+
+  // Model status
+  if(project.state == Project::LOADED) {
+    std::stringstream ss;
+    ss << "Model loaded." << std::endl;
+
+
+    // Number of known images
+    if(project.known_samples_->GetSampleCount() > 0) {
+      ss << "Known examples: " << project.known_samples_->GetSampleCount() << std::endl;
+    } else {
+      ss << "You need to import the model's known samples before updating the model." << std::endl;
+    }
+    if(project.new_set_->GetSampleCount() > 0) {
+      ss << "Unlabeled examples: " << project.new_set_->GetSampleCount() << std::endl;
+    } else {
+      ss << "No unlabeled examples, please import unlabeled data to get started." << std::endl;
+    }
+    if(project.update_set_->GetSampleCount() > 0) {
+      ss << "Ready to update: " << project.update_set_->GetSampleCount();
+    } else {
+      ss << "No labeled samples available, model cannot be updated.";
+    }
+
+    ui->modelStatusText->setText(QString::fromStdString(ss.str()));
+  } else {
+    ui->modelStatusText->setText("Welcome to Carpe Diem!\nTo get started, please create a new project or load an existing project using the menu options.");
+  }
+
+  // Window title
+  if(project.state == Project::LOADED) {
+    std::stringstream ss;
+    ss << "Carpe Diem - " << project.project_name_;
+    setWindowTitle(QString::fromStdString(ss.str()));
+  } else {
+    setWindowTitle("Carpe Diem");
+  }
+}
+
+void MainWindow::on_actionLoad_YOLO_Small_VOC_triggered()
+{
+#ifdef W_OS_LINUX
+  bool result = project.New("/home/clemens/.cn24/yolo/yolo-small-aug.json", "/home/clemens/.cn24/yolo/yolo-small.CNParamX", "YOLO-Small", "/home/clemens/.cn24/proj");
+#else
+  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");
+#endif
+  if(!result) {
+    QMessageBox::critical(this, tr("Error"), tr("Could not create new project! See log for details."), QMessageBox::Ok);
+  }
+}
+
+void MainWindow::on_predictMultipleButton_clicked()
+{
+  QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open Images to Predict"), QString(), tr("Image (*.jpg *.jpeg *.png)"));
+
+  ui->progressBar->setValue(0);
+  int old_list_count = ui->predictionList->count();
+
+  for(unsigned int i = 0; i < fileNames.size(); i++) {
+    QString fileName = fileNames[i];
+
+    std::vector<Conv::BoundingBox> predictions;
+    project.Predict(fileName.toStdString(), predictions);
+
+    ui->predictionList->addItem(new QListWidgetItem(QIcon(fileName), CDUtils::FileBaseName(fileName)));
+    prediction_boxes_.push_back(predictions);
+    prediction_files_.push_back(fileName);
+
+    int progress = (100 * (i + 1)) / fileNames.size();
+    ui->progressBar->setValue(progress);
+  }
+  if(fileNames.size() > 0)
+    ui->predictionList->setCurrentRow(old_list_count);
+
+  OnProjectStateUpdate();
+  ui->progressBar->setValue(100);
+  ui->predictionList->focusWidget();
+}
+
+void MainWindow::on_predictionList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
+{
+  if(project.state != Project::LOADED)
+    return;
+
+  if(current != nullptr) {
+    int index = ui->predictionList->row(current);
+    if(index >= 0 && index < prediction_boxes_.size()) {
+      ui->imageDisplay->DisplayImage(prediction_files_[index].toStdString(), prediction_boxes_[index], project.class_manager_);
+    }
+  }
+}
+
+void MainWindow::on_clearPredictionsButton_clicked()
+{
+  ui->predictionList->clear();
+  prediction_boxes_.clear();
+  prediction_files_.clear();
+  OnProjectStateUpdate();
+
+  ui->imageDisplay->ClearImage();
+}
+
+void MainWindow::on_importLabeledDataButton_clicked()
+{
+  auto fileName = QFileDialog::getOpenFileName(this, tr("Open Labeled Dataset"), QString(), tr("Dataset (*.json)"));
+  if(fileName.length() > 0) {
+    ImportLabeledDataDialog d(this, fileName.toStdString(), &project);
+    d.exec();
+    OnProjectStateUpdate();
+  }
+}
+
+void MainWindow::on_actionSave_triggered()
+{
+  bool result = project.Save();
+  if(!result) {
+    QMessageBox::critical(this, tr("Error"), tr("Could not save project! See log for details."), QMessageBox::Ok);
+  }
+}
+
+void MainWindow::on_actionLoad_triggered()
+{
+  auto folderName = QFileDialog::getExistingDirectory(this, tr("Select Project Folder"));
+  if(folderName.length() > 0) {
+    bool result = project.Load(folderName.toStdString());
+    if(!result) {
+      QMessageBox::critical(this, tr("Error"), tr("Could not load project! See log for details."), QMessageBox::Ok);
+    }
+  }
+}
+
+void MainWindow::on_importDataButton_clicked()
+{
+  QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open Images to Import As Unlabeled Data"), QString(), tr("Image (*.jpg *.jpeg *.png)"));
+
+  std::mt19937 generator;
+  ui->progressBar->setValue(0);
+  int maxprogress = fileNames.size();
+  int i = 0;
+  while(fileNames.size() > 0) {
+    std::uniform_int_distribution<int> dist(0, fileNames.size() - 1);
+    int file_index = dist(generator);
+    QString fileName = fileNames[file_index];
+    fileNames.removeAt(file_index);
+
+    bool result =  project.AddSample(fileName.toStdString());
+    if(!result) {
+      QMessageBox::critical(this, tr("Error"), tr("Could not import unlabeled sample! See log for details."), QMessageBox::Ok);
+      OnProjectStateUpdate();
+      break;
+    }
+
+    int progress = (100 * (i++ + 1)) / maxprogress;
+    ui->progressBar->setValue(progress);
+  }
+  OnProjectStateUpdate();
+
+  ui->progressBar->setValue(100);
+}
+
+void MainWindow::on_actionImport_New_Data_triggered()
+{
+  on_importDataButton_clicked();
+}
+
+void MainWindow::on_actionImport_Labeled_Data_triggered()
+{
+  on_importLabeledDataButton_clicked();
+}
+
+void MainWindow::AddSegmentImagesToList(Conv::Segment *segment) {
+  if(segment != nullptr) {
+    unsigned int sample_count = segment->GetSampleCount();
+    if(sample_count > 100) {
+      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);
+      if(result == QMessageBox::No)
+        sample_count = 100;
+    }
+
+    for(unsigned int sample = 0; sample < sample_count; sample++) {
+      Conv::JSON& sample_json = segment->GetSample(sample);
+      std::vector<Conv::BoundingBox> predictions;
+      Conv::Segment::CopyDetectionMetadata(sample_json, 1, 1, *(project.class_manager_), &predictions);
+
+      for(unsigned int b = 0; b < predictions.size(); b++) {
+        // Notify the view that these coordinates are not normalized
+        predictions[b].flag1 = true;
+      }
+
+      QString fileName = QString::fromStdString(sample_json["image_rpath"]);
+      ui->predictionList->addItem(new QListWidgetItem(QIcon(fileName), CDUtils::FileBaseName(fileName)));
+      prediction_boxes_.push_back(predictions);
+      prediction_files_.push_back(fileName);
+    }
+  }
+  OnProjectStateUpdate();
+}
+
+void MainWindow::on_actionReview_Known_Data_triggered()
+{
+  SelectSegmentDialog d(this, project.known_samples_, &project);
+  if(d.exec()) {
+    Conv::Segment* segment = d.GetSegment();
+    AddSegmentImagesToList(segment);
+  }
+  OnProjectStateUpdate();
+}
+
+void MainWindow::on_actionReview_Labeled_Data_triggered()
+{
+  SelectSegmentDialog d(this, project.update_set_, &project);
+  if(d.exec()) {
+    Conv::Segment* segment = d.GetSegment();
+    AddSegmentImagesToList(segment);
+  }
+  OnProjectStateUpdate();
+}
+
+void MainWindow::on_actionReview_New_Data_triggered()
+{
+  SelectScoredSegmentDialog d(this, &project);
+  if(d.exec()) {
+    Conv::Segment* segment = d.GetSegment();
+    AddSegmentImagesToList(segment);
+  }
+  OnProjectStateUpdate();
+}
+
+void MainWindow::on_updateModelButton_clicked()
+{
+  QString description = tr("Updating model...");
+  ProgressDialog d(this, &project, description);
+  QFuture<void> future = QtConcurrent::run(&project, &Project::UpdateModel, (Project::ProjectProgressHandler*)&d);
+  if(!d.exec(&future)) {
+    QMessageBox::critical(this, tr("Error"), tr("Could not update model! See log for details."), QMessageBox::Ok);
+  } else {
+    // Move update segments to known segments
+    while(project.update_set_->GetSegmentCount() > 0) {
+      Conv::Segment* segment = project.update_set_->GetSegment(0);
+      project.known_samples_->AddSegment(segment);
+      project.update_set_->RemoveSegment(0);
+    }
+  }
+  OnProjectStateUpdate();
+}
+
+void MainWindow::on_labelDataButton_clicked()
+{
+  QString description = tr("Selecting best batch from new data...");
+  ProgressDialog d(this, &project, description);
+  QFuture<void> future = QtConcurrent::run(&project, &Project::UpdateScores, (Project::ProjectProgressHandler*)&d);
+  if(!d.exec(&future)) {
+    QMessageBox::critical(this, tr("Error"), tr("Could not select best batch! See log for details."), QMessageBox::Ok);
+  } else {
+    // Segments are scored, select best segment
+    Conv::Segment* best_segment =project.new_set_->GetSegment(0);
+    Conv::datum best_score = 0;
+    int best_segment_index = 0;
+    for(unsigned int s = 0; s < project.new_set_->GetSegmentCount(); s++) {
+      if(project.new_set_->GetSegment(s)->score > best_score) {
+        best_score = project.new_set_->GetSegment(s)->score;
+        best_segment =  project.new_set_->GetSegment(s);
+        best_segment_index = s;
+      }
+    }
+
+    // Predict segment
+    QString description2 = tr("Generating predictions on new data...");
+    ProgressDialog d2(this, &project, description2);
+    std::vector<std::vector<Conv::BoundingBox>> boxes;
+    std::vector<std::string> filenames;
+
+    // Start prediction future
+    QFuture<void> future2 = QtConcurrent::run(&project, &Project::PredictSegment, (Project::ProjectProgressHandler*)&d2, best_segment, &filenames, &boxes);
+    if(!d2.exec(&future2)) {
+      QMessageBox::critical(this, tr("Error"), tr("Could not predict new samples! See log for details."), QMessageBox::Ok);
+    } else {
+      // Display labeling dialog
+      LabelHypothesesDialog d3(this, boxes, filenames, project.class_manager_);
+      if(d3.exec()) {
+        if(d3.StoreInSegment(best_segment)) {
+          project.new_set_->RemoveSegment(best_segment_index);
+          project.update_set_->AddSegment(best_segment);
+        } else {
+          // JSON error
+          QMessageBox::critical(this, tr("Error"), tr("Could not save labels! See log for details."), QMessageBox::Ok);
+        }
+      } else {
+        // User interrupted labeling, stop
+        QMessageBox::critical(this, tr("Error"), tr("Labeling was interrupted."), QMessageBox::Ok);
+      }
+    }
+  }
+  OnProjectStateUpdate();
+}
+
+void MainWindow::on_actionSet_Active_Learning_Policy_triggered()
+{
+  QStringList list({"wholeimagediff", "wholeimage1vs2"});
+  QString result = QInputDialog::getItem(this, tr("Select Active Learning Policy"), tr("Active Learning Policy:"), list, 0, false);
+  if(result.length() > 0) {
+    project.SetActiveLearningPolicy(result.toStdString());
+    OnProjectStateUpdate();
+  }
+}
+
+void MainWindow::on_actionSet_New_Sample_Batch_Size_triggered()
+{
+  int result = QInputDialog::getInt(this, tr("Select New Sample Batch Size"), tr("New Sample Batch Size:"), 32, 1, 320);
+  if(result > 0) {
+    project.SetNewBatchSize(result);
+    OnProjectStateUpdate();
+  }
+}
+
+void MainWindow::on_exportCSVButton_clicked()
+{
+  QString targetFile = QFileDialog::getSaveFileName(this, tr("Select a CSV File"), QString(), tr("CSV File (*.csv)"));
+  if(targetFile.length() > 0) {
+    std::string filename = targetFile.toStdString();
+    std::ofstream csv_output(filename, std::ios::out);
+    csv_output << "FileName;Class;CenterX;CenterY;Width;Height" << std::endl;
+
+    ui->progressBar->setValue(0);
+    for(unsigned int s = 0; s < prediction_boxes_.size(); s++) {
+      std::vector<Conv::BoundingBox>& sample_boxes = prediction_boxes_[s];
+      QImage image(prediction_files_[s]);
+      Conv::datum width = image.width(), height = image.height();
+      std::string filename = prediction_files_[s].toStdString();
+
+      // One CSV line per bounding box
+      for(unsigned int b = 0; b < sample_boxes.size(); b++) {
+        Conv::BoundingBox& box = sample_boxes[b];
+        csv_output << filename << ";";
+        csv_output << project.class_manager_->GetClassInfoById(box.c).first << ";";
+
+        if(box.flag1) {
+          // Coordinates are not normalized, direct output
+          csv_output << box.x << ";" << box.y << ";" << box.w << ";" << box.h;
+        } else {
+          // Coordinates are normalized, find out image proportions
+          csv_output << box.x * width << ";" << box.y * height << ";" << box.w * width << ";" << box.h * height;
+        }
+
+        csv_output << std::endl;
+      }
+      ui->progressBar->setValue((100 * (s+1)) / prediction_boxes_.size());
+    }
+    ui->progressBar->setValue(0);
+  }
+}
+
+void MainWindow::on_actionAbout_Carpe_Diem_triggered()
+{
+  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);
+}

+ 84 - 0
src/MainWindow.h

@@ -0,0 +1,84 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QStringList>
+#include <QListWidgetItem>
+#include <QResizeEvent>
+#include <cn24.h>
+#include "Project.h"
+
+namespace Ui {
+class MainWindow;
+}
+
+class MainWindow : public QMainWindow, public Conv::ClassManager::ClassUpdateHandler, public Project::ProjectStateHandler
+{
+  Q_OBJECT
+
+public:
+  explicit MainWindow(QWidget *parent = 0);
+  ~MainWindow();
+
+  virtual void OnClassUpdate();
+  virtual void OnProjectStateUpdate();
+
+
+private slots:
+  void on_actionNew_triggered();
+
+  void on_actionLoad_YOLO_Small_VOC_triggered();
+
+  void on_predictionList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
+
+  void on_clearPredictionsButton_clicked();
+
+  void on_predictMultipleButton_clicked();
+
+  void on_actionSave_triggered();
+
+  void on_actionLoad_triggered();
+
+  void on_importDataButton_clicked();
+
+  void on_actionImport_New_Data_triggered();
+
+  void on_actionImport_Labeled_Data_triggered();
+
+  void on_actionReview_Known_Data_triggered();
+
+  void on_actionReview_Labeled_Data_triggered();
+
+  void on_actionReview_New_Data_triggered();
+
+  void on_updateModelButton_clicked();
+
+  void on_labelDataButton_clicked();
+
+  void on_actionSet_Active_Learning_Policy_triggered();
+
+  void on_actionSet_New_Sample_Batch_Size_triggered();
+
+  void on_exportCSVButton_clicked();
+
+  void on_actionAbout_Carpe_Diem_triggered();
+
+private:
+  void on_importLabeledDataButton_clicked(); // not a slot anymore
+  void AddSegmentImagesToList(Conv::Segment* segment);
+
+  Ui::MainWindow *ui;
+  Project project;
+
+  std::vector<std::vector<Conv::BoundingBox>> prediction_boxes_;
+  std::vector<QString> prediction_files_;
+};
+
+#endif // MAINWINDOW_H

+ 300 - 0
src/MainWindow.ui

@@ -0,0 +1,300 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1224</width>
+    <height>831</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Carpe Diem</string>
+  </property>
+  <widget class="QWidget" name="centralWidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <item>
+     <widget class="AnnotatedImageView" name="imageDisplay"/>
+    </item>
+    <item>
+     <widget class="QListWidget" name="predictionList">
+      <property name="maximumSize">
+       <size>
+        <width>16777215</width>
+        <height>110</height>
+       </size>
+      </property>
+      <property name="iconSize">
+       <size>
+        <width>96</width>
+        <height>54</height>
+       </size>
+      </property>
+      <property name="isWrapping" stdset="0">
+       <bool>false</bool>
+      </property>
+      <property name="resizeMode">
+       <enum>QListView::Adjust</enum>
+      </property>
+      <property name="viewMode">
+       <enum>QListView::IconMode</enum>
+      </property>
+     </widget>
+    </item>
+    <item>
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QProgressBar" name="progressBar">
+        <property name="value">
+         <number>0</number>
+        </property>
+        <property name="textVisible">
+         <bool>true</bool>
+        </property>
+        <property name="invertedAppearance">
+         <bool>false</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="exportCSVButton">
+        <property name="text">
+         <string>Export as CSV...</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="clearPredictionsButton">
+        <property name="text">
+         <string>Clear Images</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QToolBar" name="mainToolBar">
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+  </widget>
+  <widget class="QStatusBar" name="statusBar"/>
+  <widget class="QMenuBar" name="menuBar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>1224</width>
+     <height>32</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuModel">
+    <property name="title">
+     <string>Project</string>
+    </property>
+    <addaction name="actionNew"/>
+    <addaction name="actionLoad"/>
+    <addaction name="actionSave"/>
+    <addaction name="actionSave_As"/>
+    <addaction name="separator"/>
+    <addaction name="actionLoad_YOLO_Small_VOC"/>
+   </widget>
+   <widget class="QMenu" name="menuData">
+    <property name="title">
+     <string>Data</string>
+    </property>
+    <addaction name="actionImport_New_Data"/>
+    <addaction name="actionImport_Labeled_Data"/>
+    <addaction name="separator"/>
+    <addaction name="actionReview_Known_Data"/>
+    <addaction name="actionReview_New_Data"/>
+    <addaction name="actionReview_Labeled_Data"/>
+   </widget>
+   <widget class="QMenu" name="menuAdvanced">
+    <property name="title">
+     <string>Advanced</string>
+    </property>
+    <addaction name="actionSet_Active_Learning_Policy"/>
+    <addaction name="actionSet_New_Sample_Batch_Size"/>
+   </widget>
+   <widget class="QMenu" name="menuHelp">
+    <property name="title">
+     <string>Help</string>
+    </property>
+    <addaction name="actionAbout_Carpe_Diem"/>
+   </widget>
+   <addaction name="menuModel"/>
+   <addaction name="menuData"/>
+   <addaction name="menuAdvanced"/>
+   <addaction name="menuHelp"/>
+  </widget>
+  <widget class="QDockWidget" name="dockWidget_2">
+   <property name="minimumSize">
+    <size>
+     <width>200</width>
+     <height>135</height>
+    </size>
+   </property>
+   <property name="features">
+    <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+   </property>
+   <property name="windowTitle">
+    <string>Model Status</string>
+   </property>
+   <attribute name="dockWidgetArea">
+    <number>1</number>
+   </attribute>
+   <widget class="QWidget" name="dockWidgetContents_2">
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QTextBrowser" name="modelStatusText"/>
+     </item>
+    </layout>
+   </widget>
+  </widget>
+  <widget class="QDockWidget" name="dockWidget_4">
+   <property name="sizePolicy">
+    <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+     <horstretch>0</horstretch>
+     <verstretch>0</verstretch>
+    </sizepolicy>
+   </property>
+   <property name="maximumSize">
+    <size>
+     <width>524287</width>
+     <height>300</height>
+    </size>
+   </property>
+   <property name="features">
+    <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+   </property>
+   <property name="windowTitle">
+    <string>Tasks</string>
+   </property>
+   <attribute name="dockWidgetArea">
+    <number>1</number>
+   </attribute>
+   <widget class="QWidget" name="dockWidgetContents_4">
+    <property name="sizePolicy">
+     <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+      <horstretch>0</horstretch>
+      <verstretch>0</verstretch>
+     </sizepolicy>
+    </property>
+    <layout class="QVBoxLayout" name="verticalLayout_4">
+     <item>
+      <widget class="QPushButton" name="predictMultipleButton">
+       <property name="text">
+        <string>Predict Images...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="importDataButton">
+       <property name="text">
+        <string>Import New Data...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="labelDataButton">
+       <property name="text">
+        <string>Label Data...</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="updateModelButton">
+       <property name="text">
+        <string>Update Model</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+  </widget>
+  <action name="actionLoad">
+   <property name="text">
+    <string>Load...</string>
+   </property>
+  </action>
+  <action name="actionNew">
+   <property name="text">
+    <string>New</string>
+   </property>
+  </action>
+  <action name="actionSave">
+   <property name="text">
+    <string>Save</string>
+   </property>
+  </action>
+  <action name="actionSave_As">
+   <property name="text">
+    <string>Save As...</string>
+   </property>
+  </action>
+  <action name="actionLoad_YOLO_Small_VOC">
+   <property name="text">
+    <string>Load YOLO-Small/VOC</string>
+   </property>
+  </action>
+  <action name="actionReview_Known_Data">
+   <property name="text">
+    <string>Review Known Data...</string>
+   </property>
+  </action>
+  <action name="actionReview_Labeled_Data">
+   <property name="text">
+    <string>Review Labeled Update Data...</string>
+   </property>
+  </action>
+  <action name="actionReview_New_Data">
+   <property name="text">
+    <string>Review New Data...</string>
+   </property>
+  </action>
+  <action name="actionImport_New_Data">
+   <property name="text">
+    <string>Import New Data...</string>
+   </property>
+  </action>
+  <action name="actionImport_Labeled_Data">
+   <property name="text">
+    <string>Import Labeled Data...</string>
+   </property>
+  </action>
+  <action name="actionSet_Active_Learning_Policy">
+   <property name="text">
+    <string>Set Active Learning Policy...</string>
+   </property>
+  </action>
+  <action name="actionSet_New_Sample_Batch_Size">
+   <property name="text">
+    <string>Set New Sample Batch Size...</string>
+   </property>
+  </action>
+  <action name="actionAbout_Carpe_Diem">
+   <property name="text">
+    <string>About Carpe Diem...</string>
+   </property>
+  </action>
+  <zorder>dockWidget_2</zorder>
+  <zorder>dockWidget_4</zorder>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+  <customwidget>
+   <class>AnnotatedImageView</class>
+   <extends>QGraphicsView</extends>
+   <header>AnnotatedImageView.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

+ 70 - 0
src/ProgressDialog.cpp

@@ -0,0 +1,70 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "ProgressDialog.h"
+#include "ui_ProgressDialog.h"
+
+/*ProgressDialog::ProgressDialog(QWidget *parent) :
+  QDialog(parent),
+  ui(new Ui::ProgressDialog) {
+  ui->setupUi(this);
+  reject();
+}*/
+
+ProgressDialog::ProgressDialog(QWidget *parent, Project* project, QString& description) :
+  QDialog(parent),
+  project(project),
+  ui(new Ui::ProgressDialog)
+{
+  ui->setupUi(this);
+  ui->statusText->setText(description);
+  setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint| Qt::WindowSystemMenuHint);
+}
+
+int ProgressDialog::exec(QFuture<void>* future) {
+  this->future = future;
+  dialog_ready = true;
+  if(future->isFinished())
+    return future_failed ? QDialog::Rejected : QDialog::Accepted;
+  return QDialog::exec();
+}
+
+ProgressDialog::~ProgressDialog()
+{
+  delete ui;
+}
+
+void ProgressDialog::OnProjectProgressDone() {
+  future_failed = false;
+  if(!dialog_ready)
+    return;
+  QMetaObject::invokeMethod(this, "InnerOnProjectProgressDone");
+}
+
+void ProgressDialog::OnProjectProgressFailed() {
+  if(!dialog_ready)
+    return;
+  QMetaObject::invokeMethod(this, "InnerOnProjectProgressFailed");
+}
+
+void ProgressDialog::OnProjectProgressUpdate(float progress) {
+  if(!dialog_ready)
+    return;
+  QMetaObject::invokeMethod(this, "InnerOnProjectProgressUpdate", Q_ARG(float, progress));
+}
+
+void ProgressDialog::InnerOnProjectProgressDone() {
+  accept();
+}
+
+void ProgressDialog::InnerOnProjectProgressFailed() {
+  reject();
+}
+
+void ProgressDialog::InnerOnProjectProgressUpdate(float progress) {
+  ui->progressBar->setValue((int)(100.0 * progress));
+}

+ 47 - 0
src/ProgressDialog.h

@@ -0,0 +1,47 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef PROGRESSDIALOG_H
+#define PROGRESSDIALOG_H
+
+#include <QDialog>
+#include <cn24.h>
+#include <QtConcurrent/QtConcurrent>
+#include "Project.h"
+
+namespace Ui {
+class ProgressDialog;
+}
+
+class ProgressDialog : public QDialog, public Project::ProjectProgressHandler
+{
+  Q_OBJECT
+
+public:
+  ProgressDialog(QWidget* parent, Project* project, QString& description);
+  ~ProgressDialog();
+
+  void OnProjectProgressDone();
+  void OnProjectProgressFailed();
+  void OnProjectProgressUpdate(float progress);
+
+  int exec(QFuture<void>* future);
+
+private slots:
+  void InnerOnProjectProgressDone();
+  void InnerOnProjectProgressFailed();
+  void InnerOnProjectProgressUpdate(float progress);
+
+private:
+  bool dialog_ready = false;
+  bool future_failed = true;
+  Project* project = nullptr;
+  QFuture<void>* future;
+  Ui::ProgressDialog *ui;
+};
+
+#endif // PROGRESSDIALOG_H

+ 50 - 0
src/ProgressDialog.ui

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ProgressDialog</class>
+ <widget class="QDialog" name="ProgressDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>692</width>
+    <height>85</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>692</width>
+    <height>85</height>
+   </size>
+  </property>
+  <property name="maximumSize">
+   <size>
+    <width>692</width>
+    <height>85</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Carpe Diem</string>
+  </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="statusText">
+     <property name="text">
+      <string>TextLabel</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QProgressBar" name="progressBar">
+     <property name="value">
+      <number>24</number>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 409 - 0
src/Project.cpp

@@ -0,0 +1,409 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "Project.h"
+
+#include <string>
+#include <cn24.h>
+
+Project::Project(QObject* _parent, Conv::ClassManager::ClassUpdateHandler* class_update_handler, ProjectStateHandler* project_state_handler)
+  : QObject(_parent), class_update_handler_(class_update_handler), state_handler_(project_state_handler), state(NOTHING)
+{
+
+}
+
+void Project::PredictSegment(ProjectProgressHandler *progress_handler, Conv::Segment *segment, std::vector<std::string> *prediction_filenames, std::vector<std::vector<Conv::BoundingBox> > *prediction_boxes) {
+  if(state != Project::LOADED)
+    return;
+
+  progress_handler->OnProjectProgressUpdate(0);
+
+  // Switch to testing mode
+  graph_->SetIsTesting(true);
+  input_layer_->ForceWeightsZero();
+
+  Conv::datum total_sample_count = segment->GetSampleCount();
+  Conv::datum running_sample_count = 0;
+
+  for(unsigned int sample = 0; sample < segment->GetSampleCount(); sample++) {
+    Conv::JSON& sample_json = segment->GetSample(sample);
+    std::vector<Conv::BoundingBox> sample_predictions;
+
+    // Load sample
+    input_layer_->ForceLoadDetection(sample_json, 0);
+    graph_->FeedForward();
+
+    // Copy predictions
+    Conv::DetectionMetadataPointer output_boxes = predicted_metadata_[0];
+    for(unsigned int b = 0; b < output_boxes->size(); b++) {
+      Conv::BoundingBox bbox = output_boxes->at(b);
+      sample_predictions.push_back(bbox);
+    }
+
+    // Store predictions
+    std::string sample_filename = sample_json["image_rpath"];
+    prediction_filenames->push_back(sample_filename);
+    prediction_boxes->push_back(sample_predictions);
+
+    running_sample_count += 1.0;
+    progress_handler->OnProjectProgressUpdate(running_sample_count / total_sample_count);
+  }
+
+  // Done
+  progress_handler->OnProjectProgressUpdate(1);
+  progress_handler->OnProjectProgressDone();
+}
+
+void Project::UpdateModel(ProjectProgressHandler *progress_handler) {
+  if(update_set_->GetSampleCount() == 0) {
+    progress_handler->OnProjectProgressDone();
+    return;
+  }
+  needs_rescore_ = true;
+  // Update input layer settings
+  input_layer_->training_sets_.clear();
+  input_layer_->training_weights_.clear();
+  input_layer_->training_sets_.push_back(known_samples_);
+  input_layer_->training_sets_.push_back(update_set_);
+  input_layer_->training_weights_.push_back(1);
+  input_layer_->training_weights_.push_back(1);
+  input_layer_->UpdateDatasets();
+
+  ProjectTrainerProgressHandler trainer_progress_handler(progress_handler);
+
+  progress_handler->OnProjectProgressUpdate(0);
+  trainer_->SetUpdateHandler(&trainer_progress_handler);
+  trainer_->settings()["epoch_iterations"] = 10 * update_set_->GetSampleCount();
+  trainer_->Train(1, false);
+
+  progress_handler->OnProjectProgressUpdate(1);
+  progress_handler->OnProjectProgressDone();
+}
+
+void Project::UpdateScores(ProjectProgressHandler *progress_handler) {
+  progress_handler->OnProjectProgressUpdate(0);
+  if(!needs_rescore_) {
+    progress_handler->OnProjectProgressDone();
+    return;
+  }
+
+  Conv::JSONNetGraphFactory netgraph_factory(architecture_json, 123123);
+  Conv::ActiveLearningPolicy* policy = Conv::YOLOActiveLearningPolicy::CreateWithName(active_learning_policy_,netgraph_factory.GetYOLOConfiguration());
+
+  Conv::NetGraphBuffer& prediction_buffer = graph_->GetOutputNodes()[0]->output_buffers[0];
+  Conv::DatasetMetadataPointer* predicted_metadata = prediction_buffer.combined_tensor->metadata;
+
+  input_layer_->ForceWeightsZero();
+  graph_->SetIsTesting(true);
+
+  Conv::datum total_sample_count = new_set_->GetSampleCount();
+  Conv::datum running_sample_count = 0;
+
+  for(unsigned int s = 0; s < new_set_->GetSegmentCount(); s++) {
+    Conv::Segment* segment = new_set_->GetSegment(s);
+    Conv::datum segment_score = 0;
+    for(unsigned int sample = 0; sample < segment->GetSampleCount(); sample++) {
+      Conv::JSON& sample_json = segment->GetSample(sample);
+      input_layer_->ForceLoadDetection(sample_json, 0);
+      graph_->FeedForward();
+      segment_score += policy->Score(prediction_buffer.combined_tensor->data, predicted_metadata, 0);
+      running_sample_count += 1.0;
+      progress_handler->OnProjectProgressUpdate(running_sample_count / total_sample_count);
+    }
+    segment->score = segment_score;
+  }
+
+  delete policy;
+  needs_rescore_ = false;
+  progress_handler->OnProjectProgressDone();
+}
+
+bool Project::Save() {
+  bool model_result = SaveModel();
+  if(!model_result)
+    return false;
+
+  Conv::JSON project_json = Serialize();
+  std::string project_filename = project_folder_ + "/project.json";
+  std::ofstream project_file(project_filename, std::ios::out);
+  if(!project_file.good()) {
+    LOGERROR << "Could not open " << project_filename << " for writing";
+    return false;
+  }
+
+  project_file << project_json.dump(2);
+  return true;
+}
+
+bool Project::Load(std::string project_folder) {
+  needs_rescore_ = true;
+  project_folder_ = project_folder;
+  std::string project_filename = project_folder_ + "/project.json";
+  std::ifstream project_file(project_filename, std::ios::in);
+  if(!project_file.good()) {
+    LOGERROR << "Could not open " << project_filename << " for reading";
+    return false;
+  }
+  Conv::JSON project_json = Conv::JSON::parse(project_file);
+  bool project_result = Deserialize(project_json);
+  if(!project_result) {
+    SetState(FAILED);
+    return false;
+  }
+
+  SetState(Project::LOADED);
+
+  // Load Model
+  bool model_result = LoadModel();
+  if(!model_result) {
+    SetState(FAILED);
+    return false;
+  }
+
+  return true;
+}
+
+bool Project::AddSample(std::string filename) {
+  needs_rescore_ = true;
+  Conv::Segment* target_segment = nullptr;
+
+  // If no new samples have been added, create first segment
+  if(new_set_->GetSegmentCount() == 0) {
+    std::stringstream ss; ss << "New Data Batch " << 1;
+    Conv::Segment* segment = new Conv::Segment(ss.str());
+    new_set_->AddSegment(segment);
+    target_segment = segment;
+  } else {
+    // Otherwise, get last segment and check if it has room for new sample
+    Conv::Segment* segment = new_set_->GetSegment(new_set_->GetSegmentCount() - 1);
+    if(segment->GetSampleCount() < new_batch_size_) {
+      target_segment = segment;
+    } else {
+      // No room, create new segment
+      std::stringstream ss; ss << "New Data Batch " << new_set_->GetSegmentCount() + 1;
+      Conv::Segment* segment = new Conv::Segment(ss.str());
+      new_set_->AddSegment(segment);
+      target_segment = segment;
+    }
+  }
+
+  Conv::JSON sample_json = Conv::JSON::object();
+  sample_json["image_filename"] = filename;
+  sample_json["boxes"] = Conv::JSON::array();
+
+  return target_segment->AddSample(sample_json);
+}
+
+void Project::Predict(std::string image_filename, std::vector<Conv::BoundingBox> &predictions) {
+  if(state != Project::LOADED)
+    return;
+
+  std::string found_path = Conv::PathFinder::FindPath(image_filename, "");
+  if(found_path.length() > 0) {
+    Conv::JSON sample_json = Conv::JSON::object();
+    sample_json["image_rpath"] = found_path;
+    sample_json["boxes"] = Conv::JSON::array();
+    input_layer_->ForceLoadDetection(sample_json, 0);
+
+    graph_->SetIsTesting(true);
+    graph_->FeedForward();
+
+    Conv::DetectionMetadataPointer output_boxes = predicted_metadata_[0];
+    LOGINFO << "Predicted " << output_boxes->size() << " boxes.";
+
+    for(unsigned int b = 0; b < output_boxes->size(); b++) {
+      Conv::BoundingBox bbox = output_boxes->at(b);
+      predictions.push_back(bbox);
+    }
+  } else {
+    LOGERROR << "Could not find " << image_filename << "!";
+  }
+}
+
+Conv::JSON Project::Serialize() {
+  Conv::JSON project_json = Conv::JSON::object();
+  project_json["architecture"] = architecture_json;
+  project_json["update_set"] = update_set_->Serialize();
+  project_json["known_set"] = known_samples_->Serialize();
+  project_json["new_set"] = new_set_->Serialize();
+  project_json["name"] = project_name_;
+  project_json["new_batch_size"] = new_batch_size_;
+  project_json["active_learning_policy"] = active_learning_policy_;
+  return project_json;
+}
+
+bool Project::Deserialize(Conv::JSON& project_json) {
+  if(state != Project::NOTHING) {
+    LOGERROR << "Already have a project!";
+    return false;
+  }
+
+  // Load JSON
+  architecture_filename_="_from_json_";
+  if(!project_json["architecture"].is_object()) {
+    LOGERROR << "Project JSON is missing architecture";
+    return false;
+  }
+  if(!project_json["update_set"].is_object() || !project_json["known_set"].is_object()) {
+    LOGERROR << "Project JSON is missing set informations!";
+    return false;
+  }
+  if(project_json.count("new_batch_size") == 1 && project_json["new_batch_size"].is_number()) {
+    new_batch_size_ = project_json["new_batch_size"];
+  }
+  if(project_json.count("active_learning_policy") == 1 && project_json["active_learning_policy"].is_string()) {
+    active_learning_policy_ = project_json["active_learning_policy"];
+  }
+
+  architecture_json = project_json["architecture"];
+
+  // Create class manager
+  class_manager_ = new Conv::ClassManager();
+  class_manager_->RegisterClassUpdateHandler(class_update_handler_);
+
+  // Load architecture
+  Conv::JSONNetGraphFactory netgraph_factory(architecture_json, 123123);
+  graph_ = new Conv::NetGraph();
+
+  // Create dataset input layer
+  unsigned int batch_size_parallel = 1;
+  if(netgraph_factory.GetHyperparameters().count("batch_size_parallel") == 1 && netgraph_factory.GetHyperparameters()["batch_size_parallel"].is_number()) {
+    batch_size_parallel = netgraph_factory.GetHyperparameters()["batch_size_parallel"];
+  }
+  input_layer_ = new Conv::SegmentSetInputLayer(netgraph_factory.GetDataInput(), Conv::DETECTION, class_manager_, batch_size_parallel, 123923);
+  Conv::NetGraphNode* input_node = new Conv::NetGraphNode(input_layer_);
+  input_node->is_input = true;
+
+  // Add other layers
+  graph_->AddNode(input_node);
+  bool result = netgraph_factory.AddLayers(*graph_, class_manager_, 23923);
+  if(!result) {
+    SetState(Project::FAILED);
+    LOGERROR << "Could not construct network!";
+    return false;
+  }
+  graph_->Initialize();
+  graph_->InitializeWeights(true);
+
+  // Set helper pointers
+  predicted_metadata_ = (Conv::DetectionMetadataPointer*) graph_->GetOutputNodes()[0]->output_buffers[0].combined_tensor->metadata;
+
+  // Set project properties
+  project_name_ = project_json["name"];
+
+  // Load trainer
+  trainer_ = new Conv::Trainer(*graph_, netgraph_factory.GetHyperparameters());
+
+  // Load samples
+  known_samples_ = new Conv::SegmentSet("Known Examples");
+  bool deserialization_result = known_samples_->Deserialize(project_json["known_set"]);
+  update_set_ = new Conv::SegmentSet("Update Set");
+  deserialization_result &= update_set_->Deserialize(project_json["update_set"]);
+  new_set_ = new Conv::SegmentSet("New Set");
+  deserialization_result &= new_set_->Deserialize(project_json["new_set"]);
+
+  if(!deserialization_result) {
+    LOGERROR << "SegmentSet deserialization failed! See log for details.";
+    return false;
+  }
+
+  return true;
+}
+
+bool Project::New(std::string architecture_filename, std::string model_filename, std::string project_name, std::string project_folder) {
+  needs_rescore_ = true;
+  if(state != Project::NOTHING) {
+    LOGERROR << "Already have a project!";
+    return false;
+  } else {
+    // Validate filenames
+    std::ifstream architecture_file(architecture_filename, std::ios::in);
+    if(!architecture_file.good()) {
+      LOGERROR << "Failed to open architecture!";
+      return false;
+    }
+
+    std::ifstream model_file(model_filename, std::ios::in | std::ios::binary);
+    if(!model_file.good()) {
+      LOGERROR << "Failed to open model!";
+    }
+
+    // Create class manager
+    class_manager_ = new Conv::ClassManager();
+    class_manager_->RegisterClassUpdateHandler(class_update_handler_);
+
+    // Load architecture
+    architecture_json = Conv::JSON::parse(architecture_file);
+    Conv::JSONNetGraphFactory netgraph_factory(architecture_json, 123123);
+    graph_ = new Conv::NetGraph();
+
+    // Create dataset input layer
+    unsigned int batch_size_parallel = 1;
+    if(netgraph_factory.GetHyperparameters().count("batch_size_parallel") == 1 && netgraph_factory.GetHyperparameters()["batch_size_parallel"].is_number()) {
+      batch_size_parallel = netgraph_factory.GetHyperparameters()["batch_size_parallel"];
+    }
+    input_layer_ = new Conv::SegmentSetInputLayer(netgraph_factory.GetDataInput(), Conv::DETECTION, class_manager_, batch_size_parallel, 123923);
+    Conv::NetGraphNode* input_node = new Conv::NetGraphNode(input_layer_);
+    input_node->is_input = true;
+
+    // Add other layers
+    graph_->AddNode(input_node);
+    bool result = netgraph_factory.AddLayers(*graph_, class_manager_, 23923);
+    if(!result) {
+      SetState(Project::FAILED);
+      LOGERROR << "Could not construct network!";
+      return false;
+    }
+    graph_->Initialize();
+    graph_->InitializeWeights(true);
+
+
+    // Load model
+    graph_->DeserializeParameters(model_file);
+
+    // Load trainer
+    trainer_ = new Conv::Trainer(*graph_, netgraph_factory.GetHyperparameters());
+
+    // Initialize segment sets
+    known_samples_ = new Conv::SegmentSet("Known Examples");
+    update_set_ = new Conv::SegmentSet("Update Set");
+    new_set_ = new Conv::SegmentSet("New Set");
+
+    // Set helper pointers
+    predicted_metadata_ = (Conv::DetectionMetadataPointer*) graph_->GetOutputNodes()[0]->output_buffers[0].combined_tensor->metadata;
+
+    // Set project properties
+    project_name_ = project_name;
+    project_folder_ = project_folder;
+
+    SetState(Project::LOADED);
+    return true;
+  }
+}
+
+bool Project::SaveModel() {
+  std::string model_filename = project_folder_ + "/model.CNParamX";
+  std::ofstream model_file(model_filename, std::ios::out | std::ios::binary);
+  if(!model_file.good()) {
+    LOGERROR << "Could not open " << model_filename << " for serializing the model!";
+    return false;
+  }
+  graph_->SerializeParameters(model_file);
+  return true;
+}
+
+bool Project::LoadModel() {
+  needs_rescore_ = true;
+  std::string model_filename = project_folder_ + "/model.CNParamX";
+  std::ifstream model_file(model_filename, std::ios::in | std::ios::binary);
+  if(!model_file.good()) {
+    LOGERROR << "Could not open " << model_filename << " for deserializing the model!";
+    return false;
+  }
+  graph_->DeserializeParameters(model_file);
+  return true;
+}

+ 102 - 0
src/Project.h

@@ -0,0 +1,102 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef PROJECT_H
+#define PROJECT_H
+
+#include <QObject>
+#include <string>
+#include <cn24.h>
+
+class MainWindow;
+class ImportLabeledDataDialog;
+class SelectScoredSegmentDialog;
+class Project : public QObject
+{
+  friend class MainWindow;
+  friend class ImportLabeledDataDialog;
+  friend class SelectScoredSegmentDialog;
+  Q_OBJECT
+public:
+  class ProjectStateHandler {
+  public: virtual void OnProjectStateUpdate() = 0;
+  };
+
+  class ProjectProgressHandler {
+  public: virtual void OnProjectProgressUpdate(float progress) = 0;
+    virtual void OnProjectProgressDone() = 0;
+    virtual void OnProjectProgressFailed() = 0;
+  };
+
+  class ProjectTrainerProgressHandler: public Conv::TrainerProgressUpdateHandler {
+  public:
+    explicit ProjectTrainerProgressHandler(ProjectProgressHandler* handler) : handler(handler) {}
+    ProjectProgressHandler* handler = nullptr;
+    void OnTrainerProgressUpdate(Conv::datum progress) {
+      if(handler != nullptr)
+        handler->OnProjectProgressUpdate((float)progress);
+    }
+  };
+
+  void OnTrainerProgressUpdate(Conv::datum progress);
+
+  Project(QObject *_parent, Conv::ClassManager::ClassUpdateHandler* class_update_handler, ProjectStateHandler* state_handler);
+
+  bool New(std::string architecture_filename, std::string model_filename, std::string project_name, std::string project_folder);
+
+  bool Save();
+  bool Load(std::string project_folder);
+
+  void Predict(std::string image_filename, std::vector<Conv::BoundingBox>& predictions);
+  void PredictSegment(ProjectProgressHandler* progress_handler, Conv::Segment* segment, std::vector<std::string>* prediction_filenames, std::vector<std::vector<Conv::BoundingBox>>* prediction_boxes);
+
+  bool AddSample(std::string filename);
+
+  void UpdateScores(ProjectProgressHandler* progress_handler);
+  void UpdateModel(ProjectProgressHandler* progress_handler);
+
+  enum State {
+    NOTHING,
+    LOADED,
+    FAILED
+  } state;
+
+  void SetNewBatchSize(unsigned int new_batch_size) { needs_rescore_=true; new_batch_size_ = new_batch_size; }
+  void SetActiveLearningPolicy(std::string active_learning_policy) { needs_rescore_=true; active_learning_policy_ = active_learning_policy; }
+private:
+  bool Deserialize(Conv::JSON& project_json);
+  Conv::JSON Serialize();
+  bool SaveModel();
+  bool LoadModel();
+  void SetState(State state) { this->state = state; state_handler_->OnProjectStateUpdate(); }
+
+  unsigned int new_batch_size_ = 32;
+  std::string active_learning_policy_ = "wholeimagediff";
+
+  Conv::JSON architecture_json;
+  std::string architecture_filename_;
+  std::string model_filename_;
+  std::string project_name_;
+  std::string project_folder_;
+
+  Conv::SegmentSetInputLayer* input_layer_;
+  Conv::NetGraph* graph_;
+  Conv::Trainer* trainer_;
+  Conv::DetectionMetadataPointer* predicted_metadata_;
+
+  Conv::ClassManager::ClassUpdateHandler* class_update_handler_;
+  Conv::ClassManager* class_manager_;
+  ProjectStateHandler* state_handler_;
+
+  Conv::SegmentSet* known_samples_;
+  Conv::SegmentSet* update_set_;
+  Conv::SegmentSet* new_set_;
+
+  bool needs_rescore_ = true;
+};
+
+#endif // PROJECT_H

+ 79 - 0
src/ProjectDetailsDialog.cpp

@@ -0,0 +1,79 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "ProjectDetailsDialog.h"
+#include "ui_ProjectDetailsDialog.h"
+
+#include <QFileDialog>
+#include <QMessageBox>
+
+ProjectDetailsDialog::ProjectDetailsDialog(QWidget *parent) :
+  QDialog(parent),
+  ui(new Ui::ProjectDetailsDialog)
+{
+  ui->setupUi(this);
+  Validate();
+}
+
+ProjectDetailsDialog::~ProjectDetailsDialog()
+{
+  delete ui;
+}
+
+void ProjectDetailsDialog::on_netBrowseButton_clicked()
+{
+  auto fileName = QFileDialog::getOpenFileName(this, tr("Open Network Architecture"), QString(), tr("*.json"));
+  if(fileName.length() > 0)
+    ui->netLine->setText(fileName);
+}
+
+void ProjectDetailsDialog::on_projectFolderBrowseButton_clicked()
+{
+  auto folderName = QFileDialog::getExistingDirectory(this, tr("Select Project Folder"));
+  if(folderName.length() > 0)
+    ui->projectFolderLine->setText(folderName);
+}
+
+void ProjectDetailsDialog::on_modelBrowseButton_clicked()
+{
+  auto fileName = QFileDialog::getOpenFileName(this, tr("Open Trained Model"), QString(), tr("*.CNParamX"));
+  if(fileName.length() > 0)
+    ui->modelLine->setText(fileName);
+}
+
+void ProjectDetailsDialog::Validate() {
+  if(ui->modelLine->text().length() == 0) {
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+  } else if(ui->netLine->text().length() == 0) {
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+  } else if(ui->projectFolderLine->text().length() == 0) {
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+  } else {
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
+  }
+}
+
+void ProjectDetailsDialog::on_modelLine_textChanged(const QString &arg1)
+{
+  Validate();
+}
+
+void ProjectDetailsDialog::on_netLine_textChanged(const QString &arg1)
+{
+  Validate();
+}
+
+void ProjectDetailsDialog::on_projectFolderLine_textChanged(const QString &arg1)
+{
+  Validate();
+}
+
+void ProjectDetailsDialog::GetOptions(std::string &architecture_filename, std::string &model_filename, std::string &project_folder) {
+  architecture_filename = ui->netLine->text().toStdString();
+  model_filename = ui->modelLine->text().toStdString();
+  project_folder = ui->projectFolderLine->text().toStdString();
+}

+ 46 - 0
src/ProjectDetailsDialog.h

@@ -0,0 +1,46 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef PROJECTDETAILSDIALOG_H
+#define PROJECTDETAILSDIALOG_H
+
+#include <QDialog>
+#include <string>
+
+namespace Ui {
+class ProjectDetailsDialog;
+}
+
+class ProjectDetailsDialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+  explicit ProjectDetailsDialog(QWidget *parent = 0);
+  ~ProjectDetailsDialog();
+
+  void GetOptions(std::string &architecture_filename, std::string &model_filename, std::string &project_folder);
+  void Validate();
+
+private slots:
+    void on_netBrowseButton_clicked();
+
+    void on_modelBrowseButton_clicked();
+
+    void on_modelLine_textChanged(const QString &arg1);
+
+    void on_netLine_textChanged(const QString &arg1);
+
+    void on_projectFolderBrowseButton_clicked();
+
+    void on_projectFolderLine_textChanged(const QString &arg1);
+
+private:
+  Ui::ProjectDetailsDialog *ui;
+};
+
+#endif // PROJECTDETAILSDIALOG_H

+ 143 - 0
src/ProjectDetailsDialog.ui

@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ProjectDetailsDialog</class>
+ <widget class="QDialog" name="ProjectDetailsDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>534</width>
+    <height>398</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Project Details</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Details</string>
+     </property>
+     <layout class="QFormLayout" name="formLayout">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Network Architecture</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QLineEdit" name="netLine"/>
+        </item>
+        <item>
+         <widget class="QPushButton" name="netBrowseButton">
+          <property name="text">
+           <string>Browse...</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Initial Model</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <item>
+         <widget class="QLineEdit" name="modelLine"/>
+        </item>
+        <item>
+         <widget class="QPushButton" name="modelBrowseButton">
+          <property name="text">
+           <string>Browse...</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Project Folder</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <layout class="QHBoxLayout" name="horizontalLayout_3">
+        <item>
+         <widget class="QLineEdit" name="projectFolderLine"/>
+        </item>
+        <item>
+         <widget class="QPushButton" name="projectFolderBrowseButton">
+          <property name="text">
+           <string>Browse...</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Note: Please make sure that the folder specified is empty.&lt;br/&gt;Any remaining files may be overwritten or removed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ProjectDetailsDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ProjectDetailsDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 109 - 0
src/SelectScoredSegmentDialog.cpp

@@ -0,0 +1,109 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "SelectScoredSegmentDialog.h"
+#include "ui_SelectScoredSegmentDialog.h"
+
+#include <QPushButton>
+#include <QMetaObject>
+#include <QtConcurrent/QtConcurrent>
+
+SelectScoredSegmentDialog::SelectScoredSegmentDialog(QWidget *parent, Project* project) :
+  project(project),
+  QDialog(parent),
+  ui(new Ui::SelectScoredSegmentDialog)
+{
+  ui->setupUi(this);
+  ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Review");
+
+  if(project == nullptr) {
+    reject();
+  } else {
+    UpdateSegments();
+  }
+}
+
+void SelectScoredSegmentDialog::UpdateSegments() {
+  ui->segmentTable->clearContents();
+  ui->segmentTable->setSortingEnabled(false);
+  if(project->new_set_->GetSegmentCount() > 0) {
+    ui->segmentTable->setRowCount(project->new_set_->GetSegmentCount());
+    for(unsigned int s = 0; s < project->new_set_->GetSegmentCount(); s++) {
+      Conv::Segment* segment = project->new_set_->GetSegment(s);
+      ui->segmentTable->setItem(s, 0, new QTableWidgetItem(QString::fromStdString(segment->name)));
+      ui->segmentTable->setItem(s, 1, new ScoreTableItem(QString::asprintf("%f", segment->score)));
+    }
+    segment = project->new_set_->GetSegment(0);
+    ui->segmentTable->setCurrentCell(0, 0);
+    ui->segmentTable->item(0,0)->setSelected(true);
+    ui->segmentTable->setColumnWidth(0, 200);
+  } else {
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+  }
+  ui->segmentTable->setSortingEnabled(true);
+}
+
+SelectScoredSegmentDialog::~SelectScoredSegmentDialog()
+{
+  delete ui;
+}
+
+void SelectScoredSegmentDialog::on_segmentTable_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous)
+{
+  if(current != nullptr) {
+    std::string segment_name = current->text().toStdString();
+    int index = project->new_set_->GetSegmentIndex(segment_name);
+    if(index >= 0) {
+      segment = project->new_set_->GetSegment(index);
+    }
+  }
+}
+
+void SelectScoredSegmentDialog::OnProjectProgressDone() {
+  QMetaObject::invokeMethod(this, "InnerOnProjectProgressDone");
+}
+
+void SelectScoredSegmentDialog::OnProjectProgressFailed() {
+  QMetaObject::invokeMethod(this, "InnerOnProjectProgressFailed");
+}
+
+void SelectScoredSegmentDialog::OnProjectProgressUpdate(float progress) {
+  QMetaObject::invokeMethod(this, "InnerOnProjectProgressUpdate", Q_ARG(float, progress));
+}
+
+void SelectScoredSegmentDialog::InnerOnProjectProgressDone() {
+  ui->progressBar->setValue(0);
+  ui->progressBar->setEnabled(false);
+  UpdateSegments();
+}
+
+void SelectScoredSegmentDialog::InnerOnProjectProgressFailed() {
+  ui->progressBar->setValue(0);
+  ui->progressBar->setEnabled(false);
+  UpdateSegments();
+}
+
+void SelectScoredSegmentDialog::InnerOnProjectProgressUpdate(float progress) {
+  ui->progressBar->setValue((int)(100 * progress));
+}
+
+void SelectScoredSegmentDialog::on_updateScoreButton_clicked()
+{
+  ui->progressBar->setEnabled(true);
+  QtConcurrent::run(project, &Project::UpdateScores, (ProjectProgressHandler*)this);
+}
+
+void SelectScoredSegmentDialog::on_removeSegmentButton_clicked()
+{
+  if(segment!=nullptr) {
+    int index = project->new_set_->GetSegmentIndex(segment->name);
+    if(index >= 0) {
+      project->new_set_->RemoveSegment(index);
+    }
+  }
+  UpdateSegments();
+}

+ 64 - 0
src/SelectScoredSegmentDialog.h

@@ -0,0 +1,64 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef SELECTSCOREDSEGMENTDIALOG_H
+#define SELECTSCOREDSEGMENTDIALOG_H
+
+#include <QDialog>
+#include <QTableWidget>
+#include <cn24.h>
+
+#include "Project.h"
+
+namespace Ui {
+class SelectScoredSegmentDialog;
+}
+
+class SelectScoredSegmentDialog : public QDialog, public Project::ProjectProgressHandler
+{
+  Q_OBJECT
+
+public:
+  class ScoreTableItem : public QTableWidgetItem {
+  public:
+    explicit ScoreTableItem(const QString& text) : QTableWidgetItem(text) {}
+    bool operator< (const QTableWidgetItem& other) const
+    {
+      return text().toFloat() < other.text().toFloat();
+    }
+  };
+
+  explicit SelectScoredSegmentDialog(QWidget *parent = 0, Project* project = nullptr);
+  ~SelectScoredSegmentDialog();
+
+  Conv::Segment* GetSegment() const { return segment; }
+
+  void OnProjectProgressDone();
+  void OnProjectProgressFailed();
+  void OnProjectProgressUpdate(float progress);
+
+private slots:
+  void on_segmentTable_currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous);
+
+  void on_updateScoreButton_clicked();
+
+  void InnerOnProjectProgressDone();
+  void InnerOnProjectProgressFailed();
+  void InnerOnProjectProgressUpdate(float progress);
+
+  void on_removeSegmentButton_clicked();
+
+private:
+  void UpdateSegments();
+
+
+  Ui::SelectScoredSegmentDialog *ui;
+  Project* project = nullptr;
+  Conv::Segment* segment = nullptr;
+};
+
+#endif // SELECTSCOREDSEGMENTDIALOG_H

+ 130 - 0
src/SelectScoredSegmentDialog.ui

@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SelectScoredSegmentDialog</class>
+ <widget class="QDialog" name="SelectScoredSegmentDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>663</width>
+    <height>568</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>663</width>
+    <height>568</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Select Batch For Review</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTableWidget" name="segmentTable">
+     <property name="editTriggers">
+      <set>QAbstractItemView::NoEditTriggers</set>
+     </property>
+     <property name="columnCount">
+      <number>2</number>
+     </property>
+     <column>
+      <property name="text">
+       <string>Batch</string>
+      </property>
+     </column>
+     <column>
+      <property name="text">
+       <string>Score</string>
+      </property>
+     </column>
+    </widget>
+   </item>
+   <item>
+    <widget class="QProgressBar" name="progressBar">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="value">
+      <number>0</number>
+     </property>
+     <property name="invertedAppearance">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="updateScoreButton">
+       <property name="text">
+        <string>Update Scores</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="removeSegmentButton">
+       <property name="text">
+        <string>Remove</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>SelectScoredSegmentDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>SelectScoredSegmentDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 79 - 0
src/SelectSegmentDialog.cpp

@@ -0,0 +1,79 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "SelectSegmentDialog.h"
+#include "ui_SelectSegmentDialog.h"
+
+#include <QPushButton>
+
+#include <QMessageBox>
+
+SelectSegmentDialog::SelectSegmentDialog(QWidget *parent, Conv::SegmentSet* set, Project* project):
+  QDialog(parent), project(project), set(set),
+  ui(new Ui::SelectSegmentDialog)
+{
+  ui->setupUi(this);
+  ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Review");
+
+  if(set != nullptr && project != nullptr) {
+    UpdateSegments();
+  } else {
+    reject();
+  }
+}
+
+SelectSegmentDialog::~SelectSegmentDialog()
+{
+  delete ui;
+}
+
+void SelectSegmentDialog::UpdateSegments() {
+  ui->segmentListWidget->clear();
+  for(unsigned int s = 0; s < set->GetSegmentCount(); s++) {
+    Conv::Segment* segment = set->GetSegment(s);
+    ui->segmentListWidget->addItem(QString::fromStdString(segment->name));
+  }
+  if(set->GetSegmentCount() > 0) {
+    segment = set->GetSegment(0);
+    ui->segmentListWidget->setCurrentRow(0);
+    ui->segmentListWidget->item(0)->setSelected(true);
+    ui->removeButton->setEnabled(true);
+  } else {
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+    ui->removeButton->setEnabled(false);
+  }
+}
+
+void SelectSegmentDialog::on_segmentListWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
+{
+  if(current != nullptr) {
+    segment = set->GetSegment(ui->segmentListWidget->row(current));
+  }
+}
+
+void SelectSegmentDialog::on_removeButton_clicked()
+{
+  if(segment != nullptr) {
+    set->RemoveSegment(ui->segmentListWidget->currentRow());
+    UpdateSegments();
+  }
+}
+
+void SelectSegmentDialog::on_moveToNewButton_clicked()
+{
+  if(segment != nullptr) {
+    set->RemoveSegment(ui->segmentListWidget->currentRow());
+    for(unsigned int sample = 0; sample < segment->GetSampleCount(); sample++) {
+      Conv::JSON& sample_json = segment->GetSample(sample);
+      std::string filename = sample_json["image_rpath"];
+      if(!(project->AddSample(filename))) {
+        QMessageBox::critical(this, tr("Error"), tr("Could not import unlabeled sample! See log for details."), QMessageBox::Ok);
+      }
+    }
+    UpdateSegments();
+  }
+}

+ 47 - 0
src/SelectSegmentDialog.h

@@ -0,0 +1,47 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#ifndef SELECTSEGMENTDIALOG_H
+#define SELECTSEGMENTDIALOG_H
+
+#include <QDialog>
+#include <QListWidget>
+#include <cn24.h>
+
+#include "Project.h"
+
+namespace Ui {
+class SelectSegmentDialog;
+}
+
+class SelectSegmentDialog : public QDialog
+{
+  Q_OBJECT
+
+public:
+  SelectSegmentDialog(QWidget *parent = 0, Conv::SegmentSet* set = nullptr, Project* project = nullptr);
+  ~SelectSegmentDialog();
+
+  Conv::Segment* GetSegment() const { return segment; }
+
+private slots:
+  void on_segmentListWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
+
+  void on_removeButton_clicked();
+
+  void on_moveToNewButton_clicked();
+
+private:
+  void UpdateSegments();
+
+  Ui::SelectSegmentDialog *ui;
+  Project* project = nullptr;
+  Conv::SegmentSet* set = nullptr;
+  Conv::Segment* segment = nullptr;
+};
+
+#endif // SELECTSEGMENTDIALOG_H

+ 104 - 0
src/SelectSegmentDialog.ui

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SelectSegmentDialog</class>
+ <widget class="QDialog" name="SelectSegmentDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>664</width>
+    <height>641</height>
+   </rect>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>664</width>
+    <height>641</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Select Batch For Review</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QListWidget" name="segmentListWidget"/>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="text">
+        <string>Remove</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="moveToNewButton">
+       <property name="text">
+        <string>Move To New Data (Strip Labels)</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>SelectSegmentDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>SelectSegmentDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 49 - 0
src/carpediem.pro

@@ -0,0 +1,49 @@
+# This file is part of the Carpe Diem Active Learning Software,
+# Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+
+# For licensing information, see the LICENSE file included with this project.
+
+QT       += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+TARGET = carpediem
+TEMPLATE = app
+
+
+SOURCES += main.cpp\
+        MainWindow.cpp \
+    ProjectDetailsDialog.cpp \
+    Project.cpp \
+    AnnotatedImageView.cpp \
+    CDUtils.cpp \
+    ImportLabeledDataDialog.cpp \
+    SelectSegmentDialog.cpp \
+    SelectScoredSegmentDialog.cpp \
+    ProgressDialog.cpp \
+    LabelHypothesesDialog.cpp
+
+HEADERS  += MainWindow.h \
+    ProjectDetailsDialog.h \
+    Project.h \
+    AnnotatedImageView.h \
+    CDUtils.h \
+    ImportLabeledDataDialog.h \
+    SelectSegmentDialog.h \
+    SelectScoredSegmentDialog.h \
+    ProgressDialog.h \
+    LabelHypothesesDialog.h
+
+FORMS    += MainWindow.ui \
+    ProjectDetailsDialog.ui \
+    ImportLabeledDataDialog.ui \
+    SelectSegmentDialog.ui \
+    SelectScoredSegmentDialog.ui \
+    ProgressDialog.ui \
+    LabelHypothesesDialog.ui
+
+win32:CONFIG(release, debug|release): LIBS += -lcn24 -lOpenCL -lclBLAS -ljpeg-static
+else:win32:CONFIG(debug, debug|release): LIBS += -lcn24 -lOpenCL -lclBLAS -ljpeg-static
+else:unix: LIBS += -lcn24
+
+CONFIG += c++11

+ 23 - 0
src/main.cpp

@@ -0,0 +1,23 @@
+/*
+ * This file is part of the Carpe Diem Active Learning Software,
+ * Copyright (C) 2017 Clemens-Alexander Brust (ikosa dot de at gmail dot com).
+ *
+ * For licensing information, see the LICENSE file included with this project.
+ */
+
+#include "MainWindow.h"
+#include <QApplication>
+#include <cn24.h>
+
+int main(int argc, char *argv[])
+{
+  QApplication a(argc, argv);
+  Conv::System::Init(3);
+
+  MainWindow w;
+  w.show();
+
+  auto res = a.exec();
+  LOGEND;
+  return res;
+}