Skip to content
Snippets Groups Projects
petrack.cpp 141 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * PeTrack - Software for tracking pedestrians movement in videos
    
     * Copyright (C) 2023 Forschungszentrum Jülich GmbH, IAS-7
    
     *
     * 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
     * (at your option) 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 <https://www.gnu.org/licenses/>.
     */
    
    
    d.kilic's avatar
    d.kilic committed
    #include <QSignalMapper>
    
    #include <QtOpenGL>
    #include <QtWidgets>
    
    d.kilic's avatar
    d.kilic committed
    
    // Added for Qt5 support
    
    #include "IO.h"
    
    #include "aboutDialog.h"
    #include "animation.h"
    #include "autoCalib.h"
    #include "backgroundItem.h"
    #include "calibFilter.h"
    #include "codeMarkerWidget.h"
    
    d.kilic's avatar
    d.kilic committed
    #include "colorMarkerItem.h"
    #include "colorMarkerWidget.h"
    
    #include "colorRangeWidget.h"
    
    d.kilic's avatar
    d.kilic committed
    #include "control.h"
    
    #include "filterBeforeBox.h"
    
    d.kilic's avatar
    d.kilic committed
    #include "gridItem.h"
    
    d.kilic's avatar
    d.kilic committed
    #include "imageItem.h"
    
    #include "logger.h"
    
    d.kilic's avatar
    d.kilic committed
    #include "logoItem.h"
    
    #include "moCapItem.h"
    
    #include "multiColorMarkerItem.h"
    #include "multiColorMarkerWidget.h"
    #include "openMoCapDialog.h"
    #include "pMessageBox.h"
    
    #include "person.h"
    
    #include "petrack.h"
    #include "player.h"
    
    #include "stereoItem.h"
    #include "stereoWidget.h"
    
    d.kilic's avatar
    d.kilic committed
    #include "tracker.h"
    
    #include "trackerItem.h"
    
    d.kilic's avatar
    d.kilic committed
    #include "trackerReal.h"
    
    #include "view.h"
    
    #include <QtPrintSupport/QPrintDialog>
    #include <QtPrintSupport/QPrinter>
    #include <cmath>
    
    Schrödter, Tobias's avatar
    Schrödter, Tobias committed
    #include <ctime>
    
    d.kilic's avatar
    d.kilic committed
    #include <iomanip>
    
    Schrödter, Tobias's avatar
    Schrödter, Tobias committed
    #include <opencv2/opencv.hpp>
    
    d.kilic's avatar
    d.kilic committed
    
    int Petrack::trcVersion = 0;
    
    // Reihenfolge des anlegens der objekte ist sehr wichtig
    
    Petrack::Petrack(QString petrackVersion) :
    
        mExtrCalibration(mPersonStorage),
    
        mPetrackVersion(std::move(petrackVersion)),
    
        mAuthors(IO::readAuthors(QCoreApplication::applicationDirPath() + "/.zenodo.json"))
    
    d.kilic's avatar
    d.kilic committed
    {
        QIcon icon;
    
        icon.addFile(":/icon");          // about
    
    d.kilic's avatar
    d.kilic committed
        icon.addFile(":/icon_smallest"); // window title bar
    
        setWindowIcon(icon);
    
        mHeadSize            = -1;
        mCmPerPixel          = -1;
        mScene               = nullptr;
        mTracker             = nullptr;
        mTrackerReal         = nullptr; // damit beim zeichnen von control mit analysePlot nicht auf einen feheler laeuft
        mStatusLabelFPS      = nullptr;
    
        mStatusPosRealHeight = nullptr;
    
        mStatusLabelPosReal  = nullptr;
        mImageItem           = nullptr;
        mRecognitionChanged  = true;
        mTrackChanged        = true;
        mCoordItem           = nullptr;
        mImage               = nullptr;
    
    d.kilic's avatar
    d.kilic committed
        setLoading(true);
    
        setAcceptDrops(true);
    
        int space = 2;
    
        mBrightContrastFilter.disable();
        mBorderFilter.disable();
        mSwapFilter.disable();
        mBackgroundFilter.disable();
    
        mStereoContext = nullptr;
    
        mCalibFilter   = new CalibFilter; // schoener waere erst zu erzeugen, wenn video geladen wird, da sonst bei stereo
                                          // erst normealer und dann stereo objekt erzeugt wird
        mCalibFilter->disable(); // aber control widget greift schon bei erzeugung auf alle objekte zur einstellung zurueck
    
    d.kilic's avatar
    d.kilic committed
    
    
        mScene = new QGraphicsScene(this);
    
    
        mTrackingRoiItem = new RoiItem(this, Qt::blue);
        connect(mTrackingRoiItem, &RoiItem::changed, this, [=]() { this->setTrackChanged(true); });
        mTrackingRoiItem->setZValue(4); // groesser heisst weiter oben
    
        mRecognitionRoiItem = new RoiItem(this, Qt::green);
        connect(mRecognitionRoiItem, &RoiItem::changed, this, [=]() { this->setRecognitionChanged(true); });
        mRecognitionRoiItem->setZValue(5); // groesser heisst weiter oben
    
    
    
        // setup control
    
        auto updateImageCallback = [this]()
        {
            if(!isLoading())
            {
                updateImage();
            }
        };
    
        auto *filterBeforeBox = new FilterBeforeBox(
            nullptr, // reparented when added to layout
            *getBackgroundFilter(),
            *getBrightContrastFilter(),
            *getBorderFilter(),
            *getSwapFilter(),
            updateImageCallback);
    
        mControlWidget =
            new Control(*this, *mScene, mReco, *mTrackingRoiItem, *mRecognitionRoiItem, mMissingFrames, filterBeforeBox);
    
        // end setup control
    
    d.kilic's avatar
    d.kilic committed
    
        mStereoWidget = new StereoWidget(this);
        mStereoWidget->setWindowFlags(Qt::Window);
        mStereoWidget->setWindowTitle("Stereo parameter");
    
        mColorRangeWidget = new ColorRangeWidget(this);
        mColorRangeWidget->setWindowFlags(Qt::Window);
        mColorRangeWidget->setWindowTitle("Color range");
    
        mColorMarkerWidget = new ColorMarkerWidget(this);
        mColorMarkerWidget->setWindowFlags(Qt::Window);
        mColorMarkerWidget->setWindowTitle("Color marker parameter");
    
    
        mCodeMarkerWidget = new CodeMarkerWidget(this, mReco.getCodeMarkerOptions(), nullptr);
    
    d.kilic's avatar
    d.kilic committed
        mCodeMarkerWidget->setWindowFlags(Qt::Window);
        mCodeMarkerWidget->setWindowTitle("Code marker parameter");
    
    
        mMultiColorMarkerWidget = new MultiColorMarkerWidget(this);
        mMultiColorMarkerWidget->setWindowFlags(Qt::Window);
        mMultiColorMarkerWidget->setWindowTitle("MultiColor marker parameter");
    
        mImageItem = new ImageItem(this); // durch uebergabe von scene wird indirekt ein scene->addItem() aufgerufen
    
    d.kilic's avatar
    d.kilic committed
    
        mAnimation = new Animation(this);
    
        mLogoItem = new LogoItem(this); // durch uebergabe von scene wird indirekt ein scene->addItem() aufgerufen
    
        mLogoItem->setZValue(6);        // groesser heisst weiter oben
    
    d.kilic's avatar
    d.kilic committed
    
        mExtrCalibration.setMainWindow(this);
    
        mGridItem = new GridItem(this);
        mGridItem->setZValue(2.5); // durch uebergabe von scene wird indirekt ein scene->addItem() aufgerufen
    
        mCoordItem = new CoordItem(this);
        mCoordItem->setZValue(3); // groesser heisst weiter oben
        mImageItem->setCoordItem(mCoordItem);
    
        mViewWidget = new ViewWidget(this);
    
        mView       = mViewWidget->view();
    
    d.kilic's avatar
    d.kilic committed
        mView->setScene(mScene);
    
        connect(mView, &GraphicsView::mouseDoubleClick, this, [this]() { this->openSequence(); });
        connect(mView, &GraphicsView::mouseShiftDoubleClick, this, &Petrack::addManualTrackPointOnlyVisible);
        connect(mView, &GraphicsView::mouseShiftControlDoubleClick, this, &Petrack::splitTrackPerson);
        connect(mView, &GraphicsView::mouseControlDoubleClick, this, &Petrack::addOrMoveManualTrackPoint);
        connect(mView, &GraphicsView::mouseRightDoubleClick, this, &Petrack::deleteTrackPoint);
        connect(mView, &GraphicsView::mouseMiddleDoubleClick, this, &Petrack::deleteTrackPointAll);
        connect(mView, &GraphicsView::mouseShiftWheel, this, &Petrack::skipToFrameWheel);
    
        connect(mView, &GraphicsView::mouseCtrlAltDoubleClick, this, &Petrack::skipToFrameFromTrajectory);
    
        connect(mView, &GraphicsView::mouseAltMoved, this, &Petrack::moveTrackPoint);
        connect(mView, &GraphicsView::mouseAltPressed, this, &Petrack::selectPersonForMoveTrackPoint);
        connect(mView, &GraphicsView::altReleased, this, &Petrack::releaseTrackPoint);
        connect(mView, &GraphicsView::mouseAltReleased, this, &Petrack::releaseTrackPoint);
    
        connect(mView, &GraphicsView::mouseCtrlWheel, this, &Petrack::scrollShowOnly);
    
    d.kilic's avatar
    d.kilic committed
    
        mPlayerWidget = new Player(mAnimation, this);
    
        QVBoxLayout *vLayout = new QVBoxLayout;
        vLayout->setSpacing(space);
        vLayout->addWidget(mViewWidget);
        vLayout->addWidget(mPlayerWidget);
    
        //---------------------------
    
    
        mTracker     = new Tracker(this, mPersonStorage);
        mTrackerReal = new TrackerReal(this, mPersonStorage);
        mTrackerItem = new TrackerItem(this, mPersonStorage);
    
    d.kilic's avatar
    d.kilic committed
        mTrackerItem->setZValue(5); // groesser heisst weiter oben
    
    
        mControlWidget->getColorPlot()->setPersonStorage(&mPersonStorage);
    
    d.kilic's avatar
    d.kilic committed
    #ifdef QWT
        mControlWidget->getAnalysePlot()->setTrackerReal(mTrackerReal);
    #endif
    
        //---------------------------
    
        mStereoItem = new StereoItem(this);
    
        mStereoItem->setZValue(2); // groesser heisst weiter oben
    
    d.kilic's avatar
    d.kilic committed
        mStereoItem->setVisible(false);
    
        //---------------------------
    
        mColorMarkerItem = new ColorMarkerItem(this);
    
        mColorMarkerItem->setZValue(2); // groesser heisst weiter oben
    
    d.kilic's avatar
    d.kilic committed
        mColorMarkerItem->setVisible(false);
        //---------------------------
    
    
        mCodeMarkerItem = new CodeMarkerItem(this, mReco.getCodeMarkerOptions());
    
        mCodeMarkerItem->setZValue(2); // groesser heisst weiter oben
    
    d.kilic's avatar
    d.kilic committed
        mCodeMarkerItem->setVisible(false);
        //---------------------------
    
        mMultiColorMarkerItem = new MultiColorMarkerItem(this);
    
        mMultiColorMarkerItem->setZValue(2); // groesser heisst weiter oben
    
    d.kilic's avatar
    d.kilic committed
        mMultiColorMarkerItem->setVisible(false);
    
        //---------------------------
    
    
        mBackgroundItem = new BackgroundItem(this, nullptr, *filterBeforeBox);
    
    d.kilic's avatar
    d.kilic committed
        mBackgroundItem->setZValue(2.2); // um so groesser um so hoeher um so eher zu sehen
        mBackgroundItem->setVisible(false);
    
    
        //---------------------------
    
        mMoCapItem = new MoCapItem(*this, *mAnimation, mMoCapController);
        mMoCapItem->setZValue(3); // um so groesser um so hoeher um so eher zu sehen
    
    
    d.kilic's avatar
    d.kilic committed
        /// Add Items
        mScene->addItem(mImageItem);
        mScene->addItem(mLogoItem);
        mScene->addItem(mGridItem);
        mScene->addItem(mCoordItem);
        mScene->addItem(mTrackingRoiItem);
        mScene->addItem(mRecognitionRoiItem);
        mScene->addItem(mTrackerItem);
        mScene->addItem(mStereoItem);
        mScene->addItem(mColorMarkerItem);
        mScene->addItem(mCodeMarkerItem);
        mScene->addItem(mMultiColorMarkerItem);
        mScene->addItem(mBackgroundItem);
    
        mScene->addItem(mMoCapItem);
    
    d.kilic's avatar
    d.kilic committed
    
        //---------------------------
    
        mCentralLayout = new QHBoxLayout;
        mCentralLayout->setMargin(space);
        mCentralWidget = new QFrame;
        mCentralWidget->setFrameStyle(QFrame::Sunken | QFrame::StyledPanel);
        mCentralWidget->setLayout(mCentralLayout);
        setCentralWidget(mCentralWidget);
    
        mSplitter = new QSplitter(this);
        // create playAndView-Widget to wrap layout, since QSplitter only accepts widgets
        QWidget *playAndView = new QWidget(this);
        playAndView->setLayout(vLayout);
        mSplitter->addWidget(playAndView);
    
        mSplitter->addWidget(mControlWidget);
    
    
        mSplitter->setStretchFactor(0, 1);
        mSplitter->setStretchFactor(1, 0);
    
    
        mCentralLayout->addWidget(mSplitter);
    
    
    
    d.kilic's avatar
    d.kilic committed
        setWindowTitle(tr("PeTrack"));
    
        //---------------------------
        mAutoCalib.setMainWindow(this);
        //---------------------------
    
        createActions();
        createMenus();
        createStatusBar();
    
    
        auto *exportShortCut = new QShortcut{QKeySequence("Ctrl+e"), this};
        connect(exportShortCut, &QShortcut::activated, this, [=]() { exportTracker(); });
    
        auto *toggleOnlineTracking = new QShortcut{QKeySequence("Shift+t"), this};
    
        connect(toggleOnlineTracking, &QShortcut::activated, this, [=]() { mControlWidget->toggleOnlineTracking(); });
    
        // TODO delete once we get Options to be value only (i.e. no Pointer/Ref anymore)
    
        mReco.getCodeMarkerOptions().setControlWidget(mControlWidget);
        mReco.getCodeMarkerOptions().setCodeMarkerItem(mCodeMarkerItem);
    
        mSeqFileName = QDir::currentPath(); // fuer allerersten Aufruf des Programms
    
    d.kilic's avatar
    d.kilic committed
        readSettings();
    
    
        saveXml(mDefaultSettings); // noch nicht noetig, da eh noch nicht fkt
    
    d.kilic's avatar
    d.kilic committed
    
        mShowFPS = 0;
    
        mTrcFileName = "";
    
        // initialer Aufruf, damit beim reinen Laden einer Videodatei die Defaultwerte in control genommen werden zum Setzen
        setHeadSize();
    
        // um im background subtraction filter das hoehenbild zu beruecksichtigen
        mBackgroundFilter.setStereoContext(&mStereoContext);
    
    
        mAutoBackTrack          = true;  // ist der default, dann wenn in XML-Datei nicht drin steht
    
    d.kilic's avatar
    d.kilic committed
        mAutoTrackOptimizeColor = false; // ist der default, dann wenn in XML-Datei nicht drin steht
    
        setLoading(false);
    }
    Petrack::~Petrack()
    {
        delete mImage;
        // hier muessten weitere stehen insb die im konstruktor erzeugt werden
        // aber da petrack nur vernichtet wird, wenn programm beendet wird, kann man sich das auch schenken
    }
    
    void Petrack::dragEnterEvent(QDragEnterEvent *event)
    {
    
        if(event->mimeData()->hasUrls())
    
    d.kilic's avatar
    d.kilic committed
            event->acceptProposedAction();
    
    d.kilic's avatar
    d.kilic committed
    }
    
    
    /**
     * @brief Accepts dropped .pet, .trc and media files
     *
     * Opens the project for a .pet. Imports the trajectories for a .trc
     * and tries to open the sequence for any other kind of file.
     *
     * @param event
     */
    
    d.kilic's avatar
    d.kilic committed
    void Petrack::dropEvent(QDropEvent *event)
    {
    
        if(event->mimeData()->hasUrls())
    
    d.kilic's avatar
    d.kilic committed
        {
    
            if(event->mimeData()->urls().first().toLocalFile().right(4) == ".pet")
    
    d.kilic's avatar
    d.kilic committed
                openProject(event->mimeData()->urls().first().toLocalFile());
    
            else if(event->mimeData()->urls().first().toLocalFile().right(4) == ".trc")
    
    d.kilic's avatar
    d.kilic committed
                importTracker(event->mimeData()->urls().first().toLocalFile());
    
    d.kilic's avatar
    d.kilic committed
            else
    
    d.kilic's avatar
    d.kilic committed
                openSequence(event->mimeData()->urls().first().toLocalFile());
    
    d.kilic's avatar
    d.kilic committed
            event->acceptProposedAction();
        }
    }
    
    void Petrack::updateSceneRect()
    {
    
        double iW = 0, iH = 0, bS = 0;
    
    d.kilic's avatar
    d.kilic committed
    
    
        if(mImage && !mImage->isNull())
    
    d.kilic's avatar
    d.kilic committed
        {
            iW = mImage->width();
            iH = mImage->height();
            bS = getImageBorderSize();
        }
    
        else
        {
            mScene->setSceneRect(mScene->itemsBoundingRect());
            return;
        }
    
    d.kilic's avatar
    d.kilic committed
    
    
        if(mControlWidget->getCalibCoordShow())
    
    d.kilic's avatar
    d.kilic committed
        {
    
            double scale = mControlWidget->getCalibCoordScale() / 10.;
            double tX    = mControlWidget->getCalibCoordTransX() / 10.;
            double tY    = mControlWidget->getCalibCoordTransY() / 10.;
    
    d.kilic's avatar
    d.kilic committed
    
            // setzen der bounding box der scene
            // Faktor 1.1 dient dazu, dass auch Zahl "1" bei coord gut in sichtbaren Bereich passt
    
            double xMin = (tX - 1.1 * scale < -bS) ? tX - 1.1 * scale : -bS;
            double yMin = (tY - 1.1 * scale < -bS) ? tY - 1.1 * scale : -bS;
            double xMax = (tX + 1.1 * scale > iW - bS) ? tX + 1.1 * scale : iW - bS;
            double yMax = (tY + 1.1 * scale > iH - bS) ? tY + 1.1 * scale : iH - bS;
            mScene->setSceneRect(xMin, yMin, xMax - xMin, yMax - yMin);
    
    d.kilic's avatar
    d.kilic committed
        }
        else
    
    d.kilic's avatar
    d.kilic committed
            mScene->setSceneRect(-bS, -bS, iW, iH);
    
    d.kilic's avatar
    d.kilic committed
    }
    
    
    /**
     * @brief Loads the content of a .pet file into petrack
     *
     * @param doc the DOM of the .pet file
     * @param openSeq true, if sequence given in doc should be opened
     */
    
    d.kilic's avatar
    d.kilic committed
    void Petrack::openXml(QDomDocument &doc, bool openSeq)
    {
    
        mMissingFrames.reset();
        bool                      missingFramesExecuted = false;
        std::vector<MissingFrame> missingFrames{};
    
    
    d.kilic's avatar
    d.kilic committed
        QDomElement root = doc.firstChildElement("PETRACK");
    
        QString     seq;
        int         frame = -1, sourceFrameIn = -1, sourceFrameOut = -1;
        double      fps              = DEFAULT_FPS;
        int         onlyPeopleNr     = 1;
        QString     onlyPeopleNrList = "1";
        int         zoom = 250, rotate = 0, hScroll = 0, vScroll = 0;
    
    d.kilic's avatar
    d.kilic committed
        enum Camera cam = cameraUnset;
        setLoading(true);
        for(QDomElement elem = root.firstChildElement(); !elem.isNull(); elem = elem.nextSiblingElement())
        {
    
            if(elem.tagName() == "MAIN")
    
    d.kilic's avatar
    d.kilic committed
            {
    
                if(elem.hasAttribute("SRC"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    seq            = elem.attribute("SRC");
    
    d.kilic's avatar
    d.kilic committed
                    QString tmpSeq = getExistingFile(seq, mProFileName);
    
                    if(tmpSeq != "")
    
    d.kilic's avatar
    d.kilic committed
                        seq = tmpSeq;
    
    d.kilic's avatar
    d.kilic committed
                }
    
                if(elem.hasAttribute("STATUS_HEIGHT"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    if(mStatusPosRealHeight) // null kann eigentlich nicht vorkommen, da im constructor von petrack erzeugt
                                             // wird
    
    d.kilic's avatar
    d.kilic committed
                        mStatusPosRealHeight->setValue(elem.attribute("STATUS_HEIGHT").toDouble());
    
    d.kilic's avatar
    d.kilic committed
                }
            }
    
            else if(elem.tagName() == "STEREO")
    
    d.kilic's avatar
    d.kilic committed
            {
                mStereoWidget->getXml(elem);
            }
    
            else if(elem.tagName() == "COLOR_MARKER")
    
    d.kilic's avatar
    d.kilic committed
            {
                mColorMarkerWidget->getXml(elem);
            }
    
            else if(elem.tagName() == "CODE_MARKER")
    
    d.kilic's avatar
    d.kilic committed
            {
                mCodeMarkerWidget->getXml(elem);
            }
    
            else if(elem.tagName() == "MULTI_COLOR_MARKER")
    
    d.kilic's avatar
    d.kilic committed
            {
                mMultiColorMarkerWidget->getXml(elem);
            }
    
            else if(elem.tagName() == "MOCAP")
    
            {
                mMoCapController.getXml(elem);
            }
    
            else if(elem.tagName() == "CONTROL")
    
    d.kilic's avatar
    d.kilic committed
            {
                mControlWidget->getXml(elem);
                QDomElement tmpElem = (elem.firstChildElement("TRACKING")).firstChildElement("PATH");
    
                if(tmpElem.hasAttribute("ONLY_PEOPLE_NR"))
    
    d.kilic's avatar
    d.kilic committed
                    onlyPeopleNr = tmpElem.attribute("ONLY_PEOPLE_NR").toInt();
    
                if(tmpElem.hasAttribute("ONLY_PEOPLE_NR_LIST"))
    
    d.kilic's avatar
    d.kilic committed
                    onlyPeopleNrList = tmpElem.attribute("ONLY_PEOPLE_NR_LIST");
    
    d.kilic's avatar
    d.kilic committed
            }
    
            else if(elem.tagName() == "EXTR_CALIBRATION")
    
            else if(elem.tagName() == "PLAYER")
    
    d.kilic's avatar
    d.kilic committed
            {
    
                if(elem.hasAttribute("FRAME"))
    
    d.kilic's avatar
    d.kilic committed
                {
                    frame = elem.attribute("FRAME").toInt();
                }
    
                if(elem.hasAttribute("FPS"))
    
    d.kilic's avatar
    d.kilic committed
                {
                    fps = elem.attribute("FPS").toDouble();
                }
    
                if(elem.hasAttribute("SOURCE_FRAME_IN"))
    
    d.kilic's avatar
    d.kilic committed
                {
                    sourceFrameIn = elem.attribute("SOURCE_FRAME_IN").toInt();
                }
    
                if(elem.hasAttribute("SOURCE_FRAME_OUT"))
    
    d.kilic's avatar
    d.kilic committed
                {
                    sourceFrameOut = elem.attribute("SOURCE_FRAME_OUT").toInt();
                }
    
                if(elem.hasAttribute("PLAYER_SPEED_FIXED"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    mPlayerWidget->setPlayerSpeedLimited(elem.attribute("PLAYER_SPEED_FIXED").toInt());
    
    d.kilic's avatar
    d.kilic committed
                }
            }
    
            else if(elem.tagName() == "VIEW")
    
    d.kilic's avatar
    d.kilic committed
            {
    
                if(elem.hasAttribute("ANTIALIAS"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    mAntialiasAct->setChecked(elem.attribute("ANTIALIAS").toInt() == Qt::Checked);
    
    d.kilic's avatar
    d.kilic committed
                }
    
                if(elem.hasAttribute("OPENGL"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    mOpenGLAct->setChecked(elem.attribute("OPENGL").toInt() == Qt::Checked);
    
    d.kilic's avatar
    d.kilic committed
                }
    
                if(elem.hasAttribute("SAVE_TRANSFORMED"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    mCropZoomViewAct->setChecked(elem.attribute("SAVE_TRANSFORMED") == Qt::Checked);
    
    d.kilic's avatar
    d.kilic committed
                }
    
                if(elem.hasAttribute("TRANSFORMATION"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    QString     matStr = elem.attribute("TRANSFORMATION");
    
    d.kilic's avatar
    d.kilic committed
                    QTextStream in(&matStr);
                    in >> zoom >> rotate >> hScroll >> vScroll;
                }
    
                if(elem.hasAttribute("CAMERA"))
    
    d.kilic's avatar
    d.kilic committed
                {
                    cam = (enum Camera) elem.attribute("CAMERA").toInt();
                }
    
                if(elem.hasAttribute("HIDE_CONTROLS"))
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    mHideControlsAct->setChecked(elem.attribute("HIDE_CONTROLS").toInt() == Qt::Checked);
    
    d.kilic's avatar
    d.kilic committed
                }
    
            }
            else if(elem.tagName() == "AUTO_TRACK")
    
    d.kilic's avatar
    d.kilic committed
            {
    
                if(elem.hasAttribute("BACK_TRACK"))
    
    d.kilic's avatar
    d.kilic committed
                {
                    mAutoBackTrack = elem.attribute("BACK_TRACK").toInt();
                }
    
                if(elem.hasAttribute("OPTIMZE_COLOR"))
    
    d.kilic's avatar
    d.kilic committed
                {
                    mAutoTrackOptimizeColor = elem.attribute("OPTIMZE_COLOR").toInt();
                }
            }
    
            else if(elem.tagName() == "MISSING_FRAMES")
            {
                if((elem.hasAttribute("executed")) && (elem.attribute("executed").toInt() == 1))
                {
                    missingFramesExecuted = true;
                    auto node             = elem.firstChildElement("FRAME");
                    for(; !node.isNull(); node = node.nextSiblingElement("FRAME"))
                    {
                        size_t num   = node.attribute("NUM_FRAME").toUInt();
                        int    count = node.attribute("NUM_MISSING").toInt();
                        missingFrames.push_back(MissingFrame{num, count});
                    }
                }
            }
    
    d.kilic's avatar
    d.kilic committed
            else
    
                SPDLOG_ERROR("Unknown PETRACK tag {}", elem.tagName());
    
    d.kilic's avatar
    d.kilic committed
        }
        // open koennte am schluss passieren, dann wuerde nicht erst unveraendertes bild angezeigt,
        // dafuer koennte es aber sein, dass werte zb bei fx nicht einstellbar sind!
    
            if(seq != "")
            {
                openSequence(seq);
            }
            else
            {
                mAnimation->reset();
                mImg         = cv::Mat();
                mImgFiltered = cv::Mat();
                delete mImage;
                mImage = nullptr;
                updateSequence();
                mLogoItem->ensureVisible();
                mLogoItem->fadeIn();
            }
    
    d.kilic's avatar
    d.kilic committed
    
    
        mMissingFrames.setExecuted(missingFramesExecuted);
        mMissingFrames.setMissingFrames(missingFrames);
    
    
    d.kilic's avatar
    d.kilic committed
        mViewWidget->setZoomLevel(zoom);
        mViewWidget->setRotateLevel(rotate);
        mView->horizontalScrollBar()->setValue(hScroll);
        mView->verticalScrollBar()->setValue(vScroll);
    
        bool loaded = false;
    
        if(!mBackgroundFilter.getFilename().isEmpty())
    
    d.kilic's avatar
    d.kilic committed
        {
    
            if(!(loaded = mBackgroundFilter.load(mBackgroundFilter.getFilename())))
    
                SPDLOG_ERROR("Error: loading background file {}!", mBackgroundFilter.getFilename());
    
    d.kilic's avatar
    d.kilic committed
        }
    
    
        mPlayerWidget->setFrameInNum(sourceFrameIn == -1 ? mAnimation->getSourceInFrameNum() : sourceFrameIn);
        mPlayerWidget->setFrameOutNum(sourceFrameOut == -1 ? mAnimation->getSourceOutFrameNum() : sourceFrameOut);
    
    d.kilic's avatar
    d.kilic committed
        mPlayerWidget->update();
    
    
        if(frame != -1)
    
    d.kilic's avatar
    d.kilic committed
        {
    
            if(mControlWidget->isFilterBgChecked() &&
    
               !loaded) // mit dem anfangs geladenen bild wurde bereits faelschlicherweise bg bestimmt
            {
    
    d.kilic's avatar
    d.kilic committed
                mBackgroundFilter.reset(); // erst nach dem springen zu einem frame background bestimmen
    
    d.kilic's avatar
    d.kilic committed
            mPlayerWidget->skipToFrame(frame); // hier wird updateImage ausgefuehrt
        }
    
        else if(loaded)
    
    d.kilic's avatar
    d.kilic committed
            updateImage();
    
    d.kilic's avatar
    d.kilic committed
    
        // nicht schon in control, sonst loescht opensequence wieder tracker
    
        if(mTrcFileName != "")
    
    d.kilic's avatar
    d.kilic committed
        {
            // vorher loeschen aller trajektorien, da sonst nach start im ersten bild
            // mgl zwei trackpoints
            // beim haendischen importieren sind weiterhin parallele trajektorien moeglich (warnung wird ausgegeben)
            frame = 0; // default
    
            if((mPersonStorage.largestLastFrame() >= frame) && (getPersonStorage().smallestFirstFrame() <= frame))
    
    d.kilic's avatar
    d.kilic committed
            {
    
                mPersonStorage.clear();
    
    d.kilic's avatar
    d.kilic committed
                mTracker->reset();
            }
            importTracker(mTrcFileName);
        }
    
    
        mControlWidget->setTrackShowOnlyNr(onlyPeopleNr);
        mControlWidget->trackShowOnlyNrList()->setText(onlyPeopleNrList);
    
    d.kilic's avatar
    d.kilic committed
    
    
        if(cam == cameraLeft)
    
    d.kilic's avatar
    d.kilic committed
            mCameraLeftViewAct->setChecked(true);
    
        else if(cam == cameraRight)
    
    d.kilic's avatar
    d.kilic committed
            mCameraRightViewAct->setChecked(true);
    
    d.kilic's avatar
    d.kilic committed
        setCamera();
        mPlayerWidget->setFPS(fps); // erst spaet setzen, damit Wert den des geladenen Videos ueberschreiben kann
        setLoading(false);
    }
    
    void Petrack::openProject(QString fileName, bool openSeq) // default fileName="", openSequence = true
    {
    
        if(!QFileInfo(mProFileName).isDir()) // a project is already loaded
    
            if(!maybeSave())
    
    d.kilic's avatar
    d.kilic committed
                return;
    
    d.kilic's avatar
    d.kilic committed
        // if no destination file or folder is given
    
        if(fileName.isEmpty())
    
            fileName = QFileDialog::getOpenFileName(
                this,
                tr("Select project file"),
                QFileInfo(mProFileName).path(),
                tr("PeTrack project file (*.pet);;All files (*.*)"));
    
        if(Autosave::autosaveExists(fileName) && fileName != mProFileName)
        {
            auto ret = PQuestion(this, "Autosave detected", "An autosave was detected.\nDo you want to load the Autosave?");
            if(ret == PMessageBox::StandardButton::Yes)
            {
                setProFileName(fileName);
                mAutosave.loadAutosave();
                return;
            }
        }
    
    
        if(!fileName.isEmpty())
    
    d.kilic's avatar
    d.kilic committed
        {
            QFile file(fileName);
    
            if(!file.open(QIODevice::ReadOnly))
    
    d.kilic's avatar
    d.kilic committed
            {
    
                PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2.").arg(fileName, file.errorString()));
    
    d.kilic's avatar
    d.kilic committed
                return;
            }
    
    
            QDomDocument oldSettings;
            QString      oldProFilename = mProFileName;
            saveXml(oldSettings);
    
    
    d.kilic's avatar
    d.kilic committed
            resetSettings();
            QDomDocument doc("PETRACK"); // eigentlich Pfad zu Beschreibungsdatei fuer Dateiaufbau
    
            if(!doc.setContent(&file))
    
    d.kilic's avatar
    d.kilic committed
            {
    
                PCritical(this, tr("PeTrack"), tr("Cannot read content from %1.").arg(fileName));
    
    d.kilic's avatar
    d.kilic committed
                file.close();
                return;
            }
    
    
            SPDLOG_INFO("open: {}", fileName);
    
    d.kilic's avatar
    d.kilic committed
            file.close();
    
            setProFileName(fileName);
    
    d.kilic's avatar
    d.kilic committed
    
            QDomElement root = doc.firstChildElement("PETRACK");
    
            if(root.hasAttribute("VERSION"))
    
                if(root.attribute("VERSION") != mPetrackVersion)
    
    d.kilic's avatar
    d.kilic committed
                {
    
                    PWarning(
                        this,
                        tr("PeTrack"),
                        tr("Reading %1:\nDifferent version numbers %2 (application) and %3 (file) may cause problems.")
                            .arg(fileName, mPetrackVersion, root.attribute("VERSION")));
    
    d.kilic's avatar
    d.kilic committed
                }
    
            try
            {
                openXml(doc, openSeq);
            }
            catch(std::domain_error &e)
            {
                // reset to settings before trying to load new file
                openXml(oldSettings);
                setProFileName(oldProFilename);
    
                auto errorStr = QString{"Error during reading of pet file:\n%1"}.arg(e.what());
                PCritical(this, "Could not read pet-file", errorStr);
                return;
            }
    
            mLastTrackerExport = mTrcFileName;
    
    
    d.kilic's avatar
    d.kilic committed
            updateWindowTitle();
        }
    }
    
    void Petrack::saveXml(QDomDocument &doc)
    {
        QDomElement elem;
    
        QDomElement root = doc.createElement("PETRACK");
    
        root.setAttribute("VERSION", mPetrackVersion);
    
    d.kilic's avatar
    d.kilic committed
        doc.appendChild(root);
    
        // main settings (window size, status hight)
    
        elem        = doc.createElement("MAIN");
    
        QString seq = getFileList(mSeqFileName, mProFileName);
    
    d.kilic's avatar
    d.kilic committed
    
        elem.setAttribute("SRC", seq);
        elem.setAttribute("STATUS_HEIGHT", mStatusPosRealHeight->value());
        root.appendChild(elem);
    
        // control settings (right control widget)
        elem = doc.createElement("CONTROL");
        mControlWidget->setXml(elem);
        root.appendChild(elem);
    
    
        // Reprojection error extrinsic calib
        elem = doc.createElement("EXTR_CALIBRATION");
        mExtrCalibration.setXml(elem);
        root.appendChild(elem);
    
    
    d.kilic's avatar
    d.kilic committed
        // settings for stereo
        elem = doc.createElement("STEREO");
        mStereoWidget->setXml(elem);
        root.appendChild(elem);
    
        // settings for color marker
        elem = doc.createElement("COLOR_MARKER");
        mColorMarkerWidget->setXml(elem);
        root.appendChild(elem);
    
        // settings for code marker
        elem = doc.createElement("CODE_MARKER");
        mCodeMarkerWidget->setXml(elem);
        root.appendChild(elem);
    
        // settings for multicolor marker
        elem = doc.createElement("MULTI_COLOR_MARKER");
        mMultiColorMarkerWidget->setXml(elem);
        root.appendChild(elem);
    
    
        // settings for MoCap-Visualization
        elem = doc.createElement("MOCAP");
        mMoCapController.setXml(elem);
        root.appendChild(elem);
    
    
    d.kilic's avatar
    d.kilic committed
        // player settings (which frame, frame range)
        elem = doc.createElement("PLAYER");
        elem.setAttribute("FRAME", mPlayerWidget->getPos()); // == mAnimation->getCurrentFrameNum()
        elem.setAttribute("FPS", mAnimation->getFPS());
        elem.setAttribute("SOURCE_FRAME_IN", mPlayerWidget->getFrameInNum());
        elem.setAttribute("SOURCE_FRAME_OUT", mPlayerWidget->getFrameOutNum());
    
        elem.setAttribute("PLAYER_SPEED_FIXED", mPlayerWidget->getPlayerSpeedLimited());
    
    d.kilic's avatar
    d.kilic committed
    
        root.appendChild(elem);
    
        // view settings (zoom, rotate, alias, opengl)
        elem = doc.createElement("VIEW");
        elem.setAttribute("ANTIALIAS", mAntialiasAct->isChecked());
        elem.setAttribute("OPENGL", mOpenGLAct->isChecked());
        elem.setAttribute("SAVE_TRANSFORMED", mCropZoomViewAct->isChecked());
    
        elem.setAttribute(
            "TRANSFORMATION",
            QString("%1 %2 %3 %4")
                .arg(mViewWidget->getZoomLevel())
                .arg(mViewWidget->getRotateLevel())
                .arg(mView->horizontalScrollBar()->value())
                .arg(mView->verticalScrollBar()->value()));
    
    #ifndef STEREO_DISABLED
    
    d.kilic's avatar
    d.kilic committed
        elem.setAttribute("CAMERA", mAnimation->getCamera());
    
    #else
        elem.setAttribute("CAMERA", cameraUnset);
    #endif
    
    d.kilic's avatar
    d.kilic committed
        elem.setAttribute("HIDE_CONTROLS", mHideControlsAct->isChecked());
        root.appendChild(elem);
    
        // auto track settings
        elem = doc.createElement("AUTO_TRACK");
        elem.setAttribute("BACK_TRACK", mAutoBackTrack);
        elem.setAttribute("OPTIMZE_COLOR", mAutoTrackOptimizeColor);
        root.appendChild(elem);
    
    
        elem = doc.createElement("MISSING_FRAMES");
        elem.setAttribute("executed", mMissingFrames.isExecuted());
        for(const auto &missingFrame : mMissingFrames.getMissingFrames())
        {
            auto frame = doc.createElement("FRAME");
            frame.setAttribute("NUM_FRAME", static_cast<int>(missingFrame.mNumber));
            frame.setAttribute("NUM_MISSING", missingFrame.mCount);
            elem.appendChild(frame);
        }
        root.appendChild(elem);
    
    d.kilic's avatar
    d.kilic committed
    }
    
    
    /// rueckgabewert zeigt an, ob gesichert werden konnte
    
    d.kilic's avatar
    d.kilic committed
    bool Petrack::saveSameProject()
    {
        return saveProject(mProFileName);
    }
    
    
    d.kilic's avatar
    d.kilic committed
    bool Petrack::saveProjectAs()
    {
        auto fileName = QFileDialog::getSaveFileName(
            this, tr("Select project file"), mProFileName, tr("PeTrack project file (*.pet);;All files (*.*)"));
        return saveProject(fileName);
    }
    
    
    /// rueckgabewert zeigt an, ob gesichert werden konnte
    
    d.kilic's avatar
    d.kilic committed
    bool Petrack::saveProject(QString fileName) // default fileName=""
    {
        // if no destination file or folder is given
    
        if(fileName.isEmpty() && QFileInfo(mProFileName).isDir())
    
    d.kilic's avatar
    d.kilic committed
        {
    
            fileName = QFileDialog::getSaveFileName(
                this, tr("Select project file"), mProFileName, tr("PeTrack project file (*.pet);;All files (*.*)"));
    
    d.kilic's avatar
    d.kilic committed
        }
    
    
        if(!fileName.isEmpty())
    
    d.kilic's avatar
    d.kilic committed
        {
    
            setProFileName(fileName);
    
    d.kilic's avatar
    d.kilic committed
            QDomDocument doc("PETRACK"); // eigentlich Pfad zu Beschreibungsdatei fuer Dateiaufbau
            saveXml(doc);
    
            // file output
    
            QByteArray       byteArray;
            QXmlStreamWriter xmlStream(&byteArray);
    
    d.kilic's avatar
    d.kilic committed
            xmlStream.setAutoFormatting(true);
            xmlStream.setAutoFormattingIndent(4);
    
            xmlStream.writeStartDocument();
            xmlStream.writeDTD("<!DOCTYPE PETRACK>");
    
            QDomElement element = doc.documentElement();
            writeXmlElement(xmlStream, element);
    
            xmlStream.writeEndDocument();
    
            QFile file(fileName);
    
            if(!file.open(QFile::WriteOnly | QFile::Truncate | QFile::Text))
    
    d.kilic's avatar
    d.kilic committed
            {
    
                PCritical(this, tr("PeTrack"), tr("Cannot save %1:\n%2.").arg(fileName, file.errorString()));
    
    d.kilic's avatar
    d.kilic committed
                file.close();
                return false;
            }
            file.write(byteArray);
            file.close(); // also flushes the file
    
            statusBar()->showMessage(tr("Saved project to %1.").arg(fileName), 5000);
    
            SPDLOG_INFO("save project to {}", fileName);
    
    d.kilic's avatar
    d.kilic committed
    
            updateWindowTitle();
            return true;
        }
        else
    
    d.kilic's avatar
    d.kilic committed
            return false;
    
    d.kilic's avatar
    d.kilic committed
    }
    
    
    void Petrack::writeXmlElement(QXmlStreamWriter &xmlStream, QDomElement element)
    
    d.kilic's avatar
    d.kilic committed
    {
        xmlStream.writeStartElement(element.tagName());
    
    
        QVector<QString>       attribute_names;
    
    d.kilic's avatar
    d.kilic committed
        const QDomNamedNodeMap attributes = element.attributes();
    
        for(int i = 0; i < attributes.size(); ++i)
        {
    
    d.kilic's avatar
    d.kilic committed
            attribute_names.push_back(attributes.item(i).toAttr().name());
        }
    
    
        // TODO: check if sorting of elements fits our needs
    
    d.kilic's avatar
    d.kilic committed
        std::stable_sort(attribute_names.begin(), attribute_names.end()); // for a canonical XML
    
        // Wants this macro instead of range-based for loop
    
        foreach(QString name, attribute_names)
        {
    
    d.kilic's avatar
    d.kilic committed
            QDomAttr attr = element.attributeNode(name);
            xmlStream.writeAttribute(attr.name(), attr.value());
        }
    
        // order of child nodes is defined at creation
    
        if(element.hasChildNodes())
        {
    
    d.kilic's avatar
    d.kilic committed
            const QDomNodeList children = element.childNodes();
    
            for(int i = 0; i < children.size(); ++i)
            {
    
    d.kilic's avatar
    d.kilic committed
                writeXmlElement(xmlStream, children.at(i).toElement());
            }
        }
    
        xmlStream.writeEndElement();
    }
    
    
    /**
     * @brief Opens camera livestream from cam with camID
     * @param camID id of camera to use (defaults to 0)
     */
    void Petrack::openCameraLiveStream(int camID /* =-1*/)
    
    d.kilic's avatar
    d.kilic committed
    {
    
        if(camID == -1)
    
    d.kilic's avatar
    d.kilic committed
        {
            // if more than one camera connected show to choose
    
            // camID = selectedID;
    
            SPDLOG_INFO("No camera ID delivered: Set CameraID to 0 (default Camera)");
    
    d.kilic's avatar
    d.kilic committed
            camID = 0; // default
        }
    
        if(!mAnimation->openCameraStream(camID))
    
    d.kilic's avatar
    d.kilic committed
        {
    
            PCritical(this, tr("PeTrack"), tr("Cannot start Camera Livestream."));
    
    d.kilic's avatar
    d.kilic committed
            return;
        }
        mSeqFileName = "camera live stream";
    
        SPDLOG_INFO(
            "open {} ({} frames; {} fps; {} x {} pixel)",
            mSeqFileName,
            mAnimation->getNumFrames(),
            mAnimation->getFPS(),
            mAnimation->getSize().width(),
            mAnimation->getSize().height());
    
    d.kilic's avatar
    d.kilic committed
        updateSequence();
        updateWindowTitle();
        mPlayerWidget->setFPS(mAnimation->getFPS());
    
        mLogoItem->fadeOut();
    
    d.kilic's avatar
    d.kilic committed
        updateCoord();
    
    
        mPlayerWidget->play(PlayerState::FORWARD);
    
    d.kilic's avatar
    d.kilic committed
    }
    
    void Petrack::openSequence(QString fileName) // default fileName = ""
    {
    
        if(fileName.isEmpty())
    
            fileName = QFileDialog::getOpenFileName(
                this,
                tr("Open video or image sequence"),
                QFileInfo(mSeqFileName).path(),
                tr("All supported types (*.avi *.mpg *.mts *.m2t *.m2ts *.wmv *.mp4 *.mov *.mxf *.bmp *.dib *.jpeg *.jpg "
                   "*.jpe *.png *.pbm *.pgm *.ppm *.sr *.ras *.tiff *.tif *.exr *.jp2);;Video (*.avi *.mpg *.mts *.m2ts "
                   "*.m2t *.wmv *.mov *.mp4 *.mxf);;Images (*.bmp *.dib *.jpeg *.jpg *.jpe *.png *.pbm *.pgm *.ppm *.sr "
                   "*.ras *.tiff *.tif *.exr *.jp2);;Windows bitmaps (*.bmp *.dib);;JPEG (*.jpeg *.jpg *.jpe);;Portable "
                   "network graphics (*.png);;Portable image format (*.pbm *.pgm *.ppm);;Sun rasters (*.sr *.ras);;TIFF "
                   "(*.tiff *.tif);;OpenEXR HDR (*.exr);;JPEG 2000 (*.jp2);;All files (*.*)"));
    
        if(!fileName.isEmpty())
    
    d.kilic's avatar
    d.kilic committed
        {
    
            if(!mAnimation->openAnimation(fileName))
    
    d.kilic's avatar
    d.kilic committed
            {
    
                PCritical(this, tr("PeTrack"), tr("Cannot load %1.").arg(fileName));
    
    d.kilic's avatar
    d.kilic committed
                return;
            }
    
            mCameraGroupView->setEnabled(mAnimation->isStereoVideo());
            mCameraMenu->setEnabled(mAnimation->isStereoVideo());
    
    #ifdef STEREO
    
            if(mAnimation->isStereoVideo())
    
    d.kilic's avatar
    d.kilic committed
            {