diff --git a/CMakeExternals/MatchPoint.cmake b/CMakeExternals/MatchPoint.cmake index f23fcf2da2..0ef7624db4 100644 --- a/CMakeExternals/MatchPoint.cmake +++ b/CMakeExternals/MatchPoint.cmake @@ -1,64 +1,66 @@ #----------------------------------------------------------------------------- # MatchPoint #----------------------------------------------------------------------------- if(MITK_USE_MatchPoint) set(MatchPoint_SOURCE_DIR "" CACHE PATH "Location of the MatchPoint source directory") mark_as_advanced(MatchPoint_SOURCE_DIR) # Sanity checks if(DEFINED MatchPoint_DIR AND NOT EXISTS ${MatchPoint_DIR}) message(FATAL_ERROR "MatchPoint_DIR variable is defined but corresponds to non-existing directory") endif() if(NOT MatchPoint_DIR AND MatchPoint_SOURCE_DIR AND NOT EXISTS ${MatchPoint_SOURCE_DIR}) message(FATAL_ERROR "MatchPoint_SOURCE_DIR variable is defined but corresponds to non-existing directory") endif() set(proj MatchPoint) set(proj_DEPENDENCIES ITK) set(MatchPoint_DEPENDS ${proj}) if(NOT MatchPoint_DIR) if(MatchPoint_SOURCE_DIR) set(download_step SOURCE_DIR ${MatchPoint_SOURCE_DIR}) else() set(download_step URL ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/MatchPoint_rev_24ef6072.tar.gz URL_MD5 b0a0d7d63da5071db8e73dd6e6b4db7c ) endif() ExternalProject_Add(${proj} ${download_step} # INSTALL_COMMAND "${CMAKE_COMMAND} -P cmake_install.cmake" CMAKE_GENERATOR ${gen} CMAKE_ARGS ${ep_common_args} ${additional_cmake_args} -DBUILD_TESTING:BOOL=OFF -DITK_DIR:PATH=${ITK_DIR} #/src/ITK-build -DMAP_USE_SYSTEM_GDCM:BOOL=ON -DMAP_USE_SYSTEM_HDF5:BOOL=ON -DMAP_DISABLE_ITK_IO_FACTORY_AUTO_REGISTER:BOOL=ON -DMAP_WRAP_Plastimatch:BOOL=ON + -DMAP_BUILD_Ontology:BOOL=ON + -DMAP_BUILD_Ontology_simple:BOOL=ON -DGDCM_DIR:PATH=${GDCM_DIR} -DHDF5_DIR:PATH=${HDF5_DIR} CMAKE_CACHE_ARGS ${ep_common_cache_args} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} DEPENDS ${proj_DEPENDENCIES} ) ExternalProject_Get_Property(${proj} binary_dir) set(${proj}_DIR ${binary_dir}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif(MITK_USE_MatchPoint) diff --git a/Modules/Core/src/DataManagement/mitkVectorProperty.cpp b/Modules/Core/src/DataManagement/mitkVectorProperty.cpp index 5d2066957d..3f44f4fc78 100644 --- a/Modules/Core/src/DataManagement/mitkVectorProperty.cpp +++ b/Modules/Core/src/DataManagement/mitkVectorProperty.cpp @@ -1,92 +1,94 @@ /*=================================================================== 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 "mitkVectorProperty.h" namespace mitk { template bool VectorProperty::IsEqual(const BaseProperty &property) const { return this->m_PropertyContent == static_cast(property).m_PropertyContent; } template bool VectorProperty::Assign(const BaseProperty &property) { this->m_PropertyContent = static_cast(property).m_PropertyContent; return true; } template itk::LightObject::Pointer VectorProperty::InternalClone() const { itk::LightObject::Pointer result(new Self(*this)); return result; } template std::string VectorProperty::GetValueAsString() const { const size_t displayBlockLength = 3; size_t beginningElementsCount = displayBlockLength; size_t endElementsCount = displayBlockLength; if (m_PropertyContent.size() <= 2 * displayBlockLength) { beginningElementsCount = m_PropertyContent.size(); endElementsCount = 0; } // return either a block of all items // if the total number of maximum 2*displayBlockLength // // or return the first and last "displayBlockLength" // number of items separated by "[...]"; std::stringstream string_collector; for (size_t i = 0; i < beginningElementsCount; i++) string_collector << m_PropertyContent[i] << "\n"; if (endElementsCount) string_collector << "[... " << m_PropertyContent.size() - 2 * displayBlockLength << " more]\n"; for (size_t i = m_PropertyContent.size() - endElementsCount; i < m_PropertyContent.size(); ++i) string_collector << m_PropertyContent[i] << "\n"; std::string return_value = string_collector.str(); // remove last '\n' if (!return_value.empty()) return_value.erase(return_value.size() - 1); return return_value; } template void VectorProperty::SetValue(const VectorType &newValue) { m_PropertyContent = newValue; } template const typename VectorProperty::VectorType &VectorProperty::GetValue() const { return m_PropertyContent; } // Explicit instantiation for defined types. MITK_DEFINE_VECTOR_PROPERTY(double) MITK_DEFINE_VECTOR_PROPERTY(int) + MITK_DEFINE_VECTOR_PROPERTY(unsigned int) + MITK_DEFINE_VECTOR_PROPERTY(std::string) } // namespace mitk diff --git a/Modules/ModuleList.cmake b/Modules/ModuleList.cmake index e34b1219a1..b9a9267a2a 100644 --- a/Modules/ModuleList.cmake +++ b/Modules/ModuleList.cmake @@ -1,83 +1,86 @@ # The entries in the mitk_modules list must be # ordered according to their dependencies. set(MITK_MODULES Core CommandLine AppUtil RDF LegacyIO DataTypesExt Annotation LegacyGL AlgorithmsExt MapperExt DICOMReader DICOMReaderServices DICOMQI DICOMTesting SceneSerializationBase PlanarFigure ImageDenoising ImageExtraction SceneSerialization Gizmo GraphAlgorithms Multilabel ImageStatistics ContourModel SurfaceInterpolation Segmentation PlanarFigureSegmentation QtWidgets QtWidgetsExt Chart SegmentationUI MatchPointRegistration MatchPointRegistrationUI Classification GPGPU OpenIGTLink IGTBase IGT CameraCalibration OpenCL OpenCVVideoSupport QtOverlays ToFHardware ToFProcessing ToFUI PhotoacousticsHardware PhotoacousticsAlgorithms PhotoacousticsLib US USUI DicomUI + RegistrationOntology Remeshing Python QtPython Persistence OpenIGTLinkUI IGTUI DicomRT RTUI IOExt XNAT TubeGraph BiophotonicsHardware DiffusionImaging TumorInvasionAnalysis BoundingShape RenderWindowManager RenderWindowManagerUI + SemanticRelations + SemanticRelationsUI CEST BasicImageProcessing ModelFit ModelFitUI Pharmacokinetics PharmacokineticsUI ) if(MITK_ENABLE_PIC_READER) list(APPEND MITK_MODULES IpPicSupportIO) endif() diff --git a/Modules/QtWidgets/files.cmake b/Modules/QtWidgets/files.cmake index 4822eb025f..814f2211e4 100644 --- a/Modules/QtWidgets/files.cmake +++ b/Modules/QtWidgets/files.cmake @@ -1,107 +1,109 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES QmitkAbstractDataStorageModel.cpp QmitkApplicationCursor.cpp QmitkDataStorageComboBox.cpp QmitkDataStorageDefaultListModel.cpp QmitkDataStorageListModel.cpp QmitkDataStorageTableModel.cpp QmitkDataStorageSimpleTreeModel.cpp QmitkDataStorageTreeModel.cpp QmitkDataStorageTreeModelInternalItem.cpp + QmitkDnDDataNodeWidget.cpp QmitkFileReaderOptionsDialog.cpp QmitkFileReaderWriterOptionsWidget.cpp QmitkFileWriterOptionsDialog.cpp QmitkIOUtil.cpp QmitkLevelWindowPresetDefinitionDialog.cpp QmitkLevelWindowRangeChangeDialog.cpp QmitkLevelWindowWidgetContextMenu.cpp QmitkLevelWindowWidget.cpp QmitkLineEditLevelWindowWidget.cpp QmitkMemoryUsageIndicatorView.cpp QmitkMimeTypes.cpp QmitkNodeDescriptor.cpp QmitkColoredNodeDescriptor.cpp QmitkNodeDescriptorManager.cpp QmitkRenderWindowMenu.cpp QmitkProgressBar.cpp QmitkPropertiesTableEditor.cpp QmitkPropertiesTableModel.cpp QmitkPropertyDelegate.cpp QmitkRegisterClasses.cpp QmitkRenderingManager.cpp QmitkRenderingManagerFactory.cpp QmitkRenderWindow.cpp QmitkServiceListWidget.cpp QmitkSliderLevelWindowWidget.cpp QmitkStdMultiWidget.cpp QmitkMouseModeSwitcher.cpp QmitkDataStorageFilterProxyModel.cpp QmitkDataStorageComboBoxWithSelectNone.cpp QmitkPropertyItem.cpp QmitkPropertyItemDelegate.cpp QmitkPropertyItemModel.cpp QmitkStyleManager.cpp QmitkAbstractDataStorageInspector.cpp QmitkDataStorageListInspector.cpp QmitkDataStorageTreeInspector.cpp QmitkModelViewSelectionConnector.cpp mitkIDataStorageInspectorProvider.cpp mitkQtWidgetsActivator.cpp mitkDataStorageInspectorGenerator.cpp ) set(MOC_H_FILES include/QmitkAbstractDataStorageModel.h include/QmitkDataStorageComboBox.h include/QmitkDataStorageTableModel.h include/QmitkDataStorageTreeModel.h include/QmitkDataStorageSimpleTreeModel.h include/QmitkDataStorageDefaultListModel.h + include/QmitkDnDDataNodeWidget.h include/QmitkFileReaderOptionsDialog.h include/QmitkFileReaderWriterOptionsWidget.h include/QmitkFileWriterOptionsDialog.h include/QmitkLevelWindowPresetDefinitionDialog.h include/QmitkLevelWindowRangeChangeDialog.h include/QmitkLevelWindowWidgetContextMenu.h include/QmitkLevelWindowWidget.h include/QmitkLineEditLevelWindowWidget.h include/QmitkMemoryUsageIndicatorView.h include/QmitkNodeDescriptor.h include/QmitkColoredNodeDescriptor.h include/QmitkNodeDescriptorManager.h include/QmitkRenderWindowMenu.h include/QmitkProgressBar.h include/QmitkPropertiesTableEditor.h include/QmitkPropertyDelegate.h include/QmitkRenderingManager.h include/QmitkRenderWindow.h include/QmitkServiceListWidget.h include/QmitkSliderLevelWindowWidget.h include/QmitkStdMultiWidget.h include/QmitkMouseModeSwitcher.h include/QmitkDataStorageComboBoxWithSelectNone.h include/QmitkPropertyItemDelegate.h include/QmitkPropertyItemModel.h include/QmitkDataStorageListInspector.h include/QmitkAbstractDataStorageInspector.h include/QmitkDataStorageTreeInspector.h include/QmitkModelViewSelectionConnector.h ) set(UI_FILES src/QmitkFileReaderOptionsDialog.ui src/QmitkFileWriterOptionsDialog.ui src/QmitkLevelWindowPresetDefinition.ui src/QmitkLevelWindowWidget.ui src/QmitkLevelWindowRangeChange.ui src/QmitkMemoryUsageIndicator.ui src/QmitkServiceListWidgetControls.ui src/QmitkDataStorageListInspector.ui src/QmitkDataStorageTreeInspector.ui ) set(QRC_FILES resource/Qmitk.qrc ) diff --git a/Modules/QtWidgets/include/QmitkDnDDataNodeWidget.h b/Modules/QtWidgets/include/QmitkDnDDataNodeWidget.h new file mode 100644 index 0000000000..7a927a3314 --- /dev/null +++ b/Modules/QtWidgets/include/QmitkDnDDataNodeWidget.h @@ -0,0 +1,66 @@ +/*=================================================================== + +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 QMITKDNDDATANODEWIDGET_H +#define QMITKDNDDATANODEWIDGET_H + +#include "MitkQtWidgetsExports.h" + +// mitk core module +#include + +// qt +#include + +class QDragEnterEvent; +class QDropEvent; + +/** +* @brief A drag 'n' drop widget (QFrame) that checks the mime data of the incoming +* event. If the drag event is coming from another widget and the mime data +* is of type 'application/x-qmitk-datanode-ptrs' the node(s) will be dropped +* and a signal is emitted. +* A class including this dnd-widget can than handle the vector of dropped nodes +* appropriately. +* +*/ +class MITKQTWIDGETS_EXPORT QmitkDnDDataNodeWidget : public QFrame +{ + Q_OBJECT + +public: + + QmitkDnDDataNodeWidget(QWidget* parent = nullptr); + ~QmitkDnDDataNodeWidget() override; + +Q_SIGNALS: + + /** + * @brief The signal will be emitted if the drag events are accepted. + * It contains the sending dnd-widget and the vector of nodes that have been dropped. + * + */ + void NodesDropped(QmitkDnDDataNodeWidget* dnDDataNodeWidget, std::vector nodes); + +private: + + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; + +}; + +#endif // QMITKDNDDATANODEWIDGET_H diff --git a/Modules/QtWidgets/src/QmitkDnDDataNodeWidget.cpp b/Modules/QtWidgets/src/QmitkDnDDataNodeWidget.cpp new file mode 100644 index 0000000000..010e2504a0 --- /dev/null +++ b/Modules/QtWidgets/src/QmitkDnDDataNodeWidget.cpp @@ -0,0 +1,70 @@ +/*=================================================================== + +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 + +// mitk qt widgets module +#include "QmitkMimeTypes.h" + +// qt +#include + +QmitkDnDDataNodeWidget::QmitkDnDDataNodeWidget(QWidget* parent /*= nullptr*/) + : QFrame(parent) +{ + setAcceptDrops(true); +} + +QmitkDnDDataNodeWidget::~QmitkDnDDataNodeWidget() +{ + // nothing here +} + +void QmitkDnDDataNodeWidget::dragEnterEvent(QDragEnterEvent* event) +{ + if (this != event->source()) + { + event->acceptProposedAction(); + } + else + { + event->ignore(); + } +} + +void QmitkDnDDataNodeWidget::dragMoveEvent(QDragMoveEvent* event) +{ + if (event->mimeData()->hasFormat(QmitkMimeTypes::DataNodePtrs)) + { + event->acceptProposedAction(); + } + else + { + event->ignore(); + } +} + +void QmitkDnDDataNodeWidget::dropEvent(QDropEvent* event) +{ + QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(event->mimeData()); + if (!dataNodeList.empty()) + { + emit NodesDropped(this, dataNodeList.toVector().toStdVector()); + } + + event->acceptProposedAction(); +} + diff --git a/Modules/RegistrationOntology/CMakeLists.txt b/Modules/RegistrationOntology/CMakeLists.txt new file mode 100644 index 0000000000..ad8b813481 --- /dev/null +++ b/Modules/RegistrationOntology/CMakeLists.txt @@ -0,0 +1,4 @@ +MITK_CREATE_MODULE( + DEPENDS MitkCore + PACKAGE_DEPENDS PUBLIC MatchPoint +) diff --git a/Modules/RegistrationOntology/files.cmake b/Modules/RegistrationOntology/files.cmake new file mode 100644 index 0000000000..1e696fcf9f --- /dev/null +++ b/Modules/RegistrationOntology/files.cmake @@ -0,0 +1,5 @@ +file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") + +set(CPP_FILES + mitkLesionPropagation.cpp +) diff --git a/Modules/RegistrationOntology/include/mitkLesionPropagation.h b/Modules/RegistrationOntology/include/mitkLesionPropagation.h new file mode 100644 index 0000000000..ea2a7a7cd9 --- /dev/null +++ b/Modules/RegistrationOntology/include/mitkLesionPropagation.h @@ -0,0 +1,35 @@ +/*=================================================================== + +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 MITKLESIONPROPAGATION_H +#define MITKLESIONPROPAGATION_H + +// registration ontology module +#include "MitkRegistrationOntologyExports.h" + +// mitk core +#include + +// matchpoint ontology +#include +#include + +namespace mitk +{ + MITKREGISTRATIONONTOLOGY_EXPORT void FindClosestSegmentationMask(); +} // namespace mitk + +#endif // MITKLESIONPROPAGATION_H diff --git a/Modules/RegistrationOntology/src/mitkLesionPropagation.cpp b/Modules/RegistrationOntology/src/mitkLesionPropagation.cpp new file mode 100644 index 0000000000..7f73c07a76 --- /dev/null +++ b/Modules/RegistrationOntology/src/mitkLesionPropagation.cpp @@ -0,0 +1,22 @@ +/*=================================================================== + +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 "mitkLesionPropagation.h" + +void mitk::FindClosestSegmentationMask() +{ + std::cout << "Lesion propagation works and can be called" << std::endl; +} diff --git a/Modules/SemanticRelations/CMakeLists.txt b/Modules/SemanticRelations/CMakeLists.txt new file mode 100644 index 0000000000..916ed197cc --- /dev/null +++ b/Modules/SemanticRelations/CMakeLists.txt @@ -0,0 +1,7 @@ +MITK_CREATE_MODULE( + DEPENDS MitkSceneSerializationBase MitkDICOMReader MitkMultilabel MitkPersistence +) + +if(BUILD_TESTING) + ADD_SUBDIRECTORY(Testing) +endif(BUILD_TESTING) diff --git a/Modules/SemanticRelations/Testing/CMakeLists.txt b/Modules/SemanticRelations/Testing/CMakeLists.txt new file mode 100644 index 0000000000..153cd81e2e --- /dev/null +++ b/Modules/SemanticRelations/Testing/CMakeLists.txt @@ -0,0 +1 @@ +MITK_CREATE_MODULE_TESTS() diff --git a/Modules/SemanticRelations/Testing/files.cmake b/Modules/SemanticRelations/Testing/files.cmake new file mode 100644 index 0000000000..17436cc16b --- /dev/null +++ b/Modules/SemanticRelations/Testing/files.cmake @@ -0,0 +1,3 @@ +set(MODULE_TESTS + mitkSemanticRelationsTest.cpp +) diff --git a/Modules/SemanticRelations/Testing/mitkSemanticRelationsTest.cpp b/Modules/SemanticRelations/Testing/mitkSemanticRelationsTest.cpp new file mode 100644 index 0000000000..ad477ed635 --- /dev/null +++ b/Modules/SemanticRelations/Testing/mitkSemanticRelationsTest.cpp @@ -0,0 +1,419 @@ +/*=================================================================== + +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 "mitkSemanticRelations.h" +#include "mitkSemanticRelationException.h" + +// mitk core +#include +#include +#include +#include +#include +#include + +// mitk persistence +#include + +// mitk multilabel +#include + +class mitkSemanticRelationsTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkSemanticRelationsTestSuite); + MITK_TEST(CombinedTest); + CPPUNIT_TEST_SUITE_END(); + +private: + + mitk::DataStorage::Pointer m_DataStorage; + std::vector m_BaseData; + mitk::PreferenceListReaderOptionsFunctor m_Functor; + +public: + + void setUp() override + { + mitk::PersistenceService::LoadModule(); + + m_DataStorage = mitk::StandaloneDataStorage::New(); + + mitk::PreferenceListReaderOptionsFunctor::ListType preference = { "MITK DICOM Reader v2 (classic config)" }; + mitk::PreferenceListReaderOptionsFunctor::ListType emptyList = {}; + + m_Functor = mitk::PreferenceListReaderOptionsFunctor(preference, emptyList); + } + + void tearDown() override + { + } + + void CombinedTest() + { + TestAddNewData(); + ExtendControlPoint(); + AddAndRemoveInformationType(); + SegmentationAndLesion(); + InvalidData(); + CombinedQuery(); + RemoveAndUnlink(); + Exceptions(); + Overwrite(); + } + + void TestAddNewData() + { + MITK_INFO << "TestAddNewData"; + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT Pelvis/Abd/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + m_DataStorage->Add(dataNode); + + CPPUNIT_ASSERT_MESSAGE("Not a valid data node", dataNode.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image data is empty", dataNode->GetData() != nullptr); + + mitk::SemanticRelations semanticRelations(m_DataStorage); + SemanticRelationsTestHelper::AddImageInstance(dataNode, semanticRelations); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + CPPUNIT_ASSERT_MESSAGE("CaseID not correctly stored", allCaseIDs.front() == "TCGA-VP-A87D"); + + SemanticTypes::ControlPoint controlPointOfData = semanticRelations.GetControlPointOfData(dataNode); + SemanticTypes::Date startDate; startDate.year = 1992; startDate.month = 12; startDate.day = 24; + SemanticTypes::Date endDate; endDate.year = 1992; endDate.month = 12; endDate.day = 24; + CPPUNIT_ASSERT_MESSAGE("Control point not correctly stored", controlPointOfData.startPoint == startDate && controlPointOfData.endPoint == endDate); + } + + void ExtendControlPoint() + { + MITK_INFO << "ExtendControlPoint"; + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT Pelvis/Abd Pel/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + m_DataStorage->Add(dataNode); + + CPPUNIT_ASSERT_MESSAGE("Not a valid data node", dataNode.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image data is empty", dataNode->GetData() != nullptr); + + mitk::SemanticRelations semanticRelations(m_DataStorage); + SemanticRelationsTestHelper::AddImageInstance(dataNode, semanticRelations); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + CPPUNIT_ASSERT_MESSAGE("Only one CaseID should be stored", allCaseIDs.size() == 1); + + std::vector allControlPoints = semanticRelations.GetAllControlPointsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("Only one control point should be stored", allControlPoints.size() == 1); + + SemanticTypes::ControlPoint controlPointOfData = semanticRelations.GetControlPointOfData(dataNode); + SemanticTypes::Date startDate; startDate.year = 1992; startDate.month = 12; startDate.day = 10; + SemanticTypes::Date endDate; endDate.year = 1992; endDate.month = 12; endDate.day = 24; + CPPUNIT_ASSERT_MESSAGE("Control point not correctly extended", controlPointOfData.startPoint == startDate && controlPointOfData.endPoint == endDate); + + std::vector allData = semanticRelations.GetAllDataOfControlPoint(allCaseIDs.front(), controlPointOfData); + CPPUNIT_ASSERT_MESSAGE("Two images should reference the control point", allData.size() == 2); + } + + void AddAndRemoveInformationType() + { + MITK_INFO << "AddAndRemoveInformationType"; + // load previously stored image + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT Pelvis/Abd Pel/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + + CPPUNIT_ASSERT_MESSAGE("Not a valid data node", dataNode.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image data is empty", dataNode->GetData() != nullptr); + + mitk::SemanticRelations semanticRelations(m_DataStorage); + + semanticRelations.AddInformationTypeToImage(dataNode, "CT"); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + std::vector allInformationTypes = semanticRelations.GetAllInformationTypesOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("One information type should be stored", allInformationTypes.size() == 1); + + SemanticTypes::InformationType informationType = semanticRelations.GetInformationTypeOfImage(dataNode); + CPPUNIT_ASSERT_MESSAGE("Information type should be equal to 'CT'", informationType == "CT"); + + semanticRelations.RemoveInformationTypeFromImage(dataNode, "CT"); + + allInformationTypes = semanticRelations.GetAllInformationTypesOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("No information type should be stored", allInformationTypes.size() == 0); + } + + void SegmentationAndLesion() + { + MITK_INFO << "SegmentationAndLesion"; + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/TUMOR PET RES/PET/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + + CPPUNIT_ASSERT_MESSAGE("Not a valid data node", dataNode.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image data is empty", dataNode->GetData() != nullptr); + + mitk::LabelSetImage::Pointer segmentationImage = mitk::LabelSetImage::New(); + + // workaround: manually set the PatientName tag: use the tag from the real DICOM image + mitk::PropertyList::Pointer segmentationPropertyList = mitk::PropertyList::New(); + segmentationPropertyList->SetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str(), + imageData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); + + // workaround: manually set the SeriesInstanceUID tag: use the tag from the real DICOM image and modify it to have different identifiers + std::string seriesInstanceUID = imageData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0020, 0x000e).c_str())->GetValueAsString() + "_S"; + segmentationPropertyList->SetStringProperty(mitk::GeneratePropertyNameForDICOMTag(0x0020, 0x000e).c_str(), seriesInstanceUID.c_str()); + + segmentationImage->SetPropertyList(segmentationPropertyList); + mitk::DataNode::Pointer segmentationNode = mitk::DataNode::New(); + segmentationNode->SetData(segmentationImage); + m_DataStorage->Add(segmentationNode, dataNode); + + mitk::SemanticRelations semanticRelations(m_DataStorage); + SemanticRelationsTestHelper::AddSegmentationInstance(segmentationNode, dataNode, semanticRelations); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + CPPUNIT_ASSERT_MESSAGE("Only one CaseID should be stored", allCaseIDs.size() == 1); + + std::vector allLesions = semanticRelations.GetAllLesionsInImage(dataNode); + CPPUNIT_ASSERT_MESSAGE("One lesion should be stored", allLesions.size() == 1); + + SemanticTypes::Lesion representedLesion = semanticRelations.GetRepresentedLesion(segmentationNode); + CPPUNIT_ASSERT_MESSAGE("Represented lesion should be the only stored lesion", allLesions.front().UID == representedLesion.UID); + } + + void InvalidData() + { + MITK_INFO << "InvalidData"; + mitk::SemanticRelations semanticRelations(m_DataStorage); + std::vector allControlPoints = semanticRelations.GetAllControlPointsOfCase("noID"); + CPPUNIT_ASSERT_MESSAGE("No control point should be stored, given a non-existing CaseID", allControlPoints.size() == 0); + + // load new image but do not create any semantic relation + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT Abdomen/non-contrast/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + + SemanticTypes::ControlPoint controlPoint = semanticRelations.GetControlPointOfData(dataNode); + CPPUNIT_ASSERT_MESSAGE("No control point should be stored for an image that is not stored", controlPoint.UID.empty()); + + // TODO: use invalid DICOM data (no tag - leads to empty case ID or empty node ID) + } + + void CombinedQuery() + { + MITK_INFO << "CombinedQuery"; + // load new image with a different time point + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT abd and Pelvis/CAP/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + m_DataStorage->Add(dataNode); + + CPPUNIT_ASSERT_MESSAGE("Not a valid data node", dataNode.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image data is empty", dataNode->GetData() != nullptr); + + mitk::SemanticRelations semanticRelations(m_DataStorage); + + SemanticRelationsTestHelper::AddImageInstance(dataNode, semanticRelations); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + CPPUNIT_ASSERT_MESSAGE("Only one CaseID should be stored", allCaseIDs.size() == 1); + + std::vector allControlPoints = semanticRelations.GetAllControlPointsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("Two different control points should be stored", allControlPoints.size() == 2); + + semanticRelations.AddInformationTypeToImage(dataNode, "CT"); + std::vector allInformationTypes = semanticRelations.GetAllInformationTypesOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("One information type should be stored", allInformationTypes.size() == 1); + + std::vector filteredControlPoints = semanticRelations.GetAllControlPointsOfCase(allCaseIDs.front(), allInformationTypes.front()); + allInformationTypes = semanticRelations.GetAllInformationTypesOfCase(allCaseIDs.front(), allControlPoints.back()); + CPPUNIT_ASSERT_MESSAGE("Only one control points should match", filteredControlPoints.size() == 1); + CPPUNIT_ASSERT_MESSAGE("Only one information type should match", allInformationTypes.size() == 1); + + filteredControlPoints = semanticRelations.GetAllControlPointsOfCase(allCaseIDs.front(), "MR"); + allInformationTypes = semanticRelations.GetAllInformationTypesOfCase(allCaseIDs.front(), allControlPoints.front()); + CPPUNIT_ASSERT_MESSAGE("No control point should match", filteredControlPoints.size() == 0); + CPPUNIT_ASSERT_MESSAGE("No information type should match", allInformationTypes.size() == 0); + + std::vector allLesions = semanticRelations.GetAllLesionsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("One lesion should be stored", allLesions.size() == 1); + + std::vector filteredLesions = semanticRelations.GetAllLesionsOfCase(allCaseIDs.front(), allControlPoints.front()); + CPPUNIT_ASSERT_MESSAGE("No lesion should match", filteredLesions.size() == 0); + } + + void RemoveAndUnlink() + { + MITK_INFO << "RemoveAndUnlink"; + /************************************************************************/ + /* unlink and remove control point */ + /************************************************************************/ + // load already stored image with its own single control point + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT abd and Pelvis/CAP/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + + CPPUNIT_ASSERT_MESSAGE("Not a valid data node", dataNode.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image data is empty", dataNode->GetData() != nullptr); + + mitk::SemanticRelations semanticRelations(m_DataStorage); + + SemanticTypes::ControlPoint singleControlPoint = semanticRelations.GetControlPointOfData(dataNode); + semanticRelations.UnlinkDataFromControlPoint(dataNode, singleControlPoint); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + CPPUNIT_ASSERT_MESSAGE("Only one CaseID should be stored", allCaseIDs.size() == 1); + + std::vector allControlPoints = semanticRelations.GetAllControlPointsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("Only one control point should be stored since one has been removed", allControlPoints.size() == 1); + + singleControlPoint = semanticRelations.GetControlPointOfData(dataNode); + CPPUNIT_ASSERT_MESSAGE("Control point property not correctly reset", singleControlPoint.startPoint == SemanticTypes::ControlPoint().startPoint && singleControlPoint.endPoint == SemanticTypes::ControlPoint().endPoint); + + // load already stored image which extended a previously existing control point + testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT Pelvis/Abd Pel/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + imageData = m_BaseData.front(); + dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + + CPPUNIT_ASSERT_MESSAGE("Not a valid data node", dataNode.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image data is empty", dataNode->GetData() != nullptr); + + SemanticTypes::ControlPoint extendedControlpoint = semanticRelations.GetControlPointOfData(dataNode); + semanticRelations.UnlinkDataFromControlPoint(dataNode, extendedControlpoint); + + allControlPoints = semanticRelations.GetAllControlPointsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("Still one control point should be stored since none has been removed", allControlPoints.size() == 1); + + extendedControlpoint = allControlPoints.front(); + SemanticTypes::Date startDate; startDate.year = 1992; startDate.month = 12; startDate.day = 24; + SemanticTypes::Date endDate; endDate.year = 1992; endDate.month = 12; endDate.day = 24; + CPPUNIT_ASSERT_MESSAGE("Control point not correctly computed", extendedControlpoint.startPoint == startDate && extendedControlpoint.endPoint == endDate); + } + + void Exceptions() + { + MITK_INFO << "Exceptions"; + /************************************************************************/ + /* remove lesion */ + /************************************************************************/ + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/TUMOR PET RES/PET/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + + mitk::SemanticRelations semanticRelations(m_DataStorage); + + mitk::LabelSetImage::Pointer segmentationImage = mitk::LabelSetImage::New(); + + // workaround: manually set the PatientName tag: use the tag from the real DICOM image + mitk::PropertyList::Pointer segmentationPropertyList = mitk::PropertyList::New(); + segmentationPropertyList->SetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str(), + imageData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str())); + + // workaround: manually set the SeriesInstanceUID tag: use the tag from the real DICOM image and modify it to have different identifiers + std::string seriesInstanceUID = imageData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0020, 0x000e).c_str())->GetValueAsString() + "_S"; + segmentationPropertyList->SetStringProperty(mitk::GeneratePropertyNameForDICOMTag(0x0020, 0x000e).c_str(), seriesInstanceUID.c_str()); + + segmentationImage->SetPropertyList(segmentationPropertyList); + mitk::DataNode::Pointer segmentationNode = mitk::DataNode::New(); + segmentationNode->SetData(segmentationImage); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + CPPUNIT_ASSERT_MESSAGE("Only one CaseID should be stored", allCaseIDs.size() == 1); + + std::vector allLesions = semanticRelations.GetAllLesionsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("One lesion should be stored", allLesions.size() == 1); + + CPPUNIT_ASSERT_THROW_MESSAGE("Semantic relation exception not thrown: Remove lesion instance", semanticRelations.RemoveLesion(allCaseIDs.front(), allLesions.front()), mitk::SemanticRelationException); + // test adding a lesion instance that already exists + CPPUNIT_ASSERT_THROW_MESSAGE("Semantic relation exception not thrown: Add lesion instance", semanticRelations.AddLesion(allCaseIDs.front(), allLesions.front()), mitk::SemanticRelationException); + + // use the test helper to remove the segmentation instance from the storage so that the referenced lesion instance can be removed (should not throw an exception) + SemanticRelationsTestHelper::RemoveSegmentationInstance(segmentationNode, semanticRelations); + CPPUNIT_ASSERT_NO_THROW_MESSAGE("Semantic relation exception thrown: Remove lesion instance", semanticRelations.RemoveLesion(allCaseIDs.front(), allLesions.front())); + CPPUNIT_ASSERT_NO_THROW_MESSAGE("Semantic relation exception not thrown: Add lesion instance", semanticRelations.AddLesion(allCaseIDs.front(), allLesions.front())); + + // test invalid lesion, control point UID, information type + CPPUNIT_ASSERT_THROW_MESSAGE("Semantic relation exception not thrown: Get segmentations of lesion", semanticRelations.GetAllSegmentationsOfLesion(allCaseIDs.front(), SemanticTypes::Lesion()), mitk::SemanticRelationException); + CPPUNIT_ASSERT_THROW_MESSAGE("Semantic relation exception not thrown: Get data of control point", semanticRelations.GetAllDataOfControlPoint(allCaseIDs.front(), SemanticTypes::ControlPoint()), mitk::SemanticRelationException); + CPPUNIT_ASSERT_THROW_MESSAGE("Semantic relation exception not thrown: Get data of information type", semanticRelations.GetAllDataOfInformationType(allCaseIDs.front(), SemanticTypes::InformationType()), mitk::SemanticRelationException); + + // test overwriting a lesion instance that does not exist + CPPUNIT_ASSERT_THROW_MESSAGE("Semantic relation exception not thrown: Add lesion instance", semanticRelations.OverwriteLesion(allCaseIDs.front(), SemanticTypes::Lesion()), mitk::SemanticRelationException); + } + + void Overwrite() + { + MITK_INFO << "Overwrite"; + mitk::SemanticRelations semanticRelations(m_DataStorage); + + std::vector allCaseIDs = semanticRelations.GetAllCaseIDs(); + CPPUNIT_ASSERT_MESSAGE("Only one CaseID should be stored", allCaseIDs.size() == 1); + + std::vector allLesions = semanticRelations.GetAllLesionsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("One lesion should be stored", allLesions.size() == 1); + + SemanticTypes::Lesion overwritingLesion = allLesions.front(); + SemanticTypes::LesionClass overwritingLesionClass = overwritingLesion.lesionClass; + + CPPUNIT_ASSERT_MESSAGE("Only lesion should not have a lesion class", overwritingLesionClass.classType == ""); + + overwritingLesionClass.classType = "unspecified lesion class"; + overwritingLesion.lesionClass = overwritingLesionClass; + semanticRelations.OverwriteLesion(allCaseIDs.front(), overwritingLesion); + + CPPUNIT_ASSERT_MESSAGE("Only lesion should have an unspecified lesion class", overwritingLesionClass.classType == "unspecified lesion class"); + + // load an already stored image which extends a previously existing control point (was unlinked before) + std::string testDataFilePath = GetTestDataFilePath("SemanticRelation/TCGA-VP-A87D/CT Pelvis/Abd Pel/000000.dcm"); + m_BaseData = mitk::IOUtil::Load(testDataFilePath, &m_Functor); + mitk::BaseData::Pointer imageData = m_BaseData.front(); + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); + dataNode->SetData(imageData); + + std::vector allControlPoints = semanticRelations.GetAllControlPointsOfCase(allCaseIDs.front()); + CPPUNIT_ASSERT_MESSAGE("Only one control point should be stored", allControlPoints.size() == 1); + + SemanticTypes::ControlPoint extendedControlPoint = ControlPointManager::ExtendClosestControlPoint(dataNode, allControlPoints); + CPPUNIT_ASSERT_MESSAGE("Extended control point should not be empty", extendedControlPoint.UID != ""); + + semanticRelations.OverwriteControlPointAndLinkData(dataNode, extendedControlPoint); + + SemanticTypes::ControlPoint controlPointOfData = semanticRelations.GetControlPointOfData(dataNode); + SemanticTypes::Date startDate; startDate.year = 1992; startDate.month = 12; startDate.day = 10; + SemanticTypes::Date endDate; endDate.year = 1992; endDate.month = 12; endDate.day = 24; + CPPUNIT_ASSERT_MESSAGE("Control point not correctly extended", controlPointOfData.startPoint == startDate && controlPointOfData.endPoint == endDate); + + std::vector allData = semanticRelations.GetAllDataOfControlPoint(allCaseIDs.front(), controlPointOfData); + CPPUNIT_ASSERT_MESSAGE("Two images should reference the control point", allData.size() == 2); + } +}; + +MITK_TEST_SUITE_REGISTRATION(mitkSemanticRelations) diff --git a/Modules/SemanticRelations/files.cmake b/Modules/SemanticRelations/files.cmake new file mode 100644 index 0000000000..3fbf24cd98 --- /dev/null +++ b/Modules/SemanticRelations/files.cmake @@ -0,0 +1,12 @@ +file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") + +set(CPP_FILES + mitkControlPointManager.cpp + mitkDICOMHelper.cpp + mitkLesionData.cpp + mitkLesionManager.cpp + mitkNodePredicates.cpp + mitkRelationStorage.cpp + mitkSemanticRelations.cpp + mitkUIDGeneratorBoost.cpp +) diff --git a/Modules/SemanticRelations/include/mitkControlPointManager.h b/Modules/SemanticRelations/include/mitkControlPointManager.h new file mode 100644 index 0000000000..c1343ebd8f --- /dev/null +++ b/Modules/SemanticRelations/include/mitkControlPointManager.h @@ -0,0 +1,93 @@ +/*=================================================================== + +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 MITKCONTROLPOINTMANAGER_H +#define MITKCONTROLPOINTMANAGER_H + +#include + +// semantic relations module +#include "mitkSemanticTypes.h" + +// mitk core +#include + +namespace mitk +{ + /** + * @brief Provides helper functions that are needed to work with control points. + * + * These functions help to generate new control points, check for overlapping / containing control points or provide functionality + * to find a fitting control point or even extend an already existing control point. + */ + /** + * @brief Generates a control point from a given data node. + * The date is extracted from the data node by using the 'DICOMHelper::GetDICOMDateFromDataNode'-function. + * + * @param datanode A data node pointer, whose date should be included in the newly generated control point. + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::ControlPoint GenerateControlPoint(const mitk::DataNode* datanode); + /** + * @brief Find and return a whole control point including its date given a specific control point UID. + * + * @param controlPointUID The control point UID as string. + * @param allControlPoints All currently known control points of a specific case. + * + * @return The control point with its UID and the date. + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::ControlPoint GetControlPointByUID(const SemanticTypes::ID& controlPointUID, const SemanticTypes::ControlPointVector& allControlPoints); + /** + * @brief Returns an already existing control point from the given vector of control points. This existing control point has the + * the same date (year, month, day) as the given single control point. + * If no existing control point can be found an empty control point is returned. + * + * @param controlPoint The control point to check for existence. + * @param allControlPoints The vector of already existing control points. + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::ControlPoint FindExistingControlPoint(const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ControlPointVector& allControlPoints); + /** + * @brief Returns an already existing close control point from the given vector of control points. This closest control point has a date + * date that is within a certain distance-in-days to the given control point. + * If no closest control point can be found within the distance threshold an empty control point is returned. + * + * @param controlPoint The control point to check for distance. + * @param allControlPoints The vector of already existing control points. + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::ControlPoint FindClosestControlPoint(const SemanticTypes::ControlPoint& controlPoint, SemanticTypes::ControlPointVector& allControlPoints); + /** + * @brief Returns the examination period to which the given control point belongs. + * Each examination point holds a vector of control point UIDs so that the UID of the given control point can be compared against the UIDs of the vector. + * An empty examination period is returned if, + * - the given vector of examination periods is empty + * - the examination periods do not contain any control point UIDs + * - the UID of the given control point is not contained in any examination period + * + * @param controlPoint The control point of which the examination period should be found. + * @param allExaminationPeriods All currently known examination periods of a specific case. + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::ExaminationPeriod FindExaminationPeriod(const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriodVector& allExaminationPeriods); + /** + * @brief Sort the given vector of examination periods. + * Each examination period has a vector of control point UIDs (stored in chronological order). + * The examination periods can be sorted by comparing the first control points of the examination periods. + * + * @param allExaminationPeriods All currently known examination periods of a specific case. + * @param allControlPoints All currently known control points of a specific case. + */ + MITKSEMANTICRELATIONS_EXPORT void SortExaminationPeriods(SemanticTypes::ExaminationPeriodVector& allExaminationPeriods, const SemanticTypes::ControlPointVector& allControlPoints); +} // namespace mitk + +#endif // MITKCONTROLPOINTMANAGER_H diff --git a/Modules/SemanticRelations/include/mitkDICOMHelper.h b/Modules/SemanticRelations/include/mitkDICOMHelper.h new file mode 100644 index 0000000000..082ad69e0f --- /dev/null +++ b/Modules/SemanticRelations/include/mitkDICOMHelper.h @@ -0,0 +1,110 @@ +/*=================================================================== + +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 MITKDICOMHELPER_H +#define MITKDICOMHELPER_H + +#include + +// semantic relations module +#include "mitkSemanticTypes.h" + +// mitk core +#include +#include + +// c++ +#include + +/* +* @brief Provides helper functions to convert DICOM Tag information. +* +* In order to identify the patient of an image or segmentation or to set the control point of DICOM data, +* these functions are used to retrieve the DICOM tags from the given data nodes and convert them into semantic types +* that can be used by the SemanticRelations class. +*/ +namespace mitk +{ + /* + * @brief Extracts a specific DICOM tag from the node's base data + * and returns the tag as a string. This tag string is used as an identifier for the patient (case). + * + * @pre The given data node or the node's base data has to be valid (!nullptr). + * @pre The node's base data has to have the specific DICOM Tag property set. + * @throw mitk::Exception if the given data node, the node's base data or the extracted DICOM tag are invalid (==nullptr). + * + * @par dataNode The data node, of which the DICOM tag should be extracted. + * + * @return The extracted DICOM tag as string. + * An empty string, if the DICOM tag can not be extracted (i.e. the data node or + * the underlying base data is invalid or the DICOM tag does not exist for the given data node). + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::CaseID GetCaseIDFromDataNode(const mitk::DataNode* dataNode); + /* + * @brief Extracts a specific DICOM tag (currently "0x0020, 0x000e": SeriesInstanceUID) from the node's base data + * and returns the tag as a string. This tag string is used as an identifier for the image instance. + * + * @pre The given data node or the node's base data has to be valid (!nullptr). + * @pre The node's base data has to have the "0x0020, 0x000e" DICOM Tag property set. + * @throw mitk::Exception if the given data node, the node's base data or the extracted DICOM tag are invalid (==nullptr). + * + * @par dataNode The data node, of which the DICOM tag should be extracted. + * + * @return The extracted DICOM tag as string. + * An empty string, if the DICOM tag can not be extracted (i.e. the data node or + * the underlying base data is invalid or the DICOM tag does not exist for the given data node). + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::ID GetIDFromDataNode(const mitk::DataNode* dataNode); + /* + * @brief Extracts a specific DICOM tag (currently "0x0008, 0x0022": AcquisitionDate) from the node's base data + * and returns the tag as a control point. + * + * @pre The given data node or the node's base data has to be valid (!nullptr). + * @pre The node's base data has to have the "0x0008, 0x0022" DICOM Tag property set. + * @throw mitk::Exception if the given data node, the node's base data or the extracted DICOM tag are invalid (==nullptr). + * + * @par dataNode The data node, of which the DICOM tag should be extracted. + * + * @return The extracted DICOM tag as control point. + * An empty control point, if the DICOM tag can not be extracted (i.e. the data node or + * the underlying base data is invalid or the DICOM tag does not exist for the given data node). + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::ControlPoint GetDICOMDateFromDataNode(const mitk::DataNode* dataNode); + /** + * @brief Extracts a specific DICOM tag from the node's base data and returns the tag as a information type (a string). + * + * @pre The given data node or the node's base data has to be valid (!nullptr). + * @pre The node's base data has to have the "0x0008, 0x0060" DICOM Tag property set. + * @throw mitk::Exception if the given data node, the node's base data or the extracted DICOM tag are invalid (==nullptr). + * + * @par dataNode The data node, of which the DICOM tag should be extracted. + * + * @return The extracted DICOM tag as information type (a string). + * An empty information type, if the DICOM tag can not be extracted (i.e. the data node or + * the underlying base data is invalid or the DICOM tag does not exist for the given data node). + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::InformationType GetDICOMModalityFromDataNode(const mitk::DataNode* dataNode); + /* + * @brief Removes leading and trailing whitespace from the given string. + * + * @par identifier The value of a DICOM tag. + * + * @return The trimmed DICOM tag + */ + MITKSEMANTICRELATIONS_EXPORT std::string TrimDICOM(const std::string& identifier); +} // namespace mitk + +#endif // MITKDICOMHELPER_H diff --git a/Modules/SemanticRelations/include/mitkISemanticRelationsObservable.h b/Modules/SemanticRelations/include/mitkISemanticRelationsObservable.h new file mode 100644 index 0000000000..08e50b37ac --- /dev/null +++ b/Modules/SemanticRelations/include/mitkISemanticRelationsObservable.h @@ -0,0 +1,57 @@ +/*=================================================================== + +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 MITKISEMANTICRELATIONSOBSERVABLE_H +#define MITKISEMANTICRELATIONSOBSERVABLE_H + +#include "mitkISemanticRelationsObserver.h" + +#include "mitkSemanticTypes.h" + +namespace mitk +{ + /* + * @brief This interface declares three functions each observable subject has to implement + * in order to be observed in the 'Observer pattern' sense. + * The concrete observable class has to store its observer. + */ + class ISemanticRelationsObservable + { + public: + /* + * @brief Adds the given concrete observer to a container that holds all currently registered observer. + * + * @par observer The concrete observer to register. + */ + virtual void AddObserver(ISemanticRelationsObserver* observer) = 0; + /* + * @brief Removes the given concrete observer from the container that holds all currently registered observer. + * + * @par observer The concrete observer to unregister. + */ + virtual void RemoveObserver(ISemanticRelationsObserver* observer) = 0; + /* + * @brief Updates all concrete observer in the container that holds all currently registered observer. + * The caseID can be used to only update the observer, if the caseID fulfills a certain condition. + * + * @par caseID A caseID that identifies the currently active patient / case. + */ + virtual void NotifyObserver(const mitk::SemanticTypes::CaseID& caseID) const = 0; + + }; // class ISemanticRelationsObservable +} // namespace mitk + +#endif // MITKISEMANTICRELATIONSOBSERVABLE_H diff --git a/Modules/SemanticRelations/include/mitkISemanticRelationsObserver.h b/Modules/SemanticRelations/include/mitkISemanticRelationsObserver.h new file mode 100644 index 0000000000..3fd8651456 --- /dev/null +++ b/Modules/SemanticRelations/include/mitkISemanticRelationsObserver.h @@ -0,0 +1,43 @@ +/*=================================================================== + +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 MITKISEMANTICRELATIONSOBSERVER_H +#define MITKISEMANTICRELATIONSOBSERVER_H + +#include "mitkSemanticTypes.h" + +namespace mitk +{ + /* + * @brief This interface declares a functions each observer has to implement + * in order to be notified in the 'Observer pattern' sense. + */ + class ISemanticRelationsObserver + { + public: + /* + * @brief Updates the concrete observer. + * The caseID can be used to get access to a certain patient (case), + * whose data should be used for updating. + * + * @par caseID The current case ID to identify the currently active patient / case. + */ + virtual void Update(const mitk::SemanticTypes::CaseID& caseID) = 0; + + }; // class ISemanticRelationsObserver +} // namespace mitk + +#endif // MITKISEMANTICRELATIONSOBSERVER_H diff --git a/Modules/SemanticRelations/include/mitkLesionData.h b/Modules/SemanticRelations/include/mitkLesionData.h new file mode 100644 index 0000000000..c67ded1481 --- /dev/null +++ b/Modules/SemanticRelations/include/mitkLesionData.h @@ -0,0 +1,72 @@ +/*=================================================================== + +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 MITKLESIONDATA_H +#define MITKLESIONDATA_H + +#include + +// mitk semantic relations module +#include "mitkSemanticTypes.h" + +// c++ +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251) +#endif + +namespace mitk +{ + /** + * @brief This class holds the data of each lesion in the lesion tree view. + * + */ + class MITKSEMANTICRELATIONS_EXPORT LesionData + { + public: + /** + * @brief sets the data members to their initial values + */ + LesionData(const SemanticTypes::Lesion& lesion = SemanticTypes::Lesion()); + ~LesionData(); + + SemanticTypes::Lesion GetLesion() const { return m_Lesion; }; + SemanticTypes::ID GetLesionUID() const { return m_Lesion.UID; } + std::string GetLesionName() const { return m_Lesion.name; } + + const std::vector& GetLesionPresence() const { return m_LesionPresence; }; + const std::vector& GetLesionVolume() const { return m_LesionVolume; }; + + void SetLesion(const SemanticTypes::Lesion& lesion); + void SetLesionPresence(const std::vector& lesionPresence); + void SetLesionVolume(const std::vector& lesionVolume); + + private: + + SemanticTypes::Lesion m_Lesion; + std::vector m_LesionPresence; + std::vector m_LesionVolume; + + }; +} // end namespace + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // MITKLESIONDATA_H diff --git a/Modules/SemanticRelations/include/mitkLesionManager.h b/Modules/SemanticRelations/include/mitkLesionManager.h new file mode 100644 index 0000000000..f637e7eccd --- /dev/null +++ b/Modules/SemanticRelations/include/mitkLesionManager.h @@ -0,0 +1,80 @@ +/*=================================================================== + +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 MITKLESIONMANAGER_H +#define MITKLESIONMANAGER_H + +#include + +// semantic relations module +#include "mitkSemanticRelations.h" +#include "mitkSemanticTypes.h" +#include "mitkLesionData.h" + +// mitk core +#include + +/* +* @brief Provides helper functions that are needed to work with lesions. +* +* These functions help to generate new lesions, check for existing lesions or provide functionality +* to find existing lesion class types. +*/ +namespace mitk +{ + typedef std::vector LesionClassVector; + + /** + * @brief Generate a new lesion and lesion class with UIDs and the given string as lesion class type. + * + * @param lesionClassType The lesion class type as string. Default parameter is "". + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::Lesion GenerateNewLesion(const std::string& lesionClassType = ""); + /** + * @brief Generate a new lesion class with UID and the given string as lesion class type. + * + * @param lesionClassType The lesion class type as string. Default parameter is "". + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::LesionClass GenerateNewLesionClass(const std::string& lesionClassType = ""); + /** + * @brief Find and return a whole lesion including its lesion class given a specific lesion UID. + * + * @param lesionUID The lesion UID as string. + * @param allLesions All currently known lesions of a specific case. + * + * @return The lesion with its UID and the lesion class. + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::Lesion GetLesionByUID(const SemanticTypes::ID& lesionUID, const std::vector& allLesions); + /** + * @brief Find and return the whole lesion class including its UID given a specific lesion class type. + * + * @param lesionClassType The lesion class type as string. + * @param allLesionClasses All currently known lesion classes of a specific case. + * + * @return The lesion class with its UID and the class type. + */ + MITKSEMANTICRELATIONS_EXPORT SemanticTypes::LesionClass FindExistingLesionClass(const std::string& lesionClassType, const std::vector& allLesionClasses); + /** + * @brief Generate and store additional lesion data such as lesion presence and lesion volume for each control point. + * + * @param lesionData The lesion data that holds the lesion and will hold the additional lesion data. + * @param caseID The current case ID. + * @param semanticRelations An instance of the semantic relations to retrieve the additional data. + */ + MITKSEMANTICRELATIONS_EXPORT void GenerateAdditionalLesionData(LesionData& lesionData, const SemanticTypes::CaseID& caseID, std::shared_ptr semanticRelations); +} // namespace mitk + +#endif // MITKLESIONMANAGER_H diff --git a/Modules/SemanticRelations/include/mitkNodePredicates.h b/Modules/SemanticRelations/include/mitkNodePredicates.h new file mode 100644 index 0000000000..554653b21e --- /dev/null +++ b/Modules/SemanticRelations/include/mitkNodePredicates.h @@ -0,0 +1,47 @@ +/*=================================================================== + +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 MITKNODEPREDICATES_H +#define MITKNODEPREDICATES_H + +#include + +// mitk core +#include + +namespace mitk +{ + namespace NodePredicates + { + /* + * @brief Helper function to get a node predicate that can be used to filter images. + * + * The images are of type 'mitk::Image' but must not be 'helper objects' or 'segmentation nodes'. + * For the definition of 'segmentation nodes' see 'GetSegmentationPredicate'. + */ + MITKSEMANTICRELATIONS_EXPORT mitk::NodePredicateAnd::Pointer GetImagePredicate(); + /* + * @brief Helper function to get a node predicate that can be used to filter segmentations. + * + * The segmentations are of type 'mitk::LabelSetImage' or nodes that have their 'binary' property set to true. + * Segmentations must not be 'helper objects'. + */ + MITKSEMANTICRELATIONS_EXPORT mitk::NodePredicateAnd::Pointer GetSegmentationPredicate(); + + } // namespace NodePredicates +} // namespace mitk + +#endif // MITKNODEPREDICATES_H diff --git a/Modules/SemanticRelations/include/mitkRelationStorage.h b/Modules/SemanticRelations/include/mitkRelationStorage.h new file mode 100644 index 0000000000..73fd14a149 --- /dev/null +++ b/Modules/SemanticRelations/include/mitkRelationStorage.h @@ -0,0 +1,88 @@ +/*=================================================================== + +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 MITKRELATIONSTORAGE_H +#define MITKRELATIONSTORAGE_H + +// semantic relations module +#include "mitkDICOMHelper.h" +#include "mitkSemanticTypes.h" + +// mitk core +#include + +namespace mitk +{ + class RelationStorage + { + public: + + SemanticTypes::LesionVector GetAllLesionsOfCase(const SemanticTypes::CaseID& caseID); + SemanticTypes::Lesion GetRepresentedLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationID); + + std::vector GetAllSegmentationIDsOfCase(const SemanticTypes::CaseID& caseID); + + SemanticTypes::ControlPoint GetControlPointOfImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID); + SemanticTypes::ControlPointVector GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID); + + SemanticTypes::ExaminationPeriodVector GetAllExaminationPeriodsOfCase(const SemanticTypes::CaseID& caseID); + + SemanticTypes::InformationType GetInformationTypeOfImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID); + SemanticTypes::InformationTypeVector GetAllInformationTypesOfCase(const SemanticTypes::CaseID& caseID); + + std::vector GetAllImageIDsOfCase(const SemanticTypes::CaseID& caseID); + std::vector GetAllCaseIDs(); + + void AddCase(const SemanticTypes::CaseID& caseID); + void AddImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageNodeID); + void RemoveImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageNodeID); + void AddSegmentation(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationNodeID, const SemanticTypes::ID& parentDataNodeID); + void RemoveSegmentation(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationNodeID); + + void AddLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion); + void OverwriteLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion); + void LinkSegmentationToLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationID, const SemanticTypes::Lesion& lesion); + void UnlinkSegmentationFromLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationID); + void RemoveLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion); + void RemoveLesionClass(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& lesionClassID); + + void AddControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint); + void LinkDataToControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& dataNodeID, const SemanticTypes::ControlPoint& controlPoint); + void UnlinkDataFromControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& dataNodeID); + void RemoveControlPointFromCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint); + + void AddExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod& examinationPeriod); + void AddControlPointToExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriod examinationPeriod); + void RemoveControlPointFromExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriod examinationPeriod); + void RemoveExaminationPeriodFromCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod examinationPeriod); + + void AddInformationTypeToImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID, const SemanticTypes::InformationType informationType); + void RemoveInformationTypeFromImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID); + void RemoveInformationTypeFromCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType informationType); + + private: + + // access the storage and retrieve the case data, stored under the given case ID + PropertyList::Pointer GetStorageData(const SemanticTypes::CaseID& caseID); + + SemanticTypes::Lesion GenerateLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& lesionID); + + SemanticTypes::ControlPoint GenerateControlpoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& controlPointUID); + }; + +} // namespace mitk + +#endif // MITKRELATIONSTORAGE_H diff --git a/Modules/SemanticRelations/include/mitkSemanticRelationException.h b/Modules/SemanticRelations/include/mitkSemanticRelationException.h new file mode 100644 index 0000000000..4def6f9420 --- /dev/null +++ b/Modules/SemanticRelations/include/mitkSemanticRelationException.h @@ -0,0 +1,35 @@ +/*=================================================================== + +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 MITKSEMANTICRELATIONEXCEPTION_H +#define MITKSEMANTICRELATIONEXCEPTION_H + +// mitk core +#include + +namespace mitk +{ + class SemanticRelationException : public Exception + { + + public: + + mitkExceptionClassMacro(SemanticRelationException, Exception); + + }; +} // namespace mitk + +#endif // MITKSEMANTICRELATIONEXCEPTION_H diff --git a/Modules/SemanticRelations/include/mitkSemanticRelations.h b/Modules/SemanticRelations/include/mitkSemanticRelations.h new file mode 100644 index 0000000000..0c1bae27db --- /dev/null +++ b/Modules/SemanticRelations/include/mitkSemanticRelations.h @@ -0,0 +1,650 @@ +/*=================================================================== + +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 MITKSEMANTICRELATIONS_H +#define MITKSEMANTICRELATIONS_H + +#include + +// semantic relations module +#include "mitkDICOMHelper.h" +#include "mitkISemanticRelationsObservable.h" +#include "mitkISemanticRelationsObserver.h" +#include "mitkRelationStorage.h" +#include "mitkSemanticTypes.h" + +// mitk core +#include +#include +#include + +namespace mitk +{ + /** + * @brief The API provides functions to query and manipulate image relations and instances, + * that are helpful during follow-up examination, like control-points (time period), + * types of the images or lesions that may be visible on multiple images. + * + * The class is able to generate IDs from given data nodes using DICOM information. + * These IDs are used to identify the corresponding instances of a specific case. + * The case can also be directly identified by the given case ID. + * + * In the BlackSwan context the case is identified with the DICOM PatientID. + * + * In order for most functions to work the case ID has to be defined in the model. If not, + * the functions do nothing. + */ + class MITKSEMANTICRELATIONS_EXPORT SemanticRelations : public ISemanticRelationsObservable + { + public: + + SemanticRelations(DataStorage* dataStorage); + ~SemanticRelations(); + + using DataNodeVector = std::vector ; + + /************************************************************************/ + /* functions to implement the observer pattern */ + /************************************************************************/ + /** + * @brief Adds the given concrete observer to the vector that holds all currently registered observer. + * If the observer is already registered, it will not be added to the observer vector. + * + * @param observer The concrete observer to register. + */ + virtual void AddObserver(ISemanticRelationsObserver* observer) override; + /** + * @brief Removes the given concrete observer from the vector that holds all currently registered observer. + * + * @param observer The concrete observer to unregister. + */ + virtual void RemoveObserver(ISemanticRelationsObserver* observer) override; + + /************************************************************************/ + /* functions to get instances / attributes */ + /************************************************************************/ + /** + * @brief Returns a vector of all lesions that are currently available for the given case. + * The lesions may be marked by a segmentation or may be empty - with no connection to a specific image / segmentation of the case data. + * If no lesions are stored for the current case, an empty vector is returned. + * + * @param caseID The current case identifier is defined by the given string. + * @return A vector of lesions. + */ + SemanticTypes::LesionVector GetAllLesionsOfCase(const SemanticTypes::CaseID& caseID) const; + /** + * @brief + * + * + */ + SemanticTypes::LesionClassVector GetAllLesionClassesOfCase(const SemanticTypes::CaseID& caseID) const; + /** + * @brief Returns a vector of all lesions that are valid for the given case, given a specific control point. + * + * @param caseID The current case identifier is defined by the given string. + * @param controlPoint A specific control point which has to be available at a returned (found) lesion: + * Only those lesions are returned for which the image of the associated segmentation is linked to the given control point. + * If the control point instance does not exist, an empty vector is returned. + * @return A vector of control points. + */ + SemanticTypes::LesionVector GetAllLesionsOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const; + /** + * @brief Returns a vector of all lesions that are currently available for the current case and are connected to the given image (via a segmentation). + * If no lesions are stored for the current case, an empty vector is returned. If no segmentation nodes are + * connected with the image node, no lesions for the specific image will be found and an empty vector is returned. + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * @pre The image node has to have associated segmentation nodes (child nodes) in order to reference a lesion. + * + * @param imageNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + * @return A vector of lesions. + */ + SemanticTypes::LesionVector GetAllLesionsInImage(const DataNode* imageNode) const; + /** + * @brief Returns the lesion that is defined by the given segmentation data. + * + * @pre The given segmentation data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given segmentation data node is invalid (==nullptr). + * @pre The segmentation data node has to represent a lesion. If not, the retrieved lesion will be empty, which leads to an exception. + * @throw SemanticRelationException, if the segmentation does not represent an existing lesion (this can be checked via 'IsRepresentingALesion'). + * + * @param segmentationNode The segmentation identifier is extracted from the given data node. + * @return The represented lesion. + */ + SemanticTypes::Lesion GetRepresentedLesion(const DataNode* segmentationNode) const; + /** + * @brief Check if the given lesion is present on the given data node. + * For this the given data node is checked against the image / segmentation predicate. If the node is a + * segmentation, the represented lesion of the segmentation is simply compared against the member lesion. + * If the node is an image its derived segmentations are retrieved. For each derived segmentation + * the represented lesion is compared against the member lesion. + * + * @pre The given data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given data node is invalid (==nullptr). + * + * @param dataNode The data node identifier is extracted from the given data node. + * The data node or it's derivations are used for a function call to 'GetRepresentedLesion'. + * @return True, if the lesion is present on the given data node; false otherwise. + */ + bool IsLesionPresentOnDataNode(const SemanticTypes::Lesion& lesion, const mitk::DataNode* dataNode) const; + /** + * @brief Check if the given segmentation refers to an existing lesion instance. + * This function can be used before calling 'GetRepresentedLesion' in order to avoid a possible exception. + * + * @param segmentationNode The segmentation identifier is extracted from the given data node. + * @return True, if the segmentation refers to an existing lesion; false otherwise. + */ + bool IsRepresentingALesion(const DataNode* segmentationNode) const; + /** + * @brief Check if the given data node exists in the relation storage. + * The function receives the case- and the node-ID from the DICOM tags of the node itself. + * It uses node predicates to decide if the node is an image or a segmentation node and searches + * through the corresponding relations. + * + * @param dataNode A data node to check. + * @return True, if the data node exists; false otherwise. + */ + bool InstanceExists(const DataNode* dataNode) const; + /** + * @brief Return a vector of all segmentations that are currently available for the given case. + * The segmentations may be connected / not connected to a lesion of the case. + * If no segmentations are stored for the current case, an empty vector is returned. + * + * @pre The data storage member has to be valid (!nullptr). + * @throw SemanticRelationException, if the data storage member is invalid (==nullptr). + * + * @param caseID The current case identifier is defined by the given string. + * @return A vector of data nodes representing segmentations. + */ + mitk::SemanticRelations::DataNodeVector GetAllSegmentationsOfCase(const SemanticTypes::CaseID& caseID) const; + /** + * @brief Return a vector of all segmentations that define the given lesion. These segmentations don't have to be linked to the same image. + * If the lesion is not referred to by any segmentation, an empty vector is returned. + * + * @pre The data storage member has to be valid (!nullptr). + * @throw SemanticRelationException, if the data storage member is invalid (==nullptr). + * @pre The UID of the lesion has to exist for a lesion instance. + * @throw SemanticRelationException, if UID of the lesion does not exist for a lesion instance (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion A Lesion with a UID that identifies the corresponding lesion instance. + * @return A vector of data nodes representing segmentations that define the given lesion. + */ + DataNodeVector GetAllSegmentationsOfLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const; + /** + * @brief Return a vector of all images that are currently available for the given case. + * + * @pre The data storage member has to be valid (!nullptr). + * @throw SemanticRelationException, if the data storage member is invalid (==nullptr). + * + * @param caseID The current case identifier is defined by the given string. + * @return A vector of data nodes representing images. + */ + DataNodeVector GetAllImagesOfCase(const SemanticTypes::CaseID& caseID) const; + /** + * @brief Return a vector of all images that are connected to those segmentations that are linked to the given lesion. + * If the lesion is not referred to by any segmentation, an empty vector is returned. + * + * @pre The UID of the lesion has to exist for a lesion instance. + * @throw SemanticRelationException, if UID of the lesion does not exist for a lesion instance (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion A Lesion with a UID that identifies the corresponding lesion instance. + * @return A vector of data nodes representing images on which the lesions are visible. + */ + DataNodeVector GetAllImagesOfLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const; + /** + * @brief Check if the given lesion instance exists. + * This function can be used before calling 'GetAllSegmentationsOfLesion' in order to avoid a possible exception. + * This function can be used before calling 'AddLesionInstance' in order to avoid a possible exception. + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion A Lesion with a UID that identifies the corresponding lesion instance. + * @return True, if the lesion instance exists; false otherwise. + */ + bool InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const; + /** + * @brief Return a vector of all control points that are valid for the given case. + * + * @param caseID The current case identifier is defined by the given string. + * @return A vector of control points. + */ + SemanticTypes::ControlPointVector GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID) const; + /** + * @brief Return a vector of all control points that are valid for the given case, given a specific lesion + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion A specific lesion which has to be available at a returned (found) control point: + * Only those control points are returned for which an associated data has a segmentation that references the given lesion. + * If the lesion does not exists, an empty vector is returned. + * @return A vector of control points. + */ + SemanticTypes::ControlPointVector GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const; + /** + * @brief Return a vector of all control points that are valid for the given case, given a specific information type. + * + * @param caseID The current case identifier is defined by the given string. + * @param informationType A specific information type which has to be available at a returned (found) control point: + * Only those control points are returned for which an associated data has the given information type. + * If the information type instance does not exists, an empty vector is returned. + * @return A vector of control points. + */ + SemanticTypes::ControlPointVector GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType) const; + /** + * @brief Return the control point of a data node. + * If the data node is not linked to a control point or the data node refers to a non-existing control point, + * a control point with an empty UID is returned. + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * + * @param dataNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + * @return The control point of the given data node. + */ + SemanticTypes::ControlPoint GetControlPointOfData(const DataNode* dataNode) const; + /** + * @brief Return a vector of all image nodes that link to the given control point. + * If the control point is not referred to by any data node, an empty vector is returned. + * + * @pre The UID of the control point has to exist for a control point instance. + * @throw SemanticRelationException, if the UID of the control point does not exist for a control point instance (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param controlPoint A control point with a UID that identifies the corresponding control point instance. + * @return A vector of image nodes that link to the given control point. + */ + DataNodeVector GetAllImagesOfControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const; + /** + * @brief Check if the given control point instance exists. + * This function can be used before calling 'GetAllDataOfControlPoint' in order to avoid a possible exception. + * This function can be used before adding, linking and unlinking control points to avoid a possible exception. + * + * @param caseID The current case identifier is defined by the given string. + * @param controlPoint A control point with a UID that identifies the corresponding control point instance. + * @return True, if the control point instance exists; false otherwise. + */ + bool InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const; + /** + * @brief Return a vector of all examination periods nodes that are valid for the given case. + * + * @param caseID The current case identifier is defined by the given string. + * @return A vector of examination periods. + */ + SemanticTypes::ExaminationPeriodVector GetAllExaminationPeriodsOfCase(const SemanticTypes::CaseID& caseID) const; + /** + * @brief Check if the given examination period instance exists. + * This function can be used before calling 'AddExaminationPeriod' in order to avoid a possible exception. + * + * @param caseID The current case identifier is defined by the given string. + * @param examinationPeriod An examination period with a UID that identifies the corresponding examination period instance. + * @return True, if the examination period instance exists; false otherwise. + */ + bool InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod& examinationPeriod) const; + /** + * @brief Return a vector of all information types that are valid for the given case. + * + * @param caseID The current case identifier is defined by the given string. + * @return A vector of information types. + */ + SemanticTypes::InformationTypeVector GetAllInformationTypesOfCase(const SemanticTypes::CaseID& caseID) const; + /** + * @brief Return a vector of all information types that are valid for the given case, given a specific control point. + * + * @param caseID The current case identifier is defined by the given string. + * @param controlPoint A specific control point which has to be available at a returned (found) information type: + * Only those information types are returned for which an associated data is linked to the given control point. + * If the control point instance does not exist, an empty vector is returned. + * @return A vector of information types. + */ + SemanticTypes::InformationTypeVector GetAllInformationTypesOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const; + /** + * @brief Return the information type of the given image. + * If the image does not contain any information type, an empty information type is returned. + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * + * @param imageNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + * @return The information type of the given data node. + */ + SemanticTypes::InformationType GetInformationTypeOfImage(const DataNode* imageNode) const; + /** + * @brief Return a vector of all image nodes that are defined with the given information type. + * + * @pre The information type has to exist for the given case (and is therefore used by at least one data node). + * @throw SemanticRelationException, if the information type is not used by any data node (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param informationType An information type that identifies the corresponding information type instance. + * @return A vector of image nodes that are defined with the given information type. + */ + DataNodeVector GetAllImagesOfInformationType(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType) const; + /** + * @brief Return a vector of all image nodes that are defined with the given information type and with the given control point. + * + * @pre The UID of the control point has to exist for a control point instance. + * The information type has to exist for the given case (and is therefore used by at least one data node). + * @throw SemanticRelationException, if the UID of the control point does not exist for a control point instance (this can be checked via 'InstanceExists') or + * if the information type is not used by any data node (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param controlPoint A control point with a UID that identifies the corresponding control point instance. + * @param informationType An information type that identifies the corresponding information type instance. + * @return A vector of image nodes that are defined with the given information type with the given control point. + */ + DataNodeVector GetAllSpecificImages(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::InformationType& informationType) const; + /** + * @brief Return a vector of all segmentation nodes that are defined with the given information type and with the given control point. + * The function uses the 'GetAllSpecificImages'-function to retrieve the specific images and then searches for the derived nodes (segmentation child nodes). + * + * @pre The UID of the control point has to exist for a control point instance. + * The information type has to exist for the given case (and is therefore used by at least one data node). + * @throw SemanticRelationException, if the UID of the control point does not exist for a control point instance (this can be checked via 'InstanceExists') or + * if the information type is not used by any data node (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param controlPoint A control point with a UID that identifies the corresponding control point instance. + * @param informationType An information type that identifies the corresponding information type instance. + * @return A vector of segmentation nodes that are defined with the given information type with the given control point. + */ + DataNodeVector GetAllSpecificSegmentations(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::InformationType& informationType) const; + /** + * @brief Check if the given information type exists. + * This function can be used before calling 'GetAllDataOfInformationType' in order to avoid a possible exception. + * + * @param caseID The current case identifier is defined by the given string. + * @param informationType An information type + * @return True, if the information type exists; false otherwise. + */ + bool InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType) const; + /** + * @brief Return a vector of all CaseIDs that are currently available. + * + * @return A vector of CaseIDs as strings. + */ + std::vector GetAllCaseIDs() const; + + /************************************************************************/ + /* functions to add / remove instances / attributes */ + /************************************************************************/ + /** + * @brief Add the given image to the set of already existing images. + * The date is extracted from the DICOM data of the image node and is compared to already existing control points in the semantic relations model. + * The function tries to find a fitting control point or to extend an already existing control point, if the extracted control point is close to + * any other, already existing control point. + * Finally, the image is linked to the correct control point. + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * + * @param imageNode The current case identifier and node identifier is extracted from the given image data node, which contains DICOM information about the case and the node. + */ + void AddImage(const DataNode* imageNode); + /** + * @brief Remove the given image from the set of already existing images. + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * + * @param imageNode The current case identifier and node identifier is extracted from the given image data node, which contains DICOM information about the case and the node. + */ + void RemoveImage(const DataNode* imageNode); + /** + * @brief Add a newly created lesion to the set of already existing lesions - with no connection to a specific image / segmentation of the case data. + * + * @pre The UID of the lesion must not already exist for a lesion instance. + * @throw SemanticRelationException, it the UID of the lesion already exists for a lesion instance (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion The lesion instance to add. + */ + void AddLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion); + /** + * @brief Overwrite an already existing lesion instance (this may be useful to overwrite the lesion with a different lesion class). + * + * @pre The UID of the lesion has to exist for a lesion instance. + * @throw SemanticRelationException, if the UID of the lesion does not exist for a lesion instance (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion The lesion instance that overwrites an existing lesion. + */ + void OverwriteLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion); + /** + * @brief Add a newly created lesion to the set of already existing lesions. The lesion is added and a reference to + * the lesion is added to the segmentation data. If the segmentation is already linked to a lesion, the + * old linkage is overwritten (this can be checked via 'IsRepresentingALesion'). + * + * @pre The given segmentation data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given segmentation data node is invalid (==nullptr). + * @pre The UID of the lesion must not already exist for a lesion instance. + * @throw SemanticRelationException, if the UID of the lesion already exists for a lesion instance (this can be checked via 'InstanceExists'). + * + * @param segmentationNode The segmentation identifier is extracted from the given data node. The segmentation node has DICOM information from its parent node. + * @param lesion The lesion instance to add and link. + */ + void AddLesionAndLinkSegmentation(const DataNode* segmentationNode, const SemanticTypes::Lesion& lesion); + /** + * @brief Remove the given lesion from the set of already existing lesions. + * + * @pre The UID of the lesion has to exist for a lesion instance. + * @throw SemanticRelationException, if the UID of the lesion does not exist for a lesion instance (this can be checked via 'InstanceExists'). + * @pre The function needs to assure that no segmentation is still representing (linked to) this lesion. + * @throw SemanticRelationException, if the lesion instance to remove is still linked to by any segmentation (this can be checked via 'GetAllSegmentationsOfLesion'). + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion The lesion instance to remove. + */ + void RemoveLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion); + /** + * @brief Add a segmentation instance to the set of already existing segmentations - with no connection to a specific lesion. + * + * @param segmentationNode The segmentation identifier is extracted from the given data node. The segmentation node has DICOM information from its parent node. + * @param parentNode The node identifier of the parent node is extracted from the given parent data node. + */ + void AddSegmentation(const DataNode* segmentationNode, const DataNode* parentNode); + /** + * @brief Link the given segmentation instance to an an already existing lesion instance. If the segmentation is already linked to a lesion instance, the + * old linkage is overwritten (this can be checked via 'IsRepresentingALesion'). + * + * @pre The given segmentation data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given segmentation data node is invalid (==nullptr). + * @pre The UID of the lesion has to exist for a lesion instance. + * @throw SemanticRelationException, if the UID of the lesion does not exist for a lesion instance (this can be checked via 'InstanceExists'). + * + * @param segmentationNode The segmentation identifier is extracted from the given data node. The segmentation node has DICOM information from its parent node. + * @param lesion The lesion instance to link. + */ + void LinkSegmentationToLesion(const DataNode* segmentationNode, const SemanticTypes::Lesion& lesion); + /** + * @brief Unlink the given segmentation instance from the linked lesion instance. + * The lesion may stay unlinked to any segmentation. + * + * @pre The given segmentation data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given segmentation data node is invalid (==nullptr). + * + * @param segmentationNode The segmentation identifier is extracted from the given data node. The segmentation node has DICOM information from its parent node. + */ + void UnlinkSegmentationFromLesion(const DataNode* segmentationNode); + /** + * @brief Remove the given segmentation from the set of already existing segmentations. + * + * @pre The given segmentation data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given segmentation data node is invalid (==nullptr). + * + * @param segmentationNode The segmentation identifier is extracted from the given data node. The segmentation node has DICOM information from its parent node. + */ + void RemoveSegmentation(const DataNode* segmentationNode); + /** + * @brief Set the control point for the given data node. + * + * @pre The given data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given data node is invalid (==nullptr). + */ + void SetControlPointOfData(const DataNode* dataNode, const SemanticTypes::ControlPoint& controlPoint); + /** + * @brief Add a newly created control point to the set of already existing control points. A reference to the control point is added to the given data. + * This function combines adding a control point and linking it, since a control point with no associated data is not allowed. + * + * @pre The given data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given data node is invalid (==nullptr). + * @pre The UID of the control point must not already exist for a control point instance. + * @throw SemanticRelationException, if the UID of the control point already exists for a control point instance (this can be checked via 'InstanceExists'). + * @pre The given control point must not already be contained in an existing control point interval. + * @throw SemanticRelationException, if the given control point is already contained in an existing control point interval (this can be checked via 'CheckContainingControlPoint'). + * @pre The given control point must contain the date of the given data node (if parameter 'checkConsistence = true'). + * @throw SemanticRelationException, if the given control point does not contain the date of the given data node and 'checkConsistence = true' (this can be checked via 'ControlPointManager::InsideControlPoint'). + * + * @param dataNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + * @param controlPoint The control point instance to add. For a newly added control point always has "startDate = endDate". + * @param checkConsistence If true, the function checks, whether the date of the data node actually lies inside the control point to link. + */ + void AddControlPointAndLinkData(const DataNode* dataNode, const SemanticTypes::ControlPoint& controlPoint, bool checkConsistence = true); + /** + * @brief Link the given data to an already existing control point. + * + * @pre The given data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given data node is invalid (==nullptr). + * @pre The UID of the control point has to exist for a control point instance. + * @throw SemanticRelationException, if the UID of the control point does not exists for a control point instance (this can be checked via 'InstanceExists'). + * @pre The given control point must contain the date of the given data node (if parameter 'checkConsistence = true'). + * @throw SemanticRelationException, if the given control point does not contain the date of the given data node and 'checkConsistence = true' (this can be checked via 'ControlPointManager::InsideControlPoint'). + * + * @param dataNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + * @param controlPoint The control point instance to link. + * @param checkConsistence If true, the function checks, whether the date of the data node actually lies inside the control point to link. + */ + void LinkDataToControlPoint(const DataNode* dataNode, const SemanticTypes::ControlPoint& controlPoint, bool checkConsistence = true); + /** + * @brief Unlink the given image from the linked control point. + * If data is unlinked from a control point, the function needs to check whether the control point is still linked to any other data: + * - if not, the control point instance will be removed (has to be removed since a control point with no associated data is not allowed). + * - if so, the function has to make sure that the control point instance is shortened to its minimum time period (e.g. moving the end point to an earlier date). + * + * @param dataNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + */ + void UnlinkDataFromControlPoint(const DataNode* dataNode); + /** + * @brief Add an examination period instance to the set of already existing examination periods - with no connection to a specific control point. + * + * @pre The UID of the examination period must not already exist for an examination period instance. + * @throw SemanticRelationException, if the UID of the examination period already exists for a examination period instance (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param examinationPeriod The examination period to add. + */ + void AddExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod& examinationPeriod); + /** + * @brief Add a control point to the vector of control point UIDs of an existing examination period. + * + * @pre The UID of the control point has to exist for a control point instance. + * @throw SemanticRelationException, if the UID of the control point does not exists for a control point instance (this can be checked via 'InstanceExists'). + * @pre The UID of the examination period must not already exist for an examination period instance. + * @throw SemanticRelationException, if the UID of the examination period already exists for a examination period instance (this can be checked via 'InstanceExists'). + * + * @param caseID The current case identifier is defined by the given string. + * @param controlPoint The control point instance to add to the examination period. + * @param examinationPeriod The examination period to which the control point should be added. + */ + void AddControlPointToExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriod& examinationPeriod); + /** + * @brief Set (and possibly overwrite) the information type of the given image. + * An already associated information type might be removed if is not referenced by any other image: + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * @post If the information type instance did not exist before, it is now added. + * + * @param imageNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + * @param informationType An information type that identifies the corresponding information type instance. + */ + void SetInformationType(const DataNode* imageNode, const SemanticTypes::InformationType& informationType); + /** + * @brief Set the information type of the given image. + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * @post If the information type instance did not exist before, it is now added. + * + * @param imageNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + * @param informationType An information type that identifies the corresponding information type instance. + */ + void AddInformationTypeToImage(const DataNode* imageNode, const SemanticTypes::InformationType& informationType); + /** + * @brief Remove the information type of the given image. + * If the information type is removed, the function needs to check whether the information type is referenced by any other image: + * - if not, the information type instance can be removed (has to be removed since an information type with no associated image is not allowed). + * - if so, the information type is just removed from the given image. + * + * @pre The given image data node has to be valid (!nullptr). + * @throw SemanticRelationException, if the given image data node is invalid (==nullptr). + * + * @param imageNode The current case identifier is extracted from the given data node, which contains DICOM information about the case. + */ + void RemoveInformationTypeFromImage(const DataNode* imageNode); + + private: + + // the relation storage serves as a storage accessor and can be sub-classed for custom demands + std::shared_ptr m_RelationStorage; + WeakPointer m_DataStorage; + + /** + * @brief A vector that stores the currently registered observer of this observable subject. + */ + static std::vector m_ObserverVector; + /** + * @brief The SemanticRelations, as an example of an observable subject, notifies (updates) the observer with a given case ID. + * The view's caseID was set before in the GUI. The parts of the view that observe changes in the semantic relations are only updated, + * if the given case ID is equal to the observer's current caseID and thus the observer currently shows the semantic information of the given case. + * + * @param caseID The caseID that identifies the currently active patient / case. + */ + virtual void NotifyObserver(const mitk::SemanticTypes::CaseID& caseID) const override; + /** + * @brief Determine if the given control point contains images, which are connected to segmentations that represent the given lesion. + * If the lesion or the control point are not correctly stored, the function returns false. + * + * @param caseID The current case identifier is defined by the given string. + * @param lesion A Lesion with a UID that identifies the corresponding lesion instance. + * @param controlPoint A control point with a UID that identifies the corresponding control point instance. + * + * @return True, if the given control point contains data that is related to the given lesion; false otherwise. + */ + bool ControlPointContainsLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion, const SemanticTypes::ControlPoint& controlPoint) const; + /** + * @brief Determine if the given control point contains images, which refer to the given information type. + * If the information type or the control point are not correctly stored, the function returns false. + * + * @param caseID The current case identifier is defined by the given string. + * @param informationType An information type that identifies the corresponding information type instance. + * @param controlPoint A control point with a UID that identifies the corresponding control point instance. + * + * @return True, if the given control point contains data that is related to the given information type; false otherwise. + */ + bool ControlPointContainsInformationType(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType, const SemanticTypes::ControlPoint& controlPoint) const; + /** + * @brief Remove all control points from the storage that are not referenced by any image anymore. + * This might happen if an image has been removed (and unlinked from the corresponding control point) + * or if the user sets a new control point for an image manually in the GUI. + * + * @param caseID The current case identifier is defined by the given string. + */ + void ClearControlPoints(const SemanticTypes::CaseID& caseID); + }; +} // namespace mitk + +#endif // MITKSEMANTICRELATIONS_H diff --git a/Modules/SemanticRelations/include/mitkSemanticTypes.h b/Modules/SemanticRelations/include/mitkSemanticTypes.h new file mode 100644 index 0000000000..4cd7b61824 --- /dev/null +++ b/Modules/SemanticRelations/include/mitkSemanticTypes.h @@ -0,0 +1,128 @@ +/*=================================================================== + +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 MITKSEMANTICTYPES_H +#define MITKSEMANTICTYPES_H + +#define BOOST_DATE_TIME_NO_LIB +#if defined(BOOST_ALL_DYN_LINK) +#undef BOOST_ALL_DYN_LINK +#endif + +// boost +#include + +// c++ +#include +#include +#include + +namespace mitk +{ + namespace SemanticTypes + { + using ID = std::string; + using CaseID = std::string; // an ID of the current case (e.g. the DICOM PatientID) + using InformationType = std::string; + + /* + * @brief The concept of a control point. + */ + struct ControlPoint + { + ID UID; + boost::gregorian::date date; + + ControlPoint() + { + date = boost::gregorian::date(boost::gregorian::min_date_time); + } + + // less comparison to sort containers of control points + bool operator<(const ControlPoint& other) const + { + return date < other.date; + } + + std::string ToString() const + { + std::stringstream controlPointAsString; + if (date.is_not_a_date()) + { + return ""; + } + + controlPointAsString << std::to_string(date.year()) << "-" + << std::setfill('0') << std::setw(2) << std::to_string(date.month()) << "-" + << std::setfill('0') << std::setw(2) << std::to_string(date.day()); + + return controlPointAsString.str(); + } + + void SetDateFromString(const std::string& dateAsString) + { + date = boost::gregorian::from_undelimited_string(dateAsString); + } + + int DistanceInDays(const ControlPoint& other) const + { + boost::gregorian::date_duration duration = date - other.date; + return std::abs(duration.days()); + } + }; + + /** + * @brief The concept of an examination period. + * An examination period holds a vector of control point UIDs. + * The semantic relation storage stores the UIDs such that + * the represented control points are in chronological order. + */ + struct ExaminationPeriod + { + ID UID; + std::string name = ""; + std::vector controlPointUIDs; + }; + + /* + * @brief The concept of a lesion class. + */ + struct LesionClass + { + ID UID; + std::string classType = ""; + }; + + /* + * @brief The concept of a lesion. + */ + struct Lesion + { + ID UID; + std::string name = ""; + LesionClass lesionClass; + }; + + using LesionVector = std::vector; + using LesionClassVector = std::vector; + using ControlPointVector = std::vector; + using ExaminationPeriodVector = std::vector; + using InformationTypeVector = std::vector; + + } // namespace SemanticTypes +} // namespace mitk + +#endif // MITKSEMANTICTYPES_H diff --git a/Modules/SemanticRelations/include/mitkUIDGeneratorBoost.h b/Modules/SemanticRelations/include/mitkUIDGeneratorBoost.h new file mode 100644 index 0000000000..90347efcf3 --- /dev/null +++ b/Modules/SemanticRelations/include/mitkUIDGeneratorBoost.h @@ -0,0 +1,32 @@ +/*=================================================================== + +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 MITKUIDGENERATORBOOST_H +#define MITKUIDGENERATORBOOST_H + +#include + +#include + +namespace mitk +{ + namespace UIDGeneratorBoost + { + MITKSEMANTICRELATIONS_EXPORT std::string GenerateUID(); + } // namespace UIDGeneratorBoost +} // namespace mitk + +#endif // MITKUIDGENERATORBOOST_H diff --git a/Modules/SemanticRelations/src/mitkControlPointManager.cpp b/Modules/SemanticRelations/src/mitkControlPointManager.cpp new file mode 100644 index 0000000000..e08a3fe44a --- /dev/null +++ b/Modules/SemanticRelations/src/mitkControlPointManager.cpp @@ -0,0 +1,170 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations module +#include "mitkControlPointManager.h" +#include "mitkDICOMHelper.h" +#include "mitkUIDGeneratorBoost.h" + +// mitk core +#include + +mitk::SemanticTypes::ControlPoint mitk::GenerateControlPoint(const DataNode* datanode) +{ + SemanticTypes::ControlPoint controlPoint = GetDICOMDateFromDataNode(datanode); + controlPoint.UID = UIDGeneratorBoost::GenerateUID(); + + return controlPoint; +} + +mitk::SemanticTypes::ControlPoint mitk::GetControlPointByUID(const SemanticTypes::ID& controlPointUID, const std::vector& allControlPoints) +{ + auto lambda = [&controlPointUID](const SemanticTypes::ControlPoint& currentControlPoint) + { + return currentControlPoint.UID == controlPointUID; + }; + + const auto existingControlPoint = std::find_if(allControlPoints.begin(), allControlPoints.end(), lambda); + + mitk::SemanticTypes::ControlPoint controlPoint; + if (existingControlPoint != allControlPoints.end()) + { + controlPoint = *existingControlPoint; + } + + return controlPoint; +} + +mitk::SemanticTypes::ControlPoint mitk::FindExistingControlPoint(const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ControlPointVector& allControlPoints) +{ + for (const auto& currentControlPoint : allControlPoints) + { + if (controlPoint.date == currentControlPoint.date) + { + return currentControlPoint; + } + } + + return SemanticTypes::ControlPoint(); +} + +mitk::SemanticTypes::ControlPoint mitk::FindClosestControlPoint(const SemanticTypes::ControlPoint& controlPoint, SemanticTypes::ControlPointVector& allControlPoints) +{ + if (allControlPoints.empty()) + { + return SemanticTypes::ControlPoint(); + } + + // sort the vector of control points for easier lookup + std::sort(allControlPoints.begin(), allControlPoints.end()); + // new control point does not match an existing control point + // check if the control point is close to an already existing control point + std::vector::const_iterator it; + for (it = allControlPoints.begin(); it != allControlPoints.end(); ++it) + { + if (controlPoint.date < it->date) + { + break; + } + } + + SemanticTypes::ControlPoint nextControlPoint; + SemanticTypes::ControlPoint previousControlPoint; + if (it == allControlPoints.begin()) + { + // new date is smaller ("older") than the smallest already existing control point + nextControlPoint = *it; + } + else if (it != allControlPoints.end()) + { + // new date is greater ("newer") than an already existing control point, + // but smaller ("older") than another already existing control point + nextControlPoint = *it; + previousControlPoint = *(--it); + } + else + { + // new date is greater ("newer") than the greatest already existing control point + previousControlPoint = *(--it); + } + + // test distance to next and previous time period + double distanceToNextExaminationPeriod = nextControlPoint.DistanceInDays(controlPoint); + double distanceToPreviousExaminationPeriod = previousControlPoint.DistanceInDays(controlPoint); + + SemanticTypes::ControlPoint closestControlPoint; + int closestDistanceInDays = 0; + if (distanceToNextExaminationPeriod < distanceToPreviousExaminationPeriod) + { + // control point is closer to the next control point + closestControlPoint = nextControlPoint; + closestDistanceInDays = distanceToNextExaminationPeriod; + } + else + { + // control point is closer to the previous control point + closestControlPoint = previousControlPoint; + closestDistanceInDays = distanceToPreviousExaminationPeriod; + } + + int THRESHOLD_DISTANCE_IN_DAYS = 30; + if (closestDistanceInDays < THRESHOLD_DISTANCE_IN_DAYS) + { + return closestControlPoint; + } + + return SemanticTypes::ControlPoint(); +} + +mitk::SemanticTypes::ExaminationPeriod mitk::FindExaminationPeriod(const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriodVector& allExaminationPeriods) +{ + for (const auto& examinationPeriod : allExaminationPeriods) + { + for (const auto& UID : examinationPeriod.controlPointUIDs) + { + if (controlPoint.UID == UID) + { + return examinationPeriod; + } + } + } + + return SemanticTypes::ExaminationPeriod(); +} + +void mitk::SortExaminationPeriods(SemanticTypes::ExaminationPeriodVector& allExaminationPeriods, const SemanticTypes::ControlPointVector& allControlPoints) +{ + auto lambda = [allControlPoints](const SemanticTypes::ExaminationPeriod& leftExaminationPeriod, const SemanticTypes::ExaminationPeriod& rightExaminationPeriod) + { + if (leftExaminationPeriod.controlPointUIDs.empty()) + { + return true; + } + + if (rightExaminationPeriod.controlPointUIDs.empty()) + { + return false; + } + const auto leftUID = leftExaminationPeriod.controlPointUIDs.front(); + const auto rightUID = rightExaminationPeriod.controlPointUIDs.front(); + const auto& leftControlPoint = GetControlPointByUID(leftUID, allControlPoints); + const auto& rightControlPoint = GetControlPointByUID(rightUID, allControlPoints); + + return leftControlPoint.date < rightControlPoint.date; + }; + + std::sort(allExaminationPeriods.begin(), allExaminationPeriods.end(), lambda); +} diff --git a/Modules/SemanticRelations/src/mitkDICOMHelper.cpp b/Modules/SemanticRelations/src/mitkDICOMHelper.cpp new file mode 100644 index 0000000000..7dffde337f --- /dev/null +++ b/Modules/SemanticRelations/src/mitkDICOMHelper.cpp @@ -0,0 +1,161 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations module +#include "mitkDICOMHelper.h" +#include "mitkSemanticRelationException.h" +#include "mitkUIDGeneratorBoost.h" + +// mitk core +#include + +// c++ +#include + +mitk::SemanticTypes::ControlPoint GetControlPointFromString(const std::string& dateAsString); + +mitk::SemanticTypes::CaseID mitk::GetCaseIDFromDataNode(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + mitk::BaseData* baseData = dataNode->GetData(); + if (nullptr == baseData) + { + mitkThrowException(SemanticRelationException) << "No valid base data."; + } + + // extract suitable DICOM tag to use as the case id + // two alternatives can be used: + // - DICOM tag "0x0010, 0x0010" is PatientName + // - DICOM tag "0x0010, 0x0020" is PatientID + // in the current implementation the PatientID (0x0010, 0x0010) is used + mitk::BaseProperty* dicomTag = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0010).c_str()); + if (nullptr == dicomTag) + { + mitkThrowException(SemanticRelationException) << "Not a valid DICOM property."; + } + + std::string dicomTagAsString = dicomTag->GetValueAsString(); + return dicomTagAsString; +} + +mitk::SemanticTypes::ID mitk::GetIDFromDataNode(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + mitk::BaseData* baseData = dataNode->GetData(); + if (nullptr == baseData) + { + mitkThrowException(SemanticRelationException) << "No valid base data."; + } + + // extract suitable DICOM tag to use as the data node id + // DICOM tag "0x0020, 0x000e" is SeriesInstanceUID + mitk::BaseProperty* dicomTag = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0020, 0x000e).c_str()); + if (nullptr == dicomTag) + { + mitkThrowException(SemanticRelationException) << "Not a valid DICOM property."; + } + std::string dicomTagAsString = dicomTag->GetValueAsString(); + return dicomTagAsString; +} + +mitk::SemanticTypes::ControlPoint mitk::GetDICOMDateFromDataNode(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + mitk::BaseData* baseData = dataNode->GetData(); + if (nullptr == baseData) + { + mitkThrowException(SemanticRelationException) << "No valid base data."; + } + + // extract suitable DICOM tag to use as the data node id + // DICOM tag "0x0008, 0x0022" is AcquisitionDate + mitk::BaseProperty* acquisitionDateProperty = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0022).c_str()); + if (nullptr == acquisitionDateProperty) + { + mitkThrowException(SemanticRelationException) << "Not a valid DICOM property."; + } + std::string acquisitionDateAsString = acquisitionDateProperty->GetValueAsString(); + return GetControlPointFromString(acquisitionDateAsString); +} + +mitk::SemanticTypes::InformationType mitk::GetDICOMModalityFromDataNode(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + mitk::BaseData* baseData = dataNode->GetData(); + if (nullptr == baseData) + { + mitkThrowException(SemanticRelationException) << "No valid base data."; + } + + // extract suitable DICOM tag to use as the information type + // DICOM tag "0x0008, 0x0060" is Modality + mitk::BaseProperty* dicomTag = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0060).c_str()); + if (nullptr == dicomTag) + { + mitkThrowException(SemanticRelationException) << "Not a valid DICOM property."; + } + std::string dicomTagAsString = dicomTag->GetValueAsString(); + return dicomTagAsString; +} + +std::string mitk::TrimDICOM(const std::string& identifier) +{ + if (identifier.empty()) + { + return identifier; + } + + // leading whitespace + std::size_t first = identifier.find_first_not_of(' '); + if (std::string::npos == first) + { + return ""; + } + // trailing whitespace + std::size_t last = identifier.find_last_not_of(' '); + return identifier.substr(first, last - first + 1); +} + +mitk::SemanticTypes::ControlPoint GetControlPointFromString(const std::string& dateAsString) +{ + // date expected to be YYYYMMDD (8 characters) + if (dateAsString.size() != 8) + { + // string does not represent a DICOM date + return mitk::SemanticTypes::ControlPoint(); + } + + mitk::SemanticTypes::ControlPoint controlPoint; + controlPoint.SetDateFromString(dateAsString); + + return controlPoint; +} diff --git a/Modules/SemanticRelations/src/mitkLesionData.cpp b/Modules/SemanticRelations/src/mitkLesionData.cpp new file mode 100644 index 0000000000..172b364f65 --- /dev/null +++ b/Modules/SemanticRelations/src/mitkLesionData.cpp @@ -0,0 +1,43 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations module +#include "mitkLesionData.h" + +mitk::LesionData::LesionData(const SemanticTypes::Lesion& lesion/* = SemanticTypes::Lesion()*/) +{ + m_Lesion = lesion; +} + +mitk::LesionData::~LesionData() +{ + // nothing here +} + +void mitk::LesionData::SetLesion(const SemanticTypes::Lesion& lesion) +{ + m_Lesion = lesion; +} + +void mitk::LesionData::SetLesionPresence(const std::vector& lesionPresence) +{ + m_LesionPresence = lesionPresence; +} + +void mitk::LesionData::SetLesionVolume(const std::vector& lesionVolume) +{ + m_LesionVolume = lesionVolume; +} diff --git a/Modules/SemanticRelations/src/mitkLesionManager.cpp b/Modules/SemanticRelations/src/mitkLesionManager.cpp new file mode 100644 index 0000000000..7764bbe5ba --- /dev/null +++ b/Modules/SemanticRelations/src/mitkLesionManager.cpp @@ -0,0 +1,144 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations module +#include "mitkLesionManager.h" +#include "mitkSemanticRelationException.h" +#include "mitkUIDGeneratorBoost.h" + +bool GetLesionPresence(const mitk::SemanticTypes::CaseID& caseID, std::shared_ptr semanticRelations, const mitk::SemanticTypes::Lesion& lesion, const mitk::SemanticTypes::ControlPoint& controlPoint); + +double GetLesionVolume(const mitk::SemanticTypes::CaseID& caseID, std::shared_ptr semanticRelations, const mitk::SemanticTypes::Lesion& lesion, const mitk::SemanticTypes::ControlPoint& controlPoint); + +mitk::SemanticTypes::Lesion mitk::GenerateNewLesion(const std::string& lesionClassType/* = ""*/) +{ + mitk::SemanticTypes::Lesion lesion; + lesion.UID = mitk::UIDGeneratorBoost::GenerateUID(); + lesion.name = "New lesion"; + lesion.lesionClass = mitk::SemanticTypes::LesionClass(); + lesion.lesionClass.UID = mitk::UIDGeneratorBoost::GenerateUID(); + lesion.lesionClass.classType = lesionClassType; + + return lesion; +} + +mitk::SemanticTypes::LesionClass mitk::GenerateNewLesionClass(const std::string& lesionClassType/* = ""*/) +{ + mitk::SemanticTypes::LesionClass lesionClass; + lesionClass.UID = mitk::UIDGeneratorBoost::GenerateUID(); + lesionClass.classType = lesionClassType; + + return lesionClass; +} + +mitk::SemanticTypes::Lesion mitk::GetLesionByUID(const SemanticTypes::ID& lesionUID, const std::vector& allLesions) +{ + auto lambda = [&lesionUID](const SemanticTypes::Lesion& currentLesion) + { + return currentLesion.UID == lesionUID; + }; + + const auto existingLesion = std::find_if(allLesions.begin(), allLesions.end(), lambda); + + mitk::SemanticTypes::Lesion lesion; + if (existingLesion != allLesions.end()) + { + lesion = *existingLesion; + } + + return lesion; +} + +mitk::SemanticTypes::LesionClass mitk::FindExistingLesionClass(const std::string& lesionClassType, const std::vector& allLesionClasses) +{ + auto lambda = [&lesionClassType](const SemanticTypes::LesionClass& currentLesionClass) + { + return currentLesionClass.classType == lesionClassType; + }; + + const auto existingLesionClass = std::find_if(allLesionClasses.begin(), allLesionClasses.end(), lambda); + + mitk::SemanticTypes::LesionClass lesionClass; + if (existingLesionClass != allLesionClasses.end()) + { + lesionClass = *existingLesionClass; + } + + return lesionClass; +} + +void mitk::GenerateAdditionalLesionData(LesionData& lesionData, const SemanticTypes::CaseID& caseID, std::shared_ptr semanticRelations) +{ + std::vector lesionPresence; + std::vector lesionVolume; + SemanticTypes::Lesion lesion = lesionData.GetLesion(); + bool presence = false; + double volume = 0.0; + try + { + std::vector controlPoints = semanticRelations->GetAllControlPointsOfCase(caseID); + for (const auto& controlPoint : controlPoints) + { + presence = GetLesionPresence(caseID, semanticRelations, lesion, controlPoint); + lesionPresence.push_back(presence); + + volume = GetLesionVolume(caseID, semanticRelations, lesion, controlPoint); + lesionVolume.push_back(volume); + } + } + catch (const mitk::SemanticRelationException&) + { + return; + } + + lesionData.SetLesionPresence(lesionPresence); + lesionData.SetLesionVolume(lesionVolume); +} + +bool GetLesionPresence(const mitk::SemanticTypes::CaseID& caseID, std::shared_ptr semanticRelations, const mitk::SemanticTypes::Lesion& lesion, const mitk::SemanticTypes::ControlPoint& controllPoint) +{ + try + { + mitk::SemanticRelations::DataNodeVector allImagesOfLesion = semanticRelations->GetAllImagesOfLesion(caseID, lesion); + for (const auto& image : allImagesOfLesion) + { + auto imageControlPoint = semanticRelations->GetControlPointOfData(image); + if (imageControlPoint.date == controllPoint.date) + { + return true; + } + } + } + catch (const mitk::SemanticRelationException&) + { + return false; + } + + return false; +} + +double GetLesionVolume(const mitk::SemanticTypes::CaseID& caseID, std::shared_ptr semanticRelations, const mitk::SemanticTypes::Lesion& lesion, const mitk::SemanticTypes::ControlPoint& controlPoint) +{ + bool presence = GetLesionPresence(caseID, semanticRelations, lesion, controlPoint); + if (presence) + { + return 1.0; + } + else + { + return 0.0; + } +} diff --git a/Modules/SemanticRelations/src/mitkNodePredicates.cpp b/Modules/SemanticRelations/src/mitkNodePredicates.cpp new file mode 100644 index 0000000000..d0c4b52a1a --- /dev/null +++ b/Modules/SemanticRelations/src/mitkNodePredicates.cpp @@ -0,0 +1,55 @@ +/*=================================================================== + +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 "mitkNodePredicates.h" + +// mitk core +#include +#include +#include +#include + +// multi label module +#include + +mitk::NodePredicateAnd::Pointer mitk::NodePredicates::GetImagePredicate() +{ + TNodePredicateDataType::Pointer isImage = TNodePredicateDataType::New(); + + NodePredicateOr::Pointer validImages = NodePredicateOr::New(); + validImages->AddPredicate(isImage); + + NodePredicateAnd::Pointer imagePredicate = NodePredicateAnd::New(); + imagePredicate->AddPredicate(validImages); + imagePredicate->AddPredicate(NodePredicateNot::New(GetSegmentationPredicate())); + imagePredicate->AddPredicate(NodePredicateNot::New(NodePredicateProperty::New("helper object"))); + + return imagePredicate; +} + +mitk::NodePredicateAnd::Pointer mitk::NodePredicates::GetSegmentationPredicate() +{ + NodePredicateAnd::Pointer segmentationPredicate = NodePredicateAnd::New(); + + NodePredicateProperty::Pointer isBinary = NodePredicateProperty::New("binary", BoolProperty::New(true)); + TNodePredicateDataType::Pointer isLabelSetImage = TNodePredicateDataType::New(); + NodePredicateOr::Pointer allSegmentations = NodePredicateOr::New(isBinary, isLabelSetImage); + + segmentationPredicate->AddPredicate(allSegmentations); + segmentationPredicate->AddPredicate(NodePredicateNot::New(NodePredicateProperty::New("helper object"))); + + return segmentationPredicate; +} diff --git a/Modules/SemanticRelations/src/mitkRelationStorage.cpp b/Modules/SemanticRelations/src/mitkRelationStorage.cpp new file mode 100644 index 0000000000..fa0aced1d7 --- /dev/null +++ b/Modules/SemanticRelations/src/mitkRelationStorage.cpp @@ -0,0 +1,1290 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations module +#include "mitkRelationStorage.h" +#include "mitkNodePredicates.h" + +// multi label module +#include + +// mitk core +#include +#include + +// c++ +#include +#include + +std::vector mitk::RelationStorage::GetAllLesionsOfCase(const SemanticTypes::CaseID& caseID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return std::vector(); + } + // retrieve a vector property that contains the valid lesion-IDs for the current case + VectorProperty* vectorProperty = dynamic_cast*>(propertyList->GetProperty("lesions")); + if (nullptr == vectorProperty) + { + MITK_INFO << "Could not find any lesion in the storage."; + return std::vector(); + } + + std::vector vectorValue = vectorProperty->GetValue(); + std::vector allLesionsOfCase; + for (const auto& lesionID : vectorValue) + { + SemanticTypes::Lesion generatedLesion = GenerateLesion(caseID, lesionID); + if (!generatedLesion.UID.empty()) + { + allLesionsOfCase.push_back(generatedLesion); + } + } + + return allLesionsOfCase; +} + +mitk::SemanticTypes::Lesion mitk::RelationStorage::GetRepresentedLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return SemanticTypes::Lesion(); + } + + // retrieve a vector property that contains the referenced ID of a segmentation (0. image ID 1. lesion ID) + VectorProperty* segmentationVectorProperty = dynamic_cast*>(propertyList->GetProperty(segmentationID)); + if (nullptr == segmentationVectorProperty) + { + MITK_INFO << "Could not find the segmentation node " << segmentationID << " in the storage."; + return SemanticTypes::Lesion(); + } + + std::vector segmentationVectorValue = segmentationVectorProperty->GetValue(); + // the lesion ID of a segmentation is the second value in the vector + if (segmentationVectorValue.size() != 2) + { + MITK_INFO << "Incorrect segmentation storage. Not two (2) IDs stored."; + return SemanticTypes::Lesion(); + } + + std::string lesionID = segmentationVectorValue[1]; + if (lesionID.empty()) + { + // segmentation does not refer to any lesion; return empty lesion + return SemanticTypes::Lesion(); + } + + return GenerateLesion(caseID, lesionID); +} + +std::vector mitk::RelationStorage::GetAllSegmentationIDsOfCase(const SemanticTypes::CaseID& caseID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return std::vector(); + } + // retrieve a vector property that contains the valid segmentation-IDs for the current case + VectorProperty* allSegmentationsVectorProperty = dynamic_cast*>(propertyList->GetProperty("segmentations")); + if (nullptr == allSegmentationsVectorProperty) + { + MITK_INFO << "Could not find any segmentation in the storage."; + return std::vector(); + } + + return allSegmentationsVectorProperty->GetValue(); +} + +mitk::SemanticTypes::ControlPoint mitk::RelationStorage::GetControlPointOfImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return SemanticTypes::ControlPoint(); + } + // retrieve a vector property that contains the information type and the referenced ID of a control point (0. information type 1. control point ID) + VectorProperty* dataNodeVectorProperty = dynamic_cast*>(propertyList->GetProperty(imageID)); + if (nullptr == dataNodeVectorProperty) + { + MITK_INFO << "Could not find the data node " << imageID << " in the storage."; + return SemanticTypes::ControlPoint(); + } + + std::vector dataNodeVectorValue = dataNodeVectorProperty->GetValue(); + SemanticTypes::ControlPoint controlPoint; + // an image node has to have exactly two values (the information type and the ID of the control point) + if (dataNodeVectorValue.size() != 2) + { + MITK_INFO << "Incorrect data storage. Not two (2) values stored."; + return SemanticTypes::ControlPoint(); + } + + // the second value of the data node vector is the ID of the referenced control point + std::string controlPointID = dataNodeVectorValue[1]; + // retrieve a vector property that contains the integer values of the date of a control point (0. year 1. month 2. day) + VectorProperty* controlPointVectorProperty = dynamic_cast*>(propertyList->GetProperty(controlPointID)); + if (nullptr == controlPointVectorProperty) + { + MITK_INFO << "Could not find the control point " << controlPointID << " in the storage."; + return SemanticTypes::ControlPoint(); + } + + std::vector controlPointVectorValue = controlPointVectorProperty->GetValue(); + // a control point has to have exactly three integer values (year, month and day) + if (controlPointVectorValue.size() != 3) + { + MITK_INFO << "Incorrect control point storage. Not three (3) values of the date are stored."; + return SemanticTypes::ControlPoint(); + } + + // set the values of the control point + controlPoint.UID = controlPointID; + controlPoint.date = boost::gregorian::date(controlPointVectorValue[0], + controlPointVectorValue[1], + controlPointVectorValue[2]); + + return controlPoint; +} + +std::vector mitk::RelationStorage::GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return std::vector(); + } + + // retrieve a vector property that contains the valid control point-IDs for the current case + VectorProperty* vectorProperty = dynamic_cast*>(propertyList->GetProperty("controlpoints")); + if (nullptr == vectorProperty) + { + MITK_INFO << "Could not find any control points in the storage."; + return std::vector(); + } + + std::vector vectorValue = vectorProperty->GetValue(); + std::vector allControlPointsOfCase; + for (const auto& controlPointUID : vectorValue) + { + SemanticTypes::ControlPoint generatedControlPoint = GenerateControlpoint(caseID, controlPointUID); + if (!generatedControlPoint.UID.empty()) + { + allControlPointsOfCase.push_back(generatedControlPoint); + } + } + + return allControlPointsOfCase; +} + +std::vector mitk::RelationStorage::GetAllExaminationPeriodsOfCase(const SemanticTypes::CaseID& caseID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return std::vector(); + } + + // retrieve a vector property that contains the valid examination period UIDs for the current case + VectorProperty::Pointer vectorProperty = dynamic_cast*>(propertyList->GetProperty("examinationperiods")); + if (nullptr == vectorProperty) + { + MITK_INFO << "Could not find any examination periods in the storage."; + return std::vector(); + } + + std::vector vectorValue = vectorProperty->GetValue(); + std::vector allExaminationPeriods; + for (const auto& examinationPeriodID : vectorValue) + { + // retrieve a vector property that contains the represented control point-IDs + VectorProperty::Pointer examinationPeriodVectorProperty = dynamic_cast*>(propertyList->GetProperty(examinationPeriodID)); + if (nullptr == examinationPeriodVectorProperty) + { + MITK_INFO << "Could not find the examination period " << examinationPeriodID << " in the storage."; + continue; + } + + std::vector examinationPeriodVectorValue = examinationPeriodVectorProperty->GetValue(); + // an examination period has an arbitrary number of vector values (name and control point UIDs) (at least one for the name) + if (examinationPeriodVectorValue.empty()) + { + MITK_INFO << "Incorrect examination period storage. At least one (1) value for the examination period name has to be stored."; + continue; + } + else + { + // set the values of the name and the control points + SemanticTypes::ExaminationPeriod generatedExaminationPeriod; + generatedExaminationPeriod.UID = examinationPeriodID; + generatedExaminationPeriod.name = examinationPeriodVectorValue[0]; + for (int i = 1; i < examinationPeriodVectorValue.size(); ++i) + { + generatedExaminationPeriod.controlPointUIDs.push_back(examinationPeriodVectorValue[i]); + } + + allExaminationPeriods.push_back(generatedExaminationPeriod); + } + } + return allExaminationPeriods; +} + +mitk::SemanticTypes::InformationType mitk::RelationStorage::GetInformationTypeOfImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return SemanticTypes::InformationType(); + } + // retrieve a vector property that contains the information type and the referenced ID of an image data node (0. information type 1. control point ID) + VectorProperty* dataNodeVectorProperty = dynamic_cast*>(propertyList->GetProperty(imageID)); + if (nullptr == dataNodeVectorProperty) + { + MITK_INFO << "Could not find the image " << imageID << " in the storage."; + return SemanticTypes::InformationType(); + } + + std::vector dataNodeVectorValue = dataNodeVectorProperty->GetValue(); + // an image node has to have exactly two values (the information type and the ID of the control point) + if (dataNodeVectorValue.size() != 2) + { + MITK_INFO << "Incorrect data storage. Not two (2) values stored."; + return SemanticTypes::InformationType(); + } + + // the first value of the data node vector is the information type + return dataNodeVectorValue[0]; +} + +std::vector mitk::RelationStorage::GetAllInformationTypesOfCase(const SemanticTypes::CaseID& caseID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return std::vector(); + } + // retrieve a vector property that contains the valid information types of the current case + VectorProperty* informationTypeVectorProperty = dynamic_cast*>(propertyList->GetProperty("informationtypes")); + if (nullptr == informationTypeVectorProperty) + { + MITK_INFO << "Could not find any information types in the storage."; + return std::vector(); + } + + return informationTypeVectorProperty->GetValue(); +} + +std::vector mitk::RelationStorage::GetAllImageIDsOfCase(const SemanticTypes::CaseID& caseID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return std::vector(); + } + // retrieve a vector property that contains the valid image-IDs of the current case + VectorProperty* allImagesVectorProperty = dynamic_cast*>(propertyList->GetProperty("images")); + if (nullptr == allImagesVectorProperty) + { + MITK_INFO << "Could not find any image in the storage."; + return std::vector(); + } + + return allImagesVectorProperty->GetValue(); +} + +std::vector mitk::RelationStorage::GetAllCaseIDs() +{ + PERSISTENCE_GET_SERVICE_MACRO + if (nullptr == persistenceService) + { + MITK_INFO << "Persistence service could not be loaded"; + return std::vector(); + } + // the property list is valid for a certain scenario and contains all the case IDs of the radiological user's MITK session + std::string listIdentifier = "caseIDs"; + PropertyList::Pointer propertyList = persistenceService->GetPropertyList(listIdentifier); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << listIdentifier << " for the current MITK workbench / session."; + return std::vector(); + } + // retrieve a vector property that contains all case IDs + VectorProperty* caseIDsVectorProperty = dynamic_cast*>(propertyList->GetProperty(listIdentifier)); + if (nullptr == caseIDsVectorProperty) + { + MITK_INFO << "Could not find the property " << listIdentifier << " for the " << listIdentifier << " property list."; + return std::vector(); + } + + return caseIDsVectorProperty->GetValue(); +} + +void mitk::RelationStorage::AddCase(const SemanticTypes::CaseID& caseID) +{ + PERSISTENCE_GET_SERVICE_MACRO + if (nullptr == persistenceService) + { + MITK_INFO << "Persistence service could not be loaded"; + return; + } + // the property list is valid for a certain scenario and contains all the case IDs of the radiological user's MITK session + std::string listIdentifier = "caseIDs"; + PropertyList::Pointer propertyList = persistenceService->GetPropertyList(listIdentifier); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << listIdentifier << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains all case IDs + VectorProperty::Pointer caseIDsVectorProperty = dynamic_cast*>(propertyList->GetProperty(listIdentifier)); + std::vector caseIDsVectorValue; + if (nullptr == caseIDsVectorProperty) + { + caseIDsVectorProperty = VectorProperty::New(); + } + else + { + caseIDsVectorValue = caseIDsVectorProperty->GetValue(); + } + + auto existingCase = std::find(caseIDsVectorValue.begin(), caseIDsVectorValue.end(), caseID); + if (existingCase != caseIDsVectorValue.end()) + { + return; + } + + // add case to the "caseIDs" property list + caseIDsVectorValue.push_back(caseID); + caseIDsVectorProperty->SetValue(caseIDsVectorValue); + propertyList->SetProperty(listIdentifier, caseIDsVectorProperty); +} + +void mitk::RelationStorage::AddImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageNodeID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + + // retrieve a vector property that contains the valid image-IDs for the current case + VectorProperty::Pointer allImagesVectorProperty = dynamic_cast*>(propertyList->GetProperty("images")); + std::vector allImagesIDs; + if (nullptr == allImagesVectorProperty) + { + allImagesVectorProperty = VectorProperty::New(); + } + else + { + allImagesIDs = allImagesVectorProperty->GetValue(); + } + + auto existingImage = std::find(allImagesIDs.begin(), allImagesIDs.end(), imageNodeID); + if (existingImage != allImagesIDs.end()) + { + return; + } + + // add image to the "images" property list + allImagesIDs.push_back(imageNodeID); + allImagesVectorProperty->SetValue(allImagesIDs); + propertyList->SetProperty("images", allImagesVectorProperty); + + // add the image itself + VectorProperty::Pointer imageNodeVectorProperty = VectorProperty::New(); + // an image node has to have exactly two values (the information type and the ID of the control point) + std::vector imageNodeVectorValue(2); + imageNodeVectorProperty->SetValue(imageNodeVectorValue); + propertyList->SetProperty(imageNodeID, imageNodeVectorProperty); +} + +void mitk::RelationStorage::RemoveImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageNodeID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + + // retrieve a vector property that contains the valid image-IDs for the current case + VectorProperty::Pointer allImagesVectorProperty = dynamic_cast*>(propertyList->GetProperty("images")); + if (nullptr == allImagesVectorProperty) + { + MITK_INFO << "Could not find any images in the storage."; + return; + } + + // remove the image reference from the list of all images of the current case + std::vector allImagesIDs = allImagesVectorProperty->GetValue(); + allImagesIDs.erase(std::remove(allImagesIDs.begin(), allImagesIDs.end(), imageNodeID), allImagesIDs.end()); + if (allImagesIDs.empty()) + { + // no more images stored -> remove the images property list + propertyList->DeleteProperty("images"); + } + else + { + // or store the modified vector value + allImagesVectorProperty->SetValue(allImagesIDs); + } + + // remove the image instance itself + propertyList->DeleteProperty(imageNodeID); +} + +void mitk::RelationStorage::AddSegmentation(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationNodeID, const SemanticTypes::ID& parentNodeID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + + // retrieve a vector property that contains the valid segmentation-IDs for the current case + VectorProperty::Pointer allSegmentationsVectorProperty = dynamic_cast*>(propertyList->GetProperty("segmentations")); + std::vector allSegmentationsIDs; + if (nullptr == allSegmentationsVectorProperty) + { + allSegmentationsVectorProperty = VectorProperty::New(); + } + else + { + allSegmentationsIDs = allSegmentationsVectorProperty->GetValue(); + } + + auto existingImage = std::find(allSegmentationsIDs.begin(), allSegmentationsIDs.end(), segmentationNodeID); + if (existingImage != allSegmentationsIDs.end()) + { + return; + } + + // add segmentation to the "segmentations" property list + allSegmentationsIDs.push_back(segmentationNodeID); + allSegmentationsVectorProperty->SetValue(allSegmentationsIDs); + propertyList->SetProperty("segmentations", allSegmentationsVectorProperty); + + // add the segmentation itself + VectorProperty::Pointer segmentationNodeVectorProperty = VectorProperty::New(); + // a segmentation node has to have exactly two values (the ID of the referenced image and the ID of the referenced lesion) + std::vector segmentationNodeVectorValue(2); + segmentationNodeVectorValue[0] = parentNodeID; + segmentationNodeVectorProperty->SetValue(segmentationNodeVectorValue); + propertyList->SetProperty(segmentationNodeID, segmentationNodeVectorProperty); +} + +void mitk::RelationStorage::RemoveSegmentation(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationNodeID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + + // retrieve a vector property that contains the valid segmentation-IDs for the current case + VectorProperty::Pointer allSegmentationsVectorProperty = dynamic_cast*>(propertyList->GetProperty("segmentations")); + if (nullptr == allSegmentationsVectorProperty) + { + MITK_INFO << "Could not find any segmentation in the storage."; + return; + } + + // remove the lesion reference from the list of all lesions of the current case + std::vector allSegmentationsIDs = allSegmentationsVectorProperty->GetValue(); + allSegmentationsIDs.erase(std::remove(allSegmentationsIDs.begin(), allSegmentationsIDs.end(), segmentationNodeID), allSegmentationsIDs.end()); + if (allSegmentationsIDs.empty()) + { + // no more segmentations stored -> remove the segmentations property list + propertyList->DeleteProperty("segmentations"); + } + else + { + // or store the modified vector value + allSegmentationsVectorProperty->SetValue(allSegmentationsIDs); + } + + // remove the lesion instance itself + propertyList->DeleteProperty(segmentationNodeID); +} + +void mitk::RelationStorage::AddLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid lesion-IDs for the current case + VectorProperty::Pointer lesionsVectorProperty = dynamic_cast*>(propertyList->GetProperty("lesions")); + std::vector lesionsVectorValue; + if (nullptr == lesionsVectorProperty) + { + lesionsVectorProperty = VectorProperty::New(); + } + else + { + lesionsVectorValue = lesionsVectorProperty->GetValue(); + } + + const auto& existingIndex = std::find(lesionsVectorValue.begin(), lesionsVectorValue.end(), lesion.UID); + if (existingIndex != lesionsVectorValue.end()) + { + return; + } + + // add the new lesion id from the given lesion to the vector of all current lesion IDs + lesionsVectorValue.push_back(lesion.UID); + // overwrite the current vector property with the new, extended string vector + lesionsVectorProperty->SetValue(lesionsVectorValue); + propertyList->SetProperty("lesions", lesionsVectorProperty); + + // add the lesion with the lesion UID as the key and the lesion information as value + std::vector lesionData; + lesionData.push_back(lesion.name); + lesionData.push_back(lesion.lesionClass.UID); + VectorProperty::Pointer newLesionVectorProperty = VectorProperty::New(); + newLesionVectorProperty->SetValue(lesionData); + propertyList->SetProperty(lesion.UID, newLesionVectorProperty); + + // add the lesion class with the lesion class UID as key and the class type as value + std::string lesionClassType = lesion.lesionClass.classType; + propertyList->SetStringProperty(lesion.lesionClass.UID.c_str(), lesionClassType.c_str()); +} + +void mitk::RelationStorage::OverwriteLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid lesion-IDs for the current case + VectorProperty* lesionVectorProperty = dynamic_cast*>(propertyList->GetProperty("lesions")); + if (nullptr == lesionVectorProperty) + { + MITK_INFO << "Could not find any lesion in the storage."; + return; + } + + std::vector lesionVectorValue = lesionVectorProperty->GetValue(); + const auto existingLesion = std::find(lesionVectorValue.begin(), lesionVectorValue.end(), lesion.UID); + if (existingLesion != lesionVectorValue.end()) + { + // overwrite the referenced lesion class UID with the new, given lesion class data + std::vector lesionData; + lesionData.push_back(lesion.name); + lesionData.push_back(lesion.lesionClass.UID); + VectorProperty::Pointer newLesionVectorProperty = VectorProperty::New(); + newLesionVectorProperty->SetValue(lesionData); + propertyList->SetProperty(lesion.UID, newLesionVectorProperty); + + // overwrite the lesion class with the lesion class UID as key and the new, given class type as value + std::string lesionClassType = lesion.lesionClass.classType; + propertyList->SetStringProperty(lesion.lesionClass.UID.c_str(), lesionClassType.c_str()); + } + else + { + MITK_INFO << "Could not find lesion " << lesion.UID << " in the storage. Cannot overwrite the lesion."; + } +} + +void mitk::RelationStorage::LinkSegmentationToLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationID, const SemanticTypes::Lesion& lesion) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid lesion-IDs for the current case + VectorProperty* lesionVectorProperty = dynamic_cast*>(propertyList->GetProperty("lesions")); + if (nullptr == lesionVectorProperty) + { + MITK_INFO << "Could not find any lesion property in the storage."; + return; + } + + std::vector lesionVectorValue = lesionVectorProperty->GetValue(); + const auto existingLesion = std::find(lesionVectorValue.begin(), lesionVectorValue.end(), lesion.UID); + if (existingLesion != lesionVectorValue.end()) + { + // set / overwrite the lesion reference of the given segmentation + // retrieve a vector property that contains the referenced ID of a segmentation (0. image ID 1. lesion ID) + VectorProperty* segmentationVectorProperty = dynamic_cast*>(propertyList->GetProperty(segmentationID)); + if (nullptr == segmentationVectorProperty) + { + MITK_INFO << "Could not find the segmentation node " << segmentationID << " in the storage. Cannot link segmentation to lesion."; + return; + } + + std::vector segmentationVectorValue = segmentationVectorProperty->GetValue(); + if (segmentationVectorValue.size() != 2) + { + MITK_INFO << "Incorrect segmentation storage. Not two (2) IDs stored."; + return; + } + + // the lesion ID of a segmentation is the second value in the vector + segmentationVectorValue[1] = lesion.UID; + segmentationVectorProperty->SetValue(segmentationVectorValue); + return; + } + + MITK_INFO << "Could not find lesion " << lesion.UID << " in the storage. Cannot link segmentation to lesion."; +} + +void mitk::RelationStorage::UnlinkSegmentationFromLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& segmentationID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the referenced ID of a segmentation (0. image ID 1. lesion ID) + VectorProperty* segmentationVectorProperty = dynamic_cast*>(propertyList->GetProperty(segmentationID)); + if (nullptr == segmentationVectorProperty) + { + MITK_INFO << "Could not find the segmentation node " << segmentationID << " in the storage. Cannot unlink lesion from segmentation."; + return; + } + + std::vector segmentationVectorValue = segmentationVectorProperty->GetValue(); + // a segmentation has to have exactly two values (the ID of the linked image and the ID of the lesion) + if (segmentationVectorValue.size() != 2) + { + MITK_INFO << "Incorrect data storage. Not two (2) values stored."; + return; + } + + // the second value of the data node vector is the ID of the referenced lesion + // set the lesion reference to an empty string for removal + segmentationVectorValue[1] = ""; + segmentationVectorProperty->SetValue(segmentationVectorValue); +} + +void mitk::RelationStorage::RemoveLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid lesions of the current case + VectorProperty* lesionVectorProperty = dynamic_cast*>(propertyList->GetProperty("lesions")); + if (nullptr == lesionVectorProperty) + { + MITK_INFO << "Could not find any lesion property in the storage."; + return; + } + + // remove the lesion reference from the list of all lesions of the current case + std::vector lesionVectorValue = lesionVectorProperty->GetValue(); + lesionVectorValue.erase(std::remove(lesionVectorValue.begin(), lesionVectorValue.end(), lesion.UID), lesionVectorValue.end()); + if (lesionVectorValue.empty()) + { + // no more lesions stored -> remove the lesions property list + propertyList->DeleteProperty("lesions"); + } + else + { + // or store the modified vector value + lesionVectorProperty->SetValue(lesionVectorValue); + } + + // remove the lesion instance itself + // the lesion data is stored under the lesion ID + VectorProperty* lesionDataProperty = dynamic_cast*>(propertyList->GetProperty(lesion.UID)); + if (nullptr == lesionDataProperty) + { + MITK_INFO << "Lesion " << lesion.UID << " not found (already removed?). Cannot remove the lesion."; + return; + } + + std::vector lesionData = lesionDataProperty->GetValue(); + // a lesion date has to have exactly two values (the name of the lesion and the UID of the lesion class) + if (lesionData.size() != 2) + { + MITK_INFO << "Incorrect lesion data storage. Not two (2) strings of the lesion UID and the lesion name are stored."; + } + else + { + std::string lesionClassID = lesionData[1]; + RemoveLesionClass(caseID, lesionClassID); + } + propertyList->DeleteProperty(lesion.UID); +} + +void mitk::RelationStorage::RemoveLesionClass(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& lesionClassID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + + // retrieve a vector property that contains the lesion class + StringProperty* lesionClassProperty = dynamic_cast(propertyList->GetProperty(lesionClassID)); + if (nullptr == lesionClassProperty) + { + MITK_INFO << "Lesion class " << lesionClassID << " not found (already removed?). Cannot remove the lesion class."; + return; + } + + // retrieve a vector property that contains the valid lesions of the current case + VectorProperty* lesionVectorProperty = dynamic_cast*>(propertyList->GetProperty("lesions")); + if (nullptr == lesionVectorProperty) + { + return; + } + + // check if the lesion class ID is referenced by any other lesion + std::vector lesionVectorValue = lesionVectorProperty->GetValue(); + const auto existingLesionClass = std::find_if(lesionVectorValue.begin(), lesionVectorValue.end(), + [&propertyList, &lesionClassID](const std::string& lesionID) + { + VectorProperty* lesionDataProperty = dynamic_cast*>(propertyList->GetProperty(lesionID)); + if (nullptr == lesionDataProperty) + { + return false; + } + + std::vector lesionData = lesionDataProperty->GetValue(); + // a lesion date has to have exactly two values (the name of the lesion and the UID of the lesion class) + if (lesionData.size() != 2) + { + return false; + } + + return lesionData[1] == lesionClassID; + }); + + if (existingLesionClass == lesionVectorValue.end()) + { + // lesion class ID not referenced; remove lesion class + propertyList->DeleteProperty(lesionClassID); + } +} + +void mitk::RelationStorage::AddControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid controlPoint UIDs for the current case + VectorProperty::Pointer controlPointVectorProperty = dynamic_cast*>(propertyList->GetProperty("controlpoints")); + std::vector controlPointVectorValue; + if (nullptr == controlPointVectorProperty) + { + controlPointVectorProperty = VectorProperty::New(); + } + else + { + controlPointVectorValue = controlPointVectorProperty->GetValue(); + } + + const auto existingControlPoint = std::find(controlPointVectorValue.begin(), controlPointVectorValue.end(), controlPoint.UID); + if (existingControlPoint != controlPointVectorValue.end()) + { + return; + } + + // add the new control point UID from the given control point to the vector of all current control point UIDs + controlPointVectorValue.push_back(controlPoint.UID); + // overwrite the current vector property with the new, extended string vector + controlPointVectorProperty->SetValue(controlPointVectorValue); + propertyList->SetProperty("controlpoints", controlPointVectorProperty); + + // store the control point values (the three integer values of a date) + std::vector controlPointDate; + controlPointDate.push_back(controlPoint.date.year()); + controlPointDate.push_back(controlPoint.date.month()); + controlPointDate.push_back(controlPoint.date.day()); + + VectorProperty::Pointer newControlPointVectorProperty = VectorProperty::New(); + newControlPointVectorProperty->SetValue(controlPointDate); + propertyList->SetProperty(controlPoint.UID, newControlPointVectorProperty); +} + +void mitk::RelationStorage::LinkDataToControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& dataNodeID, const SemanticTypes::ControlPoint& controlPoint) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid controlPoint UIDs for the current case + VectorProperty* controlPointVectorProperty = dynamic_cast*>(propertyList->GetProperty("controlpoints")); + if (nullptr == controlPointVectorProperty) + { + MITK_INFO << "Could not find any control point property in the storage."; + return; + } + + std::vector controlPointVectorValue = controlPointVectorProperty->GetValue(); + const auto existingControlPoint = std::find(controlPointVectorValue.begin(), controlPointVectorValue.end(), controlPoint.UID); + if (existingControlPoint != controlPointVectorValue.end()) + { + // set / overwrite the control point reference of the given data + // retrieve a vector property that contains the referenced ID of a data node (0. information type 1. control point ID) + VectorProperty* dataNodeVectorProperty = dynamic_cast*>(propertyList->GetProperty(dataNodeID)); + if (nullptr == dataNodeVectorProperty) + { + MITK_INFO << "Could not find the data node " << dataNodeID << " in the storage. Cannot link data to control point."; + return; + } + + std::vector dataNodeVectorValue = dataNodeVectorProperty->GetValue(); + // an image node has to have exactly two values (the information type and the ID of the control point) + if (dataNodeVectorValue.size() != 2) + { + MITK_INFO << "Incorrect data storage. Not two (2) values stored."; + return; + } + + // the second value of the data node vector is the ID of the referenced control point + dataNodeVectorValue[1] = controlPoint.UID; + dataNodeVectorProperty->SetValue(dataNodeVectorValue); + return; + } + + MITK_INFO << "Could not find control point " << controlPoint.UID << " in the storage. Cannot link data to control point."; +} + +void mitk::RelationStorage::UnlinkDataFromControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& dataNodeID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the referenced ID of a date (0. information type 1. control point ID) + VectorProperty* dataNodeVectorProperty = dynamic_cast*>(propertyList->GetProperty(dataNodeID)); + if (nullptr == dataNodeVectorProperty) + { + MITK_INFO << "Could not find the date " << dataNodeID << " in the storage. Cannot unlink control point from date."; + return; + } + + std::vector dataNodeVectorValue = dataNodeVectorProperty->GetValue(); + // a data node has to have exactly two values (the information type and the ID of the control point) + if (dataNodeVectorValue.size() != 2) + { + MITK_INFO << "Incorrect data storage. Not two (2) values stored."; + return; + } + + // the second value of the data node vector is the ID of the referenced control point + // set the control point reference to an empty string for removal + dataNodeVectorValue[1] = ""; + dataNodeVectorProperty->SetValue(dataNodeVectorValue); +} + +void mitk::RelationStorage::RemoveControlPointFromCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid controlPoint UIDs for the current case + VectorProperty* allControlPointsVectorProperty = dynamic_cast*>(propertyList->GetProperty("controlpoints")); + if (nullptr == allControlPointsVectorProperty) + { + MITK_INFO << "Could not find any control point property in the storage."; + return; + } + + // remove the control point reference from the list of all control points of the current case + std::vector currentControlPointVectorValue = allControlPointsVectorProperty->GetValue(); + currentControlPointVectorValue.erase(std::remove(currentControlPointVectorValue.begin(), currentControlPointVectorValue.end(), controlPoint.UID), currentControlPointVectorValue.end()); + allControlPointsVectorProperty->SetValue(currentControlPointVectorValue); + + // remove the control point instance itself + propertyList->DeleteProperty(controlPoint.UID); +} + +void mitk::RelationStorage::AddExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod& examinationPeriod) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid examination period UIDs for the current case + VectorProperty::Pointer vectorProperty = dynamic_cast*>(propertyList->GetProperty("examinationperiods")); + std::vector examinationPeriodsVectorValue; + if (nullptr == vectorProperty) + { + vectorProperty = VectorProperty::New(); + } + else + { + examinationPeriodsVectorValue = vectorProperty->GetValue(); + } + + const auto& existingIndex = std::find(examinationPeriodsVectorValue.begin(), examinationPeriodsVectorValue.end(), examinationPeriod.UID); + if (existingIndex != examinationPeriodsVectorValue.end()) + { + return; + } + + // add the new examination period id from the given examination period to the vector of all current examination period UIDs + examinationPeriodsVectorValue.push_back(examinationPeriod.UID); + // overwrite the current vector property with the new, extended string vector + vectorProperty->SetValue(examinationPeriodsVectorValue); + propertyList->SetProperty("examinationperiods", vectorProperty); + + // add the examination period with the UID as the key and the name as as the vector value + std::vector examinationPeriodData; + examinationPeriodData.push_back(examinationPeriod.name); + VectorProperty::Pointer newExaminationPeriodVectorProperty = VectorProperty::New(); + newExaminationPeriodVectorProperty->SetValue(examinationPeriodData); + propertyList->SetProperty(examinationPeriod.UID, newExaminationPeriodVectorProperty); +} + +void mitk::RelationStorage::AddControlPointToExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriod examinationPeriod) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + + // retrieve a vector property that contains the represented control point UIDs of the given examination period + VectorProperty* controlPointUIDsVectorProperty = dynamic_cast*>(propertyList->GetProperty(examinationPeriod.UID)); + if (nullptr == controlPointUIDsVectorProperty) + { + MITK_INFO << "Could not find the examination period " << examinationPeriod.UID << " in the storage. Cannot add the control point to the examination period."; + return; + } + + std::vector controlPointUIDsVectorValue = controlPointUIDsVectorProperty->GetValue(); + // store the control point UID + controlPointUIDsVectorValue.push_back(controlPoint.UID); + // sort the vector according to the date of the control points referenced by the UIDs + auto lambda = [&caseID, this](const SemanticTypes::ID& leftControlPointUID, const SemanticTypes::ID& rightControlPointUID) + { + const auto& leftControlPoint = GenerateControlpoint(caseID, leftControlPointUID); + const auto& rightControlPoint = GenerateControlpoint(caseID, rightControlPointUID); + + return leftControlPoint.date <= rightControlPoint.date; + }; + + std::sort(controlPointUIDsVectorValue.begin(), controlPointUIDsVectorValue.end(), lambda); + // store the modified and sorted control point UID vector of this examination period + controlPointUIDsVectorProperty->SetValue(controlPointUIDsVectorValue); +} + +void mitk::RelationStorage::RemoveControlPointFromExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriod examinationPeriod) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + + // retrieve a vector property that contains the represented control point UIDs of the given examination period + VectorProperty* controlPointUIDsVectorProperty = dynamic_cast*>(propertyList->GetProperty(examinationPeriod.UID)); + if (nullptr == controlPointUIDsVectorProperty) + { + MITK_INFO << "Could not find examination period " << examinationPeriod.UID << " in the storage. Cannot add the control point to the examination period."; + return; + } + + std::vector controlPointUIDsVectorValue = controlPointUIDsVectorProperty->GetValue(); + // an examination period has an arbitrary number of vector values (name and control point UIDs) (at least one for the name) + if (controlPointUIDsVectorValue.size() < 2) + { + MITK_INFO << "Incorrect examination period storage. At least one (1) control point ID has to be stored."; + return; + } + else + { + controlPointUIDsVectorValue.erase(std::remove(controlPointUIDsVectorValue.begin(), controlPointUIDsVectorValue.end(), controlPoint.UID), controlPointUIDsVectorValue.end()); + if (controlPointUIDsVectorValue.size() < 2) + { + RemoveExaminationPeriodFromCase(caseID, examinationPeriod); + } + else + { + // store the modified vector value + controlPointUIDsVectorProperty->SetValue(controlPointUIDsVectorValue); + } + } +} + +void mitk::RelationStorage::RemoveExaminationPeriodFromCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod examinationPeriod) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid examination period UIDs for the current case + VectorProperty::Pointer vectorProperty = dynamic_cast*>(propertyList->GetProperty("examinationperiods")); + if (nullptr == vectorProperty) + { + MITK_INFO << "Could not find any examination periods in the storage."; + return; + } + + std::vector examinationPeriodVectorValue = vectorProperty->GetValue(); + examinationPeriodVectorValue.erase(std::remove(examinationPeriodVectorValue.begin(), examinationPeriodVectorValue.end(), examinationPeriod.UID), examinationPeriodVectorValue.end()); + if (examinationPeriodVectorValue.empty()) + { + // no more examination periods stored -> remove the examination period property list + propertyList->DeleteProperty("examinationperiods"); + } + else + { + // or store the modified vector value + vectorProperty->SetValue(examinationPeriodVectorValue); + } +} + +void mitk::RelationStorage::AddInformationTypeToImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID, const SemanticTypes::InformationType informationType) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid information types of the current case + VectorProperty::Pointer informationTypeVectorProperty = dynamic_cast*>(propertyList->GetProperty("informationtypes")); + std::vector informationTypeVectorValue; + if (nullptr == informationTypeVectorProperty) + { + informationTypeVectorProperty = VectorProperty::New(); + } + else + { + informationTypeVectorValue = informationTypeVectorProperty->GetValue(); + } + + const auto existingInformationType = std::find(informationTypeVectorValue.begin(), informationTypeVectorValue.end(), informationType); + if (existingInformationType == informationTypeVectorValue.end()) + { + // at first: add the information type to the storage + informationTypeVectorValue.push_back(informationType); + informationTypeVectorProperty->SetValue(informationTypeVectorValue); + propertyList->SetProperty("informationtypes", informationTypeVectorProperty); + } + + // set / overwrite the information type of the given data + // retrieve a vector property that contains the referenced ID of an image (0. information type 1. control point ID) + VectorProperty* imageNodeVectorProperty = dynamic_cast*>(propertyList->GetProperty(imageID)); + if (nullptr == imageNodeVectorProperty) + { + MITK_INFO << "Could not find the image " << imageID << " in the storage. Cannot add information type to image."; + return; + } + + std::vector imageNodeVectorValue = imageNodeVectorProperty->GetValue(); + // an image node has to have exactly two values (the information type and the ID of the control point) + if (imageNodeVectorValue.size() != 2) + { + MITK_INFO << "Incorrect data storage. Not two (2) values stored."; + return; + } + + // the first value of the data node vector is the information type + imageNodeVectorValue[0] = informationType; + imageNodeVectorProperty->SetValue(imageNodeVectorValue); +} + +void mitk::RelationStorage::RemoveInformationTypeFromImage(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& imageID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the referenced ID of an image (0. information type 1. control point ID) + VectorProperty* imageNodeVectorProperty = dynamic_cast*>(propertyList->GetProperty(imageID)); + if (nullptr == imageNodeVectorProperty) + { + MITK_INFO << "Could not find the image " << imageID << " in the storage. Cannot remove information type from image."; + return; + } + + std::vector imageNodeVectorValue = imageNodeVectorProperty->GetValue(); + // an image node has to have exactly two values (the information type and the ID of the control point) + if (imageNodeVectorValue.size() != 2) + { + MITK_INFO << "Incorrect data storage. Not two (2) values stored."; + return; + } + + // the first value of the data node vector is the information type + // set the information type to an empty string for removal + imageNodeVectorValue[0] = ""; + imageNodeVectorProperty->SetValue(imageNodeVectorValue); +} + +void mitk::RelationStorage::RemoveInformationTypeFromCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType informationType) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return; + } + // retrieve a vector property that contains the valid information types of the current case + VectorProperty* informationTypeVectorProperty = dynamic_cast*>(propertyList->GetProperty("informationtypes")); + if (nullptr == informationTypeVectorProperty) + { + MITK_INFO << "Could not find any information type property in the storage."; + return; + } + + std::vector informationTypeVectorValue = informationTypeVectorProperty->GetValue(); + informationTypeVectorValue.erase(std::remove(informationTypeVectorValue.begin(), informationTypeVectorValue.end(), informationType), informationTypeVectorValue.end()); + if (informationTypeVectorValue.empty()) + { + // no more information types stored -> remove the information types property list + propertyList->DeleteProperty("informationtypes"); + } + else + { + // or store the modified vector value + informationTypeVectorProperty->SetValue(informationTypeVectorValue); + } +} + +mitk::PropertyList::Pointer mitk::RelationStorage::GetStorageData(const SemanticTypes::CaseID& caseID) +{ + // access the storage + PERSISTENCE_GET_SERVICE_MACRO + if (nullptr == persistenceService) + { + MITK_INFO << "Persistence service could not be loaded"; + return nullptr; + } + + // the property list is valid for a whole case and contains all the properties for the current case + // the persistence service may create a new property list with the given ID, if no property list is found + return persistenceService->GetPropertyList(const_cast(caseID)); +} + +mitk::SemanticTypes::Lesion mitk::RelationStorage::GenerateLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& lesionID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return SemanticTypes::Lesion(); + } + + VectorProperty* lesionDataProperty = dynamic_cast*>(propertyList->GetProperty(lesionID)); + if (nullptr == lesionDataProperty) + { + MITK_INFO << "Lesion " << lesionID << " not found. Lesion can not be retrieved."; + return SemanticTypes::Lesion(); + } + + std::vector lesionData = lesionDataProperty->GetValue(); + // a lesion date has to have exactly two values (the name of the lesion and the UID of the lesion class) + if (lesionData.size() != 2) + { + MITK_INFO << "Incorrect lesion data storage. Not two (2) strings of the lesion name and the lesion UID are stored."; + return SemanticTypes::Lesion(); + } + + // the lesion class ID is stored as the second property + std::string lesionClassID = lesionData[1]; + StringProperty* lesionClassProperty = dynamic_cast(propertyList->GetProperty(lesionClassID)); + if (nullptr != lesionClassProperty) + { + SemanticTypes::LesionClass generatedLesionClass; + generatedLesionClass.UID = lesionClassID; + generatedLesionClass.classType = lesionClassProperty->GetValue(); + + SemanticTypes::Lesion generatedLesion; + generatedLesion.UID = lesionID; + generatedLesion.name = lesionData[0]; + generatedLesion.lesionClass = generatedLesionClass; + + return generatedLesion; + } + + MITK_INFO << "Incorrect lesion class storage. Lesion " << lesionID << " can not be retrieved."; + return SemanticTypes::Lesion(); +} + +mitk::SemanticTypes::ControlPoint mitk::RelationStorage::GenerateControlpoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ID& controlPointUID) +{ + PropertyList::Pointer propertyList = GetStorageData(caseID); + if (nullptr == propertyList) + { + MITK_INFO << "Could not find the property list " << caseID << " for the current MITK workbench / session."; + return SemanticTypes::ControlPoint(); + } + + // retrieve a vector property that contains the integer values of the date of a control point (0. year 1. month 2. day) + VectorProperty* controlPointVectorProperty = dynamic_cast*>(propertyList->GetProperty(controlPointUID)); + if (nullptr == controlPointVectorProperty) + { + MITK_INFO << "Could not find the control point " << controlPointUID << " in the storage."; + return SemanticTypes::ControlPoint(); + } + + std::vector controlPointVectorValue = controlPointVectorProperty->GetValue(); + // a control point has to have exactly three integer values (year, month and day) + if (controlPointVectorValue.size() != 3) + { + MITK_INFO << "Incorrect control point storage. Not three (3) values of the date are stored."; + return SemanticTypes::ControlPoint(); + } + + // set the values of the control point + SemanticTypes::ControlPoint generatedControlPoint; + generatedControlPoint.UID = controlPointUID; + generatedControlPoint.date = boost::gregorian::date(controlPointVectorValue[0], + controlPointVectorValue[1], + controlPointVectorValue[2]); + + return generatedControlPoint; +} diff --git a/Modules/SemanticRelations/src/mitkSemanticRelations.cpp b/Modules/SemanticRelations/src/mitkSemanticRelations.cpp new file mode 100644 index 0000000000..5086f110e9 --- /dev/null +++ b/Modules/SemanticRelations/src/mitkSemanticRelations.cpp @@ -0,0 +1,1058 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations module +#include "mitkControlPointManager.h" +#include "mitkNodePredicates.h" +#include "mitkSemanticRelations.h" +#include "mitkSemanticRelationException.h" +#include "mitkUIDGeneratorBoost.h" + +// multi label module +#include + +// c++ +#include +#include + +std::vector mitk::SemanticRelations::m_ObserverVector; + +mitk::SemanticRelations::SemanticRelations(DataStorage* dataStorage) + : m_DataStorage(dataStorage) +{ + m_RelationStorage = std::make_shared(); +} + +mitk::SemanticRelations::~SemanticRelations() +{ + // nothing here +} + +void mitk::SemanticRelations::AddObserver(ISemanticRelationsObserver* observer) +{ + std::vector::iterator existingObserver = std::find(m_ObserverVector.begin(), m_ObserverVector.end(), observer); + if (existingObserver != m_ObserverVector.end()) + { + // no need to add the already existing observer + return; + } + + m_ObserverVector.push_back(observer); +} + +void mitk::SemanticRelations::RemoveObserver(ISemanticRelationsObserver* observer) +{ + m_ObserverVector.erase(std::remove(m_ObserverVector.begin(), m_ObserverVector.end(), observer), m_ObserverVector.end()); +} + +/************************************************************************/ +/* functions to get instances / attributes */ +/************************************************************************/ + +mitk::SemanticTypes::LesionVector mitk::SemanticRelations::GetAllLesionsOfCase(const SemanticTypes::CaseID& caseID) const +{ + return m_RelationStorage->GetAllLesionsOfCase(caseID); +} + +mitk::SemanticTypes::LesionVector mitk::SemanticRelations::GetAllLesionsOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const +{ + SemanticTypes::LesionVector allLesions = GetAllLesionsOfCase(caseID); + + // filter the lesions: use only those, where the associated data is connected to image data that refers to the given control point using a lambda function + auto lambda = [&caseID, &controlPoint, this](const SemanticTypes::Lesion& lesion) { return !ControlPointContainsLesion(caseID, lesion, controlPoint); }; + allLesions.erase(std::remove_if(allLesions.begin(), allLesions.end(), lambda), allLesions.end()); + + return allLesions; +} + +mitk::SemanticTypes::LesionClassVector mitk::SemanticRelations::GetAllLesionClassesOfCase(const SemanticTypes::CaseID& caseID) const +{ + SemanticTypes::LesionVector allLesionsOfCase = GetAllLesionsOfCase(caseID); + SemanticTypes::LesionClassVector allLesionClassesOfCase; + + for (const auto& lesion : allLesionsOfCase) + { + allLesionClassesOfCase.push_back(lesion.lesionClass); + } + + // remove duplicate entries + auto lessThan = [](const SemanticTypes::LesionClass& lesionClassLeft, const SemanticTypes::LesionClass& lesionClassRight) + { + return lesionClassLeft.UID < lesionClassRight.UID; + }; + + auto equal = [](const SemanticTypes::LesionClass& lesionClassLeft, const SemanticTypes::LesionClass& lesionClassRight) + { + return lesionClassLeft.UID == lesionClassRight.UID; + }; + + std::sort(allLesionClassesOfCase.begin(), allLesionClassesOfCase.end(), lessThan); + allLesionClassesOfCase.erase(std::unique(allLesionClassesOfCase.begin(), allLesionClassesOfCase.end(), equal), allLesionClassesOfCase.end()); + + return allLesionClassesOfCase; +} + +mitk::SemanticTypes::LesionVector mitk::SemanticRelations::GetAllLesionsInImage(const DataNode* imageNode) const +{ + if (nullptr == imageNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid image data node."; + } + + if (m_DataStorage.IsExpired()) + { + mitkThrowException(SemanticRelationException) << "Not a valid data storage."; + } + + SemanticTypes::LesionVector allLesionsInImage; + // get child nodes of the current node with the segmentation predicate + DataStorage::SetOfObjects::ConstPointer segmentationNodes = m_DataStorage.Lock()->GetDerivations(imageNode, NodePredicates::GetSegmentationPredicate(), false); + for (auto it = segmentationNodes->Begin(); it != segmentationNodes->End(); ++it) + { + DataNode* segmentationNode = it->Value(); + try + { + SemanticTypes::Lesion representedLesion = GetRepresentedLesion(segmentationNode); + allLesionsInImage.push_back(representedLesion); + } + catch (const SemanticRelationException&) + { + continue; + } + } + return allLesionsInImage; +} + +mitk::SemanticTypes::Lesion mitk::SemanticRelations::GetRepresentedLesion(const DataNode* segmentationNode) const +{ + if (nullptr == segmentationNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid segmentation data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(segmentationNode); + SemanticTypes::ID segmentationID = GetIDFromDataNode(segmentationNode); + SemanticTypes::Lesion representedLesion = m_RelationStorage->GetRepresentedLesion(caseID, segmentationID); + + if (representedLesion.UID.empty()) + { + mitkThrowException(SemanticRelationException) << "Could not find a represented lesion instance for the given segmentation node " << segmentationNode->GetName(); + } + else + { + return representedLesion; + } +} + +bool mitk::SemanticRelations::IsLesionPresentOnDataNode(const SemanticTypes::Lesion& lesion, const mitk::DataNode* dataNode) const +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + if (m_DataStorage.IsExpired()) + { + mitkThrowException(SemanticRelationException) << "Not a valid data storage."; + } + + try + { + if (mitk::NodePredicates::GetImagePredicate()->CheckNode(dataNode)) + { + // get segmentations of the image node with the segmentation predicate + mitk::DataStorage::SetOfObjects::ConstPointer segmentations = m_DataStorage.Lock()->GetDerivations(dataNode, mitk::NodePredicates::GetSegmentationPredicate(), false); + for (auto it = segmentations->Begin(); it != segmentations->End(); ++it) + { + const auto representedLesion = GetRepresentedLesion(it.Value()); + return lesion.UID == representedLesion.UID; + } + } + else if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) + { + const auto representedLesion = GetRepresentedLesion(dataNode); + return lesion.UID == representedLesion.UID; + } + } + catch (const mitk::SemanticRelationException&) + { + return false; + } + + return false; +} + +bool mitk::SemanticRelations::IsRepresentingALesion(const DataNode* segmentationNode) const +{ + try + { + SemanticTypes::Lesion representedLesion = GetRepresentedLesion(segmentationNode); + return true; + } + catch (const Exception&) + { + return false; + } +} + +bool mitk::SemanticRelations::InstanceExists(const DataNode* dataNode) const +{ + try + { + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(dataNode); + SemanticTypes::ID dataNodeID = GetIDFromDataNode(dataNode); + + if (NodePredicates::GetImagePredicate()->CheckNode(dataNode)) + { + std::vector allImageIDsOfCase = m_RelationStorage->GetAllImageIDsOfCase(caseID); + return std::find(allImageIDsOfCase.begin(), allImageIDsOfCase.end(), dataNodeID) != allImageIDsOfCase.end(); + } + else if (NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) + { + std::vector allSegmentationIDsOfCase = m_RelationStorage->GetAllSegmentationIDsOfCase(caseID); + return std::find(allSegmentationIDsOfCase.begin(), allSegmentationIDsOfCase.end(), dataNodeID) != allSegmentationIDsOfCase.end(); + } + else + { + return false; + } + } + catch (const SemanticRelationException&) + { + return false; + } +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllSegmentationsOfCase(const SemanticTypes::CaseID& caseID) const +{ + if (m_DataStorage.IsExpired()) + { + mitkThrowException(SemanticRelationException) << "Not a valid data storage."; + } + + std::vector allSegmentationIDsOfCase = m_RelationStorage->GetAllSegmentationIDsOfCase(caseID); + std::vector allSegmentationsOfCase; + // get all segmentation nodes of the current data storage + // only those nodes are respected, that are currently held in the data storage + DataStorage::SetOfObjects::ConstPointer segmentationNodes = m_DataStorage.Lock()->GetSubset(NodePredicates::GetSegmentationPredicate()); + for (auto it = segmentationNodes->Begin(); it != segmentationNodes->End(); ++it) + { + DataNode* segmentationNode = it->Value(); + try + { + // find the corresponding segmentation node for the given segmentation ID + std::string nodeCaseID = GetCaseIDFromDataNode(segmentationNode); + std::string nodeSegmentationID = GetIDFromDataNode(segmentationNode); + if (nodeCaseID == caseID && (std::find(allSegmentationIDsOfCase.begin(), allSegmentationIDsOfCase.end(), nodeSegmentationID) != allSegmentationIDsOfCase.end())) + { + // found current image node in the storage, add it to the return vector + allSegmentationsOfCase.push_back(segmentationNode); + } + } + catch (const std::exception&) + { + // found a segmentation node that is not stored in the semantic relations + // this segmentation node does not have any DICOM information --> exception thrown + // continue with the next segmentation to compare IDs + continue; + } + } + + return allSegmentationsOfCase; +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllSegmentationsOfLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const +{ + if (InstanceExists(caseID, lesion)) + { + // lesion exists, retrieve all case segmentations from the storage + DataNodeVector allSegmentationsOfLesion = GetAllSegmentationsOfCase(caseID); + + // filter all segmentations: check for semantic relation with the given lesion using a lambda function + auto lambda = [&lesion, this](DataNode::Pointer segmentation) + { + try + { + SemanticTypes::Lesion representedLesion = GetRepresentedLesion(segmentation); + return lesion.UID != representedLesion.UID; + } + catch (const SemanticRelationException&) + { + return true; + } + }; + allSegmentationsOfLesion.erase(std::remove_if(allSegmentationsOfLesion.begin(), allSegmentationsOfLesion.end(), lambda), allSegmentationsOfLesion.end()); + + return allSegmentationsOfLesion; + } + else + { + mitkThrowException(SemanticRelationException) << "Could not find an existing lesion instance for the given caseID " << caseID << " and lesion " << lesion.UID << "."; + } +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllImagesOfCase(const SemanticTypes::CaseID& caseID) const +{ + if (m_DataStorage.IsExpired()) + { + mitkThrowException(SemanticRelationException) << "Not a valid data storage."; + } + + std::vector allImageIDsOfCase = m_RelationStorage->GetAllImageIDsOfCase(caseID); + std::vector allImagesOfCase; + // get all image nodes of the current data storage + // only those nodes are respected, that are currently held in the data storage + DataStorage::SetOfObjects::ConstPointer imageNodes = m_DataStorage.Lock()->GetSubset(NodePredicates::GetImagePredicate()); + for (auto it = imageNodes->Begin(); it != imageNodes->End(); ++it) + { + DataNode* imageNode = it->Value(); + // find the corresponding image node for the given segmentation ID + std::string nodeCaseID = GetCaseIDFromDataNode(imageNode); + std::string nodeImageID = GetIDFromDataNode(imageNode); + if (nodeCaseID == caseID && (std::find(allImageIDsOfCase.begin(), allImageIDsOfCase.end(), nodeImageID) != allImageIDsOfCase.end())) + { + // found current image node in the storage, add it to the return vector + allImagesOfCase.push_back(imageNode); + } + } + + return allImagesOfCase; +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllImagesOfLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const +{ + if (m_DataStorage.IsExpired()) + { + mitkThrowException(SemanticRelationException) << "Not a valid data storage."; + } + + DataNodeVector allImagesOfLesion; + // 1. get all segmentations that define the lesion + // 2. retrieve the parent node (source) of the found segmentation node + DataNodeVector allSegmentationsOfLesion = GetAllSegmentationsOfLesion(caseID, lesion); + for (const auto& segmentationNode : allSegmentationsOfLesion) + { + // get parent node of the current segmentation node with the node predicate + DataStorage::SetOfObjects::ConstPointer parentNodes = m_DataStorage.Lock()->GetSources(segmentationNode, NodePredicates::GetImagePredicate(), false); + for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) + { + DataNode::Pointer dataNode = it->Value(); + allImagesOfLesion.push_back(it->Value()); + } + } + + std::sort(allImagesOfLesion.begin(), allImagesOfLesion.end()); + allImagesOfLesion.erase(std::unique(allImagesOfLesion.begin(), allImagesOfLesion.end()), allImagesOfLesion.end()); + return allImagesOfLesion; +} + +bool mitk::SemanticRelations::InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const +{ + SemanticTypes::LesionVector allLesions = GetAllLesionsOfCase(caseID); + + // filter all lesions: check for equality with the given lesion using a lambda function + auto lambda = [&lesion](const SemanticTypes::Lesion& currentLesion) { return currentLesion.UID == lesion.UID; }; + const auto existingLesion = std::find_if(allLesions.begin(), allLesions.end(), lambda); + + if (existingLesion != allLesions.end()) + { + return true; + } + else + { + return false; + } +} + +mitk::SemanticTypes::ControlPointVector mitk::SemanticRelations::GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID) const +{ + return m_RelationStorage->GetAllControlPointsOfCase(caseID); +} + +mitk::SemanticTypes::ControlPointVector mitk::SemanticRelations::GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) const +{ + SemanticTypes::ControlPointVector allControlPoints = GetAllControlPointsOfCase(caseID); + + // filter the control points: use only those, where the associated image data has a segmentation that refers to the given lesion using a lambda function + auto lambda = [&caseID, &lesion, this](const SemanticTypes::ControlPoint& controlPoint) { return !ControlPointContainsLesion(caseID, lesion, controlPoint); }; + allControlPoints.erase(std::remove_if(allControlPoints.begin(), allControlPoints.end(), lambda), allControlPoints.end()); + + return allControlPoints; +} + +mitk::SemanticTypes::ControlPointVector mitk::SemanticRelations::GetAllControlPointsOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType) const +{ + SemanticTypes::ControlPointVector allControlPoints = GetAllControlPointsOfCase(caseID); + + // filter the control points: use only those, where the associated image data refers to the given information type using a lambda function + auto lambda = [&caseID, &informationType, this](const SemanticTypes::ControlPoint& controlPoint) { return !ControlPointContainsInformationType(caseID, informationType, controlPoint); }; + allControlPoints.erase(std::remove_if(allControlPoints.begin(), allControlPoints.end(), lambda), allControlPoints.end()); + + return allControlPoints; +} + +mitk::SemanticTypes::ControlPoint mitk::SemanticRelations::GetControlPointOfData(const DataNode* imageNode) const +{ + if (nullptr == imageNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(imageNode); + SemanticTypes::ID dataNodeID = GetIDFromDataNode(imageNode); + return m_RelationStorage->GetControlPointOfImage(caseID, dataNodeID); +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllImagesOfControlPoint(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const +{ + if (InstanceExists(caseID, controlPoint)) + { + // control point exists, retrieve all images from the storage + DataNodeVector allImagesOfControlPoint = GetAllImagesOfCase(caseID); + + // filter all images to remove the ones with a different control point using a lambda function + auto lambda = [&controlPoint, this](DataNode::Pointer imageNode) + { + return controlPoint.UID != GetControlPointOfData(imageNode).UID; + }; + + allImagesOfControlPoint.erase(std::remove_if(allImagesOfControlPoint.begin(), allImagesOfControlPoint.end(), lambda), allImagesOfControlPoint.end()); + + return allImagesOfControlPoint; + } + else + { + mitkThrowException(SemanticRelationException) << "Could not find an existing control point instance for the given caseID " << caseID << " and control point " << controlPoint.UID << "."; + } +} + +bool mitk::SemanticRelations::InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const +{ + SemanticTypes::ControlPointVector allControlPoints = GetAllControlPointsOfCase(caseID); + + // filter all control points: check for equality with the given control point using a lambda function + auto lambda = [&controlPoint](const SemanticTypes::ControlPoint& currentControlPoint) { return currentControlPoint.UID == controlPoint.UID; }; + const auto existingControlPoint = std::find_if(allControlPoints.begin(), allControlPoints.end(), lambda); + + if (existingControlPoint != allControlPoints.end()) + { + return true; + } + else + { + return false; + } +} + +mitk::SemanticTypes::ExaminationPeriodVector mitk::SemanticRelations::GetAllExaminationPeriodsOfCase(const SemanticTypes::CaseID& caseID) const +{ + return m_RelationStorage->GetAllExaminationPeriodsOfCase(caseID); +} + +bool mitk::SemanticRelations::InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod& examinationPeriod) const +{ + SemanticTypes::ExaminationPeriodVector allExaminationPeriods = GetAllExaminationPeriodsOfCase(caseID); + + // filter all examination periods: check for equality with the given examination period using a lambda function + auto lambda = [&examinationPeriod](const SemanticTypes::ExaminationPeriod& currentExaminationPeriod) { return currentExaminationPeriod.UID == examinationPeriod.UID; }; + const auto existingExaminationPeriod = std::find_if(allExaminationPeriods.begin(), allExaminationPeriods.end(), lambda); + + if (existingExaminationPeriod != allExaminationPeriods.end()) + { + return true; + } + else + { + return false; + } +} + +mitk::SemanticTypes::InformationTypeVector mitk::SemanticRelations::GetAllInformationTypesOfCase(const SemanticTypes::CaseID& caseID) const +{ + return m_RelationStorage->GetAllInformationTypesOfCase(caseID); +} + +mitk::SemanticTypes::InformationTypeVector mitk::SemanticRelations::GetAllInformationTypesOfCase(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint) const +{ + SemanticTypes::InformationTypeVector allInformationTypes = GetAllInformationTypesOfCase(caseID); + + // filter the information types: use only those, where the associated data refers to the given control point using a lambda function + auto lambda = [&caseID, &controlPoint, this](const SemanticTypes::InformationType& informationType) { return !ControlPointContainsInformationType(caseID, informationType, controlPoint); }; + allInformationTypes.erase(std::remove_if(allInformationTypes.begin(), allInformationTypes.end(), lambda), allInformationTypes.end()); + + return allInformationTypes; +} + +mitk::SemanticTypes::InformationType mitk::SemanticRelations::GetInformationTypeOfImage(const DataNode* imageNode) const +{ + if (nullptr == imageNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid image data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(imageNode); + SemanticTypes::ID imageID = GetIDFromDataNode(imageNode); + return m_RelationStorage->GetInformationTypeOfImage(caseID, imageID); +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllImagesOfInformationType(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType) const +{ + if (InstanceExists(caseID, informationType)) + { + // information type exists, retrieve all images from the storage + DataNodeVector allImagesOfInformationType = GetAllImagesOfCase(caseID); + + // filter all images to remove the ones with a different information type using a lambda function + auto lambda = [&informationType, this](DataNode::Pointer imageNode) + { + return informationType != GetInformationTypeOfImage(imageNode); + }; + + allImagesOfInformationType.erase(std::remove_if(allImagesOfInformationType.begin(), allImagesOfInformationType.end(), lambda), allImagesOfInformationType.end()); + + return allImagesOfInformationType; + } + else + { + mitkThrowException(SemanticRelationException) << "Could not find an existing information type for the given caseID " << caseID << " and information type " << informationType; + } +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllSpecificImages(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::InformationType& informationType) const +{ + if (InstanceExists(caseID, controlPoint)) + { + if (InstanceExists(caseID, informationType)) + { + // control point exists, information type exists, retrieve all images from the storage + DataNodeVector allImagesOfCase = GetAllImagesOfCase(caseID); + // filter all images to remove the ones with a different control point and information type using a lambda function + auto lambda = [&controlPoint, &informationType, this](DataNode::Pointer imageNode) + { + return (informationType != GetInformationTypeOfImage(imageNode)) || (controlPoint.date != GetControlPointOfData(imageNode).date); + }; + + allImagesOfCase.erase(std::remove_if(allImagesOfCase.begin(), allImagesOfCase.end(), lambda), allImagesOfCase.end()); + + return allImagesOfCase; + } + else + { + mitkThrowException(SemanticRelationException) << "Could not find an existing information type for the given caseID " << caseID << " and information type " << informationType; + } + } + else + { + mitkThrowException(SemanticRelationException) << "Could not find an existing control point for the given caseID " << caseID << " and control point " << controlPoint.UID; + } +} + +mitk::SemanticRelations::DataNodeVector mitk::SemanticRelations::GetAllSpecificSegmentations(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::InformationType& informationType) const +{ + if (m_DataStorage.IsExpired()) + { + mitkThrow() << "Not a valid data storage."; + } + + DataNodeVector allSpecificImages = GetAllSpecificImages(caseID, controlPoint, informationType); + DataNodeVector allSpecificSegmentations; + for (const auto& imageNode : allSpecificImages) + { + DataStorage::SetOfObjects::ConstPointer segmentationNodes = m_DataStorage.Lock()->GetDerivations(imageNode, NodePredicates::GetSegmentationPredicate(), false); + for (auto it = segmentationNodes->Begin(); it != segmentationNodes->End(); ++it) + { + allSpecificSegmentations.push_back(it->Value()); + } + } + + return allSpecificSegmentations; +} + +bool mitk::SemanticRelations::InstanceExists(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType) const +{ + SemanticTypes::InformationTypeVector allInformationTypes = GetAllInformationTypesOfCase(caseID); + + // filter all information types: check for equality with the given information type using a lambda function + auto lambda = [&informationType](const SemanticTypes::InformationType& currentInformationType) { return currentInformationType == informationType; }; + const auto existingInformationType = std::find_if(allInformationTypes.begin(), allInformationTypes.end(), lambda); + + if (existingInformationType != allInformationTypes.end()) + { + return true; + } + else + { + return false; + } +} + +std::vector mitk::SemanticRelations::GetAllCaseIDs() const +{ + return m_RelationStorage->GetAllCaseIDs(); +} + +/************************************************************************/ +/* functions to add / remove instances / attributes */ +/************************************************************************/ + +void mitk::SemanticRelations::AddImage(const DataNode* imageNode) +{ + if (nullptr == imageNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + // continue with a valid data node + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(imageNode); + SemanticTypes::ID nodeID = GetIDFromDataNode(imageNode); + + m_RelationStorage->AddCase(caseID); + m_RelationStorage->AddImage(caseID, nodeID); + + SemanticTypes::InformationType informationType = GetDICOMModalityFromDataNode(imageNode); + AddInformationTypeToImage(imageNode, informationType); + + // set the correct control point for this image + SemanticTypes::ControlPoint controlPoint = GenerateControlPoint(imageNode); + SetControlPointOfData(imageNode, controlPoint); +} + +void mitk::SemanticRelations::RemoveImage(const DataNode* imageNode) +{ + if (nullptr == imageNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + // continue with a valid data node + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(imageNode); + SemanticTypes::ID nodeID = GetIDFromDataNode(imageNode); + + RemoveInformationTypeFromImage(imageNode); + UnlinkDataFromControlPoint(imageNode); + m_RelationStorage->RemoveImage(caseID, nodeID); + NotifyObserver(caseID); +} + +void mitk::SemanticRelations::AddLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) +{ + if (InstanceExists(caseID, lesion)) + { + mitkThrowException(SemanticRelationException) << "The lesion " << lesion.UID << " to add already exists for the given case."; + } + else + { + m_RelationStorage->AddLesion(caseID, lesion); + NotifyObserver(caseID); + } +} + +void mitk::SemanticRelations::OverwriteLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) +{ + if (InstanceExists(caseID, lesion)) + { + m_RelationStorage->OverwriteLesion(caseID, lesion); + NotifyObserver(caseID); + } + else + { + mitkThrowException(SemanticRelationException) << "The lesion " << lesion.UID << " to overwrite does not exist for the given case."; + } +} + +void mitk::SemanticRelations::AddLesionAndLinkSegmentation(const DataNode* segmentationNode, const SemanticTypes::Lesion& lesion) +{ + if (nullptr == segmentationNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid segmentation data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(segmentationNode); + AddLesion(caseID, lesion); + LinkSegmentationToLesion(segmentationNode, lesion); + NotifyObserver(caseID); +} + +void mitk::SemanticRelations::RemoveLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion) +{ + if (InstanceExists(caseID, lesion)) + { + DataNodeVector allSegmentationsOfLesion = GetAllSegmentationsOfLesion(caseID, lesion); + if (allSegmentationsOfLesion.empty()) + { + // no more segmentations are linked to the specific lesion + // the lesion can be removed from the storage + m_RelationStorage->RemoveLesion(caseID, lesion); + NotifyObserver(caseID); + } + else + { + mitkThrowException(SemanticRelationException) << "The lesion " << lesion.UID << " to remove is still referred to by a segmentation node. Lesion will not be removed."; + } + } + else + { + mitkThrowException(SemanticRelationException) << "The lesion " << lesion.UID << " to remove does not exist for the given case."; + } +} + +void mitk::SemanticRelations::AddSegmentation(const DataNode* segmentationNode, const DataNode* parentNode) +{ + if (nullptr == segmentationNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid segmentation data node."; + } + + if (nullptr == parentNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid parent data node."; + } + + // continue with a valid data node + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(segmentationNode); + SemanticTypes::ID segmentationNodeID = GetIDFromDataNode(segmentationNode); + SemanticTypes::ID parentNodeID = GetIDFromDataNode(parentNode); + + m_RelationStorage->AddSegmentation(caseID, segmentationNodeID, parentNodeID); + NotifyObserver(caseID); +} + +void mitk::SemanticRelations::LinkSegmentationToLesion(const DataNode* segmentationNode, const SemanticTypes::Lesion& lesion) +{ + if (nullptr == segmentationNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid segmentation data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(segmentationNode); + if (InstanceExists(caseID, lesion)) + { + SemanticTypes::ID segmentationID = GetIDFromDataNode(segmentationNode); + m_RelationStorage->LinkSegmentationToLesion(caseID, segmentationID, lesion); + NotifyObserver(caseID); + } + else + { + mitkThrowException(SemanticRelationException) << "The lesion " << lesion.UID << " to link does not exist for the given case."; + } +} + +void mitk::SemanticRelations::UnlinkSegmentationFromLesion(const DataNode* segmentationNode) +{ + if (nullptr == segmentationNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid segmentation data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(segmentationNode); + SemanticTypes::ID segmentationID = GetIDFromDataNode(segmentationNode); + m_RelationStorage->UnlinkSegmentationFromLesion(caseID, segmentationID); + NotifyObserver(caseID); +} + +void mitk::SemanticRelations::RemoveSegmentation(const DataNode* segmentationNode) +{ + if (nullptr == segmentationNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid segmentation data node."; + } + + // continue with a valid data node + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(segmentationNode); + SemanticTypes::ID segmentationNodeID = GetIDFromDataNode(segmentationNode); + m_RelationStorage->RemoveSegmentation(caseID, segmentationNodeID); + NotifyObserver(caseID); +} + +void mitk::SemanticRelations::SetControlPointOfData(const DataNode* dataNode, const SemanticTypes::ControlPoint& controlPoint) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(dataNode); + SemanticTypes::ControlPointVector allControlPoints = GetAllControlPointsOfCase(caseID); + // need to check if an already existing control point fits/contains the user control point + SemanticTypes::ControlPoint existingControlPoint = FindExistingControlPoint(controlPoint, allControlPoints); + if (!existingControlPoint.UID.empty()) + { + try + { + // found an already existing control point + LinkDataToControlPoint(dataNode, existingControlPoint, false); + } + catch (const SemanticRelationException&) + { + mitkThrowException(SemanticRelationException) << "The data can not be linked. Inconsistency in the semantic relations storage assumed."; + } + } + else + { + try + { + AddControlPointAndLinkData(dataNode, controlPoint, false); + // added a new control point + // find closest control point to add the new control point to the correct examination period + SemanticTypes::ControlPoint closestControlPoint = FindClosestControlPoint(controlPoint, allControlPoints); + SemanticTypes::ExaminationPeriodVector allExaminationPeriods = GetAllExaminationPeriodsOfCase(caseID); + SemanticTypes::ExaminationPeriod examinationPeriod = FindExaminationPeriod(closestControlPoint, allExaminationPeriods); + if (examinationPeriod.UID.empty()) + { + // no closest control point (exceed threshold) or no examination period found + // create a new examination period for this control point and add it to the storage + examinationPeriod.UID = UIDGeneratorBoost::GenerateUID(); + examinationPeriod.name = "New examination period " + std::to_string(allExaminationPeriods.size()); + AddExaminationPeriod(caseID, examinationPeriod); + } + + // add the control point to the (newly created or found / close) examination period + AddControlPointToExaminationPeriod(caseID, controlPoint, examinationPeriod); + } + catch (const SemanticRelationException&) + { + mitkThrowException(SemanticRelationException) << "The data can not be linked. Inconsistency in the semantic relations storage assumed."; + } + } + + ClearControlPoints(caseID); + NotifyObserver(caseID); +} + +void mitk::SemanticRelations::AddControlPointAndLinkData(const DataNode* dataNode, const SemanticTypes::ControlPoint& controlPoint, bool checkConsistence) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(dataNode); + if (InstanceExists(caseID, controlPoint)) + { + mitkThrowException(SemanticRelationException) << "The control point " << controlPoint.UID << " to add already exists for the given case. \n Use 'LinkDataToControlPoint' instead."; + } + + m_RelationStorage->AddControlPoint(caseID, controlPoint); + LinkDataToControlPoint(dataNode, controlPoint, checkConsistence); +} + +void mitk::SemanticRelations::LinkDataToControlPoint(const DataNode* dataNode, const SemanticTypes::ControlPoint& controlPoint, bool checkConsistence) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(dataNode); + if (InstanceExists(caseID, controlPoint)) + { + SemanticTypes::ID dataID = GetIDFromDataNode(dataNode); + m_RelationStorage->LinkDataToControlPoint(caseID, dataID, controlPoint); + } + else + { + mitkThrowException(SemanticRelationException) << "The control point " << controlPoint.UID << " to link does not exist for the given case."; + } +} + +void mitk::SemanticRelations::UnlinkDataFromControlPoint(const DataNode* dataNode) +{ + if (nullptr == dataNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(dataNode); + SemanticTypes::ID dataID = GetIDFromDataNode(dataNode); + SemanticTypes::ControlPoint controlPoint = m_RelationStorage->GetControlPointOfImage(caseID, dataID); + m_RelationStorage->UnlinkDataFromControlPoint(caseID, dataID); + ClearControlPoints(caseID); +} + +void mitk::SemanticRelations::AddExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ExaminationPeriod& examinationPeriod) +{ + if (InstanceExists(caseID, examinationPeriod)) + { + mitkThrowException(SemanticRelationException) << "The examination period " << examinationPeriod.UID << " to add already exists for the given case."; + } + else + { + m_RelationStorage->AddExaminationPeriod(caseID, examinationPeriod); + } +} + +void mitk::SemanticRelations::AddControlPointToExaminationPeriod(const SemanticTypes::CaseID& caseID, const SemanticTypes::ControlPoint& controlPoint, const SemanticTypes::ExaminationPeriod& examinationPeriod) +{ + if (!InstanceExists(caseID, controlPoint)) + { + mitkThrowException(SemanticRelationException) << "The control point " << controlPoint.UID << " to add does not exist for the given case."; + } + + if (!InstanceExists(caseID, examinationPeriod)) + { + mitkThrowException(SemanticRelationException) << "The examination period " << examinationPeriod.UID << " does not exist for the given case. \n Use 'AddExaminationPeriod' before."; + } + + m_RelationStorage->AddControlPointToExaminationPeriod(caseID, controlPoint, examinationPeriod); +} + +void mitk::SemanticRelations::SetInformationType(const DataNode* imageNode, const SemanticTypes::InformationType& informationType) +{ + RemoveInformationTypeFromImage(imageNode); + AddInformationTypeToImage(imageNode, informationType); + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(imageNode); + NotifyObserver(caseID); +} + +void mitk::SemanticRelations::AddInformationTypeToImage(const DataNode* imageNode, const SemanticTypes::InformationType& informationType) +{ + if (nullptr == imageNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid image data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(imageNode); + SemanticTypes::ID imageID = GetIDFromDataNode(imageNode); + m_RelationStorage->AddInformationTypeToImage(caseID, imageID, informationType); +} + +void mitk::SemanticRelations::RemoveInformationTypeFromImage(const DataNode* imageNode) +{ + if (nullptr == imageNode) + { + mitkThrowException(SemanticRelationException) << "Not a valid image data node."; + } + + SemanticTypes::CaseID caseID = GetCaseIDFromDataNode(imageNode); + SemanticTypes::ID imageID = GetIDFromDataNode(imageNode); + SemanticTypes::InformationType originalInformationType = m_RelationStorage->GetInformationTypeOfImage(caseID, imageID); + m_RelationStorage->RemoveInformationTypeFromImage(caseID, imageID); + + // check for further references to the removed information type + std::vector allImageIDsVectorValue = m_RelationStorage->GetAllImageIDsOfCase(caseID); + for (const auto otherImageID : allImageIDsVectorValue) + { + SemanticTypes::InformationType otherInformationType = m_RelationStorage->GetInformationTypeOfImage(caseID, otherImageID); + if (otherInformationType == originalInformationType) + { + // found the information type in another image -> cannot remove the information type from the case + return; + } + } + + // given information type was not referred by any other image of the case -> the information type can be removed from the case + m_RelationStorage->RemoveInformationTypeFromCase(caseID, originalInformationType); +} + +/************************************************************************/ +/* private functions */ +/************************************************************************/ +void mitk::SemanticRelations::NotifyObserver(const SemanticTypes::CaseID& caseID) const +{ + for (auto& observer : m_ObserverVector) + { + observer->Update(caseID); + } +} + +bool mitk::SemanticRelations::ControlPointContainsLesion(const SemanticTypes::CaseID& caseID, const SemanticTypes::Lesion& lesion, const SemanticTypes::ControlPoint& controlPoint) const +{ + DataNodeVector allImagesOfLesion; + try + { + allImagesOfLesion = GetAllImagesOfLesion(caseID, lesion); + } + catch (const SemanticRelationException&) + { + // error retrieving image data; lesion has to be outside the control point + return false; + } + + DataNodeVector allImagesOfControlPoint; + try + { + allImagesOfControlPoint = GetAllImagesOfControlPoint(caseID, controlPoint); + } + catch (const SemanticRelationException&) + { + // error retrieving control point data; lesion has to be outside the control point + return false; + } + + std::sort(allImagesOfLesion.begin(), allImagesOfLesion.end()); + std::sort(allImagesOfControlPoint.begin(), allImagesOfControlPoint.end()); + DataNodeVector allImagesIntersection; + // set intersection removes duplicated nodes, since 'GetAllImagesOfControlPoint' only contains at most one of each node + std::set_intersection(allImagesOfLesion.begin(), allImagesOfLesion.end(), + allImagesOfControlPoint.begin(), allImagesOfControlPoint.end(), + std::back_inserter(allImagesIntersection)); + + // if the vector of intersecting data is empty, the control point does not contain the lesion + return !allImagesIntersection.empty(); +} + +bool mitk::SemanticRelations::ControlPointContainsInformationType(const SemanticTypes::CaseID& caseID, const SemanticTypes::InformationType& informationType, const SemanticTypes::ControlPoint& controlPoint) const +{ + DataNodeVector allImagesIntersection = GetAllSpecificImages(caseID, controlPoint, informationType); + return !allImagesIntersection.empty(); +} + +void mitk::SemanticRelations::ClearControlPoints(const SemanticTypes::CaseID& caseID) +{ + SemanticTypes::ControlPointVector allControlPointsOfCase = GetAllControlPointsOfCase(caseID); + + DataNodeVector allImagesOfCase; + try + { + allImagesOfCase = GetAllImagesOfCase(caseID); + } + catch (const SemanticRelationException&) + { + // error retrieving image data + return; + } + + SemanticTypes::ControlPointVector referencedControlPoints; + for (const auto& image : allImagesOfCase) + { + try + { + referencedControlPoints.push_back(GetControlPointOfData(image)); + } + catch (const SemanticRelationException&) + { + // error retrieving control point of data + continue; + } + } + + std::sort(allControlPointsOfCase.begin(), allControlPointsOfCase.end()); + std::sort(referencedControlPoints.begin(), referencedControlPoints.end()); + + SemanticTypes::ControlPointVector nonReferencedControlPoints; + std::set_difference(allControlPointsOfCase.begin(), allControlPointsOfCase.end(), + referencedControlPoints.begin(), referencedControlPoints.end(), + std::inserter(nonReferencedControlPoints, nonReferencedControlPoints.begin())); + + auto allExaminationPeriods = GetAllExaminationPeriodsOfCase(caseID); + for (const auto& controlPoint : nonReferencedControlPoints) + { + const auto& examinationPeriod = FindExaminationPeriod(controlPoint, allExaminationPeriods); + m_RelationStorage->RemoveControlPointFromExaminationPeriod(caseID, controlPoint, examinationPeriod); + m_RelationStorage->RemoveControlPointFromCase(caseID, controlPoint); + } +} diff --git a/Modules/SemanticRelations/src/mitkUIDGeneratorBoost.cpp b/Modules/SemanticRelations/src/mitkUIDGeneratorBoost.cpp new file mode 100644 index 0000000000..a7913deaa5 --- /dev/null +++ b/Modules/SemanticRelations/src/mitkUIDGeneratorBoost.cpp @@ -0,0 +1,28 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relation module +#include "mitkUIDGeneratorBoost.h" + +#include +#include +#include + +std::string mitk::UIDGeneratorBoost::GenerateUID() +{ + boost::uuids::uuid uuid = boost::uuids::random_generator()(); + return boost::uuids::to_string(uuid); +} diff --git a/Modules/SemanticRelationsUI/CMakeLists.txt b/Modules/SemanticRelationsUI/CMakeLists.txt new file mode 100644 index 0000000000..1a94ea6b53 --- /dev/null +++ b/Modules/SemanticRelationsUI/CMakeLists.txt @@ -0,0 +1,4 @@ +MITK_CREATE_MODULE( + DEPENDS MitkQtWidgets MitkSemanticRelations + PACKAGE_DEPENDS Qt5|Core +) diff --git a/Modules/SemanticRelationsUI/files.cmake b/Modules/SemanticRelationsUI/files.cmake new file mode 100644 index 0000000000..e9fc94045d --- /dev/null +++ b/Modules/SemanticRelationsUI/files.cmake @@ -0,0 +1,31 @@ +file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") + +set(CPP_FILES + mitkModuleActivator.cpp + QmitkAbstractSemanticRelationsStorageModel.cpp + QmitkControlPointDialog.cpp + QmitkLesionTextDialog.cpp + QmitkLesionTreeItem.cpp + QmitkLesionTreeModel.cpp + QmitkPatientTableHeaderView.cpp + QmitkPatientTableInspector.cpp + QmitkPatientTableModel.cpp + QmitkSemanticRelationsUIHelper.cpp + QmitkTableItemThumbnailDelegate.cpp +) + +set(MOC_H_FILES + include/QmitkAbstractSemanticRelationsStorageInspector.h + include/QmitkAbstractSemanticRelationsStorageModel.h + include/QmitkControlPointDialog.h + include/QmitkLesionTextDialog.h + include/QmitkLesionTreeModel.h + include/QmitkPatientTableHeaderView.h + include/QmitkPatientTableInspector.h + include/QmitkPatientTableModel.h + include/QmitkTableItemThumbnailDelegate.h +) + +set(UI_FILES + src/QmitkPatientTableInspector.ui +) diff --git a/Modules/SemanticRelationsUI/include/QmitkAbstractSemanticRelationsStorageInspector.h b/Modules/SemanticRelationsUI/include/QmitkAbstractSemanticRelationsStorageInspector.h new file mode 100644 index 0000000000..15a06ca2ea --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkAbstractSemanticRelationsStorageInspector.h @@ -0,0 +1,55 @@ +/*=================================================================== + +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 QMITKABSTRACTSEMANTICRELATIONSSTORAGEINSPECTOR_H +#define QMITKABSTRACTSEMANTICRELATIONSSTORAGEINSPECTOR_H + +// semantic relations UI module +#include "MitkSemanticRelationsUIExports.h" + +// semantic relations module +#include "mitkSemanticTypes.h" + +// qt widgets module +#include "QmitkAbstractDataStorageInspector.h" + +/* +* @brief The QmitkAbstractSemanticRelationsStorageInspector is a QmitkAbstractDataStorageInspector that can be used to +* show the currently available data of an (abstract) semantic relations storage model. +*/ +class MITKSEMANTICRELATIONSUI_EXPORT QmitkAbstractSemanticRelationsStorageInspector : public QmitkAbstractDataStorageInspector +{ + Q_OBJECT + +public: + /** + * @brief Extends the abstract base class to allow setting the current case ID which is needed to access the + * semantic relations storage. The function sets the case ID in the storage model. + * + * @param caseID A case ID as string + */ + virtual void SetCaseID(const mitk::SemanticTypes::CaseID& caseID) = 0; + /** + * @brief Extends the abstract base class to allow setting the current lesion. + * The function sets the lesion in the storage model. + * + *@param lesion The selected lesion + */ + virtual void SetLesion(const mitk::SemanticTypes::Lesion& lesion) = 0; + +}; + +#endif // QMITKABSTRACTSEMANTICRELATIONSSTORAGEINSPECTOR_H diff --git a/Modules/SemanticRelationsUI/include/QmitkAbstractSemanticRelationsStorageModel.h b/Modules/SemanticRelationsUI/include/QmitkAbstractSemanticRelationsStorageModel.h new file mode 100644 index 0000000000..2c1abaf1ae --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkAbstractSemanticRelationsStorageModel.h @@ -0,0 +1,116 @@ +/*=================================================================== + +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 QMITKABSTRACTSEMANTICRELATIONSSTORAGEMODEL_H +#define QMITKABSTRACTSEMANTICRELATIONSSTORAGEMODEL_H + +// mitk semantic relations UI +#include "MitkSemanticRelationsUIExports.h" + +// semantic relations module +#include +#include +#include + +// qt widgets module +#include "QmitkAbstractDataStorageModel.h" + +/* +* @brief The QmitkAbstractSemanticRelationsStorageModel is a subclass of 'QmitkAbstractDataStorageModel' and provides additional +* functionality to set and store a semantic relations instance, the current case ID and the current lesion. +*/ +class MITKSEMANTICRELATIONSUI_EXPORT QmitkAbstractSemanticRelationsStorageModel : public QmitkAbstractDataStorageModel, public mitk::ISemanticRelationsObserver +{ + Q_OBJECT + +public: + + QmitkAbstractSemanticRelationsStorageModel(QObject* parent = nullptr); + virtual ~QmitkAbstractSemanticRelationsStorageModel(); + + /* + * @brief Updates this model with the data from the semantic relations. + * + * Overridden from 'ISemanticRelationsObserver'. + * In order for the Update-function to be called, this model has to be added as a observer of SemanticRelation + * (e.g. m_SemanticRelations->AddObserver(m_SemanticRelationsStorageModel);) + * + * @par caseID The current case ID to identify the currently active patient / case. + */ + virtual void Update(const mitk::SemanticTypes::CaseID& caseID) override; + + std::shared_ptr GetSemanticRelations() const { return m_SemanticRelations; } + /** + * @brief Sets the current case ID which is needed to access the semantic relations storage. + * + * @param caseID A case ID as string + */ + void SetCaseID(const mitk::SemanticTypes::CaseID& caseID); + + const mitk::SemanticTypes::CaseID& GetCaseID() const { return m_CaseID; } + /** + * @brief Sets the current lesion which can be used to show on which images the lesion is visible. + * + * @param lesion The selected lesion + */ + void SetLesion(const mitk::SemanticTypes::Lesion& lesion); + + const mitk::SemanticTypes::Lesion& GetLesion() const { return m_Lesion; } + /** + * @brief Sets the current data node selection which can be used to show which lesions + * are visible on the node selection. + * + * @param dataNodeSelection The selected data nodes + */ + void SetDataNodeSelection(const QList& dataNodeSelection); + + const QList& GetSelectedDataNodes() const { return m_SelectedDataNodes; }; + /* + * @brief Updates the semantic relations storage model with the current data from the semantic relations model, + * if the case ID is equal to the currently selected case ID of the table model. + */ + void UpdateModelData(const mitk::SemanticTypes::CaseID& caseID); + /* + * @brief Updates the semantic relations storage model with the current data from the semantic relations model + * and the current case ID. + */ + void UpdateModelData(); + +Q_SIGNALS: + void ModelUpdated(); + +protected: + + /** + * @brief Creates a new 'SemanticRelations' instance with the new data storage and updates the model data. + * This functions is called inside the 'SetDataStorage'-function from the parent class. + */ + virtual void DataStorageChanged() override; + + /** + * @brief This function is called if the model data is updated. It can be used by subclasses to define + * the way the data of a specific model is generated. It typically consists of access to the + * semantic relations storage to retrieve certain information. + */ + virtual void SetData() = 0; + + std::shared_ptr m_SemanticRelations; + mitk::SemanticTypes::CaseID m_CaseID; + QList m_SelectedDataNodes; + mitk::SemanticTypes::Lesion m_Lesion; +}; + +#endif // QMITKABSTRACTSEMANTICRELATIONSSTORAGEMODEL_H diff --git a/Modules/SemanticRelationsUI/include/QmitkControlPointDialog.h b/Modules/SemanticRelationsUI/include/QmitkControlPointDialog.h new file mode 100644 index 0000000000..f8c23395da --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkControlPointDialog.h @@ -0,0 +1,46 @@ +/*=================================================================== + +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 QMITKCONTROLPOINTDIALOG_H +#define QMITKCONTROLPOINTDIALOG_H + +#include + +// semantic relations module +#include "mitkSemanticTypes.h" + +#include +#include + +class MITKSEMANTICRELATIONSUI_EXPORT QmitkControlPointDialog : public QDialog +{ + Q_OBJECT + +public: + + QmitkControlPointDialog(QWidget *parent = nullptr); + + virtual ~QmitkControlPointDialog(); + + void SetCurrentDate(mitk::SemanticTypes::ControlPoint currentControlPoint); + QDate GetCurrentDate() const; + +private: + + QDateEdit* m_DateEdit; +}; + +#endif // QMITKCONTROLPOINTDIALOG_H diff --git a/Modules/SemanticRelationsUI/include/QmitkLesionTextDialog.h b/Modules/SemanticRelationsUI/include/QmitkLesionTextDialog.h new file mode 100644 index 0000000000..b50806adf9 --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkLesionTextDialog.h @@ -0,0 +1,49 @@ +/*=================================================================== + +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 QMITKLESIONTEXTDIALOG_H +#define QMITKLESIONTEXTDIALOG_H + +#include + +// semantic relations module +#include "mitkSemanticTypes.h" + +#include +#include + +class MITKSEMANTICRELATIONSUI_EXPORT QmitkLesionTextDialog : public QDialog +{ + Q_OBJECT + +public: + + QmitkLesionTextDialog(QWidget *parent = nullptr); + + virtual ~QmitkLesionTextDialog(); + + void SetLineEditText(const std::string& lineEditText); + QString GetLineEditText() const; + + QLineEdit* GetLineEdit() const; + +private: + + QLineEdit* m_LineEdit; + +}; + +#endif // QMITKLESIONTEXTDIALOG_H diff --git a/Modules/SemanticRelationsUI/include/QmitkLesionTreeItem.h b/Modules/SemanticRelationsUI/include/QmitkLesionTreeItem.h new file mode 100644 index 0000000000..2cd05eec35 --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkLesionTreeItem.h @@ -0,0 +1,123 @@ +/*=================================================================== + +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 QMITKLESIONTREEITEM_H +#define QMITKLESIONTREEITEM_H + +// mitk semantic relations UI +#include "mitkLesionData.h" + +// mitk semantic relations +#include + +// qt +#include + +// c++ +#include +#include + +/* +* @brief +*/ +class QmitkLesionTreeItem : public std::enable_shared_from_this +{ +public: + + using ChildPointer = std::shared_ptr; + using ChildConstPointer = std::shared_ptr; + using ParentPointer = std::weak_ptr; + + QmitkLesionTreeItem(mitk::LesionData lesionData = mitk::LesionData()); + ~QmitkLesionTreeItem(); + + /** + * @brief Return the child of this item at a specific position. + * + * @param row Determines the position of a child item to return. + * + * @return The child of this item at a specific position. + */ + ChildPointer GetChildInRow(int row) const { return m_Children.at(row); }; + /** + * @brief Return the parent item. + * + * @return The parent item as std::weak_ptr. + */ + ParentPointer GetParent() const { return m_ParentItem; }; + /** + * @brief Set the parent item of this item. + * + * @param parent The new parent item of this item. + */ + void SetParent(ParentPointer parent); + /** + * @brief Return the item data, which contains ... + * + * see mitk::LesionItemData + */ + mitk::LesionData& GetData() { return m_ItemData; }; + /** + * @brief Get the row of this item relative to its parent item using 'GetRowOfChild'. + * + * @return The row of this item relative to its parent item. + */ + int GetRow() const; + /** + * @brief Get the row of the given child item relative to this item. + * + * @param child The child item whose row is to be determined. + * + * @return The row of the child item. + */ + int GetRowOfChild(ChildConstPointer child) const; + /** + * @brief Return the number of child items. + * + * @return Number of child items. + */ + size_t ChildCount() const { return m_Children.size(); }; + /** + * @brief Add a new child to the list of children of this item if it is not already a child item. + * + * @param child The child item to add to this item. + */ + void AddChild(ChildPointer child); + /** + * @brief Remove a child from the list of children of this item. + * + * @param child The child item to remove from this item. + */ + void RemoveChild(ChildPointer child); + /** + * @brief Set the item data of this item. + * + * @param value LesionData that provides information about this item. + */ + void SetData(const mitk::LesionData& lesionData); + +private: + + ParentPointer m_ParentItem; + std::vector m_Children; + mitk::LesionData m_ItemData; + +}; + +Q_DECLARE_METATYPE(QmitkLesionTreeItem) +Q_DECLARE_METATYPE(QmitkLesionTreeItem*) + +#endif // QMITKLESIONTREEITEM_H diff --git a/Modules/SemanticRelationsUI/include/QmitkLesionTreeModel.h b/Modules/SemanticRelationsUI/include/QmitkLesionTreeModel.h new file mode 100644 index 0000000000..0cb6979447 --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkLesionTreeModel.h @@ -0,0 +1,98 @@ +/*=================================================================== + +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 QMITKLESIONTREEMODEL_H +#define QMITKLESIONTREEMODEL_H + +// mitk semantic relations UI +#include "MitkSemanticRelationsUIExports.h" +#include "QmitkAbstractSemanticRelationsStorageModel.h" +#include "QmitkLesionTreeItem.h" + +// c++ +#include + +/* +* @brief +*/ +class MITKSEMANTICRELATIONSUI_EXPORT QmitkLesionTreeModel : public QmitkAbstractSemanticRelationsStorageModel +{ + Q_OBJECT + +public: + + /** + * @brief Initialize the root item of the model. The root item does not have a parent item. + */ + QmitkLesionTreeModel(QObject* parent = nullptr); + ~QmitkLesionTreeModel(); + + ////////////////////////////////////////////////////////////////////////// + // overridden virtual functions from QAbstractItemModel + ////////////////////////////////////////////////////////////////////////// + virtual QModelIndex index(int row, int column, const QModelIndex& itemIndex = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex& itemIndex) const override; + + virtual int rowCount(const QModelIndex& itemIndex = QModelIndex()) const override; + virtual int columnCount(const QModelIndex& itemIndex = QModelIndex()) const override; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + ////////////////////////////////////////////////////////////////////////// + // end override + ////////////////////////////////////////////////////////////////////////// + +protected: + + // the following functions have to be overridden but are not implemented in this model + virtual void NodePredicateChanged() override; + virtual void NodeAdded(const mitk::DataNode* node) override; + virtual void NodeChanged(const mitk::DataNode* node) override; + virtual void NodeRemoved(const mitk::DataNode* node) override; + /** + * @brief Overridden from 'QmitkAbstractSemanticRelationsStorageModel': This function retrieves all control points + * of the current case and stores them to define the header of the tree. + * Furthermore all lesions are retrieved and the lesion data is stored and show in the tree view. + */ + virtual void SetData() override; + +private: + + void SetLesionData(); + void AddLesion(const mitk::SemanticTypes::Lesion& lesion); + void SetSelectedDataNodesPresence(); + /** + * @brief The function uses the ID of the lesion to see if a data node presence was already set. + * If not, the given bool value is used and stored inside a member variable. If the lesion presence + * was already set, it will be overwritten. + * The function is used by the 'SetSelectedDataNodesPresence' function. + * + * @param lesion The lesion whose data node presence should be set + * @param dataNodePresence The bool value that defines the data node presence of the given lesion + */ + void SetDataNodePresenceOfLesion(const mitk::SemanticTypes::Lesion* lesion, bool dataNodePresence); + + QmitkLesionTreeItem* GetItemByIndex(const QModelIndex& index) const; + + std::map m_DataNodePresence; + + std::shared_ptr m_RootItem; + mitk::SemanticTypes::ControlPointVector m_ControlPoints; + mitk::SemanticTypes::LesionVector m_CurrentLesions; +}; + +#endif // QMITKLESIONTREEMODEL_H diff --git a/Modules/SemanticRelationsUI/include/QmitkPatientInfoWidget.h b/Modules/SemanticRelationsUI/include/QmitkPatientInfoWidget.h new file mode 100644 index 0000000000..039e4b8326 --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkPatientInfoWidget.h @@ -0,0 +1,54 @@ +/*=================================================================== + +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 QMITKPATIENTINFOWIDGET_H +#define QMITKPATIENTINFOWIDGET_H + +// semantic relations UI module +#include "MitkSemanticRelationsUIExports.h" +#include + +// qt +#include + +// mitk core +#include + +/* +* @brief The QmitkPatientInfoWidget is a widget that shows some DICOM information about a specific patient (data node). +* +* CURRENTLY NOT USED +*/ +class MITKSEMANTICRELATIONSUI_EXPORT QmitkPatientInfoWidget : public QWidget +{ + Q_OBJECT + +public: + + QmitkPatientInfoWidget(QWidget* parent = nullptr); + ~QmitkPatientInfoWidget(); + + void SetPatientInfo(const mitk::DataNode* dataNode); + +private: + + void QmitkPatientInfoWidget::Init(); + + Ui::QmitkPatientInfoWidgetControls m_Controls; + +}; + +#endif // QMITKPATIENTINFOWIDGET_H diff --git a/Modules/SemanticRelationsUI/include/QmitkPatientTableHeaderView.h b/Modules/SemanticRelationsUI/include/QmitkPatientTableHeaderView.h new file mode 100644 index 0000000000..263baafa41 --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkPatientTableHeaderView.h @@ -0,0 +1,83 @@ +/*=================================================================== + +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 QMITKPATIENTTABLEHEADERVIEW_H +#define QMITKPATIENTTABLEHEADERVIEW_H + +// semantic relations ui module +#include "MitkSemanticRelationsUIExports.h" + +// qt +#include +#include +#include + +/* +* @brief +*/ +class MITKSEMANTICRELATIONSUI_EXPORT QmitkPatientTableHeaderView : public QHeaderView +{ + Q_OBJECT + +public: + + QmitkPatientTableHeaderView(QWidget* parent = nullptr); + ~QmitkPatientTableHeaderView(); + + enum HeaderDataModelRoles + { + HorizontalHeaderDataRole = Qt::UserRole + }; + + /** + * @brief Set the model of the table view of this header view. + * This model returns a standard item model for the 'HorizontalHeaderDataRole'-role. + * The header model has been previously filled with header data. + * This function is overwritten from QHeaderView. + */ + virtual void setModel(QAbstractItemModel* model) override; + +protected: + + /** + * @brief Paint each header using 'PaintHeader'. + * This function is overwritten from QHeaderView. + */ + virtual void paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const override; + /** + * @brief Get the section size by retrieving the text content. + * The section size is dependent on the child and parent headers. + * This function is overwritten from QHeaderView. + */ + QSize sectionSizeFromContents(int logicalIndex) const override; + +private: + + int PaintHeader(QPainter* painter, const QModelIndex& currentIndex, int logicalIndex, const QRect& sectionRect, int top) const; + QSize HeaderSize(const QModelIndex& index) const; + int CurrentHeaderLeft(const QModelIndex& currentIndex, const QModelIndex& headerIndex, int sectionIndex, int left) const; + int CurrentHeaderWidth(const QModelIndex& currentIndex, const QModelIndex& headerIndex, int sectionIndex) const; + + QModelIndexList ParentIndexList(QModelIndex index) const; + QModelIndex HeaderIndex(int sectionIndex) const; + QModelIndex FindHeader(const QModelIndex& currentIndex, int sectionIndex, int& currentHeaderIndex) const; + QModelIndexList ListHeader(const QModelIndex& currentIndex) const; + + QStandardItemModel* m_HeaderModel; + +}; + +#endif // QMITKPATIENTTABLEHEADERVIEW_H diff --git a/Modules/SemanticRelationsUI/include/QmitkPatientTableInspector.h b/Modules/SemanticRelationsUI/include/QmitkPatientTableInspector.h new file mode 100644 index 0000000000..1a625045f8 --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkPatientTableInspector.h @@ -0,0 +1,91 @@ +/*=================================================================== + +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 QMITKPATIENTTABLEINSPECTOR_H +#define QMITKPATIENTTABLEINSPECTOR_H + +// semantic relations UI module +#include "MitkSemanticRelationsUIExports.h" +#include "QmitkAbstractSemanticRelationsStorageInspector.h" +#include "QmitkPatientTableModel.h" +#include "QmitkTableItemThumbnailDelegate.h" + +#include "ui_QmitkPatientTableInspector.h" + +// qt widgets module +#include + +// qt +#include + +/* +* @brief The QmitkPatientTableInspector is a QmitkAbstractSemanticRelationsStorageInspector that shows the currently +* available data of the semantic relations storage model in a control-point - information type matrix. +* +* The QmitkPatientTableInspector uses the QmitkSemanticRelationsStorageModel, a QmitkAbstractDataStorageModel that +* presents the semantic relations data as a table, showing a QPixmap as thumbnail for the data nodes. +*/ +class MITKSEMANTICRELATIONSUI_EXPORT QmitkPatientTableInspector : public QmitkAbstractSemanticRelationsStorageInspector +{ + Q_OBJECT + +public: + + QmitkPatientTableInspector(QWidget* parent = nullptr); + + virtual QAbstractItemView* GetView() override; + virtual const QAbstractItemView* GetView() const override; + + virtual void SetSelectionMode(SelectionMode mode) override; + virtual SelectionMode GetSelectionMode() const override; + + virtual void SetCaseID(const mitk::SemanticTypes::CaseID& caseID) override; + virtual void SetLesion(const mitk::SemanticTypes::Lesion& lesion) override; + + QItemSelectionModel* GetSelectionModel(); + +Q_SIGNALS: + + void DataNodeDoubleClicked(const mitk::DataNode*); + void OnContextMenuRequested(const QPoint&); + void OnNodeRemoved(const mitk::DataNode*); + +private Q_SLOTS: + + void OnModelUpdated(); + void OnNodeButtonClicked(const QString&); + void OnDataNodeSelectionChanged(const QList&); + void OnItemDoubleClicked(const QModelIndex&); + +protected: + + virtual void Initialize() override; + +private: + + void SetUpConnections(); + + virtual void keyPressEvent(QKeyEvent* e) override; + + Ui::QmitkPatientTableInspector m_Controls; + QmitkPatientTableModel* m_StorageModel; + QmitkTableItemThumbnailDelegate* m_ItemDelegate; + + mitk::DataNode* m_SelectedDataNode; + +}; + +#endif // QMITKPATIENTTABLEINSPECTOR_H diff --git a/Modules/SemanticRelationsUI/include/QmitkPatientTableModel.h b/Modules/SemanticRelationsUI/include/QmitkPatientTableModel.h new file mode 100644 index 0000000000..a63d0ab83b --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkPatientTableModel.h @@ -0,0 +1,132 @@ +/*=================================================================== + +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 QMITKPATIENTTABLEMODEL_H +#define QMITKPATIENTTABLEMODEL_H + +// semantic relations UI module +#include "QmitkAbstractSemanticRelationsStorageModel.h" + +// semantic relations module +#include + +// mitk core +#include + +// qt +#include +#include + +/** +* @brief The QmitkPatientTableModel is a subclass of the QmitkAbstractSemanticRelationsStorageModel and holds the semantic relations data of the currently selected case. +* +* The QmitkPatientTableModel uses the 'data' function to return either the data node of a table cell or the thumbnail of the underlying image. +* The horizontal header of the table shows the control points of the current case and the vertical header of the table shows the information types of the current case. +* Using the 'GetFilteredData'-function of the SemanticRelations-class the model is able to retrieve the correct data node for each table entry. +* +* Additionally the model creates and holds the QPixmaps of the known data nodes in order to return a thumbnail, if needed. +*/ +class QmitkPatientTableModel : public QmitkAbstractSemanticRelationsStorageModel +{ + Q_OBJECT + +public: + + QmitkPatientTableModel(QObject* parent = nullptr); + ~QmitkPatientTableModel(); + + ////////////////////////////////////////////////////////////////////////// + // overridden functions from QAbstractItemModel + ////////////////////////////////////////////////////////////////////////// + virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + virtual QModelIndex parent(const QModelIndex& child) const override; + + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + ////////////////////////////////////////////////////////////////////////// + /// end override + ///////////////////////////////////////////////////////////////////////// + + void SetNodeType(const std::string& nodeType); + +protected: + + // the following functions have to be overridden but are not implemented in this model + virtual void NodePredicateChanged() override; + virtual void NodeAdded(const mitk::DataNode* node) override; + virtual void NodeChanged(const mitk::DataNode* node) override; + virtual void NodeRemoved(const mitk::DataNode* node) override; + /** + * @brief Overridden from 'QmitkAbstractSemanticRelationsStorageModel': This function retrieves all control points + * and information types of the current case and stores them to define the header of the table. + * Furthermore all images are retrieved and the pixmap of the images are generated and stored. + */ + virtual void SetData() override; + +private: + + void SetHeaderModel(); + void SetPixmaps(); + void SetLesionPresences(); + + /** + * @brief The function uses the ID of the node to see if a pixmap was already set. If not, the given pixmap + * is used and stored inside a member variable. If the pixmap was already set, it will be overwritten. + * Using 'nullptr' as a pixmap will erase the entry for the given data node. + * + * @param dataNode The data node whose pixmap should be set + * @param pixmapFromImage The pixmap that shows an image of the content of the data node + */ + void SetPixmapOfNode(const mitk::DataNode* dataNode, QPixmap* pixmapFromImage); + /** + * @brief The function uses the ID of the node to see if a lesion presence was already set. If not, the given + * bool value is used and stored inside a member variable. If the lesion presence was already set, it + * will be overwritten. + * The function is used by the 'SetLesionPresences' function. + * + * @param dataNode The data node whose lesion presence should be set + * @param lesionPresence The bool value that defines the lesion presence of the given data node + */ + void SetLesionPresenceOfNode(const mitk::DataNode* dataNode, bool lesionPresence); + /** + * @brief Returns the data node that is associated with the given table entry (index). + * + * The function uses the SemanticRelations-class and the current control point data and information type data to + * filter the nodes of the current case. + * The index is used to access the correct row in the table (information type) and the correct column in the table (control point). + * + * @par index The QModelIndex of the table entry + */ + mitk::DataNode* GetCurrentDataNode(const QModelIndex &index) const; + + std::map m_PixmapMap; + std::map m_LesionPresence; + + mitk::SemanticTypes::InformationTypeVector m_InformationTypes; + mitk::SemanticTypes::ControlPointVector m_ControlPoints; + mitk::SemanticTypes::ExaminationPeriodVector m_ExaminationPeriods; + mitk::SemanticRelations::DataNodeVector m_CurrentDataNodes; + std::string m_SelectedNodeType; + + QStandardItemModel* m_HeaderModel; +}; + +#endif // QMITKPATIENTTABLEMODEL_H diff --git a/Modules/SemanticRelationsUI/include/QmitkSemanticRelationsUIHelper.h b/Modules/SemanticRelationsUI/include/QmitkSemanticRelationsUIHelper.h new file mode 100644 index 0000000000..4c804178f0 --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkSemanticRelationsUIHelper.h @@ -0,0 +1,45 @@ +/*=================================================================== + +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 QMITKSEMANTICRELATIONSUIHELPER_H +#define QMITKSEMANTICRELATIONSUIHELPER_H + +// semantic relations ui module +#include "MitkSemanticRelationsUIExports.h" + +// mitk core +#include + +// qt +#include + +/** +* @brief Provides a helper function to generate a pixmap from a given image node. +*/ +namespace QmitkSemanticRelationsUIHelper +{ + /* + * @brief Generates a QPixmap of a DICOM image. + * + * The center sagittal image slice is extracted and used as the thumbnail image. + * + * @par dataNode The data node that holds the image data. + */ + MITKSEMANTICRELATIONSUI_EXPORT QPixmap GetPixmapFromImageNode(const mitk::DataNode* dataNode); + +} // namespace QmitkSemanticRelationsUIHelper + +#endif // QMITKSEMANTICRELATIONSUIHELPER_H diff --git a/Modules/SemanticRelationsUI/include/QmitkTableItemThumbnailDelegate.h b/Modules/SemanticRelationsUI/include/QmitkTableItemThumbnailDelegate.h new file mode 100644 index 0000000000..c65150847f --- /dev/null +++ b/Modules/SemanticRelationsUI/include/QmitkTableItemThumbnailDelegate.h @@ -0,0 +1,36 @@ +/*=================================================================== + +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 QMITKTABLEITEMTHUMBNAILDELEGATE_H +#define QMITKTABLEITEMTHUMBNAILDELEGATE_H + +#include + +/* +* @brief The QmitkTableItemThumbnailDelegate is a subclass of the QmitkAbstractSemanticRelationsStorageModel +*/ +class QmitkTableItemThumbnailDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + + QmitkTableItemThumbnailDelegate(QObject* parent = nullptr); + + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; +}; + +#endif // QMITKTABLEITEMTHUMBNAILDELEGATE_H diff --git a/Modules/SemanticRelationsUI/include/mitkModuleActivator.h b/Modules/SemanticRelationsUI/include/mitkModuleActivator.h new file mode 100644 index 0000000000..2fe949967c --- /dev/null +++ b/Modules/SemanticRelationsUI/include/mitkModuleActivator.h @@ -0,0 +1,46 @@ +/*=================================================================== + +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 MITKMODULEACTIVATOR_H +#define MITKMODULEACTIVATOR_H + +// Micro Services +#include +#include +#include +#include + +#include + +// qt widgets module +#include "mitkIDataStorageInspectorProvider.h" + +namespace mitk +{ + /* + * This is the module activator for the "SemanticRelationsUI" module. + */ + class SemanticRelationsUIActivator : public us::ModuleActivator + { + public: + void Load(us::ModuleContext* context) override; + void Unload(us::ModuleContext* context) override; + + private: + std::unique_ptr m_PatientTableInspector; + }; +} +#endif // MITKMODULEACTIVATOR_H diff --git a/Modules/SemanticRelationsUI/src/QmitkAbstractSemanticRelationsStorageModel.cpp b/Modules/SemanticRelationsUI/src/QmitkAbstractSemanticRelationsStorageModel.cpp new file mode 100644 index 0000000000..c1b112a0e2 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkAbstractSemanticRelationsStorageModel.cpp @@ -0,0 +1,95 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations UI module +#include "QmitkAbstractSemanticRelationsStorageModel.h" + +#include "QmitkCustomVariants.h" + +QmitkAbstractSemanticRelationsStorageModel::QmitkAbstractSemanticRelationsStorageModel(QObject* parent /*= nullptr*/) + : QmitkAbstractDataStorageModel(parent) + , m_SemanticRelations(nullptr) +{ + // nothing here +} + +QmitkAbstractSemanticRelationsStorageModel::~QmitkAbstractSemanticRelationsStorageModel() +{ + if (nullptr != m_SemanticRelations) + { + m_SemanticRelations->RemoveObserver(this); + } +} + +void QmitkAbstractSemanticRelationsStorageModel::Update(const mitk::SemanticTypes::CaseID& caseID) +{ + UpdateModelData(caseID); +} + +void QmitkAbstractSemanticRelationsStorageModel::SetCaseID(const mitk::SemanticTypes::CaseID& caseID) +{ + m_CaseID = caseID; + UpdateModelData(); +} + +void QmitkAbstractSemanticRelationsStorageModel::SetLesion(const mitk::SemanticTypes::Lesion& lesion) +{ + m_Lesion = lesion; + UpdateModelData(); +} + +void QmitkAbstractSemanticRelationsStorageModel::SetDataNodeSelection(const QList& dataNodeSelection) +{ + m_SelectedDataNodes = dataNodeSelection; + UpdateModelData(); +} + +void QmitkAbstractSemanticRelationsStorageModel::UpdateModelData(const mitk::SemanticTypes::CaseID& caseID) +{ + // if the case ID of updated instance is equal to the currently active caseID + if (caseID == m_CaseID) + { + UpdateModelData(); + } +} + +void QmitkAbstractSemanticRelationsStorageModel::UpdateModelData() +{ + if (nullptr == m_SemanticRelations) + { + return; + } + + // update the model, so that the table will be filled with the new patient information + beginResetModel(); + + SetData(); + + endResetModel(); + emit ModelUpdated(); +} + +void QmitkAbstractSemanticRelationsStorageModel::DataStorageChanged() +{ + if (nullptr != m_SemanticRelations) + { + m_SemanticRelations->RemoveObserver(this); + } + + m_SemanticRelations = std::make_shared(m_DataStorage.Lock()); + m_SemanticRelations->AddObserver(this); + UpdateModelData(); +} diff --git a/Modules/SemanticRelationsUI/src/QmitkControlPointDialog.cpp b/Modules/SemanticRelationsUI/src/QmitkControlPointDialog.cpp new file mode 100644 index 0000000000..348aade318 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkControlPointDialog.cpp @@ -0,0 +1,69 @@ +/*=================================================================== + +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 "QmitkControlPointDialog.h" + +#include +#include +#include +#include +#include + +QmitkControlPointDialog::QmitkControlPointDialog(QWidget* parent) + : QDialog(parent) +{ + QBoxLayout* verticalLayout = new QVBoxLayout(this); + verticalLayout->setMargin(5); + verticalLayout->setSpacing(5); + + QLabel* dateLabel = new QLabel(tr("Set date"), this); + verticalLayout->addWidget(dateLabel); + + m_DateEdit = new QDateEdit(this); + m_DateEdit->setDisplayFormat("yyyy-MM-dd"); + m_DateEdit->setFocus(); + verticalLayout->addWidget(m_DateEdit); + + QPushButton* acceptButton = new QPushButton(tr("Ok"), this); + QPushButton* cancelButton = new QPushButton(tr("Cancel"), this); + acceptButton->setDefault(true); + + connect(acceptButton, &QPushButton::clicked, this, &QmitkControlPointDialog::accept); + connect(cancelButton, &QPushButton::clicked, this, &QmitkControlPointDialog::reject); + + QBoxLayout* horizontalLayout = new QHBoxLayout(); + horizontalLayout->setSpacing(5); + horizontalLayout->addStretch(); + horizontalLayout->addWidget(acceptButton); + horizontalLayout->addWidget(cancelButton); + verticalLayout->addLayout(horizontalLayout); + + setMinimumSize(250, 100); +} + +QmitkControlPointDialog::~QmitkControlPointDialog() +{ +} + +void QmitkControlPointDialog::SetCurrentDate(mitk::SemanticTypes::ControlPoint currentControlPoint) +{ + m_DateEdit->setDate(QDate(currentControlPoint.date.year(), currentControlPoint.date.month(), currentControlPoint.date.day())); +} + +QDate QmitkControlPointDialog::GetCurrentDate() const +{ + return m_DateEdit->date(); +} diff --git a/Modules/SemanticRelationsUI/src/QmitkLesionTextDialog.cpp b/Modules/SemanticRelationsUI/src/QmitkLesionTextDialog.cpp new file mode 100644 index 0000000000..3ca3416a27 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkLesionTextDialog.cpp @@ -0,0 +1,72 @@ +/*=================================================================== + +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 "QmitkLesionTextDialog.h" + +#include +#include +#include + +QmitkLesionTextDialog::QmitkLesionTextDialog(QWidget* parent) + : QDialog(parent) +{ + QBoxLayout* verticalLayout = new QVBoxLayout(this); + verticalLayout->setMargin(5); + verticalLayout->setSpacing(5); + + QLabel* dialogLabel = new QLabel(tr("Set lesion information"), this); + verticalLayout->addWidget(dialogLabel); + + m_LineEdit = new QLineEdit(this); + m_LineEdit->setFocus(); + verticalLayout->addWidget(m_LineEdit); + + QPushButton* acceptButton = new QPushButton(tr("Ok"), this); + QPushButton* cancelButton = new QPushButton(tr("Cancel"), this); + acceptButton->setDefault(true); + + connect(acceptButton, &QPushButton::clicked, this, &QmitkLesionTextDialog::accept); + connect(cancelButton, &QPushButton::clicked, this, &QmitkLesionTextDialog::reject); + + QBoxLayout* horizontalLayout = new QHBoxLayout(); + horizontalLayout->setSpacing(5); + horizontalLayout->addStretch(); + horizontalLayout->addWidget(acceptButton); + horizontalLayout->addWidget(cancelButton); + verticalLayout->addLayout(horizontalLayout); + + setMinimumSize(250, 100); +} + +QmitkLesionTextDialog::~QmitkLesionTextDialog() +{ + // nothing here +} + +void QmitkLesionTextDialog::SetLineEditText(const std::string& lineEditText) +{ + m_LineEdit->setText(QString::fromStdString(lineEditText)); +} + +QString QmitkLesionTextDialog::GetLineEditText() const +{ + return m_LineEdit->text(); +} + +QLineEdit* QmitkLesionTextDialog::GetLineEdit() const +{ + return m_LineEdit; +} diff --git a/Modules/SemanticRelationsUI/src/QmitkLesionTreeItem.cpp b/Modules/SemanticRelationsUI/src/QmitkLesionTreeItem.cpp new file mode 100644 index 0000000000..a698270871 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkLesionTreeItem.cpp @@ -0,0 +1,86 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations UI module +#include "QmitkLesionTreeItem.h" + +// qt +#include + +QmitkLesionTreeItem::QmitkLesionTreeItem(mitk::LesionData lesionData/* = mitk::LesionTreeItemData()*/) +{ + m_ItemData = lesionData; +} + +QmitkLesionTreeItem::~QmitkLesionTreeItem() +{ + // nothing here +} + +void QmitkLesionTreeItem::SetParent(ParentPointer parent) +{ + m_ParentItem = parent; +} + +int QmitkLesionTreeItem::GetRow() const +{ + if (m_ParentItem.expired()) + { + return 0; + } + + auto parentItem = m_ParentItem.lock(); + return parentItem->GetRowOfChild(this->shared_from_this()); +} + +int QmitkLesionTreeItem::GetRowOfChild(ChildConstPointer child) const +{ + auto it = std::find(m_Children.begin(), m_Children.end(), child); + if (it == m_Children.end()) + { + return -1; + } + else + { + return std::distance(m_Children.begin(), it); + } +} + +void QmitkLesionTreeItem::AddChild(ChildPointer child) +{ + auto it = std::find(m_Children.begin(), m_Children.end(), child); + if (it == m_Children.end()) + { + // child does not already exist; add to vector of children + m_Children.push_back(child); + // add parent item + child->SetParent(this->shared_from_this()); + } +} + +void QmitkLesionTreeItem::RemoveChild(ChildPointer child) +{ + auto it = std::find(m_Children.begin(), m_Children.end(), child); + if (it != m_Children.end()) + { + m_Children.erase(it); + } +} + +void QmitkLesionTreeItem::SetData(const mitk::LesionData& lesionData) +{ + m_ItemData = lesionData; +} diff --git a/Modules/SemanticRelationsUI/src/QmitkLesionTreeModel.cpp b/Modules/SemanticRelationsUI/src/QmitkLesionTreeModel.cpp new file mode 100644 index 0000000000..cf39b6510c --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkLesionTreeModel.cpp @@ -0,0 +1,326 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations UI module +#include "QmitkLesionTreeModel.h" + +// semantic relations module +#include +#include +#include + +// qt +#include + +QmitkLesionTreeModel::QmitkLesionTreeModel(QObject* parent/* = nullptr*/) + : QmitkAbstractSemanticRelationsStorageModel(parent) + , m_RootItem(std::make_shared(mitk::LesionData())) +{ + // nothing here +} + +QmitkLesionTreeModel::~QmitkLesionTreeModel() +{ + // nothing here +} + +////////////////////////////////////////////////////////////////////////// +// overridden virtual functions from QAbstractItemModel +////////////////////////////////////////////////////////////////////////// +QModelIndex QmitkLesionTreeModel::index(int row, int column, const QModelIndex& itemIndex) const +{ + if (!hasIndex(row, column, itemIndex)) + { + return QModelIndex(); + } + + auto childItem = GetItemByIndex(itemIndex)->GetChildInRow(row); + if (nullptr == childItem) + { + return QModelIndex(); + } + + return createIndex(row, column, childItem.get()); +} + +QModelIndex QmitkLesionTreeModel::parent(const QModelIndex& itemIndex) const +{ + if (!itemIndex.isValid()) + { + return QModelIndex(); + } + + auto parentItem = GetItemByIndex(itemIndex)->GetParent(); + if (parentItem.expired()) + { + return QModelIndex(); + } + + auto sharedParent = parentItem.lock(); + if (sharedParent == m_RootItem) + { + return QModelIndex(); + } + + return createIndex(sharedParent->GetRow(), 0, sharedParent.get()); +} + +int QmitkLesionTreeModel::rowCount(const QModelIndex& itemIndex/* = QModelIndex()*/) const +{ + return GetItemByIndex(itemIndex)->ChildCount(); +} + +int QmitkLesionTreeModel::columnCount(const QModelIndex&/* itemIndex = QModelIndex() */) const +{ + if (0 == m_RootItem->ChildCount()) + { + // no lesion items stored, no need to display columns + return 0; + } + + return m_ControlPoints.size() + 1; +} + +QVariant QmitkLesionTreeModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + { + return QVariant(); + } + + if (index.column() < 0 || index.column() > static_cast(m_ControlPoints.size())) + { + return QVariant(); + } + + QmitkLesionTreeItem* currentItem = GetItemByIndex(index); + if (Qt::DisplayRole == role) + { + if (currentItem->GetParent().expired()) + { + return QVariant(); + } + + auto parentItem = currentItem->GetParent().lock(); + // parent exists and is the root item -> 1. item of a lesion entry + if (m_RootItem == parentItem) + { + // display role fills the first columns with the lesion UID / name + if (0 == index.column()) + { + std::string itemString = currentItem->GetData().GetLesionName(); + if (itemString.empty()) + { + itemString = currentItem->GetData().GetLesionUID(); + } + return QString::fromStdString(itemString); + } + else + { + // display role fills other columns with the lesion presence info + const auto lesionPresence = currentItem->GetData().GetLesionPresence(); + if (index.column() - 1 > static_cast(lesionPresence.size())) + { + return ""; + } + + return QVariant(lesionPresence.at(index.column() - 1)); + } + } + // parent is not the root item -> 2. item of a lesion entry + else + { + // display role fills the first columns with the property name "Volume" + if (0 == index.column()) + { + return "Volume"; + } + else + { + // display role fills other columns with the lesion volume info + const auto lesionVolume= currentItem->GetData().GetLesionVolume(); + if (index.column() - 1 > static_cast(lesionVolume.size())) + { + return ""; + } + return QVariant(lesionVolume.at(index.column() - 1)); + } + } + } + + if (Qt::BackgroundColorRole == role) + { + auto it = m_DataNodePresence.find(currentItem->GetData().GetLesion().UID); + if (it != m_DataNodePresence.end()) + { + return it->second ? QVariant(QColor(Qt::darkGreen)) : QVariant(QColor(Qt::transparent)); + } + + return QVariant(QColor(Qt::transparent)); + } + + if (Qt::UserRole == role) + { + return QVariant::fromValue(currentItem); + } + + return QVariant(); +} + +QVariant QmitkLesionTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (0 == m_RootItem->ChildCount()) + { + // no lesion items stored, no need to display the header + return QVariant(); + } + + if (Qt::Horizontal == orientation && Qt::DisplayRole == role) + { + if (0 == section) + { + return QVariant("Lesion"); + } + + if (m_ControlPoints.size() >= section) + { + mitk::SemanticTypes::ControlPoint currentControlPoint = m_ControlPoints.at(section-1); + return QVariant(QString::fromStdString(currentControlPoint.ToString())); + } + } + + return QVariant(); +} + +void QmitkLesionTreeModel::NodePredicateChanged() +{ + // does not react to node predicate changes +} + +void QmitkLesionTreeModel::NodeAdded(const mitk::DataNode* node) +{ + // does not react to data storage changes +} + +void QmitkLesionTreeModel::NodeChanged(const mitk::DataNode* node) +{ + // does not react to data storage changes +} + +void QmitkLesionTreeModel::NodeRemoved(const mitk::DataNode* node) +{ + // does not react to data storage changes +} + +void QmitkLesionTreeModel::SetData() +{ + m_RootItem = std::make_shared(mitk::LesionData()); + + // get all control points of current case + m_ControlPoints = m_SemanticRelations->GetAllControlPointsOfCase(m_CaseID); + // sort the vector of control points for the timeline + std::sort(m_ControlPoints.begin(), m_ControlPoints.end()); + + SetLesionData(); + SetSelectedDataNodesPresence(); +} + +void QmitkLesionTreeModel::SetLesionData() +{ + m_CurrentLesions = m_SemanticRelations->GetAllLesionsOfCase(m_CaseID); + for (auto& lesion : m_CurrentLesions) + { + AddLesion(lesion); + } +} + +void QmitkLesionTreeModel::AddLesion(const mitk::SemanticTypes::Lesion& lesion) +{ + if (nullptr == m_SemanticRelations) + { + return; + } + + // create new lesion tree item data and modify it according to the control point data + mitk::LesionData lesionData(lesion); + mitk::GenerateAdditionalLesionData(lesionData, m_CaseID, m_SemanticRelations); + + // add the 1. level lesion item to the root item + std::shared_ptr newLesionTreeItem = std::make_shared(lesionData); + m_RootItem->AddChild(newLesionTreeItem); + + // add the 2. level lesion item to the 1. level lesion item + std::shared_ptr newChildItem = std::make_shared(lesionData); + newLesionTreeItem->AddChild(newChildItem); +} + +void QmitkLesionTreeModel::SetSelectedDataNodesPresence() +{ + m_DataNodePresence.clear(); + for (const auto& dataNode : m_SelectedDataNodes) + { + if (!m_SemanticRelations->InstanceExists(dataNode)) + { + continue; + } + + for (const auto& lesion : m_CurrentLesions) + { + if (!m_SemanticRelations->InstanceExists(m_CaseID, lesion)) + { + continue; + } + try + { + // set the lesion presence for the current node + bool dataNodePresence = m_SemanticRelations->IsLesionPresentOnDataNode(lesion, dataNode); + SetDataNodePresenceOfLesion(&lesion, dataNodePresence); + } + catch (const mitk::SemanticRelationException&) + { + continue; + } + } + } +} + +void QmitkLesionTreeModel::SetDataNodePresenceOfLesion(const mitk::SemanticTypes::Lesion* lesion, bool dataNodePresence) +{ + std::map::iterator iter = m_DataNodePresence.find(lesion->UID); + if (iter != m_DataNodePresence.end()) + { + // key already existing, overwrite already stored bool value + iter->second = dataNodePresence; + } + else + { + m_DataNodePresence.insert(std::make_pair(lesion->UID, dataNodePresence)); + } +} + +QmitkLesionTreeItem* QmitkLesionTreeModel::GetItemByIndex(const QModelIndex& index) const +{ + if (index.isValid()) + { + auto item = static_cast(index.internalPointer()); + if (nullptr != item) + { + return item; + } + } + + return m_RootItem.get(); +} diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientInfoWidget.cpp b/Modules/SemanticRelationsUI/src/QmitkPatientInfoWidget.cpp new file mode 100644 index 0000000000..0f1c9e6099 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkPatientInfoWidget.cpp @@ -0,0 +1,105 @@ +/*=================================================================== + +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 "QmitkPatientInfoWidget.h" + +// semantic relations module +#include + +// mitk core +#include + +QmitkPatientInfoWidget::QmitkPatientInfoWidget(QWidget* parent) + : QWidget(parent) +{ + Init(); +} + +QmitkPatientInfoWidget::~QmitkPatientInfoWidget() +{ + // nothing here +} + +void QmitkPatientInfoWidget::Init() +{ + // Create GUI widgets from the Qt Designer's .ui file + m_Controls.setupUi(this); +} + +void QmitkPatientInfoWidget::SetPatientInfo(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + MITK_INFO << "Not a valid data node."; + return; + } + + mitk::BaseData* baseData = dataNode->GetData(); + if (nullptr == baseData) + { + MITK_INFO << "No valid base data."; + return; + } + + mitk::BaseProperty* acquisitionDate = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0022).c_str()); + std::string acquisitionDateAsString = ""; + if (nullptr != acquisitionDate) + { + acquisitionDateAsString = acquisitionDate->GetValueAsString(); + mitk::DICOMHelper::ReformatDICOMTag(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0022).c_str(), acquisitionDateAsString); + } + + mitk::BaseProperty* acquisitionTime = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0032).c_str()); + std::string acquisitionTimeAsString = ""; + if (nullptr != acquisitionTime) + { + acquisitionTimeAsString = acquisitionTime->GetValueAsString(); + mitk::DICOMHelper::ReformatDICOMTag(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0032).c_str(), acquisitionTimeAsString); + } + + std::string patiensBirthDateAsString = ""; + mitk::BaseProperty* patientBirthDate = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0030).c_str()); + if (nullptr != patientBirthDate) + { + patiensBirthDateAsString = patientBirthDate->GetValueAsString(); + mitk::DICOMHelper::ReformatDICOMTag(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0030).c_str(), patiensBirthDateAsString); + } + + mitk::BaseProperty* modality = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0060).c_str()); + std::string modalityAsString = ""; + if (nullptr != modality) + { + modalityAsString = modality->GetValueAsString(); + mitk::DICOMHelper::ReformatDICOMTag(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0060).c_str(), modalityAsString); + } + + mitk::BaseProperty* gender = baseData->GetProperty(mitk::GeneratePropertyNameForDICOMTag(0x0010, 0x0040).c_str()); + std::string genderAsString = ""; + if (nullptr != gender) + { + genderAsString = gender->GetValueAsString(); + mitk::DICOMHelper::ReformatDICOMTag(mitk::GeneratePropertyNameForDICOMTag(0x0008, 0x0060).c_str(), genderAsString); + } + + m_Controls.dataIDLineEdit->setText(QString::fromStdString(mitk::DICOMHelper::GetIDFromDataNode(dataNode))); + m_Controls.nameLineEdit->setText(QString::fromStdString(mitk::DICOMHelper::GetCaseIDFromDataNode(dataNode))); + m_Controls.dateLineEdit->setText(QString::fromStdString(acquisitionDateAsString)); + m_Controls.timeLineEdit->setText(QString::fromStdString(acquisitionTimeAsString)); + m_Controls.scannerLineEdit->setText(""); + m_Controls.modalityLineEdit->setText(QString::fromStdString(modalityAsString)); + m_Controls.birthdateLineEdit->setText(QString::fromStdString(patiensBirthDateAsString)); + m_Controls.genderLineEdit->setText(QString::fromStdString(genderAsString)); +} diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientInfoWidgetControls.ui b/Modules/SemanticRelationsUI/src/QmitkPatientInfoWidgetControls.ui new file mode 100644 index 0000000000..3ffbe69f32 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkPatientInfoWidgetControls.ui @@ -0,0 +1,166 @@ + + + QmitkPatientInfoWidgetControls + + + true + + + + 0 + 0 + 350 + 300 + + + + + + + + 75 + false + true + + + + Patient data information + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + ID*: + + + + + + + true + + + + + + + Name: + + + + + + + true + + + + + + + Acq. Date: + + + + + + + true + + + YYYY-MM-DD + + + + + + + Time: + + + + + + + true + + + hh:mm:ss + + + + + + + Scanner: + + + + + + + true + + + + + + + Modality: + + + + + + + true + + + + + + + Birthdate: + + + + + + + true + + + YYYY-MM-DD + + + + + + + Gender: + + + + + + + true + + + + + + + * mandatory field + + + + + + + + diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientTableHeaderView.cpp b/Modules/SemanticRelationsUI/src/QmitkPatientTableHeaderView.cpp new file mode 100644 index 0000000000..e35970cd00 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkPatientTableHeaderView.cpp @@ -0,0 +1,244 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations UI module +#include "QmitkPatientTableHeaderView.h" + +// qt +#include +#include + +QmitkPatientTableHeaderView::QmitkPatientTableHeaderView(QWidget* parent/* = nullptr*/) + : QHeaderView(Qt::Horizontal, parent) + , m_HeaderModel(nullptr) +{ + // nothing here +} + +QmitkPatientTableHeaderView::~QmitkPatientTableHeaderView() +{ + // nothing here +} + +void QmitkPatientTableHeaderView::setModel(QAbstractItemModel* model) +{ + // retrieve the header model from the given table model + QVariant variant = model->data(QModelIndex(), HorizontalHeaderDataRole); + if (variant.isValid() && variant.canConvert()) + { + m_HeaderModel = variant.value(); + } + + QHeaderView::setModel(model); +} + +void QmitkPatientTableHeaderView::paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const +{ + if (rect.isValid()) + { + int top = rect.y(); + + QModelIndex leafIndex = HeaderIndex(logicalIndex); + QModelIndexList indexes = ParentIndexList(leafIndex); + for (const auto& index : indexes) + { + top = PaintHeader(painter, index, logicalIndex, rect, top); + } + + return; + } + + QHeaderView::paintSection(painter, rect, logicalIndex); +} + +QSize QmitkPatientTableHeaderView::sectionSizeFromContents(int logicalIndex) const +{ + if (nullptr != m_HeaderModel) + { + QModelIndex headerIndex = HeaderIndex(logicalIndex); + if (headerIndex.isValid()) + { + QSize headerSize = HeaderSize(headerIndex); + headerIndex = headerIndex.parent(); + while (headerIndex.isValid()) + { + QSize currentHeaderSize = HeaderSize(headerIndex); + headerSize.rheight() += currentHeaderSize.height(); + if (currentHeaderSize.width() > headerSize.width()) + { + headerSize.rwidth() = currentHeaderSize.width(); + } + + headerIndex = headerIndex.parent(); + } + return headerSize; + } + } + + return QHeaderView::sectionSizeFromContents(logicalIndex); +} + +int QmitkPatientTableHeaderView::PaintHeader(QPainter* painter, const QModelIndex& currentIndex, int logicalIndex, const QRect& sectionRect, int top) const +{ + QModelIndex headerIndex = HeaderIndex(logicalIndex); + int height = HeaderSize(currentIndex).height(); + if (currentIndex == headerIndex) + { + height = sectionRect.height() - top; + } + int left = CurrentHeaderLeft(currentIndex, headerIndex, logicalIndex, sectionRect.left()); + int width = CurrentHeaderWidth(currentIndex, headerIndex, logicalIndex); + + QStyleOptionHeader headerStyleOptions; + initStyleOption(&headerStyleOptions); + headerStyleOptions.text = currentIndex.data(Qt::DisplayRole).toString(); + headerStyleOptions.textAlignment = Qt::AlignCenter; + painter->save(); + + QRect rect(left, top, width, height); + headerStyleOptions.rect = rect; + style()->drawControl(QStyle::CE_Header, &headerStyleOptions, painter, this); + painter->restore(); + + return top + height; +} + +QSize QmitkPatientTableHeaderView::HeaderSize(const QModelIndex& index) const +{ + QFont font = this->font(); + font.setBold(true); + QFontMetrics fontMetrics(font); + QSize fontSize = fontMetrics.size(0, index.data(Qt::DisplayRole).toString()); + QSize emptyFontSize = fontMetrics.size(0, ""); + + return fontSize + emptyFontSize; +} + +int QmitkPatientTableHeaderView::CurrentHeaderLeft(const QModelIndex& currentIndex, const QModelIndex& headerIndex, int sectionIndex, int left) const +{ + QModelIndexList headerList = ListHeader(currentIndex); + if (!headerList.empty()) + { + int index = headerList.indexOf(headerIndex); + int firstHeaderSectionIndex = sectionIndex - index; + --index; + for (; index >= 0; --index) + { + left -= sectionSize(firstHeaderSectionIndex + index); + } + } + + return left; +} + +int QmitkPatientTableHeaderView::CurrentHeaderWidth(const QModelIndex& currentIndex, const QModelIndex& headerIndex, int sectionIndex) const +{ + QModelIndexList headerList = ListHeader(currentIndex); + if (headerList.empty()) + { + return sectionSize(sectionIndex); + } + + int width = 0; + int index = headerList.indexOf(headerIndex); + int firstHeaderSectionIndex = sectionIndex - index; + for (int i = 0; i < headerList.size(); ++i) + { + width += sectionSize(firstHeaderSectionIndex + i); + } + + return width; +} + +QModelIndexList QmitkPatientTableHeaderView::ParentIndexList(QModelIndex index) const +{ + QModelIndexList indexList; + while (index.isValid()) + { + indexList.push_front(index); + index = index.parent(); + } + + return indexList; +} + +QModelIndex QmitkPatientTableHeaderView::HeaderIndex(int sectionIndex) const +{ + if (nullptr != m_HeaderModel) + { + int currentHeaderIndex = -1; + for (int i = 0; i < m_HeaderModel->columnCount(); ++i) + { + QModelIndex modelIndex = FindHeader(m_HeaderModel->index(0, i), sectionIndex, currentHeaderIndex); + if (modelIndex.isValid()) + { + return modelIndex; + } + } + } + + return QModelIndex(); +} + +QModelIndex QmitkPatientTableHeaderView::FindHeader(const QModelIndex& currentIndex, int sectionIndex, int& currentHeaderIndex) const +{ + if (currentIndex.isValid()) + { + int childCount = currentIndex.model()->columnCount(currentIndex); + if (childCount > 0) + { + for (int i = 0; i < childCount; ++i) + { + QModelIndex modelIndex = FindHeader(currentIndex.child(0, i), sectionIndex, currentHeaderIndex); + if (modelIndex.isValid()) + { + return modelIndex; + } + } + } + else + { + ++currentHeaderIndex; + if (currentHeaderIndex == sectionIndex) + { + return currentIndex; + } + } + } + + return QModelIndex(); +} + +QModelIndexList QmitkPatientTableHeaderView::ListHeader(const QModelIndex& currentIndex) const +{ + QModelIndexList headerList; + if (currentIndex.isValid()) + { + int childCount = currentIndex.model()->columnCount(currentIndex); + if (childCount > 0) + { + for (int i = 0; i < childCount; ++i) + { + headerList += ListHeader(currentIndex.child(0, i)); + } + } + else + { + headerList.push_back(currentIndex); + } + } + return headerList; +} diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.cpp b/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.cpp new file mode 100644 index 0000000000..7b02bd0e59 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.cpp @@ -0,0 +1,182 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations UI module +#include "QmitkPatientTableInspector.h" +#include "QmitkPatientTableHeaderView.h" + +// mitk qt widgets module +#include "QmitkCustomVariants.h" +#include "QmitkEnums.h" + +// qt +#include +#include + +QmitkPatientTableInspector::QmitkPatientTableInspector(QWidget* parent/* =nullptr*/) +{ + m_Controls.setupUi(this); + + QmitkPatientTableHeaderView* patientTableHeaderView = new QmitkPatientTableHeaderView(m_Controls.tableView); + m_Controls.tableView->setHorizontalHeader(patientTableHeaderView); + m_Controls.tableView->horizontalHeader()->setHighlightSections(false); + m_Controls.tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); + m_Controls.tableView->verticalHeader()->setHighlightSections(false); + m_Controls.tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + m_Controls.tableView->setSelectionMode(QAbstractItemView::SingleSelection); + m_Controls.tableView->setSelectionBehavior(QAbstractItemView::SelectItems); + m_Controls.tableView->setContextMenuPolicy(Qt::CustomContextMenu); + + m_StorageModel = new QmitkPatientTableModel(m_Controls.tableView); + m_Controls.tableView->setModel(m_StorageModel); + m_ItemDelegate = new QmitkTableItemThumbnailDelegate(m_Controls.tableView); + //m_Controls.tableView->setItemDelegate(m_ItemDelegate); + + SetUpConnections(); +} + +QAbstractItemView* QmitkPatientTableInspector::GetView() +{ + return m_Controls.tableView; +} + +const QAbstractItemView* QmitkPatientTableInspector::GetView() const +{ + return m_Controls.tableView; +} + +void QmitkPatientTableInspector::SetSelectionMode(SelectionMode mode) +{ + m_Controls.tableView->setSelectionMode(mode); +} + +QmitkPatientTableInspector::SelectionMode QmitkPatientTableInspector::GetSelectionMode() const +{ + return m_Controls.tableView->selectionMode(); +} + +void QmitkPatientTableInspector::SetCaseID(const mitk::SemanticTypes::CaseID& caseID) +{ + m_StorageModel->SetCaseID(caseID); +} + +void QmitkPatientTableInspector::SetLesion(const mitk::SemanticTypes::Lesion& lesion) +{ + m_StorageModel->SetLesion(lesion); +} + +QItemSelectionModel* QmitkPatientTableInspector::GetSelectionModel() +{ + return m_Controls.tableView->selectionModel(); +} + +void QmitkPatientTableInspector::Initialize() +{ + if (m_DataStorage.IsExpired()) + { + return; + } + + auto dataStorage = m_DataStorage.Lock(); + + m_StorageModel->SetDataStorage(dataStorage); + m_StorageModel->SetNodePredicate(m_NodePredicate); + + m_Connector->SetView(m_Controls.tableView); +} + +void QmitkPatientTableInspector::OnModelUpdated() +{ + m_Controls.tableView->resizeRowsToContents(); + m_Controls.tableView->resizeColumnsToContents(); +} + +void QmitkPatientTableInspector::OnNodeButtonClicked(const QString& nodeType) +{ + m_StorageModel->SetNodeType(nodeType.toStdString()); +} + +void QmitkPatientTableInspector::OnDataNodeSelectionChanged(const QList& dataNodeSelection) +{ + if (m_StorageModel->GetLesion().UID.empty()) + { + return; + } + + // if lesion is set, reset to empty lesion to hide the "lesion presence background highlighting" in the model + m_StorageModel->SetLesion(mitk::SemanticTypes::Lesion()); + // need to explicitly set the data node selection + SetCurrentSelection(dataNodeSelection); +} + +void QmitkPatientTableInspector::OnItemDoubleClicked(const QModelIndex& itemIndex) +{ + if (itemIndex.isValid()) + { + QVariant qvariantDataNode = m_StorageModel->data(itemIndex, QmitkDataNodeRawPointerRole); + if (qvariantDataNode.canConvert()) + { + mitk::DataNode* dataNode = qvariantDataNode.value(); + emit DataNodeDoubleClicked(dataNode); + } + } +} + +void QmitkPatientTableInspector::SetUpConnections() +{ + connect(m_StorageModel, &QmitkPatientTableModel::ModelUpdated, this, &QmitkPatientTableInspector::OnModelUpdated); + connect(m_Controls.tableView, &QTableView::customContextMenuRequested, this, &QmitkPatientTableInspector::OnContextMenuRequested); + + QSignalMapper* nodeButtonSignalMapper = new QSignalMapper(this); + nodeButtonSignalMapper->setMapping(m_Controls.imageNodeButton, QString("Image")); + nodeButtonSignalMapper->setMapping(m_Controls.segmentationNodeButton, QString("Segmentation")); + connect(nodeButtonSignalMapper, static_cast(&QSignalMapper::mapped), this, &QmitkPatientTableInspector::OnNodeButtonClicked); + connect(m_Controls.imageNodeButton, &QRadioButton::clicked, nodeButtonSignalMapper, static_cast(&QSignalMapper::map)); + connect(m_Controls.segmentationNodeButton, &QRadioButton::clicked, nodeButtonSignalMapper, static_cast(&QSignalMapper::map)); + m_Controls.imageNodeButton->setChecked(true); + + connect(this, &QmitkPatientTableInspector::CurrentSelectionChanged, this, &QmitkPatientTableInspector::OnDataNodeSelectionChanged); + connect(m_Controls.tableView, &QTableView::doubleClicked, this, &QmitkPatientTableInspector::OnItemDoubleClicked); +} + +void QmitkPatientTableInspector::keyPressEvent(QKeyEvent* e) +{ + mitk::DataNode* dataNode = nullptr; + QModelIndex selectedIndex = m_Controls.tableView->currentIndex(); + if (selectedIndex.isValid()) + { + QVariant qvariantDataNode = m_StorageModel->data(selectedIndex, QmitkDataNodeRawPointerRole); + if (qvariantDataNode.canConvert()) + { + dataNode = qvariantDataNode.value(); + } + } + + if (nullptr == dataNode) + { + return; + } + + int key = e->key(); + switch (key) + { + case Qt::Key_Delete: + emit OnNodeRemoved(dataNode); + break; + default: + break; + } +} diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.ui b/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.ui new file mode 100644 index 0000000000..c953d1b58a --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkPatientTableInspector.ui @@ -0,0 +1,63 @@ + + + QmitkPatientTableInspector + + + + 0 + 0 + 350 + 300 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Semantic Relations content: + + + + + + + Show segmentation nodes + + + nodeButtonGroup + + + + + + + Show image nodes + + + nodeButtonGroup + + + + + + + + + + + + + + diff --git a/Modules/SemanticRelationsUI/src/QmitkPatientTableModel.cpp b/Modules/SemanticRelationsUI/src/QmitkPatientTableModel.cpp new file mode 100644 index 0000000000..4509f76a69 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkPatientTableModel.cpp @@ -0,0 +1,358 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations UI module +#include "QmitkPatientTableModel.h" +#include "QmitkPatientTableHeaderView.h" +#include "QmitkSemanticRelationsUIHelper.h" + +// semantic relations module +#include +#include +#include + +#include +#include + +// qt +#include + +// c++ +#include +#include + +QmitkPatientTableModel::QmitkPatientTableModel(QObject* parent /*= nullptr*/) + : QmitkAbstractSemanticRelationsStorageModel(parent) + , m_SelectedNodeType("Image") +{ + m_HeaderModel = new QStandardItemModel(this); +} + +QmitkPatientTableModel::~QmitkPatientTableModel() +{ + // nothing here +} + +QModelIndex QmitkPatientTableModel::index(int row, int column, const QModelIndex& parent/* = QModelIndex()*/) const +{ + if (hasIndex(row, column, parent)) + { + return createIndex(row, column); + } + + return QModelIndex(); +} + +QModelIndex QmitkPatientTableModel::parent(const QModelIndex& child) const +{ + return QModelIndex(); +} + +int QmitkPatientTableModel::rowCount(const QModelIndex& parent/* = QModelIndex()*/) const +{ + if (parent.isValid()) + { + return 0; + } + + return m_InformationTypes.size(); +} + +int QmitkPatientTableModel::columnCount(const QModelIndex& parent/* = QModelIndex()*/) const +{ + return m_ControlPoints.size(); +} + +QVariant QmitkPatientTableModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const +{ + // special role for returning the horizontal header + if (QmitkPatientTableHeaderView::HorizontalHeaderDataRole == role) + { + return QVariant::fromValue(m_HeaderModel); + } + + if (!index.isValid()) + { + return QVariant(); + } + + if (index.row() < 0 || index.row() >= static_cast(m_InformationTypes.size()) + || index.column() < 0 || index.column() >= static_cast(m_ControlPoints.size())) + { + return QVariant(); + } + + mitk::DataNode* dataNode = GetCurrentDataNode(index); + if (nullptr == dataNode) + { + return QVariant(); + } + + if (Qt::DecorationRole == role) + { + auto it = m_PixmapMap.find(dataNode); + if (it != m_PixmapMap.end()) + { + return QVariant(it->second); + } + } + + if (QmitkDataNodeRole == role) + { + return QVariant::fromValue(mitk::DataNode::Pointer(dataNode)); + } + + if (QmitkDataNodeRawPointerRole == role) + { + return QVariant::fromValue(dataNode); + } + + if (Qt::BackgroundColorRole == role) + { + auto it = m_LesionPresence.find(dataNode); + if (it != m_LesionPresence.end()) + { + return it->second ? QVariant(QColor(Qt::darkGreen)) : QVariant(QColor(Qt::transparent)); + } + + return QVariant(QColor(Qt::transparent)); + } + + return QVariant(); +} + +QVariant QmitkPatientTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (Qt::Vertical == orientation && Qt::DisplayRole == role) + { + if (m_InformationTypes.size() > section) + { + mitk::SemanticTypes::InformationType currentInformationType = m_InformationTypes.at(section); + return QVariant(QString::fromStdString(currentInformationType)); + } + } + + return QVariant(); +} + +Qt::ItemFlags QmitkPatientTableModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags; + mitk::DataNode* dataNode = GetCurrentDataNode(index); + if (nullptr != dataNode) + { + flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + + return flags; +} + +void QmitkPatientTableModel::SetNodeType(const std::string& nodeType) +{ + m_SelectedNodeType = nodeType; + UpdateModelData(); +} + +void QmitkPatientTableModel::NodePredicateChanged() +{ + UpdateModelData(); +} + +void QmitkPatientTableModel::NodeAdded(const mitk::DataNode* node) +{ + // does not react to data storage changes +} + +void QmitkPatientTableModel::NodeChanged(const mitk::DataNode* node) +{ + // nothing here, since the "'NodeChanged'-event is currently sent far too often + //UpdateModelData(); +} + +void QmitkPatientTableModel::NodeRemoved(const mitk::DataNode* node) +{ + // does not react to data storage changes +} + +void QmitkPatientTableModel::SetData() +{ + // get all control points of current case + m_ControlPoints = m_SemanticRelations->GetAllControlPointsOfCase(m_CaseID); + // sort the vector of control points for the timeline + std::sort(m_ControlPoints.begin(), m_ControlPoints.end()); + + // get all examination periods of current case + m_ExaminationPeriods = m_SemanticRelations->GetAllExaminationPeriodsOfCase(m_CaseID); + // sort the vector of examination periods for the timeline + mitk::SortExaminationPeriods(m_ExaminationPeriods, m_ControlPoints); + + // get all information types points of current case + m_InformationTypes = m_SemanticRelations->GetAllInformationTypesOfCase(m_CaseID); + + if ("Image" == m_SelectedNodeType) + { + m_CurrentDataNodes = m_SemanticRelations->GetAllImagesOfCase(m_CaseID); + } + else if ("Segmentation" == m_SelectedNodeType) + { + m_CurrentDataNodes = m_SemanticRelations->GetAllSegmentationsOfCase(m_CaseID); + } + + SetHeaderModel(); + SetPixmaps(); + SetLesionPresences(); +} + +void QmitkPatientTableModel::SetHeaderModel() +{ + m_HeaderModel->clear(); + QStandardItem* rootItem = new QStandardItem("Timeline"); + QList standardItems; + + for (const auto& examinationPeriod : m_ExaminationPeriods) + { + QStandardItem* examinationPeriodItem = new QStandardItem(QString::fromStdString(examinationPeriod.name)); + standardItems.push_back(examinationPeriodItem); + rootItem->appendColumn(standardItems); + standardItems.clear(); + + const auto& currentControlPoints = examinationPeriod.controlPointUIDs; + for (const auto& controlPointUID : currentControlPoints) + { + const auto& controlPoint = mitk::GetControlPointByUID(controlPointUID, m_ControlPoints); + QStandardItem* controlPointItem = new QStandardItem(QString::fromStdString(controlPoint.ToString())); + standardItems.push_back(controlPointItem); + examinationPeriodItem->appendColumn(standardItems); + standardItems.clear(); + } + } + + m_HeaderModel->setItem(0, 0, rootItem); +} + +void QmitkPatientTableModel::SetPixmaps() +{ + m_PixmapMap.clear(); + for (const auto& dataNode : m_CurrentDataNodes) + { + // set the pixmap for the current node + QPixmap pixmapFromImage = QmitkSemanticRelationsUIHelper::GetPixmapFromImageNode(dataNode); + SetPixmapOfNode(dataNode, &pixmapFromImage); + } +} + +void QmitkPatientTableModel::SetPixmapOfNode(const mitk::DataNode* dataNode, QPixmap* pixmapFromImage) +{ + if (nullptr == dataNode) + { + return; + } + + std::map::iterator iter = m_PixmapMap.find(dataNode); + if (iter != m_PixmapMap.end()) + { + // key already existing + if (nullptr != pixmapFromImage) + { + // overwrite already stored pixmap + iter->second = pixmapFromImage->scaled(120, 120, Qt::IgnoreAspectRatio); + } + else + { + // remove key if no pixmap is given + m_PixmapMap.erase(iter); + } + } + else + { + m_PixmapMap.insert(std::make_pair(dataNode, pixmapFromImage->scaled(120, 120, Qt::IgnoreAspectRatio))); + } +} + +void QmitkPatientTableModel::SetLesionPresences() +{ + m_LesionPresence.clear(); + if (!m_SemanticRelations->InstanceExists(m_CaseID, m_Lesion)) + { + return; + } + + for (const auto& dataNode : m_CurrentDataNodes) + { + if (!m_SemanticRelations->InstanceExists(dataNode)) + { + continue; + } + + try + { + // set the lesion presence for the current node + bool lesionPresence = m_SemanticRelations->IsLesionPresentOnDataNode(m_Lesion, dataNode); + SetLesionPresenceOfNode(dataNode, lesionPresence); + } + catch (const mitk::SemanticRelationException&) + { + continue; + } + } +} + +void QmitkPatientTableModel::SetLesionPresenceOfNode(const mitk::DataNode* dataNode, bool lesionPresence) +{ + std::map::iterator iter = m_LesionPresence.find(dataNode); + if (iter != m_LesionPresence.end()) + { + // key already existing, overwrite already stored bool value + iter->second = lesionPresence; + } + else + { + m_LesionPresence.insert(std::make_pair(dataNode, lesionPresence)); + } +} + +mitk::DataNode* QmitkPatientTableModel::GetCurrentDataNode(const QModelIndex& index) const +{ + if (!index.isValid()) + { + return nullptr; + } + + mitk::SemanticTypes::ControlPoint currentControlPoint = m_ControlPoints.at(index.column()); + mitk::SemanticTypes::InformationType currentInformationType = m_InformationTypes.at(index.row()); + try + { + std::vector filteredDataNodes; + if ("Image" == m_SelectedNodeType) + { + filteredDataNodes = m_SemanticRelations->GetAllSpecificImages(m_CaseID, currentControlPoint, currentInformationType); + } + else if ("Segmentation" == m_SelectedNodeType) + { + filteredDataNodes = m_SemanticRelations->GetAllSpecificSegmentations(m_CaseID, currentControlPoint, currentInformationType); + } + + if (filteredDataNodes.empty()) + { + return nullptr; + } + return filteredDataNodes.front(); + } + catch (const mitk::SemanticRelationException&) + { + return nullptr; + } +} diff --git a/Modules/SemanticRelationsUI/src/QmitkSemanticRelationsUIHelper.cpp b/Modules/SemanticRelationsUI/src/QmitkSemanticRelationsUIHelper.cpp new file mode 100644 index 0000000000..31ba46b7d2 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkSemanticRelationsUIHelper.cpp @@ -0,0 +1,95 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations ui module +#include + +// mitk core +#include +#include +#include +#include + +// vtk +#include + +QPixmap QmitkSemanticRelationsUIHelper::GetPixmapFromImageNode(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + return QPixmap(); + } + + const mitk::Image* image = static_cast(dataNode->GetData()); + if (nullptr == image || !image->IsInitialized()) + { + return QPixmap(); + } + + if (image->GetPixelType().GetNumberOfComponents() != 1) // for now only single component are allowed + { + return QPixmap(); + } + + mitk::PlaneGeometry::Pointer sagittalPlaneGeometry = mitk::PlaneGeometry::New(); + int sliceNumber = image->GetDimension(1) / 2; + sagittalPlaneGeometry->InitializeStandardPlane(image->GetGeometry(), mitk::PlaneGeometry::Sagittal, sliceNumber); + + mitk::ExtractSliceFilter::Pointer extractSliceFilter = mitk::ExtractSliceFilter::New(); + extractSliceFilter->SetInput(image); + extractSliceFilter->SetInterpolationMode(mitk::ExtractSliceFilter::RESLICE_CUBIC); + extractSliceFilter->SetResliceTransformByGeometry(image->GetGeometry()); + extractSliceFilter->SetWorldGeometry(sagittalPlaneGeometry); + extractSliceFilter->SetOutputDimensionality(2); + extractSliceFilter->SetVtkOutputRequest(true); + extractSliceFilter->Update(); + + /* + mitk::Vector3D spacing; + mitk::FillVector3D(spacing,1.0,1.0,1.0); + slice->GetGeometry()->SetSpacing(spacing); + // save image slice + mitk::IOUtil::SaveImage( slice, d->m_Path + fileName + ".png" ); + */ + + vtkImageData* imageData = extractSliceFilter->GetVtkOutput(); + + mitk::LevelWindow levelWindow; + dataNode->GetLevelWindow(levelWindow); + vtkSmartPointer lookupTable = vtkSmartPointer::New(); + lookupTable->SetRange(levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound()); + lookupTable->SetSaturationRange(0.0, 0.0); + lookupTable->SetValueRange(0.0, 1.0); + lookupTable->SetHueRange(0.0, 0.0); + lookupTable->SetRampToLinear(); + + vtkSmartPointer levelWindowFilter = vtkSmartPointer::New(); + levelWindowFilter->SetLookupTable(lookupTable); + levelWindowFilter->SetInputData(imageData); + levelWindowFilter->SetMinOpacity(0.0); + levelWindowFilter->SetMaxOpacity(1.0); + int dims[3]; + imageData->GetDimensions(dims); + double clippingBounds[] = { 0.0, static_cast(dims[0]), 0.0, static_cast(dims[1]) }; + levelWindowFilter->SetClippingBounds(clippingBounds); + levelWindowFilter->Update(); + imageData = levelWindowFilter->GetOutput(); + + QImage thumbnailImage(reinterpret_cast(imageData->GetScalarPointer()), dims[0], dims[1], QImage::Format_ARGB32); + + thumbnailImage = thumbnailImage.rgbSwapped().mirrored(false, true); + return QPixmap::fromImage(thumbnailImage); +} diff --git a/Modules/SemanticRelationsUI/src/QmitkTableItemThumbnailDelegate.cpp b/Modules/SemanticRelationsUI/src/QmitkTableItemThumbnailDelegate.cpp new file mode 100644 index 0000000000..f0ea252f5c --- /dev/null +++ b/Modules/SemanticRelationsUI/src/QmitkTableItemThumbnailDelegate.cpp @@ -0,0 +1,47 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations UI module +#include "QmitkTableItemThumbnailDelegate.h" + +// qt +#include +#include +#include + +QmitkTableItemThumbnailDelegate::QmitkTableItemThumbnailDelegate(QObject* parent /*= nullptr*/) + : QStyledItemDelegate(parent) +{ + // nothing here +} + +void QmitkTableItemThumbnailDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QPixmap pixmap = index.data(Qt::DecorationRole).value(); + QRect rect = option.rect; + QPoint point = option.rect.center() - pixmap.rect().center(); + painter->save(); + + if (option.state & QStyle::State_Selected) + { + painter->setBrush(option.palette.highlightedText()); + const QWidget* widget = option.widget; + QStyle* style = widget ? widget->style() : QApplication::style(); + style->drawControl(QStyle::CE_ItemViewItem, &option, painter, widget); + } + painter->drawPixmap(point, pixmap); + painter->restore(); +} diff --git a/Modules/SemanticRelationsUI/src/mitkModuleActivator.cpp b/Modules/SemanticRelationsUI/src/mitkModuleActivator.cpp new file mode 100644 index 0000000000..38148b4e78 --- /dev/null +++ b/Modules/SemanticRelationsUI/src/mitkModuleActivator.cpp @@ -0,0 +1,35 @@ +/*=================================================================== + +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 "mitkModuleActivator.h" + +// semantic relations ui module +#include "QmitkPatientTableInspector.h" + +// qt widgets module +#include "QmitkDataStorageInspectorProviderBase.h" + +void mitk::SemanticRelationsUIActivator::Load(us::ModuleContext* /*context*/) +{ + m_PatientTableInspector.reset(new QmitkDataStorageInspectorProviderBase("org.mitk.QmitkPatientTableInspector", + "Patient table inspector", "Displays the content of the semantic relations storage in a control-point - modality table.")); + } + +void mitk::SemanticRelationsUIActivator::Unload(us::ModuleContext* /*context*/) +{ +} + +US_EXPORT_MODULE_ACTIVATOR(mitk::SemanticRelationsUIActivator) diff --git a/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index e4fab2431d..b26a2ba9f3 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,114 +1,115 @@ # Plug-ins must be ordered according to their dependencies set(MITK_PLUGINS org.blueberry.core.runtime:ON org.blueberry.core.expressions:OFF org.blueberry.core.commands:OFF org.blueberry.core.jobs:OFF org.blueberry.ui.qt:OFF org.blueberry.ui.qt.help:ON org.blueberry.ui.qt.log:ON org.blueberry.ui.qt.objectinspector:OFF #org.blueberry.test:ON #org.blueberry.uitest:ON #Testing/org.blueberry.core.runtime.tests:ON #Testing/org.blueberry.osgi.tests:ON org.mitk.core.services:ON org.mitk.gui.common:ON org.mitk.planarfigure:ON org.mitk.core.ext:OFF org.mitk.core.jobs:OFF org.mitk.gui.qt.application:ON org.mitk.gui.qt.coreapplication:OFF org.mitk.gui.qt.ext:OFF org.mitk.gui.qt.extapplication:OFF org.mitk.gui.qt.common:ON org.mitk.gui.qt.stdmultiwidgeteditor:ON org.mitk.gui.qt.common.legacy:OFF org.mitk.gui.qt.cmdlinemodules:OFF org.mitk.gui.qt.diffusionimagingapp:OFF org.mitk.gui.qt.datamanager:ON org.mitk.gui.qt.datamanagerlight:OFF org.mitk.gui.qt.datastorageviewertest:OFF org.mitk.gui.qt.properties:ON org.mitk.gui.qt.basicimageprocessing:OFF org.mitk.gui.qt.dicom:OFF org.mitk.gui.qt.dicominspector:OFF org.mitk.gui.qt.diffusionimaging:OFF org.mitk.gui.qt.diffusionimaging.connectomics:OFF org.mitk.gui.qt.diffusionimaging.denoising:OFF org.mitk.gui.qt.diffusionimaging.fiberfox:OFF org.mitk.gui.qt.diffusionimaging.fiberprocessing:OFF org.mitk.gui.qt.diffusionimaging.ivim:OFF org.mitk.gui.qt.diffusionimaging.odfpeaks:OFF org.mitk.gui.qt.diffusionimaging.partialvolume:OFF org.mitk.gui.qt.diffusionimaging.preprocessing:OFF org.mitk.gui.qt.diffusionimaging.reconstruction:OFF org.mitk.gui.qt.diffusionimaging.registration:OFF org.mitk.gui.qt.diffusionimaging.tbss:OFF org.mitk.gui.qt.diffusionimaging.tractography:OFF org.mitk.gui.qt.diffusionimaging.python:OFF org.mitk.gui.qt.dosevisualization:OFF org.mitk.gui.qt.geometrytools:OFF org.mitk.gui.qt.igtexamples:OFF org.mitk.gui.qt.igttracking:OFF org.mitk.gui.qt.lasercontrol:OFF org.mitk.gui.qt.openigtlink:OFF org.mitk.gui.qt.imagecropper:OFF org.mitk.gui.qt.imagenavigator:ON org.mitk.gui.qt.viewnavigator:OFF org.mitk.gui.qt.materialeditor:OFF org.mitk.gui.qt.measurementtoolbox:OFF org.mitk.gui.qt.moviemaker:OFF org.mitk.gui.qt.pointsetinteraction:OFF org.mitk.gui.qt.pointsetinteractionmultispectrum:OFF org.mitk.gui.qt.python:OFF org.mitk.gui.qt.remeshing:OFF org.mitk.gui.qt.segmentation:OFF org.mitk.gui.qt.aicpregistration:OFF org.mitk.gui.qt.renderwindowmanager:OFF + org.mitk.gui.qt.semanticrelations:OFF org.mitk.gui.qt.toftutorial:OFF org.mitk.gui.qt.tofutil:OFF org.mitk.gui.qt.tubegraph:OFF org.mitk.gui.qt.ugvisualization:OFF org.mitk.gui.qt.photoacoustics.pausviewer:OFF org.mitk.gui.qt.photoacoustics.pausmotioncompensation:OFF org.mitk.gui.qt.photoacoustics.imageprocessing:OFF org.mitk.gui.qt.photoacoustics.simulation:OFF org.mitk.gui.qt.photoacoustics.spectralunmixing:OFF org.mitk.gui.qt.ultrasound:OFF org.mitk.gui.qt.volumevisualization:OFF org.mitk.gui.qt.eventrecorder:OFF org.mitk.gui.qt.xnat:OFF org.mitk.gui.qt.igt.app.echotrack:OFF org.mitk.gui.qt.spectrocamrecorder:OFF org.mitk.gui.qt.classificationsegmentation:OFF org.mitk.gui.qt.overlaymanager:OFF org.mitk.gui.qt.igt.app.hummelprotocolmeasurements:OFF org.mitk.gui.qt.multilabelsegmentation:OFF org.mitk.matchpoint.core.helper:OFF org.mitk.gui.qt.matchpoint.algorithm.browser:OFF org.mitk.gui.qt.matchpoint.algorithm.control:OFF org.mitk.gui.qt.matchpoint.algorithm.batch:OFF org.mitk.gui.qt.matchpoint.mapper:OFF org.mitk.gui.qt.matchpoint.framereg:OFF org.mitk.gui.qt.matchpoint.visualizer:OFF org.mitk.gui.qt.matchpoint.evaluator:OFF org.mitk.gui.qt.matchpoint.manipulator:OFF org.mitk.gui.qt.preprocessing.resampling:OFF org.mitk.gui.qt.radiomics:OFF org.mitk.gui.qt.cest:OFF org.mitk.gui.qt.fit.demo:OFF org.mitk.gui.qt.fit.inspector:OFF org.mitk.gui.qt.fit.genericfitting:OFF org.mitk.gui.qt.pharmacokinetics.mri:OFF org.mitk.gui.qt.pharmacokinetics.pet:OFF org.mitk.gui.qt.pharmacokinetics.simulation:OFF org.mitk.gui.qt.pharmacokinetics.curvedescriptor:OFF org.mitk.gui.qt.pharmacokinetics.concentration.mri:OFF ) diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp index cdba488356..9f89bb1c4e 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.cpp @@ -1,186 +1,185 @@ /*=================================================================== 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 "QmitkNodeSelectionDialog.h" #include #include #include QmitkNodeSelectionDialog::QmitkNodeSelectionDialog(QWidget* parent, QString title, QString hint) : QDialog(parent), m_NodePredicate(nullptr), m_SelectOnlyVisibleNodes(false), m_SelectedNodes(NodeList()), m_SelectionMode(QAbstractItemView::SingleSelection) { m_Controls.setupUi(this); auto providers = mitk::DataStorageInspectorGenerator::GetProviders(); auto visibleProviders = mitk::GetVisibleDataStorageInspectors(); auto favoriteID = mitk::GetFavoriteDataStorageInspector(); if (visibleProviders.empty()) { MITK_DEBUG << "No presets for visible node selection inspectors available. Use fallback (show all available inspectors)"; unsigned int order = 0; for (auto proIter : providers) { visibleProviders.insert(std::make_pair(order, proIter.first)); } } int favIndex = 0; bool favoriteFound = false; for (auto proIter : visibleProviders) { auto finding = providers.find(proIter.second); if (finding != providers.end()) { auto inspector = finding->second->CreateInspector(); QString name = QString::fromStdString(finding->second->GetInspectorDisplayName()); QString desc = QString::fromStdString(finding->second->GetInspectorDescription()); AddPanel(inspector, name, desc); favoriteFound = favoriteFound || proIter.second == favoriteID; if (!favoriteFound) { ++favIndex; } } else { MITK_DEBUG << "No provider registered for inspector that is defined as visible in the preferences. Illegal inspector ID: " << proIter.second; } } m_Controls.tabWidget->setCurrentIndex(favIndex); this->setWindowTitle(title); this->setToolTip(hint); m_Controls.hint->setText(hint); m_Controls.hint->setVisible(!hint.isEmpty()); connect(m_Controls.buttonBox, SIGNAL(accepted()), this, SLOT(OnOK())); connect(m_Controls.buttonBox, SIGNAL(rejected()), this, SLOT(OnCancel())); } void QmitkNodeSelectionDialog::SetDataStorage(mitk::DataStorage* dataStorage) { if (m_DataStorage != dataStorage) { m_DataStorage = dataStorage; if (!m_DataStorage.IsExpired()) { for (auto panel : m_Panels) { panel->SetDataStorage(dataStorage); } } } -}; +} void QmitkNodeSelectionDialog::SetNodePredicate(mitk::NodePredicateBase* nodePredicate) { if (m_NodePredicate != nodePredicate) { m_NodePredicate = nodePredicate; for (auto panel : m_Panels) { panel->SetNodePredicate(m_NodePredicate); } } -}; +} mitk::NodePredicateBase* QmitkNodeSelectionDialog::GetNodePredicate() const { return m_NodePredicate; } QmitkNodeSelectionDialog::NodeList QmitkNodeSelectionDialog::GetSelectedNodes() const { return m_SelectedNodes; -}; +} void QmitkNodeSelectionDialog::SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes) { if (m_SelectOnlyVisibleNodes != selectOnlyVisibleNodes) { m_SelectOnlyVisibleNodes = selectOnlyVisibleNodes; for (auto panel : m_Panels) { panel->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); } } -}; +} void QmitkNodeSelectionDialog::SetCurrentSelection(NodeList selectedNodes) { m_SelectedNodes = selectedNodes; for (auto panel : m_Panels) { panel->SetCurrentSelection(selectedNodes); } -}; +} void QmitkNodeSelectionDialog::OnSelectionChanged(NodeList selectedNodes) { SetCurrentSelection(selectedNodes); emit CurrentSelectionChanged(selectedNodes); -}; +} void QmitkNodeSelectionDialog::AddPanel(QmitkAbstractDataStorageInspector* view, QString name, QString desc) { view->setParent(this); view->SetSelectionMode(m_SelectionMode); auto tabPanel = new QWidget(); tabPanel->setObjectName(QString("tab_")+name); tabPanel->setToolTip(desc); m_Controls.tabWidget->insertTab(m_Controls.tabWidget->count(), tabPanel, name); auto verticalLayout = new QVBoxLayout(tabPanel); verticalLayout->setSpacing(0); verticalLayout->setContentsMargins(0, 0, 0, 0); verticalLayout->addWidget(view); m_Panels.push_back(view); connect(view, &QmitkAbstractDataStorageInspector::CurrentSelectionChanged, this, &QmitkNodeSelectionDialog::OnSelectionChanged); -}; +} void QmitkNodeSelectionDialog::OnOK() { this->accept(); -}; +} void QmitkNodeSelectionDialog::OnCancel() { this->reject(); -}; +} void QmitkNodeSelectionDialog::SetSelectionMode(SelectionMode mode) { m_SelectionMode = mode; for (auto panel : m_Panels) { panel->SetSelectionMode(mode); } -}; +} QmitkNodeSelectionDialog::SelectionMode QmitkNodeSelectionDialog::GetSelectionMode() const { return m_SelectionMode; } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h index 02f828bcc5..480655c53c 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkNodeSelectionDialog.h @@ -1,127 +1,125 @@ /*=================================================================== 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 QMITK_NODE_SELECTION_DIALOG_H #define QMITK_NODE_SELECTION_DIALOG_H - #include #include #include #include #include "org_mitk_gui_qt_common_Export.h" #include "ui_QmitkNodeSelectionDialog.h" #include /** * \class QmitkNodeSelectionDialog * \brief Widget that allows to show and edit the content of an mitk::IsoDoseLevel instance. */ class MITK_QT_COMMON QmitkNodeSelectionDialog : public QDialog { Q_OBJECT public: explicit QmitkNodeSelectionDialog(QWidget* parent = nullptr, QString caption = "", QString hint = ""); /* * @brief Sets the data storage that will be used /monitored by widget. * * @param dataStorage A pointer to the data storage to set. */ void SetDataStorage(mitk::DataStorage* dataStorage); /* * @brief Sets the node predicate and updates the widget, according to the node predicate. * * @param nodePredicate A pointer to node predicate. */ virtual void SetNodePredicate(mitk::NodePredicateBase* nodePredicate); mitk::NodePredicateBase* GetNodePredicate() const; using NodeList = QList; NodeList GetSelectedNodes() const; bool GetSelectOnlyVisibleNodes() const; using SelectionMode = QAbstractItemView::SelectionMode; void SetSelectionMode(SelectionMode mode); SelectionMode GetSelectionMode() const; Q_SIGNALS: /* * @brief A signal that will be emitted if the selected node has changed. * * @param nodes A list of data nodes that are newly selected. */ void CurrentSelectionChanged(NodeList nodes); public Q_SLOTS: /* * @brief Change the selection modus of the item view's selection model. * * If true, an incoming selection will be filtered (reduced) to only those nodes that are visible by the current view. * An outgoing selection can then at most contain the filtered nodes. * If false, the incoming non-visible selection will be stored and later added to the outgoing selection, * to include the original selection that could not be modified. * The part of the original selection, that is non-visible are the nodes that are not * * @param selectOnlyVisibleNodes The bool value to define the selection modus. */ void SetSelectOnlyVisibleNodes(bool selectOnlyVisibleNodes); /* * @brief Transform a list of data nodes into a model selection and set this as a new selection of the * selection model of the private member item view. * * The function filters the given list of nodes according to the 'm_SelectOnlyVisibleNodes' member variable. If * necessary, the non-visible nodes are stored. This is done if 'm_SelectOnlyVisibleNodes' is false: In this case * the selection may be filtered and only a subset of the selected nodes may be visible and therefore (de-)selectable * in the data storage viewer. By storing the non-visible nodes it is possible to send the new, modified selection * but also include the selected nodes from the original selection that could not be modified (see 'SetSelectOnlyVisibleNodes'). * * @param nodes A list of data nodes that should be newly selected. */ void SetCurrentSelection(NodeList selectedNodes); protected Q_SLOTS: void OnSelectionChanged(NodeList selectedNodes); void OnOK(); void OnCancel(); protected: void AddPanel(QmitkAbstractDataStorageInspector* view, QString name, QString desc); mitk::WeakPointer m_DataStorage; mitk::NodePredicateBase::Pointer m_NodePredicate; bool m_SelectOnlyVisibleNodes; NodeList m_SelectedNodes; SelectionMode m_SelectionMode; using PanelVectorType = std::vector; PanelVectorType m_Panels; Ui_QmitkNodeSelectionDialog m_Controls; }; #endif // QmitkNodeSelectionDialog_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/CMakeLists.txt b/Plugins/org.mitk.gui.qt.semanticrelations/CMakeLists.txt new file mode 100644 index 0000000000..595c24082e --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/CMakeLists.txt @@ -0,0 +1,7 @@ +project(org_mitk_gui_qt_semanticrelations) + +mitk_create_plugin( + EXPORT_DIRECTIVE MITK_GUI_SEMANTICRELATIONS_EXPORT + EXPORTED_INCLUDE_SUFFIXES src + MODULE_DEPENDS MitkPersistence MitkSemanticRelationsUI MitkRenderWindowManager MitkRegistrationOntology +) diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/files.cmake b/Plugins/org.mitk.gui.qt.semanticrelations/files.cmake new file mode 100644 index 0000000000..32a1308816 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/files.cmake @@ -0,0 +1,44 @@ +set(INTERNAL_CPP_FILES + mitkPluginActivator.cpp + QmitkDataNodeAddToSemanticRelationsAction.cpp + QmitkDataNodeInformationTypeAction.cpp + QmitkDataNodeRemoveFromSemanticRelationsAction.cpp + QmitkDataNodeUnlinkFromLesionAction.cpp + QmitkDataNodeOpenInAction.cpp + QmitkDataNodeSetControlPointAction.cpp + QmitkLesionInfoWidget.cpp + QmitkSemanticRelationsContextMenu.cpp + QmitkSemanticRelationsNodeSelectionDialog.cpp + QmitkSemanticRelationsView.cpp +) + +set(UI_FILES + src/internal/QmitkLesionInfoWidgetControls.ui + src/internal/QmitkSemanticRelationsControls.ui +) + +set(MOC_H_FILES + src/internal/mitkPluginActivator.h + src/internal/QmitkDataNodeAddToSemanticRelationsAction.h + src/internal/QmitkDataNodeInformationTypeAction.h + src/internal/QmitkDataNodeRemoveFromSemanticRelationsAction.h + src/internal/QmitkDataNodeUnlinkFromLesionAction.h + src/internal/QmitkDataNodeOpenInAction.h + src/internal/QmitkDataNodeSetControlPointAction.h + src/internal/QmitkLesionInfoWidget.h + src/internal/QmitkSemanticRelationsContextMenu.h + src/internal/QmitkSemanticRelationsNodeSelectionDialog.h + src/internal/QmitkSemanticRelationsView.h +) + +set(CACHED_RESOURCE_FILES + resources/SemanticRelations_48.png + plugin.xml +) + +set(QRC_FILES +) + +foreach(file ${INTERNAL_CPP_FILES}) + set(CPP_FILES ${CPP_FILES} src/internal/${file}) +endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.semanticrelations/manifest_headers.cmake new file mode 100644 index 0000000000..d22cc46799 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/manifest_headers.cmake @@ -0,0 +1,5 @@ +set(Plugin-Name "MITK Semantic relations") +set(Plugin-Version "0.1") +set(Plugin-Vendor "DKFZ") +set(Plugin-ContactAddress "http://www.mitk.org") +set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/plugin.xml b/Plugins/org.mitk.gui.qt.semanticrelations/plugin.xml new file mode 100644 index 0000000000..be0ea613f6 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/plugin.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/resources/SemanticRelations_48.png b/Plugins/org.mitk.gui.qt.semanticrelations/resources/SemanticRelations_48.png new file mode 100644 index 0000000000..fc0096e0ac Binary files /dev/null and b/Plugins/org.mitk.gui.qt.semanticrelations/resources/SemanticRelations_48.png differ diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeAddToSemanticRelationsAction.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeAddToSemanticRelationsAction.cpp new file mode 100644 index 0000000000..4131253d55 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeAddToSemanticRelationsAction.cpp @@ -0,0 +1,221 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkDataNodeAddToSemanticRelationsAction.h" + +// semantic relations module +#include +#include +#include + +// mitk gui common plugin +#include + +// berry +#include +#include + +// qt +#include + +// namespace that contains the concrete action +namespace AddToSemanticRelationsAction +{ + void Run(mitk::SemanticRelations* semanticRelations, const mitk::DataStorage* dataStorage, const mitk::DataNode* dataNode) + { + if (nullptr == dataNode) + { + return; + } + + if (mitk::NodePredicates::GetImagePredicate()->CheckNode(dataNode)) + { + AddImage(semanticRelations, dataNode); + } + else if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) + { + AddSegmentation(semanticRelations, dataStorage, dataNode); + } + } + + void AddImage(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* image) + { + if (nullptr == image) + { + return; + } + + try + { + // add the image to the semantic relations storage + semanticRelations->AddImage(image); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not add the selected image."); + msgBox.setText("The program wasn't able to correctly add the selected images.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + } + + void AddSegmentation(mitk::SemanticRelations* semanticRelations, const mitk::DataStorage* dataStorage, const mitk::DataNode* segmentation) + { + if (nullptr == segmentation) + { + return; + } + + mitk::BaseData* baseData = segmentation->GetData(); + if (nullptr == baseData) + { + return; + } + + // continue with valid segmentation data + // get parent node of the current segmentation node with the node predicate + mitk::DataStorage::SetOfObjects::ConstPointer parentNodes = dataStorage->GetSources(segmentation, mitk::NodePredicates::GetImagePredicate(), false); + + // check for already existing, identifying base properties + mitk::BaseProperty* caseIDProperty = baseData->GetProperty("DICOM.0010.0010"); + mitk::BaseProperty* nodeIDProperty = baseData->GetProperty("DICOM.0020.000E"); + if (nullptr == caseIDProperty || nullptr == nodeIDProperty) + { + MITK_INFO << "No DICOM tags for case and node identification found. Transferring DICOM tags from the parent node to the selected segmentation node."; + + mitk::SemanticTypes::CaseID caseID = mitk::GetCaseIDFromDataNode(parentNodes->front()); + mitk::SemanticTypes::ID nodeID = mitk::GetIDFromDataNode(parentNodes->front()); + // transfer DICOM tags to the segmentation node + mitk::StringProperty::Pointer caseIDTag = mitk::StringProperty::New(caseID); + baseData->SetProperty("DICOM.0010.0010", caseIDTag); // DICOM tag is "PatientName" + + // add UID to distinguish between different segmentations of the same parent node + mitk::StringProperty::Pointer nodeIDTag = mitk::StringProperty::New(nodeID + mitk::UIDGeneratorBoost::GenerateUID()); + baseData->SetProperty("DICOM.0020.000E", nodeIDTag); // DICOM tag is "SeriesInstanceUID" + } + + try + { + semanticRelations->AddSegmentation(segmentation, parentNodes->front()); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not add the selected segmentation."); + msgBox.setText("The program wasn't able to correctly add the selected segmentation.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + } +} + +QmitkDataNodeAddToSemanticRelationsAction::QmitkDataNodeAddToSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(workbenchPartSite) +{ + setText(tr("Add to semantic relations")); + InitializeAction(); +} + +QmitkDataNodeAddToSemanticRelationsAction::QmitkDataNodeAddToSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(berry::IWorkbenchPartSite::Pointer(workbenchPartSite)) +{ + setText(tr("Add to semantic relations")); + InitializeAction(); +} + +QmitkDataNodeAddToSemanticRelationsAction::~QmitkDataNodeAddToSemanticRelationsAction() +{ + // nothing here +} + +void QmitkDataNodeAddToSemanticRelationsAction::SetDataStorage(mitk::DataStorage* dataStorage) +{ + if (m_DataStorage != dataStorage) + { + // set the new data storage + m_DataStorage = dataStorage; + m_SemanticRelations = std::make_unique(m_DataStorage.Lock()); + } +} + +void QmitkDataNodeAddToSemanticRelationsAction::InitializeAction() +{ + connect(this, &QAction::triggered, this, &QmitkDataNodeAddToSemanticRelationsAction::OnActionTriggered); +} + +void QmitkDataNodeAddToSemanticRelationsAction::OnActionTriggered(bool checked) +{ + if (nullptr == m_SemanticRelations) + { + return; + } + + if (m_DataStorage.IsExpired()) + { + return; + } + + auto dataNode = GetSelectedNode(); + AddToSemanticRelationsAction::Run(m_SemanticRelations.get(), m_DataStorage.Lock(), dataNode); +} + +QList QmitkDataNodeAddToSemanticRelationsAction::GetSelectedNodes() +{ + QList selectedNodes; + if (m_WorkbenchPartSite.Expired()) + { + return selectedNodes; + } + + berry::ISelection::ConstPointer selection = m_WorkbenchPartSite.Lock()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); + + if (currentSelection.IsNull() || currentSelection->IsEmpty()) + { + return selectedNodes; + } + + selectedNodes = QList::fromStdList(currentSelection->GetSelectedDataNodes()); + return selectedNodes; +} + +mitk::DataNode::Pointer QmitkDataNodeAddToSemanticRelationsAction::GetSelectedNode() +{ + QList selectedNodes = GetSelectedNodes(); + if (selectedNodes.empty()) + { + return nullptr; + } + + // no batch action; should only be called with a single node + mitk::DataNode::Pointer dataNode = selectedNodes.front(); + if (nullptr == dataNode) + { + return nullptr; + } + + return dataNode; +} \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeAddToSemanticRelationsAction.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeAddToSemanticRelationsAction.h new file mode 100644 index 0000000000..34cfaca946 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeAddToSemanticRelationsAction.h @@ -0,0 +1,73 @@ +/*=================================================================== + +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 QMITKDATANODEADDTOSEMANTICRELATIONSACTION_H +#define QMITKDATANODEADDTOSEMANTICRELATIONSACTION_H + +#include + +// mitk core +#include +#include +#include + +// semantic relations module +#include + +// berry +#include + +// qt +#include + +namespace AddToSemanticRelationsAction +{ + MITK_GUI_SEMANTICRELATIONS_EXPORT void Run(mitk::SemanticRelations* semanticRelations, const mitk::DataStorage* dataStorage, const mitk::DataNode* image); + + void AddImage(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* image); + void AddSegmentation(mitk::SemanticRelations* semanticRelations, const mitk::DataStorage* dataStorage, const mitk::DataNode* segmentation); +} + +class MITK_GUI_SEMANTICRELATIONS_EXPORT QmitkDataNodeAddToSemanticRelationsAction : public QAction +{ + Q_OBJECT + +public: + + QmitkDataNodeAddToSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite); + QmitkDataNodeAddToSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite); + + virtual ~QmitkDataNodeAddToSemanticRelationsAction() override; + + void SetDataStorage(mitk::DataStorage* dataStorage); + +private Q_SLOTS: + + void OnActionTriggered(bool); + +protected: + + void InitializeAction(); + + QList GetSelectedNodes(); + mitk::DataNode::Pointer GetSelectedNode(); + + berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; + mitk::WeakPointer m_DataStorage; + std::unique_ptr m_SemanticRelations; +}; + +#endif // QMITKDATANODEADDTOSEMANTICRELATIONSACTION_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeInformationTypeAction.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeInformationTypeAction.cpp new file mode 100644 index 0000000000..1babe59b68 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeInformationTypeAction.cpp @@ -0,0 +1,142 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkDataNodeInformationTypeAction.h" + +// semantic relations module +#include + +// mitk gui common plugin +#include + +// berry +#include +#include + +// qt +#include + +QmitkDataNodeInformationTypeAction::QmitkDataNodeInformationTypeAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(workbenchPartSite) +{ + setText(tr("Set information type")); + m_Parent = parent; + InitializeAction(); +} + +QmitkDataNodeInformationTypeAction::QmitkDataNodeInformationTypeAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(berry::IWorkbenchPartSite::Pointer(workbenchPartSite)) +{ + setText(tr("Set information type")); + m_Parent = parent; + InitializeAction(); +} + +QmitkDataNodeInformationTypeAction::~QmitkDataNodeInformationTypeAction() +{ + // nothing here +} + +void QmitkDataNodeInformationTypeAction::SetDataStorage(mitk::DataStorage* dataStorage) +{ + if (m_DataStorage != dataStorage) + { + // set the new data storage + m_DataStorage = dataStorage; + m_SemanticRelations = std::make_unique(m_DataStorage.Lock()); + } +} + +void QmitkDataNodeInformationTypeAction::InitializeAction() +{ + connect(this, &QAction::triggered, this, &QmitkDataNodeInformationTypeAction::OnActionTriggered); +} + +void QmitkDataNodeInformationTypeAction::OnActionTriggered(bool checked) +{ + if (nullptr == m_SemanticRelations) + { + return; + } + + auto dataNode = GetSelectedNode(); + if (dataNode.IsNull()) + { + return; + } + + QInputDialog* inputDialog = new QInputDialog(m_Parent); + inputDialog->setWindowTitle(tr("Set information type of selected node")); + inputDialog->setLabelText(tr("Information type:")); + inputDialog->setTextValue(QString::fromStdString(m_SemanticRelations->GetInformationTypeOfImage(dataNode))); + inputDialog->setMinimumSize(250, 100); + + int dialogReturnValue = inputDialog->exec(); + if (QDialog::Rejected == dialogReturnValue) + { + return; + } + + try + { + m_SemanticRelations->SetInformationType(dataNode, inputDialog->textValue().toStdString()); + } + catch (const mitk::SemanticRelationException&) + { + return; + } +} + +QList QmitkDataNodeInformationTypeAction::GetSelectedNodes() +{ + QList selectedNodes; + if (m_WorkbenchPartSite.Expired()) + { + return selectedNodes; + } + + berry::ISelection::ConstPointer selection = m_WorkbenchPartSite.Lock()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); + + if (currentSelection.IsNull() || currentSelection->IsEmpty()) + { + return selectedNodes; + } + + selectedNodes = QList::fromStdList(currentSelection->GetSelectedDataNodes()); + return selectedNodes; +} + +mitk::DataNode::Pointer QmitkDataNodeInformationTypeAction::GetSelectedNode() +{ + QList selectedNodes = GetSelectedNodes(); + if (selectedNodes.empty()) + { + return nullptr; + } + + // no batch action; should only be called with a single node + mitk::DataNode::Pointer dataNode = selectedNodes.front(); + if (nullptr == dataNode) + { + return nullptr; + } + + return dataNode; +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeInformationTypeAction.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeInformationTypeAction.h new file mode 100644 index 0000000000..5985bf5ee3 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeInformationTypeAction.h @@ -0,0 +1,64 @@ +/*=================================================================== + +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 QMITKDATANODEINFORMATIONTYPEACTION_H +#define QMITKDATANODEINFORMATIONTYPEACTION_H + +// mitk core +#include +#include +#include + +// semantic relations module +#include + +// berry +#include + +// qt +#include + +class QmitkDataNodeInformationTypeAction : public QAction +{ + Q_OBJECT + +public: + + QmitkDataNodeInformationTypeAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite); + QmitkDataNodeInformationTypeAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite); + + virtual ~QmitkDataNodeInformationTypeAction() override; + + void SetDataStorage(mitk::DataStorage* dataStorage); + +private Q_SLOTS: + + void OnActionTriggered(bool); + +protected: + + void InitializeAction(); + + QList GetSelectedNodes(); + mitk::DataNode::Pointer GetSelectedNode(); + + QWidget* m_Parent; + berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; + mitk::WeakPointer m_DataStorage; + std::unique_ptr m_SemanticRelations; +}; + +#endif // QMITKDATANODEINFORMATIONTYPEACTION_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeOpenInAction.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeOpenInAction.cpp new file mode 100644 index 0000000000..0eb010e44f --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeOpenInAction.cpp @@ -0,0 +1,170 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkDataNodeOpenInAction.h" + +// mitk core +#include +#include + +// mitk gui common plugin +#include + +// qt +#include + +// berry +#include +#include + +// qt +#include + +QmitkDataNodeOpenInAction::QmitkDataNodeOpenInAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(workbenchPartSite) +{ + setText(tr("Open in")); + InitializeAction(); +} + +QmitkDataNodeOpenInAction::QmitkDataNodeOpenInAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(berry::IWorkbenchPartSite::Pointer(workbenchPartSite)) +{ + setText(tr("Open in")); + InitializeAction(); +} + +QmitkDataNodeOpenInAction::~QmitkDataNodeOpenInAction() +{ + // nothing here +} + +void QmitkDataNodeOpenInAction::SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer) +{ + if (m_ControlledRenderer != controlledRenderer) + { + // set the new set of controlled renderer + m_ControlledRenderer = controlledRenderer; + } +} + +void QmitkDataNodeOpenInAction::InitializeAction() +{ + setCheckable(true); + + setMenu(new QMenu); + connect(menu(), &QMenu::aboutToShow, this, &QmitkDataNodeOpenInAction::OnMenuAboutToShow); + + SetControlledRenderer(); +} + +void QmitkDataNodeOpenInAction::SetControlledRenderer() +{ + const mitk::RenderingManager::RenderWindowVector allRegisteredRenderWindows = mitk::RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); + mitk::BaseRenderer* baseRenderer = nullptr; + m_ControlledRenderer.clear(); + for (const auto &renderWindow : allRegisteredRenderWindows) + { + baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow); + if (nullptr != baseRenderer) + { + m_ControlledRenderer.push_back(baseRenderer); + } + } +} + +void QmitkDataNodeOpenInAction::OnMenuAboutToShow() +{ + menu()->clear(); + QAction* action; + + for (const auto& renderer : m_ControlledRenderer) + { + action = menu()->addAction(QString::fromStdString(renderer->GetName())); + connect(action, &QAction::triggered, this, &QmitkDataNodeOpenInAction::OnActionTriggered); + } +} + +void QmitkDataNodeOpenInAction::OnActionTriggered(bool checked) +{ + auto dataNode = GetSelectedNode(); + if (dataNode.IsNull()) + { + return; + } + + QAction* senderAction = qobject_cast(QObject::sender()); + if (nullptr == senderAction) + { + return; + } + + std::string selectedRenderer = senderAction->text().toStdString(); + mitk::BaseRenderer* renderer = mitk::BaseRenderer::GetByName(selectedRenderer); + if (nullptr == renderer) + { + return; + } + + auto image = dynamic_cast(dataNode->GetData()); + if (nullptr == image) + { + return; + } + + mitk::RenderingManager::GetInstance()->InitializeView(renderer->GetRenderWindow(), image->GetTimeGeometry()); +} + +QList QmitkDataNodeOpenInAction::GetSelectedNodes() +{ + QList selectedNodes; + if (m_WorkbenchPartSite.Expired()) + { + return selectedNodes; + } + + berry::ISelection::ConstPointer selection = m_WorkbenchPartSite.Lock()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); + + if (currentSelection.IsNull() || currentSelection->IsEmpty()) + { + return selectedNodes; + } + + selectedNodes = QList::fromStdList(currentSelection->GetSelectedDataNodes()); + return selectedNodes; +} + +mitk::DataNode::Pointer QmitkDataNodeOpenInAction::GetSelectedNode() +{ + QList selectedNodes = GetSelectedNodes(); + if (selectedNodes.empty()) + { + return nullptr; + } + + // no batch action; should only be called with a single node + mitk::DataNode::Pointer dataNode = selectedNodes.front(); + if (nullptr == dataNode) + { + return nullptr; + } + + return dataNode; +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeOpenInAction.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeOpenInAction.h new file mode 100644 index 0000000000..f9a9a2e204 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeOpenInAction.h @@ -0,0 +1,65 @@ +/*=================================================================== + +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 QMITKDATANODEOPENINACTION_H +#define QMITKDATANODEOPENINACTION_H + +// mitk core +#include +#include + +// mitk render window manager module +#include + +// berry +#include + +// qt +#include + +class QmitkDataNodeOpenInAction : public QAction +{ + Q_OBJECT + +public: + + QmitkDataNodeOpenInAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite); + QmitkDataNodeOpenInAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite); + + virtual ~QmitkDataNodeOpenInAction() override; + + void SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer); + +private Q_SLOTS: + + void OnMenuAboutToShow(); + void OnActionTriggered(bool); + +protected: + + void InitializeAction(); + void SetControlledRenderer(); + + QList GetSelectedNodes(); + mitk::DataNode::Pointer GetSelectedNode(); + + berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; + + RenderWindowLayerUtilities::RendererVector m_ControlledRenderer; + +}; + +#endif // QMITKDATANODEOPENINACTION_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeRemoveFromSemanticRelationsAction.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeRemoveFromSemanticRelationsAction.cpp new file mode 100644 index 0000000000..5233610e9f --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeRemoveFromSemanticRelationsAction.cpp @@ -0,0 +1,188 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkDataNodeRemoveFromSemanticRelationsAction.h" + +// semantic relations module +#include +#include +#include + +// mitk gui common plugin +#include + +// berry +#include +#include + +// qt +#include + +// namespace that contains the concrete action +namespace RemoveFromSemanticRelationsAction +{ + void Run(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* dataNode) + { + if (nullptr == dataNode) + { + return; + } + + if (mitk::NodePredicates::GetImagePredicate()->CheckNode(dataNode)) + { + RemoveImage(semanticRelations, dataNode); + } + else if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) + { + RemoveSegmentation(semanticRelations, dataNode); + } + } + + void RemoveImage(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* image) + { + if (nullptr == image) + { + return; + } + + try + { + // remove the image from the semantic relations storage + semanticRelations->RemoveImage(image); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not remove the selected image."); + msgBox.setText("The program wasn't able to correctly remove the selected image.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + } + + void RemoveSegmentation(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* segmentation) + { + if (nullptr == segmentation) + { + return; + } + + try + { + semanticRelations->RemoveSegmentation(segmentation); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not remove the selected segmentation."); + msgBox.setText("The program wasn't able to correctly remove the selected segmentation.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + } +} + +QmitkDataNodeRemoveFromSemanticRelationsAction::QmitkDataNodeRemoveFromSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(workbenchPartSite) +{ + setText(tr("Remove from semantic relations")); + InitializeAction(); +} + +QmitkDataNodeRemoveFromSemanticRelationsAction::QmitkDataNodeRemoveFromSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(berry::IWorkbenchPartSite::Pointer(workbenchPartSite)) +{ + setText(tr("Remove from semantic relations")); + InitializeAction(); +} + +QmitkDataNodeRemoveFromSemanticRelationsAction::~QmitkDataNodeRemoveFromSemanticRelationsAction() +{ + // nothing here +} + +void QmitkDataNodeRemoveFromSemanticRelationsAction::SetDataStorage(mitk::DataStorage* dataStorage) +{ + if (m_DataStorage != dataStorage) + { + // set the new data storage + m_DataStorage = dataStorage; + m_SemanticRelations = std::make_unique(m_DataStorage.Lock()); + } +} + +void QmitkDataNodeRemoveFromSemanticRelationsAction::InitializeAction() +{ + connect(this, &QAction::triggered, this, &QmitkDataNodeRemoveFromSemanticRelationsAction::OnActionTriggered); +} + +void QmitkDataNodeRemoveFromSemanticRelationsAction::OnActionTriggered(bool checked) +{ + if (nullptr == m_SemanticRelations) + { + return; + } + + auto dataNode = GetSelectedNode(); + RemoveFromSemanticRelationsAction::Run(m_SemanticRelations.get(), dataNode); +} + +QList QmitkDataNodeRemoveFromSemanticRelationsAction::GetSelectedNodes() +{ + QList selectedNodes; + if (m_WorkbenchPartSite.Expired()) + { + return selectedNodes; + } + + berry::ISelection::ConstPointer selection = m_WorkbenchPartSite.Lock()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); + + if (currentSelection.IsNull() || currentSelection->IsEmpty()) + { + return selectedNodes; + } + + selectedNodes = QList::fromStdList(currentSelection->GetSelectedDataNodes()); + return selectedNodes; +} + +mitk::DataNode::Pointer QmitkDataNodeRemoveFromSemanticRelationsAction::GetSelectedNode() +{ + QList selectedNodes = GetSelectedNodes(); + if (selectedNodes.empty()) + { + return nullptr; + } + + // no batch action; should only be called with a single node + mitk::DataNode::Pointer dataNode = selectedNodes.front(); + if (nullptr == dataNode) + { + return nullptr; + } + + return dataNode; +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeRemoveFromSemanticRelationsAction.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeRemoveFromSemanticRelationsAction.h new file mode 100644 index 0000000000..eaa4bd2977 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeRemoveFromSemanticRelationsAction.h @@ -0,0 +1,73 @@ +/*=================================================================== + +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 QMITKDATANODEREMOVEFROMSEMANTICRELATIONSACTION_H +#define QMITKDATANODEREMOVEFROMSEMANTICRELATIONSACTION_H + +#include + +// mitk core +#include +#include +#include + +// semantic relations module +#include + +// berry +#include + +// qt +#include + +namespace RemoveFromSemanticRelationsAction +{ + MITK_GUI_SEMANTICRELATIONS_EXPORT void Run(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* dataNode); + + void RemoveImage(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* image); + void RemoveSegmentation(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* segmentation); +} + +class MITK_GUI_SEMANTICRELATIONS_EXPORT QmitkDataNodeRemoveFromSemanticRelationsAction : public QAction +{ + Q_OBJECT + +public: + + QmitkDataNodeRemoveFromSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite); + QmitkDataNodeRemoveFromSemanticRelationsAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite); + + virtual ~QmitkDataNodeRemoveFromSemanticRelationsAction() override; + + void SetDataStorage(mitk::DataStorage* dataStorage); + +private Q_SLOTS: + + void OnActionTriggered(bool); + +protected: + + void InitializeAction(); + + QList GetSelectedNodes(); + mitk::DataNode::Pointer GetSelectedNode(); + + berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; + mitk::WeakPointer m_DataStorage; + std::unique_ptr m_SemanticRelations; +}; + +#endif // QMITKDATANODEREMOVEFROMSEMANTICRELATIONSACTION_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeSetControlPointAction.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeSetControlPointAction.cpp new file mode 100644 index 0000000000..a615cc6048 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeSetControlPointAction.cpp @@ -0,0 +1,153 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkDataNodeSetControlPointAction.h" + +// semantic relations module +#include +#include +#include + +// semantic relations UI module +#include "QmitkControlPointDialog.h" + +// mitk gui common plugin +#include + +// berry +#include +#include + +// qt +#include + +QmitkDataNodeSetControlPointAction::QmitkDataNodeSetControlPointAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(workbenchPartSite) +{ + setText(tr("Set control point")); + m_Parent = parent; + InitializeAction(); +} + +QmitkDataNodeSetControlPointAction::QmitkDataNodeSetControlPointAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(berry::IWorkbenchPartSite::Pointer(workbenchPartSite)) +{ + setText(tr("Set control point")); + m_Parent = parent; + InitializeAction(); +} + +QmitkDataNodeSetControlPointAction::~QmitkDataNodeSetControlPointAction() +{ + // nothing here +} + +void QmitkDataNodeSetControlPointAction::SetDataStorage(mitk::DataStorage* dataStorage) +{ + if (m_DataStorage != dataStorage) + { + // set the new data storage + m_DataStorage = dataStorage; + m_SemanticRelations = std::make_unique(m_DataStorage.Lock()); + } +} + +void QmitkDataNodeSetControlPointAction::InitializeAction() +{ + connect(this, &QAction::triggered, this, &QmitkDataNodeSetControlPointAction::OnActionTriggered); +} + +void QmitkDataNodeSetControlPointAction::OnActionTriggered(bool checked) +{ + if (nullptr == m_SemanticRelations) + { + return; + } + + auto dataNode = GetSelectedNode(); + if (dataNode.IsNull()) + { + return; + } + + QmitkControlPointDialog* inputDialog = new QmitkControlPointDialog(m_Parent); + inputDialog->setWindowTitle("Set control point"); + inputDialog->SetCurrentDate(m_SemanticRelations->GetControlPointOfData(dataNode)); + + int dialogReturnValue = inputDialog->exec(); + if (QDialog::Rejected == dialogReturnValue) + { + return; + } + + const QDate& userSelectedDate = inputDialog->GetCurrentDate(); + mitk::SemanticTypes::ControlPoint controlPoint; + controlPoint.UID = mitk::UIDGeneratorBoost::GenerateUID(); + controlPoint.date = boost::gregorian::date(userSelectedDate.year(), + userSelectedDate.month(), + userSelectedDate.day()); + + try + { + m_SemanticRelations->UnlinkDataFromControlPoint(dataNode); + m_SemanticRelations->SetControlPointOfData(dataNode, controlPoint); + } + catch (const mitk::SemanticRelationException&) + { + return; + } +} + +QList QmitkDataNodeSetControlPointAction::GetSelectedNodes() +{ + QList selectedNodes; + if (m_WorkbenchPartSite.Expired()) + { + return selectedNodes; + } + + berry::ISelection::ConstPointer selection = m_WorkbenchPartSite.Lock()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); + + if (currentSelection.IsNull() || currentSelection->IsEmpty()) + { + return selectedNodes; + } + + selectedNodes = QList::fromStdList(currentSelection->GetSelectedDataNodes()); + return selectedNodes; +} + +mitk::DataNode::Pointer QmitkDataNodeSetControlPointAction::GetSelectedNode() +{ + QList selectedNodes = GetSelectedNodes(); + if (selectedNodes.empty()) + { + return nullptr; + } + + // no batch action; should only be called with a single node + mitk::DataNode::Pointer dataNode = selectedNodes.front(); + if (nullptr == dataNode) + { + return nullptr; + } + + return dataNode; +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeSetControlPointAction.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeSetControlPointAction.h new file mode 100644 index 0000000000..a948081620 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeSetControlPointAction.h @@ -0,0 +1,64 @@ +/*=================================================================== + +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 QMITKDATANODECONTROLPOINTEACTION_H +#define QMITKDATANODECONTROLPOINTEACTION_H + +// mitk core +#include +#include +#include + +// semantic relations module +#include + +// berry +#include + +// qt +#include + +class QmitkDataNodeSetControlPointAction : public QAction +{ + Q_OBJECT + +public: + + QmitkDataNodeSetControlPointAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite); + QmitkDataNodeSetControlPointAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite); + + virtual ~QmitkDataNodeSetControlPointAction() override; + + void SetDataStorage(mitk::DataStorage* dataStorage); + +private Q_SLOTS: + + void OnActionTriggered(bool); + +protected: + + void InitializeAction(); + + QList GetSelectedNodes(); + mitk::DataNode::Pointer GetSelectedNode(); + + QWidget* m_Parent; + berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; + mitk::WeakPointer m_DataStorage; + std::unique_ptr m_SemanticRelations; +}; + +#endif // QMITKDATANODECONTROLPOINTEACTION_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeUnlinkFromLesionAction.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeUnlinkFromLesionAction.cpp new file mode 100644 index 0000000000..0e4078d242 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeUnlinkFromLesionAction.cpp @@ -0,0 +1,168 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkDataNodeUnlinkFromLesionAction.h" + +// semantic relations module +#include +#include +#include + +// mitk gui common plugin +#include + +// berry +#include +#include + +// qt +#include + +// namespace that contains the concrete action +namespace UnlinkFromLesionAction +{ + void Run(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* dataNode) + { + if (nullptr == dataNode) + { + return; + } + + if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) + { + UnlinkSegmentation(semanticRelations, dataNode); + } + else + { + QMessageBox msgBox; + msgBox.setWindowTitle("Could not unlink the selected data node."); + msgBox.setText("Please chose a valid segmentation to unlink from its represented lesion!"); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + } + + void UnlinkSegmentation(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* segmentation) + { + if (nullptr == segmentation) + { + return; + } + + try + { + semanticRelations->UnlinkSegmentationFromLesion(segmentation); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not unlink the selected segmentation."); + msgBox.setText("The program wasn't able to correctly unlink the selected segmentation.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + } +} + +QmitkDataNodeUnlinkFromLesionAction::QmitkDataNodeUnlinkFromLesionAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(workbenchPartSite) +{ + setText(tr("Unlink from lesion")); + InitializeAction(); +} + +QmitkDataNodeUnlinkFromLesionAction::QmitkDataNodeUnlinkFromLesionAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite) + : QAction(parent) + , m_WorkbenchPartSite(berry::IWorkbenchPartSite::Pointer(workbenchPartSite)) +{ + setText(tr("Unlink from lesion")); + InitializeAction(); +} + +QmitkDataNodeUnlinkFromLesionAction::~QmitkDataNodeUnlinkFromLesionAction() +{ + // nothing here +} + +void QmitkDataNodeUnlinkFromLesionAction::SetDataStorage(mitk::DataStorage* dataStorage) +{ + if (m_DataStorage != dataStorage) + { + // set the new data storage + m_DataStorage = dataStorage; + m_SemanticRelations = std::make_unique(m_DataStorage.Lock()); + } +} + +void QmitkDataNodeUnlinkFromLesionAction::InitializeAction() +{ + connect(this, &QAction::triggered, this, &QmitkDataNodeUnlinkFromLesionAction::OnActionTriggered); +} + +void QmitkDataNodeUnlinkFromLesionAction::OnActionTriggered(bool checked) +{ + if (nullptr == m_SemanticRelations) + { + return; + } + + auto dataNode = GetSelectedNode(); + UnlinkFromLesionAction::Run(m_SemanticRelations.get(), dataNode); +} + +QList QmitkDataNodeUnlinkFromLesionAction::GetSelectedNodes() +{ + QList selectedNodes; + if (m_WorkbenchPartSite.Expired()) + { + return selectedNodes; + } + + berry::ISelection::ConstPointer selection = m_WorkbenchPartSite.Lock()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); + mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); + + if (currentSelection.IsNull() || currentSelection->IsEmpty()) + { + return selectedNodes; + } + + selectedNodes = QList::fromStdList(currentSelection->GetSelectedDataNodes()); + return selectedNodes; +} + +mitk::DataNode::Pointer QmitkDataNodeUnlinkFromLesionAction::GetSelectedNode() +{ + QList selectedNodes = GetSelectedNodes(); + if (selectedNodes.empty()) + { + return nullptr; + } + + // no batch action; should only be called with a single node + mitk::DataNode::Pointer dataNode = selectedNodes.front(); + if (nullptr == dataNode) + { + return nullptr; + } + + return dataNode; +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeUnlinkFromLesionAction.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeUnlinkFromLesionAction.h new file mode 100644 index 0000000000..4702248a80 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkDataNodeUnlinkFromLesionAction.h @@ -0,0 +1,72 @@ +/*=================================================================== + +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 QMITKDATANODEUNLINKFROMLESIONACTION_H +#define QMITKDATANODEUNLINKFROMLESIONACTION_H + +#include + +// mitk core +#include +#include +#include + +// semantic relations module +#include + +// berry +#include + +// qt +#include + +namespace UnlinkFromLesionAction +{ + MITK_GUI_SEMANTICRELATIONS_EXPORT void Run(mitk::SemanticRelations* semanticRelations, const mitk::DataStorage* dataStorage, const mitk::DataNode* dataNode); + + void UnlinkSegmentation(mitk::SemanticRelations* semanticRelations, const mitk::DataNode* segmentation); +} + +class MITK_GUI_SEMANTICRELATIONS_EXPORT QmitkDataNodeUnlinkFromLesionAction : public QAction +{ + Q_OBJECT + +public: + + QmitkDataNodeUnlinkFromLesionAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchPartSite); + QmitkDataNodeUnlinkFromLesionAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchPartSite); + + virtual ~QmitkDataNodeUnlinkFromLesionAction() override; + + void SetDataStorage(mitk::DataStorage* dataStorage); + +private Q_SLOTS: + + void OnActionTriggered(bool); + +protected: + + void InitializeAction(); + + QList GetSelectedNodes(); + mitk::DataNode::Pointer GetSelectedNode(); + + berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; + mitk::WeakPointer m_DataStorage; + std::unique_ptr m_SemanticRelations; +}; + +#endif // QMITKDATANODEUNLINKFROMLESIONACTION_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp new file mode 100644 index 0000000000..3b20caedf4 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.cpp @@ -0,0 +1,483 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkLesionInfoWidget.h" +#include "QmitkSemanticRelationsNodeSelectionDialog.h" + +// semantic relations UI module +#include + +// semantic relations module +#include +#include +#include + +// registration ontology module +#include + +// qt +#include +#include +#include +#include +#include + +QmitkLesionInfoWidget::QmitkLesionInfoWidget(mitk::DataStorage* dataStorage, QWidget* parent /*= nullptr*/) + : QWidget(parent) + , m_DataStorage(dataStorage) + , m_SemanticRelations(std::make_unique(dataStorage)) +{ + Initialize(); +} + +QmitkLesionInfoWidget::~QmitkLesionInfoWidget() +{ + // nothing here +} + +void QmitkLesionInfoWidget::Initialize() +{ + m_Controls.setupUi(this); + + m_Controls.lesionTreeView->setAlternatingRowColors(true); + m_Controls.lesionTreeView->setSelectionMode(QAbstractItemView::SingleSelection); + m_Controls.lesionTreeView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_Controls.lesionTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + + m_StorageModel = new QmitkLesionTreeModel(this); + if (m_DataStorage.IsExpired()) + { + return; + } + + auto dataStorage = m_DataStorage.Lock(); + m_StorageModel->SetDataStorage(dataStorage); + m_Controls.lesionTreeView->setModel(m_StorageModel); + + SetUpConnections(); +} + +void QmitkLesionInfoWidget::SetUpConnections() +{ + connect(m_StorageModel, &QmitkLesionTreeModel::ModelUpdated, this, &QmitkLesionInfoWidget::OnModelUpdated); + + // connect buttons to modify semantic relations + connect(m_Controls.addLesionPushButton, &QPushButton::clicked, this, &QmitkLesionInfoWidget::OnAddLesionButtonClicked); + + // connect each list widget with a custom slots + connect(m_Controls.lesionTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QmitkLesionInfoWidget::OnSelectionChanged); + + // connect context menu entries + connect(m_Controls.lesionTreeView, &QTreeView::customContextMenuRequested, this, &QmitkLesionInfoWidget::OnLesionListContextMenuRequested); +} + +void QmitkLesionInfoWidget::SetCaseID(const mitk::SemanticTypes::CaseID& caseID) +{ + m_CaseID = caseID; + m_StorageModel->SetCaseID(caseID); +} + +void QmitkLesionInfoWidget::SetDataNodeSelection(const QList& dataNodeSelection) +{ + m_StorageModel->SetDataNodeSelection(dataNodeSelection); +} + +////////////////////////////////////////////////////////////////////////// +// Implementation of the QT_SLOTS +////////////////////////////////////////////////////////////////////////// +void QmitkLesionInfoWidget::OnModelUpdated() +{ + m_Controls.lesionTreeView->expandAll(); + int columns = m_Controls.lesionTreeView->model()->columnCount(); + for (int i = 0; i < columns; ++i) + { + m_Controls.lesionTreeView->resizeColumnToContents(i); + } +} + +void QmitkLesionInfoWidget::OnAddLesionButtonClicked() +{ + if (m_CaseID.empty()) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No case ID set."); + msgBox.setText("In order to add a lesion, please specify the current case / patient."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + mitk::SemanticTypes::Lesion newLesion = mitk::GenerateNewLesion(); + try + { + m_SemanticRelations->AddLesion(m_CaseID, newLesion); + } + catch (mitk::SemanticRelationException& e) + { + MITK_INFO << "Could not add a new lesion. " << e; + } +} + +void QmitkLesionInfoWidget::OnSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ + if (nullptr == m_SemanticRelations && current.isValid()) + { + return; + } + + // only the UID is needed to identify a representing lesion + QVariant data = m_StorageModel->data(current, Qt::UserRole); + if (data.canConvert()) + { + m_CurrentLesion = data.value()->GetData().GetLesion(); + } + else + { + return; + } + + if (false == m_SemanticRelations->InstanceExists(m_CaseID, m_CurrentLesion)) + { + // no UID of a existing lesion found; cannot create a lesion + return; + } + + // if selected data nodes are set, reset to empty list to + // the "selected data nodes presence background highlighting" in the model + if (!m_StorageModel->GetSelectedDataNodes().isEmpty()) + { + m_StorageModel->SetDataNodeSelection(QList()); + } + + emit LesionSelectionChanged(m_CurrentLesion); +} + +void QmitkLesionInfoWidget::OnLesionListContextMenuRequested(const QPoint& pos) +{ + QModelIndex index = m_Controls.lesionTreeView->indexAt(pos); + if (!index.isValid()) + { + // no item clicked; cannot retrieve the current lesion + return; + } + + QVariant data = m_StorageModel->data(index, Qt::UserRole); + mitk::SemanticTypes::Lesion selectedLesion; + if (data.canConvert()) + { + selectedLesion = data.value()->GetData().GetLesion(); + } + else + { + return; + } + + QMenu* menu = new QMenu(m_Controls.lesionTreeView); + + QAction* linkToSegmentation = new QAction("Link to segmentation", this); + linkToSegmentation->setEnabled(true); + connect(linkToSegmentation, &QAction::triggered, [this, selectedLesion] { OnLinkToSegmentation(selectedLesion); }); + menu->addAction(linkToSegmentation); + + QAction* setLesionName = new QAction("Set lesion name", this); + setLesionName->setEnabled(true); + connect(setLesionName, &QAction::triggered, [this, selectedLesion] { OnSetLesionName(selectedLesion); }); + menu->addAction(setLesionName); + + QAction* setLesionClass = new QAction("Set lesion class", this); + setLesionClass->setEnabled(true); + connect(setLesionClass, &QAction::triggered, [this, selectedLesion] { OnSetLesionClass(selectedLesion); }); + menu->addAction(setLesionClass); + + QAction* propageLesionToImage = new QAction("Propagate lesion to image", this); + propageLesionToImage->setEnabled(true); + connect(propageLesionToImage, &QAction::triggered, [this, selectedLesion] { OnPropagateLesion(selectedLesion); }); + menu->addAction(propageLesionToImage); + + QAction* removeLesion = new QAction("Remove lesion", this); + removeLesion->setEnabled(true); + connect(removeLesion, &QAction::triggered, [this, selectedLesion] { OnRemoveLesion(selectedLesion); }); + menu->addAction(removeLesion); + + menu->popup(QCursor::pos()); +} + +void QmitkLesionInfoWidget::OnLinkToSegmentation(mitk::SemanticTypes::Lesion selectedLesion) +{ + if (m_CaseID.empty()) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No case ID set."); + msgBox.setText("In order to link a lesion to a segmentation, please specify the current case / patient."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + if (m_DataStorage.IsExpired()) + { + return; + } + + auto dataStorage = m_DataStorage.Lock(); + + QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select segmentation to link to the selected lesion.", ""); + dialog->SetDataStorage(dataStorage); + dialog->setWindowTitle("Select segmentation node"); + dialog->SetNodePredicate(mitk::NodePredicates::GetSegmentationPredicate()); + dialog->SetSelectOnlyVisibleNodes(true); + dialog->SetSelectionMode(QAbstractItemView::SingleSelection); + dialog->SetCaseID(m_CaseID); + + int dialogReturnValue = dialog->exec(); + if (QDialog::Rejected == dialogReturnValue) + { + return; + } + + auto nodes = dialog->GetSelectedNodes(); + mitk::DataNode::Pointer selectedDataNode = nullptr; + if (!nodes.isEmpty()) + { + // only single selection allowed + selectedDataNode = nodes.front(); + } + + if (nullptr == selectedDataNode + || false == mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(selectedDataNode)) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No valid segmentation node selected."); + msgBox.setText("In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + mitk::BaseData* baseData = selectedDataNode->GetData(); + if (nullptr == baseData) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No valid base data."); + msgBox.setText("In order to link the selected lesion to a segmentation, please specify a valid segmentation node."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + try + { + m_SemanticRelations->LinkSegmentationToLesion(selectedDataNode, selectedLesion); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not link the selected lesion."); + msgBox.setText("The program wasn't able to correctly link the selected lesion with the selected segmentation.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + } +} + +void QmitkLesionInfoWidget::OnSetLesionName(mitk::SemanticTypes::Lesion selectedLesion) +{ + if (m_CaseID.empty()) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No case ID set."); + msgBox.setText("In order to set a lesion name, please specify the current case / patient."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + // use the lesion information to set the input text for the dialog + QmitkLesionTextDialog* inputDialog = new QmitkLesionTextDialog(this); + inputDialog->setWindowTitle("Set lesion name"); + inputDialog->SetLineEditText(selectedLesion.name); + + int dialogReturnValue = inputDialog->exec(); + if (QDialog::Rejected == dialogReturnValue) + { + return; + } + + std::string newLesionName = inputDialog->GetLineEditText().toStdString(); + + selectedLesion.name = newLesionName; + m_SemanticRelations->OverwriteLesion(m_CaseID, selectedLesion); +} + +void QmitkLesionInfoWidget::OnSetLesionClass(mitk::SemanticTypes::Lesion selectedLesion) +{ + if (m_CaseID.empty()) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No case ID set."); + msgBox.setText("In order to set a lesion class, please specify the current case / patient."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + // use the lesion information to set the input text for the dialog + QmitkLesionTextDialog* inputDialog = new QmitkLesionTextDialog(this); + inputDialog->setWindowTitle("Set lesion class"); + inputDialog->SetLineEditText(selectedLesion.lesionClass.classType); + + // prepare the completer for the dialogs input text field + mitk::LesionClassVector allLesionClasses = m_SemanticRelations->GetAllLesionClassesOfCase(m_CaseID); + + QStringList wordList; + for (const auto& lesionClass : allLesionClasses) + { + wordList << QString::fromStdString(lesionClass.classType); + } + QCompleter* completer = new QCompleter(wordList, this); + completer->setCaseSensitivity(Qt::CaseInsensitive); + inputDialog->GetLineEdit()->setCompleter(completer); + + int dialogReturnValue = inputDialog->exec(); + if (QDialog::Rejected == dialogReturnValue) + { + return; + } + + // retrieve the new input lesion class type and check for an already existing lesion class types + std::string newLesionClassType = inputDialog->GetLineEditText().toStdString(); + mitk::SemanticTypes::LesionClass existingLesionClass = mitk::FindExistingLesionClass(newLesionClassType, allLesionClasses); + if (existingLesionClass.UID.empty()) + { + // could not find lesion class information for the new lesion class type + // create a new lesion class for the selected lesion + existingLesionClass = mitk::GenerateNewLesionClass(newLesionClassType); + } + + selectedLesion.lesionClass = existingLesionClass; + m_SemanticRelations->OverwriteLesion(m_CaseID, selectedLesion); +} + +void QmitkLesionInfoWidget::OnPropagateLesion(mitk::SemanticTypes::Lesion selectedLesion) +{ + if (m_CaseID.empty()) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No case ID set."); + msgBox.setText("In order to propagate a lesion to an image, please specify the current case / patient."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + if (m_DataStorage.IsExpired()) + { + return; + } + + auto dataStorage = m_DataStorage.Lock(); + + QmitkSemanticRelationsNodeSelectionDialog* dialog = new QmitkSemanticRelationsNodeSelectionDialog(this, "Select data node to propagate the selected lesion.", ""); + dialog->SetDataStorage(dataStorage); + dialog->setWindowTitle("Select image node"); + dialog->SetNodePredicate(mitk::NodePredicates::GetImagePredicate()); + dialog->SetSelectOnlyVisibleNodes(true); + dialog->SetSelectionMode(QAbstractItemView::SingleSelection); + dialog->SetCaseID(m_CaseID); + dialog->SetLesion(selectedLesion); + + int dialogReturnValue = dialog->exec(); + if (QDialog::Rejected == dialogReturnValue) + { + return; + } + + auto nodes = dialog->GetSelectedNodes(); + mitk::DataNode::Pointer selectedDataNode = nullptr; + if (!nodes.isEmpty()) + { + // only single selection allowed + selectedDataNode = nodes.front(); + } + + if (nullptr == selectedDataNode + || false == mitk::NodePredicates::GetImagePredicate()->CheckNode(selectedDataNode)) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No valid image node selected."); + msgBox.setText("In order to propagate the selected lesion to an image, please specify a valid image node."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + mitk::BaseData* baseData = selectedDataNode->GetData(); + if (nullptr == baseData) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No valid base data."); + msgBox.setText("In order to propagate the selected lesion to an image, please specify a valid image node."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + try + { + auto allSegmentationsOfLesion = m_SemanticRelations->GetAllSegmentationsOfLesion(m_CaseID, selectedLesion); + mitk::FindClosestSegmentationMask(); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not propagate the selected lesion."); + msgBox.setText("The program wasn't able to correctly propagate the selected lesion to the selected image.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + } +} + +void QmitkLesionInfoWidget::OnRemoveLesion(mitk::SemanticTypes::Lesion selectedLesion) +{ + if (m_CaseID.empty()) + { + QMessageBox msgBox; + msgBox.setWindowTitle("No case ID set."); + msgBox.setText("In order to remove a lesion, please specify the current case / patient."); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + return; + } + + try + { + m_SemanticRelations->RemoveLesion(m_CaseID, selectedLesion); + } + catch (const mitk::SemanticRelationException& e) + { + std::stringstream exceptionMessage; exceptionMessage << e; + QMessageBox msgBox; + msgBox.setWindowTitle("Could not remove the selected lesion."); + msgBox.setText("The program wasn't able to correctly remove the selected lesion from the semantic relations model.\n" + "Reason:\n" + QString::fromStdString(exceptionMessage.str())); + msgBox.setIcon(QMessageBox::Warning); + msgBox.exec(); + } +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.h new file mode 100644 index 0000000000..a26a97569b --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidget.h @@ -0,0 +1,102 @@ +/*=================================================================== + +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 QMITKLESIONINFOWIDGET_H +#define QMITKLESIONINFOWIDGET_H + +// semantic relations plugin +#include + +// semantic relations UI module +#include + +// semantic relations module +#include + +// mitk +#include + +// qt +#include + +/* +* @brief The QmitkLesionInfoWidget is a widget that shows and modifies the currently available lesion data of the semantic relations model. +* +* The widget provides a dialogs to add nodes from the data storage to the semantic relations model. +* It provides functionality to create new lesions and link them with segmentation nodes. +* +* The QmitkLesionInfoWidget provides three QListWidgets, that show the lesion data and the referenced segmentation data, as +* well as the connected image data, depending on the selected lesion. +*/ +class QmitkLesionInfoWidget : public QWidget +{ + Q_OBJECT + +public: + + static const QBrush DEFAULT_BACKGROUND_COLOR; + static const QBrush SELECTED_BACKGROUND_COLOR; + static const QBrush CONNECTED_BACKGROUND_COLOR; + + QmitkLesionInfoWidget::QmitkLesionInfoWidget(mitk::DataStorage* dataStorage, QWidget* parent = nullptr); + ~QmitkLesionInfoWidget(); + + void SetCaseID(const mitk::SemanticTypes::CaseID& caseID); + + void SetDataNodeSelection(const QList& dataNodeSelection); + + const mitk::SemanticTypes::Lesion& GetSelectedLesion() const { return m_CurrentLesion; } + +Q_SIGNALS: + + void LesionSelectionChanged(const mitk::SemanticTypes::Lesion&); + +private Q_SLOTS: + + void OnModelUpdated(); + /* + * @brief Generates a new, empty lesion to add to the semantic relations model for the current case ID. + */ + void OnAddLesionButtonClicked(); + + // slots for the mouse click events of tree view's selection model + void OnSelectionChanged(const QModelIndex& current, const QModelIndex& previous); + + void OnLesionListContextMenuRequested(const QPoint&); + + // slots for the context menu actions of the lesion list widget + void OnLinkToSegmentation(mitk::SemanticTypes::Lesion); + void OnSetLesionName(mitk::SemanticTypes::Lesion); + void OnSetLesionClass(mitk::SemanticTypes::Lesion); + void OnPropagateLesion(mitk::SemanticTypes::Lesion); + void OnRemoveLesion(mitk::SemanticTypes::Lesion); + +private: + + void Initialize(); + void SetUpConnections(); + + Ui::QmitkLesionInfoWidgetControls m_Controls; + QmitkLesionTreeModel* m_StorageModel; + + mitk::WeakPointer m_DataStorage; + std::unique_ptr m_SemanticRelations; + mitk::SemanticTypes::CaseID m_CaseID; + + mitk::SemanticTypes::Lesion m_CurrentLesion; +}; + +#endif // QMITKLESIONINFOWIDGET_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidgetControls.ui b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidgetControls.ui new file mode 100644 index 0000000000..35ace22701 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkLesionInfoWidgetControls.ui @@ -0,0 +1,50 @@ + + + QmitkLesionInfoWidgetControls + + + true + + + + 0 + 0 + 350 + 300 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Available lesions: + + + + + + + + + + Add new lesion + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsContextMenu.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsContextMenu.cpp new file mode 100644 index 0000000000..c92093ad3b --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsContextMenu.cpp @@ -0,0 +1,78 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkSemanticRelationsContextMenu.h" + +QmitkSemanticRelationsContextMenu::QmitkSemanticRelationsContextMenu(berry::IWorkbenchPartSite::Pointer workbenchPartSite, QWidget* parent) + : QMenu(parent) +{ + m_WorkbenchPartSite = workbenchPartSite; + m_Parent = parent; + + InitDefaultActions(); +} + +QmitkSemanticRelationsContextMenu::~QmitkSemanticRelationsContextMenu() +{ + // nothing here +} + +void QmitkSemanticRelationsContextMenu::SetDataStorage(mitk::DataStorage* dataStorage) +{ + if (m_DataStorage != dataStorage) + { + // set the new data storage - also for all actions + m_DataStorage = dataStorage; + m_ControlPointAction->SetDataStorage(m_DataStorage.Lock()); + m_InformationTypeAction->SetDataStorage(m_DataStorage.Lock()); + m_RemoveFromSemanticRelationsAction->SetDataStorage(m_DataStorage.Lock()); + m_UnlinkFromLesionAction->SetDataStorage(m_DataStorage.Lock()); + } +} + +void QmitkSemanticRelationsContextMenu::SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer) +{ + if (m_ControlledRenderer != controlledRenderer) + { + // set the new set of controlled renderer + m_ControlledRenderer = controlledRenderer; + m_OpenInAction->SetControlledRenderer(m_ControlledRenderer); + } +} + +void QmitkSemanticRelationsContextMenu::OnContextMenuRequested(const QPoint& /*pos*/) +{ + popup(QCursor::pos()); +} + +void QmitkSemanticRelationsContextMenu::InitDefaultActions() +{ + m_ControlPointAction = new QmitkDataNodeSetControlPointAction(m_Parent, m_WorkbenchPartSite.Lock()); + addAction(m_ControlPointAction); + + m_InformationTypeAction = new QmitkDataNodeInformationTypeAction(m_Parent, m_WorkbenchPartSite.Lock()); + addAction(m_InformationTypeAction); + + m_UnlinkFromLesionAction = new QmitkDataNodeUnlinkFromLesionAction(m_Parent, m_WorkbenchPartSite.Lock()); + addAction(m_UnlinkFromLesionAction); + + m_RemoveFromSemanticRelationsAction = new QmitkDataNodeRemoveFromSemanticRelationsAction(m_Parent, m_WorkbenchPartSite.Lock()); + addAction(m_RemoveFromSemanticRelationsAction); + + m_OpenInAction = new QmitkDataNodeOpenInAction(m_Parent, m_WorkbenchPartSite.Lock()); + addAction(m_OpenInAction); +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsContextMenu.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsContextMenu.h new file mode 100644 index 0000000000..fb94cef562 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsContextMenu.h @@ -0,0 +1,73 @@ +/*=================================================================== + +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 QMITKSEMANTICRELATIONSCONTEXTMENU_H +#define QMITKSEMANTICRELATIONSCONTEXTMENU_H + +// semantic relations plugin +#include "QmitkDataNodeSetControlPointAction.h" +#include "QmitkDataNodeInformationTypeAction.h" +#include "QmitkDataNodeUnlinkFromLesionAction.h" +#include "QmitkDataNodeRemoveFromSemanticRelationsAction.h" +#include "QmitkDataNodeOpenInAction.h" + +// mitk core +#include +#include + +// mitk render window manager module +#include + +// blueberry ui qt plugin +#include + +//qt +#include + +class QmitkSemanticRelationsContextMenu : public QMenu +{ + Q_OBJECT + +public: + + QmitkSemanticRelationsContextMenu(berry::IWorkbenchPartSite::Pointer workbenchPartSite, QWidget* parent = nullptr); + virtual ~QmitkSemanticRelationsContextMenu() override; + + void SetDataStorage(mitk::DataStorage* dataStorage); + void SetControlledRenderer(RenderWindowLayerUtilities::RendererVector controlledRenderer); + +public Q_SLOTS: + + void OnContextMenuRequested(const QPoint&); + +private: + + void InitDefaultActions(); + + QWidget* m_Parent; + berry::IWorkbenchPartSite::WeakPtr m_WorkbenchPartSite; + mitk::WeakPointer m_DataStorage; + RenderWindowLayerUtilities::RendererVector m_ControlledRenderer; + + QmitkDataNodeSetControlPointAction* m_ControlPointAction; + QmitkDataNodeInformationTypeAction* m_InformationTypeAction; + QmitkDataNodeUnlinkFromLesionAction* m_UnlinkFromLesionAction; + QmitkDataNodeRemoveFromSemanticRelationsAction* m_RemoveFromSemanticRelationsAction; + QmitkDataNodeOpenInAction* m_OpenInAction; + +}; + +#endif // QMITKSEMANTICRELATIONSCONTEXTMENU_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsControls.ui b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsControls.ui new file mode 100644 index 0000000000..bb178d3af5 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsControls.ui @@ -0,0 +1,37 @@ + + + QmitkSemanticRelationsControls + + + + 0 + 0 + 350 + 300 + + + + Semantic relations plugin + + + + + + + 0 + 0 + + + + Currently selected patient: + + + + + + + + + + + diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsNodeSelectionDialog.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsNodeSelectionDialog.cpp new file mode 100644 index 0000000000..346e265571 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsNodeSelectionDialog.cpp @@ -0,0 +1,53 @@ +/*=================================================================== + +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 "QmitkSemanticRelationsNodeSelectionDialog.h" + +// semantic relations ui module +#include + +// semantic relations ui module +#include + +QmitkSemanticRelationsNodeSelectionDialog::QmitkSemanticRelationsNodeSelectionDialog(QWidget* parent, QString title, QString hint) + : QmitkNodeSelectionDialog(parent, title, hint) +{ + // nothing here +} + +void QmitkSemanticRelationsNodeSelectionDialog::SetCaseID(const mitk::SemanticTypes::CaseID& caseID) +{ + for (auto panel : m_Panels) + { + QmitkAbstractSemanticRelationsStorageInspector* semanticRelationsStorageInspector = dynamic_cast(panel); + if (nullptr != semanticRelationsStorageInspector) + { + semanticRelationsStorageInspector->SetCaseID(caseID); + } + } +} + +void QmitkSemanticRelationsNodeSelectionDialog::SetLesion(const mitk::SemanticTypes::Lesion& lesion) +{ + for (auto panel : m_Panels) + { + QmitkAbstractSemanticRelationsStorageInspector* semanticRelationsStorageInspector = dynamic_cast(panel); + if (nullptr != semanticRelationsStorageInspector) + { + semanticRelationsStorageInspector->SetLesion(lesion); + } + } +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsNodeSelectionDialog.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsNodeSelectionDialog.h new file mode 100644 index 0000000000..3efc40031d --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsNodeSelectionDialog.h @@ -0,0 +1,54 @@ +/*=================================================================== + +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 QMITKSEMANTICRELATIONSNODESELECTIONDIALOG_H +#define QMITKSEMANTICRELATIONSNODESELECTIONDIALOG_H + +// semantic relations module +#include + +// org.mitk.gui.qt.common plugin +#include + +/* +* @brief The QmitkSemanticRelationsNodeSelectionDialog extends the QmitkNodeSelectionDialog so that a case ID and a lesion +* can be set for each known panel that is of type 'QmitkSemanticRelationsStorageInspector'. +*/ +class QmitkSemanticRelationsNodeSelectionDialog : public QmitkNodeSelectionDialog +{ + Q_OBJECT + +public: + + explicit QmitkSemanticRelationsNodeSelectionDialog(QWidget* parent = nullptr, QString caption = "", QString hint = ""); + /** + * @brief Extends the base class to allow setting the current case ID which is needed to access the + * semantic relations storage. The function sets the case ID of each 'QmitkSemanticRelationsStorageInspector'. + * + * @param caseID A case ID as string + */ + virtual void SetCaseID(const mitk::SemanticTypes::CaseID& caseID); + /** + * @brief Extends the base class to allow setting the current lesion which can be used to show on which images + * the lesion is visible. The function sets the lesion of each 'QmitkSemanticRelationsStorageInspector'. + * + * @param lesion The selected lesion + */ + virtual void SetLesion(const mitk::SemanticTypes::Lesion& lesion); + +}; + +#endif // QMITKSEMANTICRELATIONSNODESELECTIONDIALOG_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.cpp new file mode 100644 index 0000000000..64bc9c6194 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.cpp @@ -0,0 +1,276 @@ +/*=================================================================== + +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. + +===================================================================*/ + +// semantic relations plugin +#include "QmitkSemanticRelationsView.h" +#include "QmitkDataNodeAddToSemanticRelationsAction.h" +#include "QmitkDataNodeRemoveFromSemanticRelationsAction.h" + +// semantic relations module +#include +#include + +// mitk qt widgets module +#include +#include + +// mitk multi label module +#include + +// berry +#include +#include + +// qt +#include +#include + +const std::string QmitkSemanticRelationsView::VIEW_ID = "org.mitk.views.semanticrelations"; + +void QmitkSemanticRelationsView::SetFocus() +{ + // nothing here +} + +void QmitkSemanticRelationsView::CreateQtPartControl(QWidget* parent) +{ + // create GUI widgets + m_Controls.setupUi(parent); + + // initialize the semantic relations + m_SemanticRelations = std::make_unique(GetDataStorage()); + + m_LesionInfoWidget = new QmitkLesionInfoWidget(GetDataStorage(), parent); + m_Controls.gridLayout->addWidget(m_LesionInfoWidget); + + m_PatientTableInspector = new QmitkPatientTableInspector(parent); + m_PatientTableInspector->SetDataStorage(GetDataStorage()); + m_Controls.gridLayout->addWidget(m_PatientTableInspector); + + QGridLayout* dndDataNodeWidgetLayout = new QGridLayout; + dndDataNodeWidgetLayout->addWidget(m_PatientTableInspector, 0, 0); + dndDataNodeWidgetLayout->setContentsMargins(0, 0, 0, 0); + + m_DnDDataNodeWidget = new QmitkDnDDataNodeWidget(parent); + m_DnDDataNodeWidget->setLayout(dndDataNodeWidgetLayout); + + m_Controls.gridLayout->addWidget(m_DnDDataNodeWidget); + + m_ContextMenu = new QmitkSemanticRelationsContextMenu(GetSite(), m_PatientTableInspector); + m_ContextMenu->SetDataStorage(GetDataStorage()); + + mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); + if (nullptr != renderWindowPart) + { + RenderWindowPartActivated(renderWindowPart); + } + + SetUpConnections(); + + const auto& allCaseIDs = m_SemanticRelations->GetAllCaseIDs(); + for (const auto& caseID : allCaseIDs) + { + AddToComboBox(caseID); + } +} + +void QmitkSemanticRelationsView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) +{ + // connect QmitkRenderWindows - underlying vtkRenderWindow is the same as "mitk::RenderingManager::GetInstance()->GetAllRegisteredRenderWindows()" + QHash windowMap = renderWindowPart->GetQmitkRenderWindows(); + QHash::Iterator it; + + mitk::BaseRenderer* baseRenderer = nullptr; + RenderWindowLayerUtilities::RendererVector controlledRenderer; + for (it = windowMap.begin(); it != windowMap.end(); ++it) + { + baseRenderer = mitk::BaseRenderer::GetInstance(it.value()->GetVtkRenderWindow()); + if (nullptr != baseRenderer) + { + controlledRenderer.push_back(baseRenderer); + } + } + + m_ContextMenu->SetControlledRenderer(controlledRenderer); +} + +void QmitkSemanticRelationsView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) +{ + // nothing here +} + +void QmitkSemanticRelationsView::SetUpConnections() +{ + connect(m_Controls.caseIDComboBox, static_cast(&QComboBox::currentIndexChanged), this, &QmitkSemanticRelationsView::OnCaseIDSelectionChanged); + connect(m_LesionInfoWidget, &QmitkLesionInfoWidget::LesionSelectionChanged, this, &QmitkSemanticRelationsView::OnLesionSelectionChanged); + connect(m_PatientTableInspector, &QmitkPatientTableInspector::CurrentSelectionChanged, this, &QmitkSemanticRelationsView::OnDataNodeSelectionChanged); + connect(m_PatientTableInspector, &QmitkPatientTableInspector::DataNodeDoubleClicked, this, &QmitkSemanticRelationsView::OnDataNodeDoubleClicked); + connect(m_DnDDataNodeWidget, &QmitkDnDDataNodeWidget::NodesDropped, this, &QmitkSemanticRelationsView::OnNodesAdded); + + connect(m_PatientTableInspector, &QmitkPatientTableInspector::OnContextMenuRequested, m_ContextMenu, &QmitkSemanticRelationsContextMenu::OnContextMenuRequested); + connect(m_PatientTableInspector, &QmitkPatientTableInspector::OnNodeRemoved, this, &QmitkSemanticRelationsView::NodeRemoved); +} + +QItemSelectionModel* QmitkSemanticRelationsView::GetDataNodeSelectionModel() const +{ + return m_PatientTableInspector->GetSelectionModel(); +} + +void QmitkSemanticRelationsView::NodeRemoved(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + return; + } + + if (m_SemanticRelations->InstanceExists(dataNode)) + { + RemoveFromSemanticRelationsAction::Run(m_SemanticRelations.get(), dataNode); + mitk::SemanticTypes::CaseID caseID = mitk::GetCaseIDFromDataNode(dataNode); + RemoveFromComboBox(caseID); + } +} + +void QmitkSemanticRelationsView::OnLesionSelectionChanged(const mitk::SemanticTypes::Lesion& lesion) +{ + m_PatientTableInspector->SetLesion(lesion); +} + +void QmitkSemanticRelationsView::OnDataNodeSelectionChanged(const QList& dataNodeSelection) +{ + m_LesionInfoWidget->SetDataNodeSelection(dataNodeSelection); +} + +void QmitkSemanticRelationsView::OnDataNodeDoubleClicked(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + return; + } + + if (mitk::NodePredicates::GetImagePredicate()->CheckNode(dataNode)) + { + OpenInEditor(dataNode); + } + else if (mitk::NodePredicates::GetSegmentationPredicate()->CheckNode(dataNode)) + { + JumpToPosition(dataNode); + } +} + +void QmitkSemanticRelationsView::OnCaseIDSelectionChanged(const QString& caseID) +{ + m_LesionInfoWidget->SetCaseID(caseID.toStdString()); + m_PatientTableInspector->SetCaseID(caseID.toStdString()); +} + +void QmitkSemanticRelationsView::OnNodesAdded(QmitkDnDDataNodeWidget* dnDDataNodeWidget, std::vector nodes) +{ + mitk::SemanticTypes::CaseID caseID = ""; + for (mitk::DataNode* dataNode : nodes) + { + AddToSemanticRelationsAction::Run(m_SemanticRelations.get(), GetDataStorage(), dataNode); + caseID = mitk::GetCaseIDFromDataNode(dataNode); + AddToComboBox(caseID); + } +} + +void QmitkSemanticRelationsView::OnNodeRemoved(const mitk::DataNode* dataNode) +{ + NodeRemoved(dataNode); +} + +void QmitkSemanticRelationsView::AddToComboBox(const mitk::SemanticTypes::CaseID& caseID) +{ + int foundIndex = m_Controls.caseIDComboBox->findText(QString::fromStdString(caseID)); + if (-1 == foundIndex) + { + // add the caseID to the combo box, as it is not already contained + m_Controls.caseIDComboBox->addItem(QString::fromStdString(caseID)); + } +} + +void QmitkSemanticRelationsView::RemoveFromComboBox(const mitk::SemanticTypes::CaseID& caseID) +{ + std::vector allControlPoints = m_SemanticRelations->GetAllControlPointsOfCase(caseID); + int foundIndex = m_Controls.caseIDComboBox->findText(QString::fromStdString(caseID)); + if (allControlPoints.empty() && -1 != foundIndex) + { + // TODO: find new way to check for empty case id + // caseID does not contain any control points and therefore no data + // remove the caseID, if it is still contained + m_Controls.caseIDComboBox->removeItem(foundIndex); + } +} + +void QmitkSemanticRelationsView::OpenInEditor(const mitk::DataNode* dataNode) +{ + auto renderWindowPart = GetRenderWindowPart(); + if (nullptr == renderWindowPart) + { + renderWindowPart = GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::BRING_TO_FRONT | mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); + if (nullptr == renderWindowPart) + { + // no render window available + return; + } + } + + auto image = dynamic_cast(dataNode->GetData()); + if (nullptr != image) + { + mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + } +} + +void QmitkSemanticRelationsView::JumpToPosition(const mitk::DataNode* dataNode) +{ + if (nullptr == dataNode) + { + return; + } + + mitk::LabelSetImage* labelSetImage = dynamic_cast(dataNode->GetData()); + if (nullptr == labelSetImage) + { + return; + } + + int activeLayer = labelSetImage->GetActiveLayer(); + mitk::Label* activeLabel = labelSetImage->GetActiveLabel(activeLayer); + labelSetImage->UpdateCenterOfMass(activeLabel->GetValue(), activeLayer); + const mitk::Point3D& centerPosition = activeLabel->GetCenterOfMassCoordinates(); + if (centerPosition.GetVnlVector().max_value() > 0.0) + { + auto renderWindowPart = GetRenderWindowPart(); + if (nullptr == renderWindowPart) + { + renderWindowPart = GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::BRING_TO_FRONT | mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); + if (nullptr == renderWindowPart) + { + // no render window available + return; + } + } + + auto segmentation = dynamic_cast(dataNode->GetData()); + if (nullptr != segmentation) + { + renderWindowPart->SetSelectedPosition(centerPosition); + mitk::RenderingManager::GetInstance()->InitializeViews(segmentation->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + } + } +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.h new file mode 100644 index 0000000000..805dc045bf --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/QmitkSemanticRelationsView.h @@ -0,0 +1,108 @@ +/*=================================================================== + +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 QMITKSEMANTICRELATIONSVIEW_H +#define QMITKSEMANTICRELATIONSVIEW_H + +// semantic relations plugin +#include "ui_QmitkSemanticRelationsControls.h" +#include "QmitkLesionInfoWidget.h" +#include "QmitkSemanticRelationsContextMenu.h" + +// semantic relations module +#include +#include + +// semantic relations UI module +#include + +// mitk gui common plugin +#include + +// berry +#include + +// mitk qt +#include + +class QmitkDnDDataNodeWidget; +class QMenu; + +/* +* @brief The QmitkSemanticRelationsView is an MITK view to combine and show the widgets of the 'SemanticRelationsUI'-module and this semantic relations plugin. +* +* It allows the MITK user to see and modify the content of the SemanticRelations-session. +* A combo box is used to select and show the current patient. +*/ +class QmitkSemanticRelationsView : public QmitkAbstractView, public mitk::IRenderWindowPartListener +{ + Q_OBJECT + +public: + + static const std::string VIEW_ID; + +protected: + + virtual void SetFocus() override; + virtual void CreateQtPartControl(QWidget* parent) override; + + virtual void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; + virtual void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; + +private Q_SLOTS: + + void OnLesionSelectionChanged(const mitk::SemanticTypes::Lesion&); + void OnDataNodeSelectionChanged(const QList&); + void OnDataNodeDoubleClicked(const mitk::DataNode*); + void OnCaseIDSelectionChanged(const QString&); + + void OnNodesAdded(QmitkDnDDataNodeWidget*, std::vector); + void OnNodeRemoved(const mitk::DataNode*); + +private: + + void SetUpConnections(); + /** + * @brief Provide a QItemSelectionModel, which supports the data role 'QmitkDataNodeRole' (\see QmitkRenderWindowDataModel). + * + * The provided QItemSelectionModel is used in the QmitkAbstractView-base class as the selection model of + * the selection provider (\see QmitkAbstractView::SetSelectionProvider()). + * The default selection provider is a QmitkDataNodeSelectionProvider. Each time a selection in the provided + * QItemSeletionModel is changed, a selection changed event is fired. All plugins (views), that subclass the + * QmitkAbstractView will be informed about the selection changed via the OnSelectionChanged-function. + */ + virtual QItemSelectionModel* GetDataNodeSelectionModel() const override; + + virtual void NodeRemoved(const mitk::DataNode* dataNode) override; + + void AddToComboBox(const mitk::SemanticTypes::CaseID& caseID); + void RemoveFromComboBox(const mitk::SemanticTypes::CaseID& caseID); + + void OpenInEditor(const mitk::DataNode* dataNode); + void JumpToPosition(const mitk::DataNode* dataNode); + + Ui::QmitkSemanticRelationsControls m_Controls; + QmitkLesionInfoWidget* m_LesionInfoWidget; + QmitkPatientTableInspector* m_PatientTableInspector; + QmitkDnDDataNodeWidget* m_DnDDataNodeWidget; + + QmitkSemanticRelationsContextMenu* m_ContextMenu; + + std::unique_ptr m_SemanticRelations; +}; + +#endif // QMITKSEMANTICRELATIONSVIEW_H diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/mitkPluginActivator.cpp b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/mitkPluginActivator.cpp new file mode 100644 index 0000000000..9a343753d6 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/mitkPluginActivator.cpp @@ -0,0 +1,31 @@ +/*=================================================================== + +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 "mitkPluginActivator.h" +#include "QmitkSemanticRelationsView.h" +#include + +namespace mitk +{ + void SemanticRelationsActivator::start(ctkPluginContext *context) + { + mitk::PersistenceService::LoadModule(); + + BERRY_REGISTER_EXTENSION_CLASS(QmitkSemanticRelationsView, context) + } + + void SemanticRelationsActivator::stop(ctkPluginContext *context) { Q_UNUSED(context) } +} diff --git a/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/mitkPluginActivator.h b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/mitkPluginActivator.h new file mode 100644 index 0000000000..db67eb7674 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.semanticrelations/src/internal/mitkPluginActivator.h @@ -0,0 +1,37 @@ +/*=================================================================== + +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 MITKPLUGINACTIVATOR_H +#define MITKPLUGINACTIVATOR_H + +#include + +namespace mitk +{ + class SemanticRelationsActivator : public QObject, public ctkPluginActivator + { + Q_OBJECT + Q_PLUGIN_METADATA(IID "org_mitk_gui_qt_semanticrelations") + Q_INTERFACES(ctkPluginActivator) + + public: + void start(ctkPluginContext* context) override; + void stop(ctkPluginContext* context) override; + + }; +} + +#endif // MITKPLUGINACTIVATOR_H