Skip to content
Snippets Groups Projects
petrack.cpp 145 KiB
Newer Older
    // bothersome
    connect(
        mLimitPlaybackSpeed,
        &QAction::triggered,
        mPlayerWidget,
        [&]() { mPlayerWidget->setPlayerSpeedLimited(!mPlayerWidget->getPlayerSpeedLimited()); });
    mFixPlaybackSpeed = new QAction(tr("&Fix playback speed"));
    mFixPlaybackSpeed->setCheckable(true);
    connect(mFixPlaybackSpeed, &QAction::toggled, mPlayerWidget, &Player::setPlayerSpeedFixed);
    mSetToRealtime = new QAction(tr("&Realtime"));
    connect(
        mSetToRealtime, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(1.0); });
    mSetTo2p00 = new QAction(tr("&x2"));
    connect(mSetTo2p00, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(2.0); });
    mSetTo1p75 = new QAction(tr("&x1.75"));
    connect(mSetTo1p75, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(1.75); });
    mSetTo1p50 = new QAction(tr("&x1.5"));
    connect(mSetTo1p50, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(1.5); });
    mSetTo1p25 = new QAction(tr("&x1.25"));
    connect(mSetTo1p25, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(1.25); });
    mSetTo0p75 = new QAction(tr("&x0.75"));
    connect(mSetTo0p75, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(0.75); });
    mSetTo0p50 = new QAction(tr("&x0.5"));
    connect(mSetTo0p50, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(0.5); });
    mSetTo0p25 = new QAction(tr("&x0.25"));
    connect(mSetTo0p25, &QAction::triggered, mPlayerWidget, [&]() { mPlayerWidget->setSpeedRelativeToRealtime(0.25); });
d.kilic's avatar
d.kilic committed

    mPlayerLooping = new QAction(tr("&Loop"));
    mPlayerLooping->setCheckable(true);
    connect(mPlayerLooping, &QAction::triggered, mPlayerWidget, &Player::setLooping);
d.kilic's avatar
d.kilic committed
    // -------------------------------------------------------------------------------------------------------


    mDelPastAct = new QAction(tr("&Past part of all trj."), this);
    connect(
        mDelPastAct,
        &QAction::triggered,
        this,
        [this]() { this->deleteTrackPointAll(PersonStorage::Direction::Previous); });
d.kilic's avatar
d.kilic committed

    mDelFutureAct = new QAction(tr("&Future part of all trj."), this);
    connect(
        mDelFutureAct,
        &QAction::triggered,
        this,
        [this]() { this->deleteTrackPointAll(PersonStorage::Direction::Following); });
d.kilic's avatar
d.kilic committed

    mDelAllRoiAct = new QAction(tr("&Trj. moving through ROI"), this);
    connect(mDelAllRoiAct, &QAction::triggered, this, &Petrack::deleteTrackPointROI);
d.kilic's avatar
d.kilic committed
    mDelPartRoiAct = new QAction(tr("Part of Trj. inside &ROI"), this);
    connect(mDelPartRoiAct, &QAction::triggered, this, &Petrack::deleteTrackPointInsideROI);
d.kilic's avatar
d.kilic committed

    // -------------------------------------------------------------------------------------------------------

    mCommandAct = new QAction(tr("&Command line options"), this);
    connect(mCommandAct, SIGNAL(triggered()), this, SLOT(commandLineOptions()));

    mKeyAct = new QAction(tr("&Key bindings"), this);
    connect(mKeyAct, SIGNAL(triggered()), this, SLOT(keyBindings()));

    mAboutAct = new QAction(tr("&About"), this);
    connect(mAboutAct, SIGNAL(triggered()), this, SLOT(about()));

    mOnlineHelpAct = new QAction(tr("Online &Help"), this);
    mOnlineHelpAct->setShortcut(tr("Ctrl+H"));
    connect(mOnlineHelpAct, SIGNAL(triggered()), this, SLOT(onlineHelp()));
}

/**
 * @brief Helper function building menues out of QActions
 * @see Petrack::createActions()
 */
