diff --git a/src/image/polygon.cpp b/src/image/polygon.cpp index c92c3b7bd4251a2fb5d15b65c95dd077e1443ef2..997b4ac1a3895b89d67affb52a46ad5d07fbd9ec 100644 --- a/src/image/polygon.cpp +++ b/src/image/polygon.cpp @@ -17,7 +17,7 @@ #include "polygon.h" -Polygon::Polygon(MarkedClass* polygonClass) : +Polygon::Polygon(MarkedClass *polygonClass) : m_polygonClass(polygonClass) { } diff --git a/src/image/polygon.h b/src/image/polygon.h index 06d43f583e1cedd0bc5ee49c06fcae95b25df7fe..452176580d7418a252640a7ffaeec990b0cccaeb 100644 --- a/src/image/polygon.h +++ b/src/image/polygon.h @@ -28,13 +28,13 @@ class QGraphicsItem; class Polygon : public MarkedObject, public QPolygonF { public: - Polygon(MarkedClass* polygonClass = nullptr); + Polygon(MarkedClass *polygonClass = nullptr); - MarkedClass* polygonClass() const { return m_polygonClass; } - void setPolygonClass(MarkedClass* polygonClass) { m_polygonClass = polygonClass; } + MarkedClass *polygonClass() const { return m_polygonClass; } + void setPolygonClass(MarkedClass *polygonClass) { m_polygonClass = polygonClass; } private: - MarkedClass* m_polygonClass; + MarkedClass *m_polygonClass; }; #endif // POLYGON_H diff --git a/src/ui/annotatorwidget.cpp b/src/ui/annotatorwidget.cpp index 5916b8324df1c119239009f4deebabf51f527c65..c47bf423d99901c8b3a5a9d4b080e38edac3dc51 100644 --- a/src/ui/annotatorwidget.cpp +++ b/src/ui/annotatorwidget.cpp @@ -22,15 +22,15 @@ #include #include -#include - AnnotatorWidget::AnnotatorWidget(QWidget* parent): QWidget(parent), m_ui(new Ui::AnnotatorWidget), m_currentImage(nullptr), m_shape(marK::Shape::Polygon), m_scaleW(0.0), - m_scaleH(0.0) + m_scaleH(0.0), + m_autoSaveFilePath(""), + m_autoSaveType(marK::OutputType::None) { m_ui->setupUi(this); @@ -48,21 +48,6 @@ AnnotatorWidget::~AnnotatorWidget() { } -// TODO: improve me and remove this loop -QVector AnnotatorWidget::savedPolygons() const -{ - QVector copyPolygons(m_savedPolygons); - - for (Polygon& polygon : copyPolygons) { - for (QPointF& point : polygon) { - point -= m_currentImage->pos(); - point = scaledPoint(point); - } - } - - return copyPolygons; -} - void AnnotatorWidget::mousePressEvent(QMouseEvent* event) { if (m_currentImage != nullptr) { @@ -72,15 +57,15 @@ void AnnotatorWidget::mousePressEvent(QMouseEvent* event) if (m_shape == marK::Shape::Polygon) { auto savedPolClicked = std::find_if( - m_savedPolygons.begin(), m_savedPolygons.end(), - [&](const Polygon& pol) { + m_savedObjects.begin(), m_savedObjects.end(), + [&](const Polygon &pol) { return pol.containsPoint(clickedPoint, Qt::OddEvenFill); } ); - bool isSavedPolClicked = savedPolClicked != m_savedPolygons.end(); + bool isSavedPolClicked = savedPolClicked != m_savedObjects.end(); if (isSavedPolClicked) { m_currentPolygon = *savedPolClicked; - m_savedPolygons.erase(savedPolClicked); + m_savedObjects.erase(savedPolClicked); m_currentPolygon.pop_back(); } @@ -89,15 +74,16 @@ void AnnotatorWidget::mousePressEvent(QMouseEvent* event) QPointF cPolFirstPt = m_currentPolygon.first(); QRectF cPolFirstPtRect(cPolFirstPt, QPointF(cPolFirstPt.x() + 10, cPolFirstPt.y() + 10)); isPolFirstPtClicked = cPolFirstPtRect.contains(clickedPoint); - if (isPolFirstPtClicked) + if (isPolFirstPtClicked) { clickedPoint = QPointF(cPolFirstPt); + } } if (isSavedPolClicked || isPolFirstPtClicked || isImageClicked) { m_currentPolygon << clickedPoint; if (m_currentPolygon.size() > 1 && m_currentPolygon.isClosed()) { - m_savedPolygons << m_currentPolygon; + m_savedObjects << m_currentPolygon; m_currentPolygon.clear(); } @@ -106,12 +92,13 @@ void AnnotatorWidget::mousePressEvent(QMouseEvent* event) } else if (m_shape == marK::Shape::Rectangle) { if (isImageClicked) { - if (m_currentPolygon.empty()) + if (m_currentPolygon.empty()) { m_currentPolygon << clickedPoint; + } else { QPointF firstPt = m_currentPolygon.first(); m_currentPolygon << QPointF(clickedPoint.x(), firstPt.y()) << clickedPoint << QPointF(firstPt.x(), clickedPoint.y()) << firstPt; - m_savedPolygons << m_currentPolygon; + m_savedObjects << m_currentPolygon; m_currentPolygon.clear(); } @@ -125,18 +112,20 @@ void AnnotatorWidget::mousePressEvent(QMouseEvent* event) void AnnotatorWidget::repaint() { - for (QGraphicsItem* item : m_items) + for (QGraphicsItem *item : qAsConst(m_items)) { m_ui->graphicsView->scene()->removeItem(item); + } m_items.clear(); - for (Polygon& polygon : m_savedPolygons) + for (Polygon &polygon : m_savedObjects) { paintPolygon(polygon); + } paintPolygon(m_currentPolygon); } -void AnnotatorWidget::paintPolygon(Polygon& polygon) +void AnnotatorWidget::paintPolygon(Polygon &polygon) { QGraphicsScene *scene = m_ui->graphicsView->scene(); @@ -147,6 +136,10 @@ void AnnotatorWidget::paintPolygon(Polygon& polygon) QGraphicsPolygonItem *pol = scene->addPolygon(polygon, QPen(polygon.polygonClass()->color(), 2), QBrush(color)); m_items << pol; + + if (m_autoSaveType != marK::OutputType::None) { + saveObjects(m_autoSaveFilePath, m_autoSaveType); + } } else { for (auto it = polygon.begin(); it != polygon.end(); ++it) { @@ -157,8 +150,9 @@ void AnnotatorWidget::paintPolygon(Polygon& polygon) item = scene->addRect((*it).x(), (*it).y(), 10, 10, QPen(brush, 2), brush); } - else + else { item = scene->addLine(QLineF(*(it - 1), *it), QPen(QBrush(polygon.polygonClass()->color()), 2)); + } m_items << item; } @@ -176,18 +170,15 @@ void AnnotatorWidget::undo() void AnnotatorWidget::reset() { - m_savedPolygons.clear(); + m_savedObjects.clear(); m_currentPolygon.clear(); repaint(); } void AnnotatorWidget::changeItem(QString itemPath) { - // TODO: create a temp file where the polygons from this image will be temporary stored, so they can be loaded again when - // the image is reopened - m_savedPolygons.clear(); m_items.clear(); - m_currentPolygon.clear(); + reset(); QGraphicsScene *scene = m_ui->graphicsView->scene(); scene->setSceneRect(0, 0, 850, 640); @@ -196,16 +187,23 @@ void AnnotatorWidget::changeItem(QString itemPath) QPixmap image(itemPath); QPixmap scaledImage; - if (image.height() >= 1280) + if (image.height() >= 1280) { scaledImage = image.scaledToHeight(int(1280 * 0.8)); - if (image.width() >= 960) + } + + if (image.width() >= 960) { scaledImage = image.scaledToWidth(int(960 * 0.8)); + } if (!scaledImage.isNull()) { m_scaleW = qreal(scaledImage.width()) / qreal(image.width()); m_scaleH = qreal(scaledImage.height()) / qreal(image.height()); image = scaledImage; } + else { + m_scaleW = 1.0; + m_scaleH = 1.0; + } QGraphicsPixmapItem *pixmapItem = scene->addPixmap(image); @@ -223,3 +221,50 @@ void AnnotatorWidget::clearScene() { m_ui->graphicsView->scene()->clear(); } + +bool AnnotatorWidget::saveObjects(const QString &filepath, marK::OutputType type) +{ + QVector scaledObjects(m_savedObjects); + + // TODO: improve me and remove this loop + for (Polygon &object : scaledObjects) { + for (QPointF &point : object) { + point -= m_currentImage->pos(); + point = QPointF(point.x() / m_scaleW, point.y() / m_scaleH); + } + } + Serializer serializer(scaledObjects); + + return serializer.write(filepath, type); +} + +QVector AnnotatorWidget::importObjects(const QString &filepath) +{ + Serializer serializer(filepath); + + QVector objects = serializer.read(); + QVector markedClasses; + + if (!objects.isEmpty()) { + QPointF offset = m_currentImage->pos(); + + for (Polygon &object : objects) { + for (QPointF &point : object) { + point = QPointF(point.x() * m_scaleW, point.y() * m_scaleH); + point += offset; + } + markedClasses.append(object.polygonClass()); + } + + m_savedObjects = objects; + repaint(); + } + + return markedClasses; +} + +void AnnotatorWidget::setAutoSaveFile(const QString &str, marK::OutputType outputType) +{ + m_autoSaveFilePath = str; + m_autoSaveType = outputType; +} diff --git a/src/ui/annotatorwidget.h b/src/ui/annotatorwidget.h index 8619efebb0441414ea80d0ee380e7e87059162c0..f56ad079e414a1adc0e82c94a593fbe7822a7121 100644 --- a/src/ui/annotatorwidget.h +++ b/src/ui/annotatorwidget.h @@ -20,6 +20,7 @@ #include "ui/mark.h" #include "image/polygon.h" +#include "ui/serializer.h" #include #include @@ -38,7 +39,10 @@ public: ~AnnotatorWidget() override; public: - QVector savedPolygons() const; + QVector importObjects(const QString &filepath); + bool saveObjects(const QString &filepath, marK::OutputType output_type); + + void setAutoSaveFile(const QString &str, marK::OutputType outputType); void mousePressEvent(QMouseEvent* event) override; @@ -51,9 +55,6 @@ public: void setCurrentPolygonClass(MarkedClass* polygonClass) { m_currentPolygon.setPolygonClass(polygonClass); repaint(); } void setShape(marK::Shape shape) { m_shape = shape; m_currentPolygon.clear(); repaint(); } - // TODO: scale a point - QPointF scaledPoint(const QPointF& point) const { return QPointF(point.x(), point.y()); } - public slots: void undo(); void reset(); @@ -61,13 +62,16 @@ public slots: private: Ui::AnnotatorWidget* m_ui; Polygon m_currentPolygon; - QVector m_savedPolygons; + QVector m_savedObjects; QVector m_items; QGraphicsPixmapItem* m_currentImage; marK::Shape m_shape; qreal m_scaleW; qreal m_scaleH; + + QString m_autoSaveFilePath; + marK::OutputType m_autoSaveType; }; #endif // ANNOTATORWIDGET_H diff --git a/src/ui/mark.cpp b/src/ui/mark.cpp index 48f84e17014034de596ee5e7659a7a8ee4e5aa55..1bb9ccfc17fc0f73fc78c8318c6496e34f6fa965 100644 --- a/src/ui/mark.cpp +++ b/src/ui/mark.cpp @@ -20,10 +20,10 @@ #include "serializer.h" #include +#include #include #include #include -#include #include #include #include @@ -33,14 +33,15 @@ #include #include #include - -#include +#include +#include marK::marK(QWidget *parent) : QMainWindow(parent), m_ui(new Ui::marK), m_watcher(new QFileSystemWatcher(this)), - m_currentDirectory("") + m_currentDirectory(""), + m_autoSaveType(OutputType::None) { m_ui->setupUi(this); @@ -50,6 +51,10 @@ marK::marK(QWidget *parent) : openDirAction->setShortcut(QKeySequence(Qt::Modifier::CTRL + Qt::Key::Key_O)); connect(openDirAction, &QAction::triggered, this, &marK::changeDirectory); + QAction *importData = fileMenu->addAction("Import"); + importData->setShortcut(QKeySequence(Qt::Modifier::CTRL + Qt::Key::Key_I)); + connect(importData, &QAction::triggered, this, &marK::importData); + QMenu *exportMenu = fileMenu->addMenu("Export"); QAction *toXML = exportMenu->addAction("XML"); @@ -64,30 +69,58 @@ marK::marK(QWidget *parent) : undoAction->setShortcut(QKeySequence(Qt::Modifier::CTRL + Qt::Key::Key_Z)); connect(undoAction, &QAction::triggered, m_ui->annotatorWidget, &AnnotatorWidget::undo); + QMenu *autoSaveMenu = editMenu->addMenu("Auto Save"); + + QActionGroup *autoSaveActionGroup = new QActionGroup(this); + + QAction *autoSaveJsonButton = autoSaveMenu->addAction("JSON"); + autoSaveJsonButton->setCheckable(true); + connect(autoSaveJsonButton, &QAction::triggered, this, &marK::toggleAutoSave); + autoSaveJsonButton->setActionGroup(autoSaveActionGroup); + + QAction *autoSaveXmlButton = autoSaveMenu->addAction("XML"); + autoSaveXmlButton->setCheckable(true); + connect(autoSaveXmlButton, &QAction::triggered, this, &marK::toggleAutoSave); + autoSaveXmlButton->setActionGroup(autoSaveActionGroup); + + QAction *autoSaveDisableButton = autoSaveMenu->addAction("Disabled"); + autoSaveDisableButton->setCheckable(true); + autoSaveDisableButton->setChecked(true); + connect(autoSaveDisableButton, &QAction::triggered, this, &marK::toggleAutoSave); + autoSaveDisableButton->setActionGroup(autoSaveActionGroup); + + QShortcut *nextItemShortcut = new QShortcut(this); + nextItemShortcut->setKey(Qt::Key_Down); + connect(nextItemShortcut, &QShortcut::activated, this, &marK::goToNextItem); + + QShortcut *previousItemShortcut = new QShortcut(this); + previousItemShortcut->setKey(Qt::Key_Up); + connect(previousItemShortcut, &QShortcut::activated, this, &marK::goToPreviousItem); + m_ui->annotatorWidget->setMinimumSize(860, 650); updateFiles(); addNewClass(); connect(m_ui->listWidget, &QListWidget::currentItemChanged, this, - static_cast(&marK::changeItem)); + qOverload(&marK::changeItem)); connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, - static_cast(&marK::updateFiles)); + [=](){ marK::updateFiles(); }); - connect(m_ui->newClassButton, &QPushButton::clicked, this, &marK::addNewClass); + connect(m_ui->newClassButton, &QPushButton::clicked, this, qOverload<>(&marK::addNewClass)); connect(m_ui->undoButton, &QPushButton::clicked, m_ui->annotatorWidget, &AnnotatorWidget::undo); connect(m_ui->resetButton, &QPushButton::clicked, m_ui->annotatorWidget, &AnnotatorWidget::reset); - connect(m_ui->comboBox, &QComboBox::editTextChanged, + connect(m_ui->comboBox, &QComboBox::editTextChanged, this, [&](const QString & text) { m_ui->comboBox->setItemText(m_ui->comboBox->currentIndex(), text); m_polygonClasses[m_ui->comboBox->currentIndex()]->setName(text); } ); - connect(m_ui->comboBox, QOverload::of(&QComboBox::activated), + connect(m_ui->comboBox, QOverload::of(&QComboBox::activated), this, [&](int index) { m_ui->annotatorWidget->setCurrentPolygonClass(m_polygonClasses[index]); } @@ -96,59 +129,68 @@ marK::marK(QWidget *parent) : connect(m_ui->selectClassColorButton, &QPushButton::clicked, this, &marK::selectClassColor); m_ui->polygonButton->setIcon(QIcon::fromTheme("tool_polyline")); - connect(m_ui->polygonButton, &QPushButton::clicked, + connect(m_ui->polygonButton, &QPushButton::clicked, this, [&](bool checked) { changeShape(marK::Shape::Polygon); }); m_ui->rectButton->setIcon(QIcon::fromTheme("tool_rectangle")); - connect(m_ui->rectButton, &QPushButton::clicked, + connect(m_ui->rectButton, &QPushButton::clicked, this, [&](bool checked) { changeShape(marK::Shape::Rectangle); }); } void marK::updateFiles() { - updateFiles(m_currentDirectory); + int currentIndex = m_ui->listWidget->currentRow(); + updateFiles(m_currentDirectory, currentIndex); } -void marK::updateFiles(const QString &path) +void marK::updateFiles(const QString &path, const int index) { - QListWidgetItem *previousSelectedItem = m_ui->listWidget->currentItem(); - QString previousText; - if (previousSelectedItem != nullptr) - previousText = previousSelectedItem->text(); - m_ui->listWidget->clear(); QDir resDirectory(path); QStringList items = resDirectory.entryList(QStringList() << "*.jpg" << "*.jpeg" << "*.JPG" << "*.JPEG" << "*.png" << "*.PNG" << "*.txt" << "*.TXT", QDir::Files); - for (const QString &item : items) { + for (const QString &item : qAsConst(items)) { QPixmap item_pix; - if (item.endsWith(".txt") || item.endsWith(".TXT")) + if (item.endsWith(".txt") || item.endsWith(".TXT")) { item_pix = QIcon::fromTheme("document-edit-sign").pixmap(20, 20); - else + } + else { item_pix = QPixmap(resDirectory.filePath(item)); + } item_pix = item_pix.scaledToWidth(20); QListWidgetItem *itemW = new QListWidgetItem(item_pix, item); m_ui->listWidget->addItem(itemW); + } - if (previousText != "" and previousText == item) { - int currentIndex = m_ui->listWidget->count() - 1; - m_ui->listWidget->setCurrentRow(currentIndex); - changeItem(currentIndex); - } + if (index >= 0) + m_ui->listWidget->setCurrentRow(index); +} + +void marK::goToNextItem() +{ + int newIndex = m_ui->listWidget->currentRow() + 1; + if (newIndex >= m_ui->listWidget->count()) { + newIndex = 0; } - if (previousText == "") - m_ui->annotatorWidget->clearScene(); + m_ui->listWidget->setCurrentRow(newIndex); + QListWidgetItem *currentItem = m_ui->listWidget->item(newIndex); + changeItem(currentItem, nullptr); } -void marK::changeItem(int currentRow) +void marK::goToPreviousItem() { - QListWidgetItem *currentItem = m_ui->listWidget->item(currentRow); + int newIndex = m_ui->listWidget->currentRow() - 1; + if (newIndex < 0) { + newIndex = m_ui->listWidget->count() - 1; + } + m_ui->listWidget->setCurrentRow(newIndex); + QListWidgetItem *currentItem = m_ui->listWidget->item(newIndex); changeItem(currentItem, nullptr); } @@ -159,8 +201,14 @@ void marK::changeItem(QListWidgetItem *current, QListWidgetItem *previous) QString itemPath = QDir(m_currentDirectory).filePath(current->text()); if (itemPath != m_filepath) { + makeTempFile(); m_filepath = itemPath; m_ui->annotatorWidget->changeItem(itemPath); + retrieveTempFile(); + + if (m_autoSaveType != OutputType::None) { + m_ui->annotatorWidget->setAutoSaveFile(itemPath, m_autoSaveType); + } } } } @@ -175,12 +223,18 @@ void marK::changeDirectory() QString path = QFileDialog::getExistingDirectory(this, "Select Directory", QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (m_currentDirectory == path) + return; + if (!path.isEmpty()) { - if (m_currentDirectory != "") + if (m_currentDirectory != "") { m_watcher->removePath(m_currentDirectory); + } m_currentDirectory = path; m_watcher->addPath(m_currentDirectory); - updateFiles(); + m_ui->annotatorWidget->clearScene(); + m_filepath.clear(); + updateFiles(path); QFontMetrics metrics(m_ui->listLabel->font()); QString elidedText = metrics.elidedText(m_currentDirectory, Qt::ElideMiddle, @@ -224,27 +278,109 @@ void marK::selectClassColor() void marK::savePolygons(OutputType type) { - QString document; + bool success = m_ui->annotatorWidget->saveObjects(m_filepath, type); - if (type == OutputType::XML) - document = Serializer::toXML(m_ui->annotatorWidget->savedPolygons()); - else if (type == OutputType::JSON) - document = Serializer::toJSON(m_ui->annotatorWidget->savedPolygons()); + if (!success) { + QMessageBox msgBox; + msgBox.setText("failed to save annotation"); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + } +} + +void marK::importData() +{ + if (m_filepath.isEmpty()) return; //exiting because this is no image loaded - if (!document.isEmpty()) - { - QString outputFile(m_filepath); - outputFile.replace(QRegularExpression(".jpg|.png|.xpm"), (type == OutputType::XML ? ".xml" : ".json")); + QString filepath = QFileDialog::getOpenFileName(this, "Select File", QDir::homePath(), + "JSON and XML files (*.json *.xml)"); - QFile fileOut(outputFile); + QVector markedClasses; - if (!fileOut.open(QIODevice::WriteOnly | QIODevice::Text)) + markedClasses = m_ui->annotatorWidget->importObjects(filepath); + + if (markedClasses.isEmpty()) { + QMessageBox msgBox; + msgBox.setText("failed to load annotation"); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + // add new classes to comboBox + for (MarkedClass *markedClass : qAsConst(markedClasses)) { + addNewClass(markedClass); + } + m_ui->annotatorWidget->repaint(); +} + +void marK::addNewClass(MarkedClass *markedClass) +{ + for (MarkedClass *existingMarkedClass : qAsConst(m_polygonClasses)) { + if (markedClass->name() == existingMarkedClass->name()) { + markedClass->setColor(existingMarkedClass->color()); return; + } + } + int classQt = m_polygonClasses.size(); + m_polygonClasses << markedClass; + + QPixmap colorPix(70, 45); + colorPix.fill(markedClass->color()); + + m_ui->comboBox->addItem(QIcon(colorPix), markedClass->name()); + m_ui->comboBox->setCurrentIndex(classQt); + + m_ui->annotatorWidget->setCurrentPolygonClass(markedClass); +} + +void marK::makeTempFile() +{ + QDir tempDir = QDir::tempPath(); + if (!tempDir.exists("mark")) + tempDir.mkdir("mark"); - fileOut.write(document.toUtf8()); + QString tempFilePath = Serializer::getTempFileName(m_filepath); - fileOut.close(); + m_ui->annotatorWidget->saveObjects(tempFilePath, OutputType::JSON); +} + +void marK::retrieveTempFile() +{ + QString tempFilePath = Serializer::getTempFileName(m_filepath); + QVector markedClasses; + + markedClasses = m_ui->annotatorWidget->importObjects(tempFilePath); + + for (MarkedClass *markedClass : qAsConst(markedClasses)) { + addNewClass(markedClass); } + m_ui->annotatorWidget->repaint(); } -marK::~marK() = default; +void marK::toggleAutoSave() +{ + QAction *button = qobject_cast(sender()); + QString type = button->text(); + if (type == "Disabled") { + m_ui->annotatorWidget->setAutoSaveFile("", OutputType::None); + m_autoSaveType = OutputType::None; + } + + else if (type == "XML") { + m_ui->annotatorWidget->setAutoSaveFile(m_filepath, OutputType::XML); + m_autoSaveType = OutputType::XML; + } + + else if (type == "JSON") { + m_ui->annotatorWidget->setAutoSaveFile(m_filepath, OutputType::JSON); + m_autoSaveType = OutputType::JSON; + } +} + +marK::~marK() +{ + QDir tempDir = QDir::tempPath(); + if (tempDir.exists("mark")) + tempDir.removeRecursively(); +} diff --git a/src/ui/mark.h b/src/ui/mark.h index 08c7d69aec6342d2871a5b8e943f0989a4c7635a..d9ca1efda87e0722ca8f4906aab16bc5d7a543a0 100644 --- a/src/ui/mark.h +++ b/src/ui/mark.h @@ -35,6 +35,7 @@ class marK : public QMainWindow public: enum class OutputType { + None, XML, JSON }; @@ -50,18 +51,24 @@ public: public: void changeDirectory(); - void changeItem(int currentRow); void changeShape(marK::Shape shape); void updateFiles(); void savePolygons(OutputType type); + void addNewClass(MarkedClass* markedClass); + void makeTempFile(); + void retrieveTempFile(); public slots: void changeItem(QListWidgetItem *current, QListWidgetItem *previous); - void updateFiles(const QString &path); + void updateFiles(const QString &path, const int index = -1); void addNewClass(); void selectClassColor(); void saveToJson() { savePolygons(OutputType::JSON); }; void saveToXml() { savePolygons(OutputType::XML); }; + void importData(); + void goToNextItem(); + void goToPreviousItem(); + void toggleAutoSave(); private: QScopedPointer m_ui; @@ -69,6 +76,7 @@ private: QString m_currentDirectory; QString m_filepath; QVector m_polygonClasses; + OutputType m_autoSaveType; }; #endif // MARK_H diff --git a/src/ui/serializer.cpp b/src/ui/serializer.cpp index e96948989038ef42becab12bbd6553effd1b25e1..48125d600220f4b74af387505124baa44aa7842b 100644 --- a/src/ui/serializer.cpp +++ b/src/ui/serializer.cpp @@ -16,18 +16,62 @@ *************************************************************************/ #include "serializer.h" +#include "markedclass.h" #include -#include +#include #include #include #include #include +#include +#include +#include -QString Serializer::toXML(const QVector& annotatedPolygons) +Serializer::Serializer(const QString &filepath) : + m_filepath(filepath) { - if (annotatedPolygons.isEmpty()) +} + +Serializer::Serializer(const QVector items) : + m_items(items) +{ +} + +QVector Serializer::read() +{ + QVector objects; + + bool fileExists = QFile::exists(m_filepath); + if (fileExists) { + if (m_filepath.endsWith(".xml")) { + objects = this->readXML(); + } + else if (m_filepath.endsWith(".json")) { + objects = this->readJSON(); + } + } + + return objects; +} + +QString Serializer::serialize(marK::OutputType output_type) +{ + if (output_type == marK::OutputType::XML) { + return this->toXML(); + } + else if (output_type == marK::OutputType::JSON) { + return this->toJSON(); + } + + return nullptr; +} + +QString Serializer::toXML() +{ + if (m_items.isEmpty()) { return nullptr; + } QString xmldoc; QXmlStreamWriter xmlWriter(&xmldoc); @@ -36,7 +80,7 @@ QString Serializer::toXML(const QVector& annotatedPolygons) xmlWriter.writeStartElement("annotation"); - for (const Polygon& item : annotatedPolygons) { + for (const Polygon &item : qAsConst(m_items)) { xmlWriter.writeStartElement("object"); xmlWriter.writeStartElement("class"); @@ -45,7 +89,7 @@ QString Serializer::toXML(const QVector& annotatedPolygons) xmlWriter.writeStartElement("polygon"); - for (const QPointF& point : item) { + for (const QPointF &point : item) { xmlWriter.writeStartElement("pt"); xmlWriter.writeStartElement("x"); @@ -75,14 +119,15 @@ QString Serializer::toXML(const QVector& annotatedPolygons) return xmldoc; } -QString Serializer::toJSON(const QVector& annotatedPolygons) +QString Serializer::toJSON() { - if(annotatedPolygons.isEmpty()) + if (m_items.isEmpty()) { return nullptr; + } QJsonArray classesArray; - for (const Polygon& item : annotatedPolygons) { + for (const Polygon &item : qAsConst(m_items)) { QJsonObject recordObject; recordObject.insert("Class", item.polygonClass()->name()); @@ -109,3 +154,148 @@ QString Serializer::toJSON(const QVector& annotatedPolygons) return doc.toJson(); } + +QVector Serializer::readJSON() +{ + QByteArray data = getData(); + + QJsonParseError errorptr; + QJsonDocument doc = QJsonDocument::fromJson(data, &errorptr); + if (doc.isNull()) { + qDebug() << errorptr.errorString(); + } + + QJsonArray polygonArray = doc.array(); + QVector savedPolygons; + + for (const QJsonValue &classObj : qAsConst(polygonArray)) { + Polygon polygon; + auto polygonClass = new MarkedClass(classObj["Class"].toString()); + + polygon.setPolygonClass(polygonClass); + QJsonArray polygonArray = classObj["Polygon"].toArray(); + + for (const QJsonValue &polygonObj : qAsConst(polygonArray)) { + QJsonObject ptObj = polygonObj["pt"].toObject(); + + double x = ptObj.value("x").toString().toDouble(); + double y = ptObj.value("y").toString().toDouble(); + + polygon.append(QPointF(x,y)); + } + + savedPolygons.append(polygon); + } + + return savedPolygons; +} + +QVector Serializer::readXML() +{ + QVector savedObjects; + QByteArray data = getData(); + + QXmlStreamReader xmlReader(data); + xmlReader.readNextStartElement(); // going to first element + + while (!xmlReader.atEnd()) { + QXmlStreamReader::TokenType token = xmlReader.readNext(); + if (token == QXmlStreamReader::StartElement) { + Polygon object; + xmlReader.readNextStartElement(); + + if (xmlReader.name() == "class") { + auto markedClass = new MarkedClass(xmlReader.readElementText()); + object.setPolygonClass(markedClass); + } + + xmlReader.readNextStartElement(); // closing "class" and going to "polygon" + if (xmlReader.name() == "polygon") { + xmlReader.readNextStartElement(); // going to "pt" + + while (xmlReader.name() == "pt") { + xmlReader.readNextStartElement(); // going to "x" value + + double x = xmlReader.readElementText().toDouble(); + + xmlReader.readNextStartElement(); // closing "x" value and going to "y" value + + double y = xmlReader.readElementText().toDouble(); + + object.append(QPointF(x, y)); + + xmlReader.readNextStartElement(); // closing "y" value + + xmlReader.readNextStartElement(); // closing "pt" value + } + } + savedObjects.append(object); + } + } + + if (xmlReader.hasError()) { + qDebug() << xmlReader.error(); + } + + return savedObjects; +} + +QByteArray Serializer::getData() +{ + m_filepath = handleFileNameExtension(m_filepath); + + QFile file(m_filepath); + file.open(QIODevice::ReadOnly|QIODevice::Text); + QByteArray data = file.readAll(); + file.close(); + + return data; +} + +bool Serializer::write(const QString &filepath, marK::OutputType output_type) +{ + if (!m_items.isEmpty()) { + QString filename = handleFileNameExtension(filepath, output_type); + QFile file(filename); + + QString document = serialize(output_type); + + if (!document.isEmpty()) { + bool fileOpened = file.open(QIODevice::WriteOnly|QIODevice::Text); + if (!fileOpened) { + return false; + } + + file.write(document.toUtf8()); + file.close(); + + return true; + } + else { + return false; + } + } + + return false; +} + +QString Serializer::getTempFileName(const QString &filepath) +{ + QString tempFileName = filepath; + tempFileName.remove(0, 1); + + tempFileName = handleFileNameExtension(tempFileName); + + tempFileName.replace("/", "_"); + tempFileName.prepend(QDir::tempPath() + "/mark/"); + + return tempFileName; +} + +QString Serializer::handleFileNameExtension(const QString &str, marK::OutputType output_type) +{ + QString filename(str); + filename.replace(QRegularExpression(".jpg|.jpeg|.png|.xpm"), (output_type == marK::OutputType::XML ? ".xml" : ".json")); + + return filename; +} diff --git a/src/ui/serializer.h b/src/ui/serializer.h index ce95668cd4c017a56ba853317d978c845ff7aeb3..30d39ea57eb4deb54ade86cc6519ed8a55916b33 100644 --- a/src/ui/serializer.h +++ b/src/ui/serializer.h @@ -19,14 +19,38 @@ #define SERIALIZER_H #include "image/polygon.h" +#include "mark.h" #include #include +#include class Serializer { public: - static QString toJSON(const QVector& annotatedPolygons); - static QString toXML(const QVector& annotatedPolygons); + explicit Serializer(const QString& filepath); + explicit Serializer(const QVector items); + + bool write(const QString &filepath, marK::OutputType output_type); + + QVector read(); + + static QString getTempFileName(const QString &filepath); + +private: + QByteArray getData(); + QString toJSON(); + QString toXML(); + + QVector readJSON(); + QVector readXML(); + + QString serialize(marK::OutputType output_type); + static QString handleFileNameExtension(const QString &str, marK::OutputType output_type = marK::OutputType::JSON); + +private: + // put it to work with MarkedObject + QVector m_items; + QString m_filepath; }; #endif // SERIALIZER_H