diff --git a/SimpleLabel.pro b/SimpleLabel.pro new file mode 100644 index 0000000..a6f165d --- /dev/null +++ b/SimpleLabel.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS += SimpleLabel +SUBDIRS += SimpleLabelTests \ No newline at end of file diff --git a/SimpleLabel/About.cpp b/SimpleLabel/About.cpp new file mode 100644 index 0000000..81e0705 --- /dev/null +++ b/SimpleLabel/About.cpp @@ -0,0 +1,37 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ +#include "About.h" + + +About::About(QWidget *parent, Qt::WindowFlags flags) + : QDialog(parent, flags) +{ + ui.setupUi(this); + + this->setWindowTitle(this->windowTitle() + " v" + QCoreApplication::applicationVersion()); +} + +About::~About(void) +{ +} diff --git a/SimpleLabel/About.h b/SimpleLabel/About.h new file mode 100644 index 0000000..6f44425 --- /dev/null +++ b/SimpleLabel/About.h @@ -0,0 +1,44 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ +#ifndef ABOUT_H +#define ABOUT_H + +#include +#include "ui_AboutDlg.h" + + +class About : + public QDialog +{ + Q_OBJECT + +public: + About(QWidget *parent = 0, Qt::WindowFlags flags = 0); + virtual ~About(void); + +private: + Ui::AboutDlg ui; +}; + +#endif diff --git a/SimpleLabel/AboutDlg.ui b/SimpleLabel/AboutDlg.ui new file mode 100644 index 0000000..afff289 --- /dev/null +++ b/SimpleLabel/AboutDlg.ui @@ -0,0 +1,133 @@ + + + AboutDlg + + + + 0 + 0 + 430 + 380 + + + + + 430 + 380 + + + + + 430 + 380 + + + + SimpleLabel + + + true + + + + + + Qt::Horizontal + + + + 108 + 31 + + + + + + + + OK + + + + + + + Qt::Horizontal + + + + 101 + 31 + + + + + + + + false + + + QFrame::NoFrame + + + QFrame::Plain + + + QPlainTextEdit::WidgetWidth + + + true + + + SimpleLabel - a simple and light program for semi automatic labeling of regions of interest on images or image sequences. +Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Contact: eugene@cse.yorku.ca + + + false + + + true + + + + + + + + + okButton + clicked() + AboutDlg + accept() + + + 278 + 253 + + + 96 + 254 + + + + + diff --git a/SimpleLabel/Constants.h b/SimpleLabel/Constants.h new file mode 100644 index 0000000..19c4497 --- /dev/null +++ b/SimpleLabel/Constants.h @@ -0,0 +1,371 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ + +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include +#include +#include +#include +#include +#include + +#define W_DISPLAYIMAGE 800 +#define H_DISPLAYIMAGE 600 + +#define WINDOW_OFFSET_X 5 +#define WINDOW_OFFSET_Y 25 +#define CORNER_CIRCLE_RAD 5 +#define LENGTH_OF_ROTATION_LINE 20 + +#define INVALID_ANGLE 0xffffffff + +#define ROUND(X) (abs(X -floor(X))<0.5?floor(X):floor(X)+1) +#define sqr(X) ((X)*(X)) + +//#define APP_VERSION "1.0.1" +enum InputType +{ + AviFile, + ImageSequence, + None +}; + +enum LabelShape +{ + Rect, + Polyg, + Other +}; + +enum InterpolationMethod +{ + LinearIntr, + Motion +}; + +struct ViaPoint +{ +public: + ViaPoint() + { + Init(0, 0.0, QRect(0,0,0,0)); + } + + ViaPoint(int frame , float angle, QRect rc) + { + Init(frame, angle, rc); + } + + float angle; + int frame; + QRect rc; + + bool operator == (const ViaPoint& b) + { + return this->angle == b.angle && + this->frame == b.frame && + this->rc == b.rc; + } + + bool operator != (const ViaPoint& b) + { + return !(*this == b); + } + + ViaPoint& operator=(const ViaPoint& b) + { + if(this != &b) + { + this->angle = b.angle; + this->frame = b.frame; + this->rc = b.rc; + } + return *this; + } + +private: + void Init(int fr , float a, QRect r) + { + frame = fr; + angle = a; + rc = r; + } +}; + +struct ViaPointPolygon +{ + int frame; + QPolygon pl; +}; + +struct Label +{ + int number; + QString name; + QString desc; + LabelShape shape; + QList boxes; + QList viaPoints; + QList polygons; + QList viaPointsPoly; + + ViaPoint* findBoxByFrame(int frame) + { + ViaPoint *res = NULL; + int fr; + if(boxes.count() > 0) + { + fr = frame - boxes[0].frame; + if(fr >= 0 && fr < boxes.count()) + res = &(boxes[fr]); + } + + return res; + } + + ViaPointPolygon* findPolygonByFrame(int frame) + { + ViaPointPolygon *res = NULL; + int fr; + if(polygons.count() > 0) + { + fr = frame - polygons[0].frame; + if(fr >= 0 && fr < polygons.count()) + res = &(polygons[fr]); + } + + return res; + } +}; + +class CommonFunctions +{ +public: + static const QPoint NULL_POINT; + static const ViaPoint NULL_RECT; + static const QPolygon NULL_POLYGON; + + static void splitPath(QString fullpath, QString &path, QString &filename, QString &fnamePrefix, uint n, int &index, QString &ext) + { + bool ok; + QString fpath = fullpath; + fpath.replace("\\", "/"); + int dot = fpath.lastIndexOf("."); + int slash = fpath.lastIndexOf("/"); + + path = fullpath.left(slash + 1); + ext = fullpath.right(fullpath.length() - dot); + filename = fullpath.mid(slash + 1, dot - slash - 1); + fnamePrefix = filename.left(filename.length() - n); + index = filename.right(n).toInt(&ok); + if(!ok) + index = -1; + } + + static void splitPath(QString fullpath, QString &path, QString &filename, QString &ext) + { + QString fpath = fullpath; + fpath.replace("\\", "/"); + int dot = fpath.lastIndexOf("."); + int slash = fpath.lastIndexOf("/"); + + path = fullpath.left(slash + 1); + ext = fullpath.right(fullpath.length() - dot); + filename = fullpath.mid(slash + 1, dot - slash - 1); + } + + static int checkForVertices(QPoint p, QRect rc) + { + int res = -1; + if(isCloseToEachOther(rc.topLeft(), p)) + res = 0; + else if(isCloseToEachOther(rc.topRight(), p)) + res = 1; + else if(isCloseToEachOther(rc.bottomRight(), p)) + res = 2; + else if(isCloseToEachOther(rc.bottomLeft(), p)) + res = 3; + + return res; + } + + + static int checkForVerticesWithRotation(QPoint p, ViaPoint vp) + { + QPoint np = rotatePointAboutPoint(p, vp.angle, vp.rc.center()); + QRect rc = vp.rc; + + int res = -1; + if(isCloseToEachOther(rc.topLeft(), np)) + res = 0; + else if(isCloseToEachOther(rc.topRight(), np)) + res = 1; + else if(isCloseToEachOther(rc.bottomRight(), np)) + res = 2; + else if(isCloseToEachOther(rc.bottomLeft(), np)) + res = 3; + + return res; + } + + static bool checkForRotation(QPoint p, ViaPoint vp) + { + QPoint c = vp.rc.center(); + QPoint pt(vp.rc.center().x(), vp.rc.top() - LENGTH_OF_ROTATION_LINE); + + QPoint new_pt = rotatePointAboutPoint(p, vp.angle, c); + + return isCloseToEachOther(pt, new_pt); + } + + static QPoint rotatePointAboutPoint(QPoint p, double ang, QPoint o) + { + QTransform tr; + QPointF p1(p); + QPointF o1(o); + + tr.translate(o1.x(), o1.y()); + tr.rotate(ang); + tr.translate(-o1.x(), -o1.y()); + + QPointF res = tr.map(p1); + + double l1 = vectorLength(p1 - o1); + double l2 = vectorLength(res - o1); + if(l1 - l2 > 0.00001) + qDebug() << "Something is off"; + + return QPoint(qRound(res.x()), qRound(res.y())); + } + + static int checkForVertices(QPoint p, QPolygon pl) + { + int res = -1; + for(int i = 0; i < pl.count(); i++) + { + if(isCloseToEachOther(pl.point(i), p)) + { + res = i; + break; + } + } + + return res; + } + + static double vectorLength(QPoint p) + { + return vectorLength(QPointF(p)); + } + + static double vectorLength(QPointF p) + { + if(p.manhattanLength() == 0) return 0.0; + + return qSqrt( sqr(p.x()) + sqr(p.y()) ); + } + + static double findAngleBetweenVectors2(QPoint v1, QPoint v2) + { + double ang = qAtan2(v1.x(), v1.y()) - qAtan2(v2.x(), v2.y()); + + //converting to degrees + return 180*ang/M_PI; + } + + static QPoint getVertex(QRect rc, int index) + { + QPoint pt = NULL_POINT; + + switch(index) + { + case 0: + pt = rc.topLeft(); + break; + case 1: + pt = rc.topRight(); + break; + case 2: + pt = rc.bottomRight(); + break; + case 3: + pt = rc.bottomLeft(); + break; + default: + break; + } + + return pt; + } + + static QPoint getOpposingVertex(QRect rc, int index) + { + QPoint pt = NULL_POINT; + + switch(index) + { + case 0: + pt = rc.bottomRight(); + break; + case 1: + pt = rc.bottomLeft(); + break; + case 2: + pt = rc.topLeft(); + break; + case 3: + pt = rc.topRight(); + break; + default: + break; + } + + return pt; + } + + private: + static bool isCloseToEachOther(QPoint pl, QPoint p2) + { + bool res = false; + int rad = CORNER_CIRCLE_RAD; + QPoint rt = pl - p2; + float dist = sqr(rt.x()) + sqr(rt.y()); + + if(dist == 0) + { + res = true; + } + else + { + res = sqrt(dist) < rad; + } + + return res; + } +}; + + +#endif diff --git a/SimpleLabel/Monitor.cpp b/SimpleLabel/Monitor.cpp new file mode 100644 index 0000000..880440d --- /dev/null +++ b/SimpleLabel/Monitor.cpp @@ -0,0 +1,300 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ +#include "Monitor.h" +#include +#include + +Monitor::Monitor(QObject *parent) + : QThread(parent) +{ + mCurrImage = NULL; + mCvCapture = NULL; + mCurrentFrameNumber = 0; + stopExec = false; + mInputType = None; + mInitialized = false; +} + +Monitor::~Monitor() +{ + reset(); +} + +void Monitor::reset() +{ + stopExec = false; + mInputType = None; + mFileNamePrefix = ""; + mFileName = ""; + mPath = ""; + mExtention = ""; + mFirstFrameNumber = -1; + mCurrentFrameNumber = -1; + mLastFrameNumber = -1; + mInitialized = false; + + if(mCurrImage) + { + delete mCurrImage; + mCurrImage = NULL; + } +} + +void Monitor::run() +{ + stopExec = false; + int fps = getFPS(); + if(fps < 1) + fps = 30; + + int delay = 1000/fps; + while(mInitialized && !stopExec && mCurrentFrameNumber < mLastFrameNumber) + { + moveToFrame(mCurrentFrameNumber + 1); + emit imageChanged(); + msleep(delay); + } + stopExec = true; +} + +void Monitor::stop() +{ + stopExec = true; +} + +void Monitor::lockImages() +{ + mImMutex.lock(); +} + +void Monitor::releaseImages() +{ + mImMutex.unlock(); +} + + +void Monitor::setCvCapture(CvCapture* c) +{ + int w, h, bpp; + + if(c) + reset(); + + mCvCapture = c; + + if(c) + { + + cvSetCaptureProperty(mCvCapture, CV_CAP_PROP_POS_FRAMES, mCurrentFrameNumber); + IplImage *im = cvQueryFrame(mCvCapture); + w = im->width; + h = im->height; + bpp = im->nChannels; + + lockImages(); + mCurrImage = new QImage(w, h, QImage::Format_ARGB32); + convertRGB2ARGB(im, mCurrImage); + releaseImages(); + mInputType = AviFile; + + mFirstFrameNumber = 0; + mLastFrameNumber = cvGetCaptureProperty(mCvCapture, CV_CAP_PROP_FRAME_COUNT) - 1; + mInitialized = true; + emit imageChanged(); + } +} + +int Monitor::getFPS() +{ + int res = 0; + if(mInitialized) + { + if(mInputType == AviFile) + res = cvGetCaptureProperty(mCvCapture, CV_CAP_PROP_FPS); + } + + return res; +} + +void Monitor::setFisrtFilenameOfSequence(QString fname) +{ + int dot = fname.lastIndexOf("."); + int slash = fname.lastIndexOf("/"); + bool ok; + + + QImage im(fname); + + if(!im.isNull()) + { + if(mCurrImage != NULL) + reset(); + + mPath = fname.left(slash + 1); + mExtention = fname.right(fname.length() - dot); + mFileName = fname.mid(slash + 1, dot - slash - 1); + mFileNamePrefix = mFileName.left(mFileName.length() - 5); + mFirstFrameNumber = mFileName.right(5).toInt(&ok); + mCurrentFrameNumber = mFirstFrameNumber; + mInputType = ImageSequence; + + mCurrImage = new QImage(im); + + if(ok) + { + findLastImageSequenceFrameNumber(); + moveToFrame(mFirstFrameNumber); + } + else + { + mFirstFrameNumber = -1; + mLastFrameNumber = -1; + } + + + mInitialized = true; + emit imageChanged(); + + } + else + { + QMessageBox::information(NULL, "Image Error", "File " + fname + " is not an image or cannot be read."); + } +} + + +int Monitor::getFrameCount() +{ + int f = -1; + if(mInputType == AviFile) + { + if(mCvCapture) + f = (int)cvGetCaptureProperty(mCvCapture, CV_CAP_PROP_FRAME_COUNT); + } + else if(mInputType == ImageSequence) + { + f = mLastFrameNumber - mFirstFrameNumber + 1; + } + + return f; +} + +void Monitor::findLastImageSequenceFrameNumber() +{ + int i = 0; + if(mInputType == ImageSequence) + { + i = mFirstFrameNumber; + //QString s = mPath + mFileNamePrefix + QString("%1").arg(i,5,10,QChar('0')) + mExtention; + + while(QFile::exists(mPath + mFileNamePrefix + QString("%1").arg(i,5,10,QChar('0')) + mExtention)) + { + i++; + } + } + + mLastFrameNumber = i - 1; //-1 because current image does not exist +} + +void Monitor::moveToFrame(int f) +{ + if(mInputType == AviFile) + { + if(mCvCapture) + { + cvSetCaptureProperty(mCvCapture, CV_CAP_PROP_POS_FRAMES, f); + IplImage *im = cvQueryFrame(mCvCapture); + mCurrentFrameNumber = f; + lockImages(); + convertRGB2ARGB(im, mCurrImage); + releaseImages(); + } + } + else if( mInputType == ImageSequence) + { + if(f >= mFirstFrameNumber && f <= mLastFrameNumber) + { + QString fname = mPath + mFileNamePrefix + QString("%1").arg(f, 5, 10, QChar('0')) + mExtention; + + lockImages(); + mCurrImage->load(fname); + mCurrentFrameNumber = f; + releaseImages(); + } + } +} + +void Monitor::convertRGB2ARGB(IplImage *dataIn, QImage *dataOut) +{ + int w = dataIn->width; + int h = dataIn->height; +// int step = dataIn->widthStep; + int c = dataIn->nChannels; + uchar* data = (unsigned char*)dataIn->imageData; + uchar* res = dataOut->bits(); + + int i; + for(i = 0; i < w*h; i++) + { + memcpy(res, data, 3*sizeof(uchar)); + res[3] = 255; + + data += c; //next line in the IplImage + res += 4; + } +} + +void Monitor::convertARGB2RGB(QImage *dataIn, IplImage *dataOut) +{ + int w = dataIn->width(); + int h = dataIn->height(); +// int step = dataIn->widthStep; + int c = dataOut->nChannels; + uchar* res = (unsigned char*)dataOut->imageData; + uchar* data = dataIn->bits(); + + int i; + for(i = 0; i < w*h; i++) + { + memcpy(res, data, 3*sizeof(uchar)); + + data += 4; + res += c; //next line in the IplImage + } +} + + +IplImage* Monitor::getFrame(int f) +{ + cvSetCaptureProperty(mCvCapture, CV_CAP_PROP_POS_FRAMES, f); + return cvQueryFrame(mCvCapture); +} + +QSize Monitor::getImageSize() +{ + if(mInitialized) + return mCurrImage->size(); + else + return QSize(0,0); +} \ No newline at end of file diff --git a/SimpleLabel/Monitor.h b/SimpleLabel/Monitor.h new file mode 100644 index 0000000..9ea7fa2 --- /dev/null +++ b/SimpleLabel/Monitor.h @@ -0,0 +1,91 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ +#ifndef MONITOR_H +#define MONITOR_H + +#include +#include +#include +#include +#include +#include "Constants.h" + +class Monitor : public QThread +{ + Q_OBJECT + +public: + Monitor(QObject *parent = NULL); + virtual ~Monitor(); + + void setCvCapture(CvCapture* c); + CvCapture* getCvCapture(){return mCvCapture;} + void setFisrtFilenameOfSequence(QString fname); + + int getFrameCount(); + IplImage* getFrame(int f); + int getCurrentFrameNumber() { return mCurrentFrameNumber; }; + QImage* getImage() { return mCurrImage;} + QSize getImageSize(); + int getFPS(); + + void lockImages(); + void releaseImages(); + + void stop(); + void moveToFrame(int f); + + void convertARGB2RGB(QImage *dataIn, IplImage *dataOut); + + bool isInitialized() {return mInitialized;} + +protected: + virtual void run(); + +private: + void reset(); + void convertRGB2ARGB(IplImage *dataIn, QImage *dataOut); + void findLastImageSequenceFrameNumber(); + +private: + bool mInitialized; + bool stopExec; + QMutex mImMutex; + CvCapture *mCvCapture; + QString mFileNamePrefix; + QString mFileName; + QString mPath; + QString mExtention; + int mFirstFrameNumber; + int mCurrentFrameNumber; + int mLastFrameNumber; + InputType mInputType; + QImage *mCurrImage; + +signals: + void imageChanged(); +}; + + +#endif // MONITOR_H \ No newline at end of file diff --git a/SimpleLabel/README.txt b/SimpleLabel/README.txt new file mode 100644 index 0000000..0f3d22a --- /dev/null +++ b/SimpleLabel/README.txt @@ -0,0 +1,54 @@ +SimpleLabel is a image labelling tool designed to simplify the task of labelling +targets (or regions of interest) on image sequences while providing simple and +intuitive user interface. At the moment SimpleLabel supports only rectangular regions +and uses linear interpolation to create intermediate marked regions. + +Developed by Eugene Simine at York University, Toronto in Laboratory for Active and +Attentive Vision (http://www.cse.yorku.ca/LAAV/home/) headed by John K. Tsotsos. + +Contact: Eugene Simine or + John K. Tsotsos + +Requirements for development: +Qt4.* +OpenCV2.4.2 + +OS Support: +Windows (XP, Vista, 7) +Linux (Ubuntu 10.04) +MacOS (in theory) +Main development is done on Windows 7, MS VisualStudio 2010 major releases are tested on Ubuntu. + +Installation: +Windows binaries can be obtained from svn server: +https://samson.cse.yorku.ca/svn/SimpleLabel_bin + +Login/Pass: guest/guest + +It requires Microsoft Visual C++ 2010 SP1 Redistributable Package (x86) to be installed +http://www.microsoft.com/download/en/details.aspx?id=8328 + + +Source code is available from: +https://samson.cse.yorku.ca/svn/SimpleLabel + +Login/Pass: guest/guest + + +Windows: +run win_config.bat +That will create MSVS project . +Warning: A minor Qt bug prevents the project from compiling correctly. +To fix it follow these instruction: +1. Open the SimpleLabel project +2. Go to: Project->Properties->Configuration Properties->General->Target Name +3. There will be a number appended to value, should look something like this "SimpleLabel1" +4. Delete that number. The Target Name should have value "SimpleLabel" +5. Click OK +6. Compile the project +Linux: +>qmake SimpleLabel.pro +>make +>./SimpleLabel + + diff --git a/SimpleLabel/Resources/cursor_crosshair.bmp b/SimpleLabel/Resources/cursor_crosshair.bmp new file mode 100644 index 0000000..555e9f8 Binary files /dev/null and b/SimpleLabel/Resources/cursor_crosshair.bmp differ diff --git a/SimpleLabel/Resources/cursor_crosshair_mask.bmp b/SimpleLabel/Resources/cursor_crosshair_mask.bmp new file mode 100644 index 0000000..a7befc1 Binary files /dev/null and b/SimpleLabel/Resources/cursor_crosshair_mask.bmp differ diff --git a/SimpleLabel/SaveDialog.cpp b/SimpleLabel/SaveDialog.cpp new file mode 100644 index 0000000..92ebb22 --- /dev/null +++ b/SimpleLabel/SaveDialog.cpp @@ -0,0 +1,141 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ +#include "SaveDialog.h" +#include +#include + + +SaveDialog::SaveDialog(QWidget *parent, Qt::WindowFlags flags) + : QDialog(parent, flags) +{ + ui.setupUi(this); + connect(ui.rbtnBlackBgrd, SIGNAL(toggled(bool)), this, SLOT(rbtnFormat_toggled(bool))); + connect(ui.rbtnOrigImage, SIGNAL(toggled(bool)), this, SLOT(rbtnFormat_toggled(bool))); + connect(ui.rbtnMatlabStruct, SIGNAL(toggled(bool)), this, SLOT(rbtnFormat_toggled(bool))); + connect(ui.rbtnSimpleLabelXML, SIGNAL(toggled(bool)), this, SLOT(rbtnFormat_toggled(bool))); + connect(ui.rbtLabelMeXML, SIGNAL(toggled(bool)), this, SLOT(rbtnFormat_toggled(bool))); + connect(ui.rbtnSaveAsAVI, SIGNAL(toggled(bool)), this, SLOT(rbtnFormat_toggled(bool))); + connect(ui.rbtnSaveImgSeq, SIGNAL(toggled(bool)), this, SLOT(rbtnFormat_toggled(bool))); + connect(ui.edtFirstImageIndex, SIGNAL(textChanged(const QString &)), this, SLOT(on_edtFileNamePrefix_textChanged(const QString &))); + + QValidator *v = new QIntValidator(0, 99999, this); + ui.edtFirstImageIndex->setValidator(v); + ui.edtLastImageIndex->setValidator(v); + rbtnFormat_toggled(true); +} + +SaveDialog::~SaveDialog(void) +{ +} + + +void SaveDialog::rbtnFormat_toggled(bool b) +{ + if(b) + { + if(ui.rbtnBlackBgrd->isChecked() || ui.rbtnOrigImage->isChecked()) + { + ui.rbtnSaveAsAVI->setEnabled(true); + ui.rbtnSaveImgSeq->setEnabled(true); + } + else + { + ui.rbtnSaveAsAVI->setDisabled(true); + ui.rbtnSaveImgSeq->setDisabled(true); + } + + if(ui.rbtnBlackBgrd->isChecked() || ui.rbtnOrigImage->isChecked()) + { + if(ui.rbtnSaveAsAVI->isChecked()) + { + mBrowseSettings.title = tr("Save As avi"); + mBrowseSettings.ext = tr(".avi"); + mBrowseSettings.filter = tr("AVI Files (*.avi)"); + mBrowseSettings.index = ""; + } + else + { + mBrowseSettings.title = tr("Save As Image Sequense"); + mBrowseSettings.ext = tr(".png"); + mBrowseSettings.filter = tr("Image Files (*.png)"); + mBrowseSettings.index = mBrowseSettings.index.sprintf("%05d",ui.edtFirstImageIndex->text().toInt()); + + } + + } + else if(ui.rbtnMatlabStruct->isChecked()) + { + mBrowseSettings.title = tr("Save As Matlab structure"); + mBrowseSettings.ext = tr(".m"); + mBrowseSettings.filter = tr("Matlab Files (*.m)"); + mBrowseSettings.index = ""; + + } + else if(ui.rbtnSimpleLabelXML->isChecked()) + { + mBrowseSettings.title = tr("Save As SimpleLabel XML"); + mBrowseSettings.ext = tr(".xml"); + mBrowseSettings.filter = tr("XML Files (*.xml)"); + mBrowseSettings.index = ""; + } + else if(ui.rbtLabelMeXML->isChecked()) + { + mBrowseSettings.title = tr("Save As LabelMe XML"); + mBrowseSettings.ext = tr(".xml"); + mBrowseSettings.filter = tr("XML Files (*.xml)"); + mBrowseSettings.index = mBrowseSettings.index.sprintf("%05d",ui.edtFirstImageIndex->text().toInt()); + } + + ui.edtSavePath->setText(mPath + ui.edtFileNamePrefix->text() + mBrowseSettings.index + mBrowseSettings.ext); + } +} + + +void SaveDialog::on_btnBrowse_clicked() +{ + QString saveFile = QFileDialog::getExistingDirectory(this, mBrowseSettings.title, + mPath, + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + + + if(saveFile != "") + { + mPath = saveFile + "/"; + } + + rbtnFormat_toggled(true); +} + +void SaveDialog::on_edtFileNamePrefix_textChanged(const QString &text) +{ + ui.edtSavePath->setText(mPath + text + mBrowseSettings.index + mBrowseSettings.ext); + //rbtnFormat_toggled(true); +} + +QString SaveDialog::fixFileNameForMatlab(QString str) +{ + QString tmp = str.replace("-", "_"); + tmp = tmp.replace("+", "_"); + return tmp; +} \ No newline at end of file diff --git a/SimpleLabel/SaveDialog.h b/SimpleLabel/SaveDialog.h new file mode 100644 index 0000000..bcd2947 --- /dev/null +++ b/SimpleLabel/SaveDialog.h @@ -0,0 +1,62 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ +#ifndef SAVEDIALOG_H +#define SAVEDIALOG_H + +#include +#include "ui_SaveDialog.h" + +class SaveDialog : + public QDialog +{ +Q_OBJECT + +public: + SaveDialog(QWidget *parent = 0, Qt::WindowFlags flags = 0); + virtual ~SaveDialog(void); + +protected slots: + virtual void rbtnFormat_toggled(bool b); + virtual void on_btnBrowse_clicked(); + virtual void on_edtFileNamePrefix_textChanged(const QString &text); + +public: + QString mPath; + QString mPrefix; + Ui::SaveDialogUi ui; + +private: + QString fixFileNameForMatlab(QString str); + +private: + struct BrowseSettings + { + QString title; + QString ext; + QString filter; + QString index; + }mBrowseSettings; +}; + +#endif \ No newline at end of file diff --git a/SimpleLabel/SaveDialog.ui b/SimpleLabel/SaveDialog.ui new file mode 100644 index 0000000..889bc16 --- /dev/null +++ b/SimpleLabel/SaveDialog.ui @@ -0,0 +1,261 @@ + + + SaveDialogUi + + + + 0 + 0 + 551 + 353 + + + + + 0 + 0 + + + + Export Dialog + + + true + + + + + + OK + + + + + + + Cancel + + + + + + + Qt::Horizontal + + + + 131 + 31 + + + + + + + + Export Format + + + + + + Labeled regions superimposed on the black background + + + true + + + + + + + Labeled regions superimposed on original images + + + + + + + Labled regions exported into Matlab structure + + + + + + + Labeled regions exported into SimpleLabel XML file + + + + + + + Labeled regions exported into LabelMe XML file + + + + + + + + + + Save Format + + + + + + Save As avi + + + false + + + + + + + Qt::Horizontal + + + + 140 + 20 + + + + + + + + Save As Image Sequence + + + true + + + + + + + true + + + + + + + + 0 + 0 + + + + Browse... + + + + + + + + + + File Name + + + + + + + Starting image Index: + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + + 0 + + + 5 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Last image index + + + + + + + + + + + + okButton + clicked() + SaveDialogUi + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + SaveDialogUi + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/SimpleLabel/SimpleLabel.cpp b/SimpleLabel/SimpleLabel.cpp new file mode 100644 index 0000000..fdea5e5 --- /dev/null +++ b/SimpleLabel/SimpleLabel.cpp @@ -0,0 +1,2814 @@ +/* SimpleLabel - a simple and light program for semi automatic labeling of regions + of interest on images or image sequences. + Developed at Laboratory for Active and Attentive Vision, York University, Toronto. + http://www.cse.yorku.ca/LAAV/home/ headed by John K. Tsotsos. + + Copyright (C) 2010 Eugene Simine. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Contact: Eugene Simine or + John K. Tsotsos +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SimpleLabel.h" +#include "Constants.h" +#include "Monitor.h" +#include "About.h" +#include "SaveDialog.h" + +const QPoint CommonFunctions::NULL_POINT = QPoint(-1,-1); +const ViaPoint CommonFunctions::NULL_RECT = ViaPoint(-1, 0.0, QRect(0,0,0,0)); +const QPolygon CommonFunctions::NULL_POLYGON = QPolygon(); + +void test_trans() +{ + QRect tmp(10, 10, 21, 21); + tmp.translate(-tmp.center()); + QTransform tr; + tr.rotate(30); + QPointF p = tr.map(QPointF(tmp.topLeft())); + qDebug() << tmp; + + +} + + +SimpleLabel::SimpleLabel(QWidget *parent, Qt::WFlags flags) + : QMainWindow(parent, flags) +{ + //test_trans(); + + ui.setupUi(this); + setMouseTracking(true); + ui.centralWidget->setMouseTracking(true); + this->setWindowTitle(this->windowTitle() + " v" + QCoreApplication::applicationVersion()); + mSaveDgl = new SaveDialog(this); + mAboutDlg = new About(this); + mDisplayImage = new QImage(W_DISPLAYIMAGE, H_DISPLAYIMAGE, QImage::Format_ARGB32); +// mDisplayImage= NULL; + mMonitor= new Monitor(); + + connect(mMonitor, SIGNAL(imageChanged()), this, SLOT(showImage()), Qt::QueuedConnection); + connect(mSaveDgl, SIGNAL(accepted()), this, SLOT(on_SaveDialog_accept())); + + + mDrawPoint = CommonFunctions::NULL_POINT; + mDrawRect = CommonFunctions::NULL_RECT; + mMoveRect = false; + mRotateRect = false; + mSomethingChanged = false; + mResizeVertex = -1; + mIntrMethod = LinearIntr; + mNewPolygon = false; + + ui.actionLoad_XML->setDisabled(true); + ui.actionLoad_LabelMe_XML->setDisabled(true); + ui.actionExport->setDisabled(true); + ui.hSliderFrames->setDisabled(true); + ui.actionNewPolygon->setDisabled(true); + ui.actionSave_Dialog->setEnabled(true); + + mSaveDgl->ui.edtFirstImageIndex->setText("-1"); + + statusBar()->addPermanentWidget(&mStatus_Mode); + on_cmbBoxLabelShape_currentIndexChanged(Rect); + + //setting up the popup menu + mPopupMenu = new QMenu(this); + QAction *item = new QAction("Add New Vertex", this); + item->setObjectName("AddVertex"); + mPopupMenu->addAction(item); + item = new QAction("Remove This Vertex", this); + item->setObjectName("RemoveVertex"); + mPopupMenu->addAction(item); + + //create custom cursor + QBitmap cr(":/cursor_crosshair.bmp"); +#ifdef WIN32 + QBitmap mask(":/cursor_crosshair_mask.bmp"); +#else + QBitmap mask(":/cursor_crosshair.bmp"); +#endif + mCrossHairCursor = new QCursor(cr, mask, 15, 15); +} + +SimpleLabel::~SimpleLabel() +{ + releaseCapture(); + resetLabels(); + mMonitor->stop(); + mMonitor->wait(); + releaseCapture(); + delete mMonitor; + + delete mDisplayImage; + + delete mCrossHairCursor; +} + +void SimpleLabel::resetFilenames() +{ + mFileName = ""; + mFileNamePrefix = ""; + mPath = ""; + mExtention = ""; + mFirstFrameNumber = -1; +} + +void SimpleLabel::resetLabels() +{ + mDrawPoint = CommonFunctions::NULL_POINT; + mDrawRect = CommonFunctions::NULL_RECT; + mDrawPolygon = CommonFunctions::NULL_POLYGON; + mMoveRect = false; + mMovePolygon = false; + mSomethingChanged = false; + + for(int i = 0; i < mLabels.count(); i++) + { + mLabels[i].boxes.clear(); + mLabels[i].viaPoints.clear(); + mLabels[i].polygons.clear(); + mLabels[i].viaPointsPoly.clear(); + } + mLabels.clear(); + + ui.listLabels->clear(); + mSaveDgl->ui.edtFirstImageIndex->setText("-1"); +} + +void SimpleLabel::on_actionAbout_triggered() +{ + mAboutDlg->show(); +} + +void SimpleLabel::releaseCapture() +{ + CvCapture *cap = mMonitor->getCvCapture(); + if(cap) + { + cvReleaseCapture(&cap); + mMonitor->setCvCapture(NULL); + } +} + +void SimpleLabel::on_actionOpen_triggered() +{ + int frames = 0; + int firstframe = 0; + QString s = QFileDialog::getOpenFileName(this, tr("Open Video"), ".", tr("Image Files (*.png *.tif *.tiff *.jpg);;Video Files (*.avi)")); + if(!s.isEmpty()) + { + resetFilenames(); + resetLabels(); + releaseCapture(); + + //splitting the full path into parts + CommonFunctions::splitPath(s, mPath, mFileName, mFileNamePrefix, (uint)5, mFirstFrameNumber, mExtention); + + if(mExtention == ".avi") + { + mFileNamePrefix = mFileName; + //before opening file we make sure that previously opened file if closed + releaseCapture(); + + // m_Monitor->setLoadBackground(ui.chkBox_Adapt2Bkgd->isChecked()); + //opencv can't open capture from a child thread so we do it here + CvCapture* cap = cvCaptureFromFile(s.toAscii()); + mMonitor->setCvCapture(cap); + frames = mMonitor->getFrameCount(); + + } + else if(mExtention == ".tif" || mExtention == ".tiff" || mExtention == ".png" || mExtention == ".jpg") + { + if(mFirstFrameNumber >= 0) + { + firstframe = mFirstFrameNumber; + mMonitor->setFisrtFilenameOfSequence(s); + frames = mMonitor->getFrameCount(); + } + else if(mFirstFrameNumber == -1) + { + firstframe = mFirstFrameNumber; + mMonitor->setFisrtFilenameOfSequence(s); + frames = 1; + } + else + { + QMessageBox::information(this, "Error", s + ": incorrect numbering format. Expected: fname00000, fname00001, etc..."); + } + } + + if(frames > 0) + { + ui.hSliderFrames->setEnabled(true); + ui.hSliderFrames->setRange(firstframe, firstframe + frames - 1); + ui.actionLoad_XML->setEnabled(true); + ui.actionLoad_LabelMe_XML->setEnabled(true); + ui.actionExport->setEnabled(true); + } + else + ui.hSliderFrames->setDisabled(true); + + } +} + +void SimpleLabel::showImage() +{ + mMonitor->lockImages(); + *mDisplayImage = mMonitor->getImage()->scaled(W_DISPLAYIMAGE, H_DISPLAYIMAGE, Qt::KeepAspectRatio); + mMonitor->releaseImages(); + + if(mMonitor->isRunning()) + { + ui.hSliderFrames->setSliderPosition(mMonitor->getCurrentFrameNumber()); + update(); + } + else + repaint(); +} + +void SimpleLabel::drawCirclesAtVertices(QPainter *pt, QRect rc) +{ + int rad = CORNER_CIRCLE_RAD; + pt->setPen(Qt::black); +// pt->setPen(Qt::PenStyle::SolidLine); + + pt->drawEllipse(rc.topLeft(), rad, rad); + pt->drawEllipse(rc.topRight(), rad, rad); + pt->drawEllipse(rc.bottomRight(), rad, rad); + pt->drawEllipse(rc.bottomLeft(), rad, rad); + + //draw line for rotation + int x = rc.center().x(); + QLine ln(x, rc.top(), x, rc.top() - LENGTH_OF_ROTATION_LINE); + pt->drawLine(ln); + pt->drawEllipse(ln.p2(), rad, rad); +} + +void SimpleLabel::drawCirclesAtVertices(QPainter *pt, QPolygon &pl) +{ + int rad = CORNER_CIRCLE_RAD; + pt->setPen(Qt::black); +// pt->setPen(Qt::PenStyle::SolidLine); + + for(int i = 0; i < pl.count(); i++) + { + pt->drawEllipse(pl[i], rad, rad); + } +} + + +void SimpleLabel::paintEvent (QPaintEvent*) +{ + QPainter pt(this); + int fr = mMonitor->getCurrentFrameNumber(); + if(mDisplayImage) + { + pt.drawImage(WINDOW_OFFSET_X, WINDOW_OFFSET_Y, *mDisplayImage); + } + + if(ui.chkBoxShowAllLabels->isChecked()) + { + QBrush br(QColor(255,255,255, 100)); + int row = ui.listLabels->currentRow(); + int n, k; + QRect rc; + + for(n = 0; n < mLabels.count(); n++) + { + if(row == n) + br.setColor(QColor(0,255,0, 100)); + else + br.setColor(QColor(255,255,255, 100)); + + if(mLabels[n].boxes.count() > 0 && + fr >= mLabels[n].boxes.first().frame && + fr <= mLabels[n].boxes.last().frame) + { + k = fr - mLabels[n].boxes.first().frame; + rc = imageToScreen(mLabels[n].boxes[k].rc); + + pt.fillRect(rc, br); + pt.setPen( Qt::black ); + pt.drawRect(rc); + } + } + } + else + { + switch(mShapeMode) + { + case Rect: + drawModeRect(&pt, fr); + break; + case Polyg: + drawModePoly(&pt, fr); + break; + default: + break; + } + } +} + +void SimpleLabel::drawModeRect(QPainter *pt, int frame) +{ + if(mDrawRect != CommonFunctions::NULL_RECT) + { + QBrush br(QColor(255,255,255, 100)); + int row = ui.listLabels->currentRow(); + if(row >= 0) + { + if(mLabels[row].viaPoints.count() > 0 && + (frame < mLabels[row].viaPoints.first().frame || frame > mLabels[row].viaPoints.last().frame)) + { + br.setColor(QColor(255,0,0, 100)); + } + else + { + for(int i = 0; i < mLabels[row].viaPoints.count(); i++) + { + if(mLabels[row].viaPoints[i].frame == frame) + { + br.setColor(QColor(0,255,0, 100)); + break; + } + } + } + } + + QRect drc = imageToScreen(mDrawRect.rc); + //rotation + pt->translate(drc.center()); + pt->rotate(-mDrawRect.angle); + pt->translate(-drc.center()); + + pt->fillRect(drc, br); + pt->setPen( Qt::black ); + pt->drawRect(drc); + + drawCirclesAtVertices(pt, drc); + pt->rotate(mDrawRect.angle); + } +} + +//QRect SimpleLabel::rotateRect(QRect rc, float a) +//{ +// qDebug() << "Original rect " << rc; +// QRect tmp; +// QTransform tr; +// tr.translate(rc.top(), rc.left()); +// tmp = tr.mapRect(rc); +// qDebug() << "Translated rect " << tmp; +// rc.translate(10,10); +// tr.rotate(30); +// tmp = tr.mapRect(tmp); +// qDebug() << "Translated and rotated rect " << tmp; +// +// return tmp; +//} + +void SimpleLabel::drawModePoly(QPainter *pt, int frame) +{ + int row = ui.listLabels->currentRow(); + if(mNewPolygon) + { + if(row >= 0) + { + QPolygon p = imageToScreen(mFirstPolygon); + pt->drawPolyline(p); + drawCirclesAtVertices(pt, p); + } + } + else if(mDrawPolygon != CommonFunctions::NULL_POLYGON) + { + QBrush br(QColor(255,255,255, 100)); + if(row >= 0) + { + if(mLabels[row].viaPointsPoly.count() > 0 && + (frame < mLabels[row].viaPointsPoly.first().frame || frame > mLabels[row].viaPointsPoly.last().frame)) + { + br.setColor(QColor(255,0,0, 100)); + } + else + { + for(int i = 0; i < mLabels[row].viaPointsPoly.count(); i++) + { + if(mLabels[row].viaPointsPoly[i].frame == frame) + { + br.setColor(QColor(0,255,0, 100)); + break; + } + } + } + } + + QPolygon dpl = imageToScreen(mDrawPolygon); + pt->setBrush(br); + pt->setPen( Qt::black ); + pt->drawPolygon(dpl); + +// pt->drawRect(drc); + + drawCirclesAtVertices(pt, dpl); + } +} + + +void SimpleLabel::on_chkBoxShowAllLabels_toggled(bool b) +{ + ui.btnAddLabel->setDisabled(b); + update(); +} + +void SimpleLabel::on_hSliderFrames_valueChanged(int v) +{ + ui.lblCurrentFrame->setText(QString::number(v)); + + if(mMonitor->isRunning()) + { + if(mShapeMode == Rect) + setDrawRectToFrame(v); + else if(mShapeMode == Polyg) + setDrawPolygonToFrame(v); + } + else + { + mMonitor->moveToFrame(v); + if(mShapeMode == Rect) + setDrawRectToFrame(v); + else if(mShapeMode == Polyg) + setDrawPolygonToFrame(v); + showImage(); + } +} + +void SimpleLabel::setDrawRectToFrame(int v) +{ + int row = ui.listLabels->currentRow(); + if(row >= 0) + { + if(mLabels[row].boxes.count() > 0) + { + int fr = v - mLabels[row].viaPoints[0].frame; + if(fr >= 0 && mLabels[row].boxes.count() > fr) + { + if(mLabels[row].boxes[fr].frame != v) + { + QString s = QString("Frames are out of sync: box = ") + mLabels[row].boxes[fr].frame + "; v = " + v; + qDebug() << s; + } + //mDrawRect.rc = mLabels[row].boxes[fr].rc; + mDrawRect = mLabels[row].boxes[fr]; + } + } + } +} + +void SimpleLabel::setDrawPolygonToFrame(int v) +{ + int row = ui.listLabels->currentRow(); + if(row >= 0) + { + if(mLabels[row].polygons.count() > 0) + { + int fr = v - mLabels[row].viaPointsPoly[0].frame; + if(fr >= 0 && mLabels[row].polygons.count() > fr) + { + if(mLabels[row].polygons[fr].frame != v) + { + QString s = QString("Frames are out of sync: polygon = ") + mLabels[row].polygons[fr].frame + "; v = " + v; + qDebug() << s; + } + mDrawPolygon = mLabels[row].polygons[fr].pl; + } + } + } +} + +void SimpleLabel::mousePressEvent( QMouseEvent * e ) +{ + QPoint pt = screenToImage(e->pos()); + + if(e->button() == Qt::RightButton) + { + int row = ui.listLabels->currentRow(); + int vertex = CommonFunctions::checkForVertices(pt, mDrawPolygon); + if(vertex >= 0 && row >= 0) + { + QAction *tmp = mPopupMenu->exec(QCursor::pos()); + if(tmp == NULL) + { + } + else if(tmp->objectName() == "AddVertex") + { + QPoint p1, p2, p; + int v2 = (vertex + 1)%mLabels[row].viaPointsPoly[0].pl.count(); + + //add new vertex to each via point and recalculate polygons + QList::iterator i; + for(i = mLabels[row].viaPointsPoly.begin(); i != mLabels[row].viaPointsPoly.end(); ++i) + { + p1 = i->pl.point(vertex); + p2 = i->pl.point(v2); + p = (p1 + p2)/2; + + i->pl.insert(v2, p); + } + rebuildLabelPolygons(); + mDrawPolygon = mLabels[row].findPolygonByFrame(mMonitor->getCurrentFrameNumber())->pl; + update(); + } + else if(tmp->objectName() == "RemoveVertex") + { + //add new vertex to each via point and recalculate polygons + QList::iterator i; + for(i = mLabels[row].viaPointsPoly.begin(); i != mLabels[row].viaPointsPoly.end(); ++i) + { + i->pl.remove(vertex); + } + rebuildLabelPolygons(); + mDrawPolygon = mLabels[row].findPolygonByFrame(mMonitor->getCurrentFrameNumber())->pl; + update(); + } + } + } + else if(e->button() == Qt::LeftButton && mMonitor->isInitialized() && !ui.chkBoxShowAllLabels->isChecked()) + { + //check if mouse was clicked inside the image + if(mDisplayImage->rect().contains(pt)) + { + switch(mShapeMode) + { + case Rect: + //check if we should resize + //mResizeVertex = CommonFunctions::checkForVertices(pt, mDrawRect.rc); + mResizeVertex = CommonFunctions::checkForVerticesWithRotation(pt, mDrawRect); + if( mResizeVertex >= 0) + { + qDebug() << "Found vertex " << mResizeVertex; + pt = CommonFunctions::getOpposingVertex(mDrawRect.rc, mResizeVertex); + + //bring pt to screen frame of reference by rotating it in opposite direction + pt = CommonFunctions::rotatePointAboutPoint(pt, -mDrawRect.angle, mDrawRect.rc.center()); + qDebug() << "Rect: " << mDrawRect.rc; + qDebug() << "Opposing vertex: " << pt << "\n"; + } + //check if we need to rotate the rectangle + else if(CommonFunctions::checkForRotation(pt, mDrawRect)) + { + mRotateRect = true; + } + //ckeck if the click is withing the rectangle + else if(mDrawRect.rc.contains(CommonFunctions::rotatePointAboutPoint(pt, mDrawRect.angle, mDrawRect.rc.center()))) + { + mMoveRect = true; + } + break; + case Polyg: + mResizeVertex = CommonFunctions::checkForVertices(pt, mDrawPolygon); + //drawing new polygon + if(mNewPolygon) + { + int row = ui.listLabels->currentRow(); + if(row >= 0) + { + + mFirstPolygon << pt; + } + update(); + } + //resizing + else if(mResizeVertex >= 0) + { //this is intentional + } + //moving + else if(mDrawPolygon.containsPoint(pt, Qt::OddEvenFill)) + { + mMovePolygon = true; + } + break; + default: + break; + } + mDrawPoint = pt; + } + } +} + + +void SimpleLabel::mouseReleaseEvent( QMouseEvent* ) +{ + if(mMonitor->isInitialized() && !ui.chkBoxShowAllLabels->isChecked()) + { + mDrawPoint = CommonFunctions::NULL_POINT; + mMoveRect = false; + mRotateRect = false; + mMovePolygon = false; + mResizeVertex = -1; + + //reculculate boxes if something changed + if(mSomethingChanged) + { + if(mShapeMode == Rect) + { + addViaPoint(ui.hSliderFrames->value(), mDrawRect); + } + else if(mShapeMode == Polyg) + { + addViaPointPoly(ui.hSliderFrames->value(), mDrawPolygon); + } + mSomethingChanged = false; + update(); + } + } +} + +void SimpleLabel::mouseMoveEvent( QMouseEvent * e ) +{ + //working in image coordinates + QPoint pt = screenToImage(e->pos()); + + //changing mouse cursor + if(mMonitor->isInitialized()) + { + if(mDisplayImage->rect().contains(pt)) + { + if(this->cursor().shape() != Qt::CrossCursor) + { + this->setCursor(*mCrossHairCursor); + } + } + else + { + if(this->cursor().shape() != Qt::ArrowCursor) + { + QCursor cr(Qt::ArrowCursor); + this->setCursor(cr); + } + } + } + + if(e->buttons() & Qt::LeftButton && + mMonitor->isInitialized() && + mDrawPoint != CommonFunctions::NULL_POINT && + !ui.chkBoxShowAllLabels->isChecked() && + !mNewPolygon) + { + switch(mShapeMode) + { + case Rect: + //move existing rectangle + if(mMoveRect) + { + QPoint dp = pt - mDrawPoint; + + mDrawRect.rc.translate(dp); + mDrawPoint = pt; + } + //resize the shape + else if(mResizeVertex >= 0) + { + //original + /*mDrawRect.rc.setCoords(mDrawPoint.x(), mDrawPoint.y(), pt.x(), pt.y()); + mDrawRect.rc = mDrawRect.rc.normalized();*/ + + //mouse position in rect frame + QPoint tpt = CommonFunctions::rotatePointAboutPoint(pt, mDrawRect.angle, mDrawRect.rc.center()); + // reference point that should alwasy remain in the same point on the image + QPoint ref_pt = CommonFunctions::rotatePointAboutPoint(mDrawPoint, mDrawRect.angle, mDrawRect.rc.center()); + //true size of the new rect + QSize sz(tpt.x() - ref_pt.x(), tpt.y() - ref_pt.y()); + + //not rotated rect in screen coords rect + QRect tmp(mDrawPoint, sz); + tmp = tmp.normalized(); + + //rotate center of this rect about the ref point by -angle + //to find the proper position of the new center + QPoint new_center = CommonFunctions::rotatePointAboutPoint(tmp.center(), -mDrawRect.angle, mDrawPoint); + //translate rect so that it's center is the new position + tmp.translate(new_center - tmp.center()); + + mDrawRect.rc = tmp; + + } + //rotate rectangle + else if(mRotateRect) + { + QPoint o = mDrawRect.rc.center(); + QPoint v1 = pt - o; + QPoint v2 = mDrawPoint - o; + int row = ui.listLabels->currentRow(); + double ang = CommonFunctions::findAngleBetweenVectors2(v1, v2); + if(row >= 0) + { + mDrawRect.angle = mLabels[row].findBoxByFrame(mDrawRect.frame)->angle + ang; + } + } + //draw new rectangle + else + { + mDrawRect.rc.setCoords(mDrawPoint.x(), mDrawPoint.y(), pt.x(), pt.y()); + mDrawRect.rc = mDrawRect.rc.normalized(); + mDrawRect.angle = 0; + mDrawRect.frame = ui.lblCurrentFrame->text().toInt(); + } + + mDrawRect.rc = mDisplayImage->rect().intersect(mDrawRect.rc); + mSomethingChanged = true; + break; + case Polyg: + //move the polygon + if(mMovePolygon) + { + QPoint dp = pt - mDrawPoint; + mDrawPolygon.translate(dp); + mDrawPoint = pt; + mSomethingChanged = true; + } + else if(mResizeVertex >= 0) + { + mDrawPolygon.setPoint(mResizeVertex, pt); + mSomethingChanged = true; + } + break; + default: + break; + } + + repaint(); + } +} + +QPoint SimpleLabel::screenToImage(QPoint scr) +{ + return QPoint(scr.x() - WINDOW_OFFSET_X, scr.y() - WINDOW_OFFSET_Y); +} + + +QPoint SimpleLabel::imageToScreen(QPoint im) +{ + return QPoint(im.x() + WINDOW_OFFSET_X, im.y() + WINDOW_OFFSET_Y); +} + +QPoint SimpleLabel::imageToImage(int srcW, int srcH, QPoint srcP, int destW, int destH) +{ + QPoint res; + double kX, kY; + + kX = (double)destW/(double)srcW; + kY = (double)destH/(double)srcH; + + res.setX(ROUND(srcP.x() * kX)); + res.setY(ROUND(srcP.y() * kY)); + + return res; +} + +QRect SimpleLabel::imageToImage(int srcW, int srcH, QRect srcP, int destW, int destH) +{ + QRect res; + QPoint tl = imageToImage(srcW, srcH, srcP.topLeft(), destW, destH); + QPoint br = imageToImage(srcW, srcH, srcP.bottomRight(), destW, destH); + + res.setTopLeft(tl); + res.setBottomRight(br); + + return res; +} + + +QRect SimpleLabel::screenToImage(QRect scr) +{ + return scr.translated(-WINDOW_OFFSET_X, -WINDOW_OFFSET_Y); +} + + +QRect SimpleLabel::imageToScreen(QRect im) +{ + return im.translated(WINDOW_OFFSET_X, WINDOW_OFFSET_Y); +} + +QPolygon SimpleLabel::screenToImage(QPolygon scr) +{ + return scr.translated(-WINDOW_OFFSET_X, -WINDOW_OFFSET_Y); +} + +QPolygon SimpleLabel::imageToScreen(QPolygon im) +{ + return im.translated(WINDOW_OFFSET_X, WINDOW_OFFSET_Y); +} + +QPolygon SimpleLabel::imageToImage(int srcW, int srcH, QPolygon srcP, int destW, int destH) +{ + QPolygon res; + QPoint p; + for(int i = 0; i < srcP.count(); i++) + { + p = imageToImage(srcW, srcH, srcP[i], destW, destH); + res << p; + } + + return res; +} + +void SimpleLabel::on_btnAddLabel_pressed() +{ + Label lb; + lb.number = mLabels.count(); + lb.name = "New Label"; + lb.shape = Polyg; + lb.desc = ""; + mLabels.append(lb); + + updateListView(); +} + +void SimpleLabel::on_btnDeleteLabel_pressed() +{ + int row = ui.listLabels->currentRow(); + + if(row >= 0 && row < mLabels.count()) + { + mLabels.removeAt(row); + } + + updateListView(); +} + +void SimpleLabel::updateListView() +{ + int i; + ui.listLabels->clear(); + + for(i = 0; i < mLabels.count(); i++) + { + QListWidgetItem *it = new QListWidgetItem(); + it->setText(mLabels[i].name); + it->setFlags(it->flags() | Qt::ItemIsEditable); + ui.listLabels->addItem(it); + } +} + +void SimpleLabel::on_listLabels_itemChanged(QListWidgetItem* item) +{ + int row = ui.listLabels->row(item); + + mLabels[row].name = item->text(); +} + +void SimpleLabel::on_listLabels_currentItemChanged (QListWidgetItem * current, QListWidgetItem * previous) +{ + int row = ui.listLabels->row(previous); + + if(row >= 0 && row < mLabels.count()) + { + mLabels[row].desc = ui.textEditDescription->toPlainText(); + mLabels[row].shape = mShapeMode; + } + + row = ui.listLabels->row(current); + if(row >= 0 && row < mLabels.count()) + { + mShapeMode = mLabels[row].shape; + ui.textEditDescription->setText(mLabels[row].desc); + ui.cmbBoxLabelShape->setCurrentIndex(mShapeMode); + switch(mShapeMode) + { + case Rect: + if(mLabels[row].boxes.count() > 0) + { + ViaPoint *p = mLabels[row].findBoxByFrame(mMonitor->getCurrentFrameNumber()); + if(p != NULL) + mDrawRect = *p; + } + break; + case Polyg: + if(mLabels[row].polygons.count() > 0) + { + ViaPointPolygon *p = mLabels[row].findPolygonByFrame(mMonitor->getCurrentFrameNumber()); + if(p != NULL) + mDrawPolygon = p->pl; + } + break; + default: + break; + } + } + update(); +} + +void SimpleLabel::on_textEditDescription_textChanged() +{ + int row = ui.listLabels->currentRow(); + if(row >= 0) + { + mLabels[row].desc = ui.textEditDescription->toPlainText(); + } +} + +//add new polygon via point to the structure +void SimpleLabel::addViaPointPoly(int frame, QPolygon pl, bool addRect) +{ + bool ok = true; + int row = ui.listLabels->currentRow(); + if(row < 0) + { + QMessageBox::information(NULL,"Label", "Select a label."); + ok = false; + } + + if(ok) + { + QList::iterator i; + //find the place where to put new polygon + for(i = mLabels[row].viaPointsPoly.begin(); i != mLabels[row].viaPointsPoly.end(); i++) + { + if(i->frame > frame || i->frame == frame) + { + break; + } + } + + //if the frame is already a via point replace it + if(i != mLabels[row].viaPointsPoly.end() && i->frame == frame) + { + i->frame = frame; + i->pl = pl; + } + else + { + //insert new via point + ViaPointPolygon v; + v.frame = frame; + v.pl = pl; + mLabels[row].viaPointsPoly.insert(i, v); + } + + rebuildLabelPolygons(); + + //last parameter is false bacause we don't want to add a polygon + //(we just added it) + if(addRect) + addViaPoint(frame, pl.boundingRect()); + } +} + +//add new point in the right place in the label structure +void SimpleLabel::addViaPoint(int frame, QRect rc) +{ + bool ok = true; + int row = ui.listLabels->currentRow(); + if(row < 0) + { + QMessageBox::information(NULL,"Label", "Select a label."); + ok = false; + } + + if(ok) + { + QList::iterator i; + //find the place where to put new point + for(i = mLabels[row].viaPoints.begin(); i != mLabels[row].viaPoints.end(); i++) + { + if(i->frame > frame || i->frame == frame) + { + break; + } + } + + ViaPoint v; + v.frame = frame; + v.rc = rc; + //if the frame is already a via point replace it + if(i != mLabels[row].viaPoints.end() && i->frame == frame) + { + i->frame = v.frame; + i->rc = v.rc; + } + else + { + //insert new via point + mLabels[row].viaPoints.insert(i, v); + } + rebuildLabelBoxes(); + } +} + +//add new point in the right place in the label structure +void SimpleLabel::addViaPoint(int frame, ViaPoint v) +{ + bool ok = true; + int row = ui.listLabels->currentRow(); + if(row < 0) + { + QMessageBox::information(NULL,"Label", "Select a label."); + ok = false; + } + + if(ok) + { + QList::iterator i; + //find the place where to put new point + for(i = mLabels[row].viaPoints.begin(); i != mLabels[row].viaPoints.end(); i++) + { + if(i->frame > frame || i->frame == frame) + { + break; + } + } + + //if the frame is already a via point replace it + if(i != mLabels[row].viaPoints.end() && i->frame == frame) + { + *i = v; + i->frame = frame; + } + else + { + //insert new via point + ViaPoint nv = v; + nv.frame = frame; + mLabels[row].viaPoints.insert(i, nv); + } + rebuildLabelBoxes(); + + //add polygon viaPoint too + int numVerticies = 4; + if( mLabels[row].viaPointsPoly.count() > 0) + { + numVerticies = mLabels[row].viaPointsPoly[0].pl.count(); + } + + QPolygon newPolyg; + newPolyg << v.rc.topLeft(); + newPolyg << v.rc.topRight(); + newPolyg << v.rc.bottomRight(); + + for(int ver = 3; ver < numVerticies; ver++) + { + newPolyg << v.rc.bottomLeft(); + } + + //we don't want to add new rectangle + //we just added it + addViaPointPoly(v.frame, newPolyg, false); + + } +} + +//recalculate the label polygons +void SimpleLabel::rebuildLabelPolygons() +{ + int row = ui.listLabels->currentRow(); + double df = 0.0; + QPointF dp; + ViaPointPolygon b1, b2; + ViaPointPolygon newPolyg; + QPoint np; + int t; + + if(row >= 0) + { + QList::iterator i; + mLabels[row].polygons.clear(); + + + if(mLabels[row].viaPointsPoly.count() == 1) + { + mLabels[row].polygons << mLabels[row].viaPointsPoly[0]; + } + + for(i = mLabels[row].viaPointsPoly.begin(); i != mLabels[row].viaPointsPoly.end() - 1; ++i) + { + b1 = *i; + b2 = *(i + 1); + if(mIntrMethod == LinearIntr) + { + df = b2.frame - b1.frame; + } + + for(int k = b1.frame; k <= b2.frame; k++) + { + t = k - b1.frame; + newPolyg.frame = k; + newPolyg.pl = QPolygon(); + + for(int j = 0; j < b1.pl.count(); j++) + { + if(mIntrMethod == LinearIntr) + { + QPointF p1 = b2.pl.point(j) - b1.pl.point(j); + dp = p1/df; + np = linearInterpolation(b1.pl.point(j), dp, t); + } + newPolyg.pl << np; + } + + //the first frame of the new segment is the same as the last frame of the + //previous segment, so we rewrite it + if(k == b1.frame && mLabels[row].polygons.count() > 0) + { + mLabels[row].polygons[mLabels[row].polygons.count() - 1] = newPolyg; + } + else + { + mLabels[row].polygons << newPolyg; + } + } + } + } +} + + +//recalculate the label boxes +void SimpleLabel::rebuildLabelBoxes() +{ + int row = ui.listLabels->currentRow(); + + if(row >= 0) + { + QList::iterator i; + mLabels[row].boxes.clear(); + ViaPoint b1, b2; + ViaPoint newbox; + double dx = 0, dy = 0; + double dw = 0, dh = 0; + double df; + double da = 0; + int t; + + //special case + if(mLabels[row].viaPoints.count() == 1) + { + mLabels[row].boxes << mLabels[row].viaPoints[0]; + } + + for(i = mLabels[row].viaPoints.begin(); i != mLabels[row].viaPoints.end() - 1; ++i) + { + b1 = *i; + b2 = *(i+1); + + if(mIntrMethod == LinearIntr) + { + df = b2.frame - b1.frame; + dx = (b2.rc.left() - b1.rc.left())/df; + dy = (b2.rc.top() - b1.rc.top())/df; + dw = (b2.rc.width() - b1.rc.width())/df; + dh = (b2.rc.height() - b1.rc.height())/df; + da = (b2.angle - b1.angle)/df; + } + for(int k = b1.frame; k <= b2.frame; k++) + { + t = k - b1.frame; + if(mIntrMethod == LinearIntr) + { + newbox.rc = linearInterpolation(b1.rc, dx, dy, dw, dh, t); + newbox.frame = k; + newbox.angle = b1.angle + da*t; + } + else if(mIntrMethod == Motion) + { + //DO THE MOTION ALGORITHM HERE + } + + //the first frame of the new segment is the same as the last frame of the + //previous segment, so we rewrite it + if(k == b1.frame && mLabels[row].boxes.count() > 0) + { + mLabels[row].boxes[mLabels[row].boxes.count() - 1] = newbox; + } + else + { + mLabels[row].boxes.append(newbox); + } + } + } + } +} + +QPoint SimpleLabel::linearInterpolation(QPoint src, QPointF dt, int t) +{ + return src + (t*dt).toPoint(); +} + +QRect SimpleLabel::linearInterpolation(QRect initPoint, double dx, double dy, double dw, double dh, int t) +{ + QRect res; + res.setLeft(ROUND(initPoint.left() + t*dx)); + res.setTop(ROUND(initPoint.top() + t*dy)); + res.setWidth(ROUND(initPoint.width() + t*dw)); + res.setHeight(ROUND(initPoint.height() + t*dh)); + + return res; +} + +void SimpleLabel::on_btnToBeginning_pressed() +{ + int row = ui.listLabels->currentRow(); + if(mShapeMode == Rect) + { + if( row >= 0 && mLabels[row].viaPoints.count() > 0) + { + ui.hSliderFrames->setValue(mLabels[row].viaPoints[0].frame); + } + } + else if(mShapeMode == Polyg) + { + if( row >= 0 && mLabels[row].viaPointsPoly.count() > 0) + { + ui.hSliderFrames->setValue(mLabels[row].viaPointsPoly[0].frame); + } + } +} + +void SimpleLabel::on_btnPrevViaPoint_pressed() +{ + int row = ui.listLabels->currentRow(); + if(mShapeMode == Rect) + { + if( row >= 0 && mLabels[row].viaPoints.count() > 0) + { + int fr = ui.hSliderFrames->value(); + int i = mLabels[row].viaPoints.count() - 1; + while(mLabels[row].viaPoints[i].frame >= fr && i > 0) + { + i--; + } + + ui.hSliderFrames->setValue(mLabels[row].viaPoints[i].frame); + } + } + else if(mShapeMode == Polyg) + { + if( row >= 0 && mLabels[row].viaPointsPoly.count() > 0) + { + int fr = ui.hSliderFrames->value(); + int i = mLabels[row].viaPointsPoly.count() - 1; + while(mLabels[row].viaPointsPoly[i].frame >= fr && i > 0) + { + i--; + } + + ui.hSliderFrames->setValue(mLabels[row].viaPointsPoly[i].frame); + } + } +} + +void SimpleLabel::on_btnStop_pressed() +{ + mMonitor->stop(); +} + +void SimpleLabel::on_btnRemoveViaPoint_pressed() +{ + int row = ui.listLabels->currentRow(); + if(mShapeMode == Rect) + { + if( row >= 0 && mLabels[row].viaPoints.count() > 0) + { + int fr = ui.hSliderFrames->value(); + for(int i = 0; i < mLabels[row].viaPoints.count(); i++) + { + if(mLabels[row].viaPoints[i].frame == fr) + { + mLabels[row].viaPoints.removeAt(i); + if(mLabels[row].viaPoints.count() > 0) + { + rebuildLabelBoxes(); + } + break; + } + } + } + } + else if(mShapeMode == Polyg) + { + if( row >= 0 && mLabels[row].viaPointsPoly.count() > 0) + { + int fr = ui.hSliderFrames->value(); + for(int i = 0; i < mLabels[row].viaPointsPoly.count(); i++) + { + if(mLabels[row].viaPointsPoly[i].frame == fr) + { + mLabels[row].viaPointsPoly.removeAt(i); + if(mLabels[row].viaPointsPoly.count() > 0) + { + rebuildLabelPolygons(); + } + break; + } + } + } + } +} + +void SimpleLabel::on_btnPlay_pressed() +{ + mMonitor->start(); +} + + +void SimpleLabel::on_btnNextViaPoint_pressed() +{ + int row = ui.listLabels->currentRow(); + if(mShapeMode == Rect) + { + if( row >= 0 && mLabels[row].viaPoints.count() > 0) + { + int fr = ui.hSliderFrames->value(); + int i = 0; + while(mLabels[row].viaPoints[i].frame <= fr && i < mLabels[row].viaPoints.count() - 1) + { + i++; + } + + ui.hSliderFrames->setValue(mLabels[row].viaPoints[i].frame); + } + } + else if(mShapeMode == Polyg) + { + if( row >= 0 && mLabels[row].viaPointsPoly.count() > 0) + { + int fr = ui.hSliderFrames->value(); + int i = 0; + while(mLabels[row].viaPointsPoly[i].frame <= fr && i < mLabels[row].viaPointsPoly.count() - 1) + { + i++; + } + + ui.hSliderFrames->setValue(mLabels[row].viaPointsPoly[i].frame); + } + } +} + +void SimpleLabel::on_btnToEnd_pressed() +{ + int row = ui.listLabels->currentRow(); + if(mShapeMode == Rect) + { + if( row >= 0 && mLabels[row].viaPoints.count() > 0) + { + ui.hSliderFrames->setValue(mLabels[row].viaPoints.last().frame); + } + } + else if(mShapeMode == Polyg) + { + if( row >= 0 && mLabels[row].viaPointsPoly.count() > 0) + { + ui.hSliderFrames->setValue(mLabels[row].viaPointsPoly.last().frame); + } + } +} + +void SimpleLabel::exportToMovie(bool origBkgrd) +{ + int nLbls = ui.listLabels->count(); + int startF, endF; + int i, t; + QSize origSz = mMonitor->getImageSize(); + + if(nLbls > 0) + { + //find the earliest frame in all the labels + startF = qMax( mSaveDgl->ui.edtFirstImageIndex->text().toInt(), mFirstFrameNumber); + endF = qMin(mSaveDgl->ui.edtLastImageIndex->text().toInt(), mFirstFrameNumber + mMonitor->getFrameCount() - 1); + if(startF > mFirstFrameNumber + mMonitor->getFrameCount() - 1) + { + QMessageBox::information(this, "Index is out of bounds", "Starting frame number is larger than image sequence length."); + return; + } + + CvVideoWriter* aviW; + QString saveFile = mSaveDgl->ui.edtSavePath->text(); + QString path, prefix, fname, ext; + int index; + + CommonFunctions::splitPath(saveFile, path, fname, prefix, (uint)5, index, ext); + + if(mSaveDgl->ui.rbtnSaveAsAVI->isChecked()) + { + int fps = mMonitor->getFPS(); + if(fps < 1) + fps = 30; + + try + { + //CV_FOURCC('M', 'P', '4', '2') CV_FOURCC('M','J','P','G') + aviW = cvCreateVideoWriter(saveFile.toAscii(), CV_FOURCC_PROMPT, fps, cvSize(origSz.width(), origSz.height())); + // CvVideoWriter* aviW = cvCreateAVIWriter((mFileName.left(mFileName.length()-4) + "_bm.avi").toAscii(), 0, fps, cvSize(origSz.width(), origSz.height())); + } + catch(...) + { + aviW = cvCreateVideoWriter(saveFile.toAscii(), CV_FOURCC_DEFAULT, fps, cvSize(origSz.width(), origSz.height())); + } + } + + QPainter pt; + int h; + QColor cl; + QBrush br(QColor(0,0,0,255)); + QImage *im = NULL; + if(!origBkgrd) + { + im = new QImage(origSz, QImage::Format_ARGB32); + } + ViaPoint *bx; + ViaPointPolygon *polyg; + IplImage* ipl = cvCreateImage(cvSize(origSz.width(), origSz.height()), IPL_DEPTH_8U, 3); + for(t = startF; t <= endF; t++) + { + if(origBkgrd) + { + mMonitor->moveToFrame(t); + im = mMonitor->getImage(); + } + + pt.begin(im); + if(!origBkgrd) + { + pt.fillRect(im->rect(), Qt::black); + } + for(i = 0; i < nLbls; i++) + { + if(mLabels[i].shape == Rect) + { + if(t >= mLabels[i].boxes[0].frame && + t <= mLabels[i].boxes[mLabels[i].boxes.count() - 1].frame) + { + h = 200;//(int)(100 + (i + 1) * 155.0/nLbls); + cl = QColor::fromRgb(h,h,h,128); + br.setColor(cl); + bx = mLabels[i].findBoxByFrame(t); + QRect rt = imageToImage(mDisplayImage->width(), mDisplayImage->height(), bx->rc, origSz.width(), origSz.height()); + pt.fillRect(rt, br); + } + } + else if(mLabels[i].shape == Polyg) + { + if(t >= mLabels[i].polygons[0].frame && + t <= mLabels[i].polygons[mLabels[i].polygons.count() - 1].frame) + { + h = 200;//(int)(100 + (i + 1) * 155.0/nLbls); + cl = QColor::fromRgb(h,h,h,128); + br.setColor(cl); + pt.setBrush(br); + polyg = mLabels[i].findPolygonByFrame(t); + QPolygon pl = imageToImage(mDisplayImage->width(), mDisplayImage->height(), polyg->pl, origSz.width(), origSz.height()); + pt.drawPolygon(pl); + } + } + } + + if(mSaveDgl->ui.rbtnSaveAsAVI->isChecked()) + { + mMonitor->convertARGB2RGB(im, ipl); + cvWriteFrame(aviW, ipl); + } + else + { + QString sv; + sv = path + prefix + sv.sprintf("%05d", t) + ext; + im->save(sv, "PNG"); + } + + pt.end(); + } + if(mSaveDgl->ui.rbtnSaveAsAVI->isChecked()) + { + cvReleaseVideoWriter(&aviW); + } + + if(!origBkgrd) + { + delete im; + } + } +} + + +void SimpleLabel::exportToMatlabStruct() +{ + QSize origSz = mMonitor->getImageSize(); + int i, k, j; + QString saveFile = mSaveDgl->ui.edtSavePath->text(); + QString filename = mSaveDgl->ui.edtFileNamePrefix->text(); + + QFile fd(saveFile); + if(fd.open(QFile::WriteOnly | QFile::Truncate)) + { + QTextStream out(&fd); + out << "function " << filename << " = " << filename + "_lbl" << endl; + for(i = 0; i < mLabels.count(); i++) + { + out << filename << "(" << i +1 << ").number = " << mLabels[i].number << ";" << endl; + out << filename << "(" << i +1 << ").desc = '" << mLabels[i].desc.replace("\n", "") << "';" << endl; + out << filename << "(" << i +1 << ").name = '" << mLabels[i].name << "';" << endl; + out << filename << "(" << i +1 << ").startFrame = " << mLabels[i].viaPoints[0].frame << ";" << endl; + out << filename << "(" << i +1 << ").endFrame = " << mLabels[i].viaPoints[mLabels[i].viaPoints.count() - 1].frame << ";" << endl; + + out << filename << "(" << i +1 << ").boxes = ["; + for(k = 0; k < mLabels[i].boxes.count(); k++) + { + QRect rc = imageToImage(mDisplayImage->width(), mDisplayImage->height(), mLabels[i].boxes[k].rc, origSz.width(), origSz.height()); + out << rc.left()<< "," << rc.top() << "," << rc.width() << "," << rc.height(); + if(k != mLabels[i].boxes.count() - 1) + out << ";"; + } + out << "];" << endl; + + for(k = 0; k < mLabels[i].viaPoints.count(); k++) + { + QRect rc = imageToImage(mDisplayImage->width(), mDisplayImage->height(), mLabels[i].viaPoints[k].rc, origSz.width(), origSz.height()); + + out << filename << "(" << i +1 << ").pivots(" << k + 1 << ").frame = " << mLabels[i].viaPoints[k].frame << ";" << endl; + out << filename << "(" << i +1 << ").pivots(" << k + 1 << ").box = ["; + out << rc.left() << "," << rc.top() << "," << rc.width() << "," << rc.height() << "];" << endl; + } + + + + for(k = 0; k < mLabels[i].polygons.count(); k++) + { + out << filename << "(" << i +1 << ").polygons(" << k + 1 << ").polygon=["; + QPolygon pl = imageToImage(mDisplayImage->width(), mDisplayImage->height(), mLabels[i].polygons[k].pl, origSz.width(), origSz.height()); + for(j = 0; j < pl.count(); j++) + { + QPoint pt = pl.point(j); + out << pt.x() << "," << pt.y(); + if(j != pl.count() - 1) + out << ";"; + } + out << "];" << endl; + } + + for(k = 0; k < mLabels[i].viaPointsPoly.count(); k++) + { + QPolygon pl = imageToImage(mDisplayImage->width(), mDisplayImage->height(), mLabels[i].viaPointsPoly[k].pl, origSz.width(), origSz.height()); + + out << filename << "(" << i +1 << ").pivotsPolyg(" << k + 1 << ").frame = " << mLabels[i].viaPoints[k].frame << ";" << endl; + out << filename << "(" << i +1 << ").pivotsPolyg(" << k + 1 << ").polygon = ["; + for(j = 0; j < pl.count(); j++) + { + QPoint pt = pl.point(j); + out << pt.x() << "," << pt.y(); + if(j != pl.count() - 1) + out << ";"; + } + } + } + out << "];" << endl; + fd.close(); + } +} + +Label* SimpleLabel::findLabel(int number) +{ + for(int i = 0; i < mLabels.count(); i++) + { + if(mLabels[i].number == number) + { + return &mLabels[i]; + } + } + + return NULL; +} + + + +void SimpleLabel::exportToLabelMeXML() +{ + QString saveFile = mSaveDgl->ui.edtSavePath->text(); + QString path, fname, ext; + CommonFunctions::splitPath(saveFile, path, fname, ext); + + int startF = qMax( mSaveDgl->ui.edtFirstImageIndex->text().toInt(), mFirstFrameNumber); + int endF = qMin(mSaveDgl->ui.edtLastImageIndex->text().toInt(), mFirstFrameNumber + mMonitor->getFrameCount() - 1); + if(startF > mFirstFrameNumber + mMonitor->getFrameCount() - 1) + { + QMessageBox::information(this, "Index is out of bounds", "Starting frame number is larger than image sequence length."); + return; + } + + for(int i = startF; i <= endF; i++) + { + exportFrameToLabelMeXML(path, i); + } +} + + +void SimpleLabel::on_actionLoad_LabelMe_XML_triggered() +{ + int curframe = -1, totframes = 0; + int w = 0, h = 0; + bool err = false; + QString s = QFileDialog::getOpenFileName(this, tr("Open LabelMe XML"), ".", tr("XML Files (*.xml)")); + QSize sz = mMonitor->getImageSize(); + + if(!s.isEmpty()) + { + try + { + //scanning first file for global settings + QFile f(s); + if(f.open(QIODevice::ReadOnly)) + { + QDomDocument doc(""); + if(doc.setContent(&f)) + { + resetLabels(); + + QDomNode anot = doc.documentElement(); + + QDomNode n = anot.firstChild(); + while(!n.isNull()) //going through all the elements + { + QDomElement e = n.toElement(); + if(!e.isNull() && e.tagName() == "frame") + { + curframe = e.text().trimmed().toInt(); + } + if(!e.isNull() && e.tagName() == "source") + { + + QDomNode n1 = n.firstChild(); + while(!n1.isNull()) //going through all the children + { + QDomElement e1 = n1.toElement(); + if(!e1.isNull() && e1.tagName() == "numberFrames") + { + totframes = e1.text().trimmed().toInt(); + } + n1 = n1.nextSibling(); + } + } + if(!e.isNull() && e.tagName() == "imagesize") + { + QDomNode n1 = n.firstChild(); + while(!n1.isNull()) //going through all the children + { + QDomElement e1 = n1.toElement(); + if(!e1.isNull() && e1.tagName() == "rows") + { + h = e1.text().trimmed().toInt(); + } + if(!e1.isNull() && e1.tagName() == "columns") + { + w = e1.text().trimmed().toInt(); + } + n1 = n1.nextSibling(); + } + } + + n = n.nextSibling(); + } + + } + else + { + QMessageBox::information(this, "Invalid XML file", "Failed to parse XML file " + s); + } + + f.close(); + } + + QRect rc; + int x, y, id; + QString idstr; + QString path; + QString ext; + QString filename; + QString prefix; + int firstframe; + QProgressBar pBar(statusBar()); + pBar.setRange(curframe, totframes); + pBar.setVisible(true); + + //splitting the full path into parts + CommonFunctions::splitPath(s, path, filename, prefix, (uint)5, firstframe, ext); + + while(!err && curframe < totframes && QFile::exists(s)) + { + pBar.setValue(curframe); + rc.setCoords(0,0,0,0); + QFile fd(s); + if(fd.open(QIODevice::ReadOnly)) + { + QDomDocument doc(""); + if(doc.setContent(&fd)) + { + QDomNode anot = doc.documentElement(); + + curframe = anot.namedItem("frame").toElement().text().trimmed().toInt(); + + QDomNode n = anot.firstChild(); + while(!n.isNull()) //going through all the elements + { + QDomElement e = n.toElement(); + if(!e.isNull() && e.tagName() == "object") + { + idstr = e.namedItem("tgtID").toElement().text().trimmed(); + if(idstr == "") + { + n = n.nextSibling(); + continue; + } + id = idstr.toInt(); + Label *lbl = findLabel(id); + if(lbl == NULL) + { + //create a new label and added it to the list + //need to retrieve it from the list in order to + //get a pointer to the to the actual object in the list + lbl = new Label(); + lbl->number = id; + lbl->name = e.namedItem("name").toElement().text(); + mLabels.append(*lbl); + delete lbl; + lbl = findLabel(id); + + //adding ui item to the list + QListWidgetItem *it = new QListWidgetItem(); + it->setText(lbl->name); + it->setFlags(it->flags() | Qt::ItemIsEditable); + ui.listLabels->addItem(it); + } + + + QDomNode bbox = e.namedItem("bbox"); + if(!bbox.isNull()) + { + QDomNode pt = bbox.namedItem("pt"); + if(!pt.isNull()) + { + x = qRound(pt.namedItem("x").toElement().text().trimmed().toFloat()); + y = qRound(pt.namedItem("y").toElement().text().trimmed().toFloat()); + rc.setTopLeft(QPoint(x,y)); + + pt = pt.nextSibling(); + x = qRound(pt.namedItem("x").toElement().text().trimmed().toFloat()); + y = qRound(pt.namedItem("y").toElement().text().trimmed().toFloat()); + rc.setTopRight(QPoint(x,y)); + + pt = pt.nextSibling(); + x = qRound(pt.namedItem("x").toElement().text().trimmed().toFloat()); + y = qRound(pt.namedItem("y").toElement().text().trimmed().toFloat()); + rc.setBottomRight(QPoint(x,y)); + + pt = pt.nextSibling(); + x = qRound(pt.namedItem("x").toElement().text().trimmed().toFloat()); + y = qRound(pt.namedItem("y").toElement().text().trimmed().toFloat()); + rc.setBottomLeft(QPoint(x,y)); + + rc = imageToImage(w, h, rc, mDisplayImage->width(), mDisplayImage->height()); + + ViaPoint vp; + vp.frame = curframe; + vp.rc = rc; + lbl->boxes.append(vp); + lbl->viaPoints.append(vp); + } + } + + QDomNode polygon = e.namedItem("polygon"); + if(!polygon.isNull()) + { + ViaPointPolygon polyg; + polyg.frame = curframe; + QDomNode pt = polygon.namedItem("pt"); + while(!pt.isNull()) + { + x = qRound(pt.namedItem("x").toElement().text().trimmed().toFloat()); + y = qRound(pt.namedItem("y").toElement().text().trimmed().toFloat()); + + QPoint pl_pt(x,y); + + pl_pt = imageToImage(w, h, pl_pt, mDisplayImage->width(), mDisplayImage->height()); + polyg.pl << pl_pt; + pt = pt.nextSibling(); + } + //only adding polygon if it is at least a triangle + if(polyg.pl.count() > 2) + { + lbl->shape = Polyg; + lbl->polygons << polyg; + lbl->viaPointsPoly << polyg; + } + } + } + n = n.nextSibling(); + } + } + else + { + QMessageBox::information(this, "Invalid XML file", "Failed to parse XML file " + s); + err = true; + } + + fd.close(); + } + + firstframe++; + QString num; + num.sprintf("%05d",firstframe); + s = path + prefix + num + ext; + } + pBar.setVisible(false); + } + catch(...) + { + QMessageBox::information(this, "Unrecoverable error", "Error while processing file: " + s); + } + } +} + +void SimpleLabel::on_actionLoad_XML_triggered() +{ + int w, h; + QSize origSz = mMonitor->getImageSize(); + + w = origSz.width(); + h = origSz.height(); + + QString s = QFileDialog::getOpenFileName(this, tr("Open Label XML"), ".", tr("XML Files (*.xml)")); + if(!s.isEmpty()) + { + QFile fd(s); + if(fd.open(QIODevice::ReadOnly)) + { + QDomDocument doc("Labels"); + if(doc.setContent(&fd)) + { + resetLabels(); + + QDomNode root = doc.documentElement(); + QDomNode n = root.firstChild(); + while(!n.isNull()) //going through all the labels + { + QDomElement e = n.toElement(); + if(!e.isNull() && e.tagName() == "image") + { + w = e.attribute("width", "-1").toInt(); + h = e.attribute("height", "-1").toInt(); + } + else if(!e.isNull() && e.tagName() == "label") + { + Label lb; + lb.name = e.attribute("name", "-1"); + lb.desc = e.attribute("desc", "-1"); + lb.number = e.attribute("number", "-1").toInt(); + lb.shape = (LabelShape)e.attribute("shape", "-1").toInt(); + + QDomNode t = e.firstChild(); + while(!t.isNull()) + { + QDomElement el = t.toElement(); + qDebug() << el.tagName(); + if(!el.isNull()) + { + QDomNode p = el.firstChild(); + while(!p.isNull()) + { + QDomElement point = p.toElement(); + qDebug() << point.tagName(); + if(!point.isNull()) + { + if(el.tagName() == "boxes" || el.tagName() == "pivots") + { + ViaPoint pt; + + pt.frame = point.attribute("frame", "-1").toInt(); + pt.rc.setLeft(point.attribute("left", "-1").toInt()); + pt.rc.setTop(point.attribute("top", "-1").toInt()); + pt.rc.setRight(point.attribute("right", "-1").toInt()); + pt.rc.setBottom(point.attribute("bottom", "-1").toInt()); + pt.angle = (point.attribute("angle", "0.0").toFloat()); + if(point.tagName() == "box") + { + lb.boxes.append(pt); + } + else if(point.tagName() == "pivot") + { + lb.viaPoints.append(pt); + } + } + else if(el.tagName() == "polygons" || el.tagName() == "polygonPivots") + { + ViaPointPolygon pl; + pl.frame = point.attribute("frame", "-1").toInt(); + + QDomNode vertex = point.firstChild(); + while(!vertex.isNull()) + { + QDomElement ver = vertex.toElement(); + QPoint v; + v.setX(ver.attribute("x", "-1").toInt()); + v.setY(ver.attribute("y", "-1").toInt()); + + pl.pl << v; + + vertex = vertex.nextSibling(); + } + + if(point.tagName() == "polygon") + { + lb.polygons.append(pl); + } + else if(point.tagName() == "polygonPivot") + { + lb.viaPointsPoly.append(pl); + } + } + } + p = p.nextSibling(); + } + } + t = t.nextSibling(); + } + mLabels.append(lb); + ui.listLabels->addItem(lb.name); + } + n = n.nextSibling(); + } + + //converting coordinates to the screen image size + QList