d.kilic's avatar
d.kilic committed
void Petrack::createMenus()
{
    mFileMenu = new QMenu(tr("&File"), this);
    mFileMenu->addAction(mOpenPrAct);
    mFileMenu->addAction(mSaveAct);
    mFileMenu->addAction(mSavePrAct);
    mFileMenu->addSeparator();
    mFileMenu->addAction(mOpenSeqAct);
    mFileMenu->addAction(mOpenCameraAct);
    mFileMenu->addAction(mOpenMoCapAct);
    mFileMenu->addAction(mEditMoCapAct);
d.kilic's avatar
d.kilic committed
    mFileMenu->addAction(mSaveSeqVidAct);
    mFileMenu->addAction(mSaveSeqVidViewAct);
    mFileMenu->addAction(mSaveImageAct);
    mFileMenu->addAction(mSaveSeqImgAct);
    mFileMenu->addAction(mSaveViewAct);
    mFileMenu->addAction(mSaveSeqViewAct);
    mFileMenu->addAction(mPrintAct);
    mFileMenu->addSeparator();
    mFileMenu->addAction(mResetSettingsAct);
    mFileMenu->addAction(mAutosaveSettings);
d.kilic's avatar
d.kilic committed
    mFileMenu->addSeparator();
    mFileMenu->addAction(mExitAct);

    mViewMenu = new QMenu(tr("&View"), this);
    mViewMenu->addAction(mAntialiasAct);
    mViewMenu->addAction(mOpenGLAct);
    mViewMenu->addAction(mCropZoomViewAct);
    mCameraMenu = mViewMenu->addMenu(tr("&Camera"));
    mCameraMenu->addAction(mCameraLeftViewAct);
    mCameraMenu->addAction(mCameraRightViewAct);
    mViewMenu->addAction(mFixPlaybackSpeed);
    mViewMenu->addAction(mLimitPlaybackSpeed);
    mPlaybackSpeedMenu = mViewMenu->addMenu(tr("&Playback speed"));
    mPlaybackSpeedMenu->addAction(mSetToRealtime);
    mPlaybackSpeedMenu->addAction(mSetTo2p00);
    mPlaybackSpeedMenu->addAction(mSetTo1p75);
    mPlaybackSpeedMenu->addAction(mSetTo1p50);
    mPlaybackSpeedMenu->addAction(mSetTo1p25);
    mPlaybackSpeedMenu->addAction(mSetTo0p75);
    mPlaybackSpeedMenu->addAction(mSetTo0p50);
    mPlaybackSpeedMenu->addAction(mSetTo0p25);
d.kilic's avatar
d.kilic committed
    mViewMenu->addAction(mPlayerLooping);
d.kilic's avatar
d.kilic committed
    mViewMenu->addSeparator();
    mViewMenu->addAction(mFitViewAct);
    mViewMenu->addAction(mFitROIAct);
    mViewMenu->addAction(mResetAct);
    mViewMenu->addSeparator();
    mViewMenu->addAction(mFontAct);
    mViewMenu->addSeparator();
    mViewMenu->addAction(mHideControlsAct);
    mViewMenu->addSeparator();
    mViewMenu->addAction(mShowLogWindowAct);
d.kilic's avatar
d.kilic committed

    mDeleteMenu = new QMenu(tr("&Delete"), this);
    mDeleteMenu->addAction(mDelPastAct);
    mDeleteMenu->addAction(mDelFutureAct);
    mDeleteMenu->addAction(mDelAllRoiAct);
    mDeleteMenu->addAction(mDelPartRoiAct);

    mHelpMenu = new QMenu(tr("&Help"), this);
    mHelpMenu->addAction(mCommandAct);
    mHelpMenu->addAction(mKeyAct);
    mHelpMenu->addAction(mAboutAct);
    mHelpMenu->addAction(mOnlineHelpAct);

    menuBar()->addMenu(mFileMenu);
    menuBar()->addMenu(mViewMenu);
    menuBar()->addMenu(mDeleteMenu);
    menuBar()->addMenu(mHelpMenu);

    mCameraMenu->setEnabled(false);
}

/**
 * @brief Helper function to create status bar at the bottom of the window
 */
d.kilic's avatar
d.kilic committed
void Petrack::createStatusBar()
{
    QFont f("Courier", 12, QFont::Bold); // Times Helvetica, Normal
d.kilic's avatar
d.kilic committed
    statusBar()->setMaximumHeight(28);
    statusBar()->showMessage(tr("Ready"));
    statusBar()->addPermanentWidget(mStatusLabelStereo = new QLabel(" "));
    statusBar()->addPermanentWidget(mStatusLabelTime = new QLabel(" "));
    statusBar()->addPermanentWidget(mStatusLabelFPS = new QLabel(" "));
    statusBar()->addPermanentWidget(mStatusPosRealHeight = new QDoubleSpinBox());
    connect(mStatusPosRealHeight, SIGNAL(valueChanged(double)), this, SLOT(setStatusPosReal()));

    statusBar()->addPermanentWidget(mStatusLabelPosReal = new QLabel(" "));
    statusBar()->addPermanentWidget(mStatusLabelPos = new QLabel(" "));
    statusBar()->addPermanentWidget(mStatusLabelColor = new QLabel(" "));
    mStatusLabelStereo->setFont(f);
    mStatusLabelStereo->setMinimumWidth(200);
    mStatusLabelTime->setFont(f);
    mStatusLabelTime->setMinimumWidth(200);
    mStatusLabelFPS->setFont(f);
    mStatusLabelFPS->setMinimumWidth(80);
d.kilic's avatar
d.kilic committed
    mStatusLabelFPS->setAutoFillBackground(true);
    mStatusLabelFPS->setToolTip("Click to adapt play rate to fps rate");
    mStatusPosRealHeight->setRange(-999.9, 9999.9); // in cm
    mStatusPosRealHeight->setDecimals(1);
    mStatusPosRealHeight->setFont(f);
    mStatusLabelPosReal->setFont(f);
    mStatusLabelPosReal->setMinimumWidth(340);
    mStatusLabelPos->setFont(f);
    mStatusLabelPos->setMinimumWidth(100);
    mStatusLabelColor->setFont(f);
    mStatusLabelColor->setMinimumWidth(90);
    mStatusLabelColor->setAutoFillBackground(true);
}

void Petrack::resetUI()
{
d.kilic's avatar
d.kilic committed
    ///
    ///  Reset all UI elements to default settings
    ///  Noetig damit alle UI Elemente, welche in der neu geladenen Projekt-Datei z.B. noch nicht vorhanden sind, auf
    ///  sinnvolle Werte gesetzt werden. Anderenfalls kommt es evtl. beim nacheinander laden verschiedener Projekte zu
    ///  einem Programmabsturz
d.kilic's avatar
d.kilic committed
    ///
    return;
}

