diff --git a/Modules/AppUtil/include/mitkBaseApplication.h b/Modules/AppUtil/include/mitkBaseApplication.h index 6db22c584a..fd1e6f9738 100644 --- a/Modules/AppUtil/include/mitkBaseApplication.h +++ b/Modules/AppUtil/include/mitkBaseApplication.h @@ -1,327 +1,334 @@ /*=================================================================== 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. ===================================================================*/ #ifndef MITKBASEAPPLICATION_H #define MITKBASEAPPLICATION_H #include #include #include #include #include #include +#include class QCoreApplication; class ctkPluginContext; class ctkPluginFramework; namespace mitk { /** * A utility classes for starting up BlueBerry applications. * * In the simplest case, a user creates an instance of this * class and just calls run() which launches a CTK Plugin * Framework instance and executes the default application * registered by a plug-in via the org.blueberry.osgi.applications * extension point. * * This class contains many convenience methods to: * - Put the application in \emph{safe mode} which catches unhandled * exceptions thrown in the Qt event loop and displays an error * message * - Put the application in \emph{single mode} which by default * sends the command line arguments to an already running instance * of the same application instead of creating a second instance. * - Add a list of library names which should be pre-loaded at * application start-up, e.g. to speed up the initial launch during * the caching process of the plug-in meta-data. * - Set a custom provisioning file to start a specific set of CTK * plug-ins during application start-up. * - Set and get CTK Plugin Framework properties * * The behavior can further be customized by deriving from * BaseApplication and overriding specific methods, such as: * - initializeLibraryPaths() to add specific library / plugin search * paths * - defineOptions(Poco::Util::OptionSet&) to define a custom set * of command line options * - getQApplication() to provide a custom QCoreApplication instance * * A simple but complete usage example: * * #include * * int main(int argc, char** argv) * { * mitk::BaseApplication app(argc, argv); * app.setApplicationName("MyApp"); * app.setOrganizationName("MyOrganization"); * * // Run the workbench * return app.run(); * } * */ class MITKAPPUTIL_EXPORT BaseApplication : public Poco::Util::Application { public: // Command line arguments static QString ARG_NEWINSTANCE; static QString ARG_CLEAN; static QString ARG_APPLICATION; static QString ARG_PRODUCT; static QString ARG_HOME; static QString ARG_STORAGE_DIR; static QString ARG_PLUGIN_CACHE; static QString ARG_PLUGIN_DIRS; static QString ARG_FORCE_PLUGIN_INSTALL; static QString ARG_PRELOAD_LIBRARY; static QString ARG_PROVISIONING; static QString ARG_DEBUG; static QString ARG_CONSOLELOG; static QString ARG_TESTPLUGIN; static QString ARG_TESTAPPLICATION; static QString ARG_NO_REGISTRY_CACHE; static QString ARG_NO_LAZY_REGISTRY_CACHE_LOADING; static QString ARG_REGISTRY_MULTI_LANGUAGE; static QString ARG_SPLASH_IMAGE; static QString ARG_XARGS; // BlueBerry specific Plugin Framework properties static QString PROP_NEWINSTANCE; static QString PROP_FORCE_PLUGIN_INSTALL; static QString PROP_NO_REGISTRY_CACHE; static QString PROP_NO_LAZY_REGISTRY_CACHE_LOADING; static QString PROP_REGISTRY_MULTI_LANGUAGE; static QString PROP_PRODUCT; static QString PROP_APPLICATION; static QString PROP_TESTPLUGIN; static QString PROP_TESTAPPLICATION; BaseApplication(int argc, char **argv); ~BaseApplication(); /** * Initialize the Qt library such that a QCoreApplication * instance is available and e.g. Qt Widgets can be created. * * This is usually not called directly by the user. */ void initializeQt(); /** * Launches the BlueBerry framework and runs the default application * or the one specified in the PROP_APPLICATION framework property. * * @return The return code of the application after it was shut down. */ int run() override; void printHelp(const std::string &name, const std::string &value); /** * Set the application name. Same as QCoreApplication::setApplicationName. * @param name The application name. */ void setApplicationName(const QString &name); QString getApplicationName() const; /** * Set the organization name. Same as QCoreApplication::setOrganizationName. * @param name The organization name. */ void setOrganizationName(const QString &name); QString getOrganizationName() const; /** * Set the organization domain. Same as QCoreApplication::setOrganizationDomain. * @param name The organization domain. */ void setOrganizationDomain(const QString &name); QString getOrganizationDomain() const; /** * Put the application in single mode, which by default only allows * a single instance of the application to be created. * * Calling this method after run() has been called has no effect. * * @param singleMode */ void setSingleMode(bool singleMode); bool getSingleMode() const; /** * Put the application in safe mode, catching exceptions from the * Qt event loop. * * @param safeMode */ void setSafeMode(bool safeMode); bool getSafeMode() const; /** * Set a list of library names or absoulte file paths * which should be loaded at application start-up. The name * and file path may contain a library version appended at the * end and separated by a '$' charactger. * * For example liborg_mitk_gui_qt_common$1.0. * Platform specific suffixes are appended automatically. * * @param libraryBaseNames A list of library base names. */ void setPreloadLibraries(const QStringList &libraryBaseNames); /** * Get the list of library base names which should be pre-loaded. * * @return A list of pre-loaded libraries. */ QStringList getPreloadLibraries() const; /** * Set the path to the provisioning file. * * By default a provisioning file located in the same directory * as the executable and named .provisioning * is loaded if it exists. To disable parsing of provisioning * files, use an empty string as the argument. Use a * null QString (QString::null) to reset to the * default behaviour. * * @param filePath An absolute file path to the provisioning file. */ void setProvisioningFilePath(const QString &filePath); /** * Get the file path to the provisioning file. * @return The provisioning file path. */ QString getProvisioningFilePath() const; void setProperty(const QString &property, const QVariant &value); QVariant getProperty(const QString &property) const; + void installTranslator(QTranslator*); + + bool isRunning(); + + void sendMessage(const QByteArray); + protected: void initialize(Poco::Util::Application &self) override; void uninitialize() override; int getArgc() const; char **getArgv() const; /** * Get the framework storage directory for the CTK plugin * framework. This method is called in the initialize(Poco::Util::Application&) * method. It must not be called without a QCoreApplications instance. * * @return The CTK Plugin Framework storage directory. */ virtual QString getCTKFrameworkStorageDir() const; /** * Initialize the CppMicroServices library. * * The default implementation set the CppMicroServices storage * path to the current ctkPluginConstants::FRAMEWORK_STORAGE property * value. * * This method is called in the initialize(Poco::Util::Application&) * after the CTK Plugin Framework storage directory property * was set. */ virtual void initializeCppMicroServices(); /** * Get the QCoreApplication object. * * This method is called in the initialize(Poco::Util::Application&) * method and must create a QCoreApplication instance if the * global qApp variable is not initialized yet. * * @return The current QCoreApplication instance. This method * never returns null. */ virtual QCoreApplication *getQApplication() const; /** * Add plugin library search paths to the CTK Plugin Framework. * * This method is called in the nitialize(Poco::Util::Application&) * method after getQApplication() was called. */ virtual void initializeLibraryPaths(); /** * Runs the application for which the platform was started. The platform * must be running. *

* The given argument is passed to the application being run. If it is an invalid QVariant * then the command line arguments used in starting the platform, and not consumed * by the platform code, are passed to the application as a QStringList. *

