Select Git revision
petrack.cpp
Schrödter, Tobias authored
petrack.cpp 139.70 KiB
/*
* 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/>.
*/
#include <QSignalMapper>
#include <QtOpenGL>
#include <QtWidgets>
// 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"
#include "colorMarkerItem.h"
#include "colorMarkerWidget.h"
#include "colorRangeWidget.h"
#include "control.h"
#include "gridItem.h"
#include "helper.h"
#include "imageItem.h"
#include "logger.h"
#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 "roiItem.h"
#include "stereoItem.h"
#include "stereoWidget.h"
#include "tracker.h"
#include "trackerItem.h"
#include "trackerReal.h"
#include "view.h"
#include <QtPrintSupport/QPrintDialog>
#include <QtPrintSupport/QPrinter>
#include <cmath>
#include <ctime>
#include <iomanip>
#include <opencv2/opencv.hpp>
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"))
{
QIcon icon;
icon.addFile(":/icon"); // about
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;
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
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
mControlWidget = new Control(*this, *mScene, mReco, *mTrackingRoiItem, *mRecognitionRoiItem, mMissingFrames);
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);
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
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
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();
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);
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);
mTrackerItem->setZValue(5); // groesser heisst weiter oben
mControlWidget->getColorPlot()->setPersonStorage(&mPersonStorage);
#ifdef QWT
mControlWidget->getAnalysePlot()->setTrackerReal(mTrackerReal);
#endif
//---------------------------
mStereoItem = new StereoItem(this);
mStereoItem->setZValue(2); // groesser heisst weiter oben
mStereoItem->setVisible(false);
//---------------------------
mColorMarkerItem = new ColorMarkerItem(this);
mColorMarkerItem->setZValue(2); // groesser heisst weiter oben
mColorMarkerItem->setVisible(false);
//---------------------------
mCodeMarkerItem = new CodeMarkerItem(this, mReco.getCodeMarkerOptions());
mCodeMarkerItem->setZValue(2); // groesser heisst weiter oben
mCodeMarkerItem->setVisible(false);
//---------------------------
mMultiColorMarkerItem = new MultiColorMarkerItem(this);
mMultiColorMarkerItem->setZValue(2); // groesser heisst weiter oben
mMultiColorMarkerItem->setVisible(false);
//---------------------------
mBackgroundItem = new BackgroundItem(this);
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
/// 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);
//---------------------------
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);
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
readSettings();
saveXml(mDefaultSettings); // noch nicht noetig, da eh noch nicht fkt
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
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())
{
event->acceptProposedAction();
}
}
/**
* @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
*/
void Petrack::dropEvent(QDropEvent *event)
{
if(event->mimeData()->hasUrls())
{
if(event->mimeData()->urls().first().toLocalFile().right(4) == ".pet")
{
openProject(event->mimeData()->urls().first().toLocalFile());
}
else if(event->mimeData()->urls().first().toLocalFile().right(4) == ".trc")
{
importTracker(event->mimeData()->urls().first().toLocalFile());
}
else
{
openSequence(event->mimeData()->urls().first().toLocalFile());
}
event->acceptProposedAction();
}
}
void Petrack::updateSceneRect()
{
double iW = 0, iH = 0, bS = 0;
if(mImage)
{
iW = mImage->width();
iH = mImage->height();
bS = getImageBorderSize();
}
if(mControlWidget->getCalibCoordShow())
{
double scale = mControlWidget->getCalibCoordScale() / 10.;
double tX = mControlWidget->getCalibCoordTransX() / 10.;
double tY = mControlWidget->getCalibCoordTransY() / 10.;
// 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);
}
else
{
mScene->setSceneRect(-bS, -bS, iW, iH);
}
}
/**
* @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
*/
void Petrack::openXml(QDomDocument &doc, bool openSeq)
{
mMissingFrames.reset();
bool missingFramesExecuted = false;
std::vector<MissingFrame> missingFrames{};
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;
enum Camera cam = cameraUnset;
setLoading(true);
for(QDomElement elem = root.firstChildElement(); !elem.isNull(); elem = elem.nextSiblingElement())
{
if(elem.tagName() == "MAIN")
{
if(elem.hasAttribute("SRC"))
{
seq = elem.attribute("SRC");
QString tmpSeq = getExistingFile(seq, mProFileName);
if(tmpSeq != "")
{
seq = tmpSeq;
}
}
if(elem.hasAttribute("STATUS_HEIGHT"))
{
if(mStatusPosRealHeight) // null kann eigentlich nicht vorkommen, da im constructor von petrack erzeugt
// wird
{
mStatusPosRealHeight->setValue(elem.attribute("STATUS_HEIGHT").toDouble());
}
}
}
else if(elem.tagName() == "STEREO")
{
mStereoWidget->getXml(elem);
}
else if(elem.tagName() == "COLOR_MARKER")
{
mColorMarkerWidget->getXml(elem);
}
else if(elem.tagName() == "CODE_MARKER")
{
mCodeMarkerWidget->getXml(elem);
}
else if(elem.tagName() == "MULTI_COLOR_MARKER")
{
mMultiColorMarkerWidget->getXml(elem);
}
else if(elem.tagName() == "MOCAP")
{
mMoCapController.getXml(elem);
}
else if(elem.tagName() == "CONTROL")
{
mControlWidget->getXml(elem);
QDomElement tmpElem = (elem.firstChildElement("TRACKING")).firstChildElement("PATH");
if(tmpElem.hasAttribute("ONLY_PEOPLE_NR"))
{
onlyPeopleNr = tmpElem.attribute("ONLY_PEOPLE_NR").toInt();
}
if(tmpElem.hasAttribute("ONLY_PEOPLE_NR_LIST"))
{
onlyPeopleNrList = tmpElem.attribute("ONLY_PEOPLE_NR_LIST");
}
}
else if(elem.tagName() == "EXTR_CALIBRATION")
{
mExtrCalibration.getXml(elem);
}
else if(elem.tagName() == "PLAYER")
{
if(elem.hasAttribute("FRAME"))
{
frame = elem.attribute("FRAME").toInt();
}
if(elem.hasAttribute("FPS"))
{
fps = elem.attribute("FPS").toDouble();
}
if(elem.hasAttribute("SOURCE_FRAME_IN"))
{
sourceFrameIn = elem.attribute("SOURCE_FRAME_IN").toInt();
}
if(elem.hasAttribute("SOURCE_FRAME_OUT"))
{
sourceFrameOut = elem.attribute("SOURCE_FRAME_OUT").toInt();
}
if(elem.hasAttribute("PLAYER_SPEED_FIXED"))
{
mPlayerWidget->setPlayerSpeedLimited(elem.attribute("PLAYER_SPEED_FIXED").toInt());
}
}
else if(elem.tagName() == "VIEW")
{
if(elem.hasAttribute("ANTIALIAS"))
{
mAntialiasAct->setChecked(elem.attribute("ANTIALIAS").toInt() == Qt::Checked);
}
if(elem.hasAttribute("OPENGL"))
{
mOpenGLAct->setChecked(elem.attribute("OPENGL").toInt() == Qt::Checked);
}
if(elem.hasAttribute("SAVE_TRANSFORMED"))
{
mCropZoomViewAct->setChecked(elem.attribute("SAVE_TRANSFORMED") == Qt::Checked);
}
if(elem.hasAttribute("TRANSFORMATION"))
{
QString matStr = elem.attribute("TRANSFORMATION");
QTextStream in(&matStr);
in >> zoom >> rotate >> hScroll >> vScroll;
}
if(elem.hasAttribute("CAMERA"))
{
cam = (enum Camera) elem.attribute("CAMERA").toInt();
}
if(elem.hasAttribute("HIDE_CONTROLS"))
{
mHideControlsAct->setChecked(elem.attribute("HIDE_CONTROLS").toInt() == Qt::Checked);
}
}
else if(elem.tagName() == "AUTO_TRACK")
{
if(elem.hasAttribute("BACK_TRACK"))
{
mAutoBackTrack = elem.attribute("BACK_TRACK").toInt();
}
if(elem.hasAttribute("OPTIMZE_COLOR"))
{
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});
}
}
}
else
{
SPDLOG_ERROR("Unknown PETRACK tag {}", elem.tagName());
}
}
// 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!
mSeqFileName = seq;
if(openSeq && (seq != ""))
{
openSequence(seq); // wenn leer, dann kommt abfrage hoch, welche datei; abbrechen, wenn aktuelle gewuenscht
}
mMissingFrames.setExecuted(missingFramesExecuted);
mMissingFrames.setMissingFrames(missingFrames);
mViewWidget->setZoomLevel(zoom);
mViewWidget->setRotateLevel(rotate);
mView->horizontalScrollBar()->setValue(hScroll);
mView->verticalScrollBar()->setValue(vScroll);
bool loaded = false;
if(!mBackgroundFilter.getFilename().isEmpty())
{
if(!(loaded = mBackgroundFilter.load(mBackgroundFilter.getFilename())))
{
SPDLOG_ERROR("Error: loading background file {}!", mBackgroundFilter.getFilename());
}
}
mPlayerWidget->setFrameInNum(sourceFrameIn == -1 ? mAnimation->getSourceInFrameNum() : sourceFrameIn);
mPlayerWidget->setFrameOutNum(sourceFrameOut == -1 ? mAnimation->getSourceOutFrameNum() : sourceFrameOut);
mPlayerWidget->update();
if(frame != -1)
{
if(mControlWidget->isFilterBgChecked() &&
!loaded) // mit dem anfangs geladenen bild wurde bereits faelschlicherweise bg bestimmt
{
mBackgroundFilter.reset(); // erst nach dem springen zu einem frame background bestimmen
}
mPlayerWidget->skipToFrame(frame); // hier wird updateImage ausgefuehrt
}
else if(loaded)
{
updateImage();
}
// nicht schon in control, sonst loescht opensequence wieder tracker
if(mTrcFileName != "")
{
// 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))
{
mPersonStorage.clear();
mTracker->reset();
}
importTracker(mTrcFileName);
}
mControlWidget->setTrackShowOnlyNr(onlyPeopleNr);
mControlWidget->trackShowOnlyNrList()->setText(onlyPeopleNrList);
if(cam == cameraLeft)
{
mCameraLeftViewAct->setChecked(true);
}
else if(cam == cameraRight)
{
mCameraRightViewAct->setChecked(true);
}
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())
{
return;
}
}
// 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())
{
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly))
{
PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2.").arg(fileName, file.errorString()));
return;
}
resetSettings();
QDomDocument doc("PETRACK"); // eigentlich Pfad zu Beschreibungsdatei fuer Dateiaufbau
if(!doc.setContent(&file))
{
PCritical(this, tr("PeTrack"), tr("Cannot read content from %1.").arg(fileName));
file.close();
return;
}
SPDLOG_INFO("open: {}", fileName);
file.close();
setProFileName(fileName);
QDomElement root = doc.firstChildElement("PETRACK");
if(root.hasAttribute("VERSION"))
{
if(root.attribute("VERSION") != mPetrackVersion)
{
PWarning(
this,
tr("PeTrack"),
tr("Reading %1:\nDifferent version numbers %2 (application) and %3 (file) may cause problems.")
.arg(fileName, mPetrackVersion, root.attribute("VERSION")));
}
}
openXml(doc, openSeq);
mLastTrackerExport = mTrcFileName;
updateWindowTitle();
}
}
void Petrack::saveXml(QDomDocument &doc)
{
QDomElement elem;
QDomElement root = doc.createElement("PETRACK");
root.setAttribute("VERSION", mPetrackVersion);
doc.appendChild(root);
// main settings (window size, status hight)
elem = doc.createElement("MAIN");
QString seq = getFileList(mSeqFileName, mProFileName);
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);
// 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);
// 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());
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
elem.setAttribute("CAMERA", mAnimation->getCamera());
#else
elem.setAttribute("CAMERA", cameraUnset);
#endif
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);
}
/// rueckgabewert zeigt an, ob gesichert werden konnte
bool Petrack::saveSameProject()
{
return saveProject(mProFileName);
}
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
bool Petrack::saveProject(QString fileName) // default fileName=""
{
// if no destination file or folder is given
if(fileName.isEmpty() && QFileInfo(mProFileName).isDir())
{
fileName = QFileDialog::getSaveFileName(
this, tr("Select project file"), mProFileName, tr("PeTrack project file (*.pet);;All files (*.*)"));
}
if(!fileName.isEmpty())
{
setProFileName(fileName);
QDomDocument doc("PETRACK"); // eigentlich Pfad zu Beschreibungsdatei fuer Dateiaufbau
saveXml(doc);
// file output
QByteArray byteArray;
QXmlStreamWriter xmlStream(&byteArray);
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))
{
PCritical(this, tr("PeTrack"), tr("Cannot save %1:\n%2.").arg(fileName, file.errorString()));
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);
updateWindowTitle();
return true;
}
else
{
return false;
}
}
void Petrack::writeXmlElement(QXmlStreamWriter &xmlStream, QDomElement element)
{
xmlStream.writeStartElement(element.tagName());
QVector<QString> attribute_names;
const QDomNamedNodeMap attributes = element.attributes();
for(int i = 0; i < attributes.size(); ++i)
{
attribute_names.push_back(attributes.item(i).toAttr().name());
}
// TODO: check if sorting of elements fits our needs
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)
{
QDomAttr attr = element.attributeNode(name);
xmlStream.writeAttribute(attr.name(), attr.value());
}
// order of child nodes is defined at creation
if(element.hasChildNodes())
{
const QDomNodeList children = element.childNodes();
for(int i = 0; i < children.size(); ++i)
{
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*/)
{
if(camID == -1)
{
// if more than one camera connected show to choose
// camID = selectedID;
SPDLOG_INFO("No camera ID delivered: Set CameraID to 0 (default Camera)");
camID = 0; // default
}
if(!mAnimation->openCameraStream(camID))
{
PCritical(this, tr("PeTrack"), tr("Cannot start Camera Livestream."));
return;
}
mSeqFileName = "camera live stream";
SPDLOG_INFO(
"open {} ({} frames; {} fps; {} x {} pixel)",
mSeqFileName,
mAnimation->getNumFrames(),
mAnimation->getFPS(),
mAnimation->getSize().width(),
mAnimation->getSize().height());
updateSequence();
updateWindowTitle();
mPlayerWidget->setFPS(mAnimation->getFPS());
if(mOpenGLAct->isChecked())
{
mLogoItem->fadeOut(1);
}
else
{
mLogoItem->fadeOut(50);
}
updateCoord();
mPlayerWidget->play(PlayerState::FORWARD);
}
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())
{
if(!mAnimation->openAnimation(fileName))
{
PCritical(this, tr("PeTrack"), tr("Cannot load %1.").arg(fileName));
return;
}
mCameraGroupView->setEnabled(mAnimation->isStereoVideo());
mCameraMenu->setEnabled(mAnimation->isStereoVideo());
#ifdef STEREO
if(mAnimation->isStereoVideo())
{
if(mStereoContext)
delete mStereoContext;
mStereoContext = new pet::StereoContext(this);
}
bool lastIsStereoVideo = mAnimation->isStereoVideo();
if(mCalibFilter == NULL || (mAnimation->isStereoVideo() != lastIsStereoVideo))
{
bool lastCalibFilterEnabled = false;
if(mCalibFilter != NULL)
{
lastCalibFilterEnabled = mCalibFilter->getEnabled();
delete mCalibFilter;
}
if(mAnimation->isStereoVideo())
{
mCalibFilter = new CalibStereoFilter;
((CalibStereoFilter *) mCalibFilter)->setStereoContext(mStereoContext);
}
else
mCalibFilter = new CalibFilter;
mCalibFilter->setEnabled(lastCalibFilterEnabled);
}
#endif
mSeqFileName = fileName;
SPDLOG_INFO(
"open {} ({} frames; {} fps; {} x {} pixel)",
mSeqFileName,
mAnimation->getNumFrames(),
mAnimation->getFPS(),
mAnimation->getSize().width(),
mAnimation->getSize().height());
updateSequence();
updateWindowTitle();
mPlayerWidget->setFPS(mAnimation->getFPS());
if(mOpenGLAct->isChecked())
{
mLogoItem->fadeOut(1);
}
else
{
mLogoItem->fadeOut(50);
}
updateCoord();
mMissingFrames.reset();
}
}
void Petrack::openMoCapFile()
{
OpenMoCapDialog dialog(this, mMoCapController);
dialog.exec();
}
void Petrack::updateWindowTitle()
{
QString title;
QSize size = mAnimation->getSize();
if(QFileInfo(mProFileName).isDir())
{
title = tr("PeTrack (v") + mPetrackVersion + tr("): ");
}
else
{
title = tr("PeTrack (v") + mPetrackVersion + tr("): ") + QFileInfo(mProFileName).fileName();
if(mAnimation->isVideo() || mAnimation->isImageSequence())
{
title += "; ";
}
}
if(mAnimation->isVideo())
{
title += "sequence: " + mAnimation->getCurrentFileName() + tr(" (%1").arg(mAnimation->getNumFrames()) +
tr(" frames; %1x%2").arg(size.width()).arg(size.height()) + " pixel)";
}
else if(mAnimation->isImageSequence())
{
title += "sequence: " + mAnimation->getCurrentFileName() + tr(" ... (%1").arg(mAnimation->getNumFrames()) +
tr(" frames; %1x%2").arg(size.width()).arg(size.height()) + " pixel)";
}
setWindowTitle(title);
}
void Petrack::saveVideo()
{
saveSequence(true, false);
}
void Petrack::saveVideoView()
{
saveSequence(true, true);
}
void Petrack::saveImageSequence()
{
saveSequence(false, false);
}
void Petrack::saveViewSequence()
{
saveSequence(false, true);
}
/**
* @brief Saves current sequence as avi-file or image sequence
*
* Saves the loaded image sequence or video from current frame on till the end.
* One can save the sequence as is or one can save the view shown in PeTrack.
*
* @param saveVideo true, if wanting to save a video. Ignored when dest isn't empty
* @param saveView true, if sequence should be saved as shown in PeTrack (with trajectories etc.)
* @param dest destination file; if empty, the user chooses via a dialog
*/
void Petrack::saveSequence(bool saveVideo, bool saveView, QString dest) // default saveView= false, dest=""
{
static QString lastDir;
// if no destination file or folder is given
if(dest.isEmpty())
{
if(lastDir.isEmpty() && !mSeqFileName.isEmpty())
{
lastDir = QFileInfo(mSeqFileName).path();
}
if(saveVideo)
{
dest = QFileDialog::getSaveFileName(
this,
tr("Select video file"),
lastDir,
tr("Video (*.mp4 *.avi);;All files (*.*)")); //? *.mpg *.mpeg
}
else
{
if(saveView)
{
dest = QFileDialog::getExistingDirectory(
this,
tr("Select directory to save view sequence"),
lastDir,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
}
else
{
dest = QFileDialog::getExistingDirectory(
this,
tr("Select directory to save image sequence"),
lastDir,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
}
}
}
auto extension = dest.right(4);
int fourcc = -1;
if(extension == ".mp4")
{
fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
saveVideo = true;
}
else if(extension == ".avi")
{
fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
saveVideo = true;
}
else
{
saveVideo = false;
}
if(!dest.isEmpty() && mImage)
{
int rest = mAnimation->getNumFrames() - 1;
int numLength = 1;
int memPos = mPlayerWidget->getPos();
QString fileName = "";
bool formatIsSaveAble = false;
bool saveRet;
QImage *viewImage = nullptr;
QPainter *painter = nullptr;
int progEnd = mAnimation->getSourceOutFrameNum() -
mPlayerWidget->getPos(); // nur wenn nicht an anfang gesprungen wird:-mPlayerWidget->getPos()
cv::Mat iplImgFilteredBGR;
bool writeFrameRet = false;
cv::VideoWriter outputVideo;
if(saveVideo)
{
if(saveView)
{
if(mCropZoomViewAct->isChecked())
{
viewImage =
new QImage(mView->viewport()->width(), mView->viewport()->height(), QImage::Format_RGB32);
}
else
{
viewImage = new QImage((int) mScene->width(), (int) mScene->height(), QImage::Format_RGB32);
}
painter = new QPainter();
}
if(saveView)
{
outputVideo = cv::VideoWriter(
dest.toStdString(),
fourcc,
mAnimation->getFPS(),
cv::Size(viewImage->width(), viewImage->height()));
}
else
{
bool colored = (mImg.channels() > 1);
outputVideo = cv::VideoWriter(
dest.toStdString(), fourcc, mAnimation->getFPS(), cv::Size(mImg.cols, mImg.rows), colored);
}
}
if(!saveVideo)
{
if(saveView)
{
if(mCropZoomViewAct->isChecked())
{
viewImage =
new QImage(mView->viewport()->width(), mView->viewport()->height(), QImage::Format_RGB32);
}
else
{
viewImage = new QImage((int) mScene->width(), (int) mScene->height(), QImage::Format_RGB32);
}
painter = new QPainter();
}
// test, if fileformat is supported
if(mAnimation->isVideo())
{
// calculate string length of sequence number
while((rest /= 10) > 0)
{
numLength++;
}
fileName = (dest + "/" + mAnimation->getFileBase() + "%1.png")
.arg(mPlayerWidget->getPos(), numLength, 10, QChar('0'));
}
else
{
fileName = dest + "/" + mAnimation->getCurrentFileName();
}
if(saveView)
{
painter->begin(viewImage);
if(mCropZoomViewAct->isChecked())
{
mView->render(painter);
}
else
{
mScene->render(painter);
}
painter->end();
if(viewImage->save(fileName)) //, const char * format = 0 (format wird aus dateinamen geholt), int
// quality = -1 default normal (0..100)
{
formatIsSaveAble = true;
mPlayerWidget->frameForward();
}
}
else if(mImage->save(fileName)) //, const char * format = 0 (format wird aus dateinamen geholt), int quality
//= -1 default normal (0..100)
{
formatIsSaveAble = true;
mPlayerWidget->frameForward();
}
}
else if((mImgFiltered.channels() == 1) /*&& convert8To24bit*/)
{
cv::Size size;
size.width = mImgFiltered.cols;
size.height = mImgFiltered.rows;
iplImgFilteredBGR.create(size, CV_8UC3);
}
QProgressDialog progress("", "Abort save", 0, progEnd, this);
progress.setWindowModality(Qt::WindowModal); // blocks main window
if(saveVideo)
{
if(saveView)
{
progress.setLabelText("Save video view...");
}
else
{
progress.setLabelText("Save video...");
}
}
else
{
if(saveView)
{
progress.setLabelText("Save view sequence...");
}
else
{
progress.setLabelText("Save image sequence...");
}
}
do
{
progress.setValue(
mPlayerWidget->getPos() - memPos); // -mempos nur, wenn nicht an den anfang gesprungen wird
qApp->processEvents();
if(progress.wasCanceled())
{
break;
}
if(saveVideo)
{
// video sequence
if(saveView)
{
painter->begin(viewImage);
if(mCropZoomViewAct->isChecked())
{
mView->render(painter);
}
else
{
mScene->render(painter);
}
painter->end();
}
if(saveView)
{
cv::Mat frame(
viewImage->height(),
viewImage->width(),
CV_8UC4,
(unsigned char *) viewImage->bits(),
viewImage->bytesPerLine());
cv::cvtColor(frame, frame, cv::COLOR_RGBA2RGB); // need for right image interpretation
outputVideo.write(frame);
writeFrameRet = true;
}
else
{
cv::Mat frame = mImg.clone();
outputVideo.write(frame);
writeFrameRet = true;
}
if(!writeFrameRet)
{
progress.setValue(progEnd);
PCritical(
this,
tr("PeTrack"),
tr("Cannot save %1 maybe because of wrong file extension or unsupported codec.").arg(dest));
break;
}
}
else
{
// single frame sequence
if(saveView)
{
painter->begin(viewImage);
if(mCropZoomViewAct->isChecked())
{
mView->render(painter);
}
else
{
mScene->render(painter);
}
painter->end();
}
if(mAnimation->isVideo())
{
fileName = (dest + "/" + mAnimation->getFileBase() + "%1.png")
.arg(mPlayerWidget->getPos(), numLength, 10, QChar('0'));
if(saveView)
{
saveRet = viewImage->save(fileName);
}
else
{
saveRet = mImage->save(fileName);
}
}
else if(formatIsSaveAble)
{
fileName = dest + "/" + mAnimation->getCurrentFileName();
if(saveView)
{
saveRet = viewImage->save(fileName);
}
else
{
saveRet = mImage->save(fileName);
}
}
else
{
fileName = dest + "/" + QFileInfo(mAnimation->getCurrentFileName()).completeBaseName() + ".png";
if(saveView)
{
saveRet = viewImage->save(fileName);
}
else
{
saveRet = mImage->save(fileName, "PNG"); //, int quality = -1 default normal (0..100)
}
}
if(!saveRet)
{
progress.setValue(progEnd);
PCritical(this, tr("PeTrack"), tr("Cannot save %1.").arg(fileName));
break;
}
}
} while(mPlayerWidget->frameForward());
if(!saveVideo && saveView)
{
delete viewImage;
delete painter;
}
// bei abbruch koennen es auch mPlayerWidget->getPos() frames sein, die bisher geschrieben wurden
//-memPos nur, wenn nicht an den anfang gesprungen wird
SPDLOG_INFO("wrote {} of {} frames.", mPlayerWidget->getPos() + 1 - memPos, mAnimation->getNumFrames());
progress.setValue(progEnd);
if(saveVideo)
{
outputVideo.release();
}
mPlayerWidget->skipToFrame(memPos);
lastDir = dest;
}
}
/**
* @brief Saves the current View, including visualizations, in a file (.g. pdf)
*
* @param dest name of the saved file; if empty, a dialogue for the user opens
*/
void Petrack::saveView(QString dest) // default = ""
{
static QString lastFile;
if(mImage)
{
// if no destination file or folder is given
if(dest.isEmpty())
{
if(lastFile.isEmpty() && !mSeqFileName.isEmpty())
{
lastFile = QFileInfo(mSeqFileName).path();
}
// alle unetrstuetzen fileformate erhaelt man mit
// QImageReader::supportedImageFormats() and QImageWriter::supportedImageFormats()
// gif muss nicht dabei sein, dazu muss qt mit -qt-gif uebersetzt worden sein
dest = QFileDialog::getSaveFileName(
this,
tr("Select image file"),
lastFile,
tr("PDF (*.pdf);;Postscript (*.ps *.eps);;Windows bitmaps (*.bmp);;JPEG (*.jpeg *.jpg);;Portable "
"network graphics (*.png);;Portable image format (*.pbm *.pgm *.ppm);;X11 Bitmap or Pixmap (*.xbm "
"*.xpm);;Pixel Images (*.bmp *.jpeg *.jpg *.png *.pbm *.pgm *.ppm *.xbm *.xpm);;All supported types "
"(*pdf *ps *.eps *.bmp *.jpeg *.jpg *.png *.pbm *.pgm *.ppm *.xbm *.xpm);;All files (*.*)"));
}
if(!dest.isEmpty())
{
if(dest.right(4) == ".pdf" || dest.right(3) == ".ps" || dest.right(4) == ".eps")
{
QPdfWriter pdfWriter(dest);
pdfWriter.setPageMargins({0, 0, 0, 0});
QPageSize pageSize{mImage->size()};
pdfWriter.setPageSize(pageSize);
QPainter painter(&pdfWriter);
if(mCropZoomViewAct->isChecked())
{
mView->render(&painter);
}
else
{
mScene->render(&painter);
}
}
else
{
// schwarzer rand links und unten?!
QImage *img;
if(mCropZoomViewAct->isChecked())
{
img = new QImage(mView->viewport()->width(), mView->viewport()->height(), QImage::Format_RGB32);
}
else
{
img = new QImage((int) mScene->width(), (int) mScene->height(), QImage::Format_RGB32);
}
QPainter painter(img);
if(mCropZoomViewAct->isChecked())
{
mView->render(&painter);
}
else
{
mScene->render(&painter);
}
painter.end();
if(!img->save(dest)) //, "PNG"
{
PCritical(
this, tr("PeTrack"), tr("Cannot save %1 maybe because of wrong file extension.").arg(dest));
}
delete img;
}
lastFile = dest;
}
}
}
void Petrack::saveImage(QString dest) // default = ""
{
static QString lastFile;
if(mImage)
{
// if no destination file or folder is given
if(dest.isEmpty())
{
if(lastFile.isEmpty() && !mSeqFileName.isEmpty())
{
lastFile = QFileInfo(mSeqFileName).path();
}
// alle unetrstuetzen fileformate erhaelt man mit
// QImageReader::supportedImageFormats() and QImageWriter::supportedImageFormats()
// gif muss nict dabei sein, dazu muss qt mit -qt-gif uebesetz worden sein
dest = QFileDialog::getSaveFileName(
this,
tr("Select image file"),
lastFile,
tr("PDF (*.pdf);;Postscript (*.ps *.eps);;Windows bitmaps (*.bmp);;JPEG (*.jpeg *.jpg);;Portable "
"network graphics (*.png);;Portable image format (*.pbm *.pgm *.ppm);;X11 Bitmap or Pixmap (*.xbm "
"*.xpm);;Pixel Images (*.bmp *.jpeg *.jpg *.png *.pbm *.pgm *.ppm *.xbm *.xpm);;All supported types "
"(*pdf *ps *.eps *.bmp *.jpeg *.jpg *.png *.pbm *.pgm *.ppm *.xbm *.xpm);;All files (*.*)"));
}
if(!dest.isEmpty())
{
if(dest.right(4) == ".pdf" || dest.right(3) == ".ps" || dest.right(4) == ".eps")
{
QPdfWriter pdfWriter(dest);
pdfWriter.setPageMargins({0, 0, 0, 0});
QPageSize pageSize{mImage->size()};
pdfWriter.setPageSize(pageSize);
QPainter painter(&pdfWriter);
QRect rect = painter.viewport();
QSize size = mImage->size();
size.scale(rect.size(), Qt::KeepAspectRatio);
painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
painter.setWindow(mImage->rect());
painter.drawImage(0, 0, *mImage);
}
else
{
if(!mImage->save(dest)) //, "PNG"
{
PCritical(
this, tr("PeTrack"), tr("Cannot save %1 maybe because of wrong file extension.").arg(dest));
}
}
lastFile = dest;
}
}
}
void Petrack::print()
{
if(mImage)
{
// HighResolution font zu gross! und laengere laufzeit und eher overflow
// aber so pixelig und keine schoenen linien
QPrinter printer(QPrinter::ScreenResolution); // ScreenResolution, HighResolution// liefert zu hause:
// QWin32PrintEngine::initialize: GetPrinter failed ()
printer.setPageSize(QPageSize{QPageSize::PageSizeId::A4});
QPrintDialog dialog(&printer, this);
if(dialog.exec())
{
QPainter painter(&printer);
mView->render(&painter);
}
}
else
{
PCritical(this, tr("PeTrack"), tr("Nothing to print!"));
}
}
void Petrack::resetSettings()
{
mAnimation->reset();
openXml(mDefaultSettings, false);
}
void Petrack::about()
{
auto about = new AboutDialog(
this,
mPetrackVersion,
mGitCommitID,
mGitCommitDate,
mGitCommitBranch,
mCompilerID,
mCompilerVersion,
mCompileDate,
mAuthors);
about->show();
}
void Petrack::commandLineOptions()
{
PMessageBox *mb = new PMessageBox{this, tr("Command line options"), commandLineOptionsString, QIcon()};
mb->setModal(false);
mb->setAttribute(Qt::WA_DeleteOnClose);
mb->show();
}
void Petrack::keyBindings()
{
QString ctrlSign = (mCompileOS != "Darwin") ? "⌃ Ctrl" : "⌘ Cmd";
QString shiftSign = "⇧ Shift";
QString altSign = (mCompileOS != "Darwin") ? "⎇ Alt" : "⌥ Option";
QString arrowUp = "Arrow up ↑";
QString arrowDown = "Arrow down ↓";
QString arrowLeft = "Arrow left ←";
QString arrowRight = "Arrow right →";
const QString out =
tr("<p>Beside the space bar all bindings only affect inside the image.</p>"
"<dl><dt><kbd>Space bar</kbd></dt><dd>toggles between pause and last play direction</dd>"
"<dt><kbd>Mouse scroll wheel</kbd></dt><dd>zooms in and out to or from the pixel of the image at the "
"position "
"of the mouse pointer</dd>"
"<dt><kbd>%2 + mouse scroll wheel</kbd></dt><dd>plays forwards or backwards frame by frame</dd>"
"<dt><kbd>Holding left mouse button</kbd></dt><dd>moves image</dd>"
"<dt><kbd>%4/%5</kbd></dt><dd>zoom in/out</dd>"
"<dt><kbd>%6/%7</kbd></dt><dd>frame back/forward</dd>"
"<dt><kbd>Double-click left mouse button</kbd></dt><dd>opens video or image sequence</dd>"
"<dt><kbd>%1 + double-click left mouse button</kbd></dt><dd>inserts new or moves near trackpoint</dd>"
"<dt><kbd>%1 + %2 + double-click left mouse button</kbd></dt><dd>splits near trackpoint before current "
"frame</dd>"
"<dt><kbd>%1 + double-click right mouse button</kbd></dt><dd>deletes a trajectory of a near trackpoint</dd>"
"<dt><kbd>%2 + double-click right mouse button</kbd></dt><dd>deletes the past part of a trajectory of a "
"near trackpoint</dd>"
"<dt><kbd>%3 + double-click right mouse button</kbd></dt><dd>deletes the future part of a trajectory of a "
"near trackpoint</dd>"
"<dt><kbd>%1 + double-click middle mouse button</kbd></dt><dd>deletes all trajectories</dd>"
"<dt><kbd>%2 + double-click middle mouse button</kbd></dt><dd>deletes the past part of all trajectories</dd>"
"<dt><kbd>%3 + double-click middle mouse button</kbd></dt><dd>deletes the future part of all "
"trajectories</dd>"
"<dt><kbd>%2 + t</kbd></dt><dd>toggles tracking online calculation</dd>"
"<dt><kbd>%2 + double-click left mouse button</kbd></dt><dd>inserts new or moves near trackpoint and "
"enables showing only the modified trajectory</dd>"
"<dt><kbd>%1 + %3 + double-click left mouse button</kbd></dt><dd>jumps to frame of trackpoint under "
"cursor</dd>"
"<dt><kbd>%3 + holding left mouse button</kbd></dt><dd>moves trackpoint under cursor</dd>"
"<dt><kbd>%1 + e</kbd></dt><dd>export trajectories</dd>"
"<dt><kbd>%1 + mouse scroll wheel</kbd></dt><dd>change the displayed person (if show only people "
"enabled)</dd></dl>"
"<p>Further key bindings you will find next to the entries of the menus.</p>")
.arg(ctrlSign)
.arg(shiftSign)
.arg(altSign)
.arg(arrowUp)
.arg(arrowDown)
.arg(arrowLeft)
.arg(arrowRight);
PMessageBox *mb = new PMessageBox(this, tr("Key Bindings"), out, QIcon());
mb->setAttribute(Qt::WA_DeleteOnClose);
mb->setModal(false);
mb->show();
}
void Petrack::onlineHelp()
{
static QUrl url("https://jugit.fz-juelich.de/ped-dyn-emp/petrack/-/wikis/home");
if(!(QDesktopServices::openUrl(url)))
{
PCritical(this, tr("PeTrack"), tr("Cannot open external browser<br>with url ") + url.toString() + "!");
}
}
void Petrack::antialias()
{
mView->setRenderHint(QPainter::Antialiasing, mAntialiasAct->isChecked());
}
void Petrack::opengl()
{
mView->setViewport(mOpenGLAct->isChecked() ? new QGLWidget(QGLFormat(QGL::SampleBuffers)) : new QWidget);
// alten freigeben wegen new????
}
void Petrack::reset()
{
mViewWidget->resetView();
}
void Petrack::fitInView()
{
mViewWidget->fitInView();
}
void Petrack::fitInROI()
{
mViewWidget->fitInROI(getRecoRoiItem()->rect());
// what about trackingRoi???
}
void Petrack::setGlobalFont()
{
bool ok;
QFont font = QFontDialog::getFont(&ok, this->font(), this);
if(ok)
{
this->setFont(font); // font is set to the font the user selected
}
else
{
// the user canceled the dialog; font is set to the initial
// value, in this case Times, 12.
}
}
void Petrack::showHideControlWidget()
{
// show | hide Control
mViewWidget->hideControls(mHideControlsAct->isChecked());
}
void Petrack::setCamera()
{
#ifndef STEREO_DISABLED
if(mAnimation)
{
if(mCameraLeftViewAct->isChecked())
{
if((mAnimation->getCamera()) != cameraLeft)
mAnimation->setCamera(cameraLeft); // war: hier wird direkt bei Umstellung neu gelesen
else
return;
}
else if(mCameraRightViewAct->isChecked())
{
if((mAnimation->getCamera()) != cameraRight)
mAnimation->setCamera(cameraRight); // war: hier wird direkt bei Umstellung neu gelesen
else
return;
}
else // kann eigentlich nicht vorkommen
{
mAnimation->setCamera(cameraUnset);
return;
}
updateImage(mAnimation->getFrameAtIndex(
mAnimation->getCurrentFrameNum())); // wird nur aufgerufen, wenn left / right sich geaendert hat
// mPlayerWidget->updateImage();
// mPlayerWidget->skipToFrame(mPlayerWidget->getPos()); // machtpasue!!
// updateImage(true); // nur dies aufrufen, wenn nicht links rechts gleichzeitig gehalten wird
}
#endif
}
/**
* @brief Helper function to create Actions for the menu bar
* @see Petrack::createMenus()
*/
void Petrack::createActions()
{
mOpenSeqAct = new QAction(tr("&Open Sequence"), this);
mOpenSeqAct->setShortcut(tr("Ctrl+Shift+O"));
connect(mOpenSeqAct, SIGNAL(triggered()), this, SLOT(openSequence()));
mOpenCameraAct = new QAction(tr("Open Camera Stream"), this);
// mOpenCameraAct->setShortcut(tr("Ctrl+C")); // because of some reason it is sometimes fired with
// Ctrl+LeftMouseButton ==> so disabled (it's also not really needed)
connect(mOpenCameraAct, SIGNAL(triggered()), this, SLOT(openCameraLiveStream()));
mOpenMoCapAct = new QAction(tr("Manage MoCap Files"), this);
connect(mOpenMoCapAct, &QAction::triggered, this, &Petrack::openMoCapFile);
mSaveSeqVidAct = new QAction(tr("Save Video"), this);
mSaveSeqVidAct->setEnabled(false);
connect(mSaveSeqVidAct, SIGNAL(triggered()), this, SLOT(saveVideo()));
mSaveSeqVidViewAct = new QAction(tr("Save Video View"), this);
mSaveSeqVidViewAct->setEnabled(false);
connect(mSaveSeqVidViewAct, SIGNAL(triggered()), this, SLOT(saveVideoView()));
mSaveSeqImgAct = new QAction(tr("Save Image S&equence"), this);
mSaveSeqImgAct->setShortcut(tr("Ctrl+F"));
mSaveSeqImgAct->setEnabled(false);
connect(mSaveSeqImgAct, SIGNAL(triggered()), this, SLOT(saveImageSequence()));
mSaveSeqViewAct = new QAction(tr("Save View S&equence"), this);
// mSaveSeqViewAct->setShortcut(tr("Ctrl+F"));
mSaveSeqViewAct->setEnabled(false);
connect(mSaveSeqViewAct, SIGNAL(triggered()), this, SLOT(saveViewSequence()));
mOpenPrAct = new QAction(tr("&Open Project"), this);
mOpenPrAct->setShortcut(tr("Ctrl+O"));
connect(mOpenPrAct, SIGNAL(triggered()), this, SLOT(openProject()));
mSavePrAct = new QAction(tr("&Save Project As"), this);
mSavePrAct->setShortcut(tr("Ctrl+Shift+S"));
connect(mSavePrAct, &QAction::triggered, this, &Petrack::saveProjectAs);
mSaveAct = new QAction(tr("&Save Project"), this);
mSaveAct->setShortcut(tr("Ctrl+S"));
connect(mSaveAct, SIGNAL(triggered()), this, SLOT(saveSameProject()));
mSaveImageAct = new QAction(tr("&Save Image"), this);
mSaveImageAct->setShortcut(tr("Ctrl+I"));
mSaveImageAct->setEnabled(false);
connect(mSaveImageAct, SIGNAL(triggered()), this, SLOT(saveImage()));
mSaveViewAct = new QAction(tr("&Save View"), this);
mSaveViewAct->setShortcut(tr("Ctrl+V"));
mSaveViewAct->setEnabled(false);
connect(mSaveViewAct, SIGNAL(triggered()), this, SLOT(saveView()));
mPrintAct = new QAction(tr("&Print"), this);
mPrintAct->setShortcut(tr("Ctrl+P"));
mPrintAct->setEnabled(false);
connect(mPrintAct, SIGNAL(triggered()), this, SLOT(print()));
mResetSettingsAct = new QAction(tr("&Reset Settings"), this);
// mResetSettingsAct->setShortcut(tr("Ctrl+R"));
mResetSettingsAct->setEnabled(false); // da es noch nicht fehlerfrei funktioniert
connect(mResetSettingsAct, SIGNAL(triggered()), this, SLOT(resetSettings()));
mExitAct = new QAction(tr("E&xit"), this);
mExitAct->setShortcut(tr("Ctrl+Q"));
connect(mExitAct, SIGNAL(triggered()), this, SLOT(close()));
mAntialiasAct = new QAction(tr("&Antialias"), this);
mAntialiasAct->setShortcut(tr("Ctrl+A"));
mAntialiasAct->setCheckable(true);
connect(mAntialiasAct, SIGNAL(triggered()), this, SLOT(antialias()));
mFontAct = new QAction(tr("&Font"), this);
connect(mFontAct, SIGNAL(triggered()), this, SLOT(setGlobalFont()));
mHideControlsAct = new QAction(tr("&Hide controls"), this);
mHideControlsAct->setShortcut(tr("Ctrl+H"));
mHideControlsAct->setCheckable(true);
connect(mHideControlsAct, SIGNAL(triggered()), this, SLOT(showHideControlWidget()));
connect(mHideControlsAct, SIGNAL(changed()), this, SLOT(showHideControlWidget()));
mCropZoomViewAct = new QAction(tr("&Transform while saving"), this); // Crop and zoom while saving
mCropZoomViewAct->setCheckable(true);
mOpenGLAct = new QAction(tr("Open&GL"), this);
mOpenGLAct->setShortcut(tr("Ctrl+G"));
mOpenGLAct->setCheckable(true);
connect(mOpenGLAct, SIGNAL(triggered()), this, SLOT(opengl()));
mResetAct = new QAction(tr("&Reset"), this);
mResetAct->setShortcut(tr("Ctrl+R"));
connect(mResetAct, SIGNAL(triggered()), this, SLOT(reset()));
mFitViewAct =
new QAction(tr("Fit in window"), this); // Resize to window; fit in view; show all; in fenster einpassen
mFitViewAct->setShortcut(tr("Ctrl+0"));
connect(mFitViewAct, SIGNAL(triggered()), this, SLOT(fitInView()));
mFitROIAct = new QAction(tr("Fit in region of interest"), this); // Resize ROI to window; fit in view;
mFitROIAct->setShortcut(tr("Ctrl+1"));
connect(mFitROIAct, SIGNAL(triggered()), this, SLOT(fitInROI()));
mCameraGroupView = new QActionGroup(this);
// mCameraGroupView->addAction(mCameraLeftViewAct);
// mCameraGroupView->addAction(mCameraRightViewAct);
mCameraLeftViewAct = new QAction(tr("&Left"), mCameraGroupView);
mCameraLeftViewAct->setShortcut(tr("Ctrl++Shift+L"));
mCameraLeftViewAct->setCheckable(true);
connect(mCameraLeftViewAct, SIGNAL(triggered()), this, SLOT(setCamera()));
mCameraRightViewAct = new QAction(tr("&Right"), mCameraGroupView);
mCameraRightViewAct->setShortcut(tr("Ctrl++Shift+R"));
mCameraRightViewAct->setCheckable(true);
connect(mCameraRightViewAct, SIGNAL(triggered()), this, SLOT(setCamera()));
mCameraRightViewAct->setChecked(true); // right wird als default genommen, da reference image in triclops auch right
// ist // erste trj wurden mit left gerechnet
mLimitPlaybackSpeed = new QAction(tr("&Limit playback speed"));
// Not checkable like Fix since this is also controlled through clicking on FPS and syncing currently would be
// 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); });
mPlayerLooping = new QAction(tr("&Loop"));
mPlayerLooping->setCheckable(true);
connect(mPlayerLooping, &QAction::triggered, mPlayerWidget, &Player::setLooping);
// -------------------------------------------------------------------------------------------------------
mDelPastAct = new QAction(tr("&Past part of all trj."), this);
connect(
mDelPastAct,
&QAction::triggered,
this,
[this]() { this->deleteTrackPointAll(PersonStorage::Direction::Previous); });
mDelFutureAct = new QAction(tr("&Future part of all trj."), this);
connect(
mDelFutureAct,
&QAction::triggered,
this,
[this]() { this->deleteTrackPointAll(PersonStorage::Direction::Following); });
mDelAllRoiAct = new QAction(tr("&Trj. moving through ROI"), this);
connect(mDelAllRoiAct, &QAction::triggered, this, &Petrack::deleteTrackPointROI);
mDelPartRoiAct = new QAction(tr("Part of Trj. inside &ROI"), this);
connect(mDelPartRoiAct, &QAction::triggered, this, &Petrack::deleteTrackPointInsideROI);
// -------------------------------------------------------------------------------------------------------
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()
*/
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(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->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);
mViewMenu->addAction(mPlayerLooping);
mViewMenu->addSeparator();
mViewMenu->addAction(mFitViewAct);
mViewMenu->addAction(mFitROIAct);
mViewMenu->addAction(mResetAct);
mViewMenu->addSeparator();
mViewMenu->addAction(mFontAct);
mViewMenu->addSeparator();
mViewMenu->addAction(mHideControlsAct);
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
*/
void Petrack::createStatusBar()
{
QFont f("Courier", 12, QFont::Bold); // Times Helvetica, Normal
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);
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()
{
/// ToDo:
///
/// 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
///
return;
}
void Petrack::setStatusStereo(float x, float y, float z)
{
if(mStatusLabelStereo)
{
if(z < 0)
{
mStatusLabelStereo->setText(QString("x= novalue y= novalue z= novalue "));
}
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));
}
}
}
void Petrack::setStatusTime()
{
if(mStatusLabelTime)
{
mStatusLabelTime->setText(mAnimation->getTimeString());
}
}
void Petrack::setStatusFPS()
{
if(mStatusLabelFPS)
{
mStatusLabelFPS->setText(QString("%1fps ").arg(mShowFPS, 5, 'f', 1));
QPalette pal = mStatusLabelFPS->palette(); // static moeglich?
QColor color;
double diff = mShowFPS - mAnimation->getFPS();
int opacity = mPlayerWidget->getPlayerSpeedLimited() ? 128 : 20;
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);
}
pal.setColor(QPalette::Window, color);
mStatusLabelFPS->setPalette(pal);
}
}
void Petrack::setShowFPS(double fps)
{
if((fps == 0.) || (mShowFPS == 0))
{
mShowFPS = fps;
}
else
{
mShowFPS = mShowFPS * .9 + fps * .1; // glaetten durch Hinzunahme des alten Wertes
}
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;
if(skipped)
{
skippedFrames++;
return;
}
if(mPlayerWidget->getPaused())
{
setShowFPS(0.);
lastTime.invalidate();
}
else
{
if(lastTime.isValid())
{
if(lastTime.elapsed() > 0)
{
int numFrames = skippedFrames > 0 ? skippedFrames + 1 : 1;
setShowFPS(numFrames * 1000. / lastTime.elapsed());
skippedFrames = 0;
}
}
lastTime.start();
}
}
// ohne neue positionsangabe, sinnvoll, wenn berechnungsweise sich in getPosReal geaendert hat
// gebraucht in control.cpp
void Petrack::setStatusPosReal() // pos in cm
{
if(mImageItem)
{
setStatusPosReal(mImageItem->getPosReal(mMousePosOnImage, getStatusPosRealHeight()));
}
}
void Petrack::setStatusPosReal(const QPointF &pos) // pos in cm
{
if(mStatusLabelPosReal)
{
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(
getImageItem()->getAngleToGround(
mMousePosOnImage.x(), mMousePosOnImage.y(), getStatusPosRealHeight()),
5,
'f',
1);
labelText.append(deg);
mStatusLabelPosReal->setText(labelText);
}
}
void Petrack::setStatusPos(const QPoint &pos) // pos in pixel
{
mStatusLabelPos->setText(QString("%1x%2").arg(pos.x(), 4).arg(pos.y(), 4));
}
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)
{
mStatusLabelColor->setText(QString("<font color=\"#ffffff\"> %1</font>").arg(s));
}
else
{
mStatusLabelColor->setText(QString("<font color=\"#000000\"> %1</font>").arg(s));
}
QPalette pal = mStatusLabelColor->palette(); // static moeglich?
QColor color(qRed(col), qGreen(col), qBlue(col));
pal.setColor(QPalette::Window, color);
mStatusLabelColor->setPalette(pal);
mControlWidget->getColorPlot()->setCursor(color);
mControlWidget->getColorPlot()->replot();
}
void Petrack::setStatusColor()
{
QPointF pos = getMousePosOnImage();
if(pos.x() >= 0 && pos.x() < mImage->width() && pos.y() > 0 && pos.y() < mImage->height())
{
setStatusColor(mImage->pixel(pos.toPoint()));
}
}
double Petrack::getStatusPosRealHeight()
{
if(mStatusPosRealHeight)
{
return mStatusPosRealHeight->value();
}
else
{
return 0.;
}
}
/**
* @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.
*/
void Petrack::readSettings()
{
QSettings settings("Forschungszentrum Juelich GmbH", "PeTrack by Maik Boltes, Daniel Salden");
QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
QSize size = settings.value("size", QSize(400, 400)).toSize();
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
mAutoCalib.addCalibFile(settings.value("calibFile", QDir::currentPath()).toString());
resize(size);
move(pos);
antialias();
opengl();
mSplitter->restoreState(settings.value("controlSplitterSizes").toByteArray());
}
/**
* @brief Writes persistent setting.
* @see Petrack::readSettings
*/
void Petrack::writeSettings()
{
QSettings settings("Forschungszentrum Juelich GmbH", "PeTrack by Maik Boltes, Daniel Salden");
settings.setValue("pos", pos());
settings.setValue("size", size());
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()
{
settings.setValue("calibFile", mAutoCalib.getCalibFile(0));
}
settings.setValue("controlSplitterSizes", mSplitter->saveState());
}
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)
{
if(saveSameProject())
{
return true;
}
else
{
return false;
}
}
else if(ret == PMessageBox::StandardButton::Cancel)
{
return false;
}
else
{
return true;
}
}
void Petrack::closeEvent(QCloseEvent *event)
{
if(maybeSave())
{
writeSettings();
mAutosave.deleteAutosave();
event->accept();
}
else
{
event->ignore();
}
}
/**
* @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
*/
void Petrack::setMousePosOnImage(QPointF pos)
{
if(mImage)
{
mMousePosOnImage = pos;
setStatusPosReal(mImageItem->getPosReal(pos, getStatusPosRealHeight()));
// pixel coordinate
QPoint pos1((int) (pos.x()) + 1, (int) (pos.y()) + 1);
setStatusPos(pos1);
// pixel color
setStatusColor();
}
}
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:;
}
}
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())
{
mPlayerWidget->togglePlayerSpeedLimited();
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)
{
// auch moeglich hoehe und breite von bild stat border veraenderungen zu checken
static int lastBorderSize = -1;
if(isLoading())
{
lastBorderSize = -1;
}
int diffBorderSize = 0;
if(lastBorderSize != -1)
{
diffBorderSize = getImageBorderSize() - lastBorderSize;
}
lastBorderSize = getImageBorderSize();
const int imgWidth = img.cols;
const int imgHeight = img.rows;
mControlWidget->imageSizeChanged(imgWidth, imgHeight, diffBorderSize);
}
void Petrack::importTracker(QString dest) // default = ""
{
static QString lastFile;
if(lastFile == "")
{
lastFile = mTrcFileName;
}
// if no destination file or folder is given
if(dest.isEmpty())
{
dest = QFileDialog::getOpenFileName(
this,
tr("Select file for importing tracking pathes"),
lastFile,
tr("PeTrack tracker (*.trc *.txt);;All files (*.*)"));
}
if(!dest.isEmpty())
{
if(dest.right(4) == ".trc")
{
QFile file(dest);
int i, sz;
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
// errorstring ist je nach Betriebssystem in einer anderen Sprache!!!!
PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2").arg(dest).arg(file.errorString()));
return;
}
setTrackChanged(true); // flag changes of track parameters
mTracker->reset();
QTextStream in(&file);
TrackPerson tp;
QString comment;
bool ok; // shows if int stands in first line - that was in the first version of trc file
QString firstLine = in.readLine();
sz = firstLine.toInt(&ok);
if(!ok)
{
if(firstLine.contains("version 4", Qt::CaseInsensitive))
{
trcVersion = 4;
}
else if(firstLine.contains("version 3", Qt::CaseInsensitive))
{
trcVersion = 3;
}
else if(firstLine.contains("version 2", Qt::CaseInsensitive))
{
trcVersion = 2;
}
else
{
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));
return;
}
in >> sz;
}
else
{
trcVersion = 1;
}
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)
{
if(trcVersion == 2)
{
in >> tp;
}
else if(trcVersion >= 3)
{
in >> tp;
}
mPersonStorage.addPerson(tp);
tp.clear(); // loeschen, sonst immer weitere pfade angehangen werden
}
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();
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.right(4) == ".txt") // 3D Koordinaten als Tracking-Daten importieren Zeilenformat: Personennr,
// Framenr, x, y, z
{
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!"));
QFile file(dest);
// size of person list
int sz = 0;
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
// errorstring ist je nach Betriebssystem in einer anderen Sprache!!!!
PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2").arg(dest).arg(file.errorString()));
return;
}
setTrackChanged(true); // flag changes of track parameters
mTracker->reset();
QTextStream in(&file);
TrackPerson tp;
TrackPoint tPoint;
cv::Point2f p2d;
QString line;
QString headerline;
bool exec_once_flag = false;
double conversionFactorTo_cm = 1.0;
int personNr = -1, frameNr = -1, current_personNr = 1;
float x, y, z;
while(1)
{
// Falls Datei am Ende letzte Person abspeichern und Lese-Schleife beenden
if(in.atEnd())
{
tp.setLastFrame(frameNr);
mPersonStorage.addPerson(tp);
++sz;
tp.clear();
break;
}
line = in.readLine();
// Kommentare ueberlesen
if(line.startsWith("#", Qt::CaseInsensitive))
{
headerline = line;
continue;
}
if((!exec_once_flag) && (!headerline.contains("cm")))
{
conversionFactorTo_cm = 100.0;
exec_once_flag = true;
PWarning(
this,
tr("PeTrack"),
tr("PeTrack will interpret position data as unit [m]. No header with [cm] found."));
}
QTextStream stream(&line);
// Zeile als Stream einlesen Format: [id frame x y z]
stream >> personNr >> frameNr >> x >> y >> z;
// convert data to cm
x = x * conversionFactorTo_cm;
y = y * conversionFactorTo_cm;
z = z * conversionFactorTo_cm;
// 3-dimensionale Berechnung/Anzeige des Punktes
if(mControlWidget->getCalibCoordDimension() == 0)
{
p2d = mExtrCalibration.getImagePoint(cv::Point3f(x, y, z));
}
// 2-dimensionale Berechnung/Anzeige des Punktes
else
{
QPointF pos = mImageItem->getPosImage(QPointF(x, y), z);
p2d.x = pos.x();
p2d.y = pos.y();
}
tPoint = TrackPoint(Vec2F(p2d.x, p2d.y), 100);
tPoint.setSp(
x,
y,
-mControlWidget->getCalibExtrTrans3() -
z); // fuer den Abstand zur Kamera in z-Richtung wie bei einer Stereokamera
// Neue ID ? ==> letzte Person beendet ==> abspeichern
if(personNr > current_personNr)
{
mPersonStorage.addPerson(tp);
++sz;
current_personNr++;
tp.clear();
}
// TrackPerson leer ? ==> Neue TrackPerson erstellen
if(tp.isEmpty())
{
tp = TrackPerson(personNr, frameNr, tPoint);
tp.setFirstFrame(frameNr);
tp.setHeight(z);
}
// TrackPoint an TrackPerson anhaengen
else
{
tp.setLastFrame(frameNr);
tp.append(tPoint);
}
}
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();
file.close();
SPDLOG_INFO("import {} ({} person(s))", dest, sz);
mTrcFileName =
dest; // fuer Project-File, dann koennte track path direkt mitgeladen werden, wenn er noch da ist
}
else
{
PCritical(this, tr("PeTrack"), tr("Cannot load %1 maybe because of wrong file extension.").arg(dest));
}
lastFile = dest;
}
}
void Petrack::testTracker()
{
static int idx = 0; // index in Fehlerliste, die als letztes angesprungen wurde
QList<int> pers, frame;
mPersonStorage.checkPlausibility(
pers,
frame,
mControlWidget->isTestEqualChecked(),
mControlWidget->isTestVelocityChecked(),
mControlWidget->isTestInsideChecked(),
mControlWidget->isTestLengthChecked());
if(pers.length() <= idx)
{
idx = 0;
}
if(pers.length() > idx)
{
mControlWidget->setTrackShowOnly(Qt::Checked);
mControlWidget->setTrackShowOnlyNr(pers[idx]);
mPlayerWidget->skipToFrame(frame[idx]);
++idx;
}
}
int Petrack::calculateRealTracker()
{
bool autoCorrectOnlyExport = (mReco.getRecoMethod() == reco::RecognitionMethod::MultiColor) && // multicolor
mMultiColorMarkerWidget->autoCorrect->isChecked() &&
mMultiColorMarkerWidget->autoCorrectOnlyExport->isChecked();
int anz = mTrackerReal->calculate(
this,
mTracker,
mImageItem,
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);
mTrackerReal->calcMinMax();
return anz;
}
void Petrack::exportTracker(QString dest) // default = ""
{
try
{
if(!mTracker)
{
return;
}
// if no destination file or folder is given
if(dest.isEmpty())
{
QFileDialog fileDialog(
this,
tr("Select file for exporting tracking paths"),
mLastTrackerExport,
tr("Tracker (*.*);;Petrack tracker (*.trc);;Text (*.txt);;Text for gnuplot(*.dat);;XML Travisto "
"(*.trav);;All supported types (*.txt *.trc *.dat *.trav *.);;All files (*.*)"));
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
fileDialog.setFileMode(QFileDialog::AnyFile);
fileDialog.setDefaultSuffix("");
if(fileDialog.exec())
{
dest = fileDialog.selectedFiles().at(0);
}
if(dest.isEmpty())
{
return;
}
}
QList<int> pers, frame;
bool autoCorrectOnlyExport = (mReco.getRecoMethod() == reco::RecognitionMethod::MultiColor) && // multicolor
mMultiColorMarkerWidget->autoCorrect->isChecked() &&
mMultiColorMarkerWidget->autoCorrectOnlyExport->isChecked();
if(dest.right(4) == ".trc")
{
QTemporaryFile file;
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 ..."));
qApp->processEvents();
trcVersion = 4;
SPDLOG_INFO(
"export tracking data to {} ({} person(s), file version {})",
dest,
mPersonStorage.nbPersons(),
trcVersion);
QTextStream out(&file);
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();
if(QFile::exists(dest))
{
QFile::remove(dest);
}
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);
}
progress.setValue(static_cast<int>(mPersonStorage.nbPersons() + 1));
SPDLOG_INFO("finished.");
mAutosave.resetTrackPersonCounter();
mTrcFileName =
dest; // fuer Project-File, dann koennte track path direkt mitgeladen werden, wenn er// noch da ist
}
else if(dest.right(4) == ".txt")
{
QTemporaryFile file;
if(!file.open())
{
PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2.").arg(dest).arg(file.errorString()));
return;
}
SPDLOG_INFO("export tracking data to {} ({} person(s))...", dest, mPersonStorage.nbPersons());
// 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
{
; // Nothing to be done because z already the right height
}
else // 2D
{
mPersonStorage.recalcHeight(mControlWidget->getCameraAltitude());
}
}
mTrackerReal->calculate(
this,
mTracker,
mImageItem,
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);
QTextStream out(&file);
out << "# PeTrack project: " << QFileInfo(getProFileName()).fileName() << Qt::endl;
out << "# raw trajectory file: " << QFileInfo(getTrackFileName()).fileName() << Qt::endl;
out << "# framerate: " << mAnimation->getFPS() << " fps" << Qt::endl;
if(mControlWidget->isExportCommentChecked())
{
out << "# personal information:" << Qt::endl;
out << "# ID| Comment" << Qt::endl;
// std out
SPDLOG_INFO("Printing comment table...");
SPDLOG_INFO("ID | Comment");
SPDLOG_INFO("----|----------------");
for(int i = 0; i < static_cast<int>(mPersonStorage.nbPersons()); ++i)
{
auto commentSplit = mPersonStorage.at(i).comment().split("\n", Qt::KeepEmptyParts);
out << "#" << qSetFieldWidth(3) << (i + 1) << qSetFieldWidth(0) << "|" << commentSplit.at(0)
<< Qt::endl;
SPDLOG_INFO("{:04d}|{}", (i + 1), commentSplit.at(0));
commentSplit.pop_front();
for(const QString &line : commentSplit)
{
out << "#" << qSetFieldWidth(3) << " " << qSetFieldWidth(0) << "|" << line << Qt::endl;
SPDLOG_INFO(" |{}", line);
}
}
}
mTrackerReal->exportTxt(
out,
mControlWidget->getTrackAlternateHeight(),
mStereoWidget->stereoUseForExport->isChecked(),
mControlWidget->isExportViewDirChecked(),
mControlWidget->isExportAngleOfViewChecked(),
mControlWidget->isExportUseMeterChecked(),
mControlWidget->isExportMarkerIDChecked());
// out << *mTrackerReal;
file.flush();
file.close();
if(QFile::exists(dest))
{
QFile::remove(dest);
}
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);
}
SPDLOG_INFO("finished");
}
else if(dest.right(4) == ".dat")
{
QTemporaryFile fileDat;
if(!fileDat.open()) //! fileDat.open(QIODevice::WriteOnly | QIODevice::Text))
{
PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2.").arg(dest).arg(fileDat.errorString()));
return;
}
// 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())
{
mPersonStorage.recalcHeight(mControlWidget->getCameraAltitude());
}
mTrackerReal->calculate(
this,
mTracker,
mImageItem,
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);
SPDLOG_INFO("export tracking data to {} ({} person(s))...", dest, mPersonStorage.nbPersons());
QTextStream outDat(&fileDat);
mTrackerReal->exportDat(
outDat, mControlWidget->getTrackAlternateHeight(), mStereoWidget->stereoUseForExport->isChecked());
fileDat.flush();
fileDat.close();
if(QFile::exists(dest))
{
QFile::remove(dest);
}
if(!fileDat.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);
}
SPDLOG_INFO("finished");
}
else if(dest.right(5) == ".trav")
{
// 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())
{
mPersonStorage.recalcHeight(mControlWidget->getCameraAltitude());
}
mTrackerReal->calculate(
this,
mTracker,
mImageItem,
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);
QTemporaryFile fileXml;
if(!fileXml.open()) //! fileXml.open(QIODevice::WriteOnly | QIODevice::Text))
{
PCritical(this, tr("PeTrack"), tr("Cannot open %1:\n%2.").arg(dest).arg(fileXml.errorString()));
return;
}
SPDLOG_INFO("export tracking data to {} ({} person(s))...", dest, mPersonStorage.nbPersons());
// already done: mTrackerReal->calculate(mTracker, mImageItem, mControlWidget->getColorPlot(),
// getImageBorderSize(), mControlWidget->trackMissingFrames->checkState());
QTextStream outXml(&fileXml);
outXml << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << Qt::endl;
outXml << "<trajectoriesDataset>" << Qt::endl;
outXml << " <header version=\"1.0\">" << Qt::endl;
outXml << " <roomCaption>PeTrack: " << mAnimation->getFileBase() << "</roomCaption>" << Qt::endl;
outXml << " <roomID>0</roomID>" << Qt::endl;
outXml << " <agents>" << mPersonStorage.nbPersons() << "</agents>" << Qt::endl;
outXml << " <frameRate>" << mAnimation->getFPS() << "</frameRate> <!--per second-->" << Qt::endl;
// outXml << " <timeStep>" << 1000./mAnimation->getFPS() << "</timeStep> <!--
// millisecond-->"
// << endl; inverse von
outXml << " <timeFirstFrame sec=\"" << mAnimation->getFirstFrameSec() << "\" microsec=\""
<< mAnimation->getFirstFrameMicroSec() << "\"/> <!-- " << mAnimation->getTimeString(0) << " -->"
<< Qt::endl;
outXml << " </header>" << Qt::endl << Qt::endl;
mTrackerReal->exportXml(
outXml, mControlWidget->getTrackAlternateHeight(), mStereoWidget->stereoUseForExport->isChecked());
outXml << "</trajectoriesDataset>" << Qt::endl;
fileXml.flush();
fileXml.close();
if(QFile::exists(dest))
{
QFile::remove(dest);
}
if(!fileXml.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);
}
SPDLOG_INFO("finished");
}
else
{ // wenn keine Dateiendung, dann wird trc und txt herausgeschrieben
exportTracker(dest + ".trc");
exportTracker(dest + ".txt");
}
mLastTrackerExport = dest;
}
catch(const std::runtime_error &error)
{
PCritical(this, "Failed to export trajectories", error.what());
}
}
// fuer anschliessende groessenberechnung
void Petrack::playAll()
{
int memPos = mPlayerWidget->getPos();
int progVal = 0;
QProgressDialog progress("Playing whole sequence...", "Abort playing", 0, mAnimation->getNumFrames(), this);
progress.setWindowModality(Qt::WindowModal); // blocks main window
// vorwaertslaufen ab aktueller Stelle und trackOnlineCalc zum tracken nutzen
do
{
progress.setValue(++progVal); // mPlayerWidget->getPos()
qApp->processEvents();
if(progress.wasCanceled())
{
break;
}
} while(mPlayerWidget->frameForward());
mPlayerWidget->skipToFrame(memPos);
}
/**
* @brief Activates tracking and reco; calcs through the video (in both ways)
*
* This method activates tracking and reco and plays the whole video (from current
* frame on) till the end. Then, if mAutoBackTrack is set, it jumps back to the
* largest first frame, i.e. the last time a new person was added/recognized, and
* tracks backwards till the beginning of the video.
*
* The old settings for tracking and reco will be restored. No interaction with the
* main window is possible for the time of tracking.
*/
void Petrack::trackAll()
{
int memPos = mPlayerWidget->getPos();
int progVal = 0;
bool memCheckState = mControlWidget->isOnlineTrackingChecked();
bool memRecoState = mControlWidget->isPerformRecognitionChecked();
mControlWidget->setOnlineTrackingChecked(true);
mControlWidget->setPerformRecognitionChecked(true);
QProgressDialog progress(
"Tracking pedestrians through all frames...",
"Abort tracking",
0,
2 * mAnimation->getNumFrames() - memPos,
this);
progress.setWindowModality(Qt::WindowModal); // blocks main window
// vorwaertslaufen ab aktueller Stelle und trackOnlineCalc zum tracken nutzen
do
{
progress.setValue(++progVal); // mPlayerWidget->getPos()
qApp->processEvents();
if(progress.wasCanceled())
{
break;
}
} while(mPlayerWidget->frameForward());
if(mAutoBackTrack)
{
// zuruecksprinegn an die stelle, wo der letzte trackPath nicht vollstaendig
// etwas spaeter, da erste punkte in reco path meist nur ellipse ohne markererkennung
mControlWidget->setOnlineTrackingChecked(false);
mPlayerWidget->skipToFrame(mPersonStorage.largestFirstFrame() + 5);
mControlWidget->setOnlineTrackingChecked(true);
// progVal = 2*mAnimation->getNumFrames()-memPos-mPlayerWidget->getPos();
progVal += mAnimation->getNumFrames() - mPlayerWidget->getPos();
progress.setValue(progVal); // mPlayerWidget->getPos()
// recognition abstellen, bis an die stelle, wo trackAll begann
// UEBERPRUEFEN, OB TRACKPATH NICHT RECOGNITION PUNKTE UEBERSCHREIBT!!!!!!!!!!
// repeate und repaetQual koennte temporaer umgestellt werden
mControlWidget->setPerformRecognitionChecked(false);
// rueckwaertslaufen
do
{
if(progVal + 1 < 2 * mAnimation->getNumFrames() - memPos)
{
progress.setValue(++progVal); // mPlayerWidget->getPos()
}
qApp->processEvents();
if(progress.wasCanceled())
{
break;
}
if(mPlayerWidget->getPos() == memPos + 1)
{
mControlWidget->setPerformRecognitionChecked(true);
}
} while(mPlayerWidget->frameBackward());
// bei abbruch koennen es auch mPlayerWidget->getPos() frames sein, die bisher geschrieben wurden
progress.setValue(2 * mAnimation->getNumFrames() - memPos);
}
if(mAutoTrackOptimizeColor)
{
mPersonStorage.optimizeColor();
}
mControlWidget->setPerformRecognitionChecked(memRecoState);
mControlWidget->setOnlineTrackingChecked(false);
mPlayerWidget->skipToFrame(memPos);
mControlWidget->setOnlineTrackingChecked(memCheckState);
}
// default: (QPointF *pos=NULL, int pers=-1, int frame=-1);
int Petrack::winSize(QPointF *pos, int pers, int frame, int level)
{
// default of mControlWidget->trackRegionScale->value() is 16, so that
// a factor of 1.6 of the headsize is used
if(level == -1)
{
level = mControlWidget->getTrackRegionLevels();
}
return (int) ((getHeadSize(pos, pers, frame) / pow(2., level)) * (mControlWidget->getTrackRegionScale() / 10.));
}
void Petrack::updateImage(bool imageChanged) // default = false (only true for new animation frame)
{
mCodeMarkerItem->resetSavedMarkers();
static int lastRecoFrame = -10000;
static bool borderChangedForTracking = false;
// need semaphore to guarrantee that updateImage only called once
// updateValue of control automatically calls updateImage!!!
static QSemaphore semaphore(1);
if(!mImg.empty() && mImage && semaphore.tryAcquire())
{
int frameNum = mAnimation->getCurrentFrameNum();
setStatusTime();
updateShowFPS();
mImgFiltered = mImg;
// have to store because evaluation sets the filter parameter to unchanged
bool brightContrastChanged = mBrightContrastFilter.changed();
bool swapChanged = mSwapFilter.changed();
bool borderChanged = mBorderFilter.changed();
bool calibChanged = mCalibFilter->changed();
// speicherverwaltung wird komplett von filtern ueberneommen
// Filter anwenden, Reihenfolge wichtig - Rechenintensive moeglichst frueh
// fkt so nur mit kopierenden filtern
if(imageChanged || swapChanged)
{
mImgFiltered = mSwapFilter.apply(mImgFiltered);
}
else
{
mImgFiltered = mSwapFilter.getLastResult();
}
if(imageChanged || swapChanged || brightContrastChanged)
{
mImgFiltered = mBrightContrastFilter.apply(mImgFiltered);
}
else
{
mImgFiltered = mBrightContrastFilter.getLastResult();
}
if(imageChanged || swapChanged || brightContrastChanged || borderChanged)
{
mImgFiltered = mBorderFilter.apply(mImgFiltered); // mIplImg
}
else
{
mImgFiltered = mBorderFilter.getLastResult();
}
if(borderChanged)
{
updateControlImage(mImgFiltered);
}
#ifndef STEREO_DISABLED
if(imageChanged || swapChanged || brightContrastChanged || borderChanged || calibChanged)
{
if(mStereoContext)
mStereoContext->init(mImgFiltered);
}
#endif
if(imageChanged || swapChanged || brightContrastChanged || borderChanged || calibChanged)
{
mImgFiltered = mCalibFilter->apply(mImgFiltered);
}
else
{
mImgFiltered = mCalibFilter->getLastResult();
}
if(brightContrastChanged || swapChanged || borderChanged || calibChanged)
{
// abfrage hinzugenommen, damit beim laden von .pet bg-file angegeben werden kann fuer mehrere versuche und
// beim nachladen von versuch nicht bg geloescht wird
if(mBackgroundFilter.getFilename() != "")
{
SPDLOG_WARN("no background reset, because of explicit loaded background image!");
}
else
{
mBackgroundFilter
.reset(); // alle gesammelten hintergrundinfos werden verworfen und bg.changed auf true gesetzt
}
}
if(imageChanged || mBackgroundFilter.changed())
{
mImgFiltered = mBackgroundFilter.apply(mImgFiltered);
}
else
{
mImgFiltered = mBackgroundFilter.getLastResult();
}
// delete track list, if intrinsic param have changed
if(calibChanged && mPersonStorage.nbPersons() > 0) // mCalibFilter.getEnabled() &&
{
// Evtl. nicht Tracker loeschen sondern entsprechend der neuen Calibration verschieben?!?!?
mPersonStorage.clear();
mTracker->reset();
if(!isLoading())
{
SPDLOG_WARN("deleted all tracking pathes because intrinsic parameters have changed.");
}
}
else
{
#ifndef STEREO_DISABLED
// calculate position in 3D space and height of person for "old" trackPoints, if checked "even"
if(mStereoContext && mStereoWidget->stereoUseForHeightEver->isChecked() &&
mStereoWidget->stereoUseForHeight->isChecked())
{
// buildt disparity picture if it should be used for height detection
mStereoContext->getDisparity();
mPersonStorage.calcPosition(frameNum);
}
#endif
}
if(borderChanged)
{
borderChangedForTracking = true;
}
// tracking vor recognition, da dann neu gefundene punkte mit getrackten bereits ueberprueft werden koennen
if((trackChanged() || imageChanged) && (mControlWidget->isOnlineTrackingChecked())) // borderChanged ???
{
// Rect for tracking area
QRect roi(
myRound(mTrackingRoiItem->rect().x() + getImageBorderSize()),
myRound(mTrackingRoiItem->rect().y() + getImageBorderSize()),
myRound(mTrackingRoiItem->rect().width()),
myRound(mTrackingRoiItem->rect().height()));
if(borderChangedForTracking)
{
cv::Size size;
size.width = mImgFiltered.cols;
size.height = mImgFiltered.rows;
mTracker->resize(size);
mTrackingRoiItem->restoreSize();
}
#ifndef STEREO_DISABLED
// buildt disparity picture if it should be used for height detection
if(mStereoContext && mStereoWidget->stereoUseForHeight->isChecked())
mStereoContext->getDisparity();
#endif
cv::Rect rect;
getRoi(mImgFiltered, roi, rect);
// Ignore all tracking points outside of rect
// if (mPrevIplImgFiltered) // wenn ein vorheriges bild vorliegt
// mPrevIplImgFiltered == NULL zeigt an, dass neue bildfolge && mPrevFrame == -1 ebenso
// winSize(), wurde mal uebergeben
int anz = mTracker->track(
mImgFiltered,
rect,
frameNum,
mControlWidget->isTrackRepeatChecked(),
mControlWidget->getTrackRepeatQual(),
getImageBorderSize(),
mReco.getRecoMethod(),
mControlWidget->getTrackRegionLevels(),
getPedestriansToTrack());
mControlWidget->setTrackNumberNow(QString("%1").arg(anz));
mTrackChanged = false;
borderChangedForTracking = false;
}
else
{
mControlWidget->setTrackNumberNow(QString("0"));
}
// hier muesste fuer ameisen etc allgemeinABC.getPosList(...)
if(((((lastRecoFrame + mControlWidget->getRecoStep()) <= frameNum) ||
((lastRecoFrame - mControlWidget->getRecoStep()) >= frameNum)) &&
imageChanged) ||
mAnimation->isCameraLiveStream() || swapChanged || brightContrastChanged || borderChanged || calibChanged ||
recognitionChanged())
{
#ifndef STEREO_DISABLED
// buildt disparity picture if it should be used for height detection or recognition
if(mStereoContext &&
(mStereoWidget->stereoUseForHeight->isChecked() || mStereoWidget->stereoUseForReco->isChecked()))
mStereoContext->getDisparity(); // wird nicht neu berechnet, wenn vor tracking schon berechnet wurde
#endif
if(borderChanged)
{
mRecognitionRoiItem->restoreSize();
}
if(mControlWidget->isPerformRecognitionChecked())
{
QRect rect(
myRound(mRecognitionRoiItem->rect().x() + getImageBorderSize()),
myRound(mRecognitionRoiItem->rect().y() + getImageBorderSize()),
myRound(mRecognitionRoiItem->rect().width()),
myRound(mRecognitionRoiItem->rect().height()));
QList<TrackPoint> persList;
auto recoMethod = mReco.getRecoMethod();
if((recoMethod == reco::RecognitionMethod::Casern) || (recoMethod == reco::RecognitionMethod::Hermes) ||
(recoMethod == reco::RecognitionMethod::Color) || (recoMethod == reco::RecognitionMethod::Japan) ||
(recoMethod == reco::RecognitionMethod::MultiColor) ||
(recoMethod == reco::RecognitionMethod::Code)) // else
{
persList = mReco.getMarkerPos(
mImgFiltered,
rect,
mControlWidget,
getImageBorderSize(),
getBackgroundFilter(),
mControlWidget->getIntrinsicCameraParams());
}
#ifndef STEREO_DISABLED
if(mStereoContext && mStereoWidget->stereoUseForReco->isChecked())
{
PersonList pl;
pl.calcPersonPos(mImgFiltered, rect, &persList, mStereoContext, getBackgroundFilter(), markerLess);
}
#endif
mPersonStorage.addPoints(persList, frameNum, mReco.getRecoMethod());
// folgendes lieber im Anschluss, ggf beim exportieren oder statt test direkt del:
if(mStereoContext && mStereoWidget->stereoUseForReco->isChecked())
{
mPersonStorage.purge(frameNum); // bereinigen wenn weniger als 0.2 recognition und nur getrackt
}
mControlWidget->setRecoNumberNow(QString("%1").arg(persList.size()));
mRecognitionChanged = false;
if(false) // hier muss Abfage hin ob kasernen marker genutzt wird
{
mControlWidget->getColorPlot()
->replot(); // oder nur wenn tab offen oder wenn sich mtracker geaendert hat???
}
}
else
{
mControlWidget->setRecoNumberNow(QString("0"));
}
lastRecoFrame = frameNum;
}
else
{
mControlWidget->setRecoNumberNow(QString("0"));
}
mControlWidget->setTrackNumberAll(
QString("%1").arg(mPersonStorage.nbPersons())); // kann sich durch reco und tracker aendern
mControlWidget->setTrackShowOnlyNrMaximum(
static_cast<int>(MAX(mPersonStorage.nbPersons(), 1))); // kann sich durch reco und tracker aendern
mControlWidget->setTrackNumberVisible(
QString("%1").arg(mPersonStorage.visible(frameNum))); // kann sich durch reco und tracker aendern
// in anzuzeigendes Bild kopieren
// erst hier wird die bildgroesse von mimage an filteredimg mit border angepasst
copyToQImage(*mImage, mImgFiltered);
if(borderChanged)
{
mImageItem->setImage(mImage);
}
else
{
getScene()->update(); // repaint();
qApp->processEvents();
// update pixel color (because image pixel moves)
setStatusColor();
}
#ifdef QWT
mControlWidget->getAnalysePlot()->setActFrame(frameNum);
if(mControlWidget->isAnaMarkActChecked())
{
mControlWidget->getAnalysePlot()->replot();
}
#endif
semaphore.release();
}
}
void Petrack::updateImage(const cv::Mat &img)
{
mImg = img;
updateImage(true);
}
void Petrack::updateSequence()
{
QImage *oldImage = mImage;
QSize size = mAnimation->getSize();
size.setWidth(size.width() + 2 * getImageBorderSize()); // border is inside the mImage!
size.setHeight(size.height() + 2 * getImageBorderSize());
mImage = new QImage(size, QImage::Format_RGB888); // 32); //wird in updateImage gemacht
// set roi for recognition if image size changes or roi is zero
// in oldImage steckt border drin, mIplImg->height zeigt noch auf altes ursprungsbild
// mRecognitionRoiItem->rect().width() != 0 && oldImage == NULL wenn projektdatei eingelesen wird!!!!!
if((mRecognitionRoiItem->rect().width() == 0) || // default while initialization, after that >= MIN_SIZE
(oldImage && ((oldImage->width() != mImage->width()) || (oldImage->height() != mImage->height()))))
{
mRecognitionRoiItem->setRect(-getImageBorderSize(), -getImageBorderSize(), mImage->width(), mImage->height());
}
if((mTrackingRoiItem->rect().width() == 0) ||
(oldImage && ((oldImage->width() != mImage->width()) || (oldImage->height() != mImage->height()))))
{
mTrackingRoiItem->setRect(-getImageBorderSize(), -getImageBorderSize(), mImage->width(), mImage->height());
}
cv::Size size2;
size2.width = mTrackingRoiItem->rect().width();
size2.height = mTrackingRoiItem->rect().height();
mTracker->init(size2);
mPlayerWidget->setAnim(mAnimation);
mPlayerWidget->skipToFrame(0);
mImageItem->setImage(mImage); // wird in updateImage gemacht
delete oldImage;
mSaveSeqVidAct->setEnabled(true);
mSaveSeqVidViewAct->setEnabled(true);
mSaveSeqImgAct->setEnabled(true);
mSaveSeqViewAct->setEnabled(true);
mSaveImageAct->setEnabled(true);
mSaveViewAct->setEnabled(true);
mPrintAct->setEnabled(true);
mResetSettingsAct->setEnabled(true);
}
/**
* @brief Gets cm per pixel. Only recalculates when calculating head size.
* @return cm per pixel
*/
double Petrack::getCmPerPixel() const
{
return mCmPerPixel;
}
/**
* @brief Sets the size of the circle of the average head circumference in pixel.
*
* Assumption for default calculation: <br>
* 21cm avg head length <br>
* default height of person according to mapDefaultHeigt <br>
*
* Default case recalculates mCmPerPixel
*
* If headsize get recalulated also mCmPerPixel will be calculated!
* @see Petrack::getCmPerPixel
* @param hS new headsize, if hS==-1 mHeadSize will be calculated instead of set
*/
void Petrack::setHeadSize(double hS)
{
if(hS == -1)
{
mCmPerPixel = getImageItem()->getCmPerPixel();
// debout << mCmPerPixel <<endl;
mHeadSize = (HEAD_SIZE * mControlWidget->getCameraAltitude() /
(mControlWidget->getCameraAltitude() - mControlWidget->getDefaultHeight())) /
mCmPerPixel;
}
else
{
mHeadSize = hS;
}
}
// gibt Kopfgroesse zurueck
// default: (QPointF *pos=NULL, int pers=-1, int frame=-1)
double Petrack::getHeadSize(QPointF *pos, int pers, int frame)
{
double z, h;
if((pers >= 0) && (pers < static_cast<int>(mPersonStorage.nbPersons())) &&
mPersonStorage.at(pers).trackPointExist(frame))
{
if(mControlWidget->getCalibCoordDimension() == 0)
{
int diff;
cv::Point3f p3d = getExtrCalibration()->get3DPoint(
cv::Point2f(
mPersonStorage.at(pers).trackPointAt(frame).x(), mPersonStorage.at(pers).trackPointAt(frame).y()),
mControlWidget->getDefaultHeight());
cv::Point2f p3d_x1 =
getExtrCalibration()->getImagePoint(cv::Point3f(p3d.x + HEAD_SIZE * 0.5, p3d.y, p3d.z));
cv::Point2f p3d_x2 =
getExtrCalibration()->getImagePoint(cv::Point3f(p3d.x - HEAD_SIZE * 0.5, p3d.y, p3d.z));
cv::Point2f p3d_y1 =
getExtrCalibration()->getImagePoint(cv::Point3f(p3d.x, p3d.y + HEAD_SIZE * 0.5, p3d.z));
cv::Point2f p3d_y2 =
getExtrCalibration()->getImagePoint(cv::Point3f(p3d.x, p3d.y - HEAD_SIZE * 0.5, p3d.z));
diff = (int) std::max(
sqrt(pow(p3d_x2.x - p3d_x1.x, 2) + pow(p3d_x2.y - p3d_x1.y, 2)),
sqrt(pow(p3d_y2.x - p3d_y1.x, 2) + pow(p3d_y2.y - p3d_y1.y, 2)));
return diff; // < 8 ? 8 : diff;
}
else
{
z = mPersonStorage.at(pers).trackPointAt(frame).sp().z();
h = mPersonStorage.at(pers).height();
if(z > 0)
{
return (HEAD_SIZE * mControlWidget->getCameraAltitude() / z) / getImageItem()->getCmPerPixel();
}
else if(h > MIN_HEIGHT)
{
return (HEAD_SIZE * mControlWidget->getCameraAltitude() / (mControlWidget->getCameraAltitude() - h)) /
getImageItem()->getCmPerPixel();
}
else
{
return mHeadSize;
}
}
}
if(pos != nullptr)
{
return mHeadSize; // muss noch aus density map gelesen werden!!!
}
else //(pos == NULL) && (pers == -1)
{
return mHeadSize;
}
}
void Petrack::setProFileName(const QString &fileName)
{
// don't change project Name to an autosave
if(mAutosave.isAutosave(fileName) || fileName == mProFileName)
{
return;
}
// Change project => delete old autosave
mAutosave.deleteAutosave();
// NOTE: Use only the global variant in future?
// global one in helper.h because it is needed to use getFileList and shouldn't depend on Petrack
proFileName = fileName;
mProFileName = fileName;
updateWindowTitle();
}
/**
* @brief Return the user's selection of pedestrians/trajectories
*
* Only people selected via "show only people" (single person) or "show only
* people list"(multiple persons) are going to be selected.
*
* @return All user selected pedestrian (empty for all pedestrians)
*/
QSet<size_t> Petrack::getPedestrianUserSelection()
{
if(mControlWidget->isTrackShowOnlyChecked())
{
QSet<size_t> onlyVisible;
// subtraction needed as in UI ID start at 1 and internally at 0
onlyVisible.insert(mControlWidget->getTrackShowOnlyNr() - 1);
return onlyVisible;
}
if(mControlWidget->isTrackShowOnlyListChecked())
{
auto enteredIDs = util::splitStringToInt(mControlWidget->trackShowOnlyNrList()->text());
if(enteredIDs.has_value())
{
QSet<size_t> selectedIDs;
for(auto id : enteredIDs.value())
{
// subtraction needed as in UI ID start at 1 and internally at 0
selectedIDs.insert(id - 1);
}
mControlWidget->trackShowOnlyNrList()->setStyleSheet("");
return selectedIDs;
}
else
{
mControlWidget->trackShowOnlyNrList()->setStyleSheet("border: 1px solid red");
}
}
return QSet<size_t>();
}
/**
* @brief Splits the given text to get a set of integers.
*
* The given text will be split on ',' and then each element will be checked if it is a range. Ranges are marked with
* '-' as divider. Only positive integer values are allowed.
*
* Examples:
* '1,5,6' -> (1, 5, 6)
* '1-5' -> (1, 2, 3, 4, 5)
*
* @param input given text
* @return Set of int in the given text
*/
std::optional<QSet<int>> util::splitStringToInt(const QString &input)
{
QSet<int> ids;
for(const auto &id : input.split(",", Qt::SkipEmptyParts))
{
bool ok = false;
int enteredID = id.toInt(&ok);
if(ok && enteredID >= 0) // parse single values
{
ids.insert(enteredID);
}
else // error or IDs range (e.g. 1-3, 6-10, etc.)
{
if(id.startsWith("-"))
{
ok = false;
}
auto range = id.split("-");
int first = range[0].toInt(&ok);
ok = ok && range.size() == 2 && !range[1].isEmpty();
if(ok)
{
int last = range[1].toInt(&ok);
if(ok)
{
if(first > last)
{
std::swap(first, last);
}
for(int i = first; i <= last; i++)
{
ids.insert(i);
}
}
}
}
if(!ok)
{
return std::nullopt;
}
}
return ids;
}
/**
* @brief Checks which pedestrians/trajectories are selected for evaluation.
*
* If "only for selected" is checked, then only selected people (@see
* Petrack::getPedestrianUserSelection()) are going to be tracked, all people
* otherwise.
*
* @return all trajectories which should be evaluated; empty when all should be evaluated
*/
QSet<size_t> Petrack::getPedestriansToTrack()
{
if(mControlWidget->isTrackOnlySelectedChecked())
{
return getPedestrianUserSelection();
}
return QSet<size_t>();
}
void Petrack::addManualTrackPointOnlyVisible(const QPointF &pos)
{
int pers = addOrMoveManualTrackPoint(pos) + 1;
if(pers == 0)
{
pers = static_cast<int>(mPersonStorage.nbPersons()) + 1;
}
mControlWidget->setTrackShowOnlyNr(pers);
mControlWidget->setTrackShowOnly(Qt::Checked);
}
void Petrack::updateControlWidget()
{
mControlWidget->setTrackNumberAll(QString("%1").arg(mPersonStorage.nbPersons()));
mControlWidget->setTrackShowOnlyNrMaximum(static_cast<int>(MAX(mPersonStorage.nbPersons(), 1)));
mControlWidget->setTrackNumberVisible(QString("%1").arg(mPersonStorage.visible(mAnimation->getCurrentFrameNum())));
}
void Petrack::splitTrackPerson(QPointF pos)
{
mPersonStorage.splitPersonAt((Vec2F) pos, mAnimation->getCurrentFrameNum(), getPedestrianUserSelection());
updateControlWidget();
}
/**
* @brief Lets the user add or move a TrackPoint manually
*
* There is an check inside addPoint which inhibits adding a point,
* if only selected trajectories are visualized, since one wouldn't
* see the newly added TrackPoint.
*
* @param pos pixel position of mouse on image
* @return index of person whose point was moved; -1 if failed or new trajectory is started
*/
int Petrack::addOrMoveManualTrackPoint(const QPointF &pos)
{
int pers = -1;
TrackPoint tP(Vec2F{pos}, 110); // 110 is higher than 100 (max. quality) and gets clamped to 100 after insertion
// allows replacemet of every point (check for better quality always passes)
mPersonStorage.addPoint(
tP, mAnimation->getCurrentFrameNum(), getPedestrianUserSelection(), mReco.getRecoMethod(), &pers);
updateControlWidget();
return pers;
}
// direction zeigt an, ob bis zum aktuellen (-1), ab dem aktuellen (1) oder ganzer trackpath (0)
// loeschen von Trackpoints einer Trajektorie
void Petrack::deleteTrackPoint(QPointF pos, int direction) // const QPoint &pos
{
mPersonStorage.delPoint((Vec2F) pos, direction, mAnimation->getCurrentFrameNum(), getPedestrianUserSelection());
updateControlWidget();
}
void Petrack::editTrackPersonComment(QPointF pos)
{
mPersonStorage.editTrackPersonComment((Vec2F) pos, mAnimation->getCurrentFrameNum(), getPedestrianUserSelection());
updateControlWidget();
}
void Petrack::setTrackPersonHeight(QPointF pos)
{
mPersonStorage.setTrackPersonHeight((Vec2F) pos, mAnimation->getCurrentFrameNum(), getPedestrianUserSelection());
updateControlWidget();
}
void Petrack::resetTrackPersonHeight(QPointF pos)
{
mPersonStorage.resetTrackPersonHeight((Vec2F) pos, mAnimation->getCurrentFrameNum(), getPedestrianUserSelection());
updateControlWidget();
}
/**
* @brief Delete the following, previous or whole trajectory of **all** trajectories
* @param direction previous, following or whole
*/
void Petrack::deleteTrackPointAll(PersonStorage::Direction direction) // const QPoint &pos
{
mPersonStorage.delPointAll(direction, mAnimation->getCurrentFrameNum());
updateControlWidget();
}
void Petrack::deleteTrackPointROI()
{
mPersonStorage.delPointROI();
updateControlWidget();
mScene->update();
}
void Petrack::deleteTrackPointInsideROI()
{
getPersonStorage().delPointInsideROI();
updateControlWidget();
mScene->update();
}
void Petrack::moveTrackPoint(QPointF pos)
{
mManualTrackPointMover.moveTrackPoint(pos, mPersonStorage);
mScene->update();
}
void Petrack::selectPersonForMoveTrackPoint(QPointF pos)
{
FrameRange range;
range.before = mControlWidget->getTrackShowBefore();
range.after = mControlWidget->getTrackShowAfter();
range.current = mPlayerWidget->getPos();
auto successfullySelected =
mManualTrackPointMover.selectTrackPoint(pos, mPersonStorage, getPedestrianUserSelection(), range);
if(successfullySelected)
{
setCursor(QCursor{Qt::CursorShape::DragMoveCursor});
}
}
void Petrack::releaseTrackPoint()
{
mManualTrackPointMover.setTrackPoint();
mAutosave.trackPersonModified();
setCursor(QCursor{});
}
void Petrack::scrollShowOnly(int delta)
{
if(delta < 0)
{
mControlWidget->setTrackShowOnlyNr(mControlWidget->getTrackShowOnlyNr() - 1);
}
else
{
mControlWidget->setTrackShowOnlyNr(mControlWidget->getTrackShowOnlyNr() + 1);
}
}
void Petrack::updateSourceInOutFrames()
{
mPlayerWidget->setFrameInNum(mAnimation->getSourceInFrameNum());
mPlayerWidget->setFrameOutNum(mAnimation->getSourceOutFrameNum());
}
// delta gibt menge an Umdrehungen und richtung an
void Petrack::skipToFrameWheel(int delta)
{
mPlayerWidget->skipToFrame(mPlayerWidget->getPos() + delta);
}
void Petrack::skipToFrameFromTrajectory(QPointF pos)
{
auto peds = getPedestrianUserSelection();
const auto before = mControlWidget->getTrackShowBefore();
const auto after = mControlWidget->getTrackShowAfter();
const auto currFrame = mPlayerWidget->getPos();
FrameRange frameRange{before, after, currFrame};
auto res = mPersonStorage.getProximalPersons(pos, peds, frameRange);
if(res.size() == 1)
{
mPlayerWidget->skipToFrame(res.front().frame);
}
else if(res.size() > 1)
{
PWarning(
this,
tr("Too many trajectories"),
tr("PeTrack can't determine which point you meant. Try selecting fewer trajectories first."));
}
}
void Petrack::setGitInformation(
const std::string &gitCommitID,
const std::string &gitCommitDate,
const std::string &gitCommitBranch)
{
mGitCommitID = QString::fromStdString(gitCommitID);
mGitCommitDate = QString::fromStdString(gitCommitDate);
mGitCommitBranch = QString::fromStdString(gitCommitBranch);
}
void Petrack::setCompileInformation(
const std::string &compileOS,
const std::string &compileTimeStamp,
const std::string &compilerID,
const std::string &compilerVersion)
{
mCompileOS = QString::fromStdString(compileOS);
mCompileDate = QString::fromStdString(compileTimeStamp);
mCompilerID = QString::fromStdString(compilerID);
mCompilerVersion = QString::fromStdString(compilerVersion);
}
#include "moc_petrack.cpp"