void Petrack::setStatusStereo(float x, float y, float z)
{
    if(mStatusLabelStereo)
d.kilic's avatar
d.kilic committed
    {
d.kilic's avatar
d.kilic committed
            mStatusLabelStereo->setText(QString("x= novalue  y= novalue  z= novalue  "));
d.kilic's avatar
d.kilic committed
        else
            mStatusLabelStereo->setText(
                QString("x=%1cm  y=%2cm  z=%3cm  ").arg(x, 6, 'f', 1).arg(y, 6, 'f', 1).arg(z, 6, 'f', 1));
d.kilic's avatar
d.kilic committed
    }
}

void Petrack::setStatusTime()
{
    if(mStatusLabelTime)
d.kilic's avatar
d.kilic committed
        mStatusLabelTime->setText(mAnimation->getTimeString());
d.kilic's avatar
d.kilic committed
}

void Petrack::setStatusFPS()
{
    if(mStatusLabelFPS)
d.kilic's avatar
d.kilic committed
    {
        mStatusLabelFPS->setText(QString("%1fps  ").arg(mShowFPS, 5, 'f', 1));

        QPalette pal = mStatusLabelFPS->palette(); // static moeglich?
d.kilic's avatar
d.kilic committed

        double diff    = mShowFPS - mAnimation->getFPS();
        int    opacity = mPlayerWidget->getPlayerSpeedLimited() ? 128 : 20;
d.kilic's avatar
d.kilic committed

        if(diff < -6) // very slow ==> red
            color.setRgb(200, 0, 0, opacity);
        else if(diff < -2) // better ==> yellow
            color.setRgb(200, 200, 0, opacity);
        else if(diff > -2) // nearly ok ==> green
            color.setRgb(0, 200, 0, opacity);
d.kilic's avatar
d.kilic committed

        pal.setColor(QPalette::Window, color);

        mStatusLabelFPS->setPalette(pal);
    }
}
void Petrack::setShowFPS(double fps)
{
    if((fps == 0.) || (mShowFPS == 0))
d.kilic's avatar
d.kilic committed
        mShowFPS = fps;
d.kilic's avatar
d.kilic committed
    else
        mShowFPS = mShowFPS * .9 + fps * .1; // glaetten durch Hinzunahme des alten Wertes
d.kilic's avatar
d.kilic committed
    setStatusFPS();
}

/**
 * @brief Updates the FPS shown to the User
 *
 * This method calculates the FPS by remembering how long
 * it has been since it was called last time. If skipped is
 * true, it doesn't directly update the FPS since 2
 * skipped frames have essentially a time delay of 0 between
 * them, which would make calculations wonky.
 *
 * @param skipped True, if this is a skipped frame; default false
 */
void Petrack::updateShowFPS(bool skipped)
{
    static QElapsedTimer lastTime;
    static int           skippedFrames = 0;
        skippedFrames++;
        return;
    }
d.kilic's avatar
d.kilic committed

    if(mPlayerWidget->getPaused())
d.kilic's avatar
d.kilic committed
    {
        setShowFPS(0.);
d.kilic's avatar
d.kilic committed
    }
    else
    {
        if(lastTime.isValid())
            if(lastTime.elapsed() > 0)
                int numFrames = skippedFrames > 0 ? skippedFrames + 1 : 1;
                setShowFPS(numFrames * 1000. / lastTime.elapsed());
                skippedFrames = 0;
d.kilic's avatar
d.kilic committed
    }
}

// ohne neue positionsangabe, sinnvoll, wenn berechnungsweise sich in getPosReal geaendert hat
// gebraucht in control.cpp
void Petrack::setStatusPosReal() // pos in cm
{
    if(mImageItem)
        setStatusPosReal(mWorldImageCorrespondence->getPosReal(mMousePosOnImage, getStatusPosRealHeight()));
d.kilic's avatar
d.kilic committed
}
d.kilic's avatar
d.kilic committed
void Petrack::setStatusPosReal(const QPointF &pos) // pos in cm
{
    if(mStatusLabelPosReal)
d.kilic's avatar
d.kilic committed
    {
        QChar   deg(0xB0);
        QString labelText = QString(" cm from ground:%1cm,%2cm,%3")
                                .arg(pos.x(), 6, 'f', 1)
                                .arg(pos.y(), 6, 'f', 1)
                                .arg(
                                    mWorldImageCorrespondence->getAngleToGround(
                                        mMousePosOnImage.x(), mMousePosOnImage.y(), getStatusPosRealHeight()),
                                    5,
                                    'f',
                                    1);
d.kilic's avatar
d.kilic committed
        labelText.append(deg);
        mStatusLabelPosReal->setText(labelText);
    }
}
d.kilic's avatar
d.kilic committed
void Petrack::setStatusPos(const QPoint &pos) // pos in pixel
{
    mStatusLabelPos->setText(QString("%1x%2").arg(pos.x(), 4).arg(pos.y(), 4));
}
d.kilic's avatar
d.kilic committed
void Petrack::setStatusColor(const QRgb &col)
{
    QString s("#%1%2%3"); // static moeglich?
    s = s.arg(qRed(col), 2, 16, QChar('0')).arg(qGreen(col), 2, 16, QChar('0')).arg(qBlue(col), 2, 16, QChar('0'));
    if((qRed(col) + qGreen(col) + qBlue(col)) / 3 < 128)
d.kilic's avatar
d.kilic committed
        mStatusLabelColor->setText(QString("<font color=\"#ffffff\">&nbsp;%1</font>").arg(s));
d.kilic's avatar
d.kilic committed
    else
d.kilic's avatar
d.kilic committed
        mStatusLabelColor->setText(QString("<font color=\"#000000\">&nbsp;%1</font>").arg(s));
d.kilic's avatar
d.kilic committed

    QPalette pal = mStatusLabelColor->palette(); // static moeglich?
    QColor   color(qRed(col), qGreen(col), qBlue(col));
d.kilic's avatar
d.kilic committed

    pal.setColor(QPalette::Window, color);
    mStatusLabelColor->setPalette(pal);

    mControlWidget->getColorPlot()->setCursor(color);
    mControlWidget->getColorPlot()->replot();
}
d.kilic's avatar
d.kilic committed
void Petrack::setStatusColor()
{
    QPointF pos = getMousePosOnImage();
    if(pos.x() >= 0 && pos.x() < mImage->width() && pos.y() > 0 && pos.y() < mImage->height())
d.kilic's avatar
d.kilic committed
    {
        setStatusColor(mImage->pixel(pos.toPoint()));
d.kilic's avatar
d.kilic committed
    }
}

