#include "mainwindow.h" #include "annotationxml.h" #include "ui_mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "changemask.h" #include "changepacket.h" #include "tinyxml2.h" #include "classoptions.h" #include "requestfromserver.h" #include "setids.h" // Hilfstexte QString toolTitel[] = { "Objekte Verstecken", "Verschieben", "Neues Objekt", "Kopieren", "Einfügen", "Löschen", "Zerschneiden", "Vergrößern", "Allgemeine Information" }; QString toolBeschreibung[] {"Klicken sie mit der Linken Maustaste auf ein Objekt um es zu verstecken, oder wieder sichtbar zu machen.\nVersteckte Objekte werden von anderen Werkzeugen ignoriert.", "Halten sie die linke Maustaste gedrückt, um die Ecke eines Objektes zu verschieben, wenn sich die Maus in der Nähe befindet.\nBefindet sich keine Ecke in der Nähe der Maus, so wird eine neue Ecke erzeugt.", "Klicken sie mit der Linken Maustaste an die Stellen, wo die Ecken des neuen Objektes sind.\nUm den Vorgang abzuschließen klicken sie erneut auf die erste Ecke.\nMit Rechtsklick kann die vorherige Ecke wieder entfernt werden.", "Klicken sie mit der linken Maustaste auf das Objekt, welches sie kopieren möchten.", "Klicken sie mit der linken Maustaste auf ein Objekt, um die ID und die Klasse des kopierten Objektes auf dieses zu übertragen.\nKlicken sie an eine freie Stelle um eine Kopie des kopierten Objektes einzufügen.\nHalten sie die rechte Maustaste gedrückt, um das kopierte Objekt zu drehen.", "Halten sie die linke Maustaste gedrückt, um einen Bereich festzulegen, in dem alle Eckpunkte gelöscht werden sollen.", "Klicken sie auf zwei Eckpunkte eines Objektes, um es in zwei verschiedene Objekte zu zerschneiden.", "Halte die linke Maustaste gedrückt um ein Gebiet zu makieren, an welches herangezoomt werden soll.", "Klicken sie auf Öffnen und wählen sie einen Ordner aus, um eine annotierte Sequenz zu laden oder eine neue Sequenz zu erstellen."}; // Inhalt der MainWindow Klasse //------------------------------ MainWindow::MainWindow( QApplication *app, QWidget *parent ) : QMainWindow(parent), app( app ), ui(new Ui::MainWindow), contextMenu( 0 ), seq( 0 ), m( 0 ), serverAddress( "tcp://ob:55556" ) { ui->setupUi(this); setWindowTitle( "Annotation GUI v. 1.0" ); status = new QLabel( "fertig" ); ui->statusBar->layout()->setSizeConstraint(QLayout::SetMinimumSize); status->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ); ui->statusBar->layout()->addWidget( status ); ui->statusBar->addWidget( status ); workModel = new ArbeitsModel( this ); ArbeitsController::initStaticController( workModel ); v = new ArbeitsView( workModel, ui->viewWidget ); v->setController( ArbeitsController::getController( MOVE ) ); workModel->addView( v ); ui->toolTitel->setText( toolTitel[ USERMODE_COUNT ] ); ui->toolBeschreibung->setText( toolBeschreibung[ USERMODE_COUNT ] ); ui->framesTree->setContextMenuPolicy( Qt::CustomContextMenu ); ui->viewWidget->setContextMenuPolicy( Qt::CustomContextMenu ); QList btnList = ui->buttons->findChildren(); for( auto i = btnList.begin(); i != btnList.end(); i++ ) { (*i)->setIconSize( (*i)->size() ); } } MainWindow::~MainWindow() { if( seq ) { QMessageBox::StandardButton reply = QMessageBox::question(this, "Achtung", "Möchten sie die Annotationen speichern?", QMessageBox::Yes|QMessageBox::No); if (reply == QMessageBox::Yes) { seq->saveToPath( status ); } seq->refRelease(); } delete status; delete ui; delete m; delete contextMenu; } // Erstellt den Navigationsbaum void MainWindow::setupFrameTree() { ui->framesTree->setUpdatesEnabled(false); if( !seq ) return; if( !m ) { m = new FrameTreeModel(); seq->refNew(); m->setSequenz( seq ); ui->framesTree->setModel( m ); ui->framesTree->setSelectionMode( ui->framesTree->MultiSelection ); } ui->framesTree->collapseAll(); setFrameTreeSelection(); repaintFrameTree(); ui->framesTree->setUpdatesEnabled(true); } // Aktualisiert die Ausgefählten Objekte im Navigationsbaum void MainWindow::setFrameTreeSelection() { ui->framesTree->expand( m->getCameraSelectionIndex() ); ui->framesTree->expand( m->getFrameSelectionIndex() ); ui->framesTree->selectionModel()->select( m->getCameraSelectionIndex(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); ui->framesTree->selectionModel()->select( m->getFrameSelectionIndex(), QItemSelectionModel::Select | QItemSelectionModel::Rows ); QList indices = m->getSelectedPackages(); for( auto index = indices.begin(); index != indices.end(); index++ ) ui->framesTree->selectionModel()->select( *index, QItemSelectionModel::Select | QItemSelectionModel::Rows ); ui->framesTree->scrollTo(m->getFrameSelectionIndex()); ui->framesTree->repaint(); } // Zeichnet den Navigazionsbaum neu void MainWindow::repaintFrameTree() { m->update(); ui->framesTree->repaint(); } // Setzt den Controller der Arbeitsfläche void MainWindow::setMode( UserMode m ) { if( workModel->getMode() == m ) ArbeitsController::getController( m )->restartController(); workModel->setMode( m ); v->setController( ArbeitsController::getController( m ) ); ui->toolTitel->setText( toolTitel[ workModel->getMode() ] ); ui->toolBeschreibung->setText( toolBeschreibung[ workModel->getMode() ] ); } // Gibt die geladene Sequenz zurück Sequenz *MainWindow::getSequenz() { return seq; } // Wählt alle Buttons für die Werkzeuge ab void MainWindow::unselectButttons() { QList btnList = ui->buttons->findChildren(); for( auto i = btnList.begin(); i != btnList.end(); i++ ) { (*i)->setChecked( false ); } } // Fragt nach Vorabannotationen beim Server void MainWindow::requestFromServer() { if( serverAddress != "" ) { Frame *f = seq->getFrame(); RequestFromServer rfs( serverAddress, f ); rfs.exec(); f->setObjects( rfs.getObjects() ); f->applyMask(*((Kamera*)f->getParent())->getMask()); if( !seq->hasAnnotatedObjects() ) { int id = 0; for( auto obj = f->getObjects().begin(); obj != f->getObjects().end(); obj++ ) { (*obj)->setId( QString::number( id ) ); seq->addObjectName( QString::number( id ) ); id++; } } } else seq->getFrame()->setObjects( std::vector< std::vector< cv::Point > >()); setupFrameTree(); v->repaint(); } // Öffnet eine Sequenz void MainWindow::on_actionOpen_triggered() { QString path = QFileDialog::getExistingDirectory(this, tr("Open Image Directory"), "/studi-tmp/kstrohm", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if( path == "" ) return; status->setText( "Lade Annotation ..." ); AnnotationLoader loader( path, status ); if( seq ) seq->refRelease(); seq = loader.getSequenz(); if( !seq ) { QMessageBox::StandardButton reply = QMessageBox::question(this, "Achtung", "In dem ausgewählten Ordner wurde keine Annotierte Sequenz gefunden. Möchten sie eine neue Sequenz erstellen?", QMessageBox::Yes|QMessageBox::No); if (reply == QMessageBox::Yes) { AnnotationCreator creator( path, status ); seq = creator.getSequenz(); if( !seq ) { QMessageBox::critical( this, "Fehler", "Es ist ein Fehler beim erstellen der Sequenz aufgetreten." ); } } } status->setText( "fertig" ); if( seq ) { ui->framesTree->setModel( 0 ); delete m; m = 0; Frame *f = seq->getFrame(); workModel->setFrame( f ); workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() ); workModel->notifyViews(); if( f->isNotAnnotated() ) requestFromServer(); else setupFrameTree(); setMode( MOVE ); } } // Wechselt zum nächsten Bild void MainWindow::on_actionNext_triggered() { ui->framesTree->selectionModel()->clear(); int oldCam = seq->getSelectedCamera(); QModelIndex camIndex = m->getCameraSelectionIndex(); ui->framesTree->collapse( m->getFrameSelectionIndex() ); seq->nextFrame(); if(oldCam != seq->getSelectedCamera()) ui->framesTree->collapse( camIndex ); Frame *f = seq->getFrame(); workModel->setFrame( f ); workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() ); workModel->notifyViews(); if( f->isNotAnnotated() ) requestFromServer(); else setFrameTreeSelection(); } // Wechselt zum vorherigen Bild void MainWindow::on_actionBefore_triggered() { ui->framesTree->selectionModel()->clear(); int oldCam = seq->getSelectedCamera(); QModelIndex camIndex = m->getCameraSelectionIndex(); ui->framesTree->collapse( m->getFrameSelectionIndex() ); seq->previousFrame(); if(oldCam != seq->getSelectedCamera()) ui->framesTree->collapse( camIndex ); Frame *f = seq->getFrame(); workModel->setFrame( f ); workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() ); workModel->notifyViews(); if( f->isNotAnnotated() ) requestFromServer(); else setFrameTreeSelection(); } // Wechselt zum nächsten Bild void MainWindow::on_next_clicked() { on_actionNext_triggered(); } // Wechselt zum vorherigen Bild void MainWindow::on_before_clicked() { on_actionBefore_triggered(); } // Setzt die Vergrößerung der Arbeitsfläche zurück void MainWindow::on_zoom_out_clicked() { workModel->resetZoom(); workModel->notifyViews(); } // Wechselt in den ZOOM_IN Modus void MainWindow::on_zoom_in_clicked() { unselectButttons(); ui->zoom_in->setChecked( true ); setMode( ZOOM_IN ); v->repaint(); } // Wechselt in den DELETE Modus void MainWindow::on_remove_clicked() { unselectButttons(); ui->remove->setChecked( true ); setMode( DELETE ); v->repaint(); } // Wechselt in den NEW Modus void MainWindow::on_polygon_clicked() { unselectButttons(); ui->polygon->setChecked( true ); setMode( NEW ); } // Wechselt in den MOVE Modus void MainWindow::on_move_clicked() { unselectButttons(); ui->move->setChecked( true ); setMode( MOVE ); } // Wechselt zum angeklickten Bild des Navigationsbaumes void MainWindow::on_framesTree_clicked(const QModelIndex &index) { if( ((FrameTreeNode*)index.internalPointer())->getDepth() == 2 ) ((_ObjectPolygon*)index.internalPointer())->setSelected( ui->framesTree->selectionModel()->isSelected( index ) ); ui->framesTree->selectionModel()->clear(); int cam, fr; if( m && m->clickedOnFrame( index, cam, fr ) ) { if( cam != seq->getSelectedCamera() || fr != seq->getSelectedFrame() ) { if(cam != seq->getSelectedCamera()) ui->framesTree->collapse( m->getCameraSelectionIndex() ); ui->framesTree->collapse( m->getFrameSelectionIndex() ); seq->selectFrame( cam, fr ); Frame *f = seq->getFrame(); workModel->setFrame( f ); workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() ); workModel->notifyViews(); if( f->isNotAnnotated() ) requestFromServer(); } } if( ((FrameTreeNode*)index.internalPointer())->getDepth() == 0 ) { if( ((FrameTreeNode*)index.internalPointer())->getIndex() != seq->getSelectedCamera() ) { ui->framesTree->collapse( m->getCameraSelectionIndex() ); ui->framesTree->collapse( m->getFrameSelectionIndex() ); seq->selectFrame( ((FrameTreeNode*)index.internalPointer())->getIndex(), 0 ); Frame *f = seq->getFrame(); workModel->setFrame( f ); workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() ); workModel->notifyViews(); if( f->isNotAnnotated() ) requestFromServer(); } } if( ((FrameTreeNode*)index.internalPointer())->getDepth() == 2 ) { if( ((FrameTreeNode*)index.internalPointer())->getParent()->getParent()->getIndex() != seq->getSelectedCamera() ) ui->framesTree->collapse( m->getCameraSelectionIndex() ); if( ((FrameTreeNode*)index.internalPointer())->getParent()->getIndex() != seq->getSelectedFrame() ) ui->framesTree->collapse( m->getFrameSelectionIndex() ); seq->selectFrame( ((FrameTreeNode*)index.internalPointer())->getParent()->getParent()->getIndex(), ((FrameTreeNode*)index.internalPointer())->getParent()->getIndex() ); Frame *f = seq->getFrame(); workModel->setFrame( f ); workModel->setMask( seq->getCameras().at( seq->getSelectedCamera() )->getMask()->getDrawableImage() ); workModel->notifyViews(); if( f->isNotAnnotated() ) requestFromServer(); } setFrameTreeSelection(); v->repaint(); } // Wechselt in den SELECT Modus void MainWindow::on_select_clicked() { unselectButttons(); ui->select->setChecked( true ); setMode( SELECT ); } // Zeigt ein Rechtsklick Menü im Navigationsbaum an void MainWindow::on_framesTree_customContextMenuRequested(const QPoint &pos) { QModelIndex index = ui->framesTree->indexAt( pos ); ui->framesTree->selectionModel()->select( index, QItemSelectionModel::Select | QItemSelectionModel::Rows ); on_framesTree_clicked( index ); QAction *trunc = 0; if( contextMenu ) delete contextMenu; contextMenu = new QMenu( ui->framesTree ); switch( ((FrameTreeNode*)index.internalPointer())->getDepth() ) { case 0: contextMenu->addAction( "Maske Bearbeiten ..." ); break; case 1: contextMenu->addAction( "Vom Server abfragen ..." ); break; case 2: contextMenu->addAction( "Objekt ID bearbeiten ..." ); contextMenu->addAction( "Objekt Klasse bearbeiten ..." ); contextMenu->addAction( "Objekt löschen" ); if( ((_ObjectPolygon*)index.internalPointer())->getPolygonList().size() > 1 ) contextMenu->addAction( "Objekt nach Polygonen aufteilen" ); trunc = contextMenu->addAction( "Abgeschnitten" ); trunc->setCheckable( true ); trunc->setText( "Abgeschnitten" ); trunc->setChecked( ((_ObjectPolygon*)index.internalPointer())->isTruncated() ); break; } QAction *action = contextMenu->exec( QCursor::pos() ); if( !action ) return; if( action->text() == "Maske Bearbeiten ..." ) { ChangeMask chm( ((Kamera*)index.internalPointer())->getMask(), (Kamera*)index.internalPointer() ); chm.exec(); ((Kamera*)index.internalPointer())->getMask()->unloadMask(); setupFrameTree(); } if( action->text() == "Vom Server abfragen ..." ) requestFromServer(); if( action->text() == "Objekt ID bearbeiten ..." ) { ChangePacket chp( ObjectPolygon( (_ObjectPolygon*)index.internalPointer() ), seq ); chp.exec(); workModel->notifyViews(); setupFrameTree(); } if( action->text() == "Objekt Klasse bearbeiten ..." ) { QList< Sequenz::SegmentationClass > classes = seq->getClasses(); QList< QString > classNames; QString packetId = ((_ObjectPolygon*)index.internalPointer())->getId(); int currentClass = seq->getClassOfObject( packetId ); int classId = -1; foreach( auto c, classes ) { if( currentClass == c.id ) classId = classNames.size(); classNames.append( c.name ); } QString className = QInputDialog::getItem( this, "Klasse Bearbeiten", "Klasse von Objekt " + packetId, classNames, classId, false ); seq->setClassOfObject( packetId, seq->getClassId( className ) ); setupFrameTree(); } if( action->text() == "Objekt Löschen" ) { Frame *f = seq->getFrame(); f->removeObject( ObjectPolygon( (_ObjectPolygon*)index.internalPointer() ) ); setupFrameTree(); v->repaint(); } if( action->text() == "Objekt nach Polygonen aufteilen" ) { seq->getFrame()->disconnectObject( ObjectPolygon( ((_ObjectPolygon*)index.internalPointer()) ) ); setupFrameTree(); v->repaint(); } if( action == trunc ) ((_ObjectPolygon*)index.internalPointer())->setTruncated( !((_ObjectPolygon*)index.internalPointer())->isTruncated() ); } // Speichert die geladene Sequenz void MainWindow::on_actionSave_triggered() { if( !seq ) return; status->setText( "speichere Annotation ..." ); seq->saveToPath( status ); status->setText( "fertig" ); } // Wechselt in den Modus PIPETTE_SELECT void MainWindow::on_pipette_clicked() { unselectButttons(); ui->pipette->setChecked( true ); setMode( PIPETTE_SELECT ); } // Wechselt in den CUT Modus void MainWindow::on_cut_clicked() { unselectButttons(); ui->cut->setChecked( true ); setMode( CUT ); } // Zeigt die Option zum einstellen der Serveradresse an void MainWindow::on_actionServer_address_triggered() { serverAddress = QInputDialog::getText( this, "Server Adresse ändern", "Server Adresse:", QLineEdit::Normal, serverAddress ); } // Zeigt die Klassen Verwaltungsoberfläche an void MainWindow::on_actionKlassen_verwalten_triggered() { if( seq ) { ClassOptions cop( seq, this ); cop.exec(); } else QMessageBox::critical( this, "Fehler", "Sie müssen zunächst eine Sequenz geöffnet haben, bevor sie die Klassen verwalten können." ); } // Zeigt ein Rechtsklick Menü auf der Arbeitsfläche an void MainWindow::on_viewWidget_customContextMenuRequested(const QPoint &pos) { QPoint rPos = pos - v->pos(); if( !seq || !seq->getFrame() ) { v->mouseReleaseEvent( 0 ); return; } QPoint tPos = workModel->inverseTranslate( rPos ); int pIndex; ObjectPolygon obj = seq->getFrame()->getObjectAt( tPos, pIndex ); if( obj.isNull() ) return; v->mouseReleaseEvent( 0 ); if( contextMenu ) delete contextMenu; contextMenu = new QMenu( ui->viewWidget ); contextMenu->addAction( "Objekt ID bearbeiten ..." ); contextMenu->addAction( "Objekt Klasse bearbeiten ..." ); contextMenu->addAction( "Objekt Loeschen" ); if( obj->getPolygonList().size() > 1 ) contextMenu->addAction( "Objekt nach Polygonen aufteilen" ); QAction *trunc = contextMenu->addAction( "Abgeschnitten" ); trunc->setCheckable( true ); trunc->setText( "Abgeschnitten" ); trunc->setChecked( obj->isTruncated() ); QAction *action = contextMenu->exec( QCursor::pos() ); if( !action ) return; if( action->text() == "Objekt ID bearbeiten ..." ) { ChangePacket chp( obj, seq ); chp.exec(); workModel->notifyViews(); setupFrameTree(); } if( action->text() == "Objekt Klasse bearbeiten ..." ) { QList< Sequenz::SegmentationClass > classes = seq->getClasses(); QList< QString > classNames; QString packetId = obj->getId(); int currentClass = seq->getClassOfObject( packetId ); int classId = -1; foreach( auto c, classes ) { if( currentClass == c.id ) classId = classNames.size(); classNames.append( c.name ); } QString className = QInputDialog::getItem( this, "Klasse Bearbeiten", "Klasse von Objekt " + packetId, classNames, classId, false ); seq->setClassOfObject( packetId, seq->getClassId( className ) ); setupFrameTree(); } if( action->text() == "Objekt Loeschen" ) { Frame *f = seq->getFrame(); f->removeObject( obj ); setupFrameTree(); v->repaint(); } if( action->text() == "Objekt nach Polygonen aufteilen" ) { seq->getFrame()->disconnectObject( obj ); setupFrameTree(); v->repaint(); } if( action == trunc ) obj->setTruncated( !obj->isTruncated() ); } // Setzt das anzeigen der Maske in der Arbeitsfläche void MainWindow::on_actionMake_anzeigen_toggled(bool checked) { workModel->setShowMask( checked ); workModel->notifyViews(); } // Setzt die Option des einfärbens von Objekten in der Arbeitsfläche void MainWindow::on_actionObjekte_faerben_triggered(bool checked) { workModel->setShowColors( checked ); workModel->notifyViews(); } // Setzt die Option des Anzeigens von IDs in der Arbeitsfläche void MainWindow::on_actionIDs_anzeigen_triggered(bool checked) { workModel->setShowId( checked ); workModel->notifyViews(); } // Zeigt die Maskenbearbeitungsoberfläche void MainWindow::on_actionMaske_bearbeiten_triggered() { if( !seq ) return; ChangeMask chm( seq->getCameras().at( seq->getSelectedCamera() )->getMask(), seq->getCameras().at( seq->getSelectedCamera() ) ); chm.exec(); setupFrameTree(); } // zeigt die intelligente Id Bearbeitungsoberfläche an void MainWindow::on_actionIDs_bearbeiten_triggered() { if( !seq ) return; SetIds ids( seq, this ); ids.exec(); v->update(); setupFrameTree(); }