diff --git a/.arcconfig b/.arcconfig index efc16ec..2e59055 100644 --- a/.arcconfig +++ b/.arcconfig @@ -1,3 +1,9 @@ { - "phabricator.uri": "https://phabricator.mitk.org/" + "arc.feature.start.default": "develop", + "arc.land.onto": ["develop"], + "arc.land.strategy": "merge", + "base": "git:merge-base(origin/develop)", + "history.immutable": true, + "phabricator.uri": "https://phabricator.mitk.org/", + "repository.callsign": "MPT" } diff --git a/LICENSE b/LICENSE index d68700c..4cd2f35 100644 --- a/LICENSE +++ b/LICENSE @@ -1,29 +1,29 @@ BSD 3-Clause License -Copyright (c) 2003-2019, German Cancer Research Center (DKFZ) +Copyright (c) 2003-2021, German Cancer Research Center (DKFZ) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Modules/ExampleModule/cmdapps/ExampleCmdApp.cpp b/Modules/ExampleModule/cmdapps/ExampleCmdApp.cpp index a0fb949..5c0f616 100644 --- a/Modules/ExampleModule/cmdapps/ExampleCmdApp.cpp +++ b/Modules/ExampleModule/cmdapps/ExampleCmdApp.cpp @@ -1,145 +1,145 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include <mitkCommandLineParser.h> #include <mitkIOUtil.h> #include <ExampleImageFilter.h> #include <algorithm> #include <string> /** \brief Example command-line app that demonstrates the example image filter * * This command-line app will take the first given image and add the * provided offset to each voxel. */ int main(int argc, char* argv[]) { mitkCommandLineParser parser; // Set general information about your command-line app parser.setCategory("Example Cmd App Category"); parser.setTitle("Example Cmd App"); parser.setContributor("CAMIC"); parser.setDescription( "This command-line app takes the given image and adds the provided offset to each voxel."); // How should arguments be prefixed parser.setArgumentPrefix("--", "-"); // Add arguments. Unless specified otherwise, each argument is optional. // See mitkCommandLineParser::addArgument() for more information. parser.addArgument( "input", "i", - mitkCommandLineParser::InputFile, + mitkCommandLineParser::File, "Input Image", "Any image format known to MITK.", us::Any(), false); parser.addArgument( "output", "o", - mitkCommandLineParser::OutputFile, + mitkCommandLineParser::File, "Output file", "Where to save the output.", us::Any(), false); parser.addArgument( "offset", "f", mitkCommandLineParser::Int, "Offset", "the offset integer to add to each voxel.", us::Any(), false); parser.addArgument( // optional "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); // Parse arguments. This method returns a mapping of long argument names to // their values. auto parsedArgs = parser.parseArguments(argc, argv); if (parsedArgs.empty()) return EXIT_FAILURE; // Just exit, usage information was already printed. if (parsedArgs["input"].Empty() || parsedArgs["output"].Empty() || parsedArgs["offset"].Empty()) { MITK_INFO << parser.helpText(); return EXIT_FAILURE; } // Parse, cast and set required arguments auto inFilename = us::any_cast<std::string>(parsedArgs["input"]); auto outFilename = us::any_cast<std::string>(parsedArgs["output"]); auto offset = us::any_cast<int>(parsedArgs["offset"]); // Default values for optional arguments auto verbose = false; // Parse, cast and set optional arguments if (parsedArgs.end() != parsedArgs.find("verbose")) verbose = us::any_cast<bool>(parsedArgs["verbose"]); try { if (verbose) MITK_INFO << "Read input file"; auto inImage = mitk::IOUtil::Load<mitk::Image>(inFilename); if (inImage.IsNull()) { MITK_ERROR << "Could not read \"" << inFilename << "\"!"; return EXIT_FAILURE; } if (verbose) MITK_INFO << "Add offset to image"; auto exampleFilter = ExampleImageFilter::New(); exampleFilter->SetInput(inImage); exampleFilter->SetOffset(offset); exampleFilter->Update(); auto outImage = exampleFilter->GetOutput(); if (nullptr == outImage) { MITK_ERROR << "Image processing failed!"; return EXIT_FAILURE; } if (verbose) MITK_INFO << "Write output file"; mitk::IOUtil::Save(outImage, outFilename); return EXIT_SUCCESS; } catch (const std::exception &e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error!"; return EXIT_FAILURE; } } diff --git a/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/ExampleViewControls.ui b/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/ExampleViewControls.ui index 37427f6..f96e7ee 100644 --- a/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/ExampleViewControls.ui +++ b/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/ExampleViewControls.ui @@ -1,106 +1,110 @@ <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ExampleViewControls</class> <widget class="QWidget" name="ExampleViewControls"> <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>220</width> - <height>160</height> + <width>227</width> + <height>356</height> </rect> </property> <property name="windowTitle"> <string>Example View</string> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QLabel" name="selectImageLabel"> - <property name="styleSheet"> - <string notr="true"/> - </property> - <property name="text"> - <string>Please select an image.</string> - </property> - </widget> - </item> - <item> - <widget class="QWidget" name="widget" native="true"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QLabel" name="offsetLabel"> - <property name="text"> - <string>Offset</string> - </property> - </widget> - </item> - <item> - <widget class="QSpinBox" name="offsetSpinBox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximum"> - <number>9999</number> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QPushButton" name="processImageButton"> - <property name="toolTip"> - <string>Process selected image</string> - </property> - <property name="text"> - <string>Add Offset</string> - </property> - </widget> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="1"> + <widget class="QSpinBox" name="offsetSpinBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximum"> + <number>9999</number> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QPushButton" name="processImageButton"> + <property name="toolTip"> + <string>Process selected image</string> + </property> + <property name="text"> + <string>Add Offset</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="offsetLabel"> + <property name="text"> + <string>Offset</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="imageLabel"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>Image</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QmitkSingleNodeSelectionWidget" name="selectionWidget" native="true"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>40</height> + </size> + </property> + </widget> + </item> + </layout> </item> <item> <widget class="QLabel" name="infoLabel"> <property name="text"> <string>CTRL-click in result images to paint.</string> </property> <property name="wordWrap"> <bool>true</bool> </property> </widget> </item> <item> <spacer name="spacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeType"> <enum>QSizePolicy::Expanding</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>220</height> </size> </property> </spacer> </item> </layout> </widget> <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>QmitkSingleNodeSelectionWidget</class> + <extends>QWidget</extends> + <header location="global">QmitkSingleNodeSelectionWidget.h</header> + <container>1</container> + </customwidget> + </customwidgets> <resources/> <connections/> </ui> diff --git a/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.cpp b/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.cpp index 9c2fcbe..8b03ff0 100644 --- a/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.cpp +++ b/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.cpp @@ -1,176 +1,151 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include <berryISelectionService.h> #include <berryIWorkbenchWindow.h> +#include <mitkNodePredicateAnd.h> +#include <mitkNodePredicateDataType.h> +#include <mitkNodePredicateNot.h> +#include <mitkNodePredicateOr.h> +#include <mitkNodePredicateProperty.h> + #include <usModuleRegistry.h> #include <QMessageBox> #include <ExampleImageFilter.h> #include <ExampleImageInteractor.h> #include "QmitkExampleView.h" namespace { // Helper function to create a fully set up instance of our // ExampleImageInteractor, based on the state machine specified in Paint.xml // as well as its configuration in PaintConfig.xml. Both files are compiled - // into ExtExampleModule as resources. + // into MitkExampleModule as resources. static ExampleImageInteractor::Pointer CreateExampleImageInteractor() { auto exampleModule = us::ModuleRegistry::GetModule("MitkExampleModule"); if (nullptr != exampleModule) { auto interactor = ExampleImageInteractor::New(); interactor->LoadStateMachine("Paint.xml", exampleModule); interactor->SetEventConfig("PaintConfig.xml", exampleModule); return interactor; } return nullptr; } } // Don't forget to initialize the VIEW_ID. const std::string QmitkExampleView::VIEW_ID = "org.mitk.views.exampleview"; void QmitkExampleView::CreateQtPartControl(QWidget* parent) { // Setting up the UI is a true pleasure when using .ui files, isn't it? m_Controls.setupUi(parent); + m_Controls.selectionWidget->SetDataStorage(this->GetDataStorage()); + m_Controls.selectionWidget->SetSelectionIsOptional(true); + m_Controls.selectionWidget->SetEmptyInfo(QStringLiteral("Select an image")); + m_Controls.selectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( + mitk::TNodePredicateDataType<mitk::Image>::New(), + mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( + mitk::NodePredicateProperty::New("helper object"), + mitk::NodePredicateProperty::New("hidden object"))))); + // Wire up the UI widgets with our functionality. + connect(m_Controls.selectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkExampleView::OnImageChanged); connect(m_Controls.processImageButton, SIGNAL(clicked()), this, SLOT(ProcessSelectedImage())); + + // Make sure to have a consistent UI state at the very beginning. + this->OnImageChanged(m_Controls.selectionWidget->GetSelectedNodes()); } void QmitkExampleView::SetFocus() { m_Controls.processImageButton->setFocus(); } -void QmitkExampleView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList<mitk::DataNode::Pointer>& dataNodes) +void QmitkExampleView::OnImageChanged(const QmitkSingleNodeSelectionWidget::NodeList&) { - for (const auto& dataNode : dataNodes) - { - // Write robust code. Always check pointers before using them. If the - // data node pointer is null, the second half of our condition isn't - // even evaluated and we're safe (C++ short-circuit evaluation). - if (dataNode.IsNotNull() && nullptr != dynamic_cast<mitk::Image*>(dataNode->GetData())) - { - m_Controls.selectImageLabel->setVisible(false); - return; - } - } + this->EnableWidgets(m_Controls.selectionWidget->GetSelectedNode().IsNotNull()); +} - // Nothing is selected or the selection doesn't contain an image. - m_Controls.selectImageLabel->setVisible(true); +void QmitkExampleView::EnableWidgets(bool enable) +{ + m_Controls.processImageButton->setEnabled(enable); } void QmitkExampleView::ProcessSelectedImage() { - // Before we even think about processing something, we need to make sure - // that we have valid input. Don't be sloppy, this is a main reason - // for application crashes if neglected. - - auto selectedDataNodes = this->GetDataManagerSelection(); - - if (selectedDataNodes.empty()) - return; + auto selectedDataNode = m_Controls.selectionWidget->GetSelectedNode(); + auto data = selectedDataNode->GetData(); - auto firstSelectedDataNode = selectedDataNodes.front(); + // We don't use the auto keyword here, which would evaluate to a native + // image pointer. Instead, we want a smart pointer to ensure that + // the image isn't deleted somewhere else while we're using it. + mitk::Image::Pointer image = dynamic_cast<mitk::Image*>(data); - if (firstSelectedDataNode.IsNull()) - { - QMessageBox::information(nullptr, "Example View", "Please load and select an image before starting image processing."); - return; - } - - auto data = firstSelectedDataNode->GetData(); - - // Something is selected, but does it contain data? - if (data != nullptr) - { - // We don't use the auto keyword here, which would evaluate to a native - // image pointer. Instead, we want a smart pointer in order to ensure that - // the image isn't deleted somewhere else while we're using it. - mitk::Image::Pointer image = dynamic_cast<mitk::Image*>(data); + auto imageName = selectedDataNode->GetName(); + auto offset = m_Controls.offsetSpinBox->value(); - // Something is selected and it contains data, but is it an image? - if (image.IsNotNull()) - { - auto imageName = firstSelectedDataNode->GetName(); - auto offset = m_Controls.offsetSpinBox->value(); - - MITK_INFO << "Process image \"" << imageName << "\" ..."; - - // We're finally using the ExampleImageFilter from ExtExampleModule. - auto filter = ExampleImageFilter::New(); - filter->SetInput(image); - filter->SetOffset(offset); + MITK_INFO << "Process image \"" << imageName << "\" ..."; - filter->Update(); + // We're finally using the ExampleImageFilter from MitkExampleModule. + auto filter = ExampleImageFilter::New(); + filter->SetInput(image); + filter->SetOffset(offset); - mitk::Image::Pointer processedImage = filter->GetOutput(); + filter->Update(); - if (processedImage.IsNull() || !processedImage->IsInitialized()) - return; + mitk::Image::Pointer processedImage = filter->GetOutput(); - MITK_INFO << " done"; + if (processedImage.IsNull() || !processedImage->IsInitialized()) + return; - // Stuff the resulting image into a data node, set some properties, - // and add it to the data storage, which will eventually display the - // image in the application. - auto processedImageDataNode = mitk::DataNode::New(); - processedImageDataNode->SetData(processedImage); + MITK_INFO << " done"; - QString name = QString("%1 (Offset: %2)").arg(imageName.c_str()).arg(offset); - processedImageDataNode->SetName(name.toStdString()); + // Stuff the resulting image into a data node, set some properties, + // and add it to the data storage, which will eventually display the + // image in the application. + auto processedImageDataNode = mitk::DataNode::New(); + processedImageDataNode->SetData(processedImage); - // We don't really need to copy the level window, but if we wouldn't - // do it, the new level window would be initialized to display the image - // with optimal contrast in order to capture the whole range of pixel - // values. This is also true for the input image as long as one didn't - // modify its level window manually. Thus, the images would appear - // identical unless you compare the level window widget for both images. - mitk::LevelWindow levelWindow; + QString name = QString("%1 (Offset: %2)").arg(imageName.c_str()).arg(offset); + processedImageDataNode->SetName(name.toStdString()); - if (firstSelectedDataNode->GetLevelWindow(levelWindow)) - processedImageDataNode->SetLevelWindow(levelWindow); + // We don't really need to copy the level window, but if we wouldn't + // do it, the new level window would be initialized to display the image + // with optimal contrast in order to capture the whole range of pixel + // values. This is also true for the input image as long as one didn't + // modify its level window manually. Thus, the images would appear + // identical unless you compare the level window widget for both images. + mitk::LevelWindow levelWindow; - // We also attach our ExampleImageInteractor, which allows us to paint - // on the resulting images by using the mouse as long as the CTRL key - // is pressed. - auto interactor = CreateExampleImageInteractor(); + if (selectedDataNode->GetLevelWindow(levelWindow)) + processedImageDataNode->SetLevelWindow(levelWindow); - if (interactor.IsNotNull()) - interactor->SetDataNode(processedImageDataNode); + // We also attach our ExampleImageInteractor, which allows us to paint + // on the resulting images by using the mouse as long as the CTRL key + // is pressed. + auto interactor = CreateExampleImageInteractor(); - this->GetDataStorage()->Add(processedImageDataNode); - } - } + if (interactor.IsNotNull()) + interactor->SetDataNode(processedImageDataNode); - // Now it's your turn. This class/method has lots of room for improvements, - // for example: - // - // - What happens when multiple items are selected but the first one isn't - // an image? - There isn't any feedback for the user at all. - // - What's the front item of a selection? Does it depend on the order - // of selection or the position in the Data Manager? - Isn't it - // better to process all selected images? Don't forget to adjust the - // titles of the UI widgets. - // - In addition to the the displayed label, it's probably a good idea to - // enable or disable the button depending on the selection. + this->GetDataStorage()->Add(processedImageDataNode); } diff --git a/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.h b/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.h index f84d5df..658f7e3 100644 --- a/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.h +++ b/Plugins/org.mitk.gui.qt.exampleplugin/src/internal/QmitkExampleView.h @@ -1,65 +1,63 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkExampleView_h #define QmitkExampleView_h #include <berryISelectionListener.h> #include <QmitkAbstractView.h> +#include <QmitkSingleNodeSelectionWidget.h> // There's an item "ExampleViewControls.ui" in the UI_FILES list in // files.cmake. The Qt UI Compiler will parse this file and generate a // header file prefixed with "ui_", which is located in the build directory. // Use Qt Creator to view and edit .ui files. The generated header file // provides a class that contains all of the UI widgets. #include <ui_ExampleViewControls.h> // All views in MITK derive from QmitkAbstractView. You have to override // at least the two methods CreateQtPartControl() and SetFocus(). class QmitkExampleView : public QmitkAbstractView { // As ExampleView derives from QObject and we want to use the Qt // signal and slot mechanism, we must not forget the Q_OBJECT macro. // This header file also has to be listed in MOC_H_FILES in files.cmake // so that the Qt Meta-Object compiler can find and process this // class declaration. Q_OBJECT public: // This is a tricky one and will give you some headache later on in // your debug sessions if it has been forgotten. Also, don't forget // to initialize it in the implementation file. static const std::string VIEW_ID; // In this method we initialize the GUI components and connect the // associated signals and slots. void CreateQtPartControl(QWidget* parent) override; private slots: + void OnImageChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes); void ProcessSelectedImage(); private: // Typically a one-liner. Set the focus to the default widget. void SetFocus() override; - // This method is conveniently called whenever the selection of Data Manager - // items changes. - void OnSelectionChanged( - berry::IWorkbenchPart::Pointer source, - const QList<mitk::DataNode::Pointer>& dataNodes) override; + void EnableWidgets(bool enable); // Generated from the associated UI file, it encapsulates all the widgets // of our view. Ui::ExampleViewControls m_Controls; }; #endif diff --git a/README.md b/README.md index c731fa9..3b3b5b0 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,40 @@ The MITK Project Template ========================= This project provides a complete CMake-based set-up to get started with [MITK](https://github.com/MITK/MITK). Features -------- - Example module - ITK-based image filter - Interactor to paint in images - Example command-line app - Uses the image filter of the example module - Example plugin - GUI for the image filter and interactor of the example module - Example external project - Microsoft's Guidelines Support Library (GSL) -What's new in v2018.04 ----------------------- +How it works +------------ -The project template was completely restructured to fit the new extension -mechanism of MITK v2018.04. Here's how it basically works: - -1. Clone the latest stable branch `releases/2018-04` of MITK from https://phabricator.mitk.org/source/mitk.git (not yet available on GitHub) -2. Clone MITK-ProjectTemplate +1. Clone [MITK](https://github.com/MITK/MITK) and checkout the latest release tag or at least the stable master branch +2. Clone MITK-ProjectTemplate and checkout the matching tag or branch 3. Configure the MITK superbuild and set the CMake cache variable `MITK_EXTENSION_DIRS` to your working copy of the project template 4. Generate and build the MITK superbuild -The project template is integrated right into the MITK superbuild and MITK build. Thus you can extend MITK with your own modules, plugins, command-line apps, and external projects without touching the MITK source code. There is no need for a super-superbuild anymore as compared to earlier versions of the project template. +The project template is virtually integrated right into the MITK superbuild and MITK build as if it would be part of MITK. You can extend MITK with your own modules, plugins, command-line apps, and external projects without touching the MITK source code resp. repository. Supported platforms and other requirements ------------------------------------------ See the [MITK documentation](http://docs.mitk.org/2018.04/). License ------- Copyright (c) [German Cancer Research Center (DKFZ)](https://www.dkfz.de)<br> All rights reserved. The MITK-ProjectTemplate is part of [MITK](https://github.com/MITK/MITK) and as such available as free open-source software under a [3-clause BSD license](https://github.com/MITK/MITK-ProjectTemplate/blob/master/LICENSE).