double Petrack::getStatusPosRealHeight()
{
    if(mStatusPosRealHeight)
d.kilic's avatar
d.kilic committed
        return mStatusPosRealHeight->value();
d.kilic's avatar
d.kilic committed
    else
d.kilic's avatar
d.kilic committed
        return 0.;
d.kilic's avatar
d.kilic committed
}

/**
 * @brief Reads (and applies) settings form platform-independent persistent storage
 *
 * The saved size and position of the application window get reconstructed. As well as
 * the options about antialiasing and the usage of OpenGL.
 * mSeqFileName and mProFileName get set, so the "Open Project" and "Open Sequence"
 * dialogues start at correct folder. The old project/sequence is NOT actually loaded.
 */
d.kilic's avatar
d.kilic committed
void Petrack::readSettings()
{
    const QSettings settings("Forschungszentrum Juelich GmbH", "PeTrack by Maik Boltes, Daniel Salden");
d.kilic's avatar
d.kilic committed
    mAntialiasAct->setChecked(settings.value("antialias", false).toBool());
    mOpenGLAct->setChecked(settings.value("opengl", false).toBool());
    mSeqFileName = settings.value("seqFileName", QDir::currentPath()).toString();
    setProFileName(settings.value("proFilePath", QDir::currentPath()).toString());
    // nicht ganz sauber, da so immer schon zu anfang in calib file list etwas drin steht und somit auto ausgefuehrt
    // werden kann wird aber beim ersten openCalib... ueberschrieben
d.kilic's avatar
d.kilic committed
    mAutoCalib.addCalibFile(settings.value("calibFile", QDir::currentPath()).toString());
    auto geometry = settings.value("geometry").toByteArray();
    restoreGeometry(geometry);
d.kilic's avatar
d.kilic committed
    antialias();
    opengl();
    mSplitter->restoreState(settings.value("controlSplitterSizes").toByteArray());
    mAutosave.setPetSaveInterval(settings.value("petSaveInterval", 120).toDouble());
    mAutosave.setChangesTillAutosave(settings.value("changesTillAutosave", 10).toInt());
d.kilic's avatar
d.kilic committed
}

/**
 * @brief Writes persistent setting.
 * @see Petrack::readSettings
 */
d.kilic's avatar
d.kilic committed
void Petrack::writeSettings()
{
    QSettings settings("Forschungszentrum Juelich GmbH", "PeTrack by Maik Boltes, Daniel Salden");
    settings.setValue("geometry", saveGeometry());
d.kilic's avatar
d.kilic committed
    settings.setValue("antialias", mAntialiasAct->isChecked());
    settings.setValue("opengl", mOpenGLAct->isChecked());
    settings.setValue("seqFileName", mSeqFileName);
    settings.setValue("proFilePath", QFileInfo(mProFileName).path()); // nur path, damit bei saveCurrentProject
    if(!mAutoCalib.isEmptyCalibFiles())                               //! mCalibFiles.isEmpty()
d.kilic's avatar
d.kilic committed
        settings.setValue("calibFile", mAutoCalib.getCalibFile(0));
    settings.setValue("controlSplitterSizes", mSplitter->saveState());
    settings.setValue("petSaveInterval", mAutosave.getPetSaveInterval());
    settings.setValue("changesTillAutosave", mAutosave.getChangesTillAutosave());
d.kilic's avatar
d.kilic committed
}

bool Petrack::maybeSave()
{
    int ret = PWarning(
        this,
        tr("PeTrack"),
        tr("Do you want to save "
           "the current project?\n"
           "Be sure to save trajectories, background "
           "and 3D calibration point separately!"),
        PMessageBox::StandardButton::Yes | PMessageBox::StandardButton::No | PMessageBox::StandardButton::Cancel,
        PMessageBox::StandardButton::Yes);

    if(ret == PMessageBox::StandardButton::Yes)
d.kilic's avatar
d.kilic committed
    {
        if(saveSameProject())
d.kilic's avatar
d.kilic committed
            return true;
d.kilic's avatar
d.kilic committed
        else
d.kilic's avatar
d.kilic committed
            return false;
d.kilic's avatar
d.kilic committed
    }
    else if(ret == PMessageBox::StandardButton::Cancel)
d.kilic's avatar
d.kilic committed
        return false;
d.kilic's avatar
d.kilic committed
    else
d.kilic's avatar
d.kilic committed
        return true;
d.kilic's avatar
d.kilic committed
}

