diff --git a/label_img.cpp b/label_img.cpp index 8428c5a..7af0a5e 100644 --- a/label_img.cpp +++ b/label_img.cpp @@ -1,5 +1,7 @@ #include "label_img.h" +#include "qdir.h" #include +#include #include #include /* fabs */ #include @@ -26,7 +28,12 @@ label_img::label_img(QWidget *parent) void label_img::mouseMoveEvent(QMouseEvent *ev) { - setMousePosition(ev->x(), ev->y()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const QPoint pos = ev->position().toPoint(); +#else + const QPoint pos = ev->pos(); +#endif + setMousePosition(pos.x(), pos.y()); showImage(); emit Mouse_Moved(); @@ -34,7 +41,12 @@ void label_img::mouseMoveEvent(QMouseEvent *ev) void label_img::mousePressEvent(QMouseEvent *ev) { - setMousePosition(ev->x(), ev->y()); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const QPoint pos = ev->position().toPoint(); +#else + const QPoint pos = ev->pos(); +#endif + setMousePosition(pos.x(), pos.y()); if(ev->button() == Qt::RightButton) { @@ -67,20 +79,110 @@ void label_img::mousePressEvent(QMouseEvent *ev) showImage(); } } + else if(ev->button() == Qt::MiddleButton) + { + qDebug() << "Get wheel"; + // double nearestBoxDistance = 99999999999999.; + + // for(int i = 0; i < m_objBoundingBoxes.size(); i++) + // { + // QRectF objBox = m_objBoundingBoxes.at(i).box; + + + // if(objBox.contains(m_relative_mouse_pos_in_ui)) + // { + // m_objBoundingBoxes.at(i).label = (m_objBoundingBoxes.at(i).label + 1) % m_objList.size(); + + // // Визуальный эффект - временное увеличение размера + // QRectF original = box.box; + // box.box.adjust(-5, -5, 5, 5); + // showImage(); + // box.box = original; + + // QTimer::singleShot(100, this, [this]() { + // showImage(); + // }); + // } + // } + + for (auto& box : m_objBoundingBoxes) { + if (box.box.contains(m_relative_mouse_pos_in_ui)) { + box.label = (box.label + 1) % m_objList.size(); + + // Визуальный эффект - временное увеличение размера + QRectF original = box.box; + box.box.adjust(-5, -5, 5, 5); + showImage(); + box.box = original; + + QTimer::singleShot(100, this, [this]() { + showImage(); + }); + + // emit labelChanged(); + // ev->accept(); + } + } + } emit Mouse_Pressed(); } void label_img::mouseReleaseEvent(QMouseEvent *ev) { + Q_UNUSED(ev); emit Mouse_Release(); } +bool label_img::zoomAtPosition(const QPoint& posInUi, int wheelDeltaY) +{ + if(m_inputImg.isNull()) return false; + if(this->width() <= 0 || this->height() <= 0) return false; + if(wheelDeltaY == 0) return false; + + const QPoint clampedPos( + std::clamp(posInUi.x(), 0, this->width() - 1), + std::clamp(posInUi.y(), 0, this->height() - 1)); + const QPointF anchorInImage = cvtAbsoluteToRelativePoint(clampedPos); + + const double zoomStep = 1.1; + const bool isZoomIn = wheelDeltaY > 0; + + if(isZoomIn) + m_zoomFactor = std::min(m_zoomFactor * zoomStep, m_maxZoomFactor); + else + m_zoomFactor = std::max(m_zoomFactor / zoomStep, m_minZoomFactor); + + if(m_zoomFactor <= 1.0) + { + m_zoomFactor = 1.0; + m_viewTopLeftInImage = QPointF(0.0, 0.0); + } + else + { + const QPointF visibleSize = getVisibleRegionSize(); + const QPointF anchorInUi(static_cast(clampedPos.x()) / this->width(), + static_cast(clampedPos.y()) / this->height()); + + m_viewTopLeftInImage.setX(anchorInImage.x() - anchorInUi.x() * visibleSize.x()); + m_viewTopLeftInImage.setY(anchorInImage.y() - anchorInUi.y() * visibleSize.y()); + clampViewTopLeft(); + } + + showImage(); + return true; +} + void label_img::init() { m_objBoundingBoxes.clear(); m_bLabelingStarted = false; m_focusedObjectLabel = 0; + m_zoomFactor = 1.0; + m_minZoomFactor = 1.0; + m_maxZoomFactor = 20.0; + m_drawLineThickness = 3; + m_viewTopLeftInImage = QPointF(0.0, 0.0); QPoint mousePosInUi = this->mapFromGlobal(QCursor::pos()); bool mouse_is_in_image = QRect(0, 0, this->width(), this->height()).contains(mousePosInUi); @@ -127,6 +229,8 @@ void label_img::openImage(const QString &qstrImg, bool &ret) m_inputImg = m_inputImg.convertToFormat(QImage::Format_RGB888); m_resized_inputImg = m_inputImg.scaled(this->width(), this->height(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation) .convertToFormat(QImage::Format_RGB888); + m_zoomFactor = 1.0; + m_viewTopLeftInImage = QPointF(0.0, 0.0); m_bLabelingStarted = false; @@ -147,11 +251,26 @@ void label_img::openImage(const QString &qstrImg, bool &ret) void label_img::showImage() { if(m_inputImg.isNull()) return; - if(m_resized_inputImg.width() != this->width() or m_resized_inputImg.height() != this->height()) - { - m_resized_inputImg = m_inputImg.scaled(this->width(), this->height(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation) - .convertToFormat(QImage::Format_RGB888); - } + clampViewTopLeft(); + + const QPointF visibleRegionSize = getVisibleRegionSize(); + int cropX = static_cast(m_viewTopLeftInImage.x() * m_inputImg.width() + 0.5); + int cropY = static_cast(m_viewTopLeftInImage.y() * m_inputImg.height() + 0.5); + int cropW = static_cast(visibleRegionSize.x() * m_inputImg.width() + 0.5); + int cropH = static_cast(visibleRegionSize.y() * m_inputImg.height() + 0.5); + + cropW = std::max(cropW, 1); + cropH = std::max(cropH, 1); + + if(cropX + cropW > m_inputImg.width()) cropX = m_inputImg.width() - cropW; + if(cropY + cropH > m_inputImg.height()) cropY = m_inputImg.height() - cropH; + cropX = std::clamp(cropX, 0, std::max(m_inputImg.width() - 1, 0)); + cropY = std::clamp(cropY, 0, std::max(m_inputImg.height() - 1, 0)); + + const QRect cropRect(cropX, cropY, cropW, cropH); + m_resized_inputImg = m_inputImg.copy(cropRect) + .scaled(this->width(), this->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation) + .convertToFormat(QImage::Format_RGB888); QImage img = m_resized_inputImg; @@ -164,7 +283,7 @@ void label_img::showImage() font.setBold(true); painter.setFont(font); - int penThick = 3; + int penThick = m_drawLineThickness; QColor crossLineColor(255, 187, 0); @@ -179,10 +298,90 @@ void label_img::showImage() void label_img::loadLabelData(const QString& labelFilePath) { + //qDebug() << "Trying to load label file:" << labelFilePath; + m_objBoundingBoxes.clear(); + + QFile file(labelFilePath); + if (!file.exists()) { + //qDebug() << "File does not exist:" << labelFilePath; + return; + } + + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + //qDebug() << "File opened successfully"; + QTextStream in(&file); + + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (line.isEmpty()) continue; + + QStringList values = line.split(' ', Qt::SkipEmptyParts); + if (values.size() != 5) { + //qDebug() << "Invalid line format:" << line; + continue; + } + + bool ok; + ObjectLabelingBox objBox; + + // Парсим класс + objBox.label = values[0].toInt(&ok); + if (!ok) { + //qDebug() << "Invalid class id:" << values[0]; + continue; + } + + // Парсим координаты + double midX = values[1].toDouble(&ok); + if (!ok || midX < 0 || midX > 1) { + //qDebug() << "Invalid x_center:" << values[1]; + continue; + } + + double midY = values[2].toDouble(&ok); + if (!ok || midY < 0 || midY > 1) { + //qDebug() << "Invalid y_center:" << values[2]; + continue; + } + + double width = values[3].toDouble(&ok); + if (!ok || width <= 0 || width > 1) { + //qDebug() << "Invalid width:" << values[3]; + continue; + } + + double height = values[4].toDouble(&ok); + if (!ok || height <= 0 || height > 1) { + //qDebug() << "Invalid height:" << values[4]; + continue; + } + + // Рассчитываем координаты прямоугольника + double leftX = midX - width/2.0; + double topY = midY - height/2.0; + + objBox.box.setRect(leftX, topY, width, height); + m_objBoundingBoxes.push_back(objBox); + + //qDebug() << "Loaded box:" << objBox.label << leftX << topY << width << height; + } + file.close(); + } else { + qDebug() << "Failed to open file:" << labelFilePath + << "Error:" << file.errorString(); + } + + //showImage(); // Обновляем отображение +} + +void loadLabelData(const QString& labelFilePath) +{ + //qDebug() << "Inside loadLabelData"; ifstream inputFile(qPrintable(labelFilePath)); if(inputFile.is_open()) { + //qDebug() << "loadLabelData " << labelFilePath << " is open"; double inputFileValue; QVector inputFileValues; @@ -192,6 +391,10 @@ void label_img::loadLabelData(const QString& labelFilePath) for(int i = 0; i < inputFileValues.size(); i += 5) { try { + // 0 0.304567 0.628547 0.094422 0.044712 + // 0 0.815766 0.636715 0.091787 0.043852 + + //qDebug() << "Data loadLabelData" << inputFileValues.data(); ObjectLabelingBox objBox; objBox.label = static_cast(inputFileValues.at(i)); @@ -209,10 +412,10 @@ void label_img::loadLabelData(const QString& labelFilePath) objBox.box.setWidth(width); objBox.box.setHeight(height); - m_objBoundingBoxes.push_back(objBox); + // m_objBoundingBoxes.push_back(objBox); } catch (const std::out_of_range& e) { -// std::cout << "loadLabelData: Out of Range error."; + std::cout << "loadLabelData: Out of Range error."; } } } @@ -384,10 +587,17 @@ QRectF label_img::getRelativeRectFromTwoPoints(QPointF p1, QPointF p2) QRect label_img::cvtRelativeToAbsoluteRectInUi(QRectF rectF) { - return QRect(static_cast(rectF.x() * this->width() + 0.5), - static_cast(rectF.y() * this->height()+ 0.5), - static_cast(rectF.width() * this->width()+ 0.5), - static_cast(rectF.height()* this->height()+ 0.5)); + const QPointF visibleSize = getVisibleRegionSize(); + + const double x = (rectF.x() - m_viewTopLeftInImage.x()) / visibleSize.x(); + const double y = (rectF.y() - m_viewTopLeftInImage.y()) / visibleSize.y(); + const double w = rectF.width() / visibleSize.x(); + const double h = rectF.height() / visibleSize.y(); + + return QRect(static_cast(x * this->width() + 0.5), + static_cast(y * this->height()+ 0.5), + static_cast(w * this->width()+ 0.5), + static_cast(h * this->height()+ 0.5)); } QRect label_img::cvtRelativeToAbsoluteRectInImage(QRectF rectF) @@ -400,12 +610,28 @@ QRect label_img::cvtRelativeToAbsoluteRectInImage(QRectF rectF) QPoint label_img::cvtRelativeToAbsolutePoint(QPointF p) { - return QPoint(static_cast(p.x() * this->width() + 0.5), static_cast(p.y() * this->height() + 0.5)); + const QPointF visibleSize = getVisibleRegionSize(); + + const double x = (p.x() - m_viewTopLeftInImage.x()) / visibleSize.x(); + const double y = (p.y() - m_viewTopLeftInImage.y()) / visibleSize.y(); + + return QPoint(static_cast(x * this->width() + 0.5), static_cast(y * this->height() + 0.5)); } QPointF label_img::cvtAbsoluteToRelativePoint(QPoint p) { - return QPointF(static_cast(p.x()) / this->width(), static_cast(p.y()) / this->height()); + if(this->width() <= 0 || this->height() <= 0) + return QPointF(0.0, 0.0); + + const QPointF visibleSize = getVisibleRegionSize(); + + const double uiX = std::clamp(static_cast(p.x()) / this->width(), 0.0, 1.0); + const double uiY = std::clamp(static_cast(p.y()) / this->height(), 0.0, 1.0); + + const double imgX = m_viewTopLeftInImage.x() + uiX * visibleSize.x(); + const double imgY = m_viewTopLeftInImage.y() + uiY * visibleSize.y(); + + return QPointF(imgX, imgY); } void label_img::setContrastGamma(float gamma) @@ -418,3 +644,84 @@ void label_img::setContrastGamma(float gamma) } showImage(); } + +void label_img::zoomIn() +{ + setZoomFactor(m_zoomFactor * 1.25); +} + +void label_img::zoomOut() +{ + setZoomFactor(m_zoomFactor / 1.25); +} + +void label_img::setZoomFactor(double zoomFactor) +{ + if(m_inputImg.isNull()) return; + + const double clampedZoom = std::clamp(zoomFactor, m_minZoomFactor, m_maxZoomFactor); + if(std::abs(clampedZoom - m_zoomFactor) < 1e-9) return; + + const QPointF oldVisibleSize = getVisibleRegionSize(); + const QPointF viewCenter(m_viewTopLeftInImage.x() + oldVisibleSize.x() * 0.5, + m_viewTopLeftInImage.y() + oldVisibleSize.y() * 0.5); + + m_zoomFactor = clampedZoom; + + const QPointF newVisibleSize = getVisibleRegionSize(); + m_viewTopLeftInImage.setX(viewCenter.x() - newVisibleSize.x() * 0.5); + m_viewTopLeftInImage.setY(viewCenter.y() - newVisibleSize.y() * 0.5); + clampViewTopLeft(); + showImage(); +} + +double label_img::zoomFactor() const +{ + return m_zoomFactor; +} + +void label_img::panByUiPixels(int dx, int dy) +{ + if(m_inputImg.isNull()) return; + if(m_zoomFactor <= 1.0) return; + if(this->width() <= 0 || this->height() <= 0) return; + + const QPointF visibleSize = getVisibleRegionSize(); + const double dxInImage = visibleSize.x() * static_cast(dx) / this->width(); + const double dyInImage = visibleSize.y() * static_cast(dy) / this->height(); + + m_viewTopLeftInImage.setX(m_viewTopLeftInImage.x() + dxInImage); + m_viewTopLeftInImage.setY(m_viewTopLeftInImage.y() + dyInImage); + clampViewTopLeft(); + showImage(); +} + +void label_img::setLineThickness(int thickness) +{ + m_drawLineThickness = std::max(1, thickness); + showImage(); +} + +int label_img::lineThickness() const +{ + return m_drawLineThickness; +} + +void label_img::clampViewTopLeft() +{ + const QPointF visibleSize = getVisibleRegionSize(); + + const double maxX = std::max(0.0, 1.0 - visibleSize.x()); + const double maxY = std::max(0.0, 1.0 - visibleSize.y()); + + m_viewTopLeftInImage.setX(std::clamp(m_viewTopLeftInImage.x(), 0.0, maxX)); + m_viewTopLeftInImage.setY(std::clamp(m_viewTopLeftInImage.y(), 0.0, maxY)); +} + +QPointF label_img::getVisibleRegionSize() const +{ + const double safeZoomFactor = std::max(m_zoomFactor, 1.0); + const double width = 1.0 / safeZoomFactor; + const double height = 1.0 / safeZoomFactor; + return QPointF(width, height); +} diff --git a/label_img.h b/label_img.h index 157f8bc..31bb3fc 100644 --- a/label_img.h +++ b/label_img.h @@ -43,6 +43,14 @@ class label_img : public QLabel void init(); void openImage(const QString &, bool& ret); void showImage(); + bool zoomAtPosition(const QPoint& posInUi, int wheelDeltaY); + void zoomIn(); + void zoomOut(); + void setZoomFactor(double zoomFactor); + double zoomFactor() const; + void panByUiPixels(int dx, int dy); + void setLineThickness(int thickness); + int lineThickness() const; void loadLabelData(const QString & ); @@ -79,11 +87,19 @@ class label_img : public QLabel QPointF m_relative_mouse_pos_in_ui; QPointF m_relatvie_mouse_pos_LBtnClicked_in_ui; + QPointF m_viewTopLeftInImage; + + double m_zoomFactor; + double m_minZoomFactor; + double m_maxZoomFactor; + int m_drawLineThickness; unsigned char m_gammatransform_lut[256]; QVector colorTable; void setMousePosition(int , int); + void clampViewTopLeft(); + QPointF getVisibleRegionSize() const; void drawCrossLine(QPainter& , QColor , int thickWidth = 3); void drawFocusedObjectBox(QPainter& , Qt::GlobalColor , int thickWidth = 3); diff --git a/mainwindow.cpp b/mainwindow.cpp index 34cccc4..4fc6cac 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -22,6 +24,20 @@ MainWindow::MainWindow(QWidget *parent) : { ui->setupUi(this); + ui->textEdit_marker->setStyleSheet( + "background-color : rgb(0, 0, 17);" + "color : rgb(0, 255, 255);" + "border-style: outset;" + "border-width: 2px;" + "border-color: rgb(0, 255, 255);" + ); + ui->textEdit_marker->setFont(QFont("Courier New", 10)); // Моноширинный шрифт для удобства + ui->textEdit_marker->setPlaceholderText("No labels found. Add labels in format:\n "); + + // Подключаем сигнал изменения текста + connect(ui->textEdit_marker, &QTextEdit::textChanged, + this, &MainWindow::on_textEdit_marker_textChanged); + connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_S), this), SIGNAL(activated()), this, SLOT(save_label_data())); connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_C), this), SIGNAL(activated()), this, SLOT(clear_label_data())); @@ -32,6 +48,18 @@ MainWindow::MainWindow(QWidget *parent) : connect(new QShortcut(QKeySequence(Qt::Key_Space), this), SIGNAL(activated()), this, SLOT(next_img())); connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_D), this), SIGNAL(activated()), this, SLOT(remove_img())); connect(new QShortcut(QKeySequence(Qt::Key_Delete), this), SIGNAL(activated()), this, SLOT(remove_img())); + connect(new QShortcut(QKeySequence(Qt::Key_Plus), this), SIGNAL(activated()), this, SLOT(on_pushButton_zoom_in_clicked())); + connect(new QShortcut(QKeySequence(Qt::Key_Equal), this), SIGNAL(activated()), this, SLOT(on_pushButton_zoom_in_clicked())); + connect(new QShortcut(QKeySequence(Qt::Key_Minus), this), SIGNAL(activated()), this, SLOT(on_pushButton_zoom_out_clicked())); + connect(new QShortcut(QKeySequence(Qt::Key_Underscore), this), SIGNAL(activated()), this, SLOT(on_pushButton_zoom_out_clicked())); + + connect(ui->pushButton_zoom_in, &QPushButton::clicked, this, &MainWindow::on_pushButton_zoom_in_clicked); + connect(ui->pushButton_zoom_out, &QPushButton::clicked, this, &MainWindow::on_pushButton_zoom_out_clicked); + connect(ui->spinBox_line_thickness, qOverload(&QSpinBox::valueChanged), + this, &MainWindow::on_spinBox_line_thickness_valueChanged); + + ui->label_image->setLineThickness(ui->spinBox_line_thickness->value()); + update_zoom_label(); init_table_widget(); } @@ -108,6 +136,7 @@ void MainWindow::set_focused_file(const int fileIndex) void MainWindow::goto_img(const int fileIndex) { + qDebug() << "goto_img with index" << fileIndex; bool bIndexIsOutOfRange = (fileIndex < 0 || fileIndex > m_imgList.size() - 1); if (bIndexIsOutOfRange) return; @@ -118,6 +147,9 @@ void MainWindow::goto_img(const int fileIndex) ui->label_image->loadLabelData(get_labeling_data(m_imgList.at(m_imgIndex))); ui->label_image->showImage(); + update_zoom_label(); + + load_label_file_to_textedit(); set_label_progress(m_imgIndex); set_focused_file(m_imgIndex); @@ -142,37 +174,72 @@ void MainWindow::prev_img(bool bSavePrev) void MainWindow::save_label_data() { - if(m_imgList.size() == 0) return; + if (m_imgList.size() == 0 || m_imgIndex < 0 || m_imgIndex >= m_imgList.size()) + return; - QString qstrOutputLabelData = get_labeling_data(m_imgList.at(m_imgIndex)); - ofstream fileOutputLabelData(qPrintable(qstrOutputLabelData)); + QString labelFilePath = get_labeling_data(m_imgList.at(m_imgIndex)); + QFile file(labelFilePath); - if(fileOutputLabelData.is_open()) - { - for(int i = 0; i < ui->label_image->m_objBoundingBoxes.size(); i++) - { - ObjectLabelingBox objBox = ui->label_image->m_objBoundingBoxes[i]; - - double midX = objBox.box.x() + objBox.box.width() / 2.; - double midY = objBox.box.y() + objBox.box.height() / 2.; - double width = objBox.box.width(); - double height = objBox.box.height(); - - fileOutputLabelData << objBox.label; - fileOutputLabelData << " "; - fileOutputLabelData << std::fixed << std::setprecision(6) << midX; - fileOutputLabelData << " "; - fileOutputLabelData << std::fixed << std::setprecision(6) << midY; - fileOutputLabelData << " "; - fileOutputLabelData << std::fixed << std::setprecision(6) << width; - fileOutputLabelData << " "; - fileOutputLabelData << std::fixed << std::setprecision(6) << height << std::endl; + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&file); + out.setRealNumberPrecision(6); + out.setRealNumberNotation(QTextStream::FixedNotation); + + for (const auto& objBox : ui->label_image->m_objBoundingBoxes) { + double midX = objBox.box.x() + objBox.box.width() / 2.0; + double midY = objBox.box.y() + objBox.box.height() / 2.0; + double width = objBox.box.width(); + double height = objBox.box.height(); + + out << objBox.label << " " + << midX << " " + << midY << " " + << width << " " + << height << "\n"; } + m_lastLabeledImgIndex = m_imgIndex; - fileOutputLabelData.close(); + file.close(); + //qDebug() << "Labels saved to:" << labelFilePath; + } else { + qDebug() << "Failed to open file for writing:" << labelFilePath; + qDebug() << "Error:" << file.errorString(); } } +// void MainWindow::save_label_data() +// { +// if(m_imgList.size() == 0) return; + +// QString qstrOutputLabelData = get_labeling_data(m_imgList.at(m_imgIndex)); +// ofstream fileOutputLabelData(qPrintable(qstrOutputLabelData)); + +// if(fileOutputLabelData.is_open()) +// { +// for(int i = 0; i < ui->label_image->m_objBoundingBoxes.size(); i++) +// { +// ObjectLabelingBox objBox = ui->label_image->m_objBoundingBoxes[i]; + +// double midX = objBox.box.x() + objBox.box.width() / 2.; +// double midY = objBox.box.y() + objBox.box.height() / 2.; +// double width = objBox.box.width(); +// double height = objBox.box.height(); + +// fileOutputLabelData << objBox.label; +// fileOutputLabelData << " "; +// fileOutputLabelData << std::fixed << std::setprecision(6) << midX; +// fileOutputLabelData << " "; +// fileOutputLabelData << std::fixed << std::setprecision(6) << midY; +// fileOutputLabelData << " "; +// fileOutputLabelData << std::fixed << std::setprecision(6) << width; +// fileOutputLabelData << " "; +// fileOutputLabelData << std::fixed << std::setprecision(6) << height << std::endl; +// } +// m_lastLabeledImgIndex = m_imgIndex; +// fileOutputLabelData.close(); +// } +// } + void MainWindow::clear_label_data() { ui->label_image->m_objBoundingBoxes.clear(); @@ -258,7 +325,8 @@ QString MainWindow::get_labeling_data(QString qstrImgFile)const { string strImgFile = qstrImgFile.toStdString(); string strLabelData = strImgFile.substr(0, strImgFile.find_last_of('.')) + ".txt"; - + qDebug() << "get_labeling_data image path: " << qstrImgFile; + qDebug() << "get_labeling_data txt path: " << QString().fromStdString(strLabelData); return QString().fromStdString(strLabelData); } @@ -383,6 +451,15 @@ void MainWindow::keyPressEvent(QKeyEvent * event) { int nKey = event->key(); + if(ui->label_image->zoomFactor() > 1.0) + { + const int panStepUiPx = 40; + if(nKey == Qt::Key_Left) { ui->label_image->panByUiPixels(-panStepUiPx, 0); return; } + if(nKey == Qt::Key_Right) { ui->label_image->panByUiPixels( panStepUiPx, 0); return; } + if(nKey == Qt::Key_Up) { ui->label_image->panByUiPixels(0, -panStepUiPx); return; } + if(nKey == Qt::Key_Down) { ui->label_image->panByUiPixels(0, panStepUiPx); return; } + } + bool graveAccentKeyIsPressed = (nKey == Qt::Key_QuoteLeft); bool numKeyIsPressed = (nKey >= Qt::Key_0 && nKey <= Qt::Key_9 ); @@ -430,6 +507,7 @@ void MainWindow::on_tableWidget_label_cellDoubleClicked(int row, int column) void MainWindow::on_tableWidget_label_cellClicked(int row, int column) { + Q_UNUSED(column); set_label(row); } @@ -481,3 +559,93 @@ void MainWindow::on_checkBox_visualize_class_name_clicked(bool checked) ui->label_image->m_bVisualizeClassName = checked; ui->label_image->showImage(); } + +void MainWindow::on_pushButton_zoom_in_clicked() +{ + ui->label_image->zoomIn(); + update_zoom_label(); +} + +void MainWindow::on_pushButton_zoom_out_clicked() +{ + ui->label_image->zoomOut(); + update_zoom_label(); +} + +void MainWindow::on_spinBox_line_thickness_valueChanged(int value) +{ + ui->label_image->setLineThickness(value); +} + +void MainWindow::load_label_file_to_textedit() +{ + if(m_imgList.size() == 0 || m_imgIndex < 0) return; + + QString labelFile = get_labeling_data(m_imgList.at(m_imgIndex)); + QFile file(labelFile); + + if(file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QTextStream in(&file); + ui->textEdit_marker->setPlainText(in.readAll()); + file.close(); + } + else + { + ui->textEdit_marker->clear(); + ui->textEdit_marker->setPlaceholderText("No label file found. Add labels in format:\n "); + } + + // Разрешаем редактирование + ui->textEdit_marker->setReadOnly(false); + + // Устанавливаем минимальную ширину + int min_width = calculate_max_text_width(); + ui->textEdit_marker->setMinimumWidth(min_width); +} + +void MainWindow::on_textEdit_marker_textChanged() +{ + if(m_imgList.size() == 0 || m_imgIndex < 0) return; + + // Сохраняем изменения в файл + QString labelFile = get_labeling_data(m_imgList.at(m_imgIndex)); + QFile file(labelFile); + + if(file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream out(&file); + out << ui->textEdit_marker->toPlainText(); + file.close(); + + // Обновляем отображение + ui->label_image->loadLabelData(labelFile); + ui->label_image->showImage(); + } +} + +int MainWindow::calculate_max_text_width() const +{ + QFontMetrics fm(ui->textEdit_marker->font()); + int max_width = 0; + + QString text = ui->textEdit_marker->toPlainText(); + QStringList lines = text.split('\n'); + + for (const QString& line : lines) { + int line_width = fm.horizontalAdvance(line); + if (line_width > max_width) { + max_width = line_width; + } + } + + // Добавляем отступы и рамку + return max_width + ui->textEdit_marker->contentsMargins().left() + + ui->textEdit_marker->contentsMargins().right() + 20; +} + +void MainWindow::update_zoom_label() +{ + const int zoomPercent = static_cast(ui->label_image->zoomFactor() * 100.0 + 0.5); + ui->label_zoom_value->setText(QString("Zoom: %1%").arg(zoomPercent)); +} diff --git a/mainwindow.h b/mainwindow.h index f958522..3f16d7a 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -45,6 +45,11 @@ private slots: void on_horizontalSlider_contrast_sliderMoved(int value); void on_checkBox_visualize_class_name_clicked(bool checked); + void on_pushButton_zoom_in_clicked(); + void on_pushButton_zoom_out_clicked(); + void on_spinBox_line_thickness_valueChanged(int value); + + void on_textEdit_marker_textChanged(); // Добавляем в секцию private slots private: void init(); @@ -80,6 +85,10 @@ private slots: int m_lastDeletedImgIndex; int m_lastLabeledImgIndex; + void load_label_file_to_textedit(); // Добавляем в секцию private + int calculate_max_text_width() const; + void update_zoom_label(); + protected: void wheelEvent(QWheelEvent *); }; diff --git a/mainwindow.ui b/mainwindow.ui index 18c7baa..1e70368 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -29,7 +29,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus YoloLabel @@ -79,7 +79,6 @@ Arial 18 - 75 true @@ -98,10 +97,10 @@ border-color: rgb(0, 255, 255);} - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Plain + QFrame::Shadow::Plain 3 @@ -113,7 +112,7 @@ border-color: rgb(0, 255, 255);} true - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter -1 @@ -152,7 +151,7 @@ border-color: rgb(0, 255, 255);} - Qt::NoFocus + Qt::FocusPolicy::NoFocus @@ -183,7 +182,7 @@ QSlider::handle:horizontal { true - Qt::Horizontal + Qt::Orientation::Horizontal @@ -211,7 +210,6 @@ QSlider::handle:horizontal { Arial 12 - 75 true @@ -222,7 +220,7 @@ border-width: 2px; border-color: rgb(0, 255, 255); - QFrame::StyledPanel + QFrame::Shape::StyledPanel 2 @@ -231,7 +229,7 @@ border-color: rgb(0, 255, 255); Contrast - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -263,7 +261,7 @@ border-color: rgb(0, 255, 255); - Qt::NoFocus + Qt::FocusPolicy::NoFocus @@ -294,7 +292,7 @@ QSlider::handle:horizontal { true - Qt::Horizontal + Qt::Orientation::Horizontal @@ -322,7 +320,6 @@ QSlider::handle:horizontal { Arial 12 - 75 true @@ -333,7 +330,7 @@ border-width: 2px; border-color: rgb(0, 255, 255); - QFrame::StyledPanel + QFrame::Shape::StyledPanel 2 @@ -342,7 +339,7 @@ border-color: rgb(0, 255, 255); 0 / 0 - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter @@ -380,13 +377,12 @@ border-color: rgb(0, 255, 255); Arial 12 - 75 true true - Qt::NoFocus + Qt::FocusPolicy::NoFocus background-color : rgb(0, 0, 17);color : rgb(0, 255, 255); @@ -432,12 +428,11 @@ border-color: rgb(0, 255, 255); Arial 12 - 75 true - Qt::NoFocus + Qt::FocusPolicy::NoFocus background-color : rgb(0, 0, 17);color : rgb(0, 255, 255); @@ -453,10 +448,13 @@ border-color: rgb(0, 255, 255); <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Arial'; font-size:12pt; font-weight:600; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Gulim'; font-size:9pt;">Last Labeled Image:<br />Current Image:</span></p></body></html> +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Arial'; font-size:12pt; font-weight:700; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Gulim'; font-size:9pt; font-weight:600;">Last Labeled Image:<br />Current Image:</span></p></body></html> @@ -465,37 +463,44 @@ p, li { white-space: pre-wrap; } - - - - 0 - 0 - + + + QLayout::SizeConstraint::SetMinimumSize - - - 220 - 360 - - - - - 330 - 16777215 - + + 0 - - - 10 - 75 - true - - - - Qt::NoFocus - - - QHeaderView::section { + + + + + 0 + 0 + + + + + 250 + 360 + + + + + 360 + 16777215 + + + + + 10 + true + + + + Qt::FocusPolicy::NoFocus + + + QHeaderView::section { background-color: rgb(0, 0, 17); color: rgb(255, 187, 0); padding-left: 4px; @@ -516,66 +521,96 @@ QTableView { selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 1.0, y2: 1.0, stop: 0 rgb(34, 0, 85), stop: 1 white); selection-color: rgb(0, 255, 0); } - - - QFrame::WinPanel - - - QFrame::Plain - - - 5 - - - true - - - false - - - true - - - true - - - 2 - - - false - - - 40 - - - false - - - true - - - true - - - false - - - false - - - false - - - - Name - - - - - Color - - - + + + QFrame::Shape::WinPanel + + + QFrame::Shadow::Plain + + + 5 + + + true + + + false + + + true + + + true + + + 2 + + + false + + + 40 + + + false + + + true + + + true + + + false + + + false + + + false + + + + Name + + + + + Color + + + + + + + + + 0 + 1 + + + + + 250 + 200 + + + + + 360 + 16777215 + + + + + Courier New + 8 + + + + + @@ -603,6 +638,167 @@ QTableView { + + + + + 110 + 0 + + + + + Arial + 12 + true + + + + background-color : rgb(0, 0, 17);color : rgb(0, 255, 255); + + + Zoom: 100% + + + + + + + + 32 + 24 + + + + + 32 + 24 + + + + + Arial + 12 + true + + + + Qt::FocusPolicy::NoFocus + + + background-color : rgb(0, 0, 17);color : rgb(0, 255, 255); +border-style: outset; +border-width: 2px; +border-color: rgb(0, 255, 255); + + + - + + + + + + + + 32 + 24 + + + + + 32 + 24 + + + + + Arial + 12 + true + + + + Qt::FocusPolicy::NoFocus + + + background-color : rgb(0, 0, 17);color : rgb(0, 255, 255); +border-style: outset; +border-width: 2px; +border-color: rgb(0, 255, 255); + + + + + + + + + + + + Arial + 12 + true + + + + background-color : rgb(0, 0, 17);color : rgb(0, 255, 255); + + + Line Thickness + + + + + + + + 64 + 24 + + + + + Arial + 11 + true + + + + Qt::FocusPolicy::StrongFocus + + + background-color : rgb(0, 0, 17);color : rgb(0, 255, 255); +border-style: outset; +border-width: 2px; +border-color: rgb(0, 255, 255); + + + QAbstractSpinBox::ButtonSymbols::NoButtons + + + 1 + + + 12 + + + 3 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + +