diff --git a/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h index 54098651ed..3875cc63ea 100644 --- a/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h +++ b/Modules/Classification/CLActiveLearning/include/mitkActiveLearningInteractor.h @@ -1,58 +1,60 @@ /*=================================================================== 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 mitkActiveLearningInteractor_h #define mitkActiveLearningInteractor_h #include #include #include #include namespace mitk { class MITKCLACTIVELEARNING_EXPORT ActiveLearningInteractor : public DataInteractor { public: mitkClassMacro(ActiveLearningInteractor, DataInteractor) itkFactorylessNewMacro(Self) void SetPaintingPixelValue(mitk::LabelSetImage::PixelType value){m_PaintingPixelValue = value;} bool IsUsed(){return m_Used;} private: ActiveLearningInteractor(); ~ActiveLearningInteractor(); void ConnectActionsAndFunctions() override; void DataNodeChanged() override; void Paint(mitk::StateMachineAction* action, mitk::InteractionEvent* event); + void PaintInterpolate(mitk::StateMachineAction* action, mitk::InteractionEvent* event); + itk::Index<3> m_LastPixelIndex; mitk::LabelSetImage::PixelType m_PaintingPixelValue; bool m_Used; }; } #endif diff --git a/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml b/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml index 0e07ad0bab..871d3ff218 100644 --- a/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml +++ b/Modules/Classification/CLActiveLearning/resource/Interactions/Paint.xml @@ -1,16 +1,25 @@ - + - + + + + + + + + + + diff --git a/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml index e141844565..2d2fa3965a 100644 --- a/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml +++ b/Modules/Classification/CLActiveLearning/resource/Interactions/PaintConfig.xml @@ -1,10 +1,17 @@ + + + + + + + diff --git a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp index b156771c90..230da68305 100644 --- a/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp +++ b/Modules/Classification/CLActiveLearning/src/mitkActiveLearningInteractor.cpp @@ -1,147 +1,245 @@ /*=================================================================== 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 // Helper function to get an image from a data node. static mitk::LabelSetImage::Pointer GetImage(mitk::DataNode::Pointer dataNode) { if (dataNode.IsNull()) mitkThrow(); mitk::LabelSetImage::Pointer image = dynamic_cast(dataNode->GetData()); if (image.IsNull()) mitkThrow(); return image; } // Helper function to get a geometry of an image for a specific time step. static mitk::BaseGeometry::Pointer GetGeometry(mitk::LabelSetImage::Pointer image, unsigned int timeStep) { mitk::TimeGeometry::Pointer timeGeometry = image->GetTimeGeometry(); if (timeGeometry.IsNull()) mitkThrow(); auto geometry = timeGeometry->GetGeometryForTimeStep(timeStep); if (geometry.IsNull()) mitkThrow(); return geometry; } +static std::vector> InterpolateIndices(itk::Index<3> startIndex, itk::Index<3> endIndex, mitk::BaseGeometry* geometry) +{ + std::vector> resultIndices; + mitk::Point3D startPoint; + mitk::Point3D endPoint; + geometry->IndexToWorld(startIndex, startPoint); + geometry->IndexToWorld(endIndex, endPoint); + itk::Index<3> indexDelta; + int indexDeltaInc[3]; + for (int i=0; i<3; i++) + { + indexDelta[i] = endIndex[i] - startIndex[i]; + indexDeltaInc[i] = (indexDelta[i] > 0) ? 1 : (indexDelta[i] < 0) ? -1 : 0; + } + + int argm[3] = {0, 1, 2}; + if (abs(indexDelta[1]) > abs(indexDelta[0])) + { + argm[0] = 1; + argm[1] = 0; + } + if (abs(indexDelta[2]) > abs(indexDelta[argm[1]])) + { + argm[2] = argm[1]; + argm[1] = 2; + } + if (abs(indexDelta[2]) > abs(indexDelta[argm[0]])) + { + argm[1] = argm[0]; + argm[0] = 2; + } + + double slopes[2]; + slopes[0] = (endPoint[argm[1]] - startPoint[argm[1]]) / (endPoint[argm[0]] - startPoint[argm[0]]); + slopes[1] = (endPoint[argm[2]] - startPoint[argm[2]]) / sqrt((endPoint[argm[1]] - startPoint[argm[1]]) * (endPoint[argm[1]] - startPoint[argm[1]]) + (endPoint[argm[0]] - startPoint[argm[0]]) * (endPoint[argm[0]] - startPoint[argm[0]])); + itk::Index<3> currentIndex = startIndex; + mitk::Point3D currentPoint = startPoint; + + while (currentIndex != endIndex) + { + currentIndex[argm[0]] += indexDeltaInc[argm[0]]; + geometry->IndexToWorld(currentIndex, currentPoint); + currentPoint[argm[1]] = startPoint[argm[1]] + slopes[0] * (currentPoint[argm[0]] - startPoint[argm[0]]); + currentPoint[argm[2]] = startPoint[argm[2]] + slopes[1] * sqrt((currentPoint[argm[1]] - startPoint[argm[1]]) * (currentPoint[argm[1]] - startPoint[argm[1]]) + (currentPoint[argm[0]] - startPoint[argm[0]]) * (currentPoint[argm[0]] - startPoint[argm[0]])); + geometry->WorldToIndex(currentPoint, currentIndex); + resultIndices.push_back(currentIndex); + } + + return resultIndices; +} + //// Helper function to multiplex the actual Paint function call for different //// pixel types. As it's cumbersome and ugly, you may want to avoid such //// functions by using ITK for the actual painting and use the ITK access //// macros like we did for the ActiveLearningFilter. //static void Paint(mitk::Image::Pointer image, itk::Index<3> index, unsigned int timeStep) //{ // switch (image->GetPixelType().GetComponentType()) // { // case itk::ImageIOBase::CHAR: // Paint(image, index, timeStep); // break; // case itk::ImageIOBase::UCHAR: // Paint(image, index, timeStep); // break; // case itk::ImageIOBase::SHORT: // Paint(image, index, timeStep); // break; // case itk::ImageIOBase::USHORT: // Paint(image, index, timeStep); // break; // case itk::ImageIOBase::INT: // Paint(image, index, timeStep); // break; // case itk::ImageIOBase::UINT: // Paint(image, index, timeStep); // break; // default: // mitkThrow(); // } //} mitk::ActiveLearningInteractor::ActiveLearningInteractor() : m_PaintingPixelValue(0) { } mitk::ActiveLearningInteractor::~ActiveLearningInteractor() { } void mitk::ActiveLearningInteractor::ConnectActionsAndFunctions() { CONNECT_FUNCTION("paint", Paint) + CONNECT_FUNCTION("paint_interpolate", PaintInterpolate) } void mitk::ActiveLearningInteractor::DataNodeChanged() { this->ResetToStartState(); } void mitk::ActiveLearningInteractor::Paint(mitk::StateMachineAction* /*action*/, mitk::InteractionEvent* event) { try { auto renderer = event->GetSender(); auto image = GetImage(this->GetDataNode()); auto timeStep = renderer->GetTimeStep(); auto geometry = GetGeometry(image, timeStep); auto positionEvent = dynamic_cast(event); auto position = positionEvent->GetPositionInWorld(); if (!geometry->IsInside(position)) return; // Okay, we're safe. Convert the mouse position to the index of the pixel // we're pointing at. itk::Index<3> index; geometry->WorldToIndex<3>(position, index); // We don't need to paint over and over again while moving the mouse // pointer inside the same pixel. That's especially relevant when operating // on zoomed images. if (index != m_LastPixelIndex) { - // And finally... - auto layerImage = image->GetLayerImage(image->GetActiveLayer()); - mitk::ImagePixelWriteAccessor writeAccessor(layerImage, layerImage->GetVolumeData(timeStep)); + mitk::ImagePixelWriteAccessor writeAccessor(image.GetPointer(), image->GetVolumeData(timeStep)); writeAccessor.SetPixelByIndexSafe(index, m_PaintingPixelValue); image->Modified(); + this->GetDataNode()->Modified(); // mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - MITK_INFO << "(" << index[0] << ", " << index[1] << ", " << index[2] << "): target " << m_PaintingPixelValue << " is " << writeAccessor.GetPixelByIndexSafe(index); + m_LastPixelIndex = index; + m_Used = true; + } + } + catch (...) + { + return; + } +} + +void mitk::ActiveLearningInteractor::PaintInterpolate(mitk::StateMachineAction* /*action*/, mitk::InteractionEvent* event) +{ + try + { + auto renderer = event->GetSender(); + auto image = GetImage(this->GetDataNode()); + auto timeStep = renderer->GetTimeStep(); + auto geometry = GetGeometry(image, timeStep); + auto positionEvent = dynamic_cast(event); + auto position = positionEvent->GetPositionInWorld(); + + if (!geometry->IsInside(position)) return; + + // Okay, we're safe. Convert the mouse position to the index of the pixel + // we're pointing at. + itk::Index<3> index; + geometry->WorldToIndex<3>(position, index); + + // We don't need to paint over and over again while moving the mouse + // pointer inside the same pixel. That's especially relevant when operating + // on zoomed images. + if (index != m_LastPixelIndex) + { + // And finally... + mitk::ImagePixelWriteAccessor writeAccessor(image.GetPointer(), image->GetVolumeData(timeStep)); + // Paint all points between current and last pixel + auto indices = InterpolateIndices(m_LastPixelIndex, index, geometry); + for (auto i : indices) + { + writeAccessor.SetPixelByIndexSafe(i, m_PaintingPixelValue); + } + + image->Modified(); + this->GetDataNode()->Modified(); + +// mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_LastPixelIndex = index; m_Used = true; } } catch (...) { return; } } diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp index 88f7820e8f..3c46e78529 100644 --- a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.cpp @@ -1,495 +1,521 @@ /*=================================================================== 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 // Qt #include #include // Qmitk #include "QmitkActiveLearning.h" #include // MITK #include #include #include #include #include #include // Returns true if list has at least one entry and all entries are valid mitk::Images, otherwise false static bool SelectionAllImages(const QList& nodes) { if (nodes.empty()) { return false; } for (const auto& node : nodes) { if(!(node.IsNotNull() && dynamic_cast(node->GetData()) != nullptr)) return false; } return true; } // QColor to mitk::Color static mitk::Color QColorToMitkColor(const QColor& qcolor) { mitk::Color color; color.SetRed((float)qcolor.red() / 255); color.SetGreen((float)qcolor.green() / 255); color.SetBlue((float)qcolor.blue() / 255); return color; } // For debugging static void PrintAllLabels(mitk::LabelSetImage* image) { for (auto it=image->GetActiveLabelSet()->IteratorBegin(); it!=image->GetActiveLabelSet()->IteratorConstEnd(); ++it) { MITK_INFO << "Key: " << it->first << " - Name: " << it->second->GetName() << " - Value: " << it->second->GetValue() << " - Color: " << it->second->GetColor(); } } // Make values of labels a consistent range static void FillLabelValues(mitk::LabelSetImage* image) { int value(0); for (auto it=image->GetActiveLabelSet()->IteratorBegin(); it!=image->GetActiveLabelSet()->IteratorConstEnd(); ++it) { it->second->SetValue(value); value++; } image->GetActiveLabelSet()->SetActiveLabel(0); } /* ================================================================== * PUBLIC SLOTS * =============================================================== */ void ActiveLearning::Initialize() { // HANDLE WHAT HAPPENS IF INITIALIZED BEFORE, MAYBE RESET EVERYTHING? // MAYBE ALSO DISPLAY WARNING auto nodes = this->GetDataManagerSelection(); if (!SelectionAllImages(nodes)) return; // Set names again QString nameList = QString::fromStdString(nodes[0]->GetName()); if (nodes.length() >= 2) { for (int i=1; i"); nameList += QString::fromStdString(nodes[i]->GetName()); } } m_Controls.m_InitializeLabel->setText(nameList); // Initialize segmentation image with first selected image - m_Segmentation = mitk::LabelSetImage::New(); + m_AnnotationImage = mitk::LabelSetImage::New(); try { auto referenceImage = dynamic_cast(nodes[0]->GetData()); - m_Segmentation->Initialize(referenceImage); + m_AnnotationImage->Initialize(referenceImage); + } catch (mitk::Exception& e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Parent, "Error", "Could not initialize segmentation image"); return; } - auto segmentationNode = mitk::DataNode::New(); - segmentationNode->SetData(m_Segmentation); - segmentationNode->SetName("Labels"); - segmentationNode->SetColor(0., 0., 0.); - segmentationNode->SetBoolProperty("helper object", true); - m_Segmentation->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(nodes[0]->GetName().c_str())); - m_Segmentation->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New("Labels")); + m_AnnotationNode = mitk::DataNode::New(); + m_AnnotationNode->SetData(m_AnnotationImage); + m_AnnotationNode->SetName("Labels"); + m_AnnotationNode->SetColor(1., 1., 1.); +// m_AnnotationNode->SetBoolProperty("helper object", true); + m_AnnotationImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(nodes[0]->GetName().c_str())); + m_AnnotationImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New("Labels")); + + // Fill with zeros + { + unsigned int size = sizeof(mitk::LabelSetImage::PixelType); + for (unsigned int i=0; iGetDimension(); i++) + { + size *= m_AnnotationImage->GetDimension(i); + } + mitk::ImageWriteAccessor accessor(m_AnnotationImage.GetPointer(), m_AnnotationImage->GetVolumeData(0)); + memset(accessor.GetData(), 0, size); + } - if (nodes.contains(segmentationNode)) + if (nodes.contains(m_AnnotationNode)) { MITK_DEBUG << "Segmentation node shouldn't be selected"; return; } - if (this->GetDataStorage()->Exists(segmentationNode)) + if (this->GetDataStorage()->Exists(m_AnnotationNode)) { - this->GetDataStorage()->Remove(segmentationNode); + this->GetDataStorage()->Remove(m_AnnotationNode); } - this->GetDataStorage()->Add(segmentationNode, nodes[0]); + this->GetDataStorage()->Add(m_AnnotationNode, nodes[0]); // CALCULATE FEATURES HERE // Interactor auto activeLearningLib = us::ModuleRegistry::GetModule("MitkCLActiveLearning"); m_Interactor = mitk::ActiveLearningInteractor::New(); m_Interactor->LoadStateMachine("Paint.xml", activeLearningLib); m_Interactor->SetEventConfig("PaintConfig.xml", activeLearningLib); - m_Interactor->SetDataNode(segmentationNode); + m_Interactor->SetDataNode(m_AnnotationNode); // Automatically add first label OnAddLabelPushButtonClicked(); m_Controls.m_LabelControlsFrame->setVisible(true); m_Controls.m_InitializePushButton->setHidden(true); m_Active = true; } /* ================================================================== * PUBLIC * =============================================================== */ ActiveLearning::ActiveLearning() : m_Parent(nullptr), - m_Segmentation(nullptr), + m_AnnotationImage(nullptr), m_Active(false) { } ActiveLearning::~ActiveLearning() { } void ActiveLearning::CreateQtPartControl( QWidget *parent ) { m_Controls.setupUi(parent); m_Parent = parent; // Label model m_LabelListModel = new QStandardItemModel(0, 3, this); m_Controls.m_LabelTableView->setModel(m_LabelListModel); m_Controls.m_LabelTableView->horizontalHeader()->setDefaultSectionSize(20); m_Controls.m_LabelTableView->verticalHeader()->setDefaultSectionSize(20); NotEditableDelegate* itemDelegate = new NotEditableDelegate(parent); m_Controls.m_LabelTableView->setItemDelegateForColumn(1, itemDelegate); connect(m_Controls.m_LabelTableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(OnColorIconDoubleClicked(QModelIndex))); connect(m_Controls.m_LabelTableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(OnLabelListSelectionChanged(QItemSelection, QItemSelection))); connect(m_LabelListModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(OnLabelNameChanged(QModelIndex, QModelIndex))); // Buttons connect(m_Controls.m_InitializePushButton, SIGNAL(clicked()), this, SLOT(Initialize())); connect(m_Controls.m_AddLabelPushButton, SIGNAL(clicked()), this, SLOT(OnAddLabelPushButtonClicked())); connect(m_Controls.m_RemoveLabelPushButton, SIGNAL(clicked()), this, SLOT(OnRemoveLabelPushButtonClicked())); connect(m_Controls.m_PaintToolButton, SIGNAL(clicked()), this, SLOT(OnPaintToolButtonClicked())); connect(m_Controls.m_EraseToolButton, SIGNAL(clicked()), this, SLOT(OnEraseToolButtonClicked())); // Set start configuration m_Controls.m_LabelControlsFrame->setVisible(false); SetInitializeReady(false); } void ActiveLearning::ResetLabels() { // Remove all labels but the first - for (auto it=m_Segmentation->GetActiveLabelSet()->IteratorBegin(); it!=m_Segmentation->GetActiveLabelSet()->IteratorConstEnd(); it++) + for (auto it=m_AnnotationImage->GetActiveLabelSet()->IteratorBegin(); it!=m_AnnotationImage->GetActiveLabelSet()->IteratorConstEnd(); it++) { if (it->first != 0) { - m_Segmentation->GetActiveLabelSet()->RemoveLabel(it->first); + m_AnnotationImage->GetActiveLabelSet()->RemoveLabel(it->first); } } // Fill with labels from list for (int i=0; irowCount(); i++) { QString name = m_LabelListModel->item(i, 2)->text(); m_LabelListModel->item(i, 1)->setText(QString::number(i + 1)); QColor color = m_LabelListModel->item(i)->background().color(); - m_Segmentation->GetActiveLabelSet()->AddLabel(name.toStdString(), QColorToMitkColor(color)); + m_AnnotationImage->GetActiveLabelSet()->AddLabel(name.toStdString(), QColorToMitkColor(color)); } } const std::string ActiveLearning::VIEW_ID = "org.mitk.views.activelearning"; /* ================================================================== * PROTECTED SLOTS * =============================================================== */ void ActiveLearning::OnAddLabelPushButtonClicked() { - QString labelName = QString("Label ") + QString::number(m_Segmentation->GetActiveLabelSet()->ReverseIteratorBegin()->first + 1); + QString labelName = QString("Label ") + QString::number(m_AnnotationImage->GetActiveLabelSet()->ReverseIteratorBegin()->first + 1); QColor labelColor = Qt::GlobalColor(m_LabelListModel->rowCount() % 12 + 7); // We only want Qt default colors 7 to 18 // Create icon QStandardItem* colorSquare = new QStandardItem; colorSquare->setBackground(labelColor); colorSquare->setEditable(false); QPixmap colorPixmap(20, 20); colorPixmap.fill(labelColor); colorSquare->setIcon(QIcon(colorPixmap)); // Key is the highest existing key + 1 - int value = (int)m_Segmentation->GetActiveLabelSet()->ReverseIteratorBegin()->first + 1; + int value = (int)m_AnnotationImage->GetActiveLabelSet()->ReverseIteratorBegin()->first + 1; QStandardItem* valueItem = new QStandardItem(); valueItem->setText(QString::number(value)); // Create label item QStandardItem* label = new QStandardItem(labelName); // Add to image - m_Segmentation->GetActiveLabelSet()->AddLabel(labelName.toStdString(), QColorToMitkColor(labelColor)); - PrintAllLabels(m_Segmentation); + m_AnnotationImage->GetActiveLabelSet()->AddLabel(labelName.toStdString(), QColorToMitkColor(labelColor)); + PrintAllLabels(m_AnnotationImage); // Make list and insert QList list; list.append(colorSquare); list.append(valueItem); list.append(label); m_LabelListModel->appendRow(list); // If this is the first label, we activate the paint button + // We also have to set the data node color for this one, because for 1 values that color seems to define the rendered color if (m_LabelListModel->rowCount() == 1) { OnPaintToolButtonClicked(); + m_AnnotationNode->SetColor(QColorToMitkColor(labelColor)); } + + // Select newly added label + m_Controls.m_LabelTableView->selectRow(m_LabelListModel->rowCount() - 1); } void ActiveLearning::OnRemoveLabelPushButtonClicked() { QItemSelectionModel* selection = m_Controls.m_LabelTableView->selectionModel(); if (selection->hasSelection()) { unsigned int removeIndex = selection->selectedRows().first().row(); QString removeMessage = QString("Remove label '") + m_LabelListModel->item(removeIndex, 2)->text() + QString("'?"); QMessageBox::StandardButton removeReply; removeReply = QMessageBox::question(m_Parent, "Remove Label", removeMessage, QMessageBox::Yes | QMessageBox::No); if (removeReply == QMessageBox::Yes) { // if there are no annotations, reset labels if (m_Interactor->IsUsed()) { - m_Segmentation->EraseLabel(m_LabelListModel->item(removeIndex, 1)->text().toInt(), m_Segmentation->GetActiveLayer()); + std::vector labels; + labels.push_back(m_LabelListModel->item(removeIndex, 1)->text().toInt()); + m_AnnotationImage->RemoveLabels(labels); m_LabelListModel->removeRow(removeIndex); } else { m_LabelListModel->removeRow(removeIndex); ResetLabels(); } - PrintAllLabels(m_Segmentation); + PrintAllLabels(m_AnnotationImage); } } } void ActiveLearning::OnPaintToolButtonClicked() { m_Controls.m_PaintToolButton->setChecked(true); QItemSelectionModel* selection = m_Controls.m_LabelTableView->selectionModel(); int row(0); if (selection->hasSelection()) { row = selection->selectedRows().first().row(); } else { m_Controls.m_LabelTableView->selectRow(0); } m_Interactor->SetPaintingPixelValue(m_LabelListModel->item(row, 1)->text().toInt()); - m_Segmentation->GetActiveLabelSet()->SetActiveLabel(m_LabelListModel->item(row, 1)->text().toInt()); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(m_LabelListModel->item(row, 1)->text().toInt()); } void ActiveLearning::OnEraseToolButtonClicked() { m_Controls.m_EraseToolButton->setChecked(true); m_Interactor->SetPaintingPixelValue(0); - m_Segmentation->GetActiveLabelSet()->SetActiveLabel(0); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(0); } void ActiveLearning::OnActivateGuidancePushButtonToggled(bool toggled) { } void ActiveLearning::OnSaveSegmentationPushButtonClicked() { } void ActiveLearning::OnSavePredictionsPushButtonClicked() { } void ActiveLearning::OnExportSegmentationPushButtonClicked() { } void ActiveLearning::OnExportPredictionsPushButtonClicked() { } void ActiveLearning::OnColorIconDoubleClicked(const QModelIndex& index) { // Check if click is really from color icon if (index.column() != 0) { return; } else { // Color change dialog QColor setColor = QColorDialog::getColor(m_LabelListModel->itemFromIndex(index)->background().color(), m_Parent, "Select Label Color"); if (setColor.isValid()) { m_LabelListModel->itemFromIndex(index)->setBackground(setColor); QPixmap colorPixmap(20, 20); colorPixmap.fill(setColor); m_LabelListModel->itemFromIndex(index)->setIcon(QIcon(colorPixmap)); // Set color on label - m_Segmentation->GetActiveLabelSet()->GetLabel(m_LabelListModel->item(index.row(), 1)->text().toInt())->SetColor(QColorToMitkColor(setColor)); - PrintAllLabels(m_Segmentation); + m_AnnotationImage->GetActiveLabelSet()->GetLabel(m_LabelListModel->item(index.row(), 1)->text().toInt())->SetColor(QColorToMitkColor(setColor)); + m_AnnotationImage->GetActiveLabelSet()->UpdateLookupTable(m_LabelListModel->item(index.row(), 1)->text().toInt()); + PrintAllLabels(m_AnnotationImage); + + // If this is the label with value 1 we have to change the data node color + if (m_LabelListModel->item(index.row(), 1)->text().toInt() == 1) + { + m_AnnotationNode->SetColor(QColorToMitkColor(setColor)); + } } } } void ActiveLearning::OnLabelListSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/) { if (selected.empty()) return; // This assumes that only one item can be selected (single selection table view) try { int labelValue = m_LabelListModel->item(selected.indexes()[0].row(), 1)->text().toInt(); m_Interactor->SetPaintingPixelValue(labelValue); - m_Segmentation->GetActiveLabelSet()->SetActiveLabel(labelValue); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(labelValue); } catch (...) { m_Interactor->SetPaintingPixelValue(0); - m_Segmentation->GetActiveLabelSet()->SetActiveLabel(0); + m_AnnotationImage->GetActiveLabelSet()->SetActiveLabel(0); } } void ActiveLearning::OnLabelNameChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/) { auto item = m_LabelListModel->itemFromIndex(topLeft); if (item->column() != 2) return; - m_Segmentation->GetActiveLabelSet()->GetLabel(m_LabelListModel->item(item->row(), 1)->text().toInt())->SetName(item->text().toStdString()); + m_AnnotationImage->GetActiveLabelSet()->GetLabel(m_LabelListModel->item(item->row(), 1)->text().toInt())->SetName(item->text().toStdString()); } /* ================================================================== * PROTECTED * =============================================================== */ void ActiveLearning::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& nodes) { if (!SelectionAllImages(nodes)) { SetInitializeReady(false); return; } if (nodes.length() >= 2) { // First selection is the reference (could be any other) mitk::Image::Pointer referenceImage = dynamic_cast(nodes[0]->GetData()); mitk::BaseGeometry* referenceGeometry = referenceImage->GetTimeGeometry()->GetGeometryForTimeStep(0); // Adjust for multiple timesteps for (int i=1; i(nodes[i]->GetData()); mitk::BaseGeometry* currentGeometry = currentImage->GetTimeGeometry()->GetGeometryForTimeStep(0); // Adjust for multiple timesteps if (!mitk::Equal(*currentGeometry, *referenceGeometry, mitk::eps, true)) { SetInitializeReady(false); return; } } } // All nodes have the same geometry, allow init SetInitializeReady(true); } void ActiveLearning::SetFocus() { } /* ================================================================== * PRIVATE * =============================================================== */ void ActiveLearning::SetInitializeReady(bool ready) { if (ready) { // get selection, check again just to be sure auto nodes = this->GetDataManagerSelection(); if (!SelectionAllImages(nodes)) return; m_Controls.m_InitializePushButton->setEnabled(true); if (!m_Active) { QString nameList = QString::fromStdString(nodes[0]->GetName()); if (nodes.length() >= 2) { for (int i=1; i"); nameList += QString::fromStdString(nodes[i]->GetName()); } } m_Controls.m_InitializeLabel->setText(nameList); } } else { m_Controls.m_InitializePushButton->setDisabled(true); if (!m_Active) { m_Controls.m_InitializeLabel->setText("Selected images must have matching geometries"); } } } diff --git a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h index 199385b49f..e6111f4714 100644 --- a/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h +++ b/Plugins/org.mitk.gui.qt.activelearning/src/internal/QmitkActiveLearning.h @@ -1,119 +1,120 @@ /*=================================================================== 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 ActiveLearning_h #define ActiveLearning_h #include #include #include "ui_QmitkActiveLearningControls.h" // Qt #include #include #include // MITK #include #include /** \brief ActiveLearning \warning This class is not yet documented. Use "git blame" and ask the author to provide basic documentation. \sa QmitkAbstractView \ingroup ${plugin_target}_internal */ // Just a helper class class NotEditableDelegate : public QItemDelegate { Q_OBJECT public: explicit NotEditableDelegate(QObject* parent = nullptr) : QItemDelegate(parent) {} protected: QWidget* createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const {return Q_NULLPTR;} }; class ActiveLearning : public QmitkAbstractView { Q_OBJECT public slots: void Initialize(); public: ActiveLearning(); ~ActiveLearning(); void CreateQtPartControl(QWidget *parent) override; void ResetLabels(); static const std::string VIEW_ID; QStandardItemModel* m_LabelListModel; protected slots: void OnAddLabelPushButtonClicked(); void OnRemoveLabelPushButtonClicked(); void OnPaintToolButtonClicked(); void OnEraseToolButtonClicked(); void OnActivateGuidancePushButtonToggled(bool toggled); void OnSaveSegmentationPushButtonClicked(); void OnSavePredictionsPushButtonClicked(); void OnExportSegmentationPushButtonClicked(); void OnExportPredictionsPushButtonClicked(); void OnColorIconDoubleClicked(const QModelIndex& index); void OnLabelListSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/); void OnLabelNameChanged(const QModelIndex& topLeft, const QModelIndex& /*bottomRight*/); protected: void OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& nodes) override; void SetFocus() override; Ui::ActiveLearningControls m_Controls; QWidget* m_Parent; - mitk::LabelSetImage::Pointer m_Segmentation; + mitk::LabelSetImage::Pointer m_AnnotationImage; + mitk::DataNode::Pointer m_AnnotationNode; private: void SetInitializeReady(bool ready); bool m_Active; mitk::ActiveLearningInteractor::Pointer m_Interactor; }; #endif // ActiveLearning_h