diff --git a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp
index 7d53f07474..cb09f6a1b6 100644
--- a/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp
+++ b/Modules/Multilabel/autoload/IO/mitkSegmentationTaskListIO.cpp
@@ -1,259 +1,267 @@
/*============================================================================
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 "mitkSegmentationTaskListIO.h"
#include "mitkMultilabelIOMimeTypes.h"
#include
#include
#include
#include
namespace mitk
{
void to_json(nlohmann::json& json, const SegmentationTaskList::Task& task)
{
if (task.HasName())
json["Name"] = task.GetName();
if (task.HasDescription())
json["Description"] = task.GetDescription();
if (task.HasImage())
json["Image"] = task.GetImage();
if (task.HasSegmentation())
json["Segmentation"] = task.GetSegmentation();
if (task.HasLabelName())
json["LabelName"] = task.GetLabelName();
+ if (task.HasLabelNameSuggestions())
+ json["LabelNameSuggestions"] = task.GetLabelNameSuggestions();
+
if (task.HasPreset())
json["Preset"] = task.GetPreset();
if (task.HasResult())
json["Result"] = task.GetResult();
if (task.HasDynamic())
json["Dynamic"] = task.GetDynamic();
}
void from_json(const nlohmann::json& json, SegmentationTaskList::Task& task)
{
auto iter = json.find("Name");
if (iter != json.end())
task.SetName(json["Name"].get());
iter = json.find("Description");
if (iter != json.end())
task.SetDescription(json["Description"].get());
iter = json.find("Image");
if (iter != json.end())
task.SetImage(json["Image"].get());
iter = json.find("Segmentation");
if (iter != json.end())
task.SetSegmentation(json["Segmentation"].get());
iter = json.find("LabelName");
if (iter != json.end())
task.SetLabelName(json["LabelName"].get());
+ iter = json.find("LabelNameSuggestions");
+
+ if (iter != json.end())
+ task.SetLabelNameSuggestions(json["LabelNameSuggestions"].get());
+
iter = json.find("Preset");
if (iter != json.end())
task.SetPreset(json["Preset"].get());
iter = json.find("Result");
if (iter != json.end())
task.SetResult(json["Result"].get());
iter = json.find("Dynamic");
if (iter != json.end())
task.SetDynamic(json["Dynamic"].get());
}
}
mitk::SegmentationTaskListIO::SegmentationTaskListIO()
: AbstractFileIO(SegmentationTaskList::GetStaticNameOfClass(), MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE(), "MITK Segmentation Task List")
{
this->RegisterService();
}
std::vector mitk::SegmentationTaskListIO::DoRead()
{
auto* stream = this->GetInputStream();
std::ifstream fileStream;
if (nullptr == stream)
{
auto filename = this->GetInputLocation();
if (filename.empty() || !std::filesystem::exists(filename))
mitkThrow() << "Invalid or nonexistent filename: \"" << filename << "\"!";
fileStream.open(filename);
if (!fileStream.is_open())
mitkThrow() << "Could not open file \"" << filename << "\" for reading!";
stream = &fileStream;
}
nlohmann::json json;
try
{
json = nlohmann::json::parse(*stream);
}
catch (const nlohmann::json::exception& e)
{
mitkThrow() << e.what();
}
if (!json.is_object())
mitkThrow() << "Unknown file format (expected JSON object as root)!";
if ("MITK Segmentation Task List" != json.value("FileFormat", ""))
mitkThrow() << "Unknown file format (expected \"MITK Segmentation Task List\")!";
if (1 != json.value("Version", 0))
mitkThrow() << "Unknown file format version (expected \"1\")!";
if (!json.contains("Tasks") || !json["Tasks"].is_array())
mitkThrow() << "Tasks array not found!";
auto segmentationTaskList = SegmentationTaskList::New();
if (json.contains("Name"))
segmentationTaskList->SetProperty("name", StringProperty::New(json["Name"].get()));
try
{
if (json.contains("Defaults"))
{
segmentationTaskList->SetDefaults(json["Defaults"].get());
if (segmentationTaskList->GetDefaults().HasResult())
mitkThrow() << "Defaults must not contain \"Result\"!";
}
for (const auto& task : json["Tasks"])
{
auto i = segmentationTaskList->AddTask(task.get());
if (!segmentationTaskList->HasImage(i))
mitkThrow() << "Task " << i << " must contain \"Image\"!";
std::filesystem::path imagePath(segmentationTaskList->GetImage(i));
if (imagePath.is_relative())
{
auto inputLocation = this->GetInputLocation();
/* If we have access to properties, we are reading from an MITK scene
* file. In this case, paths are still relative to the original input
* location, which is preserved in the properties.
*/
const auto* properties = this->GetProperties();
if (properties != nullptr)
properties->GetStringProperty("MITK.IO.reader.inputlocation", inputLocation);
imagePath = std::filesystem::path(inputLocation).remove_filename() / imagePath;
}
if (!std::filesystem::exists(imagePath))
mitkThrow() << "Referenced image \"" << imagePath << "\" in task " << i << " does not exist!";
if (!segmentationTaskList->HasResult(i))
mitkThrow() << "Task " << i << " must contain \"Result\"!";
}
}
catch (const nlohmann::json::type_error& e)
{
mitkThrow() << e.what();
}
std::vector result;
result.push_back(segmentationTaskList.GetPointer());
return result;
}
void mitk::SegmentationTaskListIO::Write()
{
auto segmentationTaskList = dynamic_cast(this->GetInput());
if (nullptr == segmentationTaskList)
mitkThrow() << "Invalid input for writing!";
if (segmentationTaskList->GetNumberOfTasks() == 0)
mitkThrow() << "No tasks found!";
auto* stream = this->GetOutputStream();
std::ofstream fileStream;
if (nullptr == stream)
{
auto filename = this->GetOutputLocation();
if (filename.empty())
mitkThrow() << "Neither an output stream nor an output filename was specified!";
fileStream.open(filename);
if (!fileStream.is_open())
mitkThrow() << "Could not open file \"" << filename << "\" for writing!";
stream = &fileStream;
}
if (!stream->good())
mitkThrow() << "Stream for writing is not good!";
nlohmann::ordered_json json = {
{ "FileFormat", "MITK Segmentation Task List" },
{ "Version", 1 },
{ "Name", segmentationTaskList->GetProperty("name")->GetValueAsString() }
};
nlohmann::json defaults = segmentationTaskList->GetDefaults();
if (!defaults.is_null())
json["Defaults"] = defaults;
nlohmann::json tasks;
for (const auto& task : *segmentationTaskList)
tasks.push_back(task);
json["Tasks"] = tasks;
*stream << std::setw(2) << json << std::endl;
}
mitk::SegmentationTaskListIO* mitk::SegmentationTaskListIO::IOClone() const
{
return new SegmentationTaskListIO(*this);
}
diff --git a/Modules/Multilabel/mitkSegmentationTaskList.h b/Modules/Multilabel/mitkSegmentationTaskList.h
index 0f263fd343..75a43014f1 100644
--- a/Modules/Multilabel/mitkSegmentationTaskList.h
+++ b/Modules/Multilabel/mitkSegmentationTaskList.h
@@ -1,104 +1,106 @@
/*============================================================================
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 mitkSegmentationTaskList_h
#define mitkSegmentationTaskList_h
#include
#include
#include
#include
#include
namespace mitk
{
class MITKMULTILABEL_EXPORT SegmentationTaskList : public BaseData
{
public:
class MITKMULTILABEL_EXPORT Task
{
public:
Task();
~Task();
void SetDefaults(const Task* defaults);
mitkSegmentationTaskValueMacro(std::string, Name)
mitkSegmentationTaskValueMacro(std::string, Description)
mitkSegmentationTaskValueMacro(std::string, Image)
mitkSegmentationTaskValueMacro(std::string, Segmentation)
mitkSegmentationTaskValueMacro(std::string, LabelName)
+ mitkSegmentationTaskValueMacro(std::string, LabelNameSuggestions)
mitkSegmentationTaskValueMacro(std::string, Preset)
mitkSegmentationTaskValueMacro(std::string, Result)
mitkSegmentationTaskValueMacro(bool, Dynamic)
private:
const Task* m_Defaults;
};
mitkClassMacro(SegmentationTaskList, BaseData)
itkFactorylessNewMacro(Self)
itkCloneMacro(Self)
mitkSegmentationTaskListValueMacro(std::string, Name)
mitkSegmentationTaskListValueMacro(std::string, Description)
mitkSegmentationTaskListValueMacro(std::string, Image)
mitkSegmentationTaskListValueMacro(std::string, Segmentation)
mitkSegmentationTaskListValueMacro(std::string, LabelName)
+ mitkSegmentationTaskListValueMacro(std::string, LabelNameSuggestions)
mitkSegmentationTaskListValueMacro(std::string, Preset)
mitkSegmentationTaskListValueMacro(std::string, Result)
mitkSegmentationTaskListValueMacro(bool, Dynamic)
size_t GetNumberOfTasks() const;
size_t AddTask(const Task& subtask);
const Task* GetTask(size_t index) const;
Task* GetTask(size_t index);
const Task& GetDefaults() const;
void SetDefaults(const Task& defaults);
bool IsDone() const;
bool IsDone(size_t index) const;
std::filesystem::path GetInputLocation() const;
std::filesystem::path GetBasePath() const;
std::filesystem::path GetAbsolutePath(const std::filesystem::path& path) const;
void SaveTask(size_t index, const BaseData* segmentation);
std::vector::const_iterator begin() const;
std::vector::const_iterator end() const;
std::vector::iterator begin();
std::vector::iterator end();
void SetRequestedRegionToLargestPossibleRegion() override;
bool RequestedRegionIsOutsideOfTheBufferedRegion() override;
bool VerifyRequestedRegion() override;
void SetRequestedRegion(const itk::DataObject*) override;
protected:
mitkCloneMacro(Self)
SegmentationTaskList();
SegmentationTaskList(const Self& other);
~SegmentationTaskList() override;
private:
Task m_Defaults;
std::vector m_Tasks;
};
}
#endif
diff --git a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp
index 1ee3509581..4bc5a0f94a 100644
--- a/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp
+++ b/Plugins/org.mitk.gui.qt.flow.segmentation/src/internal/QmitkSegmentationTaskListWidget.cpp
@@ -1,737 +1,766 @@
/*============================================================================
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 "QmitkSegmentationTaskListWidget.h"
#include "org_mitk_gui_qt_flow_segmentation_Activator.h"
+#include
+#include
+#include
+
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace
{
+ berry::IPreferences::Pointer GetSegmentationPreferences()
+ {
+ return berry::Platform::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation");
+ }
+
mitk::DataStorage* GetDataStorage()
{
auto* pluginContext = org_mitk_gui_qt_flow_segmentation_Activator::GetContext();
auto dataStorageServiceReference = pluginContext->getServiceReference();
if (dataStorageServiceReference)
{
auto* dataStorageService = pluginContext->getService(dataStorageServiceReference);
if (dataStorageService != nullptr)
{
auto dataStorageReference = dataStorageService->GetDataStorage();
pluginContext->ungetService(dataStorageServiceReference);
return dataStorageReference->GetDataStorage();
}
}
return nullptr;
}
std::filesystem::path GetInputLocation(const mitk::BaseData* data)
{
std::string result;
if (data != nullptr)
data->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", result);
return result;
}
QString ColorString(const QString& string, const QColor& color, const QColor& backgroundColor = QColor::Invalid)
{
if (!color.isValid() && !backgroundColor.isValid())
return string;
auto result = QStringLiteral("%1").arg(string);
return result;
}
}
/* This constructor has three objectives:
* 1. Do widget initialization that cannot be done in the .ui file
* 2. Connect signals and slots
* 3. Explicitly trigger a reset to a valid initial widget state
*/
QmitkSegmentationTaskListWidget::QmitkSegmentationTaskListWidget(QWidget* parent)
: QWidget(parent),
m_Ui(new Ui::QmitkSegmentationTaskListWidget),
m_FileSystemWatcher(new QFileSystemWatcher(this)),
m_UnsavedChanges(false)
{
m_Ui->setupUi(this);
m_Ui->selectionWidget->SetDataStorage(GetDataStorage());
m_Ui->selectionWidget->SetSelectionIsOptional(true);
m_Ui->selectionWidget->SetEmptyInfo(QStringLiteral("Select a segmentation task list"));
m_Ui->selectionWidget->SetAutoSelectNewNodes(true);
m_Ui->selectionWidget->SetNodePredicate(mitk::TNodePredicateDataType::New());
m_Ui->progressBar->setStyleSheet(QString("QProgressBar::chunk { background-color: %1; }").arg(QmitkStyleManager::GetIconAccentColor()));
using Self = QmitkSegmentationTaskListWidget;
connect(m_Ui->selectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnSelectionChanged);
connect(m_Ui->previousButton, &QToolButton::clicked, this, &Self::OnPreviousButtonClicked);
connect(m_Ui->nextButton, &QToolButton::clicked, this, &Self::OnNextButtonClicked);
connect(m_Ui->loadButton, &QPushButton::clicked, this, &Self::OnLoadButtonClicked);
connect(m_FileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &Self::OnResultDirectoryChanged);
this->OnSelectionChanged(m_Ui->selectionWidget->GetSelectedNodes());
}
QmitkSegmentationTaskListWidget::~QmitkSegmentationTaskListWidget()
{
}
mitk::SegmentationTaskList* QmitkSegmentationTaskListWidget::GetTaskList() const
{
return m_TaskList;
}
std::optional QmitkSegmentationTaskListWidget::GetActiveTaskIndex() const
{
return m_ActiveTaskIndex;
}
std::optional QmitkSegmentationTaskListWidget::GetCurrentTaskIndex() const
{
return m_CurrentTaskIndex;
}
void QmitkSegmentationTaskListWidget::OnUnsavedChangesSaved()
{
if (m_UnsavedChanges)
{
m_UnsavedChanges = false;
if (this->ActiveTaskIsShown())
this->UpdateDetailsLabel();
}
}
/* Make sure that the widget transitions into a valid state whenever the
* selection changes.
*/
void QmitkSegmentationTaskListWidget::OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes)
{
this->UnloadTasks();
this->ResetControls();
if (!nodes.empty())
{
m_TaskListNode = nodes.front();
auto taskList = dynamic_cast(m_TaskListNode->GetData());
if (taskList != nullptr)
{
this->OnTaskListChanged(taskList);
return;
}
}
this->SetTaskList(nullptr);
m_TaskListNode = nullptr;
}
/* Reset all controls to a default state as a common basis for further
* adjustments.
*/
void QmitkSegmentationTaskListWidget::ResetControls()
{
m_Ui->progressBar->setEnabled(false);
m_Ui->progressBar->setFormat("");
m_Ui->progressBar->setValue(0);
m_Ui->progressBar->setMaximum(1);
m_Ui->previousButton->setEnabled(false);
m_Ui->nextButton->setEnabled(false);
this->UpdateLoadButton();
this->UpdateDetailsLabel();
}
/* If the segmentation task changed, reset all member variables to expected
* default values and reset the file system watcher.
*/
void QmitkSegmentationTaskListWidget::SetTaskList(mitk::SegmentationTaskList* taskList)
{
if (m_TaskList != taskList)
{
m_TaskList = taskList;
if (taskList != nullptr)
{
this->SetCurrentTaskIndex(0);
}
else
{
this->SetCurrentTaskIndex(std::nullopt);
}
this->ResetFileSystemWatcher();
}
}
void QmitkSegmentationTaskListWidget::ResetFileSystemWatcher()
{
auto paths = m_FileSystemWatcher->directories();
if (!paths.empty())
m_FileSystemWatcher->removePaths(paths);
if (m_TaskList.IsNotNull())
{
for (const auto& task : *m_TaskList)
{
auto resultPath = m_TaskList->GetAbsolutePath(task.GetResult()).remove_filename();
if (!std::filesystem::exists(resultPath))
{
try
{
std::filesystem::create_directories(resultPath);
}
catch (const std::filesystem::filesystem_error& e)
{
MITK_ERROR << e.what();
}
}
if (std::filesystem::exists(resultPath))
m_FileSystemWatcher->addPath(QString::fromStdString(resultPath.string()));
}
}
}
void QmitkSegmentationTaskListWidget::OnResultDirectoryChanged(const QString&)
{
// TODO: If a segmentation was modified ("Unsaved changes"), saved ("Done"), and then the file is deleted, the status should be "Unsaved changes" instead of "Not done".
this->UpdateProgressBar();
this->UpdateDetailsLabel();
}
void QmitkSegmentationTaskListWidget::UpdateProgressBar()
{
int progress = 0;
for (size_t i = 0; i < m_TaskList->GetNumberOfTasks(); ++i)
{
if (m_TaskList->IsDone(i))
++progress;
}
m_Ui->progressBar->setValue(progress);
}
/* Provided that a valid segmentation task list is currently selected and the
* widget is in its default state, update all controls accordingly.
*/
void QmitkSegmentationTaskListWidget::OnTaskListChanged(mitk::SegmentationTaskList* taskList)
{
this->SetTaskList(taskList);
const auto numTasks = taskList->GetNumberOfTasks();
m_Ui->progressBar->setMaximum(numTasks);
m_Ui->progressBar->setFormat(QStringLiteral("%v/%m Task(s) done"));
m_Ui->progressBar->setEnabled(true);
this->UpdateProgressBar();
m_Ui->loadButton->setEnabled(true);
if (numTasks > 1)
m_Ui->nextButton->setEnabled(true);
}
/* If possible, change the currently displayed task to the previous task.
* Enable/disable navigation buttons according to the task's position.
*/
void QmitkSegmentationTaskListWidget::OnPreviousButtonClicked()
{
const auto maxIndex = m_TaskList->GetNumberOfTasks() - 1;
auto current = m_CurrentTaskIndex.value();
if (current != 0)
this->SetCurrentTaskIndex(current - 1);
current = m_CurrentTaskIndex.value();
if (current == 0)
m_Ui->previousButton->setEnabled(false);
if (current < maxIndex)
m_Ui->nextButton->setEnabled(true);
}
/* If possible, change the currently displayed task to the next task.
* Enable/disable navigation buttons according to the task's position.
*/
void QmitkSegmentationTaskListWidget::OnNextButtonClicked()
{
const auto maxIndex = m_TaskList->GetNumberOfTasks() - 1;
auto current = m_CurrentTaskIndex.value();
if (current < maxIndex)
this->SetCurrentTaskIndex(current + 1);
current = m_CurrentTaskIndex.value();
if (current != 0)
m_Ui->previousButton->setEnabled(true);
if (current >= maxIndex)
m_Ui->nextButton->setEnabled(false);
}
/* Update affected controls when the currently displayed task changed.
*/
void QmitkSegmentationTaskListWidget::OnCurrentTaskChanged()
{
this->UpdateLoadButton();
this->UpdateDetailsLabel();
}
/* Update the load button according to the currently displayed task.
*/
void QmitkSegmentationTaskListWidget::UpdateLoadButton()
{
auto text = !this->ActiveTaskIsShown()
? QStringLiteral("Load task")
: QStringLiteral("Task");
if (m_CurrentTaskIndex.has_value())
{
const auto current = m_CurrentTaskIndex.value();
if (m_TaskList.IsNotNull())
{
text += QString(" %1/%2").arg(current + 1).arg(m_TaskList->GetNumberOfTasks());
if (m_TaskList->HasName(current))
text += QStringLiteral(":\n") + QString::fromStdString(m_TaskList->GetName(current));
}
m_Ui->loadButton->setDisabled(this->ActiveTaskIsShown());
}
else
{
m_Ui->loadButton->setEnabled(false);
}
m_Ui->loadButton->setText(text);
}
/* Update the details label according to the currently display task.
* The text is composed of the status of the task and a variable number
* of text blocks according to the optional values provided by the task.
*/
void QmitkSegmentationTaskListWidget::UpdateDetailsLabel()
{
if (!m_CurrentTaskIndex.has_value())
{
m_Ui->detailsLabel->clear();
return;
}
const auto current = m_CurrentTaskIndex.value();
bool isDone = m_TaskList->IsDone(current);
auto details = QString("Status: %1 / ").arg(this->ActiveTaskIsShown()
? ColorString("Active", Qt::white, QColor(Qt::green).darker())
: ColorString("Inactive", Qt::white, QColor(Qt::red).darker()));
if (m_UnsavedChanges && this->ActiveTaskIsShown())
{
details += QString("%1
").arg(ColorString("Unsaved changes", Qt::white, QColor(Qt::red).darker()));
}
else
{
details += QString("%1
").arg(isDone
? ColorString("Done", Qt::white, QColor(Qt::green).darker())
: ColorString("Not done", Qt::white, QColor(Qt::red).darker()));
}
if (m_TaskList->HasDescription(current))
details += QString("Description: %1
").arg(QString::fromStdString(m_TaskList->GetDescription(current)));
QStringList stringList;
if (m_TaskList->HasImage(current))
stringList << QString::fromStdString("Image: " + m_TaskList->GetImage(current));
if (m_TaskList->HasSegmentation(current))
stringList << QString::fromStdString("Segmentation: " + m_TaskList->GetSegmentation(current));
if (m_TaskList->HasLabelName(current))
stringList << QString::fromStdString("Label name: " + m_TaskList->GetLabelName(current));
if (m_TaskList->HasPreset(current))
stringList << QString::fromStdString("Label set preset: " + m_TaskList->GetPreset(current));
if (m_TaskList->HasDynamic(current))
stringList << QString("Segmentation type: %1").arg(m_TaskList->GetDynamic(current) ? "Dynamic" : "Static");
if (!stringList.empty())
details += QString("%1
").arg(stringList.join(QStringLiteral("
")));
m_Ui->detailsLabel->setText(details);
}
/* Load/activate the currently displayed task. Unload all data nodes from
* previously active tasks first, but spare and reuse the image if possible.
*/
void QmitkSegmentationTaskListWidget::OnLoadButtonClicked()
{
if (!this->HandleUnsavedChanges() || m_UnsavedChanges)
return;
m_Ui->loadButton->setEnabled(false);
QApplication::setOverrideCursor(Qt::BusyCursor);
this->LoadTask(this->GetImageDataNode(m_CurrentTaskIndex.value()));
QApplication::restoreOverrideCursor();
}
/* If present, return the image data node for the task with the specified
* index. Otherwise, return nullptr.
*/
mitk::DataNode* QmitkSegmentationTaskListWidget::GetImageDataNode(size_t index) const
{
const auto imagePath = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(index));
auto imageNodes = GetDataStorage()->GetDerivations(m_TaskListNode, mitk::NodePredicateFunction::New([imagePath](const mitk::DataNode* node) {
return imagePath == GetInputLocation(node->GetData());
}));
return !imageNodes->empty()
? imageNodes->front()
: nullptr;
}
/* If present, return the segmentation data node for the task with the
* specified index. Otherwise, return nullptr.
*/
mitk::DataNode* QmitkSegmentationTaskListWidget::GetSegmentationDataNode(size_t index) const
{
const auto* imageNode = this->GetImageDataNode(index);
if (imageNode != nullptr)
{
auto segmentations = GetDataStorage()->GetDerivations(imageNode, mitk::TNodePredicateDataType::New());
if (!segmentations->empty())
return segmentations->front();
}
return nullptr;
}
/* Unload all task data nodes but spare the passed image data node.
*/
void QmitkSegmentationTaskListWidget::UnloadTasks(const mitk::DataNode* skip)
{
this->UnsubscribeFromActiveSegmentation();
if (m_TaskListNode.IsNotNull())
{
mitk::DataStorage::Pointer dataStorage = GetDataStorage();
auto imageNodes = dataStorage->GetDerivations(m_TaskListNode, mitk::TNodePredicateDataType::New());
for (auto imageNode : *imageNodes)
{
dataStorage->Remove(dataStorage->GetDerivations(imageNode, nullptr, false));
if (imageNode != skip)
dataStorage->Remove(imageNode);
}
}
this->SetActiveTaskIndex(std::nullopt);
}
/* Load/activate the currently displayed task. The task must specify
* an image. The segmentation is either created from scratch with an optional
* name for the first label, possibly based on a label set preset specified by
* the task, or loaded as specified by the task. If a result file does
* exist, it is chosen as segmentation instead.
*/
void QmitkSegmentationTaskListWidget::LoadTask(mitk::DataNode::Pointer imageNode)
{
this->UnloadTasks(imageNode);
const auto current = m_CurrentTaskIndex.value();
mitk::Image::Pointer image;
mitk::LabelSetImage::Pointer segmentation;
try
{
if (imageNode.IsNull())
{
const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(current));
image = mitk::IOUtil::Load(path.string());
}
const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetResult(current));
if (std::filesystem::exists(path))
{
segmentation = mitk::IOUtil::Load(path.string());
}
else if (m_TaskList->HasSegmentation(current))
{
const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetSegmentation(current));
segmentation = mitk::IOUtil::Load(path.string());
}
}
catch (const mitk::Exception&)
{
return;
}
auto dataStorage = GetDataStorage();
if (imageNode.IsNull())
{
imageNode = mitk::DataNode::New();
imageNode->SetData(image);
dataStorage->Add(imageNode, m_TaskListNode);
mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry());
}
else
{
image = static_cast(imageNode->GetData());
}
auto name = "Task " + std::to_string(current + 1);
imageNode->SetName(name);
if (segmentation.IsNull())
{
mitk::Image::ConstPointer templateImage = image;
if (templateImage->GetDimension() > 3)
{
if (m_TaskList->HasDynamic(current))
{
if (!m_TaskList->GetDynamic(current))
templateImage = mitk::SegmentationHelper::GetStaticSegmentationTemplate(image);
}
else
{
QmitkStaticDynamicSegmentationDialog dialog(this);
dialog.SetReferenceImage(templateImage);
dialog.exec();
templateImage = dialog.GetSegmentationTemplate();
}
}
auto segmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(imageNode, templateImage, name);
segmentation = static_cast(segmentationNode->GetData());
if (m_TaskList->HasPreset(current))
{
const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetPreset(current));
mitk::LabelSetIOHelper::LoadLabelSetImagePreset(path.string(), segmentation);
}
else
{
auto label = mitk::LabelSetImageHelper::CreateNewLabel(segmentation);
if (m_TaskList->HasLabelName(current))
label->SetName(m_TaskList->GetLabelName(current));
segmentation->GetActiveLabelSet()->AddLabel(label);
}
dataStorage->Add(segmentationNode, imageNode);
}
else
{
auto segmentationNode = mitk::DataNode::New();
segmentationNode->SetName(name);
segmentationNode->SetData(segmentation);
dataStorage->Add(segmentationNode, imageNode);
}
+ auto prefs = GetSegmentationPreferences();
+
+ if (prefs.IsNotNull())
+ {
+ if (m_TaskList->HasLabelNameSuggestions(current))
+ {
+ auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetLabelNameSuggestions(current));
+
+ prefs->PutBool("default label naming", false);
+ prefs->Put("label suggestions", QString::fromStdString(path.string()));
+ prefs->PutBool("replace standard suggestions", true);
+ prefs->PutBool("suggest once", true);
+ }
+ else
+ {
+ prefs->PutBool("default label naming", true);
+ prefs->Put("label suggestions", "");
+ }
+ }
+
m_UnsavedChanges = false;
this->SetActiveTaskIndex(current);
this->SubscribeToActiveSegmentation();
this->OnCurrentTaskChanged();
}
void QmitkSegmentationTaskListWidget::SubscribeToActiveSegmentation()
{
if (m_ActiveTaskIndex.has_value())
{
auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value());
if (segmentationNode != nullptr)
{
auto segmentation = static_cast(segmentationNode->GetData());
auto command = itk::SimpleMemberCommand::New();
command->SetCallbackFunction(this, &QmitkSegmentationTaskListWidget::OnSegmentationModified);
m_SegmentationModifiedObserverTag = segmentation->AddObserver(itk::ModifiedEvent(), command);
}
}
}
void QmitkSegmentationTaskListWidget::UnsubscribeFromActiveSegmentation()
{
if (m_ActiveTaskIndex.has_value() && m_SegmentationModifiedObserverTag.has_value())
{
auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value());
if (segmentationNode != nullptr)
{
auto segmentation = static_cast(segmentationNode->GetData());
segmentation->RemoveObserver(m_SegmentationModifiedObserverTag.value());
}
m_SegmentationModifiedObserverTag.reset();
}
}
void QmitkSegmentationTaskListWidget::OnSegmentationModified()
{
if (!m_UnsavedChanges)
{
m_UnsavedChanges = true;
if (m_ActiveTaskIndex.value() == m_CurrentTaskIndex)
this->UpdateDetailsLabel();
}
}
void QmitkSegmentationTaskListWidget::SetActiveTaskIndex(const std::optional& index)
{
if (m_ActiveTaskIndex != index)
{
m_ActiveTaskIndex = index;
emit ActiveTaskChanged(m_ActiveTaskIndex);
}
}
void QmitkSegmentationTaskListWidget::SetCurrentTaskIndex(const std::optional& index)
{
if (m_CurrentTaskIndex != index)
{
m_CurrentTaskIndex = index;
this->OnCurrentTaskChanged();
emit CurrentTaskChanged(m_CurrentTaskIndex);
}
}
bool QmitkSegmentationTaskListWidget::ActiveTaskIsShown() const
{
return m_ActiveTaskIndex.has_value() && m_CurrentTaskIndex.has_value() && m_ActiveTaskIndex == m_CurrentTaskIndex;
}
bool QmitkSegmentationTaskListWidget::HandleUnsavedChanges()
{
if (m_UnsavedChanges)
{
const auto active = m_ActiveTaskIndex.value();
const auto current = m_CurrentTaskIndex.value();
auto title = QString("Load task %1").arg(current + 1);
if (m_TaskList->HasName(current))
title += ": " + QString::fromStdString(m_TaskList->GetName(current));
auto text = QString("The currently active task %1 ").arg(active + 1);
if (m_TaskList->HasName(active))
text += "(" + QString::fromStdString(m_TaskList->GetName(active)) + ") ";
text += "has unsaved changes.";
auto reply = QMessageBox::question(this, title, text, QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
switch (reply)
{
case QMessageBox::Save:
this->SaveActiveTask();
break;
case QMessageBox::Discard:
m_UnsavedChanges = false;
break;
default:
return false;
}
}
return true;
}
void QmitkSegmentationTaskListWidget::SaveActiveTask()
{
if (!m_ActiveTaskIndex.has_value())
return;
QApplication::setOverrideCursor(Qt::BusyCursor);
try
{
const auto active = m_ActiveTaskIndex.value();
m_TaskList->SaveTask(active, this->GetSegmentationDataNode(active)->GetData());
this->OnUnsavedChangesSaved();
}
catch (const mitk::Exception& e)
{
MITK_ERROR << e;
}
QApplication::restoreOverrideCursor();
}