diff --git a/Documentation/Doxygen/UserManual/MITKPluginManualsList.dox b/Documentation/Doxygen/UserManual/MITKPluginManualsList.dox index be03f2ba30..99dfd7f730 100644 --- a/Documentation/Doxygen/UserManual/MITKPluginManualsList.dox +++ b/Documentation/Doxygen/UserManual/MITKPluginManualsList.dox @@ -1,46 +1,45 @@ /** \page PluginListPage MITK Plugin Manuals \section PluginListPageOverview Overview The plugins and bundles provide much of the extended functionality of MITK. Each encapsulates a solution to a problem and associated features. This way one can easily assemble the necessary capabilites for a workflow without adding a lot of bloat, by combining plugins as needed. The distinction between developer and end user use is for convenience only and mainly distinguishes which group a plugin is primarily aimed at. \section PluginListPageEndUserPluginList List of Plugins for End User Use \li \subpage org_blueberry_ui_qt_log \li \subpage org_mitk_views_basicimageprocessing \li \subpage org_mitk_views_cmdlinemodules \li \subpage org_mitk_views_datamanager \li \subpage org_mitk_gui_qt_dicom \li \subpage org_mitk_gui_qt_diffusionimaging \li \subpage org_mitk_views_imagecropper \li \subpage org_mitk_views_imagenavigator \li \subpage org_mitk_gui_qt_measurementtoolbox - \li \subpage org_mitk_views_meshdecimation - \li \subpage org_mitk_views_moviemaker + \li \subpage org_mitk_gui_qt_moviemaker \li \subpage org_mitk_views_screenshotmaker \li \subpage org_mitk_views_pointsetinteraction \li \subpage org_mitk_gui_qt_python \li \subpage org_mitk_gui_qt_registration \li \subpage org_mitk_gui_qt_remeshing \li \subpage org_mitk_views_segmentation \li \subpage org_mitk_gui_qt_ultrasound \li \subpage org_mitk_gui_qt_viewnavigator \li \subpage org_mitk_views_volumevisualization \li \subpage org_mitk_gui_qt_xnat \section PluginListPageDevPluginList List of Plugins for Developer Use and Examples \li \subpage org_surfacematerialeditor \li \subpage org_toftutorial \li \subpage org_mitk_gui_qt_examples \li \subpage org_mitkexamplesopencv \li \subpage org_mitk_gui_qt_igtexample \li \subpage org_mitk_gui_qt_igttracking \li \subpage org_blueberry_ui_qt_objectinspector \li \subpage org_mitk_gui_qt_eventrecorder */ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker.dox b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker.dox new file mode 100644 index 0000000000..93188462b8 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker.dox @@ -0,0 +1,56 @@ +/** +\page org_mitk_gui_qt_moviemaker The Movie Maker Plugin + +\imageMacro{QmitkMovieMaker_Icon.png,"Icon of the Movie Maker Plugin.",2.00} + +\tableofcontents + +\section org_mitk_gui_qt_moviemakerOverview Overview + +The Movie Maker View allows you to create basic animations of your scene and to record them to video files. +Individual animations are arranged in a timeline and can be played back sequential or in parallel. + +The Movie Maker View uses external FFmpeg/Libav command line utilities to write compressed video files. + +You have to manually install either FFmpeg or Libav and set the corresponding path in "External Programs" in the MITK Workbench Preferences (Ctrl+P) in order to record your movies to video files. + +\imageMacro{QmitkMovieMaker_Preferences.png,"The External Programs preferences page.",12.00} + +\section org_mitk_gui_qt_moviemakerUsage Usage + +\imageMacro{QmitkMovieMaker_MovieMakerView.png,"The Movie Maker View.",16.00} + +To create a movie you have to add an animation to the timeline by clicking the "Add animation" button. +You can choose between the available types of animations, e.g., Orbit or Slice. + +The timeline surrounding bottons allow you to arrange, remove, or add further animations to your movie. + +Each animation can be set to either begin with the previous animation, i.e., run in parallel, or to start after the previous animation, i.e., run sequential. +In combination with delays, rather complex animation arrangements are possible. + +To set animation specific parameters, select the corresponding animation in the timeline first. + +You can play back, pause and stop your movie with the according controls at the bottom of the Movie Maker View. +Click the "Record" button to finally record your movie to a video file with the specified number of frames per second. +You have to choose the render window which you want to record. + +\subsection org_mitk_gui_qt_moviemakerOrbitUsage Orbit Animation + +The Orbit animation rotates the camera in the 3D window around the scene. +Align the camera directly in the 3D window and enter the number of degrees for the orbitting. +You usually do not need to adjust the "Start angle" setting - leave it at 180 degrees. +If you are planning to have a specific view in the middle of your movie you can play the movie and pause it at the specific frame of interest. +Adjust the camera in the 3D window and restart the animation. + +\imageMacro{QmitkMovieMaker_Orbit.png,"The Orbit animation.",12.00} + +\subsection org_mitk_gui_qt_moviemakerSliceUsage Slice Animation + +The Slice animation slices through an image. +You can choose the image plane (axial, sagittal, or coronal), as well as the start and end points of the slicing. +Use the image navigator in the bottom left of the Workbench to get an idea of the desired values. +Check "Reverse" in order to slice from the higher slice number to the lower slice number. + +\imageMacro{QmitkMovieMaker_Slice.png,"The Slice animation.",12.00} + +*/ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Icon.png b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Icon.png new file mode 100644 index 0000000000..d9d484927f Binary files /dev/null and b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Icon.png differ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_MovieMakerView.png b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_MovieMakerView.png new file mode 100644 index 0000000000..8b5eac9c8e Binary files /dev/null and b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_MovieMakerView.png differ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Orbit.png b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Orbit.png new file mode 100644 index 0000000000..107e752a15 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Orbit.png differ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Preferences.png b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Preferences.png new file mode 100644 index 0000000000..c26c59a63c Binary files /dev/null and b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Preferences.png differ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Slice.png b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Slice.png new file mode 100644 index 0000000000..dcd2321025 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_Slice.png differ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp index 2e6735528a..efb9b3d69e 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkMovieMakerView.cpp @@ -1,666 +1,680 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkAnimationItemDelegate.h" #include "QmitkFFmpegWriter.h" #include "QmitkMovieMakerView.h" #include "QmitkOrbitAnimationItem.h" #include "QmitkOrbitAnimationWidget.h" #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" #include #include #include #include #include #include #include #include static QmitkAnimationItem* CreateDefaultAnimation(const QString& widgetKey) { if (widgetKey == "Orbit") return new QmitkOrbitAnimationItem; if (widgetKey == "Slice") return new QmitkSliceAnimationItem; return NULL; } static QString GetFFmpegPath() { berry::IPreferencesService::Pointer preferencesService = berry::Platform::GetServiceRegistry().GetServiceById(berry::IPreferencesService::ID); berry::IPreferences::Pointer preferences = preferencesService->GetSystemPreferences()->Node("/org.mitk.gui.qt.ext.externalprograms"); return QString::fromStdString(preferences->Get("ffmpeg", "")); } static unsigned char* ReadPixels(vtkRenderWindow* renderWindow, int x, int y, int width, int height) { if (renderWindow == NULL) return NULL; unsigned char* frame = new unsigned char[width * height * 3]; renderWindow->MakeCurrent(); glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, frame); return frame; } const std::string QmitkMovieMakerView::VIEW_ID = "org.mitk.views.moviemaker"; QmitkMovieMakerView::QmitkMovieMakerView() : m_FFmpegWriter(NULL), m_Ui(new Ui::QmitkMovieMakerView), m_AnimationModel(NULL), m_AddAnimationMenu(NULL), m_RecordMenu(NULL), m_Timer(NULL), m_TotalDuration(0.0), m_NumFrames(0), m_CurrentFrame(0) { } QmitkMovieMakerView::~QmitkMovieMakerView() { } void QmitkMovieMakerView::CreateQtPartControl(QWidget* parent) { m_FFmpegWriter = new QmitkFFmpegWriter(parent); m_Ui->setupUi(parent); this->InitializeAnimationWidgets(); this->InitializeAnimationTreeViewWidgets(); this->InitializePlaybackAndRecordWidgets(); this->InitializeTimer(parent); m_Ui->animationWidgetGroupBox->setVisible(false); } void QmitkMovieMakerView::InitializeAnimationWidgets() { m_AnimationWidgets["Orbit"] = new QmitkOrbitAnimationWidget; m_AnimationWidgets["Slice"] = new QmitkSliceAnimationWidget; Q_FOREACH(QWidget* widget, m_AnimationWidgets.values()) { if (widget != NULL) { widget->setVisible(false); m_Ui->animationWidgetGroupBoxLayout->addWidget(widget); } } this->ConnectAnimationWidgets(); } void QmitkMovieMakerView::InitializeAnimationTreeViewWidgets() { this->InitializeAnimationModel(); this->InitializeAddAnimationMenu(); this->ConnectAnimationTreeViewWidgets(); } void QmitkMovieMakerView::InitializePlaybackAndRecordWidgets() { this->InitializeRecordMenu(); this->ConnectPlaybackAndRecordWidgets(); } void QmitkMovieMakerView::InitializeAnimationModel() { m_AnimationModel = new QStandardItemModel(m_Ui->animationTreeView); m_AnimationModel->setHorizontalHeaderLabels(QStringList() << "Animation" << "Timeline"); m_Ui->animationTreeView->setModel(m_AnimationModel); m_Ui->animationTreeView->setItemDelegate(new QmitkAnimationItemDelegate(m_Ui->animationTreeView)); } void QmitkMovieMakerView::InitializeAddAnimationMenu() { m_AddAnimationMenu = new QMenu(m_Ui->addAnimationButton); Q_FOREACH(const QString& key, m_AnimationWidgets.keys()) { m_AddAnimationMenu->addAction(key); } } void QmitkMovieMakerView::InitializeRecordMenu() { typedef QPair PairOfStrings; m_RecordMenu = new QMenu(m_Ui->recordButton); QVector renderWindows; renderWindows.push_back(qMakePair(QString("Axial"), QString("stdmulti.widget1"))); renderWindows.push_back(qMakePair(QString("Sagittal"), QString("stdmulti.widget2"))); renderWindows.push_back(qMakePair(QString("Coronal"), QString("stdmulti.widget3"))); renderWindows.push_back(qMakePair(QString("3D"), QString("stdmulti.widget4"))); Q_FOREACH(const PairOfStrings& renderWindow, renderWindows) { QAction* action = new QAction(m_RecordMenu); action->setText(renderWindow.first); action->setData(renderWindow.second); m_RecordMenu->addAction(action); } } void QmitkMovieMakerView::InitializeTimer(QWidget* parent) { m_Timer = new QTimer(parent); this->OnFPSSpinBoxValueChanged(m_Ui->fpsSpinBox->value()); this->ConnectTimer(); } void QmitkMovieMakerView::ConnectAnimationTreeViewWidgets() { this->connect(m_AnimationModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(OnAnimationTreeViewRowsInserted(const QModelIndex&, int, int))); this->connect(m_AnimationModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int))); this->connect(m_Ui->animationTreeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&))); this->connect(m_Ui->moveAnimationUpButton, SIGNAL(clicked()), this, SLOT(OnMoveAnimationUpButtonClicked())); this->connect(m_Ui->moveAnimationDownButton, SIGNAL(clicked()), this, SLOT(OnMoveAnimationDownButtonClicked())); this->connect(m_Ui->addAnimationButton, SIGNAL(clicked()), this, SLOT(OnAddAnimationButtonClicked())); this->connect(m_Ui->removeAnimationButton, SIGNAL(clicked()), this, SLOT(OnRemoveAnimationButtonClicked())); } void QmitkMovieMakerView::ConnectAnimationWidgets() { this->connect(m_Ui->startComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnStartComboBoxCurrentIndexChanged(int))); this->connect(m_Ui->durationSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDurationSpinBoxValueChanged(double))); this->connect(m_Ui->delaySpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnDelaySpinBoxValueChanged(double))); } void QmitkMovieMakerView::ConnectPlaybackAndRecordWidgets() { this->connect(m_Ui->playButton, SIGNAL(toggled(bool)), this, SLOT(OnPlayButtonToggled(bool))); this->connect(m_Ui->stopButton, SIGNAL(clicked()), this, SLOT(OnStopButtonClicked())); this->connect(m_Ui->recordButton, SIGNAL(clicked()), this, SLOT(OnRecordButtonClicked())); this->connect(m_Ui->fpsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnFPSSpinBoxValueChanged(int))); } void QmitkMovieMakerView::ConnectTimer() { this->connect(m_Timer, SIGNAL(timeout()), this, SLOT(OnTimerTimeout())); } void QmitkMovieMakerView::SetFocus() { m_Ui->addAnimationButton->setFocus(); } void QmitkMovieMakerView::OnMoveAnimationUpButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int selectedRow = selection[0].top(); if (selectedRow > 0) m_AnimationModel->insertRow(selectedRow - 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnMoveAnimationDownButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); if (selectedRow < rowCount - 1) m_AnimationModel->insertRow(selectedRow + 1, m_AnimationModel->takeRow(selectedRow)); } this->CalculateTotalDuration(); } void QmitkMovieMakerView::OnAddAnimationButtonClicked() { QAction* action = m_AddAnimationMenu->exec(QCursor::pos()); if (action != NULL) { const QString widgetKey = action->text(); m_AnimationModel->appendRow(QList() << new QStandardItem(widgetKey) << CreateDefaultAnimation(widgetKey)); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); } } void QmitkMovieMakerView::OnPlayButtonToggled(bool checked) { if (checked) { m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-pause.svg")); m_Ui->playButton->repaint(); m_Timer->start(static_cast(1000.0 / m_Ui->fpsSpinBox->value())); } else { m_Timer->stop(); m_Ui->playButton->setIcon(QIcon(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Ui->playButton->repaint(); } } void QmitkMovieMakerView::OnStopButtonClicked() { m_Ui->playButton->setChecked(false); m_Ui->stopButton->setEnabled(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } void QmitkMovieMakerView::OnRecordButtonClicked() // TODO: Refactor { const QString ffmpegPath = GetFFmpegPath(); if (ffmpegPath.isEmpty()) { QMessageBox::information(NULL, "Movie Maker", "Set path to FFmpeg or Libav (avconv) in preferences (Window -> Preferences... (Ctrl+P) -> External Programs) to be able to record your movies to video files."); return; } m_FFmpegWriter->SetFFmpegPath(GetFFmpegPath()); QAction* action = m_RecordMenu->exec(QCursor::pos()); if (action == NULL) return; vtkRenderWindow* renderWindow = mitk::BaseRenderer::GetRenderWindowByName(action->data().toString().toStdString()); if (renderWindow == NULL) return; const int border = 3; const int x = border; const int y = border; int width = renderWindow->GetSize()[0] - border * 2; int height = renderWindow->GetSize()[1] - border * 2; if (width & 1) --width; if (height & 1) --height; if (width < 16 || height < 16) return; m_FFmpegWriter->SetSize(width, height); m_FFmpegWriter->SetFramerate(m_Ui->fpsSpinBox->value()); QString saveFileName = QFileDialog::getSaveFileName(NULL, "Specify a filename", "", "Movie (*.mp4)"); if (saveFileName.isEmpty()) return; if(!saveFileName.endsWith(".mp4")) saveFileName += ".mp4"; m_FFmpegWriter->SetOutputPath(saveFileName); try { m_FFmpegWriter->Start(); for (m_CurrentFrame = 0; m_CurrentFrame < m_NumFrames; ++m_CurrentFrame) { this->RenderCurrentFrame(); renderWindow->MakeCurrent(); unsigned char* frame = ReadPixels(renderWindow, x, y, width, height); m_FFmpegWriter->WriteFrame(frame); delete[] frame; } m_FFmpegWriter->Stop(); m_CurrentFrame = 0; this->RenderCurrentFrame(); } catch (const mitk::Exception& exception) { m_CurrentFrame = 0; this->RenderCurrentFrame(); QMessageBox::critical(NULL, "Movie Maker", exception.GetDescription()); } } void QmitkMovieMakerView::OnRemoveAnimationButtonClicked() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (!selection.isEmpty()) m_AnimationModel->removeRow(selection[0].top()); } void QmitkMovieMakerView::OnAnimationTreeViewRowsInserted(const QModelIndex& parent, int start, int) { this->CalculateTotalDuration(); m_Ui->animationTreeView->selectionModel()->select( m_AnimationModel->index(start, 0, parent), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void QmitkMovieMakerView::OnAnimationTreeViewRowsRemoved(const QModelIndex&, int, int) { this->CalculateTotalDuration(); this->UpdateWidgets(); } void QmitkMovieMakerView::OnAnimationTreeViewSelectionChanged(const QItemSelection&, const QItemSelection&) { this->UpdateWidgets(); } void QmitkMovieMakerView::OnStartComboBoxCurrentIndexChanged(int index) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { item->SetStartWithPrevious(index); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDurationSpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { item->SetDuration(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnDelaySpinBoxValueChanged(double value) { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { item->SetDelay(value); this->RedrawTimeline(); this->CalculateTotalDuration(); } } void QmitkMovieMakerView::OnFPSSpinBoxValueChanged(int value) { this->CalculateTotalDuration(); m_Timer->setInterval(static_cast(1000.0 / value)); } void QmitkMovieMakerView::OnTimerTimeout() { this->RenderCurrentFrame(); m_CurrentFrame = std::min(m_NumFrames, m_CurrentFrame + 1); if (m_CurrentFrame >= m_NumFrames) { m_Ui->playButton->setChecked(false); m_CurrentFrame = 0; this->RenderCurrentFrame(); } m_Ui->stopButton->setEnabled(m_CurrentFrame != 0); } void QmitkMovieMakerView::RenderCurrentFrame() { - typedef QPair AnimationIterpolationFactorPair; + typedef QPair AnimationInterpolationFactorPair; const double deltaT = m_TotalDuration / (m_NumFrames - 1); - const QVector activeAnimations = this->GetActiveAnimations(m_CurrentFrame * deltaT); + const QVector activeAnimations = this->GetActiveAnimations(m_CurrentFrame * deltaT); - Q_FOREACH(const AnimationIterpolationFactorPair& animation, activeAnimations) + Q_FOREACH(const AnimationInterpolationFactorPair& animation, activeAnimations) { - animation.first->Animate(animation.second); + const QVector nextActiveAnimations = this->GetActiveAnimations((m_CurrentFrame + 1) * deltaT); + bool lastFrameForAnimation = true; + + Q_FOREACH(const AnimationInterpolationFactorPair& nextAnimation, nextActiveAnimations) + { + if (nextAnimation.first == animation.first) + { + lastFrameForAnimation = false; + break; + } + } + + animation.first->Animate(!lastFrameForAnimation + ? animation.second + : 1.0); } mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void QmitkMovieMakerView::UpdateWidgets() { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); if (selection.isEmpty()) { m_Ui->moveAnimationUpButton->setEnabled(false); m_Ui->moveAnimationDownButton->setEnabled(false); m_Ui->removeAnimationButton->setEnabled(false); m_Ui->playbackAndRecordingGroupBox->setEnabled(false); this->HideCurrentAnimationWidget(); } else { const int rowCount = m_AnimationModel->rowCount(); const int selectedRow = selection[0].top(); m_Ui->moveAnimationUpButton->setEnabled(rowCount > 1 && selectedRow != 0); m_Ui->moveAnimationDownButton->setEnabled(rowCount > 1 && selectedRow < rowCount - 1); m_Ui->removeAnimationButton->setEnabled(true); m_Ui->playbackAndRecordingGroupBox->setEnabled(true); this->ShowAnimationWidget(dynamic_cast(m_AnimationModel->item(selectedRow, 1))); } this->UpdateAnimationWidgets(); } void QmitkMovieMakerView::UpdateAnimationWidgets() { QmitkAnimationItem* item = this->GetSelectedAnimationItem(); if (item != NULL) { m_Ui->startComboBox->setCurrentIndex(item->GetStartWithPrevious()); m_Ui->durationSpinBox->setValue(item->GetDuration()); m_Ui->delaySpinBox->setValue(item->GetDelay()); m_Ui->animationGroupBox->setEnabled(true); } else { m_Ui->animationGroupBox->setEnabled(false); } } void QmitkMovieMakerView::HideCurrentAnimationWidget() { if (m_Ui->animationWidgetGroupBox->isVisible()) { m_Ui->animationWidgetGroupBox->setVisible(false); int numWidgets = m_Ui->animationWidgetGroupBoxLayout->count(); for (int i = 0; i < numWidgets; ++i) m_Ui->animationWidgetGroupBoxLayout->itemAt(i)->widget()->setVisible(false); } } void QmitkMovieMakerView::ShowAnimationWidget(QmitkAnimationItem* animationItem) { this->HideCurrentAnimationWidget(); if (animationItem == NULL) return; const QString widgetKey = animationItem->GetWidgetKey(); QmitkAnimationWidget* animationWidget = NULL; if (m_AnimationWidgets.contains(widgetKey)) { animationWidget = m_AnimationWidgets[widgetKey]; if (animationWidget != NULL) { m_Ui->animationWidgetGroupBox->setTitle(widgetKey); animationWidget->SetAnimationItem(animationItem); animationWidget->setVisible(true); } } m_Ui->animationWidgetGroupBox->setVisible(animationWidget != NULL); } void QmitkMovieMakerView::RedrawTimeline() { if (m_AnimationModel->rowCount() > 1) { m_Ui->animationTreeView->dataChanged( m_AnimationModel->index(0, 1), m_AnimationModel->index(m_AnimationModel->rowCount() - 1, 1)); } } QmitkAnimationItem* QmitkMovieMakerView::GetSelectedAnimationItem() const { const QItemSelection selection = m_Ui->animationTreeView->selectionModel()->selection(); return !selection.isEmpty() ? dynamic_cast(m_AnimationModel->item(selection[0].top(), 1)) : NULL; } void QmitkMovieMakerView::CalculateTotalDuration() { const int rowCount = m_AnimationModel->rowCount(); double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { QmitkAnimationItem* item = dynamic_cast(m_AnimationModel->item(i, 1)); if (item == NULL) continue; if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } m_TotalDuration = totalDuration; // TODO totalDuration == 0 m_NumFrames = static_cast(totalDuration * m_Ui->fpsSpinBox->value()); // TODO numFrames < 2 } QVector > QmitkMovieMakerView::GetActiveAnimations(double t) const { const int rowCount = m_AnimationModel->rowCount(); QVector > activeAnimations; double totalDuration = 0.0; double previousStart = 0.0; for (int i = 0; i < rowCount; ++i) { QmitkAnimationItem* item = dynamic_cast(m_AnimationModel->item(i, 1)); if (item == NULL) continue; if (item->GetDuration() > 0.0) { double start = item->GetStartWithPrevious() ? previousStart + item->GetDelay() : totalDuration + item->GetDelay(); if (start <= t && t <= start + item->GetDuration()) activeAnimations.push_back(qMakePair(item, (t - start) / item->GetDuration())); } if (item->GetStartWithPrevious()) { totalDuration = std::max(totalDuration, previousStart + item->GetDelay() + item->GetDuration()); } else { previousStart = totalDuration; totalDuration += item->GetDelay() + item->GetDuration(); } } return activeAnimations; } diff --git a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp index 525022fd49..b6aef991c1 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp +++ b/Plugins/org.mitk.gui.qt.moviemaker/src/internal/QmitkSliceAnimationWidget.cpp @@ -1,120 +1,131 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "QmitkSliceAnimationItem.h" #include "QmitkSliceAnimationWidget.h" #include #include -static unsigned int GetNumberOfSlices(int renderWindow) +static int GetNumberOfSlices(int renderWindow) { const QString renderWindowName = QString("stdmulti.widget%1").arg(renderWindow + 1); vtkRenderWindow* theRenderWindow = mitk::BaseRenderer::GetRenderWindowByName(renderWindowName.toStdString()); if (theRenderWindow != NULL) { mitk::Stepper* stepper = mitk::BaseRenderer::GetInstance(theRenderWindow)->GetSliceNavigationController()->GetSlice(); if (stepper != NULL) - return std::max(1U, stepper->GetSteps()); + return std::max(1, static_cast(stepper->GetSteps())); } return 1; } QmitkSliceAnimationWidget::QmitkSliceAnimationWidget(QWidget* parent) : QmitkAnimationWidget(parent), m_Ui(new Ui::QmitkSliceAnimationWidget) { m_Ui->setupUi(this); this->connect(m_Ui->windowComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnRenderWindowChanged(int))); this->connect(m_Ui->sliceRangeWidget, SIGNAL(minimumValueChanged(double)), this, SLOT(OnFromChanged(double))); this->connect(m_Ui->sliceRangeWidget, SIGNAL(maximumValueChanged(double)), this, SLOT(OnToChanged(double))); this->connect(m_Ui->reverseCheckBox, SIGNAL(clicked(bool)), this, SLOT(OnReverseChanged(bool))); } QmitkSliceAnimationWidget::~QmitkSliceAnimationWidget() { } void QmitkSliceAnimationWidget::SetAnimationItem(QmitkAnimationItem* sliceAnimationItem) { m_AnimationItem = dynamic_cast(sliceAnimationItem); if (m_AnimationItem == NULL) return; m_Ui->windowComboBox->setCurrentIndex(m_AnimationItem->GetRenderWindow()); - m_Ui->sliceRangeWidget->setMaximum(GetNumberOfSlices(m_AnimationItem->GetRenderWindow()) - 1); - m_Ui->sliceRangeWidget->setValues(m_AnimationItem->GetFrom(), m_AnimationItem->GetTo()); + + const int maximum = GetNumberOfSlices(m_AnimationItem->GetRenderWindow()) - 1; + const int from = std::min(m_AnimationItem->GetFrom(), maximum); + const int to = std::min(m_AnimationItem->GetTo(), maximum); + + m_AnimationItem->SetFrom(from); + m_AnimationItem->SetTo(to); + + m_Ui->sliceRangeWidget->setMaximum(maximum); + m_Ui->sliceRangeWidget->setValues(from, to); m_Ui->reverseCheckBox->setChecked(m_AnimationItem->GetReverse()); } void QmitkSliceAnimationWidget::OnRenderWindowChanged(int renderWindow) { if (m_AnimationItem == NULL) return; const int lastSlice = static_cast(GetNumberOfSlices(renderWindow) - 1); - m_AnimationItem->SetFrom(0); - m_AnimationItem->SetTo(lastSlice); + if (lastSlice < m_AnimationItem->GetFrom()) + m_AnimationItem->SetFrom(lastSlice); + + if (lastSlice < m_AnimationItem->GetTo()) + m_AnimationItem->SetTo(lastSlice); m_Ui->sliceRangeWidget->setMaximum(lastSlice); m_Ui->sliceRangeWidget->setValues(m_AnimationItem->GetFrom(), m_AnimationItem->GetTo()); if (m_AnimationItem->GetRenderWindow() != renderWindow) m_AnimationItem->SetRenderWindow(renderWindow); } void QmitkSliceAnimationWidget::OnFromChanged(double from) { if (m_AnimationItem == NULL) return; int intFrom = static_cast(from); if (m_AnimationItem->GetFrom() != intFrom) m_AnimationItem->SetFrom(intFrom); } void QmitkSliceAnimationWidget::OnToChanged(double to) { if (m_AnimationItem == NULL) return; int intTo = static_cast(to); if (m_AnimationItem->GetTo() != intTo) m_AnimationItem->SetTo(intTo); } void QmitkSliceAnimationWidget::OnReverseChanged(bool reverse) { if (m_AnimationItem == NULL) return; if (m_AnimationItem->GetReverse() != reverse) m_AnimationItem->SetReverse(reverse); } diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing.dox b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing.dox similarity index 86% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing.dox rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing.dox index 479aca2342..72588e7834 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing.dox +++ b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing.dox @@ -1,71 +1,71 @@ /** \page org_mitk_gui_qt_remeshing The Remeshing Plugin -\imageMacro{QmitkRemashing_Icon.png,"Icon of the Remeshing Plugin.",2.00} +\imageMacro{QmitkRemeshing_Icon.png,"Icon of the Remeshing Plugin.",2.00} \tableofcontents \section org_mitk_gui_qt_remeshingOverview Overview The Remeshing View allows you to remesh surfaces. If done right, remeshing can dramatically increase the quality of your surface mesh. However, you might lose precision if you reduce your surface mesh too strong. Even when you keep the detail of your mesh there might be a tiny distance between your original surface and the remeshed surface. Hence, be careful when using remeshed surfaces for evaluation purposes and always keep the original versions. \section org_mitk_gui_qt_remeshingUsage Usage -\imageMacro{QmitkRemashing_RemeshingView.png,"Basic and advanced mode of the Remeshing View.",13.29} +\imageMacro{QmitkRemeshing_RemeshingView.png,"Basic and advanced mode of the Remeshing View.",13.29} There are two basic and about a handful of advanced settings that influence remeshing. Most of the time you should be able to gain satisfying results by adjusting only the two basic settings or even without changing any of the default parameters. In the following the effects of all settings are described in more detail. Image examples are based on the following surface: -\imageMacro{QmitkRemashing_OriginalMesh.png,"The surface from which all examples below originate from.",10.08} +\imageMacro{QmitkRemeshing_OriginalMesh.png,"The surface from which all examples below originate from.",10.08} \subsection org_mitk_gui_qt_remeshingBasicSettings Basic Settings The Vertices setting is the number of vertices the remeshed surface will consist of. This is exact as long as Boundary fixing is turned off (default). The maximum number of vertices is limited to the number of vertices of the input surface, however, you can increase this limit by adjusting the Max. # of vertices setting. The Gradation setting controls the distribution of vertices in the remeshed surface. If set to zero the vertices are distributed equally all over the remeshed surface. You can push more vertices towards surface regions with high curvature, i.e., more detailed regions, by increasing this setting. -\imageMacro{QmitkRemashing_Gradation10Percent.png,"Vertex count reduced to 10 percent\, gradation 0 vs. 1.",16.00} +\imageMacro{QmitkRemeshing_Gradation10Percent.png,"Vertex count reduced to 10 percent\, gradation 0 vs. 1.",16.00} \subsection org_mitk_gui_qt_remeshingAdvancedSettings Advanced Settings You can arbirarily increase the maximum adjustable number of vertices by changing the Max. # of vertices setting. Edge splitting is disabled by default and might take a long time during remeshing when enabled. This setting represents a number by which the average edge length of the input surface is multiplied to serve as a threshold which regulates edge splitting. Long edges are split recursively until all edges satisfy the threshold. Edge splitting is useful for surfaces that contain thin and long polygons. -\imageMacro{QmitkRemashing_Cylinder.png,"A surface that contains extremely long polygons.",16.00} +\imageMacro{QmitkRemeshing_Cylinder.png,"A surface that contains extremely long polygons.",16.00}
-\imageMacro{QmitkRemashing_CylinderBad.png,"A remeshing attempt without edge splitting.",16.00} +\imageMacro{QmitkRemeshing_CylinderBad.png,"A remeshing attempt without edge splitting.",16.00}
-\imageMacro{QmitkRemashing_CylinderGood.png,"Increased max. # of vertices\, enabled edge splitting\, followed by a second remeshing run without edge splitting.",16.00} +\imageMacro{QmitkRemeshing_CylinderGood.png,"Increased max. # of vertices\, enabled edge splitting\, followed by a second remeshing run without edge splitting.",16.00} The Subsampling setting has direct impact on the quality of the remeshed surface. The input surface is recursively subdivided until the total number of vertices exceeds its initial vertex count times this setting. -\imageMacro{QmitkRemashing_Subsampling20Percent.png,"Vertex count reduced to 20 percent\, subsampling 10 vs. 500.",16.00} +\imageMacro{QmitkRemeshing_Subsampling20Percent.png,"Vertex count reduced to 20 percent\, subsampling 10 vs. 500.",16.00} You usually leave the Optimization level set to its default value 1. When disabled, the remeshed surface has usually a slightly smaller volume than the original surface. The optimization process minimizes the distance between the two surfaces but values higher than 1 introduce degenerated triangles to the remeshed surface. If your surface is open, i.e., it has holes in it, boundaries tend to shrink irregularly during remeshing. If the position and smoothness of your surface boundaries are important, you should activate the Boundary fixing setting. This results in additional vertices that make up extra polygons at the remeshed boundaries to keep the original boundaries. -\imageMacro{QmitkRemashing_NoBoundaryFixing10Percent.png,"Vertex count reduced to 10 percent\, no boundary fixing.",10.08} +\imageMacro{QmitkRemeshing_NoBoundaryFixing10Percent.png,"Vertex count reduced to 10 percent\, no boundary fixing.",10.08} */ diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_CylinderBad.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_CylinderBad.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_CylinderBad.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_CylinderBad.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_CylinderGood.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_CylinderGood.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_CylinderGood.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_CylinderGood.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_Gradation10Percent.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_Gradation10Percent.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_Gradation10Percent.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_Gradation10Percent.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_Icon.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_Icon.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_Icon.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_Icon.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_NoBoundaryFixing10Percent.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_NoBoundaryFixing10Percent.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_NoBoundaryFixing10Percent.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_NoBoundaryFixing10Percent.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_OriginalMesh.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_OriginalMesh.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_OriginalMesh.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_OriginalMesh.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_RemeshingView.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_RemeshingView.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_RemeshingView.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_RemeshingView.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_Subsampling20percent.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_Subsampling20percent.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_Subsampling20percent.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_Subsampling20percent.png diff --git a/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_cylinder.png b/Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_cylinder.png similarity index 100% rename from Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemashing_cylinder.png rename to Plugins/org.mitk.gui.qt.remeshing/documentation/UserManual/QmitkRemeshing_cylinder.png