* @param argument the argument passed to the application. May be invalid * @return the result of running the application * @throws std::exception if anything goes wrong */ int main(const std::vector &args) override; /** * Define command line arguments * @param options */ void defineOptions(Poco::Util::OptionSet &options) override; QSharedPointer getFramework() const; ctkPluginContext *getFrameworkContext() const; /** * Get the initial properties for the CTK plugin framework. * * The returned map contains the initial framework properties for * initializing the CTK plugin framework. The value of specific * properties may change at runtime and differ from the initial * value. * * @return The initial CTK Plugin Framework properties. */ QHash getFrameworkProperties() const; /* * Initialize and display the splash screen if an image filename is given * */ void initializeSplashScreen(QCoreApplication * application) const; private: struct Impl; QScopedPointer d; }; } #endif // MITKBASEAPPLICATION_H diff --git a/Modules/AppUtil/src/mitkBaseApplication.cpp b/Modules/AppUtil/src/mitkBaseApplication.cpp index 1d5fb246b9..6ae0058307 100644 --- a/Modules/AppUtil/src/mitkBaseApplication.cpp +++ b/Modules/AppUtil/src/mitkBaseApplication.cpp @@ -1,869 +1,896 @@ /*=================================================================== 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 "mitkBaseApplication.h" #include "mitkLogMacros.h" +#include "mitkExceptionMacro.h" #include "QmitkSafeApplication.h" #include "QmitkSingleApplication.h" #include "mitkProvisioningInfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { QString BaseApplication::ARG_NEWINSTANCE = "BlueBerry.newInstance"; QString BaseApplication::ARG_CLEAN = "BlueBerry.clean"; QString BaseApplication::ARG_APPLICATION = "BlueBerry.application"; QString BaseApplication::ARG_PRODUCT = "BlueBerry.product"; QString BaseApplication::ARG_HOME = "BlueBerry.home"; QString BaseApplication::ARG_STORAGE_DIR = "BlueBerry.storageDir"; QString BaseApplication::ARG_PLUGIN_CACHE = "BlueBerry.plugin_cache_dir"; QString BaseApplication::ARG_PLUGIN_DIRS = "BlueBerry.plugin_dirs"; QString BaseApplication::ARG_FORCE_PLUGIN_INSTALL = "BlueBerry.forcePlugins"; QString BaseApplication::ARG_PRELOAD_LIBRARY = "BlueBerry.preloadLibrary"; QString BaseApplication::ARG_PROVISIONING = "BlueBerry.provisioning"; QString BaseApplication::ARG_DEBUG = "BlueBerry.debug"; QString BaseApplication::ARG_CONSOLELOG = "BlueBerry.consoleLog"; QString BaseApplication::ARG_TESTPLUGIN = "BlueBerry.testplugin"; QString BaseApplication::ARG_TESTAPPLICATION = "BlueBerry.testapplication"; QString BaseApplication::ARG_SPLASH_IMAGE = "BlueBerry.splashscreen"; QString BaseApplication::ARG_NO_REGISTRY_CACHE = "BlueBerry.noRegistryCache"; QString BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING = "BlueBerry.noLazyRegistryCacheLoading"; QString BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE = "BlueBerry.registryMultiLanguage"; QString BaseApplication::ARG_XARGS = "xargs"; QString BaseApplication::PROP_NEWINSTANCE = BaseApplication::ARG_NEWINSTANCE; QString BaseApplication::PROP_FORCE_PLUGIN_INSTALL = BaseApplication::ARG_FORCE_PLUGIN_INSTALL; QString BaseApplication::PROP_NO_REGISTRY_CACHE = BaseApplication::ARG_NO_REGISTRY_CACHE; QString BaseApplication::PROP_NO_LAZY_REGISTRY_CACHE_LOADING = BaseApplication::ARG_NO_LAZY_REGISTRY_CACHE_LOADING; QString BaseApplication::PROP_REGISTRY_MULTI_LANGUAGE = BaseApplication::ARG_REGISTRY_MULTI_LANGUAGE; QString BaseApplication::PROP_PRODUCT = "blueberry.product"; QString BaseApplication::PROP_APPLICATION = "blueberry.application"; QString BaseApplication::PROP_TESTPLUGIN = "BlueBerry.testplugin"; QString BaseApplication::PROP_TESTAPPLICATION = "BlueBerry.testapplication"; static void outputQtMessage(QtMsgType type, const QMessageLogContext &, const QString &msg) { auto message = msg.toStdString(); switch (type) { case QtDebugMsg: MITK_DEBUG << message; break; case QtInfoMsg: MITK_INFO << message; break; case QtWarningMsg: MITK_WARN << message; break; case QtCriticalMsg: MITK_ERROR << message; break; case QtFatalMsg: MITK_ERROR << message; abort(); default: MITK_INFO << message; break; } } class SplashCloserCallback : public QRunnable { public: SplashCloserCallback(QSplashScreen* splashscreen) { this->m_Splashscreen = splashscreen; } void run() override { this->m_Splashscreen->close(); } private: QSplashScreen* m_Splashscreen; }; struct BaseApplication::Impl { ctkProperties m_FWProps; QScopedPointer m_QApp; int m_Argc; char **m_Argv; QString m_AppName; QString m_OrgaName; QString m_OrgaDomain; bool m_SingleMode; bool m_SafeMode; QSplashScreen* m_Splashscreen; SplashCloserCallback* m_SplashscreenClosingCallback; QStringList m_PreloadLibs; QString m_ProvFile; Impl(int argc, char **argv) : m_Argc(argc), m_Argv(argv), m_SingleMode(false), m_SafeMode(true), m_Splashscreen(0), m_SplashscreenClosingCallback(nullptr) { #ifdef Q_OS_MAC /* * This is a workaround for bug 19080: * On Mac OS X the prosess serial number is passed as an commandline argument (-psn_) * if the application is started via the.app bundle. * This option is unknown, which causes a Poco exception. * Since this is done by the system we have to manually remove the argument here. */ int newArgc = m_Argc - 1; char **newArgs = new char *[newArgc]; bool argFound(false); for (int i = 0; i < m_Argc; ++i) { if (QString::fromLatin1(m_Argv[i]).contains("-psn")) { argFound = true; } else { newArgs[i] = m_Argv[i]; } } if (argFound) { m_Argc = newArgc; m_Argv = newArgs; } #endif } QVariant getProperty(const QString &property) const { auto iter = m_FWProps.find(property); return iter == m_FWProps.end() ? QVariant() : iter.value(); } void handleBooleanOption(const std::string &name, const std::string & /*value*/) { QString fwKey = QString::fromStdString(name); // translate some keys to proper framework properties if (fwKey == ARG_CONSOLELOG) { fwKey = ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG; } // For all other options we use the command line option name as the // framework property key. m_FWProps[fwKey] = true; } void handlePreloadLibraryOption(const std::string & /*name*/, const std::string &value) { m_PreloadLibs.push_back(QString::fromStdString(value)); } void handleClean(const std::string & /*name*/, const std::string & /*value*/) { m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN] = ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT; } void initializeCTKPluginFrameworkProperties(Poco::Util::LayeredConfiguration &configuration) { // add all configuration key / value pairs as framework properties Poco::Util::LayeredConfiguration::Keys keys; Poco::Util::LayeredConfiguration::Keys keyStack; configuration.keys(keyStack); std::vector keyChain; while (!keyStack.empty()) { std::string currSubKey = keyStack.back(); if (!keyChain.empty() && keyChain.back() == currSubKey) { keyChain.pop_back(); keyStack.pop_back(); continue; } Poco::Util::LayeredConfiguration::Keys subKeys; configuration.keys(currSubKey, subKeys); if (subKeys.empty()) { keyStack.pop_back(); std::string finalKey; for (auto k = keyChain.begin(); k != keyChain.end(); ++k) { finalKey += *k + "."; } finalKey += currSubKey; keys.push_back(finalKey); } else { keyChain.push_back(currSubKey); for (auto s : subKeys) { keyStack.push_back(s); } } } for (auto key : keys) { QString qKey = QString::fromStdString(key); if (configuration.hasProperty(key)) { // ini and command line options overwrite already inserted keys m_FWProps[qKey] = QString::fromStdString(configuration.getString(key)); } } } void parseProvisioningFile(const QString &filePath) { // Skip parsing if the file path is empty if (filePath.isEmpty()) return; bool consoleLog = this->getProperty(ctkPluginFrameworkLauncher::PROP_CONSOLE_LOG).toBool(); // read initial plugins from a provisioning file QStringList pluginsToStart; QFileInfo provFile(filePath); if (provFile.exists()) { MITK_INFO(consoleLog) << "Using provisioning file: " << qPrintable(provFile.absoluteFilePath()); ProvisioningInfo provInfo(provFile.absoluteFilePath()); // it can still happen, that the encoding is not compatible with the fromUtf8 function ( i.e. when manipulating // the LANG variable // in such case, the QStringList in provInfo is empty which we can easily check for if (provInfo.getPluginDirs().empty()) { MITK_ERROR << "Cannot search for provisioning file, the retrieved directory list is empty.\n" << "This can occur if there are some special (non-ascii) characters in the install path."; } else { foreach (QString pluginPath, provInfo.getPluginDirs()) { ctkPluginFrameworkLauncher::addSearchPath(pluginPath); } // bool forcePluginOverwrite = this->getProperty(ARG_FORCE_PLUGIN_INSTALL).toBool(); QList pluginUrlsToStart = provInfo.getPluginsToStart(); for (auto url : pluginUrlsToStart) { pluginsToStart.push_back(url.toString()); } // foreach(QUrl pluginUrl, provInfo.getPluginsToInstall()) //{ // TODO for "uninstall", we need a proper configuration agent, e.g. a dedicated // plug-in for provisioning of the platform /* if (forcePluginOverwrite) { uninstallPugin(pluginUrl, context); } */ // try //{ // MITK_INFO(consoleLog) << "Installing CTK plug-in from: " << pluginUrl.toString().toStdString(); /* QSharedPointer plugin = context->installPlugin(pluginUrl); if (pluginsToStart.contains(pluginUrl)) { m_CTKPluginsToStart << plugin->getPluginId(); } */ /* } catch (const ctkPluginException& e) { QString errorMsg; QDebug dbg(&errorMsg); dbg << e.printStackTrace(); BERRY_ERROR << qPrintable(errorMsg); } */ //} } } else { MITK_INFO(consoleLog) << "No provisioning file set."; } if (!pluginsToStart.isEmpty()) { m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS] = pluginsToStart; // Use transient start with declared activation policy (this helps when // the provisioning file changes and some plug-ins should not be installed // in the application any more). ctkPlugin::StartOptions startOptions(ctkPlugin::START_TRANSIENT | ctkPlugin::START_ACTIVATION_POLICY); m_FWProps[ctkPluginFrameworkLauncher::PROP_PLUGINS_START_OPTIONS] = static_cast(startOptions); } } }; BaseApplication::BaseApplication(int argc, char **argv) : Application(), d(new Impl(argc, argv)) { } BaseApplication::~BaseApplication() { if (d->m_Splashscreen != 0) { delete(d->m_Splashscreen); } if (d->m_SplashscreenClosingCallback != 0) { delete(d->m_SplashscreenClosingCallback); } } void BaseApplication::printHelp(const std::string & /*name*/, const std::string & /*value*/) { Poco::Util::HelpFormatter help(this->options()); help.setAutoIndent(); help.setCommand(this->commandName()); help.format(std::cout); exit(EXIT_OK); } void BaseApplication::setApplicationName(const QString &name) { if (qApp) { qApp->setApplicationName(name); } d->m_AppName = name; } QString BaseApplication::getApplicationName() const { if (qApp) { return qApp->applicationName(); } return d->m_AppName; } void BaseApplication::setOrganizationName(const QString &name) { if (qApp) { qApp->setOrganizationName(name); } d->m_OrgaName = name; } QString BaseApplication::getOrganizationName() const { if (qApp) return qApp->organizationName(); return d->m_OrgaName; } void BaseApplication::setOrganizationDomain(const QString &domain) { if (qApp) { qApp->setOrganizationDomain(domain); } d->m_OrgaDomain = domain; } QString BaseApplication::getOrganizationDomain() const { if (qApp) return qApp->organizationDomain(); return d->m_OrgaDomain; } void BaseApplication::setSingleMode(bool singleMode) { if (qApp) return; d->m_SingleMode = singleMode; } bool BaseApplication::getSingleMode() const { return d->m_SingleMode; } void BaseApplication::setSafeMode(bool safeMode) { if (qApp && !d->m_QApp) return; d->m_SafeMode = safeMode; if (d->m_QApp) { if (getSingleMode()) { static_cast(d->m_QApp.data())->setSafeMode(safeMode); } else { static_cast(d->m_QApp.data())->setSafeMode(safeMode); } } } bool BaseApplication::getSafeMode() const { return d->m_SafeMode; } void BaseApplication::setPreloadLibraries(const QStringList &libraryBaseNames) { d->m_PreloadLibs = libraryBaseNames; } QStringList BaseApplication::getPreloadLibraries() const { return d->m_PreloadLibs; } void BaseApplication::setProvisioningFilePath(const QString &filePath) { d->m_ProvFile = filePath; } QString BaseApplication::getProvisioningFilePath() const { QString provFilePath = d->m_ProvFile; // A null QString means look up a default provisioning file if (provFilePath.isNull() && qApp) { QFileInfo appFilePath(QCoreApplication::applicationFilePath()); QDir basePath(QCoreApplication::applicationDirPath()); QString provFileName = appFilePath.baseName() + ".provisioning"; QFileInfo provFile(basePath.absoluteFilePath(provFileName)); #ifdef Q_OS_MAC /* * On Mac, if started from the build directory the .provisioning file is located at: * * but the executable path is: * * In this case we have to cdUp threetimes. * * During packaging however the MitkWorkbench.provisioning file is placed at the same * level like the executable, hence nothing has to be done. */ if (!provFile.exists()) { basePath.cdUp(); basePath.cdUp(); basePath.cdUp(); provFile = basePath.absoluteFilePath(provFileName); } #endif if (provFile.exists()) { provFilePath = provFile.absoluteFilePath(); } #ifdef CMAKE_INTDIR else { basePath.cdUp(); provFile.setFile(basePath.absoluteFilePath(provFileName)); if (provFile.exists()) { provFilePath = provFile.absoluteFilePath(); } } #endif } return provFilePath; } void BaseApplication::initializeQt() { if (qApp) return; // If previously parameters have been set we have to store them // to hand them through to the application QString appName = this->getApplicationName(); QString orgName = this->getOrganizationName(); QString orgDomain = this->getOrganizationDomain(); // Create a QCoreApplication instance this->getQApplication(); // provide parameters to QCoreApplication this->setApplicationName(appName); this->setOrganizationName(orgName); this->setOrganizationDomain(orgDomain); qInstallMessageHandler(outputQtMessage); } void BaseApplication::initialize(Poco::Util::Application &self) { // 1. Call the super-class method Poco::Util::Application::initialize(self); // 2. Initialize the Qt framework (by creating a QCoreApplication) this->initializeQt(); // 3. Seed the random number generator, once at startup. QTime time = QTime::currentTime(); qsrand((uint)time.msec()); // 4. Load the "default" configuration, which involves parsing // an optional .ini file and parsing any // command line arguments this->loadConfiguration(); // 5. Add configuration data from the command line and the // optional .ini file as CTK plugin // framework properties. d->initializeCTKPluginFrameworkProperties(this->config()); // 6. Initialize splash screen if an image path is provided // in the .ini file this->initializeSplashScreen(qApp); // 7. Set the custom CTK Plugin Framework storage directory QString storageDir = this->getCTKFrameworkStorageDir(); if (!storageDir.isEmpty()) { d->m_FWProps[ctkPluginConstants::FRAMEWORK_STORAGE] = storageDir; } // 8. Set the library search paths and the pre-load library property this->initializeLibraryPaths(); QStringList preloadLibs = this->getPreloadLibraries(); if (!preloadLibs.isEmpty()) { d->m_FWProps[ctkPluginConstants::FRAMEWORK_PRELOAD_LIBRARIES] = preloadLibs; } // 9. Initialize the CppMicroServices library. // The initializeCppMicroServices() method reuses the // FRAMEWORK_STORAGE property, so we call it after the // getCTKFrameworkStorageDir method. this->initializeCppMicroServices(); // 10. Parse the (optional) provisioning file and set the // correct framework properties. d->parseProvisioningFile(this->getProvisioningFilePath()); // Finally, set the CTK Plugin Framework properties ctkPluginFrameworkLauncher::setFrameworkProperties(d->m_FWProps); } void BaseApplication::uninitialize() { QSharedPointer pfw = this->getFramework(); if (pfw) { pfw->stop(); // wait 10 seconds for the CTK plugin framework to stop pfw->waitForStop(10000); } Poco::Util::Application::uninitialize(); } int BaseApplication::getArgc() const { return d->m_Argc; } char **BaseApplication::getArgv() const { return d->m_Argv; } QString BaseApplication::getCTKFrameworkStorageDir() const { QString storageDir; if (this->getSingleMode()) { // This function checks if an instance is already running // and either sends a message to it (containing the command // line arguments) or checks if a new instance was forced by // providing the BlueBerry.newInstance command line argument. // In the latter case, a path to a temporary directory for // the new application's storage directory is returned. storageDir = handleNewAppInstance( static_cast(d->m_QApp.data()), d->m_Argc, d->m_Argv, ARG_NEWINSTANCE); } if (storageDir.isEmpty()) { // This is a new instance and no other instance is already running. We specify // the storage directory here (this is the same code as in berryInternalPlatform.cpp // so that we can re-use the location for the persistent data location of the // the CppMicroServices library. // Append a hash value of the absolute path of the executable to the data location. // This allows to start the same application from different build or install trees. storageDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/" + this->getOrganizationName() + "/" + this->getApplicationName() + '_'; storageDir += QString::number(qHash(QCoreApplication::applicationDirPath())) + "/"; } return storageDir; } void BaseApplication::initializeCppMicroServices() { QString storageDir = this->getProperty(ctkPluginConstants::FRAMEWORK_STORAGE).toString(); if (!storageDir.isEmpty()) { us::ModuleSettings::SetStoragePath((storageDir + QString("us") + QDir::separator()).toStdString()); } } QCoreApplication *BaseApplication::getQApplication() const { vtkOpenGLRenderWindow::SetGlobalMaximumNumberOfMultiSamples(0); QSurfaceFormat::setDefaultFormat(QVTKOpenGLWidget::defaultFormat()); QCoreApplication *qCoreApp = qApp; // Needed to fix bug #18521, i.e. not responding GUI on Mac OS X with Qt5 #ifdef Q_OS_OSX qCoreApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #endif qCoreApp->setAttribute(Qt::AA_ShareOpenGLContexts); if (!qCoreApp) { if (getSingleMode()) { qCoreApp = new QmitkSingleApplication(d->m_Argc, d->m_Argv, getSafeMode()); } else { auto safeApp = new QmitkSafeApplication(d->m_Argc, d->m_Argv); safeApp->setSafeMode(d->m_SafeMode); qCoreApp = safeApp; } d->m_QApp.reset(qCoreApp); } return qCoreApp; } void BaseApplication::initializeLibraryPaths() { QStringList suffixes; suffixes << "plugins"; #ifdef Q_OS_WINDOWS suffixes << "bin/plugins"; #ifdef CMAKE_INTDIR suffixes << "bin/" CMAKE_INTDIR "/plugins"; #endif #else suffixes << "lib/plugins"; #ifdef CMAKE_INTDIR suffixes << "lib/" CMAKE_INTDIR "/plugins"; #endif #endif #ifdef Q_OS_MAC suffixes << "../../plugins"; #endif // we add a couple of standard library search paths for plug-ins QDir appDir(QCoreApplication::applicationDirPath()); // walk one directory up and add bin and lib sub-dirs; this // might be redundant appDir.cdUp(); foreach (QString suffix, suffixes) { ctkPluginFrameworkLauncher::addSearchPath(appDir.absoluteFilePath(suffix)); } } int BaseApplication::main(const std::vector &args) { // Start the plugin framework and all installed plug-ins according with // their auto-start setting. QStringList arguments; for (auto const &arg : args) { arguments.push_back(QString::fromStdString(arg)); } if (d->m_Splashscreen != 0) { // a splash screen is displayed, // creating the closing callback d->m_SplashscreenClosingCallback = new SplashCloserCallback(d->m_Splashscreen); } return ctkPluginFrameworkLauncher::run(d->m_SplashscreenClosingCallback, QVariant::fromValue(arguments)).toInt(); } void BaseApplication::defineOptions(Poco::Util::OptionSet &options) { Poco::Util::Option helpOption("help", "h", "print this help text"); helpOption.callback(Poco::Util::OptionCallback(this, &BaseApplication::printHelp)); options.addOption(helpOption); Poco::Util::Option newInstanceOption( ARG_NEWINSTANCE.toStdString(), "", "forces a new instance of this application"); newInstanceOption.callback(Poco::Util::OptionCallback(d.data(), &Impl::handleBooleanOption)); options.addOption(newInstanceOption); Poco::Util::Option cleanOption(ARG_CLEAN.toStdString(), "", "cleans the plugin cache"); cleanOption.callback(Poco::Util::OptionCallback(d.data(), &Impl::handleClean)); options.addOption(cleanOption); Poco::Util::Option productOption(ARG_PRODUCT.toStdString(), "", "the id of the product to be launched"); productOption.argument("").binding(PROP_PRODUCT.toStdString()); options.addOption(productOption); Poco::Util::Option appOption( ARG_APPLICATION.toStdString(), "", "the id of the application extension to be executed"); appOption.argument("").binding(PROP_APPLICATION.toStdString()); options.addOption(appOption); Poco::Util::Option provOption(ARG_PROVISIONING.toStdString(), "", "the location of a provisioning file"); provOption.argument("").binding(ARG_PROVISIONING.toStdString()); options.addOption(provOption); Poco::Util::Option storageDirOption( ARG_STORAGE_DIR.toStdString(), "", "the location for storing persistent application data"); storageDirOption.argument("").binding(ctkPluginConstants::FRAMEWORK_STORAGE.toStdString()); options.addOption(storageDirOption); Poco::Util::Option consoleLogOption(ARG_CONSOLELOG.toStdString(), "", "log messages to the console"); consoleLogOption.callback(Poco::Util::OptionCallback(d.data(), &Impl::handleBooleanOption)); options.addOption(consoleLogOption); Poco::Util::Option debugOption(ARG_DEBUG.toStdString(), "", "enable debug mode"); debugOption.argument("", false).binding(ctkPluginFrameworkLauncher::PROP_DEBUG.toStdString()); options.addOption(debugOption); Poco::Util::Option forcePluginOption( ARG_FORCE_PLUGIN_INSTALL.toStdString(), "", "force installing plug-ins with same symbolic name"); forcePluginOption.callback(Poco::Util::OptionCallback(d.data(), &Impl::handleBooleanOption)); options.addOption(forcePluginOption); Poco::Util::Option preloadLibsOption(ARG_PRELOAD_LIBRARY.toStdString(), "", "preload a library"); preloadLibsOption.argument("") .repeatable(true) .callback(Poco::Util::OptionCallback(d.data(), &Impl::handlePreloadLibraryOption)); options.addOption(preloadLibsOption); Poco::Util::Option testPluginOption(ARG_TESTPLUGIN.toStdString(), "", "the plug-in to be tested"); testPluginOption.argument("").binding(PROP_TESTPLUGIN.toStdString()); options.addOption(testPluginOption); Poco::Util::Option testAppOption(ARG_TESTAPPLICATION.toStdString(), "", "the application to be tested"); testAppOption.argument("").binding(PROP_TESTAPPLICATION.toStdString()); options.addOption(testAppOption); Poco::Util::Option noRegistryCacheOption( ARG_NO_REGISTRY_CACHE.toStdString(), "", "do not use a cache for the registry"); noRegistryCacheOption.callback(Poco::Util::OptionCallback(d.data(), &Impl::handleBooleanOption)); options.addOption(noRegistryCacheOption); Poco::Util::Option noLazyRegistryCacheLoadingOption( ARG_NO_LAZY_REGISTRY_CACHE_LOADING.toStdString(), "", "do not use lazy cache loading for the registry"); noLazyRegistryCacheLoadingOption.callback(Poco::Util::OptionCallback(d.data(), &Impl::handleBooleanOption)); options.addOption(noLazyRegistryCacheLoadingOption); Poco::Util::Option registryMultiLanguageOption( ARG_REGISTRY_MULTI_LANGUAGE.toStdString(), "", "enable multi-language support for the registry"); registryMultiLanguageOption.callback(Poco::Util::OptionCallback(d.data(), &Impl::handleBooleanOption)); options.addOption(registryMultiLanguageOption); Poco::Util::Option splashScreenOption(ARG_SPLASH_IMAGE.toStdString(), "", "optional picture to use as a splash screen"); splashScreenOption.argument("").binding(ARG_SPLASH_IMAGE.toStdString()); options.addOption(splashScreenOption); Poco::Util::Option xargsOption(ARG_XARGS.toStdString(), "", "Extended argument list"); xargsOption.argument("").binding(ARG_XARGS.toStdString()); options.addOption(xargsOption); Poco::Util::Application::defineOptions(options); } QSharedPointer BaseApplication::getFramework() const { return ctkPluginFrameworkLauncher::getPluginFramework(); } ctkPluginContext *BaseApplication::getFrameworkContext() const { QSharedPointer framework = getFramework(); if (framework) return framework->getPluginContext(); return nullptr; } void BaseApplication::initializeSplashScreen(QCoreApplication * application) const { QVariant pixmapFileNameProp = d->getProperty(ARG_SPLASH_IMAGE); if (!pixmapFileNameProp.isNull()) { QString pixmapFileName = pixmapFileNameProp.toString(); QFileInfo checkFile(pixmapFileName); if (checkFile.exists() && checkFile.isFile()) { QPixmap pixmap(checkFile.absoluteFilePath()); d->m_Splashscreen = new QSplashScreen(pixmap, Qt::WindowStaysOnTopHint); d->m_Splashscreen->show(); application->processEvents(); } } } QHash BaseApplication::getFrameworkProperties() const { return d->m_FWProps; } int BaseApplication::run() { this->init(d->m_Argc, d->m_Argv); return Application::run(); } void BaseApplication::setProperty(const QString &property, const QVariant &value) { d->m_FWProps[property] = value; } QVariant BaseApplication::getProperty(const QString &property) const { return d->getProperty(property); } + + void BaseApplication::installTranslator(QTranslator* translator) + { + this->getQApplication()->installTranslator(translator); + } + + bool BaseApplication::isRunning() + { + auto app = dynamic_cast(this->getQApplication()); + + if (nullptr != app) + app->isRunning(); + + mitkThrow() << "Method not implemented."; + } + + void BaseApplication::sendMessage(const QByteArray msg) + { + auto app = dynamic_cast(this->getQApplication()); + + if (nullptr != app) + app->sendMessage(msg); + + mitkThrow() << "Method not implemented."; + } + } diff --git a/Modules/Core/src/IO/mitkDicomSeriesReader.cpp b/Modules/Core/src/IO/mitkDicomSeriesReader.cpp index 52b90ef252..90f83ec0e0 100644 --- a/Modules/Core/src/IO/mitkDicomSeriesReader.cpp +++ b/Modules/Core/src/IO/mitkDicomSeriesReader.cpp @@ -1,1861 +1,1864 @@ /*=================================================================== 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. ===================================================================*/ // uncomment for learning more about the internal sorting mechanisms //#define MBILOG_ENABLE_DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mitkProperties.h" namespace mitk { std::string DicomSeriesReader::ReaderImplementationLevelToString(const ReaderImplementationLevel &enumValue) { switch (enumValue) { case ReaderImplementationLevel_Supported: return "Supported"; case ReaderImplementationLevel_PartlySupported: return "PartlySupported"; case ReaderImplementationLevel_Implemented: return "Implemented"; case ReaderImplementationLevel_Unsupported: return "Unsupported"; default: return ""; }; } std::string DicomSeriesReader::PixelSpacingInterpretationToString(const PixelSpacingInterpretation &enumValue) { switch (enumValue) { case PixelSpacingInterpretation_SpacingInPatient: return "In Patient"; case PixelSpacingInterpretation_SpacingAtDetector: return "At Detector"; case PixelSpacingInterpretation_SpacingUnknown: return "Unknown spacing"; default: return ""; }; } const DicomSeriesReader::TagToPropertyMapType &DicomSeriesReader::GetDICOMTagsToMITKPropertyMap() { static bool initialized = false; static TagToPropertyMapType dictionary; if (!initialized) { /* Selection criteria: - no sequences because we cannot represent that - nothing animal related (specied, breed registration number), MITK focusses on human medical image processing. - only general attributes so far When extending this, we should make use of a real dictionary (GDCM/DCMTK and lookup the tag names there) */ // Patient module dictionary["0010|0010"] = "dicom.patient.PatientsName"; dictionary["0010|0020"] = "dicom.patient.PatientID"; dictionary["0010|0030"] = "dicom.patient.PatientsBirthDate"; dictionary["0010|0040"] = "dicom.patient.PatientsSex"; dictionary["0010|0032"] = "dicom.patient.PatientsBirthTime"; dictionary["0010|1000"] = "dicom.patient.OtherPatientIDs"; dictionary["0010|1001"] = "dicom.patient.OtherPatientNames"; dictionary["0010|2160"] = "dicom.patient.EthnicGroup"; dictionary["0010|4000"] = "dicom.patient.PatientComments"; dictionary["0012|0062"] = "dicom.patient.PatientIdentityRemoved"; dictionary["0012|0063"] = "dicom.patient.DeIdentificationMethod"; // General Study module dictionary["0020|000d"] = "dicom.study.StudyInstanceUID"; dictionary["0008|0020"] = "dicom.study.StudyDate"; dictionary["0008|0030"] = "dicom.study.StudyTime"; dictionary["0008|0090"] = "dicom.study.ReferringPhysiciansName"; dictionary["0020|0010"] = "dicom.study.StudyID"; dictionary["0008|0050"] = "dicom.study.AccessionNumber"; dictionary["0008|1030"] = "dicom.study.StudyDescription"; dictionary["0008|1048"] = "dicom.study.PhysiciansOfRecord"; dictionary["0008|1060"] = "dicom.study.NameOfPhysicianReadingStudy"; // General Series module dictionary["0008|0060"] = "dicom.series.Modality"; dictionary["0020|000e"] = "dicom.series.SeriesInstanceUID"; dictionary["0020|0011"] = "dicom.series.SeriesNumber"; dictionary["0020|0060"] = "dicom.series.Laterality"; dictionary["0008|0021"] = "dicom.series.SeriesDate"; dictionary["0008|0031"] = "dicom.series.SeriesTime"; dictionary["0008|1050"] = "dicom.series.PerformingPhysiciansName"; dictionary["0018|1030"] = "dicom.series.ProtocolName"; dictionary["0008|103e"] = "dicom.series.SeriesDescription"; dictionary["0008|1070"] = "dicom.series.OperatorsName"; dictionary["0018|0015"] = "dicom.series.BodyPartExamined"; dictionary["0018|5100"] = "dicom.series.PatientPosition"; dictionary["0028|0108"] = "dicom.series.SmallestPixelValueInSeries"; dictionary["0028|0109"] = "dicom.series.LargestPixelValueInSeries"; // VOI LUT module dictionary["0028|1050"] = "dicom.voilut.WindowCenter"; dictionary["0028|1051"] = "dicom.voilut.WindowWidth"; dictionary["0028|1055"] = "dicom.voilut.WindowCenterAndWidthExplanation"; // Image Pixel module dictionary["0028|0004"] = "dicom.pixel.PhotometricInterpretation"; dictionary["0028|0010"] = "dicom.pixel.Rows"; dictionary["0028|0011"] = "dicom.pixel.Columns"; // Image Plane module dictionary["0028|0030"] = "dicom.PixelSpacing"; dictionary["0018|1164"] = "dicom.ImagerPixelSpacing"; + // Misc + dictionary["0008|0005"] = "dicom.SpecificCharacterSet"; + initialized = true; } return dictionary; } DataNode::Pointer DicomSeriesReader::LoadDicomSeries(const StringContainer &filenames, bool sort, bool check_4d, bool correctTilt, UpdateCallBackMethod callback, Image::Pointer preLoadedImageBlock) { DataNode::Pointer node = DataNode::New(); if (DicomSeriesReader::LoadDicomSeries( filenames, *node, sort, check_4d, correctTilt, callback, preLoadedImageBlock)) { if (filenames.empty()) { return nullptr; } return node; } else { return nullptr; } } bool DicomSeriesReader::LoadDicomSeries(const StringContainer &filenames, DataNode &node, bool sort, bool check_4d, bool correctTilt, UpdateCallBackMethod callback, itk::SmartPointer preLoadedImageBlock) { if (filenames.empty()) { MITK_DEBUG << "Calling LoadDicomSeries with empty filename string container. Probably invalid application logic."; node.SetData(nullptr); return true; // this is not actually an error but the result is very simple } DcmIoType::Pointer io = DcmIoType::New(); try { if (io->CanReadFile(filenames.front().c_str())) { io->SetFileName(filenames.front().c_str()); io->ReadImageInformation(); if (io->GetPixelType() == itk::ImageIOBase::SCALAR || io->GetPixelType() == itk::ImageIOBase::RGB) { LoadDicom(filenames, node, sort, check_4d, correctTilt, callback, preLoadedImageBlock); } if (node.GetData()) { return true; } } } catch (itk::MemoryAllocationError &e) { MITK_ERROR << "Out of memory. Cannot load DICOM series: " << e.what(); } catch (std::exception &e) { MITK_ERROR << "Error encountered when loading DICOM series:" << e.what(); } catch (...) { MITK_ERROR << "Unspecified error encountered when loading DICOM series."; } return false; } bool DicomSeriesReader::IsDicom(const std::string &filename) { DcmIoType::Pointer io = DcmIoType::New(); return io->CanReadFile(filename.c_str()); } bool DicomSeriesReader::IsPhilips3DDicom(const std::string &filename) { DcmIoType::Pointer io = DcmIoType::New(); if (io->CanReadFile(filename.c_str())) { // Look at header Tag 3001,0010 if it is "Philips3D" gdcm::Reader reader; reader.SetFileName(filename.c_str()); reader.Read(); gdcm::DataSet &data_set = reader.GetFile().GetDataSet(); gdcm::StringFilter sf; sf.SetFile(reader.GetFile()); if (data_set.FindDataElement(gdcm::Tag(0x3001, 0x0010)) && (sf.ToString(gdcm::Tag(0x3001, 0x0010)) == "Philips3D ")) { return true; } } return false; } bool DicomSeriesReader::ReadPhilips3DDicom(const std::string &filename, itk::SmartPointer output_image) { // Now get PhilipsSpecific Tags gdcm::PixmapReader reader; reader.SetFileName(filename.c_str()); reader.Read(); gdcm::DataSet &data_set = reader.GetFile().GetDataSet(); gdcm::StringFilter sf; sf.SetFile(reader.GetFile()); gdcm::Attribute<0x0028, 0x0011> dimTagX; // coloumns || sagittal gdcm::Attribute<0x3001, 0x1001, gdcm::VR::UL, gdcm::VM::VM1> dimTagZ; // I have no idea what is VM1. // (Philips specific) // axial gdcm::Attribute<0x0028, 0x0010> dimTagY; // rows || coronal gdcm::Attribute<0x0028, 0x0008> dimTagT; // how many frames gdcm::Attribute<0x0018, 0x602c> spaceTagX; // Spacing in X , unit is "physicalTagx" (usually centimeter) gdcm::Attribute<0x0018, 0x602e> spaceTagY; gdcm::Attribute<0x3001, 0x1003, gdcm::VR::FD, gdcm::VM::VM1> spaceTagZ; // (Philips specific) gdcm::Attribute<0x0018, 0x6024> physicalTagX; // if 3, then spacing params are centimeter gdcm::Attribute<0x0018, 0x6026> physicalTagY; gdcm::Attribute<0x3001, 0x1002, gdcm::VR::US, gdcm::VM::VM1> physicalTagZ; // (Philips specific) dimTagX.Set(data_set); dimTagY.Set(data_set); dimTagZ.Set(data_set); dimTagT.Set(data_set); spaceTagX.Set(data_set); spaceTagY.Set(data_set); spaceTagZ.Set(data_set); physicalTagX.Set(data_set); physicalTagY.Set(data_set); physicalTagZ.Set(data_set); unsigned int dimX = dimTagX.GetValue(), dimY = dimTagY.GetValue(), dimZ = dimTagZ.GetValue(), dimT = dimTagT.GetValue(), physicalX = physicalTagX.GetValue(), physicalY = physicalTagY.GetValue(), physicalZ = physicalTagZ.GetValue(); float spaceX = spaceTagX.GetValue(), spaceY = spaceTagY.GetValue(), spaceZ = spaceTagZ.GetValue(); if (physicalX == 3) // spacing parameter in cm, have to convert it to mm. spaceX = spaceX * 10; if (physicalY == 3) // spacing parameter in cm, have to convert it to mm. spaceY = spaceY * 10; if (physicalZ == 3) // spacing parameter in cm, have to convert it to mm. spaceZ = spaceZ * 10; // Ok, got all necessary Tags! // Now read Pixeldata (7fe0,0010) X x Y x Z x T Elements const gdcm::Pixmap &pixels = reader.GetPixmap(); gdcm::RAWCodec codec; codec.SetPhotometricInterpretation(gdcm::PhotometricInterpretation::MONOCHROME2); codec.SetPixelFormat(pixels.GetPixelFormat()); codec.SetPlanarConfiguration(0); gdcm::DataElement out; codec.Decode(data_set.GetDataElement(gdcm::Tag(0x7fe0, 0x0010)), out); const gdcm::ByteValue *bv = out.GetByteValue(); const char *new_pixels = bv->GetPointer(); // Create MITK Image + Geometry typedef itk::Image ImageType; // Pixeltype might be different sometimes? Maybe read it out from header ImageType::RegionType myRegion; ImageType::SizeType mySize; ImageType::IndexType myIndex; ImageType::SpacingType mySpacing; ImageType::Pointer imageItk = ImageType::New(); mySpacing[0] = spaceX; mySpacing[1] = spaceY; mySpacing[2] = spaceZ; mySpacing[3] = 1; myIndex[0] = 0; myIndex[1] = 0; myIndex[2] = 0; myIndex[3] = 0; mySize[0] = dimX; mySize[1] = dimY; mySize[2] = dimZ; mySize[3] = dimT; myRegion.SetSize(mySize); myRegion.SetIndex(myIndex); imageItk->SetSpacing(mySpacing); imageItk->SetRegions(myRegion); imageItk->Allocate(); imageItk->FillBuffer(0); itk::ImageRegionIterator iterator(imageItk, imageItk->GetLargestPossibleRegion()); iterator.GoToBegin(); unsigned long pixCount = 0; unsigned long planeSize = dimX * dimY; unsigned long planeCount = 0; unsigned long timeCount = 0; unsigned long numberOfSlices = dimZ; while (!iterator.IsAtEnd()) { unsigned long adressedPixel = pixCount + (numberOfSlices - 1 - planeCount) * planeSize // add offset to adress the first pixel of current plane + timeCount * numberOfSlices * planeSize; // add time offset iterator.Set(new_pixels[adressedPixel]); pixCount++; ++iterator; if (pixCount == planeSize) { pixCount = 0; planeCount++; } if (planeCount == numberOfSlices) { planeCount = 0; timeCount++; } if (timeCount == dimT) { break; } } mitk::CastToMitkImage(imageItk, output_image); return true; // actually never returns false yet.. but exception possible } std::string DicomSeriesReader::ConstCharStarToString(const char *s) { return s ? std::string(s) : std::string(); } bool DicomSeriesReader::DICOMStringToSpacing(const std::string &s, ScalarType &spacingX, ScalarType &spacingY) { bool successful = false; std::istringstream spacingReader(s); std::string spacing; if (std::getline(spacingReader, spacing, '\\')) { spacingY = atof(spacing.c_str()); if (std::getline(spacingReader, spacing, '\\')) { spacingX = atof(spacing.c_str()); successful = true; } } return successful; } Point3D DicomSeriesReader::DICOMStringToPoint3D(const std::string &s, bool &successful) { Point3D p; successful = true; std::istringstream originReader(s); std::string coordinate; unsigned int dim(0); while (std::getline(originReader, coordinate, '\\') && dim < 3) { p[dim++] = atof(coordinate.c_str()); } if (dim && dim != 3) { successful = false; MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0032). Found " << dim << " instead of 3 values."; } else if (dim == 0) { successful = false; p.Fill(0.0); // assume default (0,0,0) } return p; } void DicomSeriesReader::DICOMStringToOrientationVectors(const std::string &s, Vector3D &right, Vector3D &up, bool &successful) { successful = true; std::istringstream orientationReader(s); std::string coordinate; unsigned int dim(0); while (std::getline(orientationReader, coordinate, '\\') && dim < 6) { if (dim < 3) { right[dim++] = atof(coordinate.c_str()); } else { up[dim++ - 3] = atof(coordinate.c_str()); } } if (dim && dim != 6) { successful = false; MITK_ERROR << "Reader implementation made wrong assumption on tag (0020,0037). Found " << dim << " instead of 6 values."; } else if (dim == 0) { // fill with defaults right.Fill(0.0); right[0] = 1.0; up.Fill(0.0); up[1] = 1.0; successful = false; } } DicomSeriesReader::SliceGroupingAnalysisResult DicomSeriesReader::AnalyzeFileForITKImageSeriesReaderSpacingAssumption( const StringContainer &files, bool groupImagesWithGantryTilt, const gdcm::Scanner::MappingType &tagValueMappings_) { // result.first = files that fit ITK's assumption // result.second = files that do not fit, should be run through // AnalyzeFileForITKImageSeriesReaderSpacingAssumption() // again SliceGroupingAnalysisResult result; // we const_cast here, because I could not use a map.at(), which would make the code much more readable auto &tagValueMappings = const_cast(tagValueMappings_); const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient) const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt Vector3D fromFirstToSecondOrigin; fromFirstToSecondOrigin.Fill(0.0); bool fromFirstToSecondOriginInitialized(false); Point3D thisOrigin; thisOrigin.Fill(0.0f); Point3D lastOrigin; lastOrigin.Fill(0.0f); Point3D lastDifferentOrigin; lastDifferentOrigin.Fill(0.0f); bool lastOriginInitialized(false); MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "Analyzing files for z-spacing assumption of ITK's ImageSeriesReader (group tilted: " << groupImagesWithGantryTilt << ")"; unsigned int fileIndex(0); for (auto fileIter = files.begin(); fileIter != files.end(); ++fileIter, ++fileIndex) { bool fileFitsIntoPattern(false); std::string thisOriginString; // Read tag value into point3D. PLEASE replace this by appropriate GDCM code if you figure out how to do that thisOriginString = ConstCharStarToString(tagValueMappings[fileIter->c_str()][tagImagePositionPatient]); if (thisOriginString.empty()) { // don't let such files be in a common group. Everything without position information will be loaded as a single // slice: // with standard DICOM files this can happen to: CR, DX, SC MITK_DEBUG << " ==> Sort away " << *fileIter << " for later analysis (no position information)"; // we already have one occupying this position if (result.GetBlockFilenames().empty()) // nothing WITH position information yet { // ==> this is a group of its own, stop processing, come back later result.AddFileToSortedBlock(*fileIter); StringContainer remainingFiles; remainingFiles.insert(remainingFiles.end(), fileIter + 1, files.end()); result.AddFilesToUnsortedBlock(remainingFiles); fileFitsIntoPattern = false; break; // no files anymore } else { // ==> this does not match, consider later result.AddFileToUnsortedBlock(*fileIter); fileFitsIntoPattern = false; continue; // next file } } bool ignoredConversionError(-42); // hard to get here, no graceful way to react thisOrigin = DICOMStringToPoint3D(thisOriginString, ignoredConversionError); MITK_DEBUG << " " << fileIndex << " " << *fileIter << " at " /* << thisOriginString */ << "(" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; if (lastOriginInitialized && (thisOrigin == lastOrigin)) { MITK_DEBUG << " ==> Sort away " << *fileIter << " for separate time step"; // we already have one occupying this position result.AddFileToUnsortedBlock(*fileIter); fileFitsIntoPattern = false; } else { if (!fromFirstToSecondOriginInitialized && lastOriginInitialized) // calculate vector as soon as possible when we get a new position { fromFirstToSecondOrigin = thisOrigin - lastDifferentOrigin; fromFirstToSecondOriginInitialized = true; // Here we calculate if this slice and the previous one are well aligned, // i.e. we test if the previous origin is on a line through the current // origin, directed into the normal direction of the current slice. // If this is NOT the case, then we have a data set with a TILTED GANTRY geometry, // which cannot be simply loaded into a single mitk::Image at the moment. // For this case, we flag this finding in the result and DicomSeriesReader // can correct for that later. Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point DICOMStringToOrientationVectors( tagValueMappings[fileIter->c_str()][tagImageOrientation], right, up, ignoredConversionError); GantryTiltInformation tiltInfo(lastDifferentOrigin, thisOrigin, right, up, 1); if (tiltInfo.IsSheared()) // mitk::eps is too small; 1/1000 of a mm should be enough to detect tilt { /* optimistic approach, accepting gantry tilt: save file for later, check all further files */ // at this point we have TWO slices analyzed! if they are the only two files, we still split, because there // is // no third to verify our tilting assumption. // later with a third being available, we must check if the initial tilting vector is still valid. if yes, // continue. // if NO, we need to split the already sorted part (result.first) and the currently analyzed file // (*fileIter) // tell apart gantry tilt from overall skewedness // sort out irregularly sheared slices, that IS NOT tilting if (groupImagesWithGantryTilt && tiltInfo.IsRegularGantryTilt()) { // check if this is at least roughly the same angle as recorded in DICOM tags if (tagValueMappings[fileIter->c_str()].find(tagGantryTilt) != tagValueMappings[fileIter->c_str()].end()) { // read value, compare to calculated angle std::string tiltStr = ConstCharStarToString(tagValueMappings[fileIter->c_str()][tagGantryTilt]); double angle = atof(tiltStr.c_str()); MITK_DEBUG << "Comparing recorded tilt angle " << angle << " against calculated value " << tiltInfo.GetTiltAngleInDegrees(); // TODO we probably want the signs correct, too (that depends: this is just a rough check, nothing // serious) // TODO TODO TODO when angle -27 and tiltangle 63, this will never trigger the if-clause... useless // check // in this case! old bug..?! if (fabs(angle) - tiltInfo.GetTiltAngleInDegrees() > 0.25) { result.AddFileToUnsortedBlock(*fileIter); // sort away for further analysis fileFitsIntoPattern = false; } else // tilt angle from header is less than 0.25 degrees different from what we calculated, assume this // is // fine { result.FlagGantryTilt(); result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // we cannot check the calculated tilt angle against the one from the dicom header (so we assume we // are // right) { result.FlagGantryTilt(); result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // caller does not want tilt compensation OR shearing is more complicated than tilt { result.AddFileToUnsortedBlock(*fileIter); // sort away for further analysis fileFitsIntoPattern = false; } } else // not sheared { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else if (fromFirstToSecondOriginInitialized) // we already know the offset between slices { Point3D assumedOrigin = lastDifferentOrigin + fromFirstToSecondOrigin; Vector3D originError = assumedOrigin - thisOrigin; double norm = originError.GetNorm(); double toleratedError(0.005); // max. 1/10mm error when measurement crosses 20 slices in z direction if (norm > toleratedError) { MITK_DEBUG << " File does not fit into the inter-slice distance pattern (diff = " << norm << ", allowed " << toleratedError << ")."; MITK_DEBUG << " Expected position (" << assumedOrigin[0] << "," << assumedOrigin[1] << "," << assumedOrigin[2] << "), got position (" << thisOrigin[0] << "," << thisOrigin[1] << "," << thisOrigin[2] << ")"; MITK_DEBUG << " ==> Sort away " << *fileIter << " for later analysis"; // At this point we know we deviated from the expectation of ITK's ImageSeriesReader // We split the input file list at this point, i.e. all files up to this one (excluding it) // are returned as group 1, the remaining files (including the faulty one) are group 2 /* Optimistic approach: check if any of the remaining slices fits in */ result.AddFileToUnsortedBlock(*fileIter); // sort away for further analysis fileFitsIntoPattern = false; } else { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } else // this should be the very first slice { result.AddFileToSortedBlock(*fileIter); // this file is good for current block fileFitsIntoPattern = true; } } // record current origin for reference in later iterations if (!lastOriginInitialized || (fileFitsIntoPattern && (thisOrigin != lastOrigin))) { lastDifferentOrigin = thisOrigin; } lastOrigin = thisOrigin; lastOriginInitialized = true; } if (result.ContainsGantryTilt()) { // check here how many files were grouped. // IF it was only two files AND we assume tiltedness (e.g. save "distance") // THEN we would want to also split the two previous files (simple) because // we don't have any reason to assume they belong together if (result.GetBlockFilenames().size() == 2) { result.UndoPrematureGrouping(); } } return result; } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const StringContainer &files, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { return GetSeries(files, true, groupImagesWithGantryTilt, restrictions); } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const StringContainer &files, bool sortTo3DPlust, bool groupImagesWithGantryTilt, const StringContainer & /*restrictions*/) { /** assumption about this method: returns a map of uid-like-key --> list(filename) each entry should contain filenames that have images of same - series instance uid (automatically done by GDCMSeriesFileNames - 0020,0037 image orientation (patient) - 0028,0030 pixel spacing (x,y) - 0018,0050 slice thickness */ // use GDCM directly, itk::GDCMSeriesFileNames does not work with GDCM 2 // PART I: scan files for sorting relevant DICOM tags, // separate images that differ in any of those // attributes (they cannot possibly form a 3D block) // scan for relevant tags in dicom files gdcm::Scanner scanner; const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP class UID scanner.AddTag(tagSOPClassUID); const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID scanner.AddTag(tagSeriesInstanceUID); const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation scanner.AddTag(tagImageOrientation); const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing scanner.AddTag(tagPixelSpacing); const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing scanner.AddTag(tagImagerPixelSpacing); const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness scanner.AddTag(tagSliceThickness); const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows scanner.AddTag(tagNumberOfRows); const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols scanner.AddTag(tagNumberOfColumns); const gdcm::Tag tagGantryTilt(0x0018, 0x1120); // gantry tilt scanner.AddTag(tagGantryTilt); const gdcm::Tag tagModality(0x0008, 0x0060); // modality scanner.AddTag(tagModality); const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames scanner.AddTag(tagNumberOfFrames); // additional tags read in this scan to allow later analysis // THESE tag are not used for initial separating of files const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient) scanner.AddTag(tagImagePositionPatient); // TODO add further restrictions from arguments (when anybody asks for it) FileNamesGrouping result; // let GDCM scan files if (!scanner.Scan(files)) { MITK_ERROR << "gdcm::Scanner failed when scanning " << files.size() << " input files."; return result; } // assign files IDs that will separate them for loading into image blocks for (auto fileIter = scanner.Begin(); fileIter != scanner.End(); ++fileIter) { if (std::string(fileIter->first).empty()) continue; // TODO understand why Scanner has empty string entries if (std::string(fileIter->first) == std::string("DICOMDIR")) continue; /* sort out multi-frame if ( scanner.GetValue( fileIter->first , tagNumberOfFrames ) ) { MITK_INFO << "Ignoring " << fileIter->first << " because we cannot handle multi-frame images."; continue; } */ // we const_cast here, because I could not use a map.at() function in CreateMoreUniqueSeriesIdentifier. // doing the same thing with find would make the code less readable. Since we forget the Scanner results // anyway after this function, we can simply tolerate empty map entries introduced by bad operator[] access std::string moreUniqueSeriesId = CreateMoreUniqueSeriesIdentifier(const_cast(fileIter->second)); result[moreUniqueSeriesId].AddFile(fileIter->first); } // PART II: sort slices spatially (or at least consistently if this is NOT possible, see method) for (FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter) { try { result[groupIter->first] = ImageBlockDescriptor(SortSeriesSlices(groupIter->second.GetFilenames())); // sort each slice group spatially } catch (...) { MITK_ERROR << "Caught something."; } } // PART III: analyze pre-sorted images for valid blocks (i.e. blocks of equal z-spacing), // separate into multiple blocks if necessary. // // Analysis performs the following steps: // * imitate itk::ImageSeriesReader: use the distance between the first two images as z-spacing // * check what images actually fulfill ITK's z-spacing assumption // * separate all images that fail the test into new blocks, re-iterate analysis for these blocks // * this includes images which DO NOT PROVIDE spatial information, i.e. all images w/o // ImagePositionPatient will be loaded separately FileNamesGrouping groupsOf3DPlusTBlocks; // final result of this function for (FileNamesGrouping::const_iterator groupIter = result.begin(); groupIter != result.end(); ++groupIter) { FileNamesGrouping groupsOf3DBlocks; // intermediate result for only this group(!) StringContainer filesStillToAnalyze = groupIter->second.GetFilenames(); std::string groupUID = groupIter->first; unsigned int subgroup(0); MITK_DEBUG << "Analyze group " << groupUID << " of " << groupIter->second.GetFilenames().size() << " files"; while (!filesStillToAnalyze.empty()) // repeat until all files are grouped somehow { SliceGroupingAnalysisResult analysisResult = AnalyzeFileForITKImageSeriesReaderSpacingAssumption( filesStillToAnalyze, groupImagesWithGantryTilt, scanner.GetMappings()); // enhance the UID for additional groups std::stringstream newGroupUID; newGroupUID << groupUID << '.' << subgroup; ImageBlockDescriptor thisBlock(analysisResult.GetBlockFilenames()); std::string firstFileInBlock = thisBlock.GetFilenames().front(); thisBlock.SetImageBlockUID(newGroupUID.str()); thisBlock.SetSeriesInstanceUID( DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagSeriesInstanceUID))); thisBlock.SetHasGantryTiltCorrected(analysisResult.ContainsGantryTilt()); thisBlock.SetSOPClassUID( DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagSOPClassUID))); thisBlock.SetNumberOfFrames( ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagNumberOfFrames))); thisBlock.SetModality( DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagModality))); thisBlock.SetPixelSpacingInformation( DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagPixelSpacing)), DicomSeriesReader::ConstCharStarToString(scanner.GetValue(firstFileInBlock.c_str(), tagImagerPixelSpacing))); thisBlock.SetHasMultipleTimePoints(false); groupsOf3DBlocks[newGroupUID.str()] = thisBlock; // MITK_DEBUG << "Result: sorted 3D group " << newGroupUID.str() << " with " << groupsOf3DBlocks[ // newGroupUID.str() ].GetFilenames().size() << " files"; MITK_DEBUG << "Result: sorted 3D group with " << groupsOf3DBlocks[newGroupUID.str()].GetFilenames().size() << " files"; StringContainer debugOutputFiles = analysisResult.GetBlockFilenames(); for (StringContainer::const_iterator siter = debugOutputFiles.begin(); siter != debugOutputFiles.end(); ++siter) MITK_DEBUG << " IN " << *siter; ++subgroup; filesStillToAnalyze = analysisResult.GetUnsortedFilenames(); // remember what needs further analysis for (StringContainer::const_iterator siter = filesStillToAnalyze.begin(); siter != filesStillToAnalyze.end(); ++siter) MITK_DEBUG << " OUT " << *siter; } // end of grouping, now post-process groups // PART IV: attempt to group blocks to 3D+t blocks if requested // inspect entries of groupsOf3DBlocks // - if number of files is identical to previous entry, collect for 3D+t block // - as soon as number of files changes from previous entry, record collected blocks as 3D+t block, // start // a new one, continue // decide whether or not to group 3D blocks into 3D+t blocks where possible if (!sortTo3DPlust) { // copy 3D blocks to output groupsOf3DPlusTBlocks.insert(groupsOf3DBlocks.begin(), groupsOf3DBlocks.end()); } else { // sort 3D+t (as described in "PART IV") MITK_DEBUG << "================================================================================"; MITK_DEBUG << "3D+t analysis:"; unsigned int numberOfFilesInPreviousBlock(0); std::string previousBlockKey; for (FileNamesGrouping::const_iterator block3DIter = groupsOf3DBlocks.begin(); block3DIter != groupsOf3DBlocks.end(); ++block3DIter) { unsigned int numberOfFilesInThisBlock = block3DIter->second.GetFilenames().size(); std::string thisBlockKey = block3DIter->first; if (numberOfFilesInPreviousBlock == 0) { numberOfFilesInPreviousBlock = numberOfFilesInThisBlock; groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second; MITK_DEBUG << " 3D+t group " << thisBlockKey; previousBlockKey = thisBlockKey; } else { bool identicalOrigins; try { // check whether this and the previous block share a comon origin // TODO should be safe, but a little try/catch or other error handling wouldn't hurt const char *origin_value = scanner.GetValue(groupsOf3DBlocks[thisBlockKey].GetFilenames().front().c_str(), tagImagePositionPatient), *previous_origin_value = scanner.GetValue( groupsOf3DBlocks[previousBlockKey].GetFilenames().front().c_str(), tagImagePositionPatient), *destination_value = scanner.GetValue( groupsOf3DBlocks[thisBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient), *previous_destination_value = scanner.GetValue( groupsOf3DBlocks[previousBlockKey].GetFilenames().back().c_str(), tagImagePositionPatient); if (!origin_value || !previous_origin_value || !destination_value || !previous_destination_value) { identicalOrigins = false; } else { std::string thisOriginString = ConstCharStarToString(origin_value); std::string previousOriginString = ConstCharStarToString(previous_origin_value); // also compare last origin, because this might differ if z-spacing is different std::string thisDestinationString = ConstCharStarToString(destination_value); std::string previousDestinationString = ConstCharStarToString(previous_destination_value); identicalOrigins = ((thisOriginString == previousOriginString) && (thisDestinationString == previousDestinationString)); } } catch (...) { identicalOrigins = false; } if (identicalOrigins && (numberOfFilesInPreviousBlock == numberOfFilesInThisBlock)) { // group with previous block groupsOf3DPlusTBlocks[previousBlockKey].AddFiles(block3DIter->second.GetFilenames()); groupsOf3DPlusTBlocks[previousBlockKey].SetHasMultipleTimePoints(true); MITK_DEBUG << " --> group enhanced with another timestep"; } else { // start a new block groupsOf3DPlusTBlocks[thisBlockKey] = block3DIter->second; int numberOfTimeSteps = groupsOf3DPlusTBlocks[previousBlockKey].GetFilenames().size() / numberOfFilesInPreviousBlock; MITK_DEBUG << " ==> group closed with " << numberOfTimeSteps << " time steps"; previousBlockKey = thisBlockKey; MITK_DEBUG << " 3D+t group " << thisBlockKey << " started"; } } numberOfFilesInPreviousBlock = numberOfFilesInThisBlock; } } } MITK_DEBUG << "================================================================================"; MITK_DEBUG << "Summary: "; for (FileNamesGrouping::const_iterator groupIter = groupsOf3DPlusTBlocks.begin(); groupIter != groupsOf3DPlusTBlocks.end(); ++groupIter) { ImageBlockDescriptor block = groupIter->second; MITK_DEBUG << " " << block.GetFilenames().size() << " '" << block.GetModality() << "' images (" << block.GetSOPClassUIDAsString() << ") in volume " << block.GetImageBlockUID(); MITK_DEBUG << " (gantry tilt : " << (block.HasGantryTiltCorrected() ? "Yes" : "No") << "; " "pixel spacing : " << PixelSpacingInterpretationToString(block.GetPixelSpacingType()) << "; " "3D+t: " << (block.HasMultipleTimePoints() ? "Yes" : "No") << "; " "reader support: " << ReaderImplementationLevelToString(block.GetReaderImplementationLevel()) << ")"; StringContainer debugOutputFiles = block.GetFilenames(); for (StringContainer::const_iterator siter = debugOutputFiles.begin(); siter != debugOutputFiles.end(); ++siter) MITK_DEBUG << " F " << *siter; } MITK_DEBUG << "================================================================================"; return groupsOf3DPlusTBlocks; } DicomSeriesReader::FileNamesGrouping DicomSeriesReader::GetSeries(const std::string &dir, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { gdcm::Directory directoryLister; directoryLister.Load(dir.c_str(), false); // non-recursive return GetSeries(directoryLister.GetFilenames(), groupImagesWithGantryTilt, restrictions); } std::string DicomSeriesReader::CreateSeriesIdentifierPart(gdcm::Scanner::TagToValue &tagValueMap, const gdcm::Tag &tag) { std::string result; try { result = IDifyTagValue(tagValueMap[tag] ? tagValueMap[tag] : std::string("")); } catch (std::exception &) { // we are happy with even nothing, this will just group images of a series // MITK_WARN << "Could not access tag " << tag << ": " << e.what(); } return result; } std::string DicomSeriesReader::CreateMoreUniqueSeriesIdentifier(gdcm::Scanner::TagToValue &tagValueMap) { const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // image orientation const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing const gdcm::Tag tagSliceThickness(0x0018, 0x0050); // slice thickness const gdcm::Tag tagNumberOfRows(0x0028, 0x0010); // number rows const gdcm::Tag tagNumberOfColumns(0x0028, 0x0011); // number cols const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames const char *tagSeriesInstanceUid = tagValueMap[tagSeriesInstanceUID]; if (!tagSeriesInstanceUid) { mitkThrow() << "CreateMoreUniqueSeriesIdentifier() could not access series instance UID. Something is seriously " "wrong with this image, so stopping here."; } std::string constructedID = tagSeriesInstanceUid; constructedID += CreateSeriesIdentifierPart(tagValueMap, tagNumberOfRows); constructedID += CreateSeriesIdentifierPart(tagValueMap, tagNumberOfColumns); constructedID += CreateSeriesIdentifierPart(tagValueMap, tagPixelSpacing); constructedID += CreateSeriesIdentifierPart(tagValueMap, tagImagerPixelSpacing); constructedID += CreateSeriesIdentifierPart(tagValueMap, tagSliceThickness); constructedID += CreateSeriesIdentifierPart(tagValueMap, tagNumberOfFrames); // be a bit tolerant for orienatation, let only the first few digits matter // (http://bugs.mitk.org/show_bug.cgi?id=12263) // NOT constructedID += CreateSeriesIdentifierPart( tagValueMap, tagImageOrientation ); if (tagValueMap.find(tagImageOrientation) != tagValueMap.end()) { bool conversionError(false); Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); DICOMStringToOrientationVectors(tagValueMap[tagImageOrientation], right, up, conversionError); // string newstring sprintf(simplifiedOrientationString, "%.3f\\%.3f\\%.3f\\%.3f\\%.3f\\%.3f", right[0], // right[1], // right[2], up[0], up[1], up[2]); std::ostringstream ss; ss.setf(std::ios::fixed, std::ios::floatfield); ss.precision(5); ss << right[0] << "\\" << right[1] << "\\" << right[2] << "\\" << up[0] << "\\" << up[1] << "\\" << up[2]; std::string simplifiedOrientationString(ss.str()); constructedID += IDifyTagValue(simplifiedOrientationString); } constructedID.resize(constructedID.length() - 1); // cut of trailing '.' return constructedID; } std::string DicomSeriesReader::IDifyTagValue(const std::string &value) { std::string IDifiedValue(value); if (value.empty()) throw std::logic_error("IDifyTagValue() illegaly called with empty tag value"); // Eliminate non-alnum characters, including whitespace... // that may have been introduced by concats. for (std::size_t i = 0; i < IDifiedValue.size(); i++) { while (i < IDifiedValue.size() && !(IDifiedValue[i] == '.' || (IDifiedValue[i] >= 'a' && IDifiedValue[i] <= 'z') || (IDifiedValue[i] >= '0' && IDifiedValue[i] <= '9') || (IDifiedValue[i] >= 'A' && IDifiedValue[i] <= 'Z'))) { IDifiedValue.erase(i, 1); } } IDifiedValue += "."; return IDifiedValue; } DicomSeriesReader::StringContainer DicomSeriesReader::GetSeries(const std::string &dir, const std::string &series_uid, bool groupImagesWithGantryTilt, const StringContainer &restrictions) { FileNamesGrouping allSeries = GetSeries(dir, groupImagesWithGantryTilt, restrictions); StringContainer resultingFileList; for (FileNamesGrouping::const_iterator idIter = allSeries.begin(); idIter != allSeries.end(); ++idIter) { if (idIter->first.find(series_uid) == 0) // this ID starts with given series_uid { return idIter->second.GetFilenames(); } } return resultingFileList; } DicomSeriesReader::StringContainer DicomSeriesReader::SortSeriesSlices(const StringContainer &unsortedFilenames) { /* we CAN expect a group of equal - series instance uid - image orientation - pixel spacing - imager pixel spacing - slice thickness - number of rows/columns (each piece of information except the rows/columns might be missing) sorting with GdcmSortFunction tries its best by sorting by spatial position and more hints (acquisition number, acquisition time, trigger time) but will always produce a sorting by falling back to SOP Instance UID. */ gdcm::Sorter sorter; sorter.SetSortFunction(DicomSeriesReader::GdcmSortFunction); try { if (sorter.Sort(unsortedFilenames)) { return sorter.GetFilenames(); } else { MITK_WARN << "Sorting error. Leaving series unsorted."; return unsortedFilenames; } } catch (std::logic_error &) { MITK_WARN << "Sorting error. Leaving series unsorted."; return unsortedFilenames; } } bool DicomSeriesReader::GdcmSortFunction(const gdcm::DataSet &ds1, const gdcm::DataSet &ds2) { // This method MUST accept missing location and position information (and all else, too) // because we cannot rely on anything // (restriction on the sentence before: we have to provide consistent sorting, so we // rely on the minimum information all DICOM files need to provide: SOP Instance UID) /* we CAN expect a group of equal - series instance uid - image orientation - pixel spacing - imager pixel spacing - slice thickness - number of rows/columns */ static const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient) static const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation // see if we have Image Position and Orientation if (ds1.FindDataElement(tagImagePositionPatient) && ds1.FindDataElement(tagImageOrientation) && ds2.FindDataElement(tagImagePositionPatient) && ds2.FindDataElement(tagImageOrientation)) { gdcm::Attribute<0x0020, 0x0032> image_pos1; // Image Position (Patient) gdcm::Attribute<0x0020, 0x0037> image_orientation1; // Image Orientation (Patient) image_pos1.Set(ds1); image_orientation1.Set(ds1); gdcm::Attribute<0x0020, 0x0032> image_pos2; gdcm::Attribute<0x0020, 0x0037> image_orientation2; image_pos2.Set(ds2); image_orientation2.Set(ds2); /* we tolerate very small differences in image orientation, since we got to know about acquisitions where these values change across a single series (7th decimal digit) (http://bugs.mitk.org/show_bug.cgi?id=12263) still, we want to check if our assumption of 'almost equal' orientations is valid */ for (unsigned int dim = 0; dim < 6; ++dim) { if (fabs(image_orientation2[dim] - image_orientation1[dim]) > 0.0001) { MITK_ERROR << "Dicom images have different orientations."; throw std::logic_error( "Dicom images have different orientations. Call GetSeries() first to separate images."); } } double normal[3]; normal[0] = image_orientation1[1] * image_orientation1[5] - image_orientation1[2] * image_orientation1[4]; normal[1] = image_orientation1[2] * image_orientation1[3] - image_orientation1[0] * image_orientation1[5]; normal[2] = image_orientation1[0] * image_orientation1[4] - image_orientation1[1] * image_orientation1[3]; double dist1 = 0.0, dist2 = 0.0; // this computes the distance from world origin (0,0,0) ALONG THE NORMAL of the image planes for (unsigned char i = 0u; i < 3u; ++i) { dist1 += normal[i] * image_pos1[i]; dist2 += normal[i] * image_pos2[i]; } // if we can sort by just comparing the distance, we do exactly that if (fabs(dist1 - dist2) >= mitk::eps) { // default: compare position return dist1 < dist2; } else // we need to check more properties to distinguish slices { // try to sort by Acquisition Number static const gdcm::Tag tagAcquisitionNumber(0x0020, 0x0012); if (ds1.FindDataElement(tagAcquisitionNumber) && ds2.FindDataElement(tagAcquisitionNumber)) { gdcm::Attribute<0x0020, 0x0012> acquisition_number1; // Acquisition number gdcm::Attribute<0x0020, 0x0012> acquisition_number2; acquisition_number1.Set(ds1); acquisition_number2.Set(ds2); if (acquisition_number1 != acquisition_number2) { return acquisition_number1 < acquisition_number2; } else // neither position nor acquisition number are good for sorting, so check more { // try to sort by Acquisition Time static const gdcm::Tag tagAcquisitionTime(0x0008, 0x0032); if (ds1.FindDataElement(tagAcquisitionTime) && ds2.FindDataElement(tagAcquisitionTime)) { gdcm::Attribute<0x0008, 0x0032> acquisition_time1; // Acquisition time gdcm::Attribute<0x0008, 0x0032> acquisition_time2; acquisition_time1.Set(ds1); acquisition_time2.Set(ds2); if (acquisition_time1 != acquisition_time2) { return acquisition_time1 < acquisition_time2; } else // we gave up on image position, acquisition number and acquisition time now { // let's try trigger time static const gdcm::Tag tagTriggerTime(0x0018, 0x1060); if (ds1.FindDataElement(tagTriggerTime) && ds2.FindDataElement(tagTriggerTime)) { gdcm::Attribute<0x0018, 0x1060> trigger_time1; // Trigger time gdcm::Attribute<0x0018, 0x1060> trigger_time2; trigger_time1.Set(ds1); trigger_time2.Set(ds2); if (trigger_time1 != trigger_time2) { return trigger_time1 < trigger_time2; } // ELSE! // for this and many previous ifs we fall through if nothing lets us sort } // . } // . } // . } } } } // . // LAST RESORT: all valuable information for sorting is missing. // Sort by some meaningless but unique identifiers to satisfy the sort function static const gdcm::Tag tagSOPInstanceUID(0x0008, 0x0018); if (ds1.FindDataElement(tagSOPInstanceUID) && ds2.FindDataElement(tagSOPInstanceUID)) { MITK_DEBUG << "Dicom images are missing attributes for a meaningful sorting, falling back to SOP instance UID comparison."; gdcm::Attribute<0x0008, 0x0018> SOPInstanceUID1; // SOP instance UID is mandatory and unique gdcm::Attribute<0x0008, 0x0018> SOPInstanceUID2; SOPInstanceUID1.Set(ds1); SOPInstanceUID2.Set(ds2); return SOPInstanceUID1 < SOPInstanceUID2; } else { // no DICOM file should really come down here, this should only be reached with unskillful and unlucky // manipulation // of files std::string error_message("Malformed DICOM images, which do not even contain a SOP Instance UID."); MITK_ERROR << error_message; throw std::logic_error(error_message); } } std::string DicomSeriesReader::GetConfigurationString() { std::stringstream configuration; configuration << "MITK_USE_GDCMIO: "; configuration << "true"; configuration << "\n"; configuration << "GDCM_VERSION: "; #ifdef GDCM_MAJOR_VERSION configuration << GDCM_VERSION; #endif // configuration << "\n"; return configuration.str(); } void DicomSeriesReader::CopyMetaDataToImageProperties(StringContainer filenames, const gdcm::Scanner::MappingType &tagValueMappings_, DcmIoType *io, const ImageBlockDescriptor &blockInfo, Image *image) { std::list imageBlock; imageBlock.push_back(filenames); CopyMetaDataToImageProperties(imageBlock, tagValueMappings_, io, blockInfo, image); } void DicomSeriesReader::CopyMetaDataToImageProperties(std::list imageBlock, const gdcm::Scanner::MappingType &tagValueMappings_, DcmIoType *io, const ImageBlockDescriptor &blockInfo, Image *image) { if (!io || !image) return; StringLookupTable filesForSlices; StringLookupTable sliceLocationForSlices; StringLookupTable instanceNumberForSlices; StringLookupTable SOPInstanceNumberForSlices; auto &tagValueMappings = const_cast(tagValueMappings_); // DICOM tags which should be added to the image properties const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number unsigned int timeStep(0); std::string propertyKeySliceLocation = "dicom.image.0020.1041"; std::string propertyKeyInstanceNumber = "dicom.image.0020.0013"; std::string propertyKeySOPInstanceNumber = "dicom.image.0008.0018"; // tags for each image for (auto i = imageBlock.begin(); i != imageBlock.end(); i++, timeStep++) { const StringContainer &files = (*i); unsigned int slice(0); for (auto fIter = files.begin(); fIter != files.end(); ++fIter, ++slice) { filesForSlices.SetTableValue(slice, *fIter); gdcm::Scanner::TagToValue tagValueMapForFile = tagValueMappings[fIter->c_str()]; if (tagValueMapForFile.find(tagSliceLocation) != tagValueMapForFile.end()) sliceLocationForSlices.SetTableValue(slice, tagValueMapForFile[tagSliceLocation]); if (tagValueMapForFile.find(tagInstanceNumber) != tagValueMapForFile.end()) instanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagInstanceNumber]); if (tagValueMapForFile.find(tagSOPInstanceNumber) != tagValueMapForFile.end()) SOPInstanceNumberForSlices.SetTableValue(slice, tagValueMapForFile[tagSOPInstanceNumber]); } image->SetProperty("files", StringLookupTableProperty::New(filesForSlices)); // If more than one time step add postfix ".t" + timestep if (timeStep != 0) { std::ostringstream postfix; postfix << ".t" << timeStep; propertyKeySliceLocation.append(postfix.str()); propertyKeyInstanceNumber.append(postfix.str()); propertyKeySOPInstanceNumber.append(postfix.str()); } image->SetProperty(propertyKeySliceLocation.c_str(), StringLookupTableProperty::New(sliceLocationForSlices)); image->SetProperty(propertyKeyInstanceNumber.c_str(), StringLookupTableProperty::New(instanceNumberForSlices)); image->SetProperty(propertyKeySOPInstanceNumber.c_str(), StringLookupTableProperty::New(SOPInstanceNumberForSlices)); } // Copy tags for series, study, patient level (leave interpretation to application). // These properties will be copied to the DataNode by DicomSeriesReader. // tags for the series (we just use the one that ITK copied to its dictionary (proably that of the last slice) const itk::MetaDataDictionary &dict = io->GetMetaDataDictionary(); const TagToPropertyMapType &propertyLookup = DicomSeriesReader::GetDICOMTagsToMITKPropertyMap(); auto dictIter = dict.Begin(); while (dictIter != dict.End()) { // MITK_DEBUG << "Key " << dictIter->first; std::string value; if (itk::ExposeMetaData(dict, dictIter->first, value)) { // MITK_DEBUG << "Value " << value; auto valuePosition = propertyLookup.find(dictIter->first); if (valuePosition != propertyLookup.end()) { std::string propertyKey = valuePosition->second; // MITK_DEBUG << "--> " << propertyKey; image->SetProperty(propertyKey.c_str(), StringProperty::New(value)); } } else { MITK_WARN << "Tag " << dictIter->first << " not read as string as expected. Ignoring..."; } ++dictIter; } // copy imageblockdescriptor as properties image->SetProperty("dicomseriesreader.SOPClass", StringProperty::New(blockInfo.GetSOPClassUIDAsString())); image->SetProperty( "dicomseriesreader.ReaderImplementationLevelString", StringProperty::New(ReaderImplementationLevelToString(blockInfo.GetReaderImplementationLevel()))); image->SetProperty("dicomseriesreader.ReaderImplementationLevel", GenericProperty::New(blockInfo.GetReaderImplementationLevel())); image->SetProperty("dicomseriesreader.PixelSpacingInterpretationString", StringProperty::New(PixelSpacingInterpretationToString(blockInfo.GetPixelSpacingType()))); image->SetProperty("dicomseriesreader.PixelSpacingInterpretation", GenericProperty::New(blockInfo.GetPixelSpacingType())); image->SetProperty("dicomseriesreader.MultiFrameImage", BoolProperty::New(blockInfo.IsMultiFrameImage())); image->SetProperty("dicomseriesreader.GantyTiltCorrected", BoolProperty::New(blockInfo.HasGantryTiltCorrected())); image->SetProperty("dicomseriesreader.3D+t", BoolProperty::New(blockInfo.HasMultipleTimePoints())); } void DicomSeriesReader::FixSpacingInformation(mitk::Image *image, const ImageBlockDescriptor &imageBlockDescriptor) { // spacing provided by ITK/GDCM Vector3D imageSpacing = image->GetGeometry()->GetSpacing(); ScalarType imageSpacingX = imageSpacing[0]; ScalarType imageSpacingY = imageSpacing[1]; // spacing as desired by MITK (preference for "in patient", else "on detector", or "1.0/1.0") ScalarType desiredSpacingX = imageSpacingX; ScalarType desiredSpacingY = imageSpacingY; imageBlockDescriptor.GetDesiredMITKImagePixelSpacing(desiredSpacingX, desiredSpacingY); MITK_DEBUG << "Loaded spacing: " << imageSpacingX << "/" << imageSpacingY; MITK_DEBUG << "Corrected spacing: " << desiredSpacingX << "/" << desiredSpacingY; imageSpacing[0] = desiredSpacingX; imageSpacing[1] = desiredSpacingY; image->GetGeometry()->SetSpacing(imageSpacing); } void DicomSeriesReader::LoadDicom(const StringContainer &filenames, DataNode &node, bool sort, bool load4D, bool correctTilt, UpdateCallBackMethod callback, Image::Pointer preLoadedImageBlock) { mitk::LocaleSwitch localeSwitch("C"); std::locale previousCppLocale(std::cin.getloc()); std::locale l("C"); std::cin.imbue(l); ImageBlockDescriptor imageBlockDescriptor; const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image Position (Patient) const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image Orientation const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP class UID const gdcm::Tag tagModality(0x0008, 0x0060); // modality const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // pixel spacing const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // imager pixel spacing const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames try { Image::Pointer image = preLoadedImageBlock.IsNull() ? Image::New() : preLoadedImageBlock; CallbackCommand *command = callback ? new CallbackCommand(callback) : nullptr; bool initialize_node = false; /* special case for Philips 3D+t ultrasound images */ if (DicomSeriesReader::IsPhilips3DDicom(filenames.front().c_str())) { // TODO what about imageBlockDescriptor? // TODO what about preLoadedImageBlock? ReadPhilips3DDicom(filenames.front().c_str(), image); initialize_node = true; } else { /* default case: assume "normal" image blocks, possibly 3D+t */ bool canLoadAs4D(true); gdcm::Scanner scanner; ScanForSliceInformation(filenames, scanner); // need non-const access for map auto &tagValueMappings = const_cast(scanner.GetMappings()); std::list imageBlocks = SortIntoBlocksFor3DplusT(filenames, tagValueMappings, sort, canLoadAs4D); unsigned int volume_count = imageBlocks.size(); imageBlockDescriptor.SetSeriesInstanceUID( DicomSeriesReader::ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagSeriesInstanceUID))); imageBlockDescriptor.SetSOPClassUID( DicomSeriesReader::ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagSOPClassUID))); imageBlockDescriptor.SetModality( DicomSeriesReader::ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagModality))); imageBlockDescriptor.SetNumberOfFrames( ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagNumberOfFrames))); imageBlockDescriptor.SetPixelSpacingInformation( ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagPixelSpacing)), ConstCharStarToString(scanner.GetValue(filenames.front().c_str(), tagImagerPixelSpacing))); GantryTiltInformation tiltInfo; // check possibility of a single slice with many timesteps. In this case, don't check for tilt, no second slice // possible if (!imageBlocks.empty() && imageBlocks.front().size() > 1 && correctTilt) { // check tiltedness here, potentially fixup ITK's loading result by shifting slice contents // check first and last position slice from tags, make some calculations to detect tilt std::string firstFilename(imageBlocks.front().front()); // calculate from first and last slice to minimize rounding errors std::string secondFilename(imageBlocks.front().back()); std::string imagePosition1( ConstCharStarToString(tagValueMappings[firstFilename.c_str()][tagImagePositionPatient])); std::string imageOrientation( ConstCharStarToString(tagValueMappings[firstFilename.c_str()][tagImageOrientation])); std::string imagePosition2( ConstCharStarToString(tagValueMappings[secondFilename.c_str()][tagImagePositionPatient])); bool ignoredConversionError(-42); // hard to get here, no graceful way to react Point3D origin1(DICOMStringToPoint3D(imagePosition1, ignoredConversionError)); Point3D origin2(DICOMStringToPoint3D(imagePosition2, ignoredConversionError)); Vector3D right; right.Fill(0.0); Vector3D up; right.Fill(0.0); // might be down as well, but it is just a name at this point DICOMStringToOrientationVectors(imageOrientation, right, up, ignoredConversionError); tiltInfo = GantryTiltInformation(origin1, origin2, right, up, filenames.size() - 1); correctTilt = tiltInfo.IsSheared() && tiltInfo.IsRegularGantryTilt(); } else { correctTilt = false; // we CANNOT do that } imageBlockDescriptor.SetHasGantryTiltCorrected(correctTilt); if (volume_count == 1 || !canLoadAs4D || !load4D) { DcmIoType::Pointer io; image = MultiplexLoadDICOMByITK( imageBlocks.front(), correctTilt, tiltInfo, io, command, preLoadedImageBlock); // load first 3D block imageBlockDescriptor.AddFiles(imageBlocks.front()); // only the first part is loaded imageBlockDescriptor.SetHasMultipleTimePoints(false); FixSpacingInformation(image, imageBlockDescriptor); CopyMetaDataToImageProperties(imageBlocks.front(), scanner.GetMappings(), io, imageBlockDescriptor, image); initialize_node = true; } else if (volume_count > 1) { imageBlockDescriptor.AddFiles(filenames); // all is loaded imageBlockDescriptor.SetHasMultipleTimePoints(true); DcmIoType::Pointer io; image = MultiplexLoadDICOMByITK4D( imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command, preLoadedImageBlock); initialize_node = true; } } if (initialize_node) { // forward some image properties to node node.GetPropertyList()->ConcatenatePropertyList(image->GetPropertyList(), true); std::string patientName = "NoName"; if (node.GetProperty("dicom.patient.PatientsName")) patientName = node.GetProperty("dicom.patient.PatientsName")->GetValueAsString(); node.SetData(image); node.SetName(patientName); std::cin.imbue(previousCppLocale); } MITK_DEBUG << "--------------------------------------------------------------------------------"; MITK_DEBUG << "DICOM files loaded (from series UID " << imageBlockDescriptor.GetSeriesInstanceUID() << "):"; MITK_DEBUG << " " << imageBlockDescriptor.GetFilenames().size() << " '" << imageBlockDescriptor.GetModality() << "' files (" << imageBlockDescriptor.GetSOPClassUIDAsString() << ") loaded into 1 mitk::Image"; MITK_DEBUG << " multi-frame: " << (imageBlockDescriptor.IsMultiFrameImage() ? "Yes" : "No"); MITK_DEBUG << " reader support: " << ReaderImplementationLevelToString(imageBlockDescriptor.GetReaderImplementationLevel()); MITK_DEBUG << " pixel spacing type: " << PixelSpacingInterpretationToString(imageBlockDescriptor.GetPixelSpacingType()) << " " << image->GetGeometry()->GetSpacing()[0] << "/" << image->GetGeometry()->GetSpacing()[0]; MITK_DEBUG << " gantry tilt corrected: " << (imageBlockDescriptor.HasGantryTiltCorrected() ? "Yes" : "No"); MITK_DEBUG << " 3D+t: " << (imageBlockDescriptor.HasMultipleTimePoints() ? "Yes" : "No"); MITK_DEBUG << "--------------------------------------------------------------------------------"; } catch (std::exception &e) { // reset locale then throw up std::cin.imbue(previousCppLocale); MITK_DEBUG << "Caught exception in DicomSeriesReader::LoadDicom"; throw e; } } void DicomSeriesReader::ScanForSliceInformation(const StringContainer &filenames, gdcm::Scanner &scanner) { const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image position (Patient) scanner.AddTag(tagImagePositionPatient); const gdcm::Tag tagSeriesInstanceUID(0x0020, 0x000e); // Series Instance UID scanner.AddTag(tagSeriesInstanceUID); const gdcm::Tag tagImageOrientation(0x0020, 0x0037); // Image orientation scanner.AddTag(tagImageOrientation); const gdcm::Tag tagSliceLocation(0x0020, 0x1041); // slice location scanner.AddTag(tagSliceLocation); const gdcm::Tag tagInstanceNumber(0x0020, 0x0013); // (image) instance number scanner.AddTag(tagInstanceNumber); const gdcm::Tag tagSOPInstanceNumber(0x0008, 0x0018); // SOP instance number scanner.AddTag(tagSOPInstanceNumber); const gdcm::Tag tagPixelSpacing(0x0028, 0x0030); // Pixel Spacing scanner.AddTag(tagPixelSpacing); const gdcm::Tag tagImagerPixelSpacing(0x0018, 0x1164); // Imager Pixel Spacing scanner.AddTag(tagImagerPixelSpacing); const gdcm::Tag tagModality(0x0008, 0x0060); // Modality scanner.AddTag(tagModality); const gdcm::Tag tagSOPClassUID(0x0008, 0x0016); // SOP Class UID scanner.AddTag(tagSOPClassUID); const gdcm::Tag tagNumberOfFrames(0x0028, 0x0008); // number of frames scanner.AddTag(tagNumberOfFrames); scanner.Scan(filenames); // make available image information for each file } std::list DicomSeriesReader::SortIntoBlocksFor3DplusT( const StringContainer &presortedFilenames, const gdcm::Scanner::MappingType &tagValueMappings, bool /*sort*/, bool &canLoadAs4D) { std::list imageBlocks; // ignore sort request, because most likely re-sorting is now needed due to changes in GetSeries(bug #8022) StringContainer sorted_filenames = DicomSeriesReader::SortSeriesSlices(presortedFilenames); std::string firstPosition; unsigned int numberOfBlocks(0); // number of 3D image blocks static const gdcm::Tag tagImagePositionPatient(0x0020, 0x0032); // Image position (Patient) const gdcm::Tag tagModality(0x0008, 0x0060); // loop files to determine number of image blocks for (StringContainer::const_iterator fileIter = sorted_filenames.begin(); fileIter != sorted_filenames.end(); ++fileIter) { gdcm::Scanner::TagToValue tagToValueMap = tagValueMappings.find(fileIter->c_str())->second; if (tagToValueMap.find(tagImagePositionPatient) == tagToValueMap.end()) { const std::string &modality = tagToValueMap.find(tagModality)->second; if (modality.compare("RTIMAGE ") == 0 || modality.compare("RTIMAGE") == 0) { MITK_WARN << "Modality " << modality << " is not fully supported yet."; numberOfBlocks = 1; break; } else { // we expect to get images w/ missing position information ONLY as separated blocks. assert(presortedFilenames.size() == 1); numberOfBlocks = 1; break; } } std::string position = tagToValueMap.find(tagImagePositionPatient)->second; MITK_DEBUG << " " << *fileIter << " at " << position; if (firstPosition.empty()) { firstPosition = position; } if (position == firstPosition) { ++numberOfBlocks; } else { break; // enough information to know the number of image blocks } } MITK_DEBUG << " ==> Assuming " << numberOfBlocks << " time steps"; if (numberOfBlocks == 0) return imageBlocks; // only possible if called with no files // loop files to sort them into image blocks unsigned int numberOfExpectedSlices(0); for (unsigned int block = 0; block < numberOfBlocks; ++block) { StringContainer filesOfCurrentBlock; for (StringContainer::const_iterator fileIter = sorted_filenames.begin() + block; fileIter != sorted_filenames.end(); // fileIter += numberOfBlocks) // TODO shouldn't this work? give invalid iterators on first attempts ) { filesOfCurrentBlock.push_back(*fileIter); for (unsigned int b = 0; b < numberOfBlocks; ++b) { if (fileIter != sorted_filenames.end()) ++fileIter; } } imageBlocks.push_back(filesOfCurrentBlock); if (block == 0) { numberOfExpectedSlices = filesOfCurrentBlock.size(); } else { if (filesOfCurrentBlock.size() != numberOfExpectedSlices) { MITK_WARN << "DicomSeriesReader expected " << numberOfBlocks << " image blocks of " << numberOfExpectedSlices << " images each. Block " << block << " got " << filesOfCurrentBlock.size() << " instead. Cannot load this as 3D+t"; // TODO implement recovery (load as many slices 3D+t as much // as possible) canLoadAs4D = false; } } } return imageBlocks; } Image::Pointer DicomSeriesReader::MultiplexLoadDICOMByITK(const StringContainer &filenames, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, Image::Pointer preLoadedImageBlock) { io = DcmIoType::New(); io->SetFileName(filenames.front().c_str()); io->ReadImageInformation(); if (io->GetPixelType() == itk::ImageIOBase::SCALAR) { return MultiplexLoadDICOMByITKScalar(filenames, correctTilt, tiltInfo, io, command, preLoadedImageBlock); } else if (io->GetPixelType() == itk::ImageIOBase::RGB) { return MultiplexLoadDICOMByITKRGBPixel(filenames, correctTilt, tiltInfo, io, command, preLoadedImageBlock); } else { return nullptr; } } Image::Pointer DicomSeriesReader::MultiplexLoadDICOMByITK4D(std::list &imageBlocks, ImageBlockDescriptor imageBlockDescriptor, bool correctTilt, const GantryTiltInformation &tiltInfo, DcmIoType::Pointer &io, CallbackCommand *command, Image::Pointer preLoadedImageBlock) { io = DcmIoType::New(); io->SetFileName(imageBlocks.front().front().c_str()); io->ReadImageInformation(); if (io->GetPixelType() == itk::ImageIOBase::SCALAR) { return MultiplexLoadDICOMByITK4DScalar( imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command, preLoadedImageBlock); } else if (io->GetPixelType() == itk::ImageIOBase::RGB) { return MultiplexLoadDICOMByITK4DRGBPixel( imageBlocks, imageBlockDescriptor, correctTilt, tiltInfo, io, command, preLoadedImageBlock); } else { return nullptr; } } } // end namespace mitk diff --git a/Modules/SceneSerialization/src/mitkSceneIO.cpp b/Modules/SceneSerialization/src/mitkSceneIO.cpp index bba0763bf8..265f9ea83e 100644 --- a/Modules/SceneSerialization/src/mitkSceneIO.cpp +++ b/Modules/SceneSerialization/src/mitkSceneIO.cpp @@ -1,542 +1,549 @@ /*=================================================================== 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 #include #include #include #include #include "mitkBaseDataSerializer.h" #include "mitkPropertyListSerializer.h" #include "mitkSceneIO.h" #include "mitkSceneReader.h" #include "mitkBaseRenderer.h" #include "mitkProgressBar.h" #include "mitkRenderingManager.h" #include "mitkStandaloneDataStorage.h" #include #include #include #include #include #include #include #include "itksys/SystemTools.hxx" mitk::SceneIO::SceneIO() : m_WorkingDirectory(""), m_UnzipErrors(0) { } mitk::SceneIO::~SceneIO() { } std::string mitk::SceneIO::CreateEmptyTempDirectory() { mitk::UIDGenerator uidGen("UID_", 6); // std::string returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() + // Poco::Path::separator() + "SceneIOTemp" + uidGen.GetUID(); std::string returnValue = Poco::Path::temp() + "SceneIOTemp" + uidGen.GetUID(); std::string uniquename = returnValue + Poco::Path::separator(); Poco::File tempdir(uniquename); try { bool existsNot = tempdir.createDirectory(); if (!existsNot) { MITK_ERROR << "Warning: Directory already exitsts: " << uniquename << " (choosing another)"; returnValue = mitk::StandardFileLocations::GetInstance()->GetOptionDirectory() + Poco::Path::separator() + "SceneIOTempDirectory" + uidGen.GetUID(); uniquename = returnValue + Poco::Path::separator(); Poco::File tempdir2(uniquename); if (!tempdir2.createDirectory()) { MITK_ERROR << "Warning: Second directory also already exitsts: " << uniquename; } } } catch (std::exception &e) { MITK_ERROR << "Could not create temporary directory " << uniquename << ":" << e.what(); return ""; } return returnValue; } mitk::DataStorage::Pointer mitk::SceneIO::LoadScene(const std::string &filename, DataStorage *pStorage, bool clearStorageFirst) { mitk::LocaleSwitch localeSwitch("C"); // prepare data storage DataStorage::Pointer storage = pStorage; if (storage.IsNull()) { storage = StandaloneDataStorage::New().GetPointer(); } if (clearStorageFirst) { try { storage->Remove(storage->GetAll()); } catch (...) { MITK_ERROR << "DataStorage cannot be cleared properly."; } } // test input filename if (filename.empty()) { MITK_ERROR << "No filename given. Not possible to load scene."; return storage; } // test if filename can be read std::ifstream file(filename.c_str(), std::ios::binary); if (!file.good()) { MITK_ERROR << "Cannot open '" << filename << "' for reading"; return storage; } // get new temporary directory m_WorkingDirectory = CreateEmptyTempDirectory(); if (m_WorkingDirectory.empty()) { MITK_ERROR << "Could not create temporary directory. Cannot open scene files."; return storage; } // unzip all filenames contents to temp dir m_UnzipErrors = 0; Poco::Zip::Decompress unzipper(file, Poco::Path(m_WorkingDirectory)); unzipper.EError += Poco::Delegate>( this, &SceneIO::OnUnzipError); unzipper.EOk += Poco::Delegate>( this, &SceneIO::OnUnzipOk); unzipper.decompressAllFiles(); unzipper.EError -= Poco::Delegate>( this, &SceneIO::OnUnzipError); unzipper.EOk -= Poco::Delegate>( this, &SceneIO::OnUnzipOk); if (m_UnzipErrors) { MITK_ERROR << "There were " << m_UnzipErrors << " errors unzipping '" << filename << "'. Will attempt to read whatever could be unzipped."; } + // transcode locale-dependent string + m_WorkingDirectory = Poco::Path::transcode (m_WorkingDirectory); + // test if index.xml exists // parse index.xml with TinyXML TiXmlDocument document(m_WorkingDirectory + mitk::IOUtil::GetDirectorySeparator() + "index.xml"); if (!document.LoadFile()) { MITK_ERROR << "Could not open/read/parse " << m_WorkingDirectory << mitk::IOUtil::GetDirectorySeparator() << "index.xml\nTinyXML reports: " << document.ErrorDesc() << std::endl; return storage; } SceneReader::Pointer reader = SceneReader::New(); if (!reader->LoadScene(document, m_WorkingDirectory, storage)) { MITK_ERROR << "There were errors while loading scene file " << filename << ". Your data may be corrupted"; } // delete temp directory try { Poco::File deleteDir(m_WorkingDirectory); deleteDir.remove(true); // recursive } catch (...) { MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory; } // return new data storage, even if empty or uncomplete (return as much as possible but notify calling method) return storage; } bool mitk::SceneIO::SaveScene(DataStorage::SetOfObjects::ConstPointer sceneNodes, const DataStorage *storage, const std::string &filename) { if (!sceneNodes) { MITK_ERROR << "No set of nodes given. Not possible to save scene."; return false; } if (!storage) { MITK_ERROR << "No data storage given. Not possible to save scene."; // \TODO: Technically, it would be possible to // save the nodes without their relation return false; } if (filename.empty()) { MITK_ERROR << "No filename given. Not possible to save scene."; return false; } mitk::LocaleSwitch localeSwitch("C"); try { m_FailedNodes = DataStorage::SetOfObjects::New(); m_FailedProperties = PropertyList::New(); // start XML DOM TiXmlDocument document; auto *decl = new TiXmlDeclaration( "1.0", "UTF-8", ""); // TODO what to write here? encoding? standalone would mean that we provide a DTD somewhere... document.LinkEndChild(decl); auto *version = new TiXmlElement("Version"); version->SetAttribute("Writer", __FILE__); version->SetAttribute("Revision", "$Revision: 17055 $"); version->SetAttribute("FileVersion", 1); document.LinkEndChild(version); // DataStorage::SetOfObjects::ConstPointer sceneNodes = storage->GetSubset( predicate ); if (sceneNodes.IsNull()) { MITK_WARN << "Saving empty scene to " << filename; } else { if (sceneNodes->size() == 0) { MITK_WARN << "Saving empty scene to " << filename; } MITK_INFO << "Storing scene with " << sceneNodes->size() << " objects to " << filename; m_WorkingDirectory = CreateEmptyTempDirectory(); if (m_WorkingDirectory.empty()) { MITK_ERROR << "Could not create temporary directory. Cannot create scene files."; return false; } ProgressBar::GetInstance()->AddStepsToDo(sceneNodes->size()); // find out about dependencies typedef std::map UIDMapType; typedef std::map> SourcesMapType; UIDMapType nodeUIDs; // for dependencies: ID of each node SourcesMapType sourceUIDs; // for dependencies: IDs of a node's parent nodes UIDGenerator nodeUIDGen("OBJECT_"); for (auto iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter) { DataNode *node = iter->GetPointer(); if (!node) continue; // unlikely event that we get a nullptr pointer as an object for saving. just ignore // generate UIDs for all source objects DataStorage::SetOfObjects::ConstPointer sourceObjects = storage->GetSources(node); for (auto sourceIter = sourceObjects->begin(); sourceIter != sourceObjects->end(); ++sourceIter) { if (std::find(sceneNodes->begin(), sceneNodes->end(), *sourceIter) == sceneNodes->end()) continue; // source is not saved, so don't generate a UID for this source // create a uid for the parent object if (nodeUIDs[*sourceIter].empty()) { nodeUIDs[*sourceIter] = nodeUIDGen.GetUID(); } // store this dependency for writing sourceUIDs[node].push_back(nodeUIDs[*sourceIter]); } if (nodeUIDs[node].empty()) { nodeUIDs[node] = nodeUIDGen.GetUID(); } } // write out objects, dependencies and properties for (auto iter = sceneNodes->begin(); iter != sceneNodes->end(); ++iter) { DataNode *node = iter->GetPointer(); if (node) { auto *nodeElement = new TiXmlElement("node"); std::string filenameHint(node->GetName()); filenameHint = itksys::SystemTools::MakeCindentifier( filenameHint.c_str()); // escape filename <-- only allow [A-Za-z0-9_], replace everything else with _ // store dependencies auto searchUIDIter = nodeUIDs.find(node); if (searchUIDIter != nodeUIDs.end()) { // store this node's ID nodeElement->SetAttribute("UID", searchUIDIter->second.c_str()); } auto searchSourcesIter = sourceUIDs.find(node); if (searchSourcesIter != sourceUIDs.end()) { // store all source IDs for (auto sourceUIDIter = searchSourcesIter->second.begin(); sourceUIDIter != searchSourcesIter->second.end(); ++sourceUIDIter) { auto *uidElement = new TiXmlElement("source"); uidElement->SetAttribute("UID", sourceUIDIter->c_str()); nodeElement->LinkEndChild(uidElement); } } // store basedata if (BaseData *data = node->GetData()) { // std::string filenameHint( node->GetName() ); bool error(false); TiXmlElement *dataElement(SaveBaseData(data, filenameHint, error)); // returns a reference to a file if (error) { m_FailedNodes->push_back(node); } // store basedata properties PropertyList *propertyList = data->GetPropertyList(); if (propertyList && !propertyList->IsEmpty()) { TiXmlElement *baseDataPropertiesElement( SavePropertyList(propertyList, filenameHint + "-data")); // returns a reference to a file dataElement->LinkEndChild(baseDataPropertiesElement); } nodeElement->LinkEndChild(dataElement); } // store all renderwindow specific propertylists mitk::DataNode::PropertyListKeyNames propertyListKeys = node->GetPropertyListNames(); for (auto renderWindowName : propertyListKeys) { PropertyList *propertyList = node->GetPropertyList(renderWindowName); if (propertyList && !propertyList->IsEmpty()) { TiXmlElement *renderWindowPropertiesElement( SavePropertyList(propertyList, filenameHint + "-" + renderWindowName)); // returns a reference to a file renderWindowPropertiesElement->SetAttribute("renderwindow", renderWindowName); nodeElement->LinkEndChild(renderWindowPropertiesElement); } } // don't forget the renderwindow independent list PropertyList *propertyList = node->GetPropertyList(); if (propertyList && !propertyList->IsEmpty()) { TiXmlElement *propertiesElement( SavePropertyList(propertyList, filenameHint + "-node")); // returns a reference to a file nodeElement->LinkEndChild(propertiesElement); } document.LinkEndChild(nodeElement); } else { MITK_WARN << "Ignoring nullptr node during scene serialization."; } ProgressBar::GetInstance()->Progress(); } // end for all nodes } // end if sceneNodes - if (!document.SaveFile(m_WorkingDirectory + Poco::Path::separator() + "index.xml")) + std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); + + if (!document.SaveFile(defaultLocale_WorkingDirectory + Poco::Path::separator() + "index.xml")) { - MITK_ERROR << "Could not write scene to " << m_WorkingDirectory << Poco::Path::separator() << "index.xml" + MITK_ERROR << "Could not write scene to " << defaultLocale_WorkingDirectory << Poco::Path::separator() << "index.xml" << "\nTinyXML reports '" << document.ErrorDesc() << "'"; return false; } else { try { Poco::File deleteFile(filename.c_str()); if (deleteFile.exists()) { deleteFile.remove(); } // create zip at filename std::ofstream file(filename.c_str(), std::ios::binary | std::ios::out); if (!file.good()) { MITK_ERROR << "Could not open a zip file for writing: '" << filename << "'"; return false; } else { Poco::Zip::Compress zipper(file, true); Poco::Path tmpdir(m_WorkingDirectory); zipper.addRecursive(tmpdir); zipper.close(); } try { Poco::File deleteDir(m_WorkingDirectory); deleteDir.remove(true); // recursive } catch (...) { MITK_ERROR << "Could not delete temporary directory " << m_WorkingDirectory; return false; // ok? } } catch (std::exception &e) { MITK_ERROR << "Could not create ZIP file from " << m_WorkingDirectory << "\nReason: " << e.what(); return false; } return true; } } catch (std::exception &e) { MITK_ERROR << "Caught exception during saving temporary files to disk. Error description: '" << e.what() << "'"; return false; } } TiXmlElement *mitk::SceneIO::SaveBaseData(BaseData *data, const std::string &filenamehint, bool &error) { assert(data); error = true; // find correct serializer // the serializer must // - create a file containing all information to recreate the BaseData object --> needs to know where to put this // file (and a filename?) // - TODO what to do about writers that creates one file per timestep? auto *element = new TiXmlElement("data"); element->SetAttribute("type", data->GetNameOfClass()); // construct name of serializer class std::string serializername(data->GetNameOfClass()); serializername += "Serializer"; std::list thingsThatCanSerializeThis = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (thingsThatCanSerializeThis.size() < 1) { MITK_ERROR << "No serializer found for " << data->GetNameOfClass() << ". Skipping object"; } for (auto iter = thingsThatCanSerializeThis.begin(); iter != thingsThatCanSerializeThis.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { serializer->SetData(data); serializer->SetFilenameHint(filenamehint); - serializer->SetWorkingDirectory(m_WorkingDirectory); + std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); + serializer->SetWorkingDirectory(defaultLocale_WorkingDirectory); try { std::string writtenfilename = serializer->Serialize(); element->SetAttribute("file", writtenfilename); error = false; } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } return element; } TiXmlElement *mitk::SceneIO::SavePropertyList(PropertyList *propertyList, const std::string &filenamehint) { assert(propertyList); // - TODO what to do about shared properties (same object in two lists or behind several keys)? auto *element = new TiXmlElement("properties"); // construct name of serializer class PropertyListSerializer::Pointer serializer = PropertyListSerializer::New(); serializer->SetPropertyList(propertyList); serializer->SetFilenameHint(filenamehint); - serializer->SetWorkingDirectory(m_WorkingDirectory); + std::string defaultLocale_WorkingDirectory = Poco::Path::transcode( m_WorkingDirectory ); + serializer->SetWorkingDirectory(defaultLocale_WorkingDirectory); try { std::string writtenfilename = serializer->Serialize(); element->SetAttribute("file", writtenfilename); PropertyList::Pointer failedProperties = serializer->GetFailedProperties(); if (failedProperties.IsNotNull()) { // move failed properties to global list m_FailedProperties->ConcatenatePropertyList(failedProperties, true); } } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } return element; } const mitk::SceneIO::FailedBaseDataListType *mitk::SceneIO::GetFailedNodes() { return m_FailedNodes.GetPointer(); } const mitk::PropertyList *mitk::SceneIO::GetFailedProperties() { return m_FailedProperties; } void mitk::SceneIO::OnUnzipError(const void * /*pSender*/, std::pair &info) { ++m_UnzipErrors; MITK_ERROR << "Error while unzipping: " << info.second; } void mitk::SceneIO::OnUnzipOk(const void * /*pSender*/, std::pair & /*info*/) { // MITK_INFO << "Unzipped ok: " << info.second.toString(); } diff --git a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp index 014df3c5dd..0d1bea4319 100644 --- a/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp +++ b/Modules/SceneSerialization/src/mitkSceneReaderV1.cpp @@ -1,429 +1,429 @@ /*=================================================================== 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 "mitkSceneReaderV1.h" #include "Poco/Path.h" #include "mitkBaseRenderer.h" #include "mitkIOUtil.h" #include "mitkProgressBar.h" #include "mitkPropertyListDeserializer.h" #include "mitkSerializerMacros.h" #include MITK_REGISTER_SERIALIZER(SceneReaderV1) namespace { typedef std::pair> NodesAndParentsPair; bool NodeSortByLayerIsLessThan(const NodesAndParentsPair &left, const NodesAndParentsPair &right) { if (left.first.IsNotNull() && right.first.IsNotNull()) { int leftLayer; int rightLayer; if (left.first->GetIntProperty("layer", leftLayer) && right.first->GetIntProperty("layer", rightLayer)) { return leftLayer < rightLayer; } else { // fall back to name sort return left.first->GetName() < right.first->GetName(); } } // in all other cases, fall back to stupid pointer comparison // this is not reasonable but at least answers the sorting // question clearly return left.first.GetPointer() < right.first.GetPointer(); } } bool mitk::SceneReaderV1::LoadScene(TiXmlDocument &document, const std::string &workingDirectory, DataStorage *storage) { assert(storage); bool error(false); // TODO prepare to detect errors (such as cycles) from wrongly written or edited xml files // Get number of elements to initialze progress bar // 1. if there is a element, // - construct a name for the appropriate serializer // - try to instantiate this serializer via itk object factory // - if serializer could be created, use it to read the file into a BaseData object // - if successful, call the new node's SetData(..) // create a node for the tag "data" and test if node was created typedef std::vector DataNodeVector; DataNodeVector DataNodes; unsigned int listSize = 0; for (TiXmlElement *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { ++listSize; } ProgressBar::GetInstance()->AddStepsToDo(listSize * 2); for (TiXmlElement *element = document.FirstChildElement("node"); element != nullptr; element = element->NextSiblingElement("node")) { DataNodes.push_back(LoadBaseDataFromDataTag(element->FirstChildElement("data"), workingDirectory, error)); ProgressBar::GetInstance()->Progress(); } // iterate all nodes // first level nodes should be elements auto nit = DataNodes.begin(); for (TiXmlElement *element = document.FirstChildElement("node"); element != nullptr || nit != DataNodes.end(); element = element->NextSiblingElement("node"), ++nit) { mitk::DataNode::Pointer node = *nit; // in case dataXmlElement is valid test whether it containts the "properties" child tag // and process further if and only if yes TiXmlElement *dataXmlElement = element->FirstChildElement("data"); if (dataXmlElement && dataXmlElement->FirstChildElement("properties")) { TiXmlElement *baseDataElement = dataXmlElement->FirstChildElement("properties"); if (node->GetData()) { DecorateBaseDataWithProperties(node->GetData(), baseDataElement, workingDirectory); } else { MITK_WARN << "BaseData properties stored in scene file, but BaseData could not be read" << std::endl; } } // 2. check child nodes const char *uida = element->Attribute("UID"); std::string uid(""); if (uida) { uid = uida; m_NodeForID[uid] = node.GetPointer(); m_IDForNode[node.GetPointer()] = uid; } else { MITK_ERROR << "No UID found for current node. Node will have no parents."; error = true; } // 3. if there are nodes, // - instantiate the appropriate PropertyListDeSerializer // - use them to construct PropertyList objects // - add these properties to the node (if necessary, use renderwindow name) bool success = DecorateNodeWithProperties(node, element, workingDirectory); if (!success) { MITK_ERROR << "Could not load properties for node."; error = true; } // remember node for later adding to DataStorage m_OrderedNodePairs.push_back(std::make_pair(node, std::list())); // 4. if there are elements, remember parent objects for (TiXmlElement *source = element->FirstChildElement("source"); source != nullptr; source = source->NextSiblingElement("source")) { const char *sourceUID = source->Attribute("UID"); if (sourceUID) { m_OrderedNodePairs.back().second.push_back(std::string(sourceUID)); } } ProgressBar::GetInstance()->Progress(); } // end for all // sort our nodes by their "layer" property // (to be inserted in that order) m_OrderedNodePairs.sort(&NodeSortByLayerIsLessThan); // remove all unknown parent UIDs for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end();) { if (m_NodeForID.find(*parentsIter) == m_NodeForID.end()) { parentsIter = nodesIter->second.erase(parentsIter); MITK_WARN << "Found a DataNode with unknown parents. Will add it to DataStorage without any parent objects."; error = true; } else { ++parentsIter; } } } // repeat the following loop ... // ... for all created nodes unsigned int lastMapSize(0); while (lastMapSize != m_OrderedNodePairs .size()) // this is to prevent infinite loops; each iteration must at least add one node to DataStorage { lastMapSize = m_OrderedNodePairs.size(); // iterate (layer) ordered nodes backwards // we insert the highest layers first for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { bool addThisNode(true); // if any parent node is not yet in DataStorage, skip node for now and check later for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { if (!storage->Exists(m_NodeForID[*parentsIter])) { addThisNode = false; break; } } if (addThisNode) { DataStorage::SetOfObjects::Pointer parents = DataStorage::SetOfObjects::New(); for (auto parentsIter = nodesIter->second.begin(); parentsIter != nodesIter->second.end(); ++parentsIter) { parents->push_back(m_NodeForID[*parentsIter]); } // if all parents are found in datastorage (or are unknown), add node to DataStorage storage->Add(nodesIter->first, parents); // remove this node from m_OrderedNodePairs m_OrderedNodePairs.erase(nodesIter); // break this for loop because iterators are probably invalid break; } } } // All nodes that are still in m_OrderedNodePairs at this point are not part of a proper directed graph structure. // We'll add such nodes without any parent information. for (auto nodesIter = m_OrderedNodePairs.begin(); nodesIter != m_OrderedNodePairs.end(); ++nodesIter) { storage->Add(nodesIter->first); MITK_WARN << "Encountered node that is not part of a directed graph structure. Will be added to DataStorage " "without parents."; error = true; } return !error; } mitk::DataNode::Pointer mitk::SceneReaderV1::LoadBaseDataFromDataTag(TiXmlElement *dataElement, const std::string &workingDirectory, bool &error) { DataNode::Pointer node; if (dataElement) { const char *filename = dataElement->Attribute("file"); - if (filename) + if (filename && strlen(filename) != 0) { try { std::vector baseData = IOUtil::Load(workingDirectory + Poco::Path::separator() + filename); if (baseData.size() > 1) { MITK_WARN << "Discarding multiple base data results from " << filename << " except the first one."; } node = DataNode::New(); node->SetData(baseData.front()); } catch (std::exception &e) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Exception says: " << e.what(); error = true; } if (node.IsNull()) { MITK_ERROR << "Error during attempt to read '" << filename << "'. Factory returned nullptr object."; error = true; } } } // in case there was no element we create a new empty node (for appending a propertylist later) if (node.IsNull()) { node = DataNode::New(); } return node; } void mitk::SceneReaderV1::ClearNodePropertyListWithExceptions(DataNode &node, PropertyList &propertyList) { // Basically call propertyList.Clear(), but implement exceptions (see bug 19354) BaseData *data = node.GetData(); PropertyList::Pointer propertiesToKeep = PropertyList::New(); if (dynamic_cast(data)) { /* Older scene files (before changes of bug 17547) could contain a RenderingMode property with value "LevelWindow_Color". Since bug 17547 this value has been removed and replaced by the default value LookupTable_LevelWindow_Color. This new default value does only result in "black-to-white" CT images (or others) if there is a corresponding lookup table. Such a lookup table is provided as a default value by the Image mapper. Since that value was never present in older scene files, we do well in not removing the new default value here. Otherwise the mapper would fall back to another default which is all the colors of the rainbow :-( */ BaseProperty::Pointer lutProperty = propertyList.GetProperty("LookupTable"); propertiesToKeep->SetProperty("LookupTable", lutProperty); /* Older scene files (before changes of T14807) may contain multi-component images without the "Image.Displayed Component" property. As the treatment as multi-component image and the corresponding visualization options hinges on that property we should not delete it, if it was added by the mapper. Old diffusion images might contain the "DisplayChannel" property which stores the same information, however ignoring it is an acceptable loss of precision as usually which channel is selected is not terribly important. This is a fix for the issue reported in T19919. */ BaseProperty::Pointer compProperty = propertyList.GetProperty("Image.Displayed Component"); if (compProperty.IsNotNull()) { propertiesToKeep->SetProperty("Image.Displayed Component", compProperty); } } propertyList.Clear(); propertyList.ConcatenatePropertyList(propertiesToKeep); } bool mitk::SceneReaderV1::DecorateNodeWithProperties(DataNode *node, TiXmlElement *nodeElement, const std::string &workingDirectory) { assert(node); assert(nodeElement); bool error(false); for (TiXmlElement *properties = nodeElement->FirstChildElement("properties"); properties != nullptr; properties = properties->NextSiblingElement("properties")) { const char *propertiesfilea(properties->Attribute("file")); std::string propertiesfile(propertiesfilea ? propertiesfilea : ""); const char *renderwindowa(properties->Attribute("renderwindow")); std::string renderwindow(renderwindowa ? renderwindowa : ""); PropertyList::Pointer propertyList = node->GetPropertyList(renderwindow); // DataNode implementation always returns a propertylist ClearNodePropertyListWithExceptions(*node, *propertyList); // use deserializer to construct new properties PropertyListDeserializer::Pointer deserializer = PropertyListDeserializer::New(); deserializer->SetFilename(workingDirectory + Poco::Path::separator() + propertiesfile); bool success = deserializer->Deserialize(); error |= !success; PropertyList::Pointer readProperties = deserializer->GetOutput(); if (readProperties.IsNotNull()) { propertyList->ConcatenatePropertyList(readProperties, true); // true = replace } else { MITK_ERROR << "Property list reader did not return a property list. This is an implementation error. Please tell " "your developer."; error = true; } } return !error; } bool mitk::SceneReaderV1::DecorateBaseDataWithProperties(BaseData::Pointer data, TiXmlElement *baseDataNodeElem, const std::string &workingDir) { // check given variables, initialize error variable assert(baseDataNodeElem); bool error(false); // get the file name stored in the tag const char *baseDataPropertyFile(baseDataNodeElem->Attribute("file")); // check if the filename was found if (baseDataPropertyFile) { // PropertyList::Pointer dataPropList = data->GetPropertyList(); PropertyListDeserializer::Pointer propertyDeserializer = PropertyListDeserializer::New(); // initialize the property reader propertyDeserializer->SetFilename(workingDir + Poco::Path::separator() + baseDataPropertyFile); bool ioSuccess = propertyDeserializer->Deserialize(); error = !ioSuccess; // get the output PropertyList::Pointer inProperties = propertyDeserializer->GetOutput(); // store the read-in properties to the given node or throw error otherwise if (inProperties.IsNotNull()) { data->SetPropertyList(inProperties); } else { MITK_ERROR << "The property deserializer did not return a (valid) property list."; error = true; } } else { MITK_ERROR << "Function DecorateBaseDataWithProperties(...) called with false TiXmlElement. \n \t ->Given element " "does not contain a 'file' attribute. \n"; error = true; } return !error; } diff --git a/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.cpp b/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.cpp index 43ad2a7c93..5e626d7836 100644 --- a/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.cpp +++ b/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.cpp @@ -1,79 +1,79 @@ /*=================================================================== 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 "QmitkUSAbstractCustomWidget.h" #include #include std::string QmitkUSAbstractCustomWidget::US_DEVICE_PROPKEY_CLASS() { - static std::string s = "ork.mitk.services.UltrasoundCustomWidget.deviceClass"; + static std::string s = "org.mitk.services.UltrasoundCustomWidget.deviceClass"; return s; } QmitkUSAbstractCustomWidget::QmitkUSAbstractCustomWidget(QWidget* parent) : QWidget(parent), m_PrototypeServiceFactory(0), m_IsClonedForQt(false) { } QmitkUSAbstractCustomWidget::~QmitkUSAbstractCustomWidget() { delete m_PrototypeServiceFactory; } void QmitkUSAbstractCustomWidget::SetDevice(mitk::USDevice::Pointer device) { m_Device = device; if ( device ) { this->OnDeviceSet(); } } mitk::USDevice::Pointer QmitkUSAbstractCustomWidget::GetDevice() const { return m_Device; } QmitkUSAbstractCustomWidget* QmitkUSAbstractCustomWidget::CloneForQt(QWidget* parent) const { QmitkUSAbstractCustomWidget* clonedWidget = this->Clone(parent); clonedWidget->Initialize(); // initialize the Qt stuff of the widget clonedWidget->m_IsClonedForQt = true; // set flag that this object was really cloned return clonedWidget; } us::ServiceProperties QmitkUSAbstractCustomWidget::GetServiceProperties() const { us::ServiceProperties result; result[QmitkUSAbstractCustomWidget::US_DEVICE_PROPKEY_CLASS()] = this->GetDeviceClass(); return result; } void QmitkUSAbstractCustomWidget::showEvent ( QShowEvent * event ) { // using object from micro service directly in Qt without cloning it first // can cause problems when Qt deletes this object -> throw an exception to // show that object should be cloned before if ( ! m_IsClonedForQt ) { MITK_ERROR << "Object wasn't cloned with CloneForQt() before using as QWidget."; mitkThrow() << "Object wasn't cloned with CloneForQt() before using as QWidget."; } QWidget::showEvent(event); } diff --git a/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.h b/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.h index c07c8857db..7f8eb49674 100644 --- a/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.h +++ b/Modules/USUI/Qmitk/QmitkUSAbstractCustomWidget.h @@ -1,157 +1,157 @@ /*=================================================================== 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. ===================================================================*/ #ifndef QmitkUSAbstractCustomWidget_h #define QmitkUSAbstractCustomWidget_h #include #include #include "mitkUSDevice.h" // Microservices #include #include #include #include namespace us { struct PrototypeServiceFactory; class ModuleContext; } /** * \brief Abstract superclass for all custom control widgets of mitk::USDevice classes. * * The custom control widgets are made available using a us::PrototypeServiceFactory. This means that each * concrete subclass should be registered in the microservice by calling * QmitkUSAbstractCustomWidget::RegisterService() on an object. The best place for doing this would be in * the corresponding module or plugin activator. * * Afterwards a copy of the registered object can be obtained from the microservice as shown in the example * below. Do not forget to call QmitkUSAbstractCustomWidget::CloneForQt() on the object received from the * microservice. This is necessary to allow deleting the object as it is necessary in Qt for removing it from * a layout. * * * Subclasses must implement three methods: * - QmitkUSAbstractCustomWidget::OnDeviceSet() -> should handle initialization when mitk:USDevice was set * - QmitkUSAbstractCustomWidget::GetDeviceClass() -> must return device class of corresponding mitk::USDevice * - QmitkUSAbstractCustomWidget::Clone() -> must create a copy of the current object * * * The code to use a custom control widget in a plugin can look like this: * * \code * ctkPluginContext* pluginContext = // get the plugig context * mitk::USDevice device = // get the ultrasound device * * // get service references for ultrasound device - * std::string filter = "(ork.mitk.services.UltrasoundCustomWidget.deviceClass=" + device->GetDeviceClass() + ")"; + * std::string filter = "(org.mitk.services.UltrasoundCustomWidget.deviceClass=" + device->GetDeviceClass() + ")"; * QString interfaceName ( us_service_interface_iid() ); * QList serviceRefs = pluginContext->getServiceReferences(interfaceName, QString::fromStdString(filter)); * * if (serviceRefs.size() > 0) * { * // get widget from the service and make sure that it is cloned, so that * // it can be deleted if it should be removed from the GUI later * QmitkUSAbstractCustomWidget* widget = pluginContext->getService * (serviceRefs.at(0))->CloneForQt(parentWidget); * // now the widget can be used like any other QWidget * } * \endcode */ class MITKUSUI_EXPORT QmitkUSAbstractCustomWidget : public QWidget { Q_OBJECT public: QmitkUSAbstractCustomWidget(QWidget* parent = 0); virtual ~QmitkUSAbstractCustomWidget(); void SetDevice(mitk::USDevice::Pointer device); mitk::USDevice::Pointer GetDevice() const; /** * \brief Called every time a mitk::USDevice was set with QmitkUSAbstractCustomWidget::SetDevice(). * A sublcass can implement this function to handle initialiation actions * necessary when a device was set. */ virtual void OnDeviceSet() = 0; /** * \brief Subclass must implement this method to return device class of corresponding mitk::USDevice. * * \return same value as mitk::USDevice::GetDeviceClass() of the corresponding mitk::USDevice */ virtual std::string GetDeviceClass() const = 0; /** * \brief Subclass must implement this method to return a pointer to a copy of the object. */ virtual QmitkUSAbstractCustomWidget* Clone(QWidget* parent = 0) const = 0; /** * \brief Method for initializing the Qt stuff of the widget (setupUI, connect). * This method will be called in CloneForQt() and has to be implemented by concrete * subclasses. * \warning All Qt initialization stuff belongs into this method rather than in the constructor. */ virtual void Initialize() = 0; /** * \brief Return pointer to copy of the object. * Internally use of QmitkUSAbstractCustomWidget::Clone() with additionaly * setting an internal flag that the object was really cloned. */ QmitkUSAbstractCustomWidget* CloneForQt(QWidget* parent = 0) const; /** * \brief Returns the properties of the micro service. * Properties consist of just the device class of the corresponding * mitk::USDevice. */ us::ServiceProperties GetServiceProperties() const; /** * \brief Overwritten Qt even method. * It is checked if the object was cloned with * QmitkUSAbstractCustomWidget::CloneForQt() before. An exception is thrown * if not. This is done, because using the object from micro service directly * in Qt without cloning it first can cause problems after Qt deleted the * object. * * \throws mitk::Exception */ void showEvent ( QShowEvent * event ) override; /** * \brief Property key for the class name of corresponding us device object. */ static std::string US_DEVICE_PROPKEY_CLASS(); private: mitk::USDevice::Pointer m_Device; us::PrototypeServiceFactory* m_PrototypeServiceFactory; bool m_IsClonedForQt; }; // This is the microservice declaration. Do not meddle! MITK_DECLARE_SERVICE_INTERFACE(QmitkUSAbstractCustomWidget, "org.mitk.QmitkUSAbstractCustomWidget") #endif // QmitkUSAbstractCustomWidget_h diff --git a/Plugins/org.mitk.gui.qt.common/src/internal/QmitkViewCoordinator.cpp b/Plugins/org.mitk.gui.qt.common/src/internal/QmitkViewCoordinator.cpp index 0d152a43b5..513edecd85 100644 --- a/Plugins/org.mitk.gui.qt.common/src/internal/QmitkViewCoordinator.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/internal/QmitkViewCoordinator.cpp @@ -1,205 +1,217 @@ /*=================================================================== 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 "QmitkViewCoordinator.h" #include "QmitkAbstractView.h" #include #include #include #include #include QmitkViewCoordinator::QmitkViewCoordinator() : m_ActiveZombieView(nullptr) , m_ActiveRenderWindowPart(nullptr) , m_VisibleRenderWindowPart(nullptr) { } QmitkViewCoordinator::~QmitkViewCoordinator() { } void QmitkViewCoordinator::Start() { berry::PlatformUI::GetWorkbench()->AddWindowListener(this); QList wnds(berry::PlatformUI::GetWorkbench()->GetWorkbenchWindows()); for (QList::iterator i = wnds.begin(); i != wnds.end(); ++i) { (*i)->GetPartService()->AddPartListener(this); } } void QmitkViewCoordinator::Stop() { if (!berry::PlatformUI::IsWorkbenchRunning()) return; berry::PlatformUI::GetWorkbench()->RemoveWindowListener(this); QList wnds(berry::PlatformUI::GetWorkbench()->GetWorkbenchWindows()); for (QList::iterator i = wnds.begin(); i != wnds.end(); ++i) { (*i)->GetPartService()->RemovePartListener(this); } } berry::IPartListener::Events::Types QmitkViewCoordinator::GetPartEventTypes() const { return berry::IPartListener::Events::ACTIVATED | berry::IPartListener::Events::DEACTIVATED | berry::IPartListener::Events::CLOSED | berry::IPartListener::Events::HIDDEN | berry::IPartListener::Events::VISIBLE | berry::IPartListener::Events::OPENED; } void QmitkViewCoordinator::PartActivated(const berry::IWorkbenchPartReference::Pointer& partRef ) { //MITK_INFO << "*** PartActivated (" << partRef->GetPart(false)->GetPartName() << ")"; berry::IWorkbenchPart* part = partRef->GetPart(false).GetPointer(); // Check for a render window part and inform IRenderWindowPartListener views // that it was activated if ( mitk::IRenderWindowPart* renderPart = dynamic_cast(part) ) { if (m_VisibleRenderWindowPart != renderPart) { RenderWindowPartActivated(renderPart); m_ActiveRenderWindowPart = renderPart; m_VisibleRenderWindowPart = renderPart; } } // Check if the activated part wants to be notified if (mitk::ILifecycleAwarePart* lifecycleAwarePart = dynamic_cast(part)) { lifecycleAwarePart->Activated(); } // Check if a zombie view has been activated. if (mitk::IZombieViewPart* zombieView = dynamic_cast(part)) { if (m_ActiveZombieView && (m_ActiveZombieView != zombieView)) { // Another zombie view has been activated. Tell the old one about it. m_ActiveZombieView->ActivatedZombieView(partRef); m_ActiveZombieView = zombieView; } } } void QmitkViewCoordinator::PartDeactivated(const berry::IWorkbenchPartReference::Pointer& partRef ) { //MITK_INFO << "*** PartDeactivated (" << partRef->GetPart(false)->GetPartName() << ")"; berry::IWorkbenchPart* part = partRef->GetPart(false).GetPointer(); + // Check for a render window part and inform IRenderWindowPartListener views + // that it was deactivated + if (mitk::IRenderWindowPart* renderPart = dynamic_cast(part)) + { + if (m_ActiveRenderWindowPart == renderPart) + { + this->RenderWindowPartDeactivated(renderPart); + m_ActiveRenderWindowPart = nullptr; + m_VisibleRenderWindowPart = nullptr; + } + } + if (mitk::ILifecycleAwarePart* lifecycleAwarePart = dynamic_cast(part)) { lifecycleAwarePart->Deactivated(); } } void QmitkViewCoordinator::PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef ) { //MITK_INFO << "*** PartOpened (" << partRef->GetPart(false)->GetPartName() << ")"; berry::IWorkbenchPart* part = partRef->GetPart(false).GetPointer(); if (mitk::IRenderWindowPartListener* renderWindowListener = dynamic_cast(part)) { m_RenderWindowListeners.insert(renderWindowListener); } } void QmitkViewCoordinator::PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef ) { //MITK_INFO << "*** PartClosed (" << partRef->GetPart(false)->GetPartName() << ")"; berry::IWorkbenchPart* part = partRef->GetPart(false).GetPointer(); if (mitk::IRenderWindowPartListener* renderWindowListener = dynamic_cast(part)) { m_RenderWindowListeners.remove(renderWindowListener); } } void QmitkViewCoordinator::PartHidden(const berry::IWorkbenchPartReference::Pointer& partRef ) { //MITK_INFO << "*** PartHidden (" << partRef->GetPart(false)->GetPartName() << ")"; berry::IWorkbenchPart* part = partRef->GetPart(false).GetPointer(); // Check for a render window part and if it is the currently active on. // Inform IRenderWindowPartListener views that it has been hidden. if ( mitk::IRenderWindowPart* renderPart = dynamic_cast(part) ) { if (!m_ActiveRenderWindowPart && m_VisibleRenderWindowPart == renderPart) { RenderWindowPartDeactivated(renderPart); m_VisibleRenderWindowPart = nullptr; } } if (mitk::ILifecycleAwarePart* lifecycleAwarePart = dynamic_cast(part)) { lifecycleAwarePart->Hidden(); } } void QmitkViewCoordinator::PartVisible(const berry::IWorkbenchPartReference::Pointer& partRef ) { //MITK_INFO << "*** PartVisible (" << partRef->GetPart(false)->GetPartName() << ")"; berry::IWorkbenchPart* part = partRef->GetPart(false).GetPointer(); // Check for a render window part and inform IRenderWindowPartListener views // that it was activated if ( mitk::IRenderWindowPart* renderPart = dynamic_cast(part) ) { if (!m_ActiveRenderWindowPart) { RenderWindowPartActivated(renderPart); m_VisibleRenderWindowPart = renderPart; } } if (mitk::ILifecycleAwarePart* lifecycleAwarePart = dynamic_cast(part)) { lifecycleAwarePart->Visible(); } } void QmitkViewCoordinator::RenderWindowPartActivated(mitk::IRenderWindowPart* renderPart) { foreach (mitk::IRenderWindowPartListener* l, m_RenderWindowListeners) { l->RenderWindowPartActivated(renderPart); } } void QmitkViewCoordinator::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderPart) { foreach (mitk::IRenderWindowPartListener* l, m_RenderWindowListeners) { l->RenderWindowPartDeactivated(renderPart); } } void QmitkViewCoordinator::WindowClosed(const berry::IWorkbenchWindow::Pointer& /*window*/ ) { } void QmitkViewCoordinator::WindowOpened(const berry::IWorkbenchWindow::Pointer& window ) { window->GetPartService()->AddPartListener(this); } diff --git a/Plugins/org.mitk.gui.qt.ultrasound/src/internal/UltrasoundSupport.cpp b/Plugins/org.mitk.gui.qt.ultrasound/src/internal/UltrasoundSupport.cpp index 1d133e4393..26af2211b7 100644 --- a/Plugins/org.mitk.gui.qt.ultrasound/src/internal/UltrasoundSupport.cpp +++ b/Plugins/org.mitk.gui.qt.ultrasound/src/internal/UltrasoundSupport.cpp @@ -1,697 +1,697 @@ /*=================================================================== 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. ===================================================================*/ // Blueberry #include #include #include //Mitk #include #include #include #include #include #include #include #include "QmitkRegisterClasses.h" #include "QmitkRenderWindow.h" #include #include // Qmitk #include "UltrasoundSupport.h" // Qt #include #include #include // Ultrasound #include "mitkUSDevice.h" #include "QmitkUSAbstractCustomWidget.h" #include #include #include "usServiceReference.h" #include "internal/org_mitk_gui_qt_ultrasound_Activator.h" #include "mitkNodePredicateDataType.h" #include const std::string UltrasoundSupport::VIEW_ID = "org.mitk.views.ultrasoundsupport"; void UltrasoundSupport::SetFocus() { } void UltrasoundSupport::CreateQtPartControl(QWidget *parent) { //initialize timers m_UpdateTimer = new QTimer(this); m_RenderingTimer2d = new QTimer(this); m_RenderingTimer3d = new QTimer(this); // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); //load persistence data before connecting slots (so no slots are called in this phase...) LoadUISettings(); //connect signals and slots... connect(m_Controls.m_DeviceManagerWidget, SIGNAL(NewDeviceButtonClicked()), this, SLOT(OnClickedAddNewDevice())); // Change Widget Visibilities connect(m_Controls.m_DeviceManagerWidget, SIGNAL(NewDeviceButtonClicked()), this->m_Controls.m_NewVideoDeviceWidget, SLOT(CreateNewDevice())); // Init NewDeviceWidget connect(m_Controls.m_ActiveVideoDevices, SIGNAL(ServiceSelectionChanged(us::ServiceReferenceU)), this, SLOT(OnChangedActiveDevice())); connect(m_Controls.m_RunImageTimer, SIGNAL(clicked()), this, SLOT(OnChangedActiveDevice())); connect(m_Controls.m_ShowImageStream, SIGNAL(clicked()), this, SLOT(OnChangedActiveDevice())); connect(m_Controls.m_NewVideoDeviceWidget, SIGNAL(Finished()), this, SLOT(OnNewDeviceWidgetDone())); // After NewDeviceWidget finished editing connect(m_Controls.m_FrameRatePipeline, SIGNAL(valueChanged(int)), this, SLOT(OnChangedFramerateLimit())); connect(m_Controls.m_FrameRate2d, SIGNAL(valueChanged(int)), this, SLOT(OnChangedFramerateLimit())); connect(m_Controls.m_FrameRate3d, SIGNAL(valueChanged(int)), this, SLOT(OnChangedFramerateLimit())); connect(m_Controls.m_FreezeButton, SIGNAL(clicked()), this, SLOT(OnClickedFreezeButton())); connect(m_UpdateTimer, SIGNAL(timeout()), this, SLOT(UpdateImage())); connect(m_RenderingTimer2d, SIGNAL(timeout()), this, SLOT(RenderImage2d())); connect(m_RenderingTimer3d, SIGNAL(timeout()), this, SLOT(RenderImage3d())); connect(m_Controls.m_Update2DView, SIGNAL(clicked()), this, SLOT(StartTimers())); connect(m_Controls.m_Update3DView, SIGNAL(clicked()), this, SLOT(StartTimers())); connect(m_Controls.m_DeviceManagerWidget, SIGNAL(EditDeviceButtonClicked(mitk::USDevice::Pointer)), this, SLOT(OnClickedEditDevice())); //Change Widget Visibilities connect(m_Controls.m_DeviceManagerWidget, SIGNAL(EditDeviceButtonClicked(mitk::USDevice::Pointer)), this->m_Controls.m_NewVideoDeviceWidget, SLOT(EditDevice(mitk::USDevice::Pointer))); // Initializations m_Controls.m_NewVideoDeviceWidget->setVisible(false); std::string filter = "(&(" + us::ServiceConstants::OBJECTCLASS() + "=" + "org.mitk.services.UltrasoundDevice)(" + mitk::USDevice::GetPropertyKeys().US_PROPKEY_ISACTIVE + "=true))"; m_Controls.m_ActiveVideoDevices->Initialize( mitk::USDevice::GetPropertyKeys().US_PROPKEY_LABEL, filter); m_Controls.m_ActiveVideoDevices->SetAutomaticallySelectFirstEntry(true); m_FrameCounterPipeline = 0; m_FrameCounter2d = 0; m_FrameCounter3d = 0; m_Controls.tabWidget->setTabEnabled(1, false); } void UltrasoundSupport::CreateWindows() { auto pausViewer = GetSite()->GetWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.photoacoustics.pausviewer"); if (pausViewer != nullptr) { m_PausViewerView = dynamic_cast(pausViewer.GetPointer()); m_PausViewerView->SetPADataStorage(m_PADataStorage); m_PausViewerView->SetUSDataStorage(m_USDataStorage); m_PausViewerView->InitWindows(); m_PausViewerView->SetUltrasoundReference(&m_PausViewerView); UpdateLevelWindows(); m_ForceRequestUpdateAll = true; } } void UltrasoundSupport::InitNewNode() { m_Node.push_back(nullptr); auto& Node = m_Node.back(); Node = mitk::DataNode::New(); Node->SetName("No Data received yet ..."); //create a dummy image (gray values 0..255) for correct initialization of level window, etc. mitk::Image::Pointer dummyImage = mitk::ImageGenerator::GenerateRandomImage(100, 100, 1, 1, 1, 1, 1, 255, 0); Node->SetData(dummyImage); m_OldGeometry = dynamic_cast(dummyImage->GetGeometry()); // Add to Control Datastorage this->GetDataStorage()->Add(Node); // Add to Display Datastorage if (m_Node.size() > 1) m_USDataStorage->Add(Node); else if (m_Node.size() == 1) m_PADataStorage->Add(Node); } void UltrasoundSupport::DestroyLastNode() { auto& Node = m_Node.back(); // Remove from Control Datastorage this->GetDataStorage()->Remove(Node); // Remove from Display Datastorage if (m_Node.size() > 1) m_USDataStorage->Remove(Node); else if (m_Node.size() == 1) m_PADataStorage->Remove(Node); // clean up Node->ReleaseData(); m_Node.pop_back(); } void UltrasoundSupport::UpdateLevelWindows() { mitk::LevelWindow levelWindow; if (m_Node.size() > 1) { for (unsigned int index = 0; index < m_AmountOfOutputs; ++index) { m_Node.at(index)->GetLevelWindow(levelWindow); if (!m_curOutput.at(index)->IsEmpty()) //levelWindow.SetWindowBounds(0, 4 * (index+1)); levelWindow.SetToImageRange(m_curOutput.at(index)); m_Node.at(index)->SetLevelWindow(levelWindow); } } else if (m_Node.size() == 1) { m_Node.back()->GetLevelWindow(levelWindow); if (!m_curOutput.at(0)->IsEmpty()) levelWindow.SetToImageRange(m_curOutput.at(0)); m_Node.back()->SetLevelWindow(levelWindow); } } void UltrasoundSupport::SetColormap(mitk::DataNode::Pointer node, mitk::LookupTable::LookupTableType type) { // consider removing this, unused for now, and probably forever mitk::LookupTable::Pointer lookupTable = mitk::LookupTable::New(); mitk::LookupTableProperty::Pointer lookupTableProperty = mitk::LookupTableProperty::New(); lookupTable->SetType(type); lookupTableProperty->SetLookupTable(lookupTable); node->SetProperty("LookupTable", lookupTableProperty); mitk::RenderingModeProperty::Pointer renderingMode = dynamic_cast(node->GetProperty("Image Rendering.Mode")); renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); } void UltrasoundSupport::OnClickedAddNewDevice() { m_Controls.m_NewVideoDeviceWidget->setVisible(true); m_Controls.m_DeviceManagerWidget->setVisible(false); m_Controls.m_Headline->setText("Add New Video Device:"); m_Controls.m_WidgetActiveDevices->setVisible(false); } void UltrasoundSupport::OnClickedEditDevice() { m_Controls.m_NewVideoDeviceWidget->setVisible(true); m_Controls.m_DeviceManagerWidget->setVisible(false); m_Controls.m_WidgetActiveDevices->setVisible(false); m_Controls.m_Headline->setText("Edit Video Device:"); } void UltrasoundSupport::UpdateAmountOfOutputs() { // Update the amount of Nodes; there should be one Node for every slide that is set. Note that we must check whether the slices are set, // just using the m_Image->dimension(3) will produce nullpointers on slices of the image that were not set bool isSet = true; m_AmountOfOutputs = 0; while (isSet) { isSet = m_Image->IsSliceSet(m_AmountOfOutputs); if (isSet) ++m_AmountOfOutputs; } // correct the amount of Nodes to display data while (m_Node.size() < m_AmountOfOutputs) { InitNewNode(); } while (m_Node.size() > m_AmountOfOutputs) { DestroyLastNode(); } // correct the amount of image outputs that we feed the nodes with while (m_curOutput.size() < m_AmountOfOutputs) { m_curOutput.push_back(mitk::Image::New()); // initialize the slice images as 2d images with the size of m_Images unsigned int* dimOld = m_Image->GetDimensions(); unsigned int dim[2] = { dimOld[0], dimOld[1] }; m_curOutput.back()->Initialize(m_Image->GetPixelType(), 2, dim); } while (m_curOutput.size() > m_AmountOfOutputs) { m_curOutput.pop_back(); } } void UltrasoundSupport::UpdateImage() { if(m_Controls.m_ShowImageStream->isChecked()) { m_Device->Modified(); m_Device->Update(); // Update device m_Image = m_Device->GetOutput(); // get the Image data to display UpdateAmountOfOutputs(); // create as many Nodes and Outputs as there are slices in m_Image if (m_AmountOfOutputs == 0) return; // if there is no image to be displayed, skip the rest of this method for (unsigned int index = 0; index < m_AmountOfOutputs; ++index) { if (m_curOutput.at(index)->GetDimension(0) != m_Image->GetDimension(0) || m_curOutput.at(index)->GetDimension(1) != m_Image->GetDimension(1) || m_curOutput.at(index)->GetDimension(2) != m_Image->GetDimension(2) || m_curOutput.at(index)->GetPixelType() != m_Image->GetPixelType()) { unsigned int* dimOld = m_Image->GetDimensions(); unsigned int dim[2] = { dimOld[0], dimOld[1]}; m_curOutput.at(index)->Initialize(m_Image->GetPixelType(), 2, dim); // if we switched image resolution or type the outputs must be reinitialized! } if (!m_Image->IsEmpty()) { mitk::ImageReadAccessor inputReadAccessor(m_Image, m_Image->GetSliceData(index,0,0,nullptr,mitk::Image::ReferenceMemory)); // just reference the slices, to get a small performance gain m_curOutput.at(index)->SetSlice(inputReadAccessor.GetData()); m_curOutput.at(index)->GetGeometry()->SetIndexToWorldTransform(m_Image->GetSlicedGeometry()->GetIndexToWorldTransform()); // Update the image Output with seperate slices } if (m_curOutput.at(index)->IsEmpty()) { m_Node.at(index)->SetName("No Data received yet ..."); // create a noise image for correct initialization of level window, etc. mitk::Image::Pointer randomImage = mitk::ImageGenerator::GenerateRandomImage(32, 32, 1, 1, 1, 1, 1, 255, 0); m_Node.at(index)->SetData(randomImage); m_curOutput.at(index)->SetGeometry(randomImage->GetGeometry()); } else { char name[30]; sprintf(name, "US Viewing Stream - Image %d", index); m_Node.at(index)->SetName(name); m_Node.at(index)->SetData(m_curOutput.at(index)); // set the name of the Output } } float eps = 0.000001f; // if the geometry changed: reinitialize the ultrasound image. we use the m_curOutput.at(0) to readjust the geometry if (((m_OldGeometry.IsNotNull()) && (m_curOutput.at(0)->GetGeometry() != nullptr)) && ( (abs(m_OldGeometry->GetSpacing()[0] - m_curOutput.at(0)->GetGeometry()->GetSpacing()[0]) > eps )|| (abs(m_OldGeometry->GetSpacing()[1] - m_curOutput.at(0)->GetGeometry()->GetSpacing()[1]) > eps )|| (abs(m_OldGeometry->GetCenter()[0] - m_curOutput.at(0)->GetGeometry()->GetCenter()[0]) > eps) || (abs(m_OldGeometry->GetCenter()[1] - m_curOutput.at(0)->GetGeometry()->GetCenter()[1]) > eps) || m_ForceRequestUpdateAll)) { MITK_INFO << "now"; mitk::IRenderWindowPart* renderWindow = this->GetRenderWindowPart(); if ((renderWindow != nullptr) && (m_curOutput.at(0)->GetTimeGeometry()->IsValid()) && (m_Controls.m_ShowImageStream->isChecked())) { renderWindow->GetRenderingManager()->InitializeViews( m_curOutput.at(0)->GetGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); renderWindow->GetRenderingManager()->RequestUpdateAll(); } m_CurrentImageWidth = m_curOutput.at(0)->GetDimension(0); m_CurrentImageHeight = m_curOutput.at(0)->GetDimension(1); m_OldGeometry = dynamic_cast(m_curOutput.at(0)->GetGeometry()); UpdateLevelWindows(); m_ForceRequestUpdateAll = false; } } //Update frame counter m_FrameCounterPipeline++; if (m_FrameCounterPipeline >= 10) { // compute framerate of pipeline update int nMilliseconds = m_Clock.restart(); int fps = 10000.0f / (nMilliseconds); m_FPSPipeline = fps; m_FrameCounterPipeline = 0; // display lowest framerate in UI int lowestFPS = m_FPSPipeline; if (m_Controls.m_Update2DView->isChecked() && (m_FPS2d < lowestFPS)) { lowestFPS = m_FPS2d; } if (m_Controls.m_Update3DView->isChecked() && (m_FPS3d < lowestFPS)) { lowestFPS = m_FPS3d; } m_Controls.m_FramerateLabel->setText("Current Framerate: " + QString::number(lowestFPS) + " FPS"); } } void UltrasoundSupport::RenderImage2d() { if (!m_Controls.m_Update2DView->isChecked()) return; if (m_PausViewerView != nullptr) { auto renderingManager = mitk::RenderingManager::GetInstance(); renderingManager->RequestUpdate(m_PausViewerView->GetPARenderWindow()); renderingManager->RequestUpdate(m_PausViewerView->GetUSRenderWindow()); } else { CreateWindows(); // try to check whether the PausViwer has been opened by now } //mitk::IRenderWindowPart* renderWindow = this->GetRenderWindowPart(); //renderWindow->GetRenderingManager()->RequestUpdate(mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))->GetRenderWindow()); // TODO: figure out how to proceed with the standard display //this->RequestRenderWindowUpdate(mitk::RenderingManager::REQUEST_UPDATE_2DWINDOWS); m_FrameCounter2d++; if (m_FrameCounter2d >= 10) { // compute framerate of 2d render window update int nMilliseconds = m_Clock2d.restart(); int fps = 10000.0f / (nMilliseconds); m_FPS2d = fps; m_FrameCounter2d = 0; } } void UltrasoundSupport::RenderImage3d() { if (!m_Controls.m_Update3DView->isChecked()) return; this->RequestRenderWindowUpdate(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); m_FrameCounter3d++; if (m_FrameCounter3d >= 10) { // compute framerate of 2d render window update int nMilliseconds = m_Clock3d.restart(); int fps = 10000.0f / (nMilliseconds); m_FPS3d = fps; m_FrameCounter3d = 0; } } void UltrasoundSupport::OnChangedFramerateLimit() { StopTimers(); int intervalPipeline = (1000 / m_Controls.m_FrameRatePipeline->value()); int interval2D = (1000 / m_Controls.m_FrameRate2d->value()); int interval3D = (1000 / m_Controls.m_FrameRate3d->value()); SetTimerIntervals(intervalPipeline, interval2D, interval3D); StartTimers(); } void UltrasoundSupport::OnClickedFreezeButton() { if (m_Device.IsNull()) { MITK_WARN("UltrasoundSupport") << "Freeze button clicked though no device is selected."; return; } if (m_Device->GetIsFreezed()) { m_Device->SetIsFreezed(false); m_Controls.m_FreezeButton->setText("Freeze"); } else { m_Device->SetIsFreezed(true); m_Controls.m_FreezeButton->setText("Start Viewing Again"); } } void UltrasoundSupport::OnChangedActiveDevice() { //clean up, delete nodes and stop timer StopTimers(); this->RemoveControlWidgets(); for (auto& Node : m_Node) { this->GetDataStorage()->Remove(Node); Node->ReleaseData(); } m_Node.clear(); // disconnect from the PausViewer m_PausViewerView = nullptr; //get current device, abort if it is invalid m_Device = m_Controls.m_ActiveVideoDevices->GetSelectedService(); if (m_Device.IsNull()) { m_Controls.tabWidget->setTabEnabled(1, false); return; } //create the widgets for this device and enable the widget tab this->CreateControlWidgets(); m_Controls.tabWidget->setTabEnabled(1, true); //start timer if (m_Controls.m_RunImageTimer->isChecked()) { int intervalPipeline = (1000 / m_Controls.m_FrameRatePipeline->value()); int interval2D = (1000 / m_Controls.m_FrameRate2d->value()); int interval3D = (1000 / m_Controls.m_FrameRate3d->value()); SetTimerIntervals(intervalPipeline, interval2D, interval3D); StartTimers(); m_Controls.m_TimerWidget->setEnabled(true); } else { m_Controls.m_TimerWidget->setEnabled(false); } // connect to PausViewer and Set it up CreateWindows(); } void UltrasoundSupport::OnNewDeviceWidgetDone() { m_Controls.m_NewVideoDeviceWidget->setVisible(false); m_Controls.m_DeviceManagerWidget->setVisible(true); m_Controls.m_Headline->setText("Ultrasound Devices:"); m_Controls.m_WidgetActiveDevices->setVisible(true); } void UltrasoundSupport::CreateControlWidgets() { m_ControlProbesWidget = new QmitkUSControlsProbesWidget(m_Device->GetControlInterfaceProbes(), m_Controls.m_ToolBoxControlWidgets); m_Controls.probesWidgetContainer->addWidget(m_ControlProbesWidget); // create b mode widget for current device m_ControlBModeWidget = new QmitkUSControlsBModeWidget(m_Device->GetControlInterfaceBMode(), m_Controls.m_ToolBoxControlWidgets); if (m_Device->GetControlInterfaceBMode()) { m_Controls.m_ToolBoxControlWidgets->addItem(m_ControlBModeWidget, "B Mode Controls"); //m_Controls.m_ToolBoxControlWidgets->setItemEnabled(m_Controls.m_ToolBoxControlWidgets->count() - 1, false); } // create doppler widget for current device m_ControlDopplerWidget = new QmitkUSControlsDopplerWidget(m_Device->GetControlInterfaceDoppler(), m_Controls.m_ToolBoxControlWidgets); if (m_Device->GetControlInterfaceDoppler()) { m_Controls.m_ToolBoxControlWidgets->addItem(m_ControlDopplerWidget, "Doppler Controls"); //m_Controls.m_ToolBoxControlWidgets->setItemEnabled(m_Controls.m_ToolBoxControlWidgets->count() - 1, false); } ctkPluginContext* pluginContext = mitk::PluginActivator::GetContext(); if (pluginContext) { - std::string filter = "(ork.mitk.services.UltrasoundCustomWidget.deviceClass=" + m_Device->GetDeviceClass() + ")"; + std::string filter = "(org.mitk.services.UltrasoundCustomWidget.deviceClass=" + m_Device->GetDeviceClass() + ")"; QString interfaceName = QString::fromStdString(us_service_interface_iid()); m_CustomWidgetServiceReference = pluginContext->getServiceReferences(interfaceName, QString::fromStdString(filter)); if (m_CustomWidgetServiceReference.size() > 0) { m_ControlCustomWidget = pluginContext->getService (m_CustomWidgetServiceReference.at(0))->CloneForQt(m_Controls.tab2); m_ControlCustomWidget->SetDevice(m_Device); m_Controls.m_ToolBoxControlWidgets->addItem(m_ControlCustomWidget, "Custom Controls"); } else { m_Controls.m_ToolBoxControlWidgets->addItem(new QWidget(m_Controls.m_ToolBoxControlWidgets), "Custom Controls"); m_Controls.m_ToolBoxControlWidgets->setItemEnabled(m_Controls.m_ToolBoxControlWidgets->count() - 1, false); } } // select first enabled control widget for (int n = 0; n < m_Controls.m_ToolBoxControlWidgets->count(); ++n) { if (m_Controls.m_ToolBoxControlWidgets->isItemEnabled(n)) { m_Controls.m_ToolBoxControlWidgets->setCurrentIndex(n); break; } } } void UltrasoundSupport::RemoveControlWidgets() { if (!m_ControlProbesWidget) { return; } //widgets do not exist... nothing to do // remove all control widgets from the tool box widget while (m_Controls.m_ToolBoxControlWidgets->count() > 0) { m_Controls.m_ToolBoxControlWidgets->removeItem(0); } // remove probes widget (which is not part of the tool box widget) m_Controls.probesWidgetContainer->removeWidget(m_ControlProbesWidget); delete m_ControlProbesWidget; m_ControlProbesWidget = 0; delete m_ControlBModeWidget; m_ControlBModeWidget = 0; delete m_ControlDopplerWidget; m_ControlDopplerWidget = 0; // delete custom widget if it is present if (m_ControlCustomWidget) { ctkPluginContext* pluginContext = mitk::PluginActivator::GetContext(); delete m_ControlCustomWidget; m_ControlCustomWidget = 0; if (m_CustomWidgetServiceReference.size() > 0) { pluginContext->ungetService(m_CustomWidgetServiceReference.at(0)); } } } void UltrasoundSupport::OnDeciveServiceEvent(const ctkServiceEvent event) { if (m_Device.IsNull() || event.getType() != static_cast(us::ServiceEvent::MODIFIED)) { return; } ctkServiceReference service = event.getServiceReference(); if (m_Device->GetManufacturer() != service.getProperty(QString::fromStdString(mitk::USDevice::GetPropertyKeys().US_PROPKEY_MANUFACTURER)).toString().toStdString() && m_Device->GetName() != service.getProperty(QString::fromStdString(mitk::USDevice::GetPropertyKeys().US_PROPKEY_NAME)).toString().toStdString()) { return; } if (!m_Device->GetIsActive() && m_UpdateTimer->isActive()) { StopTimers(); } if (m_CurrentDynamicRange != service.getProperty(QString::fromStdString(mitk::USDevice::GetPropertyKeys().US_PROPKEY_BMODE_DYNAMIC_RANGE)).toDouble()) { m_CurrentDynamicRange = service.getProperty(QString::fromStdString(mitk::USDevice::GetPropertyKeys().US_PROPKEY_BMODE_DYNAMIC_RANGE)).toDouble(); // update level window for the current dynamic range mitk::LevelWindow levelWindow; for (auto& Node : m_Node) { Node->GetLevelWindow(levelWindow); levelWindow.SetAuto(m_Image, true, true); levelWindow.SetWindowBounds(55, 125,true); Node->SetLevelWindow(levelWindow); } } } UltrasoundSupport::UltrasoundSupport() : m_ControlCustomWidget(0), m_ControlBModeWidget(0), m_ControlProbesWidget(0), m_ImageAlreadySetToNode(false), m_CurrentImageWidth(0), m_CurrentImageHeight(0), m_AmountOfOutputs(0), m_PADataStorage(mitk::StandaloneDataStorage::New()), m_USDataStorage(mitk::StandaloneDataStorage::New()), m_ForceRequestUpdateAll(false) { ctkPluginContext* pluginContext = mitk::PluginActivator::GetContext(); if (pluginContext) { // to be notified about service event of an USDevice pluginContext->connectServiceListener(this, "OnDeciveServiceEvent", QString::fromStdString("(" + us::ServiceConstants::OBJECTCLASS() + "=" + us_service_interface_iid() + ")")); } } UltrasoundSupport::~UltrasoundSupport() { try { StoreUISettings(); StopTimers(); // Get all active devicesand deactivate them to prevent freeze for (auto device : this->m_Controls.m_ActiveVideoDevices->GetAllServices()) { if (device != nullptr && device->GetIsActive()) { device->Deactivate(); device->Disconnect(); } } } catch (std::exception &e) { MITK_ERROR << "Exception during call of destructor! Message: " << e.what(); } } void UltrasoundSupport::StoreUISettings() { QSettings settings; settings.beginGroup(QString::fromStdString(VIEW_ID)); settings.setValue("DisplayImage", QVariant(m_Controls.m_ShowImageStream->isChecked())); settings.setValue("RunImageTimer", QVariant(m_Controls.m_RunImageTimer->isChecked())); settings.setValue("Update2DView", QVariant(m_Controls.m_Update2DView->isChecked())); settings.setValue("Update3DView", QVariant(m_Controls.m_Update3DView->isChecked())); settings.setValue("UpdateRatePipeline", QVariant(m_Controls.m_FrameRatePipeline->value())); settings.setValue("UpdateRate2d", QVariant(m_Controls.m_FrameRate2d->value())); settings.setValue("UpdateRate3d", QVariant(m_Controls.m_FrameRate3d->value())); settings.endGroup(); } void UltrasoundSupport::LoadUISettings() { QSettings settings; settings.beginGroup(QString::fromStdString(VIEW_ID)); m_Controls.m_ShowImageStream->setChecked(settings.value("DisplayImage", true).toBool()); m_Controls.m_RunImageTimer->setChecked(settings.value("RunImageTimer", true).toBool()); m_Controls.m_Update2DView->setChecked(settings.value("Update2DView", true).toBool()); m_Controls.m_Update3DView->setChecked(settings.value("Update3DView", true).toBool()); m_Controls.m_FrameRatePipeline->setValue(settings.value("UpdateRatePipeline", 50).toInt()); m_Controls.m_FrameRate2d->setValue(settings.value("UpdateRate2d", 20).toInt()); m_Controls.m_FrameRate3d->setValue(settings.value("UpdateRate3d", 5).toInt()); settings.endGroup(); } void UltrasoundSupport::StartTimers() { m_UpdateTimer->start(); if (m_Controls.m_Update2DView->isChecked()) { m_RenderingTimer2d->start(); } if (m_Controls.m_Update3DView->isChecked()) { m_RenderingTimer3d->start(); } } void UltrasoundSupport::StopTimers() { m_UpdateTimer->stop(); m_RenderingTimer2d->stop(); m_RenderingTimer3d->stop(); } void UltrasoundSupport::SetTimerIntervals(int intervalPipeline, int interval2D, int interval3D) { m_UpdateTimer->setInterval(intervalPipeline); m_RenderingTimer2d->setInterval(interval2D); m_RenderingTimer3d->setInterval(interval3D); }