void Petrack::closeEvent(QCloseEvent *event)
{
    if(maybeSave())
d.kilic's avatar
d.kilic committed
    {
        writeSettings();
        mAutosave.deleteAutosave();
d.kilic's avatar
d.kilic committed
        mPlayerWidget->pause();
d.kilic's avatar
d.kilic committed
        event->accept();
    }
    else
d.kilic's avatar
d.kilic committed
        event->ignore();
d.kilic's avatar
d.kilic committed
}

/**
 * @brief Sets the mMousePosOnImage member variable and displayed pixel/real coordinates
 *
 * Gets called from ImageItem::hoverMoveEvent() and enables an easy access
 * to the mouse position.
 * @param pos Position of mouse cursor in image pixel coordinates
 */
d.kilic's avatar
d.kilic committed
void Petrack::setMousePosOnImage(QPointF pos)
{
d.kilic's avatar
d.kilic committed
    {
        mMousePosOnImage = pos;
        setStatusPosReal(mWorldImageCorrespondence->getPosReal(pos, getStatusPosRealHeight()));
d.kilic's avatar
d.kilic committed

        // pixel coordinate
        QPoint pos1((int) (pos.x()) + 1, (int) (pos.y()) + 1);
        setStatusPos(pos1);
d.kilic's avatar
d.kilic committed
        // pixel color
        setStatusColor();
d.kilic's avatar
d.kilic committed
    }
}

void Petrack::keyPressEvent(QKeyEvent *event)
{
    switch(event->key())
    {
        case Qt::Key_Left:
            mPlayerWidget->frameBackward();
            break;
        case Qt::Key_Right:
            mPlayerWidget->frameForward();
            break;
        case Qt::Key_Down:
            mViewWidget->zoomOut(1);
            break;
        case Qt::Key_Up:
            mViewWidget->zoomIn(1);
            break;
        case Qt::Key_Space:
            // space wird von buttons, wenn focus drauf ist als Aktivierung vorher abgegriffen und nicht durchgereicht
            mPlayerWidget->togglePlayPause();
            break;
        case Qt::Key_D:
            break;
        default:;
d.kilic's avatar
d.kilic committed
    }
}

void Petrack::mousePressEvent(QMouseEvent *event)
{
    // mouse click in fps status label ?
    if(event->pos().x() >= mStatusLabelFPS->pos().x() &&
       event->pos().x() <= mStatusLabelFPS->pos().x() + mStatusLabelFPS->width())
d.kilic's avatar
d.kilic committed
    {
        mPlayerWidget->togglePlayerSpeedLimited();
d.kilic's avatar
d.kilic committed
        setStatusFPS();
    }
}

const QString &Petrack::getLastTrackerExport() const
{
    return mLastTrackerExport;
}

void Petrack::setLastTrackerExport(const QString &newLastTrackerExport)
{
    mLastTrackerExport = newLastTrackerExport;
}

/// update control widget, if image size changed (especially because of changing border)
void Petrack::updateControlImage(cv::Mat &img)
d.kilic's avatar
d.kilic committed
{
    // auch moeglich hoehe und breite von bild stat border veraenderungen zu checken
    static int lastBorderSize = -1;

    if(isLoading())
d.kilic's avatar
d.kilic committed
        lastBorderSize = -1;
    int diffBorderSize = 0;
    if(lastBorderSize != -1)
d.kilic's avatar
d.kilic committed
    {
        diffBorderSize = getImageBorderSize() - lastBorderSize;
d.kilic's avatar
d.kilic committed
    }
    lastBorderSize = getImageBorderSize();
d.kilic's avatar
d.kilic committed

    const int imgWidth  = img.cols;
    const int imgHeight = img.rows;

    // no direct invocation to have correct order of invocations
    // (direct invocation gets executed immediately, i.e. before queued connection)
    QMetaObject::invokeMethod(
        mControlWidget,
        "imageSizeChanged",
        Qt::ConnectionType::QueuedConnection,
        Q_ARG(int, imgWidth),
        Q_ARG(int, imgHeight),
        Q_ARG(int, diffBorderSize));
d.kilic's avatar
d.kilic committed
}

void Petrack::importTracker(QString dest) // default = ""
d.kilic's avatar
d.kilic committed
{
    static QString lastFile;

    if(lastFile == "")
d.kilic's avatar
d.kilic committed
        lastFile = mTrcFileName;
d.kilic's avatar
d.kilic committed

    // if no destination file or folder is given
    if(dest.isEmpty())
d.kilic's avatar
d.kilic committed
    {
        dest = QFileDialog::getOpenFileName(
            this,
            tr("Select file for importing tracking pathes"),
            lastFile,
            tr("PeTrack tracker (*.trc *.txt);;All files (*.*)"));
d.kilic's avatar
d.kilic committed
    }

    if(!dest.isEmpty())
d.kilic's avatar
d.kilic committed
    {
        if(dest.endsWith(".trc", Qt::CaseInsensitive))
d.kilic's avatar
d.kilic committed
        {
            QFile file(dest);
d.kilic's avatar
d.kilic committed

            if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
d.kilic's avatar
d.kilic committed
            {
                // errorstring ist je nach Betriebssystem in einer anderen Sprache!!!!
                PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2").arg(dest).arg(file.errorString()));
d.kilic's avatar
d.kilic committed
                return;
            }

            setTrackChanged(true); // flag changes of track parameters
d.kilic's avatar
d.kilic committed
            mTracker->reset();

            QTextStream in(&file);
            QString     comment;
d.kilic's avatar
d.kilic committed

            bool    ok; // shows if int stands in first line - that was in the first version of trc file
d.kilic's avatar
d.kilic committed
            QString firstLine = in.readLine();
            sz                = firstLine.toInt(&ok);
            if(!ok)
d.kilic's avatar
d.kilic committed
            {
                if(firstLine.contains("version 4", Qt::CaseInsensitive))
                else if(firstLine.contains("version 3", Qt::CaseInsensitive))
d.kilic's avatar
d.kilic committed
                {
                    trcVersion = 3;
                else if(firstLine.contains("version 2", Qt::CaseInsensitive))
d.kilic's avatar
d.kilic committed
                {
                    trcVersion = 2;
d.kilic's avatar
d.kilic committed
                {
                    SPDLOG_ERROR("wrong header while reading TRC file.");
                    QMessageBox::critical(
                        this,
                        tr("PeTrack"),
                        tr("Could not import tracker:\nNot supported trc version in file: %1.").arg(dest));
d.kilic's avatar
d.kilic committed
                }
                in >> sz;
            }
            else
d.kilic's avatar
d.kilic committed
                trcVersion = 1;
d.kilic's avatar
d.kilic committed

            if((sz > 0) && (mPersonStorage.nbPersons() != 0))
                SPDLOG_WARN("overlapping trajectories will be joined not until tracking adds new TrackPoints.");
            for(i = 0; i < sz; ++i)
d.kilic's avatar
d.kilic committed
            {
                TrackPerson tp = fromTrc(in);
                mPersonStorage.addPerson(tp);
d.kilic's avatar
d.kilic committed
            }

            mControlWidget->setTrackNumberAll(QString("%1").arg(mPersonStorage.nbPersons()));
            mControlWidget->setTrackShowOnlyNr(static_cast<int>(MAX(mPersonStorage.nbPersons(), 1)));
            mControlWidget->setTrackNumberVisible(
                QString("%1").arg(mPersonStorage.visible(mAnimation->getCurrentFrameNum())));
            mControlWidget->replotColorplot();
d.kilic's avatar
d.kilic committed
            file.close();
            SPDLOG_INFO("import {} ({} person(s), file version {})", dest, sz, trcVersion);
            mTrcFileName =
                dest; // fuer Project-File, dann koennte track path direkt mitgeladen werden, wenn er noch da ist
        }
        else if(dest.endsWith(".txt", Qt::CaseInsensitive)) // 3D Koordinaten als Tracking-Daten importieren
                                                            // Zeilenformat: Personennr, Framenr, x, y, z
d.kilic's avatar
d.kilic committed
        {
            PWarning(
                this,
                tr("PeTrack"),
                tr("Are you sure you want to import 3D data from TXT-File? You have to make sure that the coordinate "
                   "system now is exactly at the same position and orientation than at export time!"));
d.kilic's avatar
d.kilic committed

            QFile file(dest);

            int numberImportedPersons = 0;
d.kilic's avatar
d.kilic committed

            if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
d.kilic's avatar
d.kilic committed
            {
                // errorstring ist je nach Betriebssystem in einer anderen Sprache!!!!
                PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2").arg(dest).arg(file.errorString()));
d.kilic's avatar
d.kilic committed
                return;
            }

            setTrackChanged(true); // flag changes of track parameters
d.kilic's avatar
d.kilic committed
            mTracker->reset();

            QTextStream in(&file);
            TrackPoint  tPoint;
d.kilic's avatar
d.kilic committed

            QString line;
            bool    unitFound            = false;
            double  conversionFactorToCM = 1.0;
d.kilic's avatar
d.kilic committed

            std::unordered_map<int, std::map<int, Vec3F>> personData;
            QString                                       headerline;
d.kilic's avatar
d.kilic committed

            while(in.readLineInto(&line))
            {
                if(line.startsWith("#", Qt::CaseInsensitive))
d.kilic's avatar
d.kilic committed
                {
                    headerline = line;
                    continue;
                }

                if((!unitFound) && (!headerline.contains("cm")))
d.kilic's avatar
d.kilic committed
                {
                    conversionFactorToCM = 100.0;
                    unitFound            = true;
                    PWarning(
                        this,
                        tr("PeTrack"),
                        tr("PeTrack will interpret position data as unit [m]. No header with [cm] found."));
d.kilic's avatar
d.kilic committed
                }

                // Read data from line with format: persNr frameNr x y z
                int   personNr = -1, frameNr = -1;
                float x, y, z;
d.kilic's avatar
d.kilic committed

                QTextStream stream(&line);
                stream >> personNr >> frameNr >> x >> y >> z;
d.kilic's avatar
d.kilic committed

                // convert data to cm
                x = x * conversionFactorToCM;
                y = y * conversionFactorToCM;
                z = z * conversionFactorToCM;
d.kilic's avatar
d.kilic committed

                if(personData[personNr].find(frameNr) == personData[personNr].end())
d.kilic's avatar
d.kilic committed
                {
                    personData[personNr][frameNr] = Vec3F(x, y, z);
d.kilic's avatar
d.kilic committed
                }
                else
                {
                    PCritical(
                        this,
                        "Error importing txt file",
                        tr("Could not import the data from the provided txt file, as the data for person %1 in frame "
                           "%1 is twice in the txt-file.")
                            .arg(personNr)
                            .arg(frameNr));
                    return;
d.kilic's avatar
d.kilic committed
                }
d.kilic's avatar
d.kilic committed

            for(auto &[persNr, frameData] : personData)
            {
                std::deque<TrackPoint> pixelPoints;
                for(auto &[frameNr, realWorldCoordinates] : frameData)
d.kilic's avatar
d.kilic committed
                {
d.kilic's avatar
d.kilic committed

                    if(mControlWidget->getCalibCoordDimension() == 0)
                    {
                        // compute image point from 3d calibration
                        p2d = mExtrCalibration.getImagePoint(
                            cv::Point3f(realWorldCoordinates.x(), realWorldCoordinates.y(), realWorldCoordinates.z()));
                    }
                    else
                    {
                        // compute image point from 2d calibration
                        QPointF pos = mWorldImageCorrespondence->getPosImage(
                            QPointF(realWorldCoordinates.x(), realWorldCoordinates.y()), realWorldCoordinates.z());
                        p2d.x = pos.x();
                        p2d.y = pos.y();
                    }

                    TrackPoint trackPoint(Vec2F(p2d.x, p2d.y), 100);
                    trackPoint.setSp(
                        realWorldCoordinates.x(),
                        realWorldCoordinates.y(),
                        -mControlWidget->getExtrinsicParameters().trans3 -
                            realWorldCoordinates.z()); // distance to camera as with stereo cameras
                    pixelPoints.push_back(trackPoint);
d.kilic's avatar
d.kilic committed
                }

                TrackPerson trackPerson(persNr, frameData.begin()->first, pixelPoints.front());
                trackPerson.setHeight(frameData.begin()->second.z());
                pixelPoints.pop_front();

                for(const auto &trackPoint : pixelPoints)
d.kilic's avatar
d.kilic committed
                {
                    trackPerson.append(trackPoint);
d.kilic's avatar
d.kilic committed
                }
                mPersonStorage.addPerson(trackPerson);
                numberImportedPersons++;
d.kilic's avatar
d.kilic committed
            }

            mControlWidget->setTrackNumberAll(QString("%1").arg(mPersonStorage.nbPersons()));
            mControlWidget->setTrackShowOnlyNr(static_cast<int>(MAX(mPersonStorage.nbPersons(), 1)));
            mControlWidget->setTrackNumberVisible(
                QString("%1").arg(mPersonStorage.visible(mAnimation->getCurrentFrameNum())));
            mControlWidget->replotColorplot();
d.kilic's avatar
d.kilic committed
            file.close();
            SPDLOG_INFO("import {} ({} person(s))", dest, numberImportedPersons);
            mTrcFileName = dest;
d.kilic's avatar
d.kilic committed
        }
        else
        {
            PCritical(this, tr("PeTrack"), tr("Cannot load %1 maybe because of wrong file extension.").arg(dest));
d.kilic's avatar
d.kilic committed
        }
        lastFile = dest;
    }
}

int Petrack::calculateRealTracker()
{
    bool autoCorrectOnlyExport = (mReco.getRecoMethod() == reco::RecognitionMethod::MultiColor) && // multicolor
                                 mMultiColorMarkerWidget->autoCorrect->isChecked() &&
                                 mMultiColorMarkerWidget->autoCorrectOnlyExport->isChecked();
    int anz = mTrackerReal->calculate(
        mControlWidget->getColorPlot(),
        mMissingFrames,
        getImageBorderSize(),
        mControlWidget->getAnaMissingFrames(),
        mStereoWidget->stereoUseForExport->isChecked(),
        mControlWidget->getTrackAlternateHeight(),
        mControlWidget->getCameraAltitude(),
        mStereoWidget->stereoUseCalibrationCenter->isChecked(),
        mControlWidget->isExportElimTpChecked(),
        mControlWidget->isExportElimTrjChecked(),
        mControlWidget->isExportSmoothChecked(),
        mControlWidget->isExportViewDirChecked(),
        mControlWidget->isExportAngleOfViewChecked(),
        mControlWidget->isExportMarkerIDChecked(),
        autoCorrectOnlyExport);
d.kilic's avatar
d.kilic committed

    mTrackerReal->calcMinMax();
    return anz;
}

void Petrack::exportTracker(QString dest) // default = ""
d.kilic's avatar
d.kilic committed
{
d.kilic's avatar
d.kilic committed
    {
d.kilic's avatar
d.kilic committed
        // if no destination file or folder is given
        if(dest.isEmpty())
d.kilic's avatar
d.kilic committed
        {
            QFileDialog fileDialog(
                this,
                tr("Select file for exporting tracking paths"),
                tr("Tracker (*.*);;Petrack tracker (*.trc);;Text (*.txt);;Text for gnuplot(*.dat);;XML Travisto "
                   "(*.trav);;All supported types (*.txt *.trc *.dat *.trav *.);;All files (*.*)"));
d.kilic's avatar
d.kilic committed
            fileDialog.setAcceptMode(QFileDialog::AcceptSave);
            fileDialog.setFileMode(QFileDialog::AnyFile);
            fileDialog.setDefaultSuffix("");

            if(fileDialog.exec())
            {
d.kilic's avatar
d.kilic committed
                dest = fileDialog.selectedFiles().at(0);
            }
d.kilic's avatar
d.kilic committed

        QList<int> pers, frame;
        bool autoCorrectOnlyExport = (mReco.getRecoMethod() == reco::RecognitionMethod::MultiColor) && // multicolor
                                     mMultiColorMarkerWidget->autoCorrect->isChecked() &&
                                     mMultiColorMarkerWidget->autoCorrectOnlyExport->isChecked();
d.kilic's avatar
d.kilic committed

        if(dest.endsWith(".trc", Qt::CaseInsensitive))
d.kilic's avatar
d.kilic committed

            if(!file.open() /*!file.open(QIODevice::WriteOnly | QIODevice::Text)*/)
            {
                PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2.").arg(dest).arg(file.errorString()));
                return;
            }
            QProgressDialog progress(
                "Export TRC-File", nullptr, 0, static_cast<int>(mPersonStorage.nbPersons() + 1), this->window());
            progress.setWindowTitle("Export .trc-File");
            progress.setWindowModality(Qt::WindowModal);
            progress.setVisible(true);
            progress.setValue(0);
            progress.setLabelText(QString("Export tracking data ..."));
d.kilic's avatar
d.kilic committed

d.kilic's avatar
d.kilic committed

d.kilic's avatar
d.kilic committed

            SPDLOG_INFO(
                "export tracking data to {} ({} person(s), file version {})",
                dest,
                mPersonStorage.nbPersons(),
                trcVersion);
            QTextStream out(&file);
d.kilic's avatar
d.kilic committed

            out << "version " << trcVersion << Qt::endl;
            out << mPersonStorage.nbPersons() << Qt::endl;
            const auto &persons = mPersonStorage.getPersons();
            for(size_t i = 0; i < persons.size(); ++i)
            {
                qApp->processEvents();
                progress.setLabelText(QString("Export person %1 of %2 ...").arg(i + 1).arg(mPersonStorage.nbPersons()));
                progress.setValue(static_cast<int>(i + 1));
                out << persons[i] << Qt::endl;
            }
            file.flush();
            file.close();

            progress.setLabelText(QString("Save file ..."));
            qApp->processEvents();
d.kilic's avatar
d.kilic committed

d.kilic's avatar
d.kilic committed

            if(!file.copy(dest))
            {
                PCritical(
                    this,
                    tr("PeTrack"),
                    tr("Could not export tracking data.\n"
                       "Please try again!"));
            }
            else
            {
                statusBar()->showMessage(tr("Saved tracking data to %1.").arg(dest), 5000);
            }
d.kilic's avatar
d.kilic committed

            progress.setValue(static_cast<int>(mPersonStorage.nbPersons() + 1));
d.kilic's avatar
d.kilic committed

            SPDLOG_INFO("finished.");
            mAutosave.resetTrackPersonCounter();
d.kilic's avatar
d.kilic committed

            mTrcFileName =
                dest; // fuer Project-File, dann koennte track path direkt mitgeladen werden, wenn er// noch da ist
        else if(dest.endsWith(".txt", Qt::CaseInsensitive))
d.kilic's avatar
d.kilic committed

            if(!file.open())
            {
                PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2.").arg(dest).arg(file.errorString()));
                return;
            }
d.kilic's avatar
d.kilic committed

            SPDLOG_INFO("export tracking data to {} ({} person(s))...", dest, mPersonStorage.nbPersons());
d.kilic's avatar
d.kilic committed

            // recalcHeight true, wenn personenhoehe ueber trackpoints neu berechnet werden soll (z.b. um
            // waehrend play mehrfachberuecksichtigung von punkten auszuschliessen, aenderungen in altitude neu
            // in berechnung einfliessen zu lassen)
            if(mControlWidget->isTrackRecalcHeightChecked())
            {
                if(mControlWidget->getCalibCoordDimension() == 0) // 3D
d.kilic's avatar
d.kilic committed
                {
                    ; // Nothing to be done because z already the right height
                }
                else // 2D
                {
                    mPersonStorage.recalcHeight(mControlWidget->getCameraAltitude());
d.kilic's avatar
d.kilic committed
                }
d.kilic's avatar
d.kilic committed

            mTrackerReal->calculate(
                this,
                mTracker,
                mControlWidget->getColorPlot(),
                mMissingFrames,
                getImageBorderSize(),
                mControlWidget->isTrackMissingFramesChecked(),
                mStereoWidget->stereoUseForExport->isChecked(),
                mControlWidget->getTrackAlternateHeight(),
                mControlWidget->getCameraAltitude(),
                mStereoWidget->stereoUseCalibrationCenter->isChecked(),
                mControlWidget->isExportElimTpChecked(),
                mControlWidget->isExportElimTrjChecked(),
                mControlWidget->isExportSmoothChecked(),
                mControlWidget->isExportViewDirChecked(),
                mControlWidget->isExportAngleOfViewChecked(),
                mControlWidget->isExportMarkerIDChecked(),
                autoCorrectOnlyExport);
d.kilic's avatar
d.kilic committed

d.kilic's avatar
d.kilic committed

            out << "# PeTrack project: " << QFileInfo(getProFileName()).fileName() << Qt::endl;