diff --git a/CMakeExternals/Eigen.cmake b/CMakeExternals/Eigen.cmake index a39df68097..eb734a01b6 100644 --- a/CMakeExternals/Eigen.cmake +++ b/CMakeExternals/Eigen.cmake @@ -1,43 +1,45 @@ #----------------------------------------------------------------------------- # Eigen #----------------------------------------------------------------------------- if(MITK_USE_Eigen) # Sanity checks if(DEFINED Eigen_DIR AND NOT EXISTS ${Eigen_DIR}) message(FATAL_ERROR "Eigen_DIR variable is defined but corresponds to non-existing directory") endif() set(proj Eigen) - set(proj_DEPENDENCIES ) + set(proj_DEPENDENCIES Boost) set(Eigen_DEPENDS ${proj}) if(NOT DEFINED Eigen_DIR) ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git GIT_TAG 3.4.0 CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} + "-DBoost_DIR:PATH=${Boost_DIR}" -DBUILD_TESTING:BOOL=ON -DEIGEN_BUILD_PKGCONFIG:BOOL=OFF CMAKE_CACHE_ARGS ${ep_common_cache_args} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} + DEPENDS ${proj_DEPENDENCIES} ) set(Eigen_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeExternals/ExternalProjectList.cmake b/CMakeExternals/ExternalProjectList.cmake index 9cf98902b0..a017fae980 100644 --- a/CMakeExternals/ExternalProjectList.cmake +++ b/CMakeExternals/ExternalProjectList.cmake @@ -1,32 +1,32 @@ mitkFunctionAddExternalProject(NAME Poco ON COMPONENTS Foundation Net Util XML Zip) mitkFunctionAddExternalProject(NAME DCMTK ON DOC "EXPERIMENTAL, superbuild only: Use DCMTK in MITK") mitkFunctionAddExternalProject(NAME OpenIGTLink OFF) mitkFunctionAddExternalProject(NAME tinyxml2 ON ADVANCED) mitkFunctionAddExternalProject(NAME GDCM ON ADVANCED) -mitkFunctionAddExternalProject(NAME Eigen ON ADVANCED DOC "Use the Eigen library") +mitkFunctionAddExternalProject(NAME Boost ON NO_CACHE) +mitkFunctionAddExternalProject(NAME Eigen ON DEPENDS Boost ADVANCED DOC "Use the Eigen library") mitkFunctionAddExternalProject(NAME ANN ON ADVANCED DOC "Use Approximate Nearest Neighbor Library") mitkFunctionAddExternalProject(NAME CppUnit ON ADVANCED DOC "Use CppUnit for unit tests") mitkFunctionAddExternalProject(NAME HDF5 ON ADVANCED) mitkFunctionAddExternalProject(NAME OpenCV OFF) mitkFunctionAddExternalProject(NAME ITK ON NO_CACHE DEPENDS HDF5) mitkFunctionAddExternalProject(NAME VTK ON NO_CACHE) -mitkFunctionAddExternalProject(NAME Boost ON NO_CACHE) mitkFunctionAddExternalProject(NAME ZLIB OFF ADVANCED) mitkFunctionAddExternalProject(NAME lz4 ON ADVANCED) mitkFunctionAddExternalProject(NAME cpprestsdk OFF DEPENDS Boost ZLIB ADVANCED) mitkFunctionAddExternalProject(NAME ACVD OFF DOC "Use Approximated Centroidal Voronoi Diagrams") mitkFunctionAddExternalProject(NAME CTK ON DEPENDS Qt5 DCMTK DOC "Use CTK in MITK") mitkFunctionAddExternalProject(NAME DCMQI ON DEPENDS DCMTK ITK DOC "Use dcmqi in MITK") mitkFunctionAddExternalProject(NAME MatchPoint OFF ADVANCED DEPENDS Boost ITK DOC "Use the MatchPoint translation image registration library") mitkFunctionAddExternalProject(NAME nlohmann_json ON ADVANCED) if(MITK_USE_Qt5) mitkFunctionAddExternalProject(NAME Qwt ON ADVANCED DEPENDS Qt5) endif() if(UNIX AND NOT APPLE) mitkFunctionAddExternalProject(NAME PCRE OFF ADVANCED NO_PACKAGE) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE DEPENDS PCRE) elseif(WIN32) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE) endif() diff --git a/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp b/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp index cfecd85744..f093067910 100644 --- a/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp @@ -1,621 +1,626 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPointSetDataInteractor.h" #include "mitkMouseMoveEvent.h" #include "mitkInteractionConst.h" // TODO: refactor file #include "mitkInternalEvent.h" #include "mitkOperationEvent.h" #include "mitkRenderingManager.h" #include // #include "mitkBaseRenderer.h" #include "mitkDispatcher.h" #include "mitkUndoController.h" void mitk::PointSetDataInteractor::ConnectActionsAndFunctions() { // Condition which is evaluated before transition is taken // following actions in the statemachine are only executed if it returns TRUE CONNECT_CONDITION("isoverpoint", CheckSelection); CONNECT_FUNCTION("addpoint", AddPoint); CONNECT_FUNCTION("selectpoint", SelectPoint); CONNECT_FUNCTION("unselect", UnSelectPointAtPosition); CONNECT_FUNCTION("unselectAll", UnSelectAll); CONNECT_FUNCTION("initMove", InitMove); CONNECT_FUNCTION("movePoint", MovePoint); CONNECT_FUNCTION("finishMovement", FinishMove); CONNECT_FUNCTION("removePoint", RemovePoint); } void mitk::PointSetDataInteractor::AddPoint(StateMachineAction *stateMachineAction, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); // disallow adding of new points if maximum number of points is reached if (m_MaxNumberOfPoints > 1 && m_PointSet->GetSize(timeStep) >= m_MaxNumberOfPoints) { return; } // To add a point the minimal information is the position, this method accepts all InteractionsPositionEvents auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { mitk::Point3D itkPoint = positionEvent->GetPositionInWorld(); this->UnselectAll(timeStep, timeInMs); int lastPosition = 0; mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { if (!m_PointSet->IndexExists(lastPosition, timeStep)) break; ++it; ++lastPosition; } // Insert a Point to the PointSet // 2) Create the Operation inserting the point if (m_PointSet->IsEmpty()) { lastPosition = 0; } auto *doOp = new mitk::PointOperation(OpINSERT, timeInMs, itkPoint, lastPosition); // 3) If Undo is enabled, also create the inverse Operation if (m_UndoEnabled) { auto *undoOp = new mitk::PointOperation(OpREMOVE, timeInMs, itkPoint, lastPosition); // 4) Do and Undo Operations are combined in an Operation event which also contains the target of the operations // (here m_PointSet) OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Add point"); // 5) Store the Operation in the UndoController OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // 6) Execute the Operation performs the actual insertion of the point into the PointSet m_PointSet->ExecuteOperation(doOp); // 7) If Undo is not enabled the Do-Operation is to be dropped to prevent memory leaks. if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); // Check if points form a closed contour now, if so fire an InternalEvent IsClosedContour(stateMachineAction, interactionEvent); if (m_MaxNumberOfPoints > 0 && m_PointSet->GetSize(timeStep) >= m_MaxNumberOfPoints) { // Signal that DataNode is fully filled this->NotifyResultReady(); // Send internal event that can be used by StateMachines to switch in a different state InternalEvent::Pointer event = InternalEvent::New(nullptr, this, "MaximalNumberOfPoints"); positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } } } void mitk::PointSetDataInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); if (index != -1) { // first deselect the other points // undoable deselect of all points in the DataList this->UnselectAll(timeStep, timeInMs); auto *doOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, point, index); /*if (m_UndoEnabled) { PointOperation* undoOp = new mitk::PointOperation(OpDESELECTPOINT,timeInMs,point, index); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Select Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); } } } mitk::PointSetDataInteractor::PointSetDataInteractor() : m_MaxNumberOfPoints(0), m_SelectionAccuracy(3.5) { } mitk::PointSetDataInteractor::~PointSetDataInteractor() { } void mitk::PointSetDataInteractor::RemovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { mitk::Point3D itkPoint = positionEvent->GetPositionInWorld(); // search the point in the list int position = m_PointSet->SearchPoint(itkPoint, m_SelectionAccuracy, timeStep); if (position >= 0) // found a point { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); itkPoint[0] = pt[0]; itkPoint[1] = pt[1]; itkPoint[2] = pt[2]; auto *doOp = new mitk::PointOperation(OpREMOVE, timeInMs, itkPoint, position); if (m_UndoEnabled) // write to UndoMechanism { auto *undoOp = new mitk::PointOperation(OpINSERT, timeInMs, itkPoint, position); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Remove point"); mitk::OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; /*now select the point "position-1", and if it is the first in list, then continue at the last in list*/ // only then a select of a point is possible! if (m_PointSet->GetSize(timeStep) > 0) { this->SelectPoint(m_PointSet->Begin(timeStep)->Index(), timeStep, timeInMs); } } RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PointSetDataInteractor::IsClosedContour(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected if (GetPointIndexByPosition(point, timeStep) != -1 && m_PointSet->GetSize(timeStep) >= 3) { InternalEvent::Pointer event = InternalEvent::New(nullptr, this, "ClosedContour"); positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } } } void mitk::PointSetDataInteractor::MovePoint(StateMachineAction *stateMachineAction, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { IsClosedContour(stateMachineAction, interactionEvent); mitk::Point3D newPoint, resultPoint; newPoint = positionEvent->GetPositionInWorld(); // search the elements in the list that are selected then calculate the // vector, because only with the vector we can move several elements in // the same direction // newPoint - lastPoint = vector // then move all selected and set the lastPoint = newPoint. // then add all vectors to a summeryVector (to be able to calculate the // startpoint for undoOperation) mitk::Vector3D dirVector = newPoint - m_LastPoint; // sum up all Movement for Undo in FinishMovement m_SumVec = m_SumVec + dirVector; mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { int position = it->Index(); if (m_PointSet->GetSelectInfo(position, timeStep)) // if selected { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); mitk::Point3D sumVec; sumVec[0] = pt[0]; sumVec[1] = pt[1]; sumVec[2] = pt[2]; resultPoint = sumVec + dirVector; auto *doOp = new mitk::PointOperation(OpMOVE, timeInMs, resultPoint, position); // execute the Operation // here no undo is stored, because the movement-steps aren't interesting. // only the start and the end is interisting to store for undo. m_PointSet->ExecuteOperation(doOp); delete doOp; } ++it; } m_LastPoint = newPoint; // for calculation of the direction vector // Update the display RenderingManager::GetInstance()->RequestUpdateAll(); IsClosedContour(stateMachineAction, interactionEvent); } } void mitk::PointSetDataInteractor::UnSelectPointAtPosition(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); // here it is ensured that we don't switch from one point being selected to another one being selected, // without accepting the unselect of the current point if (index != -1) { auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, point, index); /*if (m_UndoEnabled) { PointOperation* undoOp = new mitk::PointOperation(OpSELECTPOINT,timeInMs, point, index); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Unselect Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); } } } void mitk::PointSetDataInteractor::UnSelectAll(mitk::StateMachineAction *, mitk::InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D positioninWorld = positionEvent->GetPositionInWorld(); PointSet::PointsContainer::Iterator it, end; PointSet::DataType *itkPointSet = m_PointSet->GetPointSet(timeStep); end = itkPointSet->GetPoints()->End(); for (it = itkPointSet->GetPoints()->Begin(); it != end; it++) { int position = it->Index(); // then declare an operation which unselects this point; // UndoOperation as well! if (m_PointSet->GetSelectInfo(position, timeStep)) { float distance = sqrt(positioninWorld.SquaredEuclideanDistanceTo(m_PointSet->GetPoint(position, timeStep))); if (distance > m_SelectionAccuracy) { mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation* undoOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, noPoint, position); OperationEvent *operationEvent = new OperationEvent( m_PointSet, doOp, undoOp, "Unselect Point" ); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent( operationEvent ); }*/ m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } } } } else { this->UnselectAll(timeStep, timeInMs); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PointSetDataInteractor::UpdatePointSet(mitk::StateMachineAction *, mitk::InteractionEvent *) { auto *pointSet = dynamic_cast(this->GetDataNode()->GetData()); if (pointSet == nullptr) { MITK_ERROR << "PointSetDataInteractor:: No valid point set ."; return; } m_PointSet = pointSet; } void mitk::PointSetDataInteractor::Abort(StateMachineAction *, InteractionEvent *interactionEvent) { InternalEvent::Pointer event = InternalEvent::New(nullptr, this, IntDeactivateMe); interactionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } /* * Check whether the DataNode contains a pointset, if not create one and add it. */ void mitk::PointSetDataInteractor::DataNodeChanged() { if (GetDataNode() != nullptr) { auto *points = dynamic_cast(GetDataNode()->GetData()); if (points == nullptr) { m_PointSet = PointSet::New(); GetDataNode()->SetData(m_PointSet); } else { m_PointSet = points; } // load config file parameter: maximal number of points mitk::PropertyList::Pointer properties = GetAttributes(); std::string strNumber; if (properties->GetStringProperty("MaxPoints", strNumber)) { m_MaxNumberOfPoints = atoi(strNumber.c_str()); } } } void mitk::PointSetDataInteractor::InitMove(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; // start of the Movement is stored to calculate the undoKoordinate // in FinishMovement m_LastPoint = positionEvent->GetPositionInWorld(); // initialize a value to calculate the movement through all // MouseMoveEvents from MouseClick to MouseRelease m_SumVec.Fill(0); GetDataNode()->SetProperty("contourcolor", ColorProperty::New(1.0, 1.0, 1.0)); } void mitk::PointSetDataInteractor::FinishMove(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { // finish the movement: // the final point is m_LastPoint // m_SumVec stores the movement in a vector // the operation would not be necessary, but we need it for the undo Operation. // m_LastPoint is for the Operation // the point for undoOperation calculates from all selected // elements (point) - m_SumVec // search all selected elements and move them with undo-functionality. mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { int position = it->Index(); if (m_PointSet->GetSelectInfo(position, timeStep)) // if selected { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); Point3D itkPoint; itkPoint[0] = pt[0]; itkPoint[1] = pt[1]; itkPoint[2] = pt[2]; auto *doOp = new mitk::PointOperation(OpMOVE, timeInMs, itkPoint, position); if (m_UndoEnabled) //&& (posEvent->GetType() == mitk::Type_MouseButtonRelease) { // set the undo-operation, so the final position is undo-able // calculate the old Position from the already moved position - m_SumVec mitk::Point3D undoPoint = (itkPoint - m_SumVec); auto *undoOp = new mitk::PointOperation(OpMOVE, timeInMs, undoPoint, position); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Move point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } ++it; } // Update the display RenderingManager::GetInstance()->RequestUpdateAll(); } else { return; } this->NotifyResultReady(); } void mitk::PointSetDataInteractor::SetAccuracy(float accuracy) { m_SelectionAccuracy = accuracy; } void mitk::PointSetDataInteractor::SetMaxPoints(unsigned int maxNumber) { m_MaxNumberOfPoints = maxNumber; } int mitk::PointSetDataInteractor::GetPointIndexByPosition(Point3D position, unsigned int time, float accuracy) { - // iterate over point set and check if it contains a point close enough to the pointer to be selected + // Iterate over point set and check if any point is close enough to the pointer to be selected. + // Choose the closest one of these candidates. + auto *points = dynamic_cast(GetDataNode()->GetData()); int index = -1; if (points == nullptr) { return index; } if (points->GetPointSet(time) == nullptr) return -1; PointSet::PointsContainer *pointsContainer = points->GetPointSet(time)->GetPoints(); - float minDistance = m_SelectionAccuracy; - if (accuracy != -1) - minDistance = accuracy; + if (accuracy == -1) + accuracy = m_SelectionAccuracy; + + const auto end = pointsContainer->End(); + float minDistance = accuracy; + float distance; - for (PointSet::PointsIterator it = pointsContainer->Begin(); it != pointsContainer->End(); it++) + for (auto it = pointsContainer->Begin(); it != end; ++it) { - float distance = sqrt(position.SquaredEuclideanDistanceTo(points->GetPoint(it->Index(), time))); - if (distance < - minDistance) // if several points fall within the margin, choose the one with minimal distance to position + distance = sqrtf(position.SquaredEuclideanDistanceTo(points->GetPoint(it->Index(), time))); + if (distance < minDistance) { + minDistance = distance; index = it->Index(); } } return index; } bool mitk::PointSetDataInteractor::CheckSelection(const mitk::InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); if (index != -1) return true; } return false; } void mitk::PointSetDataInteractor::UnselectAll(unsigned int timeStep, ScalarType timeInMs) { auto *pointSet = dynamic_cast(GetDataNode()->GetData()); if (pointSet == nullptr) { return; } mitk::PointSet::DataType *itkPointSet = pointSet->GetPointSet(timeStep); if (itkPointSet == nullptr) { return; } mitk::PointSet::PointsContainer::Iterator it, end; end = itkPointSet->GetPoints()->End(); for (it = itkPointSet->GetPoints()->Begin(); it != end; it++) { int position = it->Index(); PointSet::PointDataType pointData = {0, false, PTUNDEFINED}; itkPointSet->GetPointData(position, &pointData); // then declare an operation which unselects this point; // UndoOperation as well! if (pointData.selected) { mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation *undoOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, noPoint, position); OperationEvent *operationEvent = new OperationEvent( pointSet, doOp, undoOp, "Unselect Point" ); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent( operationEvent ); }*/ pointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } } } void mitk::PointSetDataInteractor::SelectPoint(int position, unsigned int timeStep, ScalarType timeInMS) { auto *pointSet = dynamic_cast(this->GetDataNode()->GetData()); // if List is empty, then no selection of a point can be done! if ((pointSet == nullptr) || (pointSet->GetSize(timeStep) <= 0)) { return; } // dummyPoint... not needed anyway mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpSELECTPOINT, timeInMS, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation* undoOp = new mitk::PointOperation(OpDESELECTPOINT,timeInMS, noPoint, position); OperationEvent *operationEvent = new OperationEvent(pointSet, doOp, undoOp, "Select Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ pointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } diff --git a/Modules/CppMicroServices/core/test/CMakeLists.txt b/Modules/CppMicroServices/core/test/CMakeLists.txt index f0c606b453..5c21ab2162 100644 --- a/Modules/CppMicroServices/core/test/CMakeLists.txt +++ b/Modules/CppMicroServices/core/test/CMakeLists.txt @@ -1,100 +1,101 @@ #----------------------------------------------------------------------------- # Configure files, include dirs, etc. #----------------------------------------------------------------------------- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/usTestingConfig.h.in" "${PROJECT_BINARY_DIR}/include/usTestingConfig.h") include_directories(${CMAKE_CURRENT_SOURCE_DIR}) #----------------------------------------------------------------------------- # Create test modules #----------------------------------------------------------------------------- include(usFunctionCreateTestModule) set(_us_test_module_libs "" CACHE INTERNAL "" FORCE) add_subdirectory(modules) #----------------------------------------------------------------------------- # Add unit tests #----------------------------------------------------------------------------- set(_tests usAnyTest usLDAPFilterTest usLogTest usModuleHooksTest usModuleManifestTest usModuleTest usModuleResourceTest usServiceFactoryTest usServiceHooksTest usServiceRegistryPerformanceTest usServiceRegistryTest usServiceTemplateTest usServiceTrackerTest usStaticModuleResourceTest usStaticModuleTest ) if(US_BUILD_SHARED_LIBS) list(APPEND _tests usServiceListenerTest usSharedLibraryTest ) if(US_ENABLE_AUTOLOADING_SUPPORT) list(APPEND _tests usModuleAutoLoadTest) endif() endif() list(TRANSFORM _tests APPEND ".cpp" OUTPUT_VARIABLE _test_files) set(_additional_srcs usTestDriverActivator.cpp usTestManager.cpp usTestUtilModuleListener.cpp ) set(_test_driver us${PROJECT_NAME}TestDriver) set(_test_sourcelist_extra_args ) create_test_sourcelist(_srcs ${_test_driver}.cpp ${_test_files} ${_test_sourcelist_extra_args}) # Generate a custom "module init" file for the test driver executable usFunctionGenerateModuleInit(_srcs) usFunctionGetResourceSource(TARGET ${_test_driver} OUT _srcs) add_executable(${_test_driver} ${_srcs} ${_additional_srcs}) set_property(TARGET ${_test_driver} APPEND PROPERTY COMPILE_DEFINITIONS US_MODULE_NAME=main) set_property(TARGET ${_test_driver} PROPERTY US_MODULE_NAME main) +set_property(TARGET ${_test_driver} PROPERTY ENABLE_EXPORTS YES) set_property(TARGET ${_test_driver} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/CppMicroServices/Tests") if(NOT US_BUILD_SHARED_LIBS) set_property(TARGET ${_test_driver} APPEND PROPERTY COMPILE_DEFINITIONS US_STATIC_MODULE) target_link_libraries(${_test_driver} ${_us_test_module_libs}) endif() target_link_libraries(${_test_driver} ${Core_TARGET}) if(UNIX AND NOT APPLE) target_link_libraries(${_test_driver} rt) endif() # Add resources usFunctionEmbedResources(TARGET ${_test_driver} FILES usTestResource.txt manifest.json ZIP_ARCHIVES ${Core_TARGET} ${_us_test_module_libs}) # Register tests foreach(_test ${_tests}) add_test(NAME ${_test} COMMAND ${_test_driver} ${_test}) endforeach() #----------------------------------------------------------------------------- # Add dependencies for shared libraries #----------------------------------------------------------------------------- if(US_BUILD_SHARED_LIBS) foreach(_test_module ${_us_test_module_libs}) add_dependencies(${_test_driver} ${_test_module}) endforeach() endif() diff --git a/Modules/DICOM/CMakeLists.txt b/Modules/DICOM/CMakeLists.txt index 7a333dad07..4af61879f1 100644 --- a/Modules/DICOM/CMakeLists.txt +++ b/Modules/DICOM/CMakeLists.txt @@ -1,11 +1,12 @@ MITK_CREATE_MODULE( DEPENDS MitkCore PACKAGE_DEPENDS PUBLIC tinyxml2 PRIVATE DCMTK|dcmdata+ofstd ITK|IOGDCM TARGET_DEPENDS PUBLIC gdcmMSFF ) add_subdirectory(test) add_subdirectory(autoload/DICOMImageIO) +add_subdirectory(cmdapps) \ No newline at end of file diff --git a/Modules/DICOM/cmdapps/CMakeLists.txt b/Modules/DICOM/cmdapps/CMakeLists.txt new file mode 100644 index 0000000000..b02aa8fddc --- /dev/null +++ b/Modules/DICOM/cmdapps/CMakeLists.txt @@ -0,0 +1,8 @@ +option(BUILD_DICOMCmdApps "Build command-line apps of the MitkDICOM module" OFF) + +if(BUILD_DICOMCmdApps OR MITK_BUILD_ALL_APPS) + mitkFunctionCreateCommandLineApp( + NAME DICOMVolumeDiagnostics + DEPENDS MitkDICOM + ) +endif() diff --git a/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp new file mode 100644 index 0000000000..ae7d47f01d --- /dev/null +++ b/Modules/DICOM/cmdapps/DICOMVolumeDiagnostics.cpp @@ -0,0 +1,199 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include + + +#include +#include +#include +#include + +#include + +#include + +void InitializeCommandLineParser(mitkCommandLineParser& parser) +{ + parser.setTitle("DICOM Volume Diagnostics"); + parser.setCategory("DICOM"); + parser.setDescription("Gives insights how MITK readers would convert a set of DICOM files into image volumes (e.g. number of volumes and the sorting of the files)"); + parser.setContributor("German Cancer Research Center (DKFZ)"); + parser.setArgumentPrefix("--", "-"); + + parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); + parser.addArgument("only-own-series", "s", mitkCommandLineParser::Bool, "Only own series", "Analyze only files in the same directory that have the same DICOM Series UID, if a file is provided as input.", us::Any()); + parser.addArgument("check-3d", "d", mitkCommandLineParser::Bool, "Check 3D configs", "Analyze the input by using all known 3D configurations. If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); + parser.addArgument("check-3d+t", "t", mitkCommandLineParser::Bool, "Check 3D+t configs", "Analyze the input by using all known 3D+t configurations (thus dynamic image configurations). If flag is not set all configurations (3D and 3D+t) will be used.", us::Any()); + parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file or path", "Input contour(s)", us::Any(), false, false, false, mitkCommandLineParser::Input); + parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "Output file where the diagnostics results are stored as json.", us::Any()); +} + +int main(int argc, char* argv[]) +{ + int returnValue = EXIT_SUCCESS; + + mitkCommandLineParser parser; + InitializeCommandLineParser(parser); + + auto args = parser.parseArguments(argc, argv); + + if (args.empty()) + { + std::cout << parser.helpText(); + return EXIT_FAILURE; + } + + nlohmann::json diagnosticsResult; + + try + { + auto inputFilename = us::any_cast(args["input"]); + auto outputFilename = args.count("output")==0 ? std::string() : us::any_cast(args["output"]); + bool onlyOwnSeries = args.count("only-own-series"); + bool check3D = args.count("check-3d"); + bool check3DPlusT = args.count("check-3d+t"); + + if (!check3D && !check3DPlusT) + { //if no check option is selected all are activated by default. + check3D = true; + check3DPlusT = true; + } + + diagnosticsResult["input"] = inputFilename; + diagnosticsResult["only-own-series"] = onlyOwnSeries; + diagnosticsResult["check-3d"] = check3D; + diagnosticsResult["check-3d+t"] = check3DPlusT; + + mitk::StringList relevantFiles = mitk::GetDICOMFilesInSameDirectory(inputFilename); + + if (relevantFiles.empty()) + { + mitkThrow() << "DICOM Volume Diagnostics found no relevant files in specified location. No data is loaded. Location: " << inputFilename; + } + else + { + bool pathIsDirectory = std::filesystem::is_directory(inputFilename); + + if (!pathIsDirectory && onlyOwnSeries) + { + relevantFiles = mitk::FilterDICOMFilesForSameSeries(inputFilename, relevantFiles); + } + + diagnosticsResult["analyzed_files"] = relevantFiles; + + auto selector = mitk::DICOMFileReaderSelector::New(); + + if (check3D) selector->LoadBuiltIn3DConfigs(); + if (check3DPlusT) selector->LoadBuiltIn3DnTConfigs(); + + nlohmann::json readerInfos; + for (const auto& reader : selector->GetAllConfiguredReaders()) + { + nlohmann::json readerInfo; + readerInfo["class_name"] = reader->GetNameOfClass(); + readerInfo["configuration_label"] = reader->GetConfigurationLabel(); + readerInfo["configuration_description"] = reader->GetConfigurationDescription(); + readerInfos.push_back(readerInfo); + } + diagnosticsResult["checked_readers"] = readerInfos; + + selector->SetInputFiles(relevantFiles); + + auto reader = selector->GetFirstReaderWithMinimumNumberOfOutputImages(); + + if (reader.IsNull()) + { + mitkThrow() << "DICOM Volume Diagnostics service found no suitable reader configuration for relevant files."; + } + else + { + nlohmann::json readerInfo; + readerInfo["class_name"] = reader->GetNameOfClass(); + readerInfo["configuration_label"] = reader->GetConfigurationLabel(); + readerInfo["configuration_description"] = reader->GetConfigurationDescription(); + readerInfo["configuration_description"] = reader->GetConfigurationDescription(); + std::stringstream config; + reader->PrintConfiguration(config); + readerInfo["config_details"] = config.str(); + + diagnosticsResult["selected_reader"] = readerInfo; + + nlohmann::json outputInfos; + + unsigned int relevantOutputCount = 0; + const auto nrOfOutputs = reader->GetNumberOfOutputs(); + for (std::remove_const_t outputIndex = 0; outputIndex < nrOfOutputs; ++outputIndex) + { + bool isRelevantOutput = true; + if (!pathIsDirectory) + { + const auto frameList = reader->GetOutput(outputIndex).GetImageFrameList(); + auto finding = std::find_if(frameList.begin(), frameList.end(), [&](const mitk::DICOMImageFrameInfo::Pointer& frame) + { + std::filesystem::path framePath(frame->Filename); + std::filesystem::path inputPath(inputFilename); + return framePath == inputPath; + }); + isRelevantOutput = finding != frameList.end(); + } + + if (isRelevantOutput) + { + ++relevantOutputCount; + nlohmann::json outputInfo; + + const auto output = reader->GetOutput(outputIndex); + const auto frameList = output.GetImageFrameList(); + mitk::DICOMFilePathList outputFiles; + outputFiles.resize(frameList.size()); + std::transform(frameList.begin(), frameList.end(), outputFiles.begin(), [](const mitk::DICOMImageFrameInfo::Pointer& frame) { return frame->Filename; }); + + outputInfo["files"] = outputFiles; + outputInfo["timesteps"] = output.GetNumberOfTimeSteps(); + outputInfo["frames_per_timesteps"] = output.GetNumberOfFramesPerTimeStep(); + outputInfos.push_back(outputInfo); + } + } + diagnosticsResult["volume_count"] = relevantOutputCount; + diagnosticsResult["volumes"] = outputInfos; + } + } + std::cout << "\n### DIAGNOSTICS REPORT ###\n" << std::endl; + std::cout << std::setw(2) << diagnosticsResult << std::endl; + + if (!outputFilename.empty()) + { + std::ofstream fileout(outputFilename); + fileout << diagnosticsResult; + fileout.close(); + } + + } + catch (const mitk::Exception& e) + { + MITK_ERROR << e.GetDescription(); + return EXIT_FAILURE; + } + catch (const std::exception& e) + { + MITK_ERROR << e.what(); + return EXIT_FAILURE; + } + catch (...) + { + MITK_ERROR << "An unknown error occurred!"; + return EXIT_FAILURE; + } + + return returnValue; +} diff --git a/Modules/DICOM/include/mitkDICOMFilesHelper.h b/Modules/DICOM/include/mitkDICOMFilesHelper.h index 355b30834c..a561cc8689 100644 --- a/Modules/DICOM/include/mitkDICOMFilesHelper.h +++ b/Modules/DICOM/include/mitkDICOMFilesHelper.h @@ -1,41 +1,41 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkDICOMFilesHelper_h #define mitkDICOMFilesHelper_h #include #include #include namespace mitk { typedef std::vector DICOMFilePathList; /** Helper functions. Searches for all files in the directory of the passed file path. All files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ -DICOMFilePathList GetDICOMFilesInSameDirectory(const std::string& filePath); +DICOMFilePathList MITKDICOM_EXPORT GetDICOMFilesInSameDirectory(const std::string& filePath); /** All passed files will be checked if they are DICOM files. All DICOM files will be added to the result and returned. @remark The helper does no sorting of any kind.*/ -DICOMFilePathList FilterForDICOMFiles(const DICOMFilePathList& fileList); +DICOMFilePathList MITKDICOM_EXPORT FilterForDICOMFiles(const DICOMFilePathList& fileList); /** Returns all DICOM files passed with fileList that have the same series instance UID then the passed refFilePath. @pre refFilePath must point to a valid DICOM file.*/ -DICOMFilePathList FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList); +DICOMFilePathList MITKDICOM_EXPORT FilterDICOMFilesForSameSeries(const std::string& refFilePath, const DICOMFilePathList& fileList); } #endif diff --git a/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt index 8fbb245098..42465f083c 100644 --- a/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt +++ b/Modules/MatchPointRegistration/cmdapps/CMakeLists.txt @@ -1,34 +1,34 @@ option(BUILD_MatchPointCmdApps "Build commandline tools for the MatchPoint module" OFF) if(BUILD_MatchPointCmdApps OR MITK_BUILD_ALL_APPS) # needed include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # list of CmdApps # if an app requires additional dependencies # they are added after a "^^" and separated by "_" set( cmdapps - StitchImagesMiniApp^^ - MatchImageMiniApp^^ - MapImageMiniApp^^ + StitchImages^^ + MatchImage^^ + MapImage^^ ) foreach(cmdapp ${cmdapps}) # extract cmd name and dependencies string(REPLACE "^^" "\\;" cmdapp_info ${cmdapp}) set(cmdapp_info_list ${cmdapp_info}) list(GET cmdapp_info_list 0 appname) list(GET cmdapp_info_list 1 raw_dependencies) string(REPLACE "_" "\\;" dependencies "${raw_dependencies}") set(dependencies_list ${dependencies}) mitkFunctionCreateCommandLineApp( NAME ${appname} DEPENDS MitkCore MitkMatchPointRegistration ${dependencies_list} ) endforeach() endif(BUILD_MatchPointCmdApps OR MITK_BUILD_ALL_APPS) diff --git a/Modules/MatchPointRegistration/cmdapps/MapImageMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/MapImage.cpp similarity index 100% rename from Modules/MatchPointRegistration/cmdapps/MapImageMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/MapImage.cpp diff --git a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp similarity index 62% rename from Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/MatchImage.cpp index bbcb447ea4..17e05af7f5 100644 --- a/Modules/MatchPointRegistration/cmdapps/MatchImageMiniApp.cpp +++ b/Modules/MatchPointRegistration/cmdapps/MatchImage.cpp @@ -1,318 +1,482 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkCommandLineParser.h" #include #include #include #include #include #include #include // MatchPoint #include #include #include #include #include #include #include +#include +#include #include #include #include #include +#include + struct Settings { std::string movingFileName = ""; std::string targetFileName = ""; std::string outFileName = ""; std::string algFileName = ""; + std::string parameters = ""; }; void SetupParser(mitkCommandLineParser& parser) { - parser.setTitle("Image Matcher"); + parser.setTitle("Match Image"); parser.setCategory("Registration Tools"); parser.setDescription(""); parser.setContributor("MIC, German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--", "-"); // Add command line argument names parser.beginGroup("Required I/O parameters"); parser.addArgument( "moving", "m", mitkCommandLineParser::File, "Moving image files", "Path to the data that should be registred into the target space.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "target", "t", mitkCommandLineParser::File, "Tareget image files", "Path to the data that should be the target data on which the moving data should be registered.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument( "algorithm", "a", mitkCommandLineParser::File, "Registration algorithm", "Path to the registration algorithm that should be used for registration.", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file path", "Path to the generated registration.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Optional parameters"); - parser.addArgument("template", - "t", - mitkCommandLineParser::File, - "Output template image.", - "File path to an image that serves as template for the output geometry.", - us::Any(), - false, false, false, mitkCommandLineParser::Input); parser.addArgument( - "registrations", "r", mitkCommandLineParser::StringList, "Registration files", "Pathes to the registrations that should be used to map the input images. If this parameter is not set, identity transforms are assumed. If this parameter is set, it must have the same number of entries then the parameter inputs. If you want to use and identity transform for a specific input, specify an empty string. The application assumes that inputs and registrations have the same order, so the n-th input should use thr n-th registration.", us::Any(), true, false, false, mitkCommandLineParser::Input); + "parameters", "p", mitkCommandLineParser::String, "Parameters", "Json string containing a json object that contains the parameters that should be passed to the algorithm as key value pairs."); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); } bool ConfigureApplicationSettings(std::map parsedArgs, Settings& settings) { try { if (parsedArgs.size() == 0) return false; settings.movingFileName = us::any_cast(parsedArgs["moving"]); settings.targetFileName = us::any_cast(parsedArgs["target"]); settings.outFileName = us::any_cast(parsedArgs["output"]); settings.algFileName = us::any_cast(parsedArgs["algorithm"]); + if (parsedArgs.count("parameters") > 0) + { + settings.parameters = us::any_cast(parsedArgs["parameters"]); + } } catch (...) { return false; } return true; } map::deployment::RegistrationAlgorithmBasePointer loadAlgorithm(const Settings& settings) { map::deployment::RegistrationAlgorithmBasePointer spAlgorithmBase = nullptr; std::cout << std::endl << "Load registration algorithm..." << std::endl; map::deployment::DLLHandle::Pointer spHandle = nullptr; spHandle = map::deployment::openDeploymentDLL(settings.algFileName); if (spHandle.IsNull()) { mapDefaultExceptionStaticMacro(<< "Cannot open deployed registration algorithm file."); } std::cout << "... libary opened..." << std::endl; std::cout << "Algorithm information: " << std::endl; spHandle->getAlgorithmUID().Print(std::cout, 2); std::cout << std::endl; //Now load the algorthm from DLL spAlgorithmBase = map::deployment::getRegistrationAlgorithm(spHandle); if (spAlgorithmBase.IsNotNull()) { std::cout << "... done" << std::endl << std::endl; } else { mapDefaultExceptionStaticMacro(<< "Cannot create algorithm instance"); } return spAlgorithmBase; }; mitk::Image::Pointer ExtractFirstFrame(const mitk::Image* dynamicImage) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(dynamicImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } +template +map::core::MetaPropertyBase::Pointer +CheckCastAndSetProp(const nlohmann::json& value) +{ + map::core::MetaPropertyBase::Pointer prop; + + try + { + const auto castedValue = value.get(); + prop = map::core::MetaProperty::New(castedValue).GetPointer(); + } + catch (const std::exception& e) + { + MITK_ERROR << "Cannot convert value \"" << value << "\" into type: " << typeid(TValueType).name() << ". Details: " << e.what(); + } + catch (...) + { + MITK_ERROR << "Unkown error. Cannot convert value \"" << value << "\" into type: " << typeid(TValueType).name(); + } + + return prop; +}; + +template +map::core::MetaPropertyBase::Pointer +CheckCastAndSetItkArrayProp(const nlohmann::json& valueSequence) +{ + using ArrayType = ::itk::Array; + ArrayType castedValue; + map::core::MetaPropertyBase::Pointer prop; + + try + { + castedValue.SetSize(valueSequence.size()); + + typename ::itk::Array::SizeValueType index = 0; + for (const auto& element : valueSequence) + { + const auto castedElement = element.template get(); + castedValue[index] = castedElement; + } + prop = map::core::MetaProperty<::itk::Array>::New(castedValue).GetPointer(); + } + catch (const std::exception& e) + { + MITK_ERROR << "Cannot convert value \"" << valueSequence << "\" into type: " << typeid(ArrayType).name() << ". Details: " << e.what(); + } + catch (...) + { + MITK_ERROR << "Unkown error. Cannot convert value \"" << valueSequence << "\" into type: " << typeid(ArrayType).name(); + } + + return prop; +}; + +::map::core::MetaPropertyBase::Pointer +WrapIntoMetaProperty(const ::map::algorithm::MetaPropertyInfo* pInfo, const nlohmann::json& value) +{ + map::core::MetaPropertyBase::Pointer metaProp; + + if (pInfo == nullptr) + { + return metaProp; + } + + if (pInfo->getTypeInfo() == typeid(int)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(unsigned int)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(long)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(unsigned long)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(float)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(double)) { + metaProp = CheckCastAndSetProp(value); + } + else if (pInfo->getTypeInfo() == typeid(::itk::Array)) { + metaProp = CheckCastAndSetItkArrayProp< double >(value); + } + else if (pInfo->getTypeInfo() == typeid(bool)) { + metaProp = CheckCastAndSetProp< bool >(value); + } + else if (pInfo->getTypeInfo() == typeid(::map::core::String)) + { + metaProp = map::core::MetaProperty::New(value).GetPointer(); + } + + return metaProp; +}; + void OnMapAlgorithmEvent(::itk::Object*, const itk::EventObject& event, void*) { const map::events::AlgorithmEvent* pAlgEvent = dynamic_cast(&event); const map::events::AlgorithmWrapperEvent* pWrapEvent = dynamic_cast(&event); const map::events::InitializingAlgorithmEvent* pInitEvent = dynamic_cast(&event); const map::events::StartingAlgorithmEvent* pStartEvent = dynamic_cast(&event); const map::events::StoppingAlgorithmEvent* pStoppingEvent = dynamic_cast(&event); const map::events::StoppedAlgorithmEvent* pStoppedEvent = dynamic_cast(&event); const map::events::FinalizingAlgorithmEvent* pFinalizingEvent = dynamic_cast(&event); const map::events::FinalizedAlgorithmEvent* pFinalizedEvent = dynamic_cast(&event); if (pInitEvent) { std::cout <<"Initializing algorithm ..." << std::endl; } else if (pStartEvent) { std::cout <<"Starting algorithm ..." << std::endl; } else if (pStoppingEvent) { std::cout <<"Stopping algorithm ..." << std::endl; } else if (pStoppedEvent) { std::cout <<"Stopped algorithm ..." << std::endl; if (!pStoppedEvent->getComment().empty()) { std::cout <<"Stopping condition: " << pStoppedEvent->getComment() << std::endl; } } else if (pFinalizingEvent) { std::cout <<"Finalizing algorithm and results ..." << std::endl; } else if (pFinalizedEvent) { std::cout <<"Finalized algorithm ..." << std::endl; } else if (pAlgEvent && !pWrapEvent) { std::cout << pAlgEvent->getComment() << std::endl; } } int main(int argc, char* argv[]) { - std::cout << "MitkRegistrationMiniApp - Generic light weight image registration tool based on MatchPoint." << std::endl; + std::cout << "MitkMatchImage - Generic light weight image registration tool based on MatchPoint." << std::endl; Settings settings; mitkCommandLineParser parser; SetupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); if (!ConfigureApplicationSettings(parsedArgs, settings)) { MITK_ERROR << "Command line arguments are invalid. To see the correct usage please call with -h or --help to show the help information."; return EXIT_FAILURE; }; // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } std::cout << std::endl << "*******************************************" << std::endl; std::cout << "Moving file: " << settings.movingFileName << std::endl; std::cout << "Target file: " << settings.targetFileName << std::endl; std::cout << "Output file: " << settings.outFileName << std::endl; std::cout << "Algorithm location: " << settings.algFileName << std::endl; - //load algorithm try { auto algorithm = loadAlgorithm(settings); auto command = ::itk::CStyleCommand::New(); command->SetCallback(OnMapAlgorithmEvent); algorithm->AddObserver(::map::events::AlgorithmEvent(), command); + auto metaPropInterface = dynamic_cast(algorithm.GetPointer()); + + if (!settings.parameters.empty()) + { + if (nullptr == metaPropInterface) + { + MITK_WARN << "loaded algorithm does not support custom parameterization. Passed user parameters are ignored."; + } + else + { + nlohmann::json paramMap; + + std::string parseError = ""; + try + { + paramMap = nlohmann::json::parse(settings.parameters); + } + catch (const std::exception& e) + { + parseError = e.what(); + } + if (!parseError.empty()) + { + mitkThrow() << "Cannot parametrize algorithm. Passed JSON parameter string seems to be invalid. Passed string: \"" << settings.parameters << "\". Error details: " << parseError; + } + + std::cout << "Configuring algorithm with user specified parameters ..." << std::endl; + + for (const auto& [key, val] : paramMap.items()) + { + const auto info = metaPropInterface->getPropertyInfo(key); + + if (info.IsNotNull()) + { + if (info->isWritable()) + { + std::cout << "Set meta property: " << key << " = " << val << std::endl; + ::map::core::MetaPropertyBase::Pointer prop = WrapIntoMetaProperty(info, val); + if (prop.IsNull()) + { + mitkThrow() << "Error. Cannot set specified meta property. Type conversion is not supported or value cannot be converted into type. Property name: " << info->getName() << "; property type: " << info->getTypeName(); + } + else + { + metaPropInterface->setProperty(key, prop); + } + } + else + { + mitkThrow() << "Cannot parametrize algorithm. A passed parameter is not writable for the algorithm. Violating parameter: \"" << key << "\"."; + } + } + else + { + auto knownProps = metaPropInterface->getPropertyInfos(); + std::ostringstream knownPropsNameString; + for (const auto& knownProp : knownProps) + { + knownPropsNameString << knownProp->getName() << "; "; + } + mitkThrow() << "Cannot parametrize algorithm. A parameter is unkown to algorithm. Unkown passed parameter: \"" << key << "\". Known parameters: " << knownPropsNameString.str(); + } + } + } + } + std::cout << "Load moving data..." << std::endl; auto movingImage = mitk::IOUtil::Load(settings.movingFileName); if (movingImage.IsNull()) { MITK_ERROR << "Cannot load moving image."; return EXIT_FAILURE; } if (movingImage->GetTimeSteps() > 1) { movingImage = mitk::SelectImageByTimeStep(movingImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Moving image has multiple time steps. Use first time step for registartion." << std::endl; } std::cout << "Load target data..." << std::endl; auto targetImage = mitk::IOUtil::Load(settings.targetFileName); if (targetImage.IsNull()) { MITK_ERROR << "Cannot load target image."; return EXIT_FAILURE; } if (targetImage->GetTimeSteps() > 1) { targetImage = mitk::SelectImageByTimeStep(targetImage, 0)->Clone(); //we have to clone because SelectImageByTimeStep //only generates as new view of the data and we //are overwriting the only smartpointer to the source. std::cout << "Target image has multiple time steps. Use first time step for registartion." << std::endl; } std::cout << "Start registration...." << std::endl; mitk::MAPAlgorithmHelper helper(algorithm); helper.SetData(movingImage, targetImage); ::itk::StdStreamLogOutput::Pointer spStreamLogOutput = ::itk::StdStreamLogOutput::New(); spStreamLogOutput->SetStream(std::cout); map::core::Logbook::addAdditionalLogOutput(spStreamLogOutput); auto registration = helper.GetRegistration(); // wrap the registration in a data node if (registration.IsNull()) { MITK_ERROR << "No valid registration generated"; return EXIT_FAILURE; } auto regWrapper = mitk::MAPRegistrationWrapper::New(registration); std::cout << "Store registration...." << std::endl; mitk::IOUtil::Save(regWrapper, settings.outFileName); } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } return EXIT_SUCCESS; } diff --git a/Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp b/Modules/MatchPointRegistration/cmdapps/StitchImages.cpp similarity index 100% rename from Modules/MatchPointRegistration/cmdapps/StitchImagesMiniApp.cpp rename to Modules/MatchPointRegistration/cmdapps/StitchImages.cpp diff --git a/Modules/ModelFit/src/Common/mitkFormulaParser.cpp b/Modules/ModelFit/src/Common/mitkFormulaParser.cpp index ab6f4e85e8..e833d4d10a 100644 --- a/Modules/ModelFit/src/Common/mitkFormulaParser.cpp +++ b/Modules/ModelFit/src/Common/mitkFormulaParser.cpp @@ -1,288 +1,287 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include "mitkFormulaParser.h" #include "mitkFresnel.h" namespace qi = boost::spirit::qi; namespace ascii = boost::spirit::ascii; namespace phx = boost::phoenix; typedef std::string::const_iterator Iter; typedef ascii::space_type Skipper; namespace qi = boost::spirit::qi; namespace mitk { /*! * @brief Transforms the given number from degrees to radians and returns it. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] deg A scalar value in degrees. * @return The given value in radians. */ template inline T deg2rad(const T deg) { return deg * boost::math::constants::pi() / static_cast(180); } /*! * @brief Returns the cosine of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose cosine should be returned. * @return The cosine of the given degree scalar. */ template inline T cosd(const T t) { return std::cos(deg2rad(t)); } /*! * @brief Returns the sine of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose sine should be returned. * @return The sine of the given degree scalar. */ template inline T sind(const T t) { return std::sin(deg2rad(t)); } /*! * @brief Returns the tangent of the given degree scalar. * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t A scalar value in degrees whose tangent should be returned. * @return The tangent of the given degree scalar. */ template inline T tand(const T t) { return std::tan(deg2rad(t)); } /*! * @brief Returns the fresnel integral sine at the given x-coordinate. * @details Code for "fresnel_s()" (fresnel.cpp and fresnel.h) taken as-is from the GNU * Scientific Library (https://www.gnu.org/software/gsl/). * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t The x-coordinate at which the fresnel integral sine should be returned. * @return The fresnel integral sine at the given x-coordinate. */ template T fresnelS(const T t) { T x = t / boost::math::constants::root_half_pi(); return static_cast(fresnel_s(x) / boost::math::constants::root_two_div_pi()); } /*! * @brief Returns the fresnel integral cosine at the given x-coordinate. * @details Code for "fresnel_c()" (fresnel.cpp and fresnel.h) taken as-is from the GNU * Scientific Library (https://www.gnu.org/software/gsl/). * @tparam T The scalar type that represents a value (e.g. double). * @param[in] t The x-coordinate at which the fresnel integral cosine should be returned. * @return The fresnel integral cosine at the given x-coordinate. */ template T fresnelC(const T t) { T x = t / boost::math::constants::root_half_pi(); return static_cast(fresnel_c(x) / boost::math::constants::root_two_div_pi()); } /*! * @brief The grammar that defines the language (i.e. what is allowed) for the parser. */ class Grammar : public qi::grammar { /*! * @brief Helper structure that makes it easier to dynamically call any * one-parameter-function by overloading the @c () operator. */ struct func1_ { // Required for Phoenix 3+ template struct result; /*! * @brief Helper structure that is needed for compatibility with * @c boost::phoenix. * @tparam Functor Type of the functor (this struct). * @tparam Function Type of the function that should be called. * @tparam Arg1 Type of the argument the function should be called with. * */ template struct result { /*! @brief The result structure always needs this typedef */ typedef Arg1 type; }; /*! * @brief Calls the function @b f with the argument @b a1 and returns the * result. * The result always has the same type as the argument. * @tparam Function Type of the function that should be called. * @tparam Arg1 Type of the argument the function should be called with. * @param[in] f The function that should be called. * @param[in] a1 The argument the function should be called with. * @return The result of the called function. */ template Arg1 operator()(const Function f, const Arg1 a1) const { return f(a1); } }; /*! * @brief Helper structure that maps strings to function calls so that parsing e.g. * @c "cos(0)" actually calls the @c std::cos function with parameter @c 1 so it * returns @c 0. */ class unaryFunction_ : public qi::symbols::value_type, FormulaParser::ValueType(*)(FormulaParser::ValueType)> { public: /*! * @brief Constructs the structure, this is where the mapping takes place. */ unaryFunction_() { this->add ("abs", static_cast(&std::abs)) ("exp", static_cast(&std::exp)) // @TODO: exp ignores division by zero ("sin", static_cast(&std::sin)) ("cos", static_cast(&std::cos)) ("tan", static_cast(&std::tan)) ("sind", &sind) ("cosd", &cosd) ("tand", &tand) ("fresnelS", &fresnelS) ("fresnelC", &fresnelC); } } unaryFunction; public: /*! * @brief Constructs the grammar with the given formula parser. * @param[in, out] formulaParser The formula parser this grammar is for - so it can * access its variable map. */ Grammar(FormulaParser& formulaParser) : Grammar::base_type(start) { using qi::_val; using qi::_1; using qi::_2; using qi::char_; using qi::alpha; using qi::alnum; using qi::double_; using qi::as_string; phx::function func1; start = expression > qi::eoi; expression = term[_val = _1] >> *(('+' >> term[_val += _1]) | ('-' >> term[_val -= _1])); term = factor[_val = _1] >> *(('*' >> factor[_val *= _1]) | ('/' >> factor[_val /= _1])); - factor = primary[_val = _1]; - /*! @TODO: Repair exponentiation */ - //>> *('^' >> factor[phx::bind(std::pow, _val, _1)]); + factor = primary[_val = _1] + >> *('^' >> primary[_val = phx::bind(std::pow, _val, _1)]); variable = as_string[alpha >> *(alnum | char_('_'))] [_val = phx::bind(&FormulaParser::lookupVariable, &formulaParser, _1)]; primary = double_[_val = _1] | '(' >> expression[_val = _1] >> ')' | ('-' >> primary[_val = -_1]) | ('+' >> primary[_val = _1]) | (unaryFunction >> '(' >> expression >> ')')[_val = func1(_1, _2)] | variable[_val = _1]; } /*! the rules of the grammar. */ qi::rule start; qi::rule expression; qi::rule term; qi::rule factor; qi::rule variable; qi::rule primary; }; FormulaParser::FormulaParser(const VariableMapType* variables) : m_Variables(variables) {} FormulaParser::ValueType FormulaParser::parse(const std::string& input) { std::string::const_iterator iter = input.begin(); std::string::const_iterator end = input.end(); FormulaParser::ValueType result = static_cast(0); try { if (!qi::phrase_parse(iter, end, Grammar(*this), ascii::space, result)) { mitkThrowException(FormulaParserException) << "Could not parse '" << input << "': Grammar could not be applied to the input " << "at all."; } } catch (qi::expectation_failure& e) { std::string parsed = ""; for (Iter i = input.begin(); i != e.first; i++) { parsed += *i; } mitkThrowException(FormulaParserException) << "Error while parsing '" << input << "': Unexpected character '" << *e.first << "' after '" << parsed << "'"; } return result; }; FormulaParser::ValueType FormulaParser::lookupVariable(const std::string var) { if (m_Variables == nullptr) { mitkThrowException(FormulaParserException) << "Map of variables is empty"; } try { return m_Variables->at(var); } catch (std::out_of_range&) { mitkThrowException(FormulaParserException) << "No variable '" << var << "' defined in lookup"; } }; } diff --git a/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp b/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp index 3f821e1357..404182ea47 100644 --- a/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp +++ b/Modules/ModelFit/src/Models/mitkGenericParamModelFactory.cpp @@ -1,45 +1,47 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkGenericParamModelFactory.h" #include "mitkGenericParamModelParameterizer.h" mitk::GenericParamModelFactory::GenericParamModelFactory() = default; mitk::GenericParamModelFactory::~GenericParamModelFactory() = default; mitk::ModelParameterizerBase::ParametersType mitk::GenericParamModelFactory::GetDefaultInitialParameterization() const { return GenericParamModelParameterizer::New()->GetDefaultInitialParameterization(); }; mitk::ModelParameterizerBase::Pointer mitk::GenericParamModelFactory::DoCreateParameterizer( const mitk::modelFit::ModelFitInfo* fit) const { mitk::ModelParameterizerBase::Pointer result; GenericParamModelParameterizer::Pointer modelParameterizer = GenericParamModelParameterizer::New(); auto paramCount = fit->staticParamMap.Get( GenericParamModel::NAME_STATIC_PARAMETER_number); modelParameterizer->SetNumberOfParameters(paramCount[0]); + modelParameterizer->SetFunctionString(fit->function); + result = modelParameterizer.GetPointer(); return result; }; diff --git a/Modules/ModelFit/test/mitkFormulaParserTest.cpp b/Modules/ModelFit/test/mitkFormulaParserTest.cpp index 1b91a8a70f..f372b79303 100644 --- a/Modules/ModelFit/test/mitkFormulaParserTest.cpp +++ b/Modules/ModelFit/test/mitkFormulaParserTest.cpp @@ -1,225 +1,224 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkTestingMacros.h" #include "mitkFormulaParser.h" using namespace mitk; #define TEST_NOTHROW(expression, MSG) \ do \ { \ MITK_TEST_OUTPUT_NO_ENDL(<< MSG) \ bool test_caught = false; \ try \ { \ expression; \ } \ catch(...) \ { \ test_caught = true; \ MITK_TEST_FAILED_MSG(<< "An unwanted exception was thrown"); \ } \ if(!test_caught) \ { \ MITK_TEST_OUTPUT(<< " [PASSED]") \ mitk::TestManager::GetInstance()->TestPassed(); \ } \ } while(0) /*! * @author Sascha Diatschuk */ class FormulaParserTests { public: static void TestConstructor() { std::map varMap; FormulaParser *nullParser = nullptr, *parser = nullptr; TEST_NOTHROW(nullParser = new FormulaParser(nullptr), "Testing constructor with NULL argument"); TEST_NOTHROW(parser = new FormulaParser(&varMap), "Testing constructor with valid argument"); delete nullParser; delete parser; } static void TestLookupVariable() { // variable map is NULL FormulaParser *nullParser = new FormulaParser(nullptr); MITK_TEST_FOR_EXCEPTION(FormulaParserException, nullParser->lookupVariable("test")); delete nullParser; // variable map is empty std::map varMap; FormulaParser *parser = new FormulaParser(&varMap); MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->lookupVariable("test")); // lookup should succeed double var; varMap["test"] = 17; TEST_NOTHROW(var = parser->lookupVariable("test"), "Testing if lookupVariable throws unwanted exceptions"); MITK_TEST_CONDITION_REQUIRED(var == 17, "Testing if lookupVariable returns the correct value"); delete parser; } static void TestParse() { std::map varMap; varMap["test"] = 17; FormulaParser *parser = new FormulaParser(&varMap); // empty string MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("")); // grammar can't process string MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("_")); // unexpected character MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("5=")); // unknown variable MITK_TEST_FOR_EXCEPTION(FormulaParserException, parser->parse("a")); double d; // addition TEST_NOTHROW(d = parser->parse("1+2"), "Testing if addition throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 3, "Testing if addition produces the correct result"); // subtraction TEST_NOTHROW(d = parser->parse("5-1"), "Testing if subtraction throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 4, "Testing if subtraction produces the correct result"); // multiplication TEST_NOTHROW(d = parser->parse("3*4"), "Testing if multiplication throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 12, "Testing if multiplication produces the correct result"); // division TEST_NOTHROW(d = parser->parse("28/4"), "Testing if division throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 7, "Testing if division produces the correct result"); - /*! @TODO: Reactivate as soon as exponentiation works again */ // exponentiation - //TEST_NOTHROW(d = parser->parse("2^3"), - // "Testing if exponentiation throws an unwanted exception"); - //MITK_TEST_CONDITION_REQUIRED(d == 8, - // "Testing if exponentiation produces the correct result"); + TEST_NOTHROW(d = parser->parse("2^3"), + "Testing if exponentiation throws an unwanted exception"); + MITK_TEST_CONDITION_REQUIRED(d == 8, + "Testing if exponentiation produces the correct result"); // algebraic signs TEST_NOTHROW(d = parser->parse("-7 + +1 - -1"), "Testing if algebraic signs throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == -5, "Testing if algebraic signs produce the correct result"); // parentheses TEST_NOTHROW(d = parser->parse("(1+2)*(4-2)"), "Testing if parentheses throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 6, "Testing if parentheses produce the correct result"); // variables TEST_NOTHROW(d = parser->parse("2*test-test"), "Testing if variables throw an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 17, "Testing if variables produce the correct result"); // abs TEST_NOTHROW(d = parser->parse("abs(-5)"), "Testing if abs throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d == 5, "Testing if abs produces the correct result"); const double eps = 0.0001; // exp TEST_NOTHROW(d = parser->parse("exp(1)"), "Testing if exp throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 2.71828182846) < eps, "Testing if exp produces the correct result"); // sin TEST_NOTHROW(d = parser->parse("sin(1.57079632679)"), "Testing if sin throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 1) < eps, "Testing if sin produces the correct result"); // cos TEST_NOTHROW(d = parser->parse("cos(3.14159265359)"), "Testing if cos throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d + 1) < eps, "Testing if cos produces the correct result"); // tan TEST_NOTHROW(d = parser->parse("tan(0.46364760899)"), "Testing if tan throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.5) < eps, "Testing if tan produces the correct result"); // sind TEST_NOTHROW(d = parser->parse("sind(145)"), "Testing if sind throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.57357643635) < eps, "Testing if sind produces the correct result"); // cosd TEST_NOTHROW(d = parser->parse("cosd(90)"), "Testing if cosd throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(d < eps, "Testing if cosd produces the correct result"); // tand TEST_NOTHROW(d = parser->parse("tand(15)"), "Testing if tand throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.26794919243) < eps, "Testing if tand produces the correct result"); // fresnelS TEST_NOTHROW(d = parser->parse("fresnelS(1)"), "Testing if fresnelS throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.310268) < eps, "Testing if fresnelS produces the correct result"); TEST_NOTHROW(d = parser->parse("fresnelC(1)"), "Testing if fresnelC throws an unwanted exception"); MITK_TEST_CONDITION_REQUIRED(std::abs(d - 0.904524) < eps, "Testing if fresnelC produces the correct result"); delete parser; } }; int mitkFormulaParserTest(int, char *[]) { MITK_TEST_BEGIN("FormulaParser Test"); FormulaParserTests::TestConstructor(); FormulaParserTests::TestLookupVariable(); FormulaParserTests::TestParse(); MITK_TEST_END(); } diff --git a/Modules/Pharmacokinetics/autoload/Models/mitkPharmacokineticModelsActivator.cpp b/Modules/Pharmacokinetics/autoload/Models/mitkPharmacokineticModelsActivator.cpp index b3c4088926..f406aa87b1 100644 --- a/Modules/Pharmacokinetics/autoload/Models/mitkPharmacokineticModelsActivator.cpp +++ b/Modules/Pharmacokinetics/autoload/Models/mitkPharmacokineticModelsActivator.cpp @@ -1,77 +1,73 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include //MR perfusion models #include "mitkDescriptivePharmacokineticBrixModelFactory.h" #include "mitkExtendedToftsModelFactory.h" #include "mitkStandardToftsModelFactory.h" #include "mitkTwoCompartmentExchangeModelFactory.h" -#include "mitkNumericTwoCompartmentExchangeModelFactory.h" //PET perfusion models #include "mitkOneTissueCompartmentModelFactory.h" #include "mitkExtendedOneTissueCompartmentModelFactory.h" #include "mitkTwoTissueCompartmentModelFactory.h" #include "mitkTwoTissueCompartmentFDGModelFactory.h" -#include "mitkNumericTwoTissueCompartmentModelFactory.h" namespace mitk { /* * This is the module activator for the IO aspects of the "pharmacokinetics" module. */ class PharmacokineticModelsActivator : public us::ModuleActivator { public: template void RegisterProvider(us::ModuleContext* context) { auto provider = new TProvider(); provider->RegisterService(context); m_RegisteredProviders.push_back(std::unique_ptr(provider)); } void Load(us::ModuleContext* context) override { m_RegisteredProviders.clear(); RegisterProvider >(context); RegisterProvider >(context); RegisterProvider >(context); RegisterProvider >(context); - RegisterProvider >(context); RegisterProvider >(context); RegisterProvider >(context); RegisterProvider >(context); RegisterProvider >(context); - RegisterProvider >(context); } void Unload(us::ModuleContext* ) override { } private: std::vector > m_RegisteredProviders; }; } US_EXPORT_MODULE_ACTIVATOR(mitk::PharmacokineticModelsActivator) diff --git a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp index 66828da564..918f310434 100644 --- a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp +++ b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp @@ -1,305 +1,286 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include #include std::string inFilename; std::string outFileName; mitk::Image::Pointer image; bool verbose(false); bool t1_absolute(false); bool t1_relative(false); -bool t1_flash(false); bool t2(false); float k(1.0); float te(0); float rec_time(0); float relaxivity(0); float rel_time(0); void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("MR Signal to Concentration Converter"); parser.setDescription("MiniApp that allows to convert a T1 or T2 signal image into a concentration image for perfusion analysis."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Required I/O parameters"); parser.addArgument( "input", "i", mitkCommandLineParser::File, "Input file", "input 3D+t image file", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "where to save the output concentration image.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Conversion parameters"); parser.addArgument( "t1-absolute", "", mitkCommandLineParser::Bool, "T1 absolute signal enhancement", "Activate conversion for T1 absolute signal enhancement."); parser.addArgument( "t1-relative", "", mitkCommandLineParser::Bool, "T1 relative signal enhancement", "Activate conversion for T1 relative signal enhancement."); - parser.addArgument( - "t1-flash", "", mitkCommandLineParser::Bool, "T1 turbo flash", "Activate specific conversion for T1 turbo flash sequences."); parser.addArgument( "t2", "", mitkCommandLineParser::Bool, "T2 signal conversion", "Activate conversion for T2 signal enhancement to concentration."); parser.addArgument( "k", "k", mitkCommandLineParser::Float, "Conversion factor k", "Needed for the following conversion modes: T1-absolute, T1-relative, T2. Default value is 1.", us::Any(1)); parser.addArgument( "recovery-time", "", mitkCommandLineParser::Float, "Recovery time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxivity", "", mitkCommandLineParser::Float, "Relaxivity", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxation-time", "", mitkCommandLineParser::Float, "Relaxation time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "te", "", mitkCommandLineParser::Float, "Echo time TE", "Needed for the following conversion modes: T2.", us::Any(1)); parser.beginGroup("Optional parameters"); parser.addArgument( "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; inFilename = us::any_cast(parsedArgs["input"]); outFileName = us::any_cast(parsedArgs["output"]); verbose = false; if (parsedArgs.count("verbose")) { verbose = us::any_cast(parsedArgs["verbose"]); } t1_absolute = false; if (parsedArgs.count("t1-absolute")) { t1_absolute = us::any_cast(parsedArgs["t1-absolute"]); } t1_relative = false; if (parsedArgs.count("t1-relative")) { t1_relative = us::any_cast(parsedArgs["t1-relative"]); } - t1_flash = false; - if (parsedArgs.count("t1-flash")) - { - t1_flash = us::any_cast(parsedArgs["t1-flash"]); - } t2 = false; if (parsedArgs.count("t2")) { t2 = us::any_cast(parsedArgs["t2"]); } k = 0.0; if (parsedArgs.count("k")) { k = us::any_cast(parsedArgs["k"]); } relaxivity = 0.0; if (parsedArgs.count("relaxivity")) { relaxivity = us::any_cast(parsedArgs["relaxivity"]); } rec_time = 0.0; if (parsedArgs.count("recovery-time")) { rec_time = us::any_cast(parsedArgs["recovery-time"]); } rel_time = 0.0; if (parsedArgs.count("relaxation-time")) { rel_time = us::any_cast(parsedArgs["relaxation-time"]); } te = 0.0; if (parsedArgs.count("te")) { te = us::any_cast(parsedArgs["te"]); } //consistency checks int modeCount = 0; if (t1_absolute) ++modeCount; - if (t1_flash) ++modeCount; if (t1_relative) ++modeCount; if (t2) ++modeCount; if (modeCount==0) { mitkThrow() << "Invalid program call. Please select the type of conversion."; } if (modeCount >1) { mitkThrow() << "Invalid program call. Please select only ONE type of conversion."; } if (!k && (t2 || t1_absolute || t1_relative)) { mitkThrow() << "Invalid program call. Please set 'k', if you use t1-absolute, t1-relative or t2."; } if (!te && t2) { mitkThrow() << "Invalid program call. Please set 'te', if you use t2 mode."; } - if ((!rec_time||!rel_time||!relaxivity) && t1_flash) - { - mitkThrow() << "Invalid program call. Please set 'recovery-time', 'relaxation-time' and 'relaxivity', if you use t1-flash mode."; - } return true; } void doConversion() { mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); concentrationGen->SetDynamicImage(image); - concentrationGen->SetisTurboFlashSequence(t1_flash); + //concentrationGen->SetisTurboFlashSequence(t1_flash); concentrationGen->SetAbsoluteSignalEnhancement(t1_absolute); concentrationGen->SetRelativeSignalEnhancement(t1_relative); concentrationGen->SetisT2weightedImage(t2); - if (t1_flash) - { - concentrationGen->SetRecoveryTime(rec_time); - concentrationGen->SetRelaxationTime(rel_time); - concentrationGen->SetRelaxivity(relaxivity); - } - else if (t2) + if (t2) { concentrationGen->SetT2Factor(k); concentrationGen->SetT2EchoTime(te); } else { concentrationGen->SetFactor(k); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); mitk::IOUtil::Save(concentrationImage, outFileName); std::cout << "Store result: " << outFileName << std::endl; } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); try { if (!configureApplicationSettings(parsedArgs)) { return EXIT_FAILURE; } } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered when parsing the CLI arguments."; return EXIT_FAILURE; } mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } //! [do processing] try { image = mitk::IOUtil::Load(inFilename, &readerFilterFunctor); std::cout << "Input: " << inFilename << std::endl; doConversion(); std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/Pharmacokinetics/files.cmake b/Modules/Pharmacokinetics/files.cmake index 6af6b8dd1c..48ead4c7e1 100644 --- a/Modules/Pharmacokinetics/files.cmake +++ b/Modules/Pharmacokinetics/files.cmake @@ -1,61 +1,55 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES Common/mitkAterialInputFunctionGenerator.cpp Common/mitkAIFParametrizerHelper.cpp Common/mitkConcentrationCurveGenerator.cpp Common/mitkDescriptionParameterImageGeneratorBase.cpp Common/mitkPixelBasedDescriptionParameterImageGenerator.cpp DescriptionParameters/mitkCurveDescriptionParameterBase.cpp DescriptionParameters/mitkAreaUnderTheCurveDescriptionParameter.cpp DescriptionParameters/mitkAreaUnderFirstMomentDescriptionParameter.cpp DescriptionParameters/mitkMeanResidenceTimeDescriptionParameter.cpp DescriptionParameters/mitkTimeToPeakCurveDescriptionParameter.cpp DescriptionParameters/mitkMaximumCurveDescriptionParameter.cpp Functors/mitkCurveParameterFunctor.cpp Models/mitkAIFBasedModelBase.cpp Models/mitkDescriptivePharmacokineticBrixModel.cpp Models/mitkDescriptivePharmacokineticBrixModelFactory.cpp Models/mitkDescriptivePharmacokineticBrixModelValueBasedParameterizer.cpp Models/mitkDescriptivePharmacokineticBrixModelParameterizer.cpp Models/mitkTwoCompartmentExchangeModel.cpp Models/mitkTwoCompartmentExchangeModelFactory.cpp Models/mitkTwoCompartmentExchangeModelParameterizer.cpp - Models/mitkNumericTwoCompartmentExchangeModel.cpp - Models/mitkNumericTwoCompartmentExchangeModelFactory.cpp - Models/mitkNumericTwoCompartmentExchangeModelParameterizer.cpp Models/mitkExtendedToftsModel.cpp Models/mitkExtendedToftsModelFactory.cpp Models/mitkExtendedToftsModelParameterizer.cpp Models/mitkStandardToftsModel.cpp Models/mitkStandardToftsModelFactory.cpp Models/mitkStandardToftsModelParameterizer.cpp Models/mitkOneTissueCompartmentModel.cpp Models/mitkOneTissueCompartmentModelFactory.cpp Models/mitkOneTissueCompartmentModelParameterizer.cpp Models/mitkExtendedOneTissueCompartmentModel.cpp Models/mitkExtendedOneTissueCompartmentModelFactory.cpp Models/mitkExtendedOneTissueCompartmentModelParameterizer.cpp Models/mitkTwoTissueCompartmentModel.cpp Models/mitkTwoTissueCompartmentModelFactory.cpp Models/mitkTwoTissueCompartmentModelParameterizer.cpp Models/mitkTwoTissueCompartmentFDGModel.cpp Models/mitkTwoTissueCompartmentFDGModelFactory.cpp Models/mitkTwoTissueCompartmentFDGModelParameterizer.cpp - Models/mitkNumericTwoTissueCompartmentModel.cpp - Models/mitkNumericTwoTissueCompartmentModelFactory.cpp - Models/mitkNumericTwoTissueCompartmentModelParameterizer.cpp SimulationFramework/mitkImageGenerationHelper.cpp ) set(HXX_FILES mitkDICOMSegmentationConstants.h ) set(MOC_H_FILES ) diff --git a/Modules/Pharmacokinetics/include/mitkConcentrationCurveGenerator.h b/Modules/Pharmacokinetics/include/mitkConcentrationCurveGenerator.h index d3bbcf0a67..1889ef0280 100644 --- a/Modules/Pharmacokinetics/include/mitkConcentrationCurveGenerator.h +++ b/Modules/Pharmacokinetics/include/mitkConcentrationCurveGenerator.h @@ -1,157 +1,167 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkConcentrationCurveGenerator_h #define mitkConcentrationCurveGenerator_h #include #include #include "mitkConvertToConcentrationAbsoluteFunctor.h" #include "mitkConvertToConcentrationRelativeFunctor.h" #include "MitkPharmacokineticsExports.h" namespace mitk { /** \class ConcentrationCurveGenerator * \brief Converts a given 4D mitk::Image with MR signal values into a 4D mitk::Image with corresponding contrast agent concentration values * * From a given 4D image, the Generator takes the 3D image of the first time point as baseline image. It then loops over all time steps, casts * the current 3D image to itk and passes it to the ConvertToconcentrationFunctor. The returned 3D image has now values of concentration type and is stored at its timepoint * in the return image. */ class MITKPHARMACOKINETICS_EXPORT ConcentrationCurveGenerator : public itk::Object { public: mitkClassMacroItkParent(ConcentrationCurveGenerator, itk::Object); itkNewMacro(Self); //typedef itk::Image ImageType; typedef itk::Image ConvertedImageType; /** Getter and Setter for 4D mitk::Image*/ itkSetConstObjectMacro(DynamicImage,Image); itkGetConstObjectMacro(DynamicImage,Image); /** Parameters Relevant for conversion Calculation; Have to be Set externally (Sequence Dependend)*/ itkSetMacro(RelaxationTime, double); itkGetConstReferenceMacro(RelaxationTime, double); itkSetMacro(Relaxivity, double); itkGetConstReferenceMacro(Relaxivity, double); itkSetMacro(RecoveryTime, double); itkGetConstReferenceMacro(RecoveryTime, double); + itkSetMacro(RepetitionTime, double); + itkGetConstReferenceMacro(RepetitionTime, double); + itkSetMacro(FlipAngle, double); itkGetConstReferenceMacro(FlipAngle, double); + itkSetMacro(FlipAnglePDW, double); + itkGetConstReferenceMacro(FlipAnglePDW, double); + itkSetMacro(Factor, double); itkGetConstReferenceMacro(Factor, double); - /** Getter and Setter for T10 Map image*/ - itkSetConstObjectMacro(T10Image,Image); - itkGetConstObjectMacro(T10Image,Image); + /** Getter and Setter for PDW Map image*/ + itkSetConstObjectMacro(PDWImage,Image); + itkGetConstObjectMacro(PDWImage,Image); itkSetMacro(T2Factor, double); itkGetConstReferenceMacro(T2Factor, double); itkSetMacro(T2EchoTime, double); itkGetConstReferenceMacro(T2EchoTime, double); /** @brief Calls Convert and returns the 4D mitk::image in Concentration units*/ itkSetMacro(BaselineStartTimeStep, unsigned int); itkGetConstReferenceMacro(BaselineStartTimeStep, unsigned int); itkSetMacro(BaselineEndTimeStep, unsigned int); itkGetConstReferenceMacro(BaselineEndTimeStep, unsigned int); itkSetMacro(isTurboFlashSequence,bool); itkGetConstReferenceMacro(isTurboFlashSequence,bool); itkSetMacro(AbsoluteSignalEnhancement,bool); itkGetConstReferenceMacro(AbsoluteSignalEnhancement,bool); itkSetMacro(RelativeSignalEnhancement,bool); itkGetConstReferenceMacro(RelativeSignalEnhancement,bool); itkSetMacro(UsingT1Map,bool); itkGetConstReferenceMacro(UsingT1Map,bool); itkSetMacro(isT2weightedImage,bool); itkGetConstReferenceMacro(isT2weightedImage,bool); Image::Pointer GetConvertedImage(); protected: ConcentrationCurveGenerator(); ~ConcentrationCurveGenerator() override; template mitk::Image::Pointer convertToConcentration(const itk::Image *itkInputImage, const itk::Image *itkBaselineImage); /** Calls ConvertToconcentrationFunctor for passed 3D itk::image*/ mitk::Image::Pointer ConvertSignalToConcentrationCurve(const mitk::Image* inputImage, const mitk::Image* baselineImage); /** @brief Takes the 3D image of the first timepoint to set as baseline image*/ void PrepareBaselineImage(); template void CalculateAverageBaselineImage(const itk::Image *itkBaselineImage); /** @brief loops over all timepoints, casts the current timepoint 3D mitk::image to itk and passes it to ConvertSignalToConcentrationCurve */ virtual void Convert(); private: Image::ConstPointer m_DynamicImage; Image::ConstPointer m_BaselineImage; - Image::ConstPointer m_T10Image; + Image::ConstPointer m_PDWImage; Image::Pointer m_ConvertSignalToConcentrationCurve_OutputImage; Image::Pointer m_ConvertedImage; bool m_isT2weightedImage; bool m_isTurboFlashSequence; bool m_AbsoluteSignalEnhancement; bool m_RelativeSignalEnhancement; bool m_UsingT1Map; double m_Factor; - //=Repetition Time TR + //=Recovery Time double m_RecoveryTime; + //=Repetition Time TR + double m_RepetitionTime; //= pre-CA T1 time double m_RelaxationTime; //= contrast agent relaxivity double m_Relaxivity; double m_FlipAngle; + double m_FlipAnglePDW; + double m_T2Factor; double m_T2EchoTime; // The baseline image is averaged from the signal within time step range [m_BaselineStartTimeStep, m_BaselineEndTimeStep]. // m_BaselineStartTimeStep is the first time frame, that is included into the baseline averaging (starting with 0). unsigned int m_BaselineStartTimeStep; // m_BaselinStopTimeStep is the last time frame, that is included into the baseline averaging. unsigned int m_BaselineEndTimeStep; }; } #endif diff --git a/Modules/Pharmacokinetics/include/mitkConvertToConcentrationViaT1Functor.h b/Modules/Pharmacokinetics/include/mitkConvertToConcentrationViaT1Functor.h index f39d68cbcc..a95b9e176d 100644 --- a/Modules/Pharmacokinetics/include/mitkConvertToConcentrationViaT1Functor.h +++ b/Modules/Pharmacokinetics/include/mitkConvertToConcentrationViaT1Functor.h @@ -1,79 +1,83 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkConvertToConcentrationViaT1Functor_h #define mitkConvertToConcentrationViaT1Functor_h #include "itkMath.h" #include "MitkPharmacokineticsExports.h" namespace mitk { template class MITKPHARMACOKINETICS_EXPORT ConvertToConcentrationViaT1CalcFunctor { public: - ConvertToConcentrationViaT1CalcFunctor(): m_relaxivity(0.0), m_TR(0.0), m_flipangle(0.0) {}; - ~ConvertToConcentrationViaT1CalcFunctor() {}; + ConvertToConcentrationViaT1CalcFunctor(): m_relaxivity(0.0), m_TR(0.0), m_flipangle(0.0), m_flipanglePDW(0.0) {}; + ~ConvertToConcentrationViaT1CalcFunctor() {}; - void initialize(double relaxivity, double TR, double flipangle) + void initialize(double relaxivity, double TR, double flipangle, double flipanglePDW) { - - m_relaxivity = relaxivity; - m_TR = TR; - m_flipangle = flipangle; + m_relaxivity = relaxivity; + m_TR = TR; + m_flipangle = flipangle; + m_flipanglePDW = flipanglePDW; } bool operator!=( const ConvertToConcentrationViaT1CalcFunctor & other) const { return !(*this == other); } bool operator==( const ConvertToConcentrationViaT1CalcFunctor & other) const { - return (this->m_relaxivity == other.m_relaxivity) && (this->m_TR == other.m_TR) && (this->m_flipangle == other.m_flipangle); + return (this->m_relaxivity == other.m_relaxivity) && (this->m_TR == other.m_TR) && (this->m_flipangle == other.m_flipangle) && (this->m_flipanglePDW == other.m_flipanglePDW); } - inline TOutputpixel operator()( const TInputPixel1 & value, const TInputPixel2 & baseline, const TInputPixel3 & nativeT1) + inline TOutputpixel operator()( const TInputPixel1 & value, const TInputPixel2 & baseline, const TInputPixel3 & pdw) { - TOutputpixel concentration(0); - double R10, R1; - if (baseline !=0 && nativeT1 != 0 && value != 0) - { - double s = (double) value/baseline; - R10 = (double) 1/nativeT1; - double tmp1 = log(1-s + s*exp(-R10*m_TR) - exp(-R10*m_TR)* cos(m_flipangle)); - double tmp2 = (1-s*cos(m_flipangle) + s*exp(-R10*m_TR)*cos(m_flipangle) - exp(-R10*m_TR)* cos(m_flipangle)); - - R1 = (double) -1/m_TR * tmp1/tmp2 ; - - concentration = (double) (R1 - R10)/ m_relaxivity; - } - else - { - concentration = 0; - } - - return concentration; + TOutputpixel concentration(0.0); + if (baseline != 0 && pdw != 0 && value != 0) + { + // calculate signal scaling factor S0 and pre-contrast T1 relaxation time T10 + const double a = pdw * sin(m_flipangle) / (baseline * sin(m_flipanglePDW)); + const double b = (a - 1.0) / (a * cos(m_flipanglePDW) - cos(m_flipangle)); + const double T10 = static_cast((-1.0) * m_TR / log(b)); + const double lambda = exp((-1.0) * m_TR / T10); + const double S0 = pdw * (1.0-lambda * cos(m_flipanglePDW)) / ((1.0 - lambda) * sin(m_flipanglePDW)); + + // calculate T1 + const double c = (value - S0 * sin(m_flipangle)) / (value * cos(m_flipangle) - S0 * sin(m_flipangle)); + const double T1 = static_cast((-1.0) * m_TR / log(c)); + + //calculate concentration + concentration = static_cast((1.0 / T1 - 1.0 / T10) / (m_relaxivity/1000.0)); + } + else + { + concentration = 0.0; + } + + return concentration; } private: - double m_relaxivity; - double m_TR; - double m_flipangle; - + double m_relaxivity; + double m_TR; + double m_flipangle; + double m_flipanglePDW; }; } #endif diff --git a/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModel.h b/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModel.h deleted file mode 100644 index b8bcc20432..0000000000 --- a/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModel.h +++ /dev/null @@ -1,128 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkNumericTwoCompartmentExchangeModel_h -#define mitkNumericTwoCompartmentExchangeModel_h - -#include "mitkAIFBasedModelBase.h" -#include "MitkPharmacokineticsExports.h" - - -namespace mitk -{ - /** @class NumericTwoCompartmentExchangeModel - * @brief Implementation of the numeric model function of the 2 Compartment Exchange model, using an Aterial Input Function - * The Model calculates the measured Concentration-Time-Curve from the mass balance equations of the 2-tissue compartent Model - * - * vp * dCp(t)/dt = F * (CA(t) - Cp(t)) - PS * (Cp(t) - Ci(t)) - * ve * dCi(t)/dt = PS * (Cp(t) - Ci(t)) - * - * with concentration curve Cp(t) of the Blood Plasma p and Ce(t) of the Extracellular Extravascular Space(EES)(interstitial volume). CA(t) is the aterial concentration, i.e. the AIF - * Cp(t) and Ce(t) are found numerical via Runge-Kutta methode, implemented in Boosts numeric library ODEINT. Here we use a runge_kutta_cash_karp54 stepper with - * adaptive step size and error controll. - * From the resulting curves Cp(t) and Ce(t) the measured concentration Ctotal(t) is found vial - * - * Ctotal(t) = vp * Cp(t) + ve * Ce(t) - * - * where vp=Vp/VT and ve=Ve/VT are the portion of Plasma/EES volume Vp/Ve of the total volume VT respectively. - * The parameters PS, F, vp and ve are subject to the fitting routine*/ - - class MITKPHARMACOKINETICS_EXPORT NumericTwoCompartmentExchangeModel : public AIFBasedModelBase - { - - public: - typedef NumericTwoCompartmentExchangeModel Self; - typedef AIFBasedModelBase Superclass; - typedef itk::SmartPointer< Self > Pointer; - typedef itk::SmartPointer< const Self > ConstPointer; - - - /** Method for creation through the object factory. */ - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - /** Run-time type information (and related methods). */ - itkTypeMacro(NumericTwoCompartmentExchangeModel, ModelBase); - - typedef std::vector state_type; - - - static const std::string MODEL_DISPLAY_NAME; - - static const std::string NAME_PARAMETER_F; - static const std::string NAME_PARAMETER_PS; - static const std::string NAME_PARAMETER_ve; - static const std::string NAME_PARAMETER_vp; - static const std::string NAME_STATIC_PARAMETER_ODEINTStepSize; - - static const std::string UNIT_PARAMETER_F; - static const std::string UNIT_PARAMETER_PS; - static const std::string UNIT_PARAMETER_ve; - static const std::string UNIT_PARAMETER_vp; - - static const unsigned int POSITION_PARAMETER_F; - static const unsigned int POSITION_PARAMETER_PS; - static const unsigned int POSITION_PARAMETER_ve; - static const unsigned int POSITION_PARAMETER_vp; - - static const unsigned int NUMBER_OF_PARAMETERS; - - std::string GetModelDisplayName() const override; - - std::string GetModelType() const override; - - itkGetConstReferenceMacro(ODEINTStepSize, double); - itkSetMacro(ODEINTStepSize, double); - - - ParameterNamesType GetParameterNames() const override; - ParametersSizeType GetNumberOfParameters() const override; - - ParamterUnitMapType GetParameterUnits() const override; - - ParameterNamesType GetStaticParameterNames() const override; - ParametersSizeType GetNumberOfStaticParameters() const override; - - - protected: - NumericTwoCompartmentExchangeModel(); - ~NumericTwoCompartmentExchangeModel() override; - - /** - * Actual implementation of the clone method. This method should be reimplemeted - * in subclasses to clone the extra required parameters. - */ - itk::LightObject::Pointer InternalClone() const override; - - ModelResultType ComputeModelfunction(const ParametersType& parameters) const override; - - void SetStaticParameter(const ParameterNameType& name, const StaticParameterValuesType& values) override; - StaticParameterValuesType GetStaticParameterValue(const ParameterNameType& name) const override; - - void PrintSelf(std::ostream& os, ::itk::Indent indent) const override; - - private: - - - - //No copy constructor allowed - NumericTwoCompartmentExchangeModel(const Self& source); - void operator=(const Self&); //purposely not implemented - - double m_ODEINTStepSize; - - - - }; -} - -#endif diff --git a/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModelFactory.h b/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModelFactory.h deleted file mode 100644 index f94e55ef59..0000000000 --- a/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModelFactory.h +++ /dev/null @@ -1,46 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ -#ifndef mitkNumericTwoCompartmentExchangeModelFactory_h -#define mitkNumericTwoCompartmentExchangeModelFactory_h - -#include "mitkTwoCompartmentExchangeModelFactoryBase.h" -#include "mitkNumericTwoCompartmentExchangeModelParameterizer.h" - -namespace mitk -{ - - class MITKPHARMACOKINETICS_EXPORT NumericTwoCompartmentExchangeModelFactory : public - mitk::TwoCompartmentExchangeModelFactoryBase - { - public: - mitkClassMacro(NumericTwoCompartmentExchangeModelFactory, - TwoCompartmentExchangeModelFactoryBase); - itkFactorylessNewMacro(Self); - - protected: - - ModelParameterizerBase::Pointer DoCreateParameterizer(const modelFit::ModelFitInfo* fit) const override; - - NumericTwoCompartmentExchangeModelFactory(); - - ~NumericTwoCompartmentExchangeModelFactory() override; - - private: - - //No copy constructor allowed - NumericTwoCompartmentExchangeModelFactory(const Self& source); - void operator=(const Self&); //purposely not implemented - - }; - -} -#endif diff --git a/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModelParameterizer.h b/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModelParameterizer.h deleted file mode 100644 index 076952c6c3..0000000000 --- a/Modules/Pharmacokinetics/include/mitkNumericTwoCompartmentExchangeModelParameterizer.h +++ /dev/null @@ -1,77 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkNumericTwoCompartmentExchangeModelParameterizer_h -#define mitkNumericTwoCompartmentExchangeModelParameterizer_h - -#include "mitkAIFBasedModelParameterizerBase.h" -#include "mitkNumericTwoCompartmentExchangeModel.h" - -#include "MitkPharmacokineticsExports.h" - -namespace mitk -{ - - class MITKPHARMACOKINETICS_EXPORT NumericTwoCompartmentExchangeModelParameterizer : public - mitk::AIFBasedModelParameterizerBase - { - public: - typedef NumericTwoCompartmentExchangeModelParameterizer Self; - typedef mitk::AIFBasedModelParameterizerBase Superclass; - typedef itk::SmartPointer< Self > Pointer; - typedef itk::SmartPointer< const Self > ConstPointer; - - itkTypeMacro(NumericTwoCompartmentExchangeModelParameterizer, - mitk::AIFBasedModelParameterizerBase); - itkFactorylessNewMacro(Self); - - typedef Superclass::ModelBaseType ModelBaseType; - typedef Superclass::ModelBasePointer ModelBasePointer; - - typedef Superclass::ModelType ModelType; - typedef ModelType::Pointer ModelPointer; - - typedef Superclass::StaticParameterValueType StaticParameterValueType; - typedef Superclass::StaticParameterValuesType StaticParameterValuesType; - typedef Superclass::StaticParameterMapType StaticParameterMapType; - - typedef Superclass::IndexType IndexType; - - itkSetMacro(ODEINTStepSize, double); - itkGetConstReferenceMacro(ODEINTStepSize, double); - - /** Returns the global static parameters for the model. - * @remark this default implementation assumes only AIF and its timegrid as static parameters. - * Reimplement in derived classes to change this behavior.*/ - StaticParameterMapType GetGlobalStaticParameters() const override; - - - /** This function returns the default parameterization (e.g. initial parametrization for fitting) - defined by the model developer for for the given model.*/ - ParametersType GetDefaultInitialParameterization() const override; - - protected: - - double m_ODEINTStepSize; - - NumericTwoCompartmentExchangeModelParameterizer(); - - ~NumericTwoCompartmentExchangeModelParameterizer() override; - - private: - - //No copy constructor allowed - NumericTwoCompartmentExchangeModelParameterizer(const Self& source); - void operator=(const Self&); //purposely not implemented - }; -} -#endif diff --git a/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModel.h b/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModel.h deleted file mode 100644 index 74181746b5..0000000000 --- a/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModel.h +++ /dev/null @@ -1,103 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkNumericTwoTissueCompartmentModel_h -#define mitkNumericTwoTissueCompartmentModel_h - -#include "mitkAIFBasedModelBase.h" -#include "MitkPharmacokineticsExports.h" - - -namespace mitk -{ - class MITKPHARMACOKINETICS_EXPORT NumericTwoTissueCompartmentModel : public AIFBasedModelBase - { - - public: - typedef NumericTwoTissueCompartmentModel Self; - typedef AIFBasedModelBase Superclass; - typedef itk::SmartPointer< Self > Pointer; - typedef itk::SmartPointer< const Self > ConstPointer; - - /** Method for creation through the object factory. */ - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - /** Run-time type information (and related methods). */ - itkTypeMacro(NumericTwoTissueCompartmentModel, ModelBase); - - typedef std::vector state_type; - - - /** Model Specifications */ - static const std::string MODEL_DISPLAY_NAME; - - static const std::string NAME_PARAMETER_K1; - static const std::string NAME_PARAMETER_k2; - static const std::string NAME_PARAMETER_k3; - static const std::string NAME_PARAMETER_k4; - static const std::string NAME_PARAMETER_vb; - - static const std::string UNIT_PARAMETER_K1; - static const std::string UNIT_PARAMETER_k2; - static const std::string UNIT_PARAMETER_k3; - static const std::string UNIT_PARAMETER_k4; - static const std::string UNIT_PARAMETER_vb; - - static const unsigned int POSITION_PARAMETER_K1; - static const unsigned int POSITION_PARAMETER_k2; - static const unsigned int POSITION_PARAMETER_k3; - static const unsigned int POSITION_PARAMETER_k4; - static const unsigned int POSITION_PARAMETER_vb; - - static const unsigned int NUMBER_OF_PARAMETERS; - - static const unsigned int NUMBER_OF_DERIVED_PARAMETERS; - - static const std::string MODEL_TYPE; - - - std::string GetModelDisplayName() const override; - - std::string GetModelType() const override; - - ParameterNamesType GetParameterNames() const override; - ParametersSizeType GetNumberOfParameters() const override; - ParametersSizeType GetNumberOfDerivedParameters() const override; - - ParamterUnitMapType GetParameterUnits() const override; - - protected: - NumericTwoTissueCompartmentModel(); - ~NumericTwoTissueCompartmentModel() override; - - /** - * Actual implementation of the clone method. This method should be reimplemeted - * in subclasses to clone the extra required parameters. - */ - itk::LightObject::Pointer InternalClone() const override; - - ModelResultType ComputeModelfunction(const ParametersType& parameters) const override; - - void PrintSelf(std::ostream& os, ::itk::Indent indent) const override; - - private: - - - //No copy constructor allowed - NumericTwoTissueCompartmentModel(const Self& source); - void operator=(const Self&); //purposely not implemented - - }; -} - -#endif diff --git a/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModelFactory.h b/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModelFactory.h deleted file mode 100644 index 6157d8b36a..0000000000 --- a/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModelFactory.h +++ /dev/null @@ -1,48 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkNumericTwoTissueCompartmentModelFactory_h -#define mitkNumericTwoTissueCompartmentModelFactory_h - -#include "mitkTwoTissueCompartmentModelFactoryBase.h" -#include "mitkNumericTwoTissueCompartmentModelParameterizer.h" - -namespace mitk -{ - - - class MITKPHARMACOKINETICS_EXPORT NumericTwoTissueCompartmentModelFactory : public - mitk::TwoTissueCompartmentModelFactoryBase - { - public: - mitkClassMacro(NumericTwoTissueCompartmentModelFactory, - TwoTissueCompartmentModelFactoryBase); - itkFactorylessNewMacro(Self); - - protected: - - NumericTwoTissueCompartmentModelFactory(); - - ~NumericTwoTissueCompartmentModelFactory() override; - - private: - - //No copy constructor allowed - NumericTwoTissueCompartmentModelFactory(const Self& source); - void operator=(const Self&); //purposely not implemented - - }; - -} - - -#endif diff --git a/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModelParameterizer.h b/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModelParameterizer.h deleted file mode 100644 index 07f71e2720..0000000000 --- a/Modules/Pharmacokinetics/include/mitkNumericTwoTissueCompartmentModelParameterizer.h +++ /dev/null @@ -1,65 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkNumericTwoTissueCompartmentModelParameterizer_h -#define mitkNumericTwoTissueCompartmentModelParameterizer_h - -#include "mitkAIFBasedModelParameterizerBase.h" -#include "mitkNumericTwoTissueCompartmentModel.h" - -#include "MitkPharmacokineticsExports.h" - -namespace mitk -{ - - class MITKPHARMACOKINETICS_EXPORT NumericTwoTissueCompartmentModelParameterizer : public - mitk::AIFBasedModelParameterizerBase - { - public: - typedef NumericTwoTissueCompartmentModelParameterizer Self; - typedef mitk::AIFBasedModelParameterizerBase Superclass; - typedef itk::SmartPointer< Self > Pointer; - typedef itk::SmartPointer< const Self > ConstPointer; - - itkTypeMacro(NumericTwoTissueCompartmentModelParameterizer, - mitk::AIFBasedModelParameterizerBase); - itkFactorylessNewMacro(Self); - - typedef Superclass::ModelBaseType ModelBaseType; - typedef Superclass::ModelBasePointer ModelBasePointer; - - typedef Superclass::ModelType ModelType; - typedef ModelType::Pointer ModelPointer; - - typedef Superclass::StaticParameterValueType StaticParameterValueType; - typedef Superclass::StaticParameterValuesType StaticParameterValuesType; - typedef Superclass::StaticParameterMapType StaticParameterMapType; - - typedef Superclass::IndexType IndexType; - - /** This function returns the default parameterization (e.g. initial parametrization for fitting) - defined by the model developer for for the given model.*/ - ParametersType GetDefaultInitialParameterization() const override; - - protected: - NumericTwoTissueCompartmentModelParameterizer(); - - ~NumericTwoTissueCompartmentModelParameterizer() override; - - private: - - //No copy constructor allowed - NumericTwoTissueCompartmentModelParameterizer(const Self& source); - void operator=(const Self&); //purposely not implemented - }; -} -#endif diff --git a/Modules/Pharmacokinetics/include/mitkTwoCompartmentExchangeModelDifferentialEquations.h b/Modules/Pharmacokinetics/include/mitkTwoCompartmentExchangeModelDifferentialEquations.h deleted file mode 100644 index f9e4b461fa..0000000000 --- a/Modules/Pharmacokinetics/include/mitkTwoCompartmentExchangeModelDifferentialEquations.h +++ /dev/null @@ -1,114 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkTwoCompartmentExchangeModelDifferentialEquations_h -#define mitkTwoCompartmentExchangeModelDifferentialEquations_h - -#include "mitkNumericTwoCompartmentExchangeModel.h" - -namespace mitk{ -/** @class TwoCompartmentExchangeModelDifferentialEquations - * @brief Helper Class for NumericTwoCompartmentExchangeModel: Defines the differential equations (Mass Balance Equations) in the 2 Compartment Exchange model. - * The 2 Compartment Exchange model is defined via the mass balance equations - * vp * dCp(t)/dt = F * (CA(t) - Cp(t)) - PS * (Cp(t) - Ce(t)) - * ve * dCe(t)/dt = PS * (Cp(t) - Ce(t)) - * Boost ODEINT performs a stepwise numeric integration (e.g. via Runge-Kutta method) of the initial value problem - * x' = dx/dt = f(x,t) - * It needs an operator () (a functor) that calculates dx/dt = dxdt for a given x and t. - * Parameters are F, PS, ve and vp and the time dependent Ca(t) =AIF, that is interpolated to the current step t -*/ - -class TwoCompartmentExchangeModelDifferentialEquations -{ -public: - - typedef std::vector< double > AIFType; - - /** @brief Functor for differential equation of Physiological Pharmacokinetic Brix Model - * Takes current state x = x(t) and time t and calculates the corresponding dxdt = dx/dt - */ - void operator() (const mitk::NumericTwoCompartmentExchangeModel::state_type &x, mitk::NumericTwoCompartmentExchangeModel::state_type &dxdt, const double t) - { - double Ca_t = InterpolateAIFToCurrentTimeStep(t); - -// dxdt[0] = -(this->FVP + this->PSVP)*x[0] - this->PSVP*x[1]+this->FVP*Ca_t; - dxdt[0] = (1/this->vp) * ( this->F*(Ca_t - x[0]) - this->PS*(x[0] - x[1]) ); - dxdt[1] = (1/this->ve) * this->PS * (x[0] - x[1]); - - } - - TwoCompartmentExchangeModelDifferentialEquations() : F(0), PS(0), ve(0), vp(0), m_AIF(0), m_AIFTimeGrid(0) - { - } - - /** @brief Initialize class with parameters F/Vp, PS/Vp, fi and fp that are free fit parameters*/ - void initialize(double Fp, double ps, double fi, double fp) - { - this->F = Fp; - this->PS = ps; - this->ve = fi; - this->vp = fp; - } - - - void setAIF(AIFType &aif) - { - this->m_AIF = aif; - } - - - void setAIFTimeGrid(AIFType &grid) - { - this->m_AIFTimeGrid = grid; - } - -private: - - double F; - double PS; - double ve; - double vp; - - AIFType m_AIF; - AIFType m_AIFTimeGrid; - - - /** @brief Internal routine to interpolate the AIF to the current time point t used for integration - * The numerical integration of ODEINT is performed on an adaptive timegrid (adaptive step size dt) different from the time grid of the AIF and model function. - * Thus, the AIF value Ca(t) has to be interpolated from the set AIF - */ - double InterpolateAIFToCurrentTimeStep(double t) - { - double lastValue = m_AIF[0]; - double lastTime = std::numeric_limits::min(); - - AIFType::const_iterator posITime = m_AIFTimeGrid.begin(); - AIFType::const_iterator posValue = m_AIF.begin(); - - while(t > *posITime) - { - lastValue = *posValue; - lastTime = *posITime; - ++posValue; - ++posITime; - } - double weightLast = 1 - (t - lastTime)/(*posITime - lastTime); - double weightNext = 1- (*posITime - t)/(*posITime - lastTime); - double result = weightLast * lastValue + weightNext * (*posValue); - - return result; - } - -}; -} - -#endif diff --git a/Modules/Pharmacokinetics/include/mitkTwoTissueCompartmentModelDifferentialEquations.h b/Modules/Pharmacokinetics/include/mitkTwoTissueCompartmentModelDifferentialEquations.h deleted file mode 100644 index b892bef899..0000000000 --- a/Modules/Pharmacokinetics/include/mitkTwoTissueCompartmentModelDifferentialEquations.h +++ /dev/null @@ -1,114 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#ifndef mitkTwoTissueCompartmentModelDifferentialEquations_h -#define mitkTwoTissueCompartmentModelDifferentialEquations_h -#include "mitkNumericTwoTissueCompartmentModel.h" - -namespace mitk{ -/** @class TwoTissueCompartmentModelDifferentialEquations - * @brief Helper Class for NumericTwoTissueCompartment Model: Defines the differential equations (Mass Balance Equations) in the - * 2-tissue-compartment model for dynamic PET data modeling. - * The 2-Tissue Compartment model is defined via the mass balance equations - * dC1(t)/dt = K1*Ca(t) - (k2 + k3)*C1(t) + k4*C2(t) - * dC2(t)/dt = k3*C1(t) - k4*C2(t) - * CT(t) = C_a(t)*VB + (1-VB)*(C1(t)+C2(t) - * where Ca(t) is the plasma concentration(aterial input function) - * Boost ODEINT performs a stepwise numeric integration (e.g. via Runge-Kutta method) of the initial value problem - * x' = dx/dt = f(x,t) - * It needs an operator () (a functor) that calculates dx/dt = dxdt for a given x and t. - * Parameters are K1,k2,k3,k4, VB and the time dependent Ca(t) =AIF, that is interpolated to the current step t -*/ - -class TwoTissueCompartmentModelDifferentialEquations -{ -public: - - typedef std::vector< double > AIFType; - - /** @brief Functor for differential equation of Two Tissue Compartment Model - * Takes current state x = x(t) and time t and calculates the corresponding dxdt = dx/dt - */ - void operator() (const mitk::NumericTwoTissueCompartmentModel::state_type &x, mitk::NumericTwoTissueCompartmentModel::state_type &dxdt, const double t) - { - double Ca_t = InterpolateAIFToCurrentTimeStep(t); - - dxdt[0] = this->K1*Ca_t-(this->k2+this->k3)*x[0] + this->k4*x[1]; - dxdt[1] = this->k3*x[0] - this->k4*x[1]; - } - - TwoTissueCompartmentModelDifferentialEquations() : K1(0), k2(0), k3(0), k4(0), m_AIF(0), m_AIFTimeGrid(0) - { - } - - /** @brief Initialize class with parameters K1, k2, k3 and k4 that are free fit parameters*/ - void initialize(double k_1, double k_2, double k_3, double k_4) - { - this->K1 = k_1; - this->k2 = k_2; - this->k3 = k_3; - this->k4 = k_4; - } - - - void setAIF(AIFType &aif) - { - this->m_AIF = aif; - } - - - void setAIFTimeGrid(AIFType &grid) - { - this->m_AIFTimeGrid = grid; - } - -private: - - double K1; - double k2; - double k3; - double k4; - - AIFType m_AIF; - AIFType m_AIFTimeGrid; - - - /** @brief Internal routine to interpolate the AIF to the current time point t used for integration - * The numerical integration of ODEINT is performed on an adaptive timegrid (adaptive step size dt) different from the time grid of the AIF and model function. - * Thus, the AIF value Ca(t) has to be interpolated from the set AIF - */ - double InterpolateAIFToCurrentTimeStep(double t) - { - double lastValue = m_AIF[0]; - double lastTime = std::numeric_limits::min(); - - AIFType::const_iterator posITime = m_AIFTimeGrid.begin(); - AIFType::const_iterator posValue = m_AIF.begin(); - - while(t > *posITime) - { - lastValue = *posValue; - lastTime = *posITime; - ++posValue; - ++posITime; - } - double weightLast = 1 - (t - lastTime)/(*posITime - lastTime); - double weightNext = 1- (*posITime - t)/(*posITime - lastTime); - double result = weightLast * lastValue + weightNext * (*posValue); - - return result; - } - -}; -} - -#endif diff --git a/Modules/Pharmacokinetics/src/Common/mitkConcentrationCurveGenerator.cpp b/Modules/Pharmacokinetics/src/Common/mitkConcentrationCurveGenerator.cpp index f19254394b..f1c1198126 100644 --- a/Modules/Pharmacokinetics/src/Common/mitkConcentrationCurveGenerator.cpp +++ b/Modules/Pharmacokinetics/src/Common/mitkConcentrationCurveGenerator.cpp @@ -1,324 +1,394 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkConcentrationCurveGenerator.h" #include "mitkConvertToConcentrationTurboFlashFunctor.h" #include "mitkConvertT2ConcentrationFunctor.h" #include "mitkConvertToConcentrationViaT1Functor.h" #include "mitkImageTimeSelector.h" #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include "mitkModelBase.h" #include "mitkExtractTimeGrid.h" #include "mitkArbitraryTimeGeometry.h" #include "itkNaryAddImageFilter.h" #include "mitkImageAccessByItk.h" #include "itkImageIOBase.h" #include "itkBinaryFunctorImageFilter.h" #include "itkTernaryFunctorImageFilter.h" #include #include "itkMeanProjectionImageFilter.h" mitk::ConcentrationCurveGenerator::ConcentrationCurveGenerator() : m_isT2weightedImage(false), m_isTurboFlashSequence(false), - m_AbsoluteSignalEnhancement(false), m_RelativeSignalEnhancement(0.0), m_UsingT1Map(false), m_Factor(0.0), m_RecoveryTime(0.0), m_RelaxationTime(0.0), - m_Relaxivity(0.0), m_FlipAngle(0.0), m_T2Factor(0.0), m_T2EchoTime(0.0) + m_AbsoluteSignalEnhancement(false), m_RelativeSignalEnhancement(false), m_UsingT1Map(false), m_Factor(std::numeric_limits::quiet_NaN()), + m_RecoveryTime(std::numeric_limits::quiet_NaN()), m_RepetitionTime(std::numeric_limits::quiet_NaN()), + m_RelaxationTime(std::numeric_limits::quiet_NaN()), m_Relaxivity(std::numeric_limits::quiet_NaN()), + m_FlipAngle(std::numeric_limits::quiet_NaN()), m_FlipAnglePDW(std::numeric_limits::quiet_NaN()), + m_T2Factor(std::numeric_limits::quiet_NaN()), m_T2EchoTime(std::numeric_limits::quiet_NaN()) { } mitk::ConcentrationCurveGenerator::~ConcentrationCurveGenerator() { } mitk::Image::Pointer mitk::ConcentrationCurveGenerator::GetConvertedImage() { if(this->m_DynamicImage.IsNull()) { itkExceptionMacro( << "Dynamic Image not set!"); } else { Convert(); } return m_ConvertedImage; } void mitk::ConcentrationCurveGenerator::Convert() { mitk::Image::Pointer tempImage = mitk::Image::New(); mitk::PixelType pixeltype = mitk::MakeScalarPixelType(); tempImage->Initialize(pixeltype,*this->m_DynamicImage->GetTimeGeometry()); mitk::TimeGeometry::Pointer timeGeometry = (this->m_DynamicImage->GetTimeGeometry())->Clone(); tempImage->SetTimeGeometry(timeGeometry); PrepareBaselineImage(); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(this->m_DynamicImage); for(unsigned int i = 0; i< this->m_DynamicImage->GetTimeSteps(); ++i) { imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkInputImage = imageTimeSelector->GetOutput(); mitk::Image::Pointer outputImage = mitk::Image::New(); outputImage = ConvertSignalToConcentrationCurve(mitkInputImage,this->m_BaselineImage); mitk::ImageReadAccessor accessor(outputImage); tempImage->SetVolume(accessor.GetData(), i); } this->m_ConvertedImage = tempImage; } void mitk::ConcentrationCurveGenerator::PrepareBaselineImage() { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(this->m_DynamicImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image::Pointer baselineImage; baselineImage = imageTimeSelector->GetOutput(); if (m_BaselineStartTimeStep == m_BaselineEndTimeStep) { this->m_BaselineImage = imageTimeSelector->GetOutput(); } else { try { AccessFixedDimensionByItk(this->m_DynamicImage, mitk::ConcentrationCurveGenerator::CalculateAverageBaselineImage, 4); } catch (itk::ExceptionObject & err) { std::cerr << "ExceptionObject in ConcentrationCurveGenerator::CalculateAverageBaselineImage caught!" << std::endl; std::cerr << err << std::endl; } } } template void mitk::ConcentrationCurveGenerator::CalculateAverageBaselineImage(const itk::Image *itkBaselineImage) { if (itkBaselineImage == NULL) { mitkThrow() << "Error in ConcentrationCurveGenerator::CalculateAverageBaselineImage. Input image is NULL."; } if (m_BaselineStartTimeStep > m_BaselineEndTimeStep) { mitkThrow() << "Error in ConcentrationCurveGenerator::CalculateAverageBaselineImage. End time point is before start time point."; } typedef itk::Image TPixel4DImageType; typedef itk::Image Double3DImageType; typedef itk::Image Double4DImageType; typedef itk::ExtractImageFilter ExtractImageFilterType; typedef itk::ExtractImageFilter Extract3DImageFilterType; typedef itk::MeanProjectionImageFilter MeanProjectionImageFilterType; typename MeanProjectionImageFilterType::Pointer MeanProjectionImageFilter = MeanProjectionImageFilterType::New(); typename Extract3DImageFilterType::Pointer Extract3DImageFilter = Extract3DImageFilterType::New(); typename TPixel4DImageType::RegionType region_input = itkBaselineImage->GetLargestPossibleRegion(); if (m_BaselineEndTimeStep > region_input.GetSize()[3]) { mitkThrow() << "Error in ConcentrationCurveGenerator::CalculateAverageBaselineImage. End time point is larger than total number of time points."; } typename ExtractImageFilterType::Pointer ExtractFilter = ExtractImageFilterType::New(); typename TPixel4DImageType::Pointer baselineTimeFrameImage = TPixel4DImageType::New(); typename TPixel4DImageType::RegionType extractionRegion; typename TPixel4DImageType::SizeType size_input_aux = region_input.GetSize(); size_input_aux[3] = m_BaselineEndTimeStep - m_BaselineStartTimeStep + 1; typename TPixel4DImageType::IndexType start_input_aux = region_input.GetIndex(); start_input_aux[3] = m_BaselineStartTimeStep; extractionRegion.SetSize(size_input_aux); extractionRegion.SetIndex(start_input_aux); ExtractFilter->SetExtractionRegion(extractionRegion); ExtractFilter->SetInput(itkBaselineImage); ExtractFilter->SetDirectionCollapseToSubmatrix(); try { ExtractFilter->Update(); } catch (itk::ExceptionObject & err) { std::cerr << "ExceptionObject caught!" << std::endl; std::cerr << err << std::endl; } baselineTimeFrameImage = ExtractFilter->GetOutput(); MeanProjectionImageFilter->SetProjectionDimension(3); MeanProjectionImageFilter->SetInput(baselineTimeFrameImage); try { MeanProjectionImageFilter->Update(); } catch (itk::ExceptionObject & err) { std::cerr << "ExceptionObject caught!" << std::endl; std::cerr << err << std::endl; } Extract3DImageFilter->SetInput(MeanProjectionImageFilter->GetOutput()); size_input_aux[3] = 0; start_input_aux[3] = 0; extractionRegion.SetSize(size_input_aux); extractionRegion.SetIndex(start_input_aux); Extract3DImageFilter->SetExtractionRegion(extractionRegion); Extract3DImageFilter->SetDirectionCollapseToSubmatrix(); try { Extract3DImageFilter->Update(); } catch (itk::ExceptionObject & err) { std::cerr << "ExceptionObject caught!" << std::endl; std::cerr << err << std::endl; } Image::Pointer mitkBaselineImage = Image::New(); CastToMitkImage(Extract3DImageFilter->GetOutput(), mitkBaselineImage); this->m_BaselineImage = mitkBaselineImage; } mitk::Image::Pointer mitk::ConcentrationCurveGenerator::ConvertSignalToConcentrationCurve(const mitk::Image* inputImage,const mitk::Image* baselineImage) { mitk::PixelType m_PixelType = inputImage->GetPixelType(); AccessTwoImagesFixedDimensionByItk(inputImage, baselineImage, mitk::ConcentrationCurveGenerator::convertToConcentration, 3); return m_ConvertSignalToConcentrationCurve_OutputImage; } template mitk::Image::Pointer mitk::ConcentrationCurveGenerator::convertToConcentration(const itk::Image *itkInputImage, const itk::Image *itkBaselineImage) { typedef itk::Image InputImageType; typedef itk::Image BaselineImageType; if (this->m_isT2weightedImage) { - typedef mitk::ConvertT2ConcentrationFunctor ConversionFunctorT2Type; - typedef itk::BinaryFunctorImageFilter FilterT2Type; + typedef mitk::ConvertT2ConcentrationFunctor ConversionFunctorT2Type; + typedef itk::BinaryFunctorImageFilter FilterT2Type; - ConversionFunctorT2Type ConversionT2Functor; + ConversionFunctorT2Type ConversionT2Functor; + + if (std::isnan(this->m_T2Factor)) + { + mitkThrow() << "The conversion factor k for T2-weighted images must be set."; + } + else if (std::isnan(this->m_T2EchoTime)) + { + mitkThrow() << "The echo time TE for T2-weighted images must be set."; + } + else + { ConversionT2Functor.initialize(this->m_T2Factor, this->m_T2EchoTime); + } - typename FilterT2Type::Pointer ConversionT2Filter = FilterT2Type::New(); + + + typename FilterT2Type::Pointer ConversionT2Filter = FilterT2Type::New(); ConversionT2Filter->SetFunctor(ConversionT2Functor); - ConversionT2Filter->SetInput1(itkInputImage); - ConversionT2Filter->SetInput2(itkBaselineImage); + ConversionT2Filter->SetInput1(itkInputImage); + ConversionT2Filter->SetInput2(itkBaselineImage); - ConversionT2Filter->Update(); + ConversionT2Filter->Update(); - m_ConvertSignalToConcentrationCurve_OutputImage = mitk::ImportItkImage(ConversionT2Filter->GetOutput())->Clone(); - } + m_ConvertSignalToConcentrationCurve_OutputImage = mitk::ImportItkImage(ConversionT2Filter->GetOutput())->Clone(); + } else { if(this->m_isTurboFlashSequence) { typedef mitk::ConvertToConcentrationTurboFlashFunctor ConversionFunctorTurboFlashType; typedef itk::BinaryFunctorImageFilter FilterTurboFlashType; ConversionFunctorTurboFlashType ConversionTurboFlashFunctor; - ConversionTurboFlashFunctor.initialize(this->m_RelaxationTime, this->m_Relaxivity, this->m_RecoveryTime); + + if (std::isnan(this->m_RelaxationTime)) + { + mitkThrow() << "The relaxation time must be set."; + } + else if (std::isnan(this->m_Relaxivity)) + { + mitkThrow() << "The relaxivity must be set."; + } + else if (std::isnan(this->m_RecoveryTime)) + { + mitkThrow() << "The recovery time must be set."; + } + else + { + ConversionTurboFlashFunctor.initialize(this->m_RelaxationTime, this->m_Relaxivity, this->m_RecoveryTime); + } + typename FilterTurboFlashType::Pointer ConversionTurboFlashFilter = FilterTurboFlashType::New(); ConversionTurboFlashFilter->SetFunctor(ConversionTurboFlashFunctor); ConversionTurboFlashFilter->SetInput1(itkInputImage); ConversionTurboFlashFilter->SetInput2(itkBaselineImage); ConversionTurboFlashFilter->Update(); m_ConvertSignalToConcentrationCurve_OutputImage = mitk::ImportItkImage(ConversionTurboFlashFilter->GetOutput())->Clone(); } else if(this->m_UsingT1Map) { - typename ConvertedImageType::Pointer itkT10Image = ConvertedImageType::New(); - mitk::CastToItkImage(m_T10Image, itkT10Image); + typename BaselineImageType::Pointer itkPDWImage = BaselineImageType::New(); + mitk::CastToItkImage(m_PDWImage, itkPDWImage); typedef mitk::ConvertToConcentrationViaT1CalcFunctor ConvertToConcentrationViaT1CalcFunctorType; - typedef itk::TernaryFunctorImageFilter FilterT1MapType; + typedef itk::TernaryFunctorImageFilter FilterT1MapType; ConvertToConcentrationViaT1CalcFunctorType ConversionT1MapFunctor; - ConversionT1MapFunctor.initialize(this->m_Relaxivity, this->m_RecoveryTime, this->m_FlipAngle); + + if (std::isnan(this->m_Relaxivity)) + { + mitkThrow() << "The relaxivity must be set."; + } + else if (std::isnan(this->m_RepetitionTime)) + { + mitkThrow() << "The repetition time must be set."; + } + else if (std::isnan(this->m_FlipAngle)) + { + mitkThrow() << "The flip angle must be set."; + } + else if (std::isnan(this->m_FlipAnglePDW)) + { + mitkThrow() << "The flip angle of the PDW image must be set."; + } + else + { + ConversionT1MapFunctor.initialize(this->m_Relaxivity, this->m_RepetitionTime, this->m_FlipAngle, this->m_FlipAnglePDW); + } typename FilterT1MapType::Pointer ConversionT1MapFilter = FilterT1MapType::New(); ConversionT1MapFilter->SetFunctor(ConversionT1MapFunctor); ConversionT1MapFilter->SetInput1(itkInputImage); ConversionT1MapFilter->SetInput2(itkBaselineImage); - ConversionT1MapFilter->SetInput3(itkT10Image); + ConversionT1MapFilter->SetInput3(itkPDWImage); ConversionT1MapFilter->Update(); m_ConvertSignalToConcentrationCurve_OutputImage = mitk::ImportItkImage(ConversionT1MapFilter->GetOutput())->Clone(); } else if(this->m_AbsoluteSignalEnhancement) { typedef mitk::ConvertToConcentrationAbsoluteFunctor ConversionFunctorAbsoluteType; typedef itk::BinaryFunctorImageFilter FilterAbsoluteType; ConversionFunctorAbsoluteType ConversionAbsoluteFunctor; - ConversionAbsoluteFunctor.initialize(this->m_Factor); + + if (std::isnan(this->m_Factor)) + { + mitkThrow() << "The conversion factor k must be set."; + } + else + { + ConversionAbsoluteFunctor.initialize(this->m_Factor); + } typename FilterAbsoluteType::Pointer ConversionAbsoluteFilter = FilterAbsoluteType::New(); ConversionAbsoluteFilter->SetFunctor(ConversionAbsoluteFunctor); ConversionAbsoluteFilter->SetInput1(itkInputImage); ConversionAbsoluteFilter->SetInput2(itkBaselineImage); ConversionAbsoluteFilter->Update(); m_ConvertSignalToConcentrationCurve_OutputImage = mitk::ImportItkImage(ConversionAbsoluteFilter->GetOutput())->Clone(); } else if(this->m_RelativeSignalEnhancement) { typedef mitk::ConvertToConcentrationRelativeFunctor ConversionFunctorRelativeType; typedef itk::BinaryFunctorImageFilter FilterRelativeType; ConversionFunctorRelativeType ConversionRelativeFunctor; - ConversionRelativeFunctor.initialize(this->m_Factor); + + if (std::isnan(this->m_Factor)) + { + mitkThrow() << "The conversion factor k must be set."; + } + else + { + ConversionRelativeFunctor.initialize(this->m_Factor); + } typename FilterRelativeType::Pointer ConversionRelativeFilter = FilterRelativeType::New(); ConversionRelativeFilter->SetFunctor(ConversionRelativeFunctor); ConversionRelativeFilter->SetInput1(itkInputImage); ConversionRelativeFilter->SetInput2(itkBaselineImage); ConversionRelativeFilter->Update(); m_ConvertSignalToConcentrationCurve_OutputImage = mitk::ImportItkImage(ConversionRelativeFilter->GetOutput())->Clone(); } } return m_ConvertSignalToConcentrationCurve_OutputImage; } diff --git a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModel.cpp b/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModel.cpp deleted file mode 100644 index 2d95cd5e57..0000000000 --- a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModel.cpp +++ /dev/null @@ -1,286 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkNumericTwoCompartmentExchangeModel.h" -#include "mitkAIFParametrizerHelper.h" -#include "mitkTimeGridHelper.h" -#include "mitkTwoCompartmentExchangeModelDifferentialEquations.h" -#include -#include -#include - -const std::string mitk::NumericTwoCompartmentExchangeModel::MODEL_DISPLAY_NAME = - "Numeric Two Compartment Exchange Model"; - -const std::string mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_F = "F"; -const std::string mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_PS = "PS"; -const std::string mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_ve = "ve"; -const std::string mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_vp = "vp"; - -const std::string mitk::NumericTwoCompartmentExchangeModel::UNIT_PARAMETER_F = "ml/min/100ml"; -const std::string mitk::NumericTwoCompartmentExchangeModel::UNIT_PARAMETER_PS = "ml/min/100ml"; -const std::string mitk::NumericTwoCompartmentExchangeModel::UNIT_PARAMETER_ve = "ml/ml"; -const std::string mitk::NumericTwoCompartmentExchangeModel::UNIT_PARAMETER_vp = "ml/ml"; - -const unsigned int mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_F = 0; -const unsigned int mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_PS = 1; -const unsigned int mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_ve = 2; -const unsigned int mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_vp = 3; - -const unsigned int mitk::NumericTwoCompartmentExchangeModel::NUMBER_OF_PARAMETERS = 4; - -const std::string mitk::NumericTwoCompartmentExchangeModel::NAME_STATIC_PARAMETER_ODEINTStepSize = "ODEIntStepSize"; - - -std::string mitk::NumericTwoCompartmentExchangeModel::GetModelDisplayName() const -{ - return MODEL_DISPLAY_NAME; -}; - -std::string mitk::NumericTwoCompartmentExchangeModel::GetModelType() const -{ - return "Perfusion.MR"; -}; - - -mitk::NumericTwoCompartmentExchangeModel::NumericTwoCompartmentExchangeModel() -{ - -} - -mitk::NumericTwoCompartmentExchangeModel::~NumericTwoCompartmentExchangeModel() -{ - -} - -mitk::NumericTwoCompartmentExchangeModel::ParameterNamesType mitk::NumericTwoCompartmentExchangeModel::GetStaticParameterNames() const -{ - ParameterNamesType result; - - result.push_back(NAME_STATIC_PARAMETER_AIF); - result.push_back(NAME_STATIC_PARAMETER_AIFTimeGrid); - result.push_back(NAME_STATIC_PARAMETER_ODEINTStepSize); - - return result; -} - -mitk::NumericTwoCompartmentExchangeModel::ParametersSizeType mitk::NumericTwoCompartmentExchangeModel::GetNumberOfStaticParameters() -const -{ - return 3; -} - - -void mitk::NumericTwoCompartmentExchangeModel::SetStaticParameter(const ParameterNameType& name, - const StaticParameterValuesType& values) -{ - if (name == NAME_STATIC_PARAMETER_AIF) - { - AterialInputFunctionType aif = mitk::convertParameterToArray(values); - - SetAterialInputFunctionValues(aif); - } - - if (name == NAME_STATIC_PARAMETER_AIFTimeGrid) - { - TimeGridType timegrid = mitk::convertParameterToArray(values); - - SetAterialInputFunctionTimeGrid(timegrid); - } - - if (name == NAME_STATIC_PARAMETER_ODEINTStepSize) - { - SetODEINTStepSize(values[0]); - } -}; - -mitk::NumericTwoCompartmentExchangeModel::StaticParameterValuesType mitk::NumericTwoCompartmentExchangeModel::GetStaticParameterValue( - const ParameterNameType& name) const -{ - StaticParameterValuesType result; - - if (name == NAME_STATIC_PARAMETER_AIF) - { - result = mitk::convertArrayToParameter(this->m_AterialInputFunctionValues); - } - - if (name == NAME_STATIC_PARAMETER_AIFTimeGrid) - { - result = mitk::convertArrayToParameter(this->m_AterialInputFunctionTimeGrid); - } - if (name == NAME_STATIC_PARAMETER_ODEINTStepSize) - { - result.push_back(GetODEINTStepSize()); - } - - return result; -}; - - -mitk::NumericTwoCompartmentExchangeModel::ParameterNamesType -mitk::NumericTwoCompartmentExchangeModel::GetParameterNames() const -{ - ParameterNamesType result; - - result.push_back(NAME_PARAMETER_F); - result.push_back(NAME_PARAMETER_PS); - result.push_back(NAME_PARAMETER_ve); - result.push_back(NAME_PARAMETER_vp); - - return result; -} - -mitk::NumericTwoCompartmentExchangeModel::ParametersSizeType -mitk::NumericTwoCompartmentExchangeModel::GetNumberOfParameters() const -{ - return NUMBER_OF_PARAMETERS; -} - - -mitk::NumericTwoCompartmentExchangeModel::ParamterUnitMapType -mitk::NumericTwoCompartmentExchangeModel::GetParameterUnits() const -{ - ParamterUnitMapType result; - - result.insert(std::make_pair(NAME_PARAMETER_F, UNIT_PARAMETER_F)); - result.insert(std::make_pair(NAME_PARAMETER_PS, UNIT_PARAMETER_PS)); - result.insert(std::make_pair(NAME_PARAMETER_vp, UNIT_PARAMETER_vp)); - result.insert(std::make_pair(NAME_PARAMETER_ve, UNIT_PARAMETER_ve)); - - return result; -}; - -mitk::NumericTwoCompartmentExchangeModel::ModelResultType -mitk::NumericTwoCompartmentExchangeModel::ComputeModelfunction(const ParametersType& parameters) -const -{ - typedef itk::Array ConcentrationCurveType; - typedef std::vector ConcentrationVectorType; - - if (this->m_TimeGrid.GetSize() == 0) - { - itkExceptionMacro("No Time Grid Set! Cannot Calculate Signal"); - } - - AterialInputFunctionType aterialInputFunction; - aterialInputFunction = GetAterialInputFunction(this->m_TimeGrid); - - unsigned int timeSteps = this->m_TimeGrid.GetSize(); - - /** @brief Boost::numeric::odeint works with type std::vector thus, aif and grid are converted to ModelParameters( of type std::vector) - */ - mitk::TwoCompartmentExchangeModelDifferentialEquations::AIFType aif = mitk::convertArrayToParameter( - aterialInputFunction); - mitk::TwoCompartmentExchangeModelDifferentialEquations::AIFType grid = - mitk::convertArrayToParameter(m_TimeGrid); - - mitk::TwoCompartmentExchangeModelDifferentialEquations::AIFType aifODE = aif; - aifODE.push_back(aif[timeSteps - 1]); - mitk::TwoCompartmentExchangeModelDifferentialEquations::AIFType gridODE = grid; - gridODE.push_back(grid[timeSteps - 1] + (grid[timeSteps - 1] - grid[timeSteps - 2])); - - - - //Model Parameters - double F = (double) parameters[POSITION_PARAMETER_F] / 6000.0; - double PS = (double) parameters[POSITION_PARAMETER_PS] / 6000.0; - double ve = (double) parameters[POSITION_PARAMETER_ve]; - double vp = (double) parameters[POSITION_PARAMETER_vp]; - - - /** @brief Initialize class TwoCompartmentExchangeModelDifferentialEquations defining the differential equations. AIF and Grid must be set so that at step t the aterial Concentration Ca(t) can be interpolated from AIF*/ - mitk::TwoCompartmentExchangeModelDifferentialEquations ode; - ode.initialize(F, PS, ve, vp); - ode.setAIF(aifODE); - ode.setAIFTimeGrid(gridODE); - - state_type x(2); - x[0] = 0.0; - x[1] = 0.0; - typedef boost::numeric::odeint::runge_kutta_cash_karp54 error_stepper_type; - //typedef boost::numeric::odeint::runge_kutta4< state_type > stepper_type; - - /** @brief Results of odeeint x[0] and x[1]*/ - ConcentrationVectorType Cp; - ConcentrationVectorType Ce; - ConcentrationVectorType odeTimeGrid; - - error_stepper_type stepper; - - - /** @brief Stepsize. Should be adapted by stepper (runge_kutta_cash_karp54) */ -// const double dt = 0.05; - const double dt = this->m_ODEINTStepSize; - - - /** @brief perform Step t -> t+dt to calculate approximate value x(t+dt)*/ - for (double t = 0.0; t < this->m_TimeGrid(this->m_TimeGrid.GetSize() - 1) - 2*dt; t += dt) - { - stepper.do_step(ode, x, t, dt); - Cp.push_back(x[0]); - Ce.push_back(x[1]); - odeTimeGrid.push_back(t); - } - - /** @brief transfom result of Differential equations back to itk::Array and interpolate to m_TimeGrid (they are calculated on a different grid defined by stepsize of odeint)*/ - ConcentrationCurveType plasmaConcentration = mitk::convertParameterToArray(Cp); - ConcentrationCurveType EESConcentration = mitk::convertParameterToArray(Ce); - ConcentrationCurveType rungeKuttaTimeGrid = mitk::convertParameterToArray(odeTimeGrid); - - mitk::ModelBase::ModelResultType C_Plasma = mitk::InterpolateSignalToNewTimeGrid(plasmaConcentration, rungeKuttaTimeGrid, m_TimeGrid); - mitk::ModelBase::ModelResultType C_EES = mitk::InterpolateSignalToNewTimeGrid(EESConcentration, rungeKuttaTimeGrid, m_TimeGrid); - - - //Signal that will be returned by ComputeModelFunction - mitk::ModelBase::ModelResultType signal(timeSteps); - signal.fill(0.0); - - mitk::ModelBase::ModelResultType::iterator signalPos = signal.begin(); - mitk::ModelBase::ModelResultType::const_iterator CePos = C_EES.begin(); - - mitk::ModelBase::ModelResultType::const_iterator t = this->m_TimeGrid.begin(); - mitk::ModelBase::ModelResultType::const_iterator Cin = aterialInputFunction.begin(); - - - - for (mitk::ModelBase::ModelResultType::const_iterator CpPos = C_Plasma.begin(); - CpPos != C_Plasma.end(); ++CpPos, ++CePos, ++signalPos, ++t, ++Cin) - { - *signalPos = vp * (*CpPos) + ve * (*CePos); - - } - - return signal; - -} - - - - -itk::LightObject::Pointer mitk::NumericTwoCompartmentExchangeModel::InternalClone() const -{ - NumericTwoCompartmentExchangeModel::Pointer newClone = NumericTwoCompartmentExchangeModel::New(); - - newClone->SetTimeGrid(this->m_TimeGrid); - - return newClone.GetPointer(); -} - -void mitk::NumericTwoCompartmentExchangeModel::PrintSelf(std::ostream& os, - ::itk::Indent indent) const -{ - Superclass::PrintSelf(os, indent); - - -} - - diff --git a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModelFactory.cpp b/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModelFactory.cpp deleted file mode 100644 index ef04fa136a..0000000000 --- a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModelFactory.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkNumericTwoCompartmentExchangeModelFactory.h" - - -#include "mitkNumericTwoCompartmentExchangeModelParameterizer.h" -#include "mitkAIFParametrizerHelper.h" - -mitk::NumericTwoCompartmentExchangeModelFactory::NumericTwoCompartmentExchangeModelFactory() -{ -}; - -mitk::NumericTwoCompartmentExchangeModelFactory::~NumericTwoCompartmentExchangeModelFactory() -{ -}; - -mitk::ModelParameterizerBase::Pointer -mitk::NumericTwoCompartmentExchangeModelFactory::DoCreateParameterizer( - const modelFit::ModelFitInfo* fit) -const -{ - mitk::ModelParameterizerBase::Pointer result; - - ModelParameterizerType::Pointer modelParameterizer = - ModelParameterizerType::New(); - - modelFit::StaticParameterMap::ValueType aif = fit->staticParamMap.Get( - ModelType::NAME_STATIC_PARAMETER_AIF); - - modelParameterizer->SetAIF(mitk::convertParameterToArray(aif)); - - modelFit::StaticParameterMap::ValueType aifGrid = fit->staticParamMap.Get( - ModelType::NAME_STATIC_PARAMETER_AIFTimeGrid); - modelParameterizer->SetAIFTimeGrid(mitk::convertParameterToArray(aifGrid)); - - modelFit::StaticParameterMap::ValueType odeStepSize = fit->staticParamMap.Get( - ModelType::NAME_STATIC_PARAMETER_ODEINTStepSize); - modelParameterizer->SetODEINTStepSize(odeStepSize[0]); - - - result = modelParameterizer.GetPointer(); - - return result; -}; diff --git a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModelParameterizer.cpp b/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModelParameterizer.cpp deleted file mode 100644 index 25910af578..0000000000 --- a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoCompartmentExchangeModelParameterizer.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkNumericTwoCompartmentExchangeModelParameterizer.h" - -mitk::NumericTwoCompartmentExchangeModelParameterizer::ParametersType -mitk::NumericTwoCompartmentExchangeModelParameterizer::GetDefaultInitialParameterization() const -{ - ParametersType initialParameters; - initialParameters.SetSize(4); - initialParameters[mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_PS] = 5.0; - initialParameters[mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_F] = 20.0; - initialParameters[mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_ve] = 0.1; - initialParameters[mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_vp] = 0.04; - - return initialParameters; -}; - -mitk::NumericTwoCompartmentExchangeModelParameterizer::NumericTwoCompartmentExchangeModelParameterizer() -{ -}; - -mitk::NumericTwoCompartmentExchangeModelParameterizer::~NumericTwoCompartmentExchangeModelParameterizer() -{ -}; - -mitk::NumericTwoCompartmentExchangeModelParameterizer::StaticParameterMapType mitk::NumericTwoCompartmentExchangeModelParameterizer::GetGlobalStaticParameters() const -{ - StaticParameterMapType result; - StaticParameterValuesType valuesAIF = mitk::convertArrayToParameter(this->m_AIF); - StaticParameterValuesType valuesAIFGrid = mitk::convertArrayToParameter(this->m_AIFTimeGrid); - StaticParameterValuesType values; - values.push_back(m_ODEINTStepSize); - - result.insert(std::make_pair(ModelType::NAME_STATIC_PARAMETER_AIF, valuesAIF)); - result.insert(std::make_pair(ModelType::NAME_STATIC_PARAMETER_AIFTimeGrid, valuesAIFGrid)); - result.insert(std::make_pair(ModelType::NAME_STATIC_PARAMETER_ODEINTStepSize, values)); - - return result; -}; diff --git a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModel.cpp b/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModel.cpp deleted file mode 100644 index 4807a457ff..0000000000 --- a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModel.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkNumericTwoTissueCompartmentModel.h" -#include "mitkAIFParametrizerHelper.h" -#include "mitkTimeGridHelper.h" -#include "mitkTwoTissueCompartmentModelDifferentialEquations.h" -#include -#include -#include - -const std::string mitk::NumericTwoTissueCompartmentModel::MODEL_DISPLAY_NAME = "Numeric Two Tissue Compartment Model"; - -const std::string mitk::NumericTwoTissueCompartmentModel::NAME_PARAMETER_K1 = "K_1"; -const std::string mitk::NumericTwoTissueCompartmentModel::NAME_PARAMETER_k2 = "k_2"; -const std::string mitk::NumericTwoTissueCompartmentModel::NAME_PARAMETER_k3 = "k_3"; -const std::string mitk::NumericTwoTissueCompartmentModel::NAME_PARAMETER_k4 = "k_4"; -const std::string mitk::NumericTwoTissueCompartmentModel::NAME_PARAMETER_vb = "v_b"; - -const std::string mitk::NumericTwoTissueCompartmentModel::UNIT_PARAMETER_K1 = "1/min"; -const std::string mitk::NumericTwoTissueCompartmentModel::UNIT_PARAMETER_k2 = "1/min"; -const std::string mitk::NumericTwoTissueCompartmentModel::UNIT_PARAMETER_k3 = "1/min"; -const std::string mitk::NumericTwoTissueCompartmentModel::UNIT_PARAMETER_k4 = "1/min"; -const std::string mitk::NumericTwoTissueCompartmentModel::UNIT_PARAMETER_vb = "ml/ml"; - -const unsigned int mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_K1 = 0; -const unsigned int mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_k2 = 1; -const unsigned int mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_k3 = 2; -const unsigned int mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_k4 = 3; -const unsigned int mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_vb = 4; - -const unsigned int mitk::NumericTwoTissueCompartmentModel::NUMBER_OF_PARAMETERS = 5; - -const unsigned int mitk::NumericTwoTissueCompartmentModel::NUMBER_OF_DERIVED_PARAMETERS = 0; - -const std::string mitk::NumericTwoTissueCompartmentModel::MODEL_TYPE = "Dynamic.PET"; - -std::string mitk::NumericTwoTissueCompartmentModel::GetModelDisplayName() const -{ - return MODEL_DISPLAY_NAME; -}; - -std::string mitk::NumericTwoTissueCompartmentModel::GetModelType() const -{ - return MODEL_TYPE; -}; - -mitk::NumericTwoTissueCompartmentModel::NumericTwoTissueCompartmentModel() -{ - -} - -mitk::NumericTwoTissueCompartmentModel::~NumericTwoTissueCompartmentModel() -{ - -} - -mitk::NumericTwoTissueCompartmentModel::ParameterNamesType -mitk::NumericTwoTissueCompartmentModel::GetParameterNames() const -{ - ParameterNamesType result; - - result.push_back(NAME_PARAMETER_K1); - result.push_back(NAME_PARAMETER_k2); - result.push_back(NAME_PARAMETER_k3); - result.push_back(NAME_PARAMETER_k4); - result.push_back(NAME_PARAMETER_vb); - - return result; -} - -mitk::NumericTwoTissueCompartmentModel::ParametersSizeType -mitk::NumericTwoTissueCompartmentModel::GetNumberOfParameters() const -{ - return NUMBER_OF_PARAMETERS; -} - -mitk::NumericTwoTissueCompartmentModel::ParametersSizeType -mitk::NumericTwoTissueCompartmentModel::GetNumberOfDerivedParameters() const -{ - return NUMBER_OF_DERIVED_PARAMETERS; -} - -mitk::NumericTwoTissueCompartmentModel::ParamterUnitMapType -mitk::NumericTwoTissueCompartmentModel::GetParameterUnits() const -{ - ParamterUnitMapType result; - - result.insert(std::make_pair(NAME_PARAMETER_K1, UNIT_PARAMETER_K1)); - result.insert(std::make_pair(NAME_PARAMETER_k2, UNIT_PARAMETER_k2)); - result.insert(std::make_pair(NAME_PARAMETER_k3, UNIT_PARAMETER_k3)); - result.insert(std::make_pair(NAME_PARAMETER_k4, UNIT_PARAMETER_k4)); - result.insert(std::make_pair(NAME_PARAMETER_vb, UNIT_PARAMETER_vb)); - - return result; -}; - - -mitk::NumericTwoTissueCompartmentModel::ModelResultType -mitk::NumericTwoTissueCompartmentModel::ComputeModelfunction(const ParametersType& parameters) const -{ - typedef itk::Array ConcentrationCurveType; - typedef std::vector ConcentrationVectorType; - - if (this->m_TimeGrid.GetSize() == 0) - { - itkExceptionMacro("No Time Grid Set! Cannot Calculate Signal"); - } - - AterialInputFunctionType aterialInputFunction; - aterialInputFunction = GetAterialInputFunction(this->m_TimeGrid); - - unsigned int timeSteps = this->m_TimeGrid.GetSize(); - - /** @brief Boost::numeric::odeint works with type std::vector thus, aif and grid are converted to ModelParameters( of type std::vector) - */ - mitk::TwoTissueCompartmentModelDifferentialEquations::AIFType aif = mitk::convertArrayToParameter( - aterialInputFunction); - mitk::TwoTissueCompartmentModelDifferentialEquations::AIFType grid = mitk::convertArrayToParameter( - m_TimeGrid); - - mitk::TwoTissueCompartmentModelDifferentialEquations::AIFType aifODE = aif; - aifODE.push_back(aif[timeSteps - 1]); - mitk::TwoTissueCompartmentModelDifferentialEquations::AIFType gridODE = grid; - gridODE.push_back(grid[timeSteps - 1] + (grid[timeSteps - 1] - grid[timeSteps - 2])); - - //Model Parameters - double K1 = (double)parameters[POSITION_PARAMETER_K1] / 60.0; - double k2 = (double)parameters[POSITION_PARAMETER_k2] / 60.0; - double k3 = (double)parameters[POSITION_PARAMETER_k3] / 60.0; - double k4 = (double)parameters[POSITION_PARAMETER_k4] / 60.0; - double vb = parameters[POSITION_PARAMETER_vb]; - - - /** @brief Initialize class TwpTissueCompartmentModelDifferentialEquations defining the differential equations. AIF and Grid must be set so that at step t the aterial Concentration Ca(t) can be interpolated from AIF*/ - mitk::TwoTissueCompartmentModelDifferentialEquations ode; - ode.initialize(K1, k2, k3, k4); - ode.setAIF(aifODE); - ode.setAIFTimeGrid(gridODE); - - state_type x(2); - x[0] = 0.0; - x[1] = 0.0; - typedef boost::numeric::odeint::runge_kutta_cash_karp54 error_stepper_type; - //typedef boost::numeric::odeint::runge_kutta4< state_type > stepper_type; - - /** @brief Results of odeeint x[0] and x[1]*/ - ConcentrationVectorType C1; - ConcentrationVectorType C2; - ConcentrationVectorType odeTimeGrid; - - error_stepper_type stepper; - /** @brief Stepsize. Should be adapted by stepper (runge_kutta_cash_karp54) */ - const double dt = 0.1; - /** @brief perform Step t -> t+dt to calculate approximate value x(t+dt)*/ - - double T = this->m_TimeGrid(timeSteps - 1) + (grid[timeSteps - 1] - grid[timeSteps - 2]); - - for (double t = 0.0; t < T; t += dt) - { - stepper.do_step(ode, x, t, dt); - C1.push_back(x[0]); - C2.push_back(x[1]); - odeTimeGrid.push_back(t); - - } - - /** @brief transfom result of Differential equations back to itk::Array and interpolate to m_TimeGrid (they are calculated on a different grid defined by stepsize of odeint)*/ - ConcentrationCurveType ConcentrationCompartment1 = mitk::convertParameterToArray(C1); - ConcentrationCurveType ConcentrationCompartment2 = mitk::convertParameterToArray(C2); - ConcentrationCurveType rungeKuttaTimeGrid = mitk::convertParameterToArray(odeTimeGrid); - - mitk::ModelBase::ModelResultType C_1 = mitk::InterpolateSignalToNewTimeGrid( - ConcentrationCompartment1, rungeKuttaTimeGrid, m_TimeGrid); - mitk::ModelBase::ModelResultType C_2 = mitk::InterpolateSignalToNewTimeGrid( - ConcentrationCompartment2, rungeKuttaTimeGrid, m_TimeGrid); - - - //Signal that will be returned by ComputeModelFunction - mitk::ModelBase::ModelResultType signal(timeSteps); - signal.fill(0.0); - mitk::ModelBase::ModelResultType::iterator signalPos = signal.begin(); - mitk::ModelBase::ModelResultType::const_iterator C1Pos = C_1.begin(); - mitk::ModelBase::ModelResultType::const_iterator C2Pos = C_2.begin(); - - - for (AterialInputFunctionType::const_iterator aifpos = aterialInputFunction.begin(); - aifpos != aterialInputFunction.end(); ++aifpos, ++C1Pos, ++C2Pos, ++signalPos) - { - *signalPos = vb * (*aifpos) + (1 - vb) * (*C1Pos + *C2Pos); - } - - return signal; - -} - -itk::LightObject::Pointer mitk::NumericTwoTissueCompartmentModel::InternalClone() const -{ - NumericTwoTissueCompartmentModel::Pointer newClone = NumericTwoTissueCompartmentModel::New(); - - newClone->SetTimeGrid(this->m_TimeGrid); - - return newClone.GetPointer(); -} - -void mitk::NumericTwoTissueCompartmentModel::PrintSelf(std::ostream& os, ::itk::Indent indent) const -{ - Superclass::PrintSelf(os, indent); - - -} - - - - diff --git a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModelFactory.cpp b/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModelFactory.cpp deleted file mode 100644 index d00b9d3370..0000000000 --- a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModelFactory.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkNumericTwoTissueCompartmentModelFactory.h" - -#include "mitkNumericTwoTissueCompartmentModelParameterizer.h" -#include "mitkAIFParametrizerHelper.h" - -mitk::NumericTwoTissueCompartmentModelFactory::NumericTwoTissueCompartmentModelFactory() -{ -}; - -mitk::NumericTwoTissueCompartmentModelFactory::~NumericTwoTissueCompartmentModelFactory() -{ -}; - diff --git a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModelParameterizer.cpp b/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModelParameterizer.cpp deleted file mode 100644 index a1c6b1de07..0000000000 --- a/Modules/Pharmacokinetics/src/Models/mitkNumericTwoTissueCompartmentModelParameterizer.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include "mitkNumericTwoTissueCompartmentModelParameterizer.h" - -mitk::NumericTwoTissueCompartmentModelParameterizer::ParametersType -mitk::NumericTwoTissueCompartmentModelParameterizer::GetDefaultInitialParameterization() const -{ - ParametersType initialParameters; - initialParameters.SetSize(5); - initialParameters[mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_K1] = 0.23; - initialParameters[mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_k2] = 0.4; - initialParameters[mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_k3] = 0.13; - initialParameters[mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_k4] = 0.15; - initialParameters[mitk::NumericTwoTissueCompartmentModel::POSITION_PARAMETER_vb] = 0.03; - - return initialParameters; -}; - -mitk::NumericTwoTissueCompartmentModelParameterizer::NumericTwoTissueCompartmentModelParameterizer() -{ -}; - -mitk::NumericTwoTissueCompartmentModelParameterizer::~NumericTwoTissueCompartmentModelParameterizer() -{ -}; diff --git a/Modules/Pharmacokinetics/test/mitkConvertSignalToConcentrationTest.cpp b/Modules/Pharmacokinetics/test/mitkConvertSignalToConcentrationTest.cpp index 771d25e57b..55115da524 100644 --- a/Modules/Pharmacokinetics/test/mitkConvertSignalToConcentrationTest.cpp +++ b/Modules/Pharmacokinetics/test/mitkConvertSignalToConcentrationTest.cpp @@ -1,217 +1,222 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Testing #include "mitkTestingMacros.h" #include "mitkTestFixture.h" //MITK includes #include "mitkConcentrationCurveGenerator.h" #include "mitkTestDynamicImageGenerator.h" #include "mitkImagePixelReadAccessor.h" +#include "boost/math/constants/constants.hpp" class mitkConvertSignalToConcentrationTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkConvertSignalToConcentrationTestSuite); MITK_TEST(GetConvertedImageAbsoluteEnhancementTest); MITK_TEST(GetConvertedImageAbsoluteEnhancementAveragedBaselineTest); MITK_TEST(GetConvertedImageRelativeEnhancementTest); MITK_TEST(GetConvertedImageturboFLASHTest); MITK_TEST(GetConvertedImageVFATest); MITK_TEST(GetConvertedImageT2Test); CPPUNIT_TEST_SUITE_END(); private: mitk::Image::Pointer m_dynamicImage; mitk::Image::Pointer m_convertedImage; mitk::ConcentrationCurveGenerator::Pointer m_concentrationGen; std::vector > m_testIndices; public: void setUp() override { m_dynamicImage = mitk::GenerateDynamicTestImageMITK(); m_concentrationGen = mitk::ConcentrationCurveGenerator::New(); m_concentrationGen->SetDynamicImage(m_dynamicImage); itk::Index<4> testIndex0 = { { 0, 0, 0, 0 } }; itk::Index<4> testIndex1 = { { 0, 2, 2, 3 } }; itk::Index<4> testIndex2 = { { 2, 2, 1, 9 } }; itk::Index<4> testIndex3 = { { 0, 3, 2, 1 } }; itk::Index<4> testIndex4 = { { 2, 2, 2, 4 } }; m_testIndices.push_back(testIndex0); m_testIndices.push_back(testIndex1); m_testIndices.push_back(testIndex2); m_testIndices.push_back(testIndex3); m_testIndices.push_back(testIndex4); } void tearDown() override { } void GetConvertedImageAbsoluteEnhancementTest() { m_concentrationGen->SetAbsoluteSignalEnhancement(true); m_concentrationGen->SetFactor(1.0); m_concentrationGen->SetBaselineStartTimeStep(0); m_concentrationGen->SetBaselineEndTimeStep(0); m_convertedImage = m_concentrationGen->GetConvertedImage(); mitk::ImagePixelReadAccessor readAccess(m_convertedImage, m_convertedImage->GetSliceData(4)); std::vector refValues; refValues.push_back(0.0); refValues.push_back(90.0); refValues.push_back(360.0); refValues.push_back(0.0); refValues.push_back(160.0); std::stringstream ss; for (long unsigned int i = 0; i < m_testIndices.size(); i++) { ss << "Checking value of image converted using absolute enhancement at test index " << i << "."; std::string message = ss.str(); CPPUNIT_ASSERT_MESSAGE(message, mitk::Equal(refValues.at(i), readAccess.GetPixelByIndex(m_testIndices.at(i)), 1e-6, true) == true); } } void GetConvertedImageAbsoluteEnhancementAveragedBaselineTest() { m_concentrationGen->SetAbsoluteSignalEnhancement(true); m_concentrationGen->SetFactor(1.0); m_concentrationGen->SetBaselineStartTimeStep(1); m_concentrationGen->SetBaselineEndTimeStep(3); m_convertedImage = m_concentrationGen->GetConvertedImage(); mitk::ImagePixelReadAccessor readAccess(m_convertedImage, m_convertedImage->GetSliceData(4)); std::vector refValues; refValues.push_back(0.0); refValues.push_back(30.0); refValues.push_back(280.0); refValues.push_back(0.0); refValues.push_back(80.0); std::stringstream ss; for (long unsigned int i = 0; i < m_testIndices.size(); i++) { ss << "Checking value of image converted using absolute enhancement with averaged baseline at test index " << i << "."; std::string message = ss.str(); CPPUNIT_ASSERT_MESSAGE(message, mitk::Equal(refValues.at(i), readAccess.GetPixelByIndex(m_testIndices.at(i)), 1e-6, true) == true); } } void GetConvertedImageRelativeEnhancementTest() { m_concentrationGen->SetRelativeSignalEnhancement(true); m_concentrationGen->SetFactor(1.0); m_concentrationGen->SetBaselineStartTimeStep(0); m_concentrationGen->SetBaselineEndTimeStep(0); m_convertedImage = m_concentrationGen->GetConvertedImage(); mitk::ImagePixelReadAccessor readAccess(m_convertedImage, m_convertedImage->GetSliceData(4)); std::vector refValues; refValues.push_back(0.0); refValues.push_back(3.461538); refValues.push_back(20.0); refValues.push_back(0.0); refValues.push_back(5.714286); std::stringstream ss; for (long unsigned int i = 0; i < m_testIndices.size(); i++) { ss << "Checking value of image converted using relative enhancement at test index " << i << "."; std::string message = ss.str(); CPPUNIT_ASSERT_MESSAGE(message, mitk::Equal(refValues.at(i), readAccess.GetPixelByIndex(m_testIndices.at(i)), 1e-6, true) == true); } } void GetConvertedImageturboFLASHTest() { m_concentrationGen->SetisTurboFlashSequence(true); m_concentrationGen->SetRecoveryTime(1.0); m_concentrationGen->SetRelaxationTime(1.0); m_concentrationGen->SetRelaxivity(1.0); m_concentrationGen->SetBaselineStartTimeStep(0); m_concentrationGen->SetBaselineEndTimeStep(0); m_convertedImage = m_concentrationGen->GetConvertedImage(); mitk::ImagePixelReadAccessor readAccess(m_convertedImage, m_convertedImage->GetSliceData(4)); std::vector refValues; refValues.push_back(0.0); refValues.push_back(0.0); refValues.push_back(0.0); refValues.push_back(0.0); refValues.push_back(0.0); std::stringstream ss; for (long unsigned int i = 0; i < m_testIndices.size(); i++) { ss << "Checking value of image converted using the turboFLASH model at test index " << i << "."; std::string message = ss.str(); CPPUNIT_ASSERT_MESSAGE(message, mitk::Equal(refValues.at(i), readAccess.GetPixelByIndex(m_testIndices.at(i)), 1e-6, true) == true); } } void GetConvertedImageVFATest() { mitk::Image::Pointer PDWImage; - PDWImage = mitk::GenerateTestFrame(1.0); + PDWImage = mitk::GenerateTestFrame(2.0); m_concentrationGen->SetUsingT1Map(true); - m_concentrationGen->SetRecoveryTime(1.0); - m_concentrationGen->SetRelaxivity(1.0); - m_concentrationGen->SetT10Image(PDWImage); - m_concentrationGen->SetFlipAngle(1.0); + m_concentrationGen->SetRepetitionTime(1.0); + m_concentrationGen->SetRelaxivity(1.0/1000.0); + m_concentrationGen->SetPDWImage(PDWImage); + m_concentrationGen->SetFlipAngle(2.0 / 360 * 2 * boost::math::constants::pi()); + m_concentrationGen->SetFlipAnglePDW(1.0 / 360 * 2 * boost::math::constants::pi()); m_concentrationGen->SetBaselineStartTimeStep(0); m_concentrationGen->SetBaselineEndTimeStep(0); m_convertedImage = m_concentrationGen->GetConvertedImage(); + + mitk::ImagePixelReadAccessor readAccessPDW(PDWImage, PDWImage->GetSliceData(3)); + mitk::ImagePixelReadAccessor readAccessDyn(m_dynamicImage, m_dynamicImage->GetSliceData(4)); mitk::ImagePixelReadAccessor readAccess(m_convertedImage, m_convertedImage->GetSliceData(4)); std::vector refValues; refValues.push_back(0.0); - refValues.push_back(2.956868); - refValues.push_back(0.0); + refValues.push_back(7871.5616001); + refValues.push_back(-1059.012343); refValues.push_back(0.0); - refValues.push_back(3.989374); + refValues.push_back(-3251.392613); std::stringstream ss; for (long unsigned int i = 0; i < m_testIndices.size(); i++) { ss << "Checking value of image converted using the VFA method at test index " << i << "."; std::string message = ss.str(); CPPUNIT_ASSERT_MESSAGE(message, mitk::Equal(refValues.at(i), readAccess.GetPixelByIndex(m_testIndices.at(i)), 1e-6, true) == true); } } void GetConvertedImageT2Test() { m_concentrationGen->SetisT2weightedImage(true); m_concentrationGen->SetT2Factor(1.0); m_concentrationGen->SetT2EchoTime(1.0); m_concentrationGen->SetBaselineStartTimeStep(0); m_concentrationGen->SetBaselineEndTimeStep(0); m_convertedImage = m_concentrationGen->GetConvertedImage(); mitk::ImagePixelReadAccessor readAccess(m_convertedImage, m_convertedImage->GetSliceData(4)); std::vector refValues; refValues.push_back(0.0); refValues.push_back(-1.495494); refValues.push_back(-3.044522); refValues.push_back(0.0); refValues.push_back(-1.904237); std::stringstream ss; for (long unsigned int i = 0; i < m_testIndices.size(); i++) { ss << "Checking value of converted T2 image at test index " << i << "."; std::string message = ss.str(); CPPUNIT_ASSERT_MESSAGE(message, mitk::Equal(refValues.at(i), readAccess.GetPixelByIndex(m_testIndices.at(i)), 1e-6, true) == true); } } }; MITK_TEST_SUITE_REGISTRATION(mitkConvertSignalToConcentration) diff --git a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h index ddafc6b443..104403f81f 100644 --- a/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h +++ b/Modules/QtWidgets/include/QmitkSynchronizedNodeSelectionWidget.h @@ -1,101 +1,103 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSynchronizedNodeSelectionWidget_h #define QmitkSynchronizedNodeSelectionWidget_h #include #include "ui_QmitkSynchronizedNodeSelectionWidget.h" // mitk core #include // qt widgets module #include #include /* * @brief The 'QmitkSynchronizedNodeSelectionWidget' implements the 'QmitkAbstractNodeSelectionWidget' * by providing a table view, using a 'QmitkRenderWindowDataNodeTableModel' and extending it * with base renderer-specific functionality. * * Given a base renderer, the selection widget is able to display and access render window specific properties * of the selected nodes, making it possible to switch between a "synchronized" and "desynchronized" selection * state. * The widget can be used to decide if all data nodes of the data storage should be selected or * only an individually selected set of nodes, defined by a 'QmitkNodeSelectionDialog'. * If individual nodes are selected / removed from the selection, the widget can inform other * 'QmitkSynchronizedNodeSelectionWidget' about the current selection, if desired. * Additionally the widget allows to reinitialize the corresponding base renderer with a specific * data node geometry. */ class MITKQTWIDGETS_EXPORT QmitkSynchronizedNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: QmitkSynchronizedNodeSelectionWidget(QWidget* parent); ~QmitkSynchronizedNodeSelectionWidget(); using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; void SetBaseRenderer(mitk::BaseRenderer* baseRenderer); void SetSelectAll(bool selectAll); bool GetSelectAll() const; + void SelectAll(); void SetSynchronized(bool synchronize); bool IsSynchronized() const; Q_SIGNALS: void SelectionModeChanged(bool selectAll); + void DeregisterSynchronization(); private Q_SLOTS: void OnModelUpdated(); void OnSelectionModeChanged(bool selectAll); void OnEditSelection(); void OnTableClicked(const QModelIndex& index); protected: void SetUpConnections(); void Initialize(); void UpdateInfo() override; void OnDataStorageChanged() override; void OnNodePredicateChanged() override; void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; void OnInternalSelectionChanged() override; bool AllowEmissionOfSelection(const NodeList& emissionCandidates) const override; void OnNodeAddedToStorage(const mitk::DataNode* node) override; void OnNodeModified(const itk::Object* caller, const itk::EventObject& event) override; private: void ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); void ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection); void ReinitNode(const mitk::DataNode* dataNode); void RemoveFromInternalSelection(mitk::DataNode* dataNode); bool IsParentNodeSelected(const mitk::DataNode* dataNode) const; void DeselectNode(mitk::DataNode* dataNode); Ui::QmitkSynchronizedNodeSelectionWidget m_Controls; mitk::WeakPointer m_BaseRenderer; std::unique_ptr m_StorageModel; }; #endif diff --git a/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h index ad672d5223..8975764392 100644 --- a/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h +++ b/Modules/QtWidgets/include/QmitkSynchronizedWidgetConnector.h @@ -1,150 +1,160 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkSynchronizedWidgetConnector_h #define QmitkSynchronizedWidgetConnector_h #include // mitk core #include // mitk qt widgets #include // qt #include /* * @brief This class connects different 'QmitkSynchronizedNodeSelectionWidget', such that * they can synchronize their current node selection and their current selection mode. * * In order to synchronize a new node selection widget with other already connected * node selection widgets, 'ConnectWidget(const QmitkSynchronizedNodeSelectionWidget*)' has to be used. * In order to desynchronize a node selection widget, * 'DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget*)' has to be used. * If a new node selection has been connected / synchronized, * 'SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget*' can be used to initialy set * the current selection and the current selection mode. * For this, both values are stored in this class internally. */ class MITKQTWIDGETS_EXPORT QmitkSynchronizedWidgetConnector : public QObject { Q_OBJECT public: - using NodeList = QList; + using NodeList = QmitkSynchronizedNodeSelectionWidget::NodeList; QmitkSynchronizedWidgetConnector(); /* * @brief This function connects the different signals and slots of this instance and the given * given node selection widget, such that changes to the current list of nodes * and the selection mode can be forwarded or received. * The connections are as follows: * - QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged * -> QmitkSynchronizedWidgetConnector::ChangeSelection * - QmitkSynchronizedWidgetConnector::NodeSelectionChanged * -> QmitkAbstractNodeSelectionWidget::SetCurrentSelection * - QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged * -> QmitkSynchronizedWidgetConnector::ChangeSelectionMode * - QmitkSynchronizedWidgetConnector::SelectionModeChanged * -> QmitkSynchronizedNodeSelectionWidget::SetSelectAll * * @param nodeSelectionWidget The synchronized node selection widget to be connected / synchronized. */ - void ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + void ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget); /* * @brief This function disconnects the different signals and slot of this instance and the given * given node selection widget, such that changes to the current list of nodes * and the selection mode cannot be forwarded or received anymore. * * @param nodeSelectionWidget The synchronized node selection widget to be disconnected / desynchronized. */ - void DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; + void DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget); /* * @brief This function sets the current selection and the selection mode of the given node selection widget * to the values of this instance. The required values are stored in this class internally. * It can be used to newly initialize the given node selection widget. * * @param nodeSelectionWidget The synchronized node selection widget for which the * current selection and the selection mode should be set. */ void SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const; /* * @brief Get the current internal node selection. * * @return NodeList The current internal node selection stored as a member variable. */ NodeList GetNodeSelection() const; /* * @brief Get the current internal selection mode. * * @return The current internal selection mode stored as a member variable. */ bool GetSelectionMode() const; Q_SIGNALS: /* * @brief A signal that will be emitted by the 'ChangeSelection'-slot. * This happens if a new selection / list of nodes is set from outside of this class, * e.g. from a QmitkSynchronizedNodeSelectionWidget. * This signal is connected to the 'SetCurrentSelection'-slot of each * QmitkSynchronizedNodeSelectionWidget to propagate the new selection. * * @param nodes A list of data nodes that are newly selected. */ void NodeSelectionChanged(NodeList nodes); /* * @brief A signal that will be emitted by the 'ChangeSelectionMode'-slot. * This happens if the selection mode is change from outside of this class, * e.g. from a QmitkSynchronizedNodeSelectionWidget. * This signal is connected to the 'SetSelectAll'-slot of each * QmitkSynchronizedNodeSelectionWidget to propagate the selection mode. * * @param selectAll True, if the selection mode is changed to "select all" nodes. * False otherwise. */ void SelectionModeChanged(bool selectAll); public Q_SLOTS: /* * @brief Set a new internal selection and send this new selection to connected * QmitkSynchronizedNodeSelectionWidgets using the 'NodeSelectionChanged'-signal. * * This slot itself is connected to the 'CurrentSelectionChanged'-signal of each * QmitkSynchronizedNodeSelectionWidget to receive a new selection. * * @param nodes A list of data nodes that are newly selected. */ void ChangeSelection(NodeList nodes); /* * @brief Set a new selection mode and send this new selection mode to connected * QmitkSynchronizedNodeSelectionWidgets using the 'SelectionModeChanged'-signal. * * This slot itself is connected to the 'SelectionModeChanged'-signal of each * QmitkSynchronizedNodeSelectionWidget to receive a new selection mode. * * @param selectAll True, if the selection mode is changed to "select all" nodes. * False otherwise. */ void ChangeSelectionMode(bool selectAll); + /* + * @brief Decrease the internal counter of connections to keep track of how many + * QmitkSynchronizedNodeSelectionWidgets are synchronized. + * + * This slot itself is connected to the 'DeregisterSynchronization'-signal of each + * QmitkSynchronizedNodeSelectionWidget to get notified when a synchronized + * widget is deleted. + */ + void DeregisterWidget(); private: NodeList m_InternalSelection; bool m_SelectAll; + unsigned int m_ConnectionCounter; }; #endif diff --git a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp index ce0614d60b..4a950fb941 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowUtilityWidget.cpp @@ -1,181 +1,185 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkRenderWindowUtilityWidget.h" #include // mitk core #include #include #include #include // mitk qt widgets #include #include // itk #include QmitkRenderWindowUtilityWidget::QmitkRenderWindowUtilityWidget( QWidget* parent/* = nullptr */, QmitkRenderWindow* renderWindow/* = nullptr */, mitk::DataStorage* dataStorage/* = nullptr */) : m_NodeSelectionWidget(nullptr) , m_SliceNavigationWidget(nullptr) , m_StepperAdapter(nullptr) , m_ViewDirectionSelector(nullptr) { this->setParent(parent); auto layout = new QHBoxLayout(this); layout->setMargin(0); mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_BaseRenderer = mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow()); m_NodeSelectionWidget = new QmitkSynchronizedNodeSelectionWidget(parent); m_NodeSelectionWidget->SetBaseRenderer(m_BaseRenderer); m_NodeSelectionWidget->SetDataStorage(dataStorage); m_NodeSelectionWidget->SetNodePredicate(noHelperObjects); auto menuBar = new QMenuBar(this); menuBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + menuBar->setNativeMenuBar(false); auto dataMenu = menuBar->addMenu("Data"); QWidgetAction* dataAction = new QWidgetAction(dataMenu); dataAction->setDefaultWidget(m_NodeSelectionWidget); dataMenu->addAction(dataAction); layout->addWidget(menuBar); auto* synchPushButton = new QPushButton(this); auto* synchIcon = new QIcon(); auto synchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto desynchronizeSvg = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); synchIcon->addPixmap(synchronizeSvg.pixmap(64), QIcon::Normal, QIcon::On); synchIcon->addPixmap(desynchronizeSvg.pixmap(64), QIcon::Normal, QIcon::Off); synchPushButton->setIcon(*synchIcon); synchPushButton->setToolTip("Synchronize / desynchronize data management"); synchPushButton->setCheckable(true); synchPushButton->setChecked(true); connect(synchPushButton, &QPushButton::clicked, this, &QmitkRenderWindowUtilityWidget::ToggleSynchronization); layout->addWidget(synchPushButton); auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); m_SliceNavigationWidget = new QmitkSliceNavigationWidget(this); m_StepperAdapter = new QmitkStepperAdapter(m_SliceNavigationWidget, sliceNavigationController->GetStepper()); layout->addWidget(m_SliceNavigationWidget); mitk::RenderWindowLayerUtilities::RendererVector controlledRenderer{ m_BaseRenderer }; m_RenderWindowViewDirectionController = std::make_unique(); m_RenderWindowViewDirectionController->SetControlledRenderer(controlledRenderer); m_RenderWindowViewDirectionController->SetDataStorage(dataStorage); m_ViewDirectionSelector = new QComboBox(this); QStringList viewDirections{ "axial", "coronal", "sagittal"}; m_ViewDirectionSelector->insertItems(0, viewDirections); + m_ViewDirectionSelector->setMinimumContentsLength(12); connect(m_ViewDirectionSelector, &QComboBox::currentTextChanged, this, &QmitkRenderWindowUtilityWidget::ChangeViewDirection); UpdateViewPlaneSelection(); layout->addWidget(m_ViewDirectionSelector); // finally add observer, after all relevant objects have been created / initialized sliceNavigationController->ConnectGeometrySendEvent(this); } QmitkRenderWindowUtilityWidget::~QmitkRenderWindowUtilityWidget() { } void QmitkRenderWindowUtilityWidget::ToggleSynchronization(bool synchronized) { m_NodeSelectionWidget->SetSynchronized(synchronized); emit SynchronizationToggled(m_NodeSelectionWidget); } void QmitkRenderWindowUtilityWidget::SetGeometry(const itk::EventObject& event) { if (!mitk::SliceNavigationController::GeometrySendEvent(nullptr, 0).CheckEvent(&event)) { return; } const auto* sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); auto viewDirection = sliceNavigationController->GetViewDirection(); unsigned int axis = 0; switch (viewDirection) { case mitk::AnatomicalPlane::Original: return; case mitk::AnatomicalPlane::Axial: { axis = 2; break; } case mitk::AnatomicalPlane::Coronal: { axis = 1; break; } case mitk::AnatomicalPlane::Sagittal: { axis = 0; break; } } const auto* inputTimeGeometry = sliceNavigationController->GetInputWorldTimeGeometry(); const mitk::BaseGeometry* rendererGeometry = m_BaseRenderer->GetCurrentWorldGeometry(); mitk::TimeStepType timeStep = sliceNavigationController->GetStepper()->GetPos(); mitk::BaseGeometry::ConstPointer geometry = inputTimeGeometry->GetGeometryForTimeStep(timeStep); + if (geometry == nullptr) + return; mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); matrix.GetVnlMatrix().normalize_columns(); mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); int dominantAxis = itk::Function::Max3(inverseMatrix[0][axis], inverseMatrix[1][axis], inverseMatrix[2][axis]); bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][axis] < 0; bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[axis] < 0; m_SliceNavigationWidget->SetInverseDirection(referenceGeometryAxisInverted != rendererZAxisInverted); } void QmitkRenderWindowUtilityWidget::ChangeViewDirection(const QString& viewDirection) { m_RenderWindowViewDirectionController->SetViewDirectionOfRenderer(viewDirection.toStdString()); } void QmitkRenderWindowUtilityWidget::UpdateViewPlaneSelection() { const auto sliceNavigationController = m_BaseRenderer->GetSliceNavigationController(); const auto viewDirection = sliceNavigationController->GetDefaultViewDirection(); switch (viewDirection) { case mitk::AnatomicalPlane::Axial: m_ViewDirectionSelector->setCurrentIndex(0); break; case mitk::AnatomicalPlane::Coronal: m_ViewDirectionSelector->setCurrentIndex(1); break; case mitk::AnatomicalPlane::Sagittal: m_ViewDirectionSelector->setCurrentIndex(2); break; default: break; } } diff --git a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp index ac64be0664..aae35e9ac6 100644 --- a/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp +++ b/Modules/QtWidgets/src/QmitkSynchronizedNodeSelectionWidget.cpp @@ -1,701 +1,709 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // mitk qt widgets module #include #include #include #include // mitk core module #include #include #include #include QmitkSynchronizedNodeSelectionWidget::QmitkSynchronizedNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) { m_Controls.setupUi(this); m_StorageModel = std::make_unique(this); m_Controls.tableView->setModel(m_StorageModel.get()); m_Controls.tableView->horizontalHeader()->setVisible(false); m_Controls.tableView->verticalHeader()->setVisible(false); m_Controls.tableView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.tableView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.tableView->setContextMenuPolicy(Qt::CustomContextMenu); this->SetUpConnections(); this->Initialize(); } QmitkSynchronizedNodeSelectionWidget::~QmitkSynchronizedNodeSelectionWidget() { + bool isSynchronized = this->IsSynchronized(); + if (isSynchronized) + { + emit DeregisterSynchronization(); + return; + } + auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } - bool isSynchronized = this->IsSynchronized(); - if (!isSynchronized) + // If the model is not synchronized, + // we know that renderer-specific properties exist for all nodes. + // These properties need to be removed from the nodes. + auto allNodes = dataStorage->GetAll(); + for (auto& node : *allNodes) { - // If the model is not synchronizes, - // we know that renderer-specific properties exist for all nodes. - // These properties need to be removed from the nodes. - auto allNodes = dataStorage->GetAll(); - for (auto& node : *allNodes) - { - // Delete the relevant renderer-specific properties for the node using the current base renderer. - mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); - } + // Delete the relevant renderer-specific properties for the node using the current base renderer. + mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } void QmitkSynchronizedNodeSelectionWidget::SetBaseRenderer(mitk::BaseRenderer* baseRenderer) { if (m_BaseRenderer == baseRenderer) { // no need to do something return; } if (nullptr == baseRenderer) { return; } auto oldBaseRenderer = m_BaseRenderer.Lock(); m_BaseRenderer = baseRenderer; auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { // If the model is synchronized, // all nodes use global / default properties. // No renderer-specific property lists should exist // so there is no need to transfer any property values. } else { // If the model is not synchronized, // we know that renderer-specific properties exist for all nodes. // These properties need to be removed from the nodes and // we need to transfer their values to new renderer-specific properties. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // Set the relevant renderer-specific properties for the node using the new base renderer. // By transferring the values from the old property list, // the same property-state is kept when switching to another base renderer. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, oldBaseRenderer); // Delete the relevant renderer-specific properties for the node using the old base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, oldBaseRenderer); } } this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::SetSelectAll(bool selectAll) { if (selectAll == m_Controls.selectionModeCheckBox->isChecked()) { // no need to do something return; } m_Controls.selectionModeCheckBox->setChecked(selectAll); } bool QmitkSynchronizedNodeSelectionWidget::GetSelectAll() const { return m_Controls.selectionModeCheckBox->isChecked(); } void QmitkSynchronizedNodeSelectionWidget::SetSynchronized(bool synchronize) { if (synchronize == this->IsSynchronized()) { // no need to do something return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return; } if (synchronize) { // set the base renderer of the model to nullptr, such that global properties are used m_StorageModel->SetCurrentRenderer(nullptr); // If the model is synchronized, // we know that the model was not synchronized before. // That means that all nodes use renderer-specific properties, // but now all nodes need global properties. // Thus we need to remove the renderer-specific properties of all nodes of the // datastorage. auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // For helper / hidden nodes: // If the node predicate does not match, do not remove the renderer-specific property // This is relevant for the crosshair data nodes, which are only visible inside their // corresponding render window. if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { // Delete the relevant renderer-specific properties for the node using the current base renderer. mitk::RenderWindowLayerUtilities::DeleteRenderWindowProperties(node, baseRenderer); } } } else { // set the base renderer of the model to current base renderer, such that renderer-specific properties are used m_StorageModel->SetCurrentRenderer(baseRenderer); // If the model is not synchronized anymore, // we know that the model was synchronized before. // That means that all nodes use global / default properties, // but now all nodes need renderer-specific properties. // Thus we need to modify the renderer-specific properties of all nodes of the // datastorage: // - hide those nodes, which are not part of the newly selected nodes. // - keep the property values of those nodes, which are part of the new selection AND // have been selected before auto currentNodeSelection = this->GetCurrentInternalSelection(); auto allNodes = dataStorage->GetAll(); for (auto& node : *allNodes) { // check if the node is part of the current selection auto finding = std::find(std::begin(currentNodeSelection), std::end(currentNodeSelection), node); if (finding != std::end(currentNodeSelection)) // node found / part of the current selection { // Set the relevant renderer-specific properties for the node using the curent base renderer. // By transferring the values from the global / default property list, // the same property-state is kept when switching to non-synchronized mode. mitk::RenderWindowLayerUtilities::TransferRenderWindowProperties(node, baseRenderer, nullptr); } else { // If the node is not part of the selection, unset the relevant renderer-specific properties. // This will unset the "visible" and "layer" property for the renderer-specific property list and // hide the node for this renderer. // ATTENTION: This is required, since the synchronized property needs to be overwritten // to make sure that the visibility is correctly set for the specific base renderer. this->DeselectNode(node); } } } // Since the synchronization might lead to a different node order depending on the layer properties, the render window // needs to be updated. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } bool QmitkSynchronizedNodeSelectionWidget::IsSynchronized() const { return m_StorageModel->GetCurrentRenderer().IsNull(); } void QmitkSynchronizedNodeSelectionWidget::OnModelUpdated() { m_Controls.tableView->resizeRowsToContents(); m_Controls.tableView->resizeColumnsToContents(); } void QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged(bool selectAll) { emit SelectionModeChanged(selectAll); if (selectAll) { - auto dataStorage = m_DataStorage.Lock(); - if (dataStorage.IsNull()) - { - return; - } - - auto allNodes = m_NodePredicate ? dataStorage->GetSubset(m_NodePredicate) : dataStorage->GetAll(); - NodeList currentSelection; - for (auto& node : *allNodes) - { - currentSelection.append(node); - } - - this->HandleChangeOfInternalSelection(currentSelection); + this->SelectAll(); } } void QmitkSynchronizedNodeSelectionWidget::OnEditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(m_StorageModel->GetCurrentSelection()); dialog->SetSelectionMode(QAbstractItemView::MultiSelection); m_Controls.changeSelectionButton->setChecked(true); if (dialog->exec()) { m_Controls.selectionModeCheckBox->setChecked(false); emit SelectionModeChanged(false); auto selectedNodes = dialog->GetSelectedNodes(); this->HandleChangeOfInternalSelection(selectedNodes); } m_Controls.changeSelectionButton->setChecked(false); delete dialog; } void QmitkSynchronizedNodeSelectionWidget::OnTableClicked(const QModelIndex& index) { if (!index.isValid() || m_StorageModel.get() != index.model()) { return; } auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } QVariant dataNodeVariant = index.data(QmitkDataNodeRole); auto dataNode = dataNodeVariant.value(); if (index.column() == 1) // node visibility column { bool visibiliy = index.data(Qt::EditRole).toBool(); m_StorageModel->setData(index, QVariant(!visibiliy), Qt::EditRole); return; } if (index.column() == 2) // reinit node column { this->ReinitNode(dataNode); return; } if (index.column() == 3) // remove node column { this->RemoveFromInternalSelection(dataNode); return; } } void QmitkSynchronizedNodeSelectionWidget::SetUpConnections() { connect(m_StorageModel.get(), &QmitkRenderWindowDataNodeTableModel::ModelUpdated, this, &QmitkSynchronizedNodeSelectionWidget::OnModelUpdated); connect(m_Controls.selectionModeCheckBox, &QCheckBox::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnSelectionModeChanged); connect(m_Controls.changeSelectionButton, &QPushButton::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnEditSelection); connect(m_Controls.tableView, &QTableView::clicked, this, &QmitkSynchronizedNodeSelectionWidget::OnTableClicked); } void QmitkSynchronizedNodeSelectionWidget::Initialize() { auto baseRenderer = m_BaseRenderer.Lock(); auto dataStorage = m_DataStorage.Lock(); m_StorageModel->SetDataStorage(dataStorage); m_StorageModel->SetCurrentRenderer(baseRenderer); if (baseRenderer.IsNull() || dataStorage.IsNull()) { m_Controls.selectionModeCheckBox->setEnabled(false); m_Controls.changeSelectionButton->setEnabled(false); // reset the model if no data storage is defined m_StorageModel->removeRows(0, m_StorageModel->rowCount()); return; } // Use the new data storage / node predicate to correctly set the list of // currently selected data nodes for the model. // If a new data storage or node predicate has been defined, // we switch to the "selectAll" mode and synchronize the selection for simplicity. // enable UI m_Controls.selectionModeCheckBox->setEnabled(true); m_Controls.changeSelectionButton->setEnabled(true); m_Controls.selectionModeCheckBox->setChecked(true); // set the base renderer of the model to nullptr, such that global properties are used (synchronized mode) m_StorageModel->SetCurrentRenderer(nullptr); } void QmitkSynchronizedNodeSelectionWidget::UpdateInfo() { } void QmitkSynchronizedNodeSelectionWidget::OnDataStorageChanged() { this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::OnNodePredicateChanged() { this->Initialize(); } void QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } bool isSynchronized = this->IsSynchronized(); if (isSynchronized) { this->ReviseSynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); } else { this->ReviseDesynchronizedSelectionChanged(oldInternalSelection, newInternalSelection); } // Since a new selection might have a different rendering tree the render windows // need to be updated. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); } void QmitkSynchronizedNodeSelectionWidget::OnInternalSelectionChanged() { m_StorageModel->SetCurrentSelection(this->GetCurrentInternalSelection()); } bool QmitkSynchronizedNodeSelectionWidget::AllowEmissionOfSelection(const NodeList& /*emissionCandidates*/) const { return this->IsSynchronized(); } void QmitkSynchronizedNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* node) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } // For helper / hidden nodes if (m_NodePredicate.IsNotNull() && !m_NodePredicate->CheckNode(node)) { // If the node predicate does not match, do not add the node to the current selection. // Leave the visibility as it is. return; } // The selection mode determines if we want to show all nodes from the data storage // or use a local selected list of nodes. // We need to hide each new incoming data node, if we use a local selection, // since we do not want to show / select newly added nodes immediately. // We need to add the incoming node to our selection, if the selection mode check box // is checked. // We want to add the incoming node to our selection, if the node is a child node // of an already selected node. // Nodes added to the selection will be made visible. if (m_Controls.selectionModeCheckBox->isChecked() || this->IsParentNodeSelected(node)) { auto currentSelection = this->GetCurrentInternalSelection(); // Check if the nodes is already part of the internal selection. // That can happen if another render window already added the new node and sent out the new, updated // selection to be synchronized. auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); if (finding != std::end(currentSelection)) // node found { // node already part of the selection return; } currentSelection.append(const_cast(node)); // This function will call 'QmitkSynchronizedNodeSelectionWidget::ReviseSelectionChanged' // which will take care of the visibility-property for newly added node. this->HandleChangeOfInternalSelection(currentSelection); } else { // If the widget is in "local-selection" state (selectionModeCheckBox unchecked), // the new incoming node needs to be hid. // Here it depends on the synchronization-state which properties need // to be modified. if (this->IsSynchronized()) { // If the node will not be part of the new selection, hide the node. const_cast(node)->SetVisibility(false); } else { // If the widget is not synchronized, all nodes use renderer-specific properties. // Thus we need to modify the renderer-specific properties of the node: // - hide the node, which is not part of the selection this->DeselectNode(const_cast(node)); } } } void QmitkSynchronizedNodeSelectionWidget::OnNodeModified(const itk::Object* caller, const itk::EventObject& event) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (!itk::ModifiedEvent().CheckEvent(&event)) { return; } auto node = dynamic_cast(caller); if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(node)) { auto currentSelection = this->GetCurrentInternalSelection(); // check if the node to be modified is part of the current selection auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), node); if (finding == std::end(currentSelection)) // node not found { // node not part of the selection return; } // We know that the node is relevant, but we don't know if the node modification was relevant // for the rendering. We just request an update here. // Explicitly request an update since a renderer-specific property change does not mark the node as modified. // see https://phabricator.mitk.org/T22322 mitk::RenderingManager::GetInstance()->RequestUpdate(baseRenderer->GetRenderWindow()); m_StorageModel->UpdateModelData(); } } void QmitkSynchronizedNodeSelectionWidget::ReviseSynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { // If the model is synchronized, all nodes use global / default properties. // Thus we need to modify the global properties of the selection: // - a) show those nodes, which are part of the new selection AND have not been // selected before // - b) keep the property values of those nodes, which are part of the new selection AND // have been selected before // - c) hide those nodes, which are part of the old selection AND // have not been newly selected for (auto& node : newInternalSelection) { // check if the node is part of the old selection auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); if (finding == std::end(oldInternalSelection)) // node not found { // If the node is part of the new selection and was not already part of the old selection, // set the relevant renderer-specific properties. // This will set the "visible" property for the global / default property list // and show the node for this renderer. node->SetVisibility(true); // item a) } // else: item b): node that was already selected before does not need to be modified } for (auto& node : oldInternalSelection) { // check if the node is part of the new selection auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); if (finding == std::end(newInternalSelection)) // node not found { // If the node is not part of the new selection, hide the node. node->SetVisibility(false); // item c) } // else: item b): node that was already selected before does not need to be modified } } void QmitkSynchronizedNodeSelectionWidget::ReviseDesynchronizedSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } // If the model is not synchronized, all nodes need renderer-specific properties. // Thus we need to modify the renderer-specific properties of the selection: // - a) set the renderer-specific properties of those nodes, which are part of the new selection AND // have not been selected before (see 'SelectNode') // - b) show those nodes, which are part of the new selection AND have not been // selected before // - c) keep the property values of those nodes, which are part of the new selection AND // have been selected before // - d) hide those nodes, which are part of the old selection AND // have not been newly selected // - e) set the renderer-specific properties of those nodes, which are part of the old selection AND // have not been newly selected, to denote which nodes are selected for (auto& node : newInternalSelection) { // check if the node is part of the old selection auto finding = std::find(std::begin(oldInternalSelection), std::end(oldInternalSelection), node); if (finding == std::end(oldInternalSelection)) // node not found { // If the node is part of the new selection and was not already part of the old selection, // set the relevant renderer-specific properties. // This will set the "visible" and "layer" property for the renderer-specific property list // such that the global / default property list values are overwritten mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(node, baseRenderer); // item a) // Explicitly set the visibility to true for selected nodes to show them in the render window. node->SetVisibility(true, baseRenderer); // item b) } // else: item c): node that was already selected before does not need to be modified } for (auto& node : oldInternalSelection) { // check if the node is part of the new selection auto finding = std::find(std::begin(newInternalSelection), std::end(newInternalSelection), node); if (finding == std::end(newInternalSelection)) // node not found { // If the node is not part of the new selection, unset the relevant renderer-specific properties. // This will unset the "visible" and "layer" property for the renderer-specific property list and // hide the node for this renderer. // ATTENTION: This is required, since the synchronized global property needs to be overwritten // to make sure that the visibility is correctly set for the specific base renderer. this->DeselectNode(node); // item d) and e) } // else: item c): node that was already selected before does not need to be modified } } void QmitkSynchronizedNodeSelectionWidget::ReinitNode(const mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } auto selectedImage = dynamic_cast(dataNode->GetData()); if (nullptr == selectedImage) { return; } auto boundingBoxPredicate = mitk::NodePredicateNot::New( mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false), baseRenderer)); if (!boundingBoxPredicate->CheckNode(dataNode)) { return; } mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), selectedImage->GetTimeGeometry()); } void QmitkSynchronizedNodeSelectionWidget::RemoveFromInternalSelection(mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (this->IsSynchronized()) { // If the model is synchronized, all nodes use global / default properties. // Thus we need to modify the global property of the node. // Explicitly set the visibility to false for unselected nodes to hide them in the render window. dataNode->SetVisibility(false); } m_Controls.selectionModeCheckBox->setChecked(false); emit SelectionModeChanged(false); this->RemoveNodeFromSelection(dataNode); } bool QmitkSynchronizedNodeSelectionWidget::IsParentNodeSelected(const mitk::DataNode* dataNode) const { auto dataStorage = m_DataStorage.Lock(); if (dataStorage.IsNull()) { return false; } auto currentSelection = this->GetCurrentInternalSelection(); auto parentNodes = dataStorage->GetSources(dataNode, m_NodePredicate, false); for (auto it = parentNodes->Begin(); it != parentNodes->End(); ++it) { const mitk::DataNode* parentNode = it->Value(); auto finding = std::find(std::begin(currentSelection), std::end(currentSelection), parentNode); if (finding != std::end(currentSelection)) // parent node found { // at least one parent node is part of the selection return true; } } return false; } void QmitkSynchronizedNodeSelectionWidget::DeselectNode(mitk::DataNode* dataNode) { auto baseRenderer = m_BaseRenderer.Lock(); if (baseRenderer.IsNull()) { return; } if (nullptr == dataNode) { return; } if (m_NodePredicate.IsNull() || m_NodePredicate->CheckNode(dataNode)) { // If the node should not be part of the selection, set the relevant renderer-specific properties. // This will set the "visible" and "layer" property for the renderer-specific property list, // such that the global / default property list values are overwritten. mitk::RenderWindowLayerUtilities::SetRenderWindowProperties(dataNode, baseRenderer); // Explicitly set the visibility to false for the node to hide them in the render window. dataNode->SetVisibility(false, baseRenderer); } } + +void QmitkSynchronizedNodeSelectionWidget::SelectAll() +{ + auto dataStorage = m_DataStorage.Lock(); + if (dataStorage.IsNull()) + { + return; + } + + auto allNodes = m_NodePredicate ? dataStorage->GetSubset(m_NodePredicate) : dataStorage->GetAll(); + NodeList currentSelection; + for (auto& node : *allNodes) + { + currentSelection.append(node); + } + + this->HandleChangeOfInternalSelection(currentSelection); +} diff --git a/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp index 8091ac673f..52949b0cea 100644 --- a/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp +++ b/Modules/QtWidgets/src/QmitkSynchronizedWidgetConnector.cpp @@ -1,101 +1,134 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // mitk gui qt common plugin #include "QmitkSynchronizedWidgetConnector.h" bool NodeListsEqual(const QmitkSynchronizedWidgetConnector::NodeList& selection1, const QmitkSynchronizedWidgetConnector::NodeList& selection2) { if (selection1.size() != selection2.size()) { return false; } // lambda to compare node pointer inside both lists auto lambda = [](mitk::DataNode::Pointer lhs, mitk::DataNode::Pointer rhs) { return lhs == rhs; }; return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin(), selection2.end(), lambda); } QmitkSynchronizedWidgetConnector::QmitkSynchronizedWidgetConnector() : m_SelectAll(true) + , m_ConnectionCounter(0) { } -void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +void QmitkSynchronizedWidgetConnector::ConnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) { connect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelection); connect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged, nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode); connect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged, nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll); + + connect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization, + this, &QmitkSynchronizedWidgetConnector::DeregisterWidget); + + m_ConnectionCounter++; } -void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const +void QmitkSynchronizedWidgetConnector::DisconnectWidget(const QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) { disconnect(nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelection); disconnect(this, &QmitkSynchronizedWidgetConnector::NodeSelectionChanged, nodeSelectionWidget, &QmitkAbstractNodeSelectionWidget::SetCurrentSelection); disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SelectionModeChanged, this, &QmitkSynchronizedWidgetConnector::ChangeSelectionMode); disconnect(this, &QmitkSynchronizedWidgetConnector::SelectionModeChanged, nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::SetSelectAll); + + disconnect(nodeSelectionWidget, &QmitkSynchronizedNodeSelectionWidget::DeregisterSynchronization, + this, &QmitkSynchronizedWidgetConnector::DeregisterWidget); + + this->DeregisterWidget(); } void QmitkSynchronizedWidgetConnector::SynchronizeWidget(QmitkSynchronizedNodeSelectionWidget* nodeSelectionWidget) const { - // widget is newly synchronized / connected so an initial setup needs to be made - nodeSelectionWidget->SetCurrentSelection(m_InternalSelection); + // We need to explicitly differentiate when "Select All" is active, since the internal selection + // might not contain all nodes. When no selection widget is synchronized, the m_InternalSelection + // won't be updated. + if (m_SelectAll) + { + nodeSelectionWidget->SelectAll(); + } + else + { + nodeSelectionWidget->SetCurrentSelection(m_InternalSelection); + } + nodeSelectionWidget->SetSelectAll(m_SelectAll); } QmitkSynchronizedWidgetConnector::NodeList QmitkSynchronizedWidgetConnector::GetNodeSelection() const { return m_InternalSelection; } bool QmitkSynchronizedWidgetConnector::GetSelectionMode() const { return m_SelectAll; } void QmitkSynchronizedWidgetConnector::ChangeSelection(NodeList nodes) { if (!NodeListsEqual(m_InternalSelection, nodes)) { m_InternalSelection = nodes; emit NodeSelectionChanged(m_InternalSelection); } } void QmitkSynchronizedWidgetConnector::ChangeSelectionMode(bool selectAll) { if (m_SelectAll!= selectAll) { m_SelectAll = selectAll; emit SelectionModeChanged(m_SelectAll); } } + +void QmitkSynchronizedWidgetConnector::DeregisterWidget() +{ + m_ConnectionCounter--; + + // When no more widgets are synchronized anymore, turn on SelectAll to avoid losing + // nodes that are added until synchronization of a widget is turned on again. + if (m_ConnectionCounter == 0) + { + m_SelectAll = true; + } +} diff --git a/Modules/QtWidgetsExt/CMakeLists.txt b/Modules/QtWidgetsExt/CMakeLists.txt index 85fdc156a1..9609183729 100644 --- a/Modules/QtWidgetsExt/CMakeLists.txt +++ b/Modules/QtWidgetsExt/CMakeLists.txt @@ -1,6 +1,18 @@ MITK_CREATE_MODULE( DEPENDS MitkAlgorithmsExt MitkQtWidgets PRIVATE MitkSceneSerializationBase PACKAGE_DEPENDS PUBLIC Qwt CTK|CTKWidgets PRIVATE Qt5|Concurrent+Svg+Xml VTK|IOImage ) + +if (TARGET ${MODULE_TARGET} AND MSVC) + #[[ Compiler warnings/errors because of QList, resp. Qwt on Visual Studio 2022 version 17.8: + + 'stdext::checked_array_iterator': warning STL4043: stdext::checked_array_iterator, + stdext::unchecked_array_iterator, and related factory functions are non-Standard extensions + and will be removed in the future. std::span (since C++20) and gsl::span can be used instead. + You can define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING or _SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS + to suppress this warning. + ]] + target_compile_definitions(${MODULE_TARGET} PUBLIC _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING) +endif() diff --git a/Modules/Segmentation/Interactions/mitkProcessExecutor.h b/Modules/Segmentation/Interactions/mitkProcessExecutor.h index 56aaed7dde..f67567de25 100644 --- a/Modules/Segmentation/Interactions/mitkProcessExecutor.h +++ b/Modules/Segmentation/Interactions/mitkProcessExecutor.h @@ -1,116 +1,116 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkProcessExecutor_h #define mitkProcessExecutor_h #include #include #include #include namespace mitk { // Class is adapted from MatchPoint ProcessExecutor class ExternalProcessOutputEvent : public itk::AnyEvent { public: typedef ExternalProcessOutputEvent Self; typedef itk::AnyEvent Superclass; explicit ExternalProcessOutputEvent(const std::string &output = "") : m_Output(output) {} ~ExternalProcessOutputEvent() override {} const char *GetEventName() const override { return "ExternalProcessOutputEvent"; } bool CheckEvent(const ::itk::EventObject *e) const override { return dynamic_cast(e); } itk::EventObject *MakeObject() const override { return new Self(m_Output); } std::string GetOutput() const { return m_Output; } private: std::string m_Output; }; #define mitkProcessExecutorEventMacro(classname) \ class classname : public ExternalProcessOutputEvent \ { \ public: \ typedef classname Self; \ typedef ExternalProcessOutputEvent Superclass; \ \ explicit classname(const std::string &output) : Superclass(output) {} \ ~classname() override {} \ \ virtual const char *GetEventName() const { return #classname; } \ virtual bool CheckEvent(const ::itk::EventObject *e) const { return dynamic_cast(e); } \ virtual ::itk::EventObject *MakeObject() const { return new Self(this->GetOutput()); } \ }; mitkProcessExecutorEventMacro(ExternalProcessStdOutEvent); mitkProcessExecutorEventMacro(ExternalProcessStdErrEvent); /** * @brief You may register an observer for an ExternalProcessOutputEvent, ExternalProcessStdOutEvent or * ExternalProcessStdErrEvent in order to get notified of any output. * @remark The events will only be invoked if the pipes are NOT(!) shared. By default the pipes are not shared. * */ class MITKSEGMENTATION_EXPORT ProcessExecutor : public itk::Object { public: using Self = ProcessExecutor; using Superclass = ::itk::Object; using Pointer = ::itk::SmartPointer; using ConstPointer = ::itk::SmartPointer; itkTypeMacro(ProcessExecutor, ::itk::Object); itkFactorylessNewMacro(Self); itkSetMacro(SharedOutputPipes, bool); itkGetConstMacro(SharedOutputPipes, bool); using ArgumentListType = std::vector; bool Execute(const std::string &executionPath, const std::string &executableName, ArgumentListType &argumentList); /** * @brief Executes the process. This version assumes that the executable name is the first argument in the argument * list and has already been converted to its OS dependent name via the static convert function of this class. */ - bool Execute(const std::string &executionPath, const ArgumentListType &argumentList); + virtual bool Execute(const std::string &executionPath, const ArgumentListType &argumentList); int GetExitValue(); static std::string EnsureCorrectOSPathSeparator(const std::string &); static std::string GetOSDependendExecutableName(const std::string &name); void KillProcess(); protected: ProcessExecutor(); ~ProcessExecutor() override; int m_ExitValue; /** * @brief Specifies if the child process should share the output pipes (true) or not (false). * If pipes are not shared the output will be passed by invoking ExternalProcessOutputEvents * @remark The events will only be invoked if the pipes are NOT(!) shared. */ bool m_SharedOutputPipes; private: itksysProcess *m_ProcessID = nullptr; }; } // namespace mitk #endif diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 9ff48f4df5..77ffb417cd 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,819 +1,842 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::~SegWithPreviewTool() { } void mitk::SegWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferScope(LabelTransferScope labelTransferScope) { m_LabelTransferScope = labelTransferScope; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferMode(LabelTransferMode labelTransferMode) { m_LabelTransferMode = labelTransferMode; this->Modified(); } void mitk::SegWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) { m_SelectedLabels = labelsToTransfer; this->Modified(); } bool mitk::SegWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return false; auto* referenceImage = dynamic_cast(referenceData); if (referenceImage == nullptr) return false; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* workingImage = dynamic_cast(workingData); if (workingImage == nullptr) return false; // If the working image is a normal image and not a label set image // it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == workingImage->GetPixelType(); } void mitk::SegWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } } void mitk::SegWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::SegWithPreviewTool::ConfirmSegmentation() { bool labelChanged = this->EnsureUpToDateUserDefinedActiveLabel(); if ((m_LazyDynamicPreviews && m_CreateAllTimeSteps) || labelChanged) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } + this->ConfirmCleanUp(); } void mitk::SegWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } const mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::SegWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::SegWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::SegWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::SegWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } void mitk::SegWithPreviewTool::ResetPreviewContent() { auto previewImage = this->GetPreviewSegmentation(); if (nullptr != previewImage) { auto castedPreviewImage = dynamic_cast(previewImage); if (nullptr == castedPreviewImage) mitkThrow() << "Application is on wrong state / invalid tool implementation. Preview image should always be of type LabelSetImage now."; castedPreviewImage->ClearBuffer(); } } void mitk::SegWithPreviewTool::ResetPreviewNode() { if (m_IsUpdating) { mitkThrow() << "Used tool is implemented incorrectly. ResetPreviewNode is called while preview update is ongoing. Check implementation!"; } itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { LabelSetImage::ConstPointer workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); auto* activeLabelSet = newPreviewImage->GetActiveLabelSet(); if (nullptr == activeLabelSet) { newPreviewImage->AddLayer(); activeLabelSet = newPreviewImage->GetActiveLabelSet(); } auto* activeLabel = activeLabelSet->GetActiveLabel(); if (nullptr == activeLabel) { activeLabel = activeLabelSet->AddLabel("toolresult", previewColor); activeLabelSet = newPreviewImage->GetActiveLabelSet(); activeLabelSet->UpdateLookupTable(activeLabel->GetValue()); } else if (m_UseSpecialPreviewColor) { // Let's paint the feedback node green... activeLabel->SetColor(previewColor); activeLabelSet->UpdateLookupTable(activeLabel->GetValue()); } activeLabel->SetVisible(true); } else { Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } mitk::SegWithPreviewTool::LabelMappingType mitk::SegWithPreviewTool::GetLabelMapping() const { LabelSetImage::LabelValueType offset = 0; if (LabelTransferMode::AddLabel == m_LabelTransferMode && LabelTransferScope::ActiveLabel!=m_LabelTransferScope) { //If we are not just working on active label and transfer mode is add, we need to compute an offset for adding the //preview labels instat of just mapping them to existing segmentation labels. const auto segmentation = this->GetTargetSegmentation(); if (nullptr == segmentation) mitkThrow() << "Invalid state of SegWithPreviewTool. Cannot GetLabelMapping if no target segmentation is set."; auto labels = segmentation->GetLabels(); auto maxLabelIter = std::max_element(std::begin(labels), std::end(labels), [](const Label::Pointer& a, const Label::Pointer& b) { return a->GetValue() < b->GetValue(); }); if (maxLabelIter != labels.end()) { offset = maxLabelIter->GetPointer()->GetValue(); } } LabelMappingType labelMapping = {}; switch (this->m_LabelTransferScope) { case LabelTransferScope::SelectedLabels: { for (auto label : this->m_SelectedLabels) { labelMapping.push_back({label, label + offset}); } } break; case LabelTransferScope::AllLabels: { const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { labelMapping.push_back({labelIter->second->GetValue(), labelIter->second->GetValue() + offset}); } } break; default: { if (m_SelectedLabels.empty()) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but no label is indicated as selected label. Check " "implementation of derived tool class."; if (m_SelectedLabels.size() > 1) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but more then one selected label is indicated." "Should be only one. Check implementation of derived tool class."; labelMapping.push_back({m_SelectedLabels.front(), this->GetUserDefinedActiveLabel()}); } break; } return labelMapping; } void mitk::SegWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); - SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); + auto resultSlice = + SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), destinationImage, timeStep)->Clone(); + auto destLSImage = dynamic_cast(destinationImage); + //We need to transfer explictly to a copy of the current working image to ensure that labelMapping is done and things + //like merge style, overwrite style and locks are regarded. + TransferLabelContentAtTimeStep(sourceSlice, + resultSlice, + destLSImage->GetActiveLabelSet(), + timeStep, + 0, + 0, + destLSImage->GetUnlabeledLabelLock(), + labelMapping, + m_MergeStyle, + m_OverwriteStyle); + //We use WriteBackSegmentationResult to ensure undo/redo is supported also by derived tools of this class. + SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, resultSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); TransferLabelContentAtTimeStep(sourceLSImage, destLSImage, timeStep, labelMapping, m_MergeStyle, m_OverwriteStyle); } } catch (mitk::Exception& e) { Tool::ErrorMessage(e.GetDescription()); mitkReThrow(e); } } void mitk::SegWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working referenceImage (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } auto labelMapping = this->GetLabelMapping(); this->PreparePreviewToResultTransfer(labelMapping); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } // since we are maybe working on a smaller referenceImage, pad it to the size of the original referenceImage if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { PadImageFilter::Pointer padFilter = PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::SegWithPreviewTool::OnRoiDataChanged() { DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { MaskAndCutRoiImageFilter::Pointer roiFilter = MaskAndCutRoiImageFilter::New(); Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); DataNode::Pointer tmpNode = DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::SegWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic referenceImage this->UpdatePreview(); } } } bool mitk::SegWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() { bool labelChanged = true; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (const auto& labelSetImage = dynamic_cast(workingImage)) { // this is a fix for T28131 / T28986, which should be refactored if T28524 is being worked on auto newLabel = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } void mitk::SegWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); this->EnsureUpToDateUserDefinedActiveLabel(); this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback referenceImage. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback referenceImage feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::SegWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::SegWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } +void mitk::SegWithPreviewTool::ConfirmCleanUp() +{ + // default implementation does nothing + // reimplement in derived classes for special behavior +} + void mitk::SegWithPreviewTool::TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target) { for (const auto& [sourceLabel, targetLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue != sourceLabel && LabelSetImage::UnlabeledValue != targetLabel && !target->ExistLabel(targetLabel, target->GetActiveLayer())) { if (!source->ExistLabel(sourceLabel, source->GetActiveLayer())) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << sourceLabel; } auto clonedLabel = source->GetLabel(sourceLabel)->Clone(); clonedLabel->SetValue(targetLabel); target->GetActiveLabelSet()->AddLabel(clonedLabel); } } } void mitk::SegWithPreviewTool::PreparePreviewToResultTransfer(const LabelMappingType& labelMapping) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); if (nullptr == resultSegmentation) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Tool is in invalid state as segmentation is not existing or of right type"; } auto preview = this->GetPreviewSegmentation(); TransferLabelInformation(labelMapping, preview, resultSegmentation); } } mitk::TimePointType mitk::SegWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } mitk::LabelSetImage::LabelValueType mitk::SegWithPreviewTool::GetActiveLabelValueOfPreview() const { const auto previewImage = this->GetPreviewSegmentation(); const auto activeLabel = previewImage->GetActiveLabel(previewImage->GetActiveLayer()); if (nullptr == activeLabel) mitkThrow() << this->GetNameOfClass() <<" is in an invalid state, as " "preview has no active label indicated. Check " "implementation of the class."; return activeLabel->GetValue(); } const char* mitk::SegWithPreviewTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::Pointer mitk::SegWithPreviewTool::GetImageByTimeStep(mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { return SelectImageByTimePoint(image, timePoint); } void mitk::SegWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); auto dataStorage = this->GetToolManager()->GetDataStorage(); if (!dataStorage->Exists(targetNode)) { dataStorage->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } std::string mitk::SegWithPreviewTool::GetCurrentSegmentationName() { auto workingData = this->GetToolManager()->GetWorkingData(0); return nullptr != workingData ? workingData->GetName() : ""; } mitk::DataNode* mitk::SegWithPreviewTool::GetTargetSegmentationNode() const { return this->GetToolManager()->GetWorkingData(0); } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetTargetSegmentation() const { auto node = this->GetTargetSegmentationNode(); if (nullptr == node) return nullptr; return dynamic_cast(node->GetData()); } void mitk::SegWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) { mitk::ImageReadAccessor newMitkImgAcc(source); LabelMappingType labelMapping; const auto labelSet = source->GetActiveLabelSet(); for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); } TransferLabelInformation(labelMapping, source, target); target->SetVolume(newMitkImgAcc.GetData(), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h index a4141de0b1..109fb9370e 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.h @@ -1,321 +1,326 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegWithPreviewTool_h #define mitkSegWithPreviewTool_h #include "mitkTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkToolCommand.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. This tool class implements a lot basic logic to handle auto segmentation tools with preview, Time point and ROI support. Derived classes will ask to update the segmentation preview if needed (e.g. because the ROI or the current time point has changed) or because derived tools indicated the need to update themselves. This class also takes care to properly transfer a confirmed preview into the segementation result. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT SegWithPreviewTool : public Tool { public: mitkClassMacro(SegWithPreviewTool, Tool); void Activated() override; void Deactivated() override; void ConfirmSegmentation(); itkSetMacro(CreateAllTimeSteps, bool); itkGetMacro(CreateAllTimeSteps, bool); itkBooleanMacro(CreateAllTimeSteps); itkSetMacro(KeepActiveAfterAccept, bool); itkGetMacro(KeepActiveAfterAccept, bool); itkBooleanMacro(KeepActiveAfterAccept); itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); itkSetMacro(ResetsToEmptyPreview, bool); itkGetMacro(ResetsToEmptyPreview, bool); itkBooleanMacro(ResetsToEmptyPreview); itkSetMacro(UseSpecialPreviewColor, bool); itkGetMacro(UseSpecialPreviewColor, bool); itkBooleanMacro(UseSpecialPreviewColor); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle); itkGetMacro(MergeStyle, MultiLabelSegmentation::MergeStyle); /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle); itkGetMacro(OverwriteStyle, MultiLabelSegmentation::OverwriteStyle); enum class LabelTransferScope { ActiveLabel, //Only the selected label will be transfered from the preview segmentation //to the result segmentation. //If this mode is selected the class expects that GetSelectedLabels indicate //the label in the preview. SelectedLabels, //The labels defined as selected labels will be transfered. AllLabels //Transfer all labels of the preview }; /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetLabelTransferScope(LabelTransferScope labelTransferScope); itkGetMacro(LabelTransferScope, LabelTransferScope); using SelectedLabelVectorType = std::vector; /** Specifies the labels that should be transfered form preview to the working image, if the segmentation is confirmed. The setting will be used, if LabelTransferScope is set to "ActiveLabel" or "SelectedLabels". @remark If the LabelTransferScope=="ActiveLabel", the class expects only one label to be selected. @remark The selected label IDs corespond to the labels of the preview image.*/ void SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer); itkGetMacro(SelectedLabels, SelectedLabelVectorType); enum class LabelTransferMode { MapLabel, //Only the active label will be transfered from preview to segmentation. AddLabel //The labels defined as selected labels will be transfered. }; /*itk macro was not used on purpose, to aviod the change of mtime.*/ void SetLabelTransferMode(LabelTransferMode labelTransferMode); itkGetMacro(LabelTransferMode, LabelTransferMode); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** Triggers the actualization of the preview * @param ignoreLazyPreviewSetting If set true UpdatePreview will always * generate the preview for all time steps. If set to false, UpdatePreview * will regard the setting specified by the constructor. * To define the update generation for time steps implement DoUpdatePreview. * To alter what should be done directly before or after the update of the preview, * reimplement UpdatePrepare() or UpdateCleanUp().*/ void UpdatePreview(bool ignoreLazyPreviewSetting = false); /** Indicate if currently UpdatePreview is triggered (true) or not (false).*/ bool IsUpdating() const; /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** * @brief Returns the currently selected segmentation node * @return a mitk::DataNode which contains a segmentation image */ virtual DataNode* GetTargetSegmentationNode() const; LabelSetImage* GetTargetSegmentation() const; /** Returns the image that contains the preview of the current segmentation. * Returns null if the node is not set or does not contain an image.*/ LabelSetImage* GetPreviewSegmentation(); const LabelSetImage* GetPreviewSegmentation() const; DataNode* GetPreviewSegmentationNode(); protected: ToolCommand::Pointer m_ProgressCommand; SegWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden ~SegWithPreviewTool() override; const char* GetGroup() const override; /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimeStep(const Image* image, TimeStepType timestep); /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::Pointer GetImageByTimeStep(Image* image, TimeStepType timestep); /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); void EnsureTargetSegmentationNodeInDataStorage() const; /** Member is always called if GetSegmentationInput() has changed * (e.g. because a new ROI was defined, or on activation) to give derived * classes the posibility to initiate their state accordingly. * Reimplement this function to implement special behavior. */ virtual void InitiateToolByInput(); /** This member function offers derived classes the possibility to alter what should happen directly before the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdatePrepare(); /** This member function offers derived classes the possibility to alter what should happen directly after the update of the preview is performed. It is called by UpdatePreview. Default implementation does nothing.*/ virtual void UpdateCleanUp(); + /** This member function offers derived classes the possibility to alter what should + happen directly after the Confirmation of the preview is performed. It is called by + ConfirmSegmentation. Default implementation does nothing.*/ + virtual void ConfirmCleanUp(); + using LabelMappingType = std::vector >; /** This member function offers derived classes the possibility to alter what should happen directly before the content of the preview is transfered to the segmentation, when the segmentation is confirmed. It is called by CreateResultSegmentationFromPreview. Default implementation ensure that all labels that will be transfered, exist in the segmentation. If they are not existing before the transfer, the will be added by cloning the label information of the preview. @param labelMapping the mapping that should be used for transfering the labels. */ virtual void PreparePreviewToResultTransfer(const LabelMappingType& labelMapping); static void TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target); /**Helper function that can be used to move the content of an LabelSetImage (the pixels of the active source layer and the labels). This is e.g. helpfull if you generate an LabelSetImage content in DoUpdatePreview and you want to transfer it into the preview image.*/ static void TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep); /** This function does the real work. Here the preview for a given * input image should be computed and stored in the also passed * preview image at the passed time step. * It also provides the current/old segmentation at the time point, * which can be used, if the preview depends on the the segmenation so far. */ virtual void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) = 0; /** Returns the input that should be used for any segmentation/preview or tool update. * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask * provided by the tool manager. Derived classes should regard this as the relevant * input data for any processing. * Returns null if the node is not set or does not contain an image.*/ const Image* GetSegmentationInput() const; /** Returns the image that is provided by the ReferenceDataNode. * Returns null if the node is not set or does not contain an image.*/ const Image* GetReferenceData() const; /** Resets the preview node so it is empty and ready to be filled by the tool @remark Calling this function will generate a new preview image, and the old might be invalidated. Therefore this function should not be used within the scope of UpdatePreview (m_IsUpdating == true).*/ void ResetPreviewNode(); /** Resets the complete content of the preview image. The instance of the preview image and its settings * stay the same.*/ void ResetPreviewContent(); /** Resets only the image content of the specified timeStep of the preview image. If the preview image or the specified time step does not exist, nothing happens.*/ void ResetPreviewContentAtTimeStep(unsigned int timeStep); TimePointType GetLastTimePointOfUpdate() const; LabelSetImage::LabelValueType GetActiveLabelValueOfPreview() const; itkGetConstMacro(UserDefinedActiveLabel, Label::PixelType); itkSetObjectMacro(WorkingPlaneGeometry, PlaneGeometry); itkGetConstObjectMacro(WorkingPlaneGeometry, PlaneGeometry); private: void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping); void CreateResultSegmentationFromPreview(); void OnRoiDataChanged(); void OnTimePointChanged(); /**Internal helper that ensures that the stored active label is up to date. This is a fix for T28131 / T28986. It should be refactored if T28524 is being worked on. On the long run, the active label will be communicated/set by the user/toolmanager as a state of the tool and the tool should react accordingly (like it does for other external state changes). @return indicates if the label has changed (true) or not. */ bool EnsureUpToDateUserDefinedActiveLabel(); /**Returns that label mapping between preview segmentation (first element of pair) and result segmentation (second element of pair). The content depends on the settings of LabelTransferMode and LabelTransferScope*/ LabelMappingType GetLabelMapping() const; /** Node that containes the preview data generated and managed by this class or derived ones.*/ DataNode::Pointer m_PreviewSegmentationNode; /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ DataNode::Pointer m_ReferenceDataNode; /** Node that containes the data that should be used as input for any auto segmentation. It might * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ DataNode::Pointer m_SegmentationInputNode; /** Indicates if Accepting the threshold should transfer/create the segmentations of all time steps (true) or only of the currently selected timepoint (false).*/ bool m_CreateAllTimeSteps = false; /** Indicates if the tool should kept active after accepting the segmentation or not.*/ bool m_KeepActiveAfterAccept = false; /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. * Therefore it always has to update the preview if current time point has changed and it has to (re)compute * all timeframes if ConfirmSegmentation() is called.*/ bool m_LazyDynamicPreviews = false; bool m_IsTimePointChangeAware = true; /** Controls if ResetPreviewNode generates an empty content (true) or clones the current segmentation (false).*/ bool m_ResetsToEmptyPreview = false; /** Controls if for the preview of the active label a special preview color is used. * If set to false, coloring will stay in the preview like it is in the working image.*/ bool m_UseSpecialPreviewColor = true; TimePointType m_LastTimePointOfUpdate = 0.; bool m_IsUpdating = false; Label::PixelType m_UserDefinedActiveLabel = 1; /** This variable indicates if for the tool a working plane geometry is defined. * If a working plane is defined the tool will only work an the slice of the input * and the segmentation. Thus only the relevant input slice will be passed to * DoUpdatePreview(...) and only the relevant slice of the preview will be transfered when * ConfirmSegmentation() is called.*/ PlaneGeometry::Pointer m_WorkingPlaneGeometry; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::MergeStyle. */ MultiLabelSegmentation::MergeStyle m_MergeStyle = MultiLabelSegmentation::MergeStyle::Replace; /** This variable controles how the label pixel content of the preview should be transfered into the segmentation- For more details of the behavior see documentation of MultiLabelSegmentation::OverwriteStyle. */ MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = MultiLabelSegmentation::OverwriteStyle::RegardLocks; LabelTransferScope m_LabelTransferScope = LabelTransferScope::ActiveLabel; SelectedLabelVectorType m_SelectedLabels = {}; LabelTransferMode m_LabelTransferMode = LabelTransferMode::MapLabel; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingProcessExecutor.cpp b/Modules/Segmentation/Interactions/mitkSegmentAnythingProcessExecutor.cpp new file mode 100644 index 0000000000..517628a279 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingProcessExecutor.cpp @@ -0,0 +1,83 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkSegmentAnythingProcessExecutor.h" + +#include + +bool mitk::SegmentAnythingProcessExecutor::Execute(const std::string &executionPath, const ArgumentListType &argumentList) +{ + std::vector pArguments_(argumentList.size() + 1); + + for (ArgumentListType::size_type index = 0; index < argumentList.size(); ++index) + { + pArguments_[index] = argumentList[index].c_str(); + } + pArguments_.push_back(nullptr); // terminating null element as required by ITK + bool normalExit = false; + try + { + m_ProcessID = itksysProcess_New(); + itksysProcess_SetCommand(m_ProcessID, pArguments_.data()); + + /* Place the process in a new process group for seamless interruption when required. */ + itksysProcess_SetOption(m_ProcessID, itksysProcess_Option_CreateProcessGroup, 1); + + itksysProcess_SetWorkingDirectory(m_ProcessID, executionPath.c_str()); + if (this->m_SharedOutputPipes) + { + itksysProcess_SetPipeShared(m_ProcessID, itksysProcess_Pipe_STDOUT, 1); + itksysProcess_SetPipeShared(m_ProcessID, itksysProcess_Pipe_STDERR, 1); + } + itksysProcess_Execute(m_ProcessID); + char *rawOutput = nullptr; + int outputLength = 0; + double timer = m_Timeout; + while (!m_Stop) + { + double *timeout = &timer; + *timeout = m_Timeout; //re-assigning timeout since itksysProcess calls will tamper with input timeout argument. + int dataStatus = itksysProcess_WaitForData(m_ProcessID, &rawOutput, &outputLength, timeout); + if (dataStatus == itksysProcess_Pipe_STDOUT) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdOutEvent(data)); + } + else if (dataStatus == itksysProcess_Pipe_STDERR) + { + std::string data(rawOutput, outputLength); + this->InvokeEvent(ExternalProcessStdErrEvent(data)); + } + } + timer = m_Timeout; //re-assigning timeout since itksysProcess calls will tamper with input timeout argument. + itksysProcess_Kill(m_ProcessID); + itksysProcess_WaitForExit(m_ProcessID, &timer); + auto state = static_cast(itksysProcess_GetState(m_ProcessID)); + normalExit = (state == itksysProcess_State_Exited); + this->m_ExitValue = itksysProcess_GetExitValue(m_ProcessID); + } + catch (...) + { + throw; + } + return normalExit; +}; + +mitk::SegmentAnythingProcessExecutor::SegmentAnythingProcessExecutor(double &timeout) +{ + this->SetTimeout(timeout); +} + +void mitk::SegmentAnythingProcessExecutor::SetTimeout(double &timeout) +{ + m_Timeout = timeout; +} diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingProcessExecutor.h b/Modules/Segmentation/Interactions/mitkSegmentAnythingProcessExecutor.h new file mode 100644 index 0000000000..9e45db8452 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingProcessExecutor.h @@ -0,0 +1,64 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkSegmentAnythingProcessExecutor_h +#define mitkSegmentAnythingProcessExecutor_h + +#include +#include +#include +#include + +namespace mitk +{ + /** + * @brief You may register an observer for an ExternalProcessOutputEvent, ExternalProcessStdOutEvent or + * ExternalProcessStdErrEvent in order to get notified of any output. + * @remark The events will only be invoked if the pipes are NOT(!) shared. By default the pipes are not shared. + * + */ + class MITKSEGMENTATION_EXPORT SegmentAnythingProcessExecutor : public mitk::ProcessExecutor + { + public: + using Self = SegmentAnythingProcessExecutor; + using Superclass = mitk::ProcessExecutor; + using Pointer = ::itk::SmartPointer; + using ConstPointer = ::itk::SmartPointer; + using mitk::ProcessExecutor::Execute; + + itkTypeMacro(SegmentAnythingProcessExecutor, mitk::ProcessExecutor); + mitkNewMacro1Param(SegmentAnythingProcessExecutor, double&); + + itkSetMacro(Stop, bool); + itkGetConstMacro(Stop, bool); + + /** + * @brief Executes the process. This version assumes that the executable name is the first argument in the argument + * list and has already been converted to its OS dependent name via the static convert function of this class. + */ + bool Execute(const std::string &executionPath, const ArgumentListType &argumentList); + + void SetTimeout(double &timeout); + + protected: + SegmentAnythingProcessExecutor() = delete; + SegmentAnythingProcessExecutor(double &); + ~SegmentAnythingProcessExecutor() = default; + + private: + itksysProcess *m_ProcessID = nullptr; + double m_Timeout; + bool m_Stop = false; // Exit token to force stop the daemon. + }; + +} // namespace mitk +#endif diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.cpp b/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.cpp new file mode 100644 index 0000000000..13674f1066 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.cpp @@ -0,0 +1,265 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkSegmentAnythingPythonService.h" + +#include "mitkIOUtil.h" +#include +#include +#include +#include +#include +#include +#include "mitkImageAccessByItk.h" +#include + +using namespace std::chrono_literals; +using sys_clock = std::chrono::system_clock; + +namespace mitk +{ + const std::string SIGNALCONSTANTS::READY = "READY"; + const std::string SIGNALCONSTANTS::KILL = "KILL"; + const std::string SIGNALCONSTANTS::OFF = "OFF"; + const std::string SIGNALCONSTANTS::CUDA_OUT_OF_MEMORY_ERROR = "CudaOutOfMemoryError"; + const std::string SIGNALCONSTANTS::TIMEOUT_ERROR = "TimeOut"; + SegmentAnythingPythonService::Status SegmentAnythingPythonService::CurrentStatus = + SegmentAnythingPythonService::Status::OFF; +} + +mitk::SegmentAnythingPythonService::SegmentAnythingPythonService( + std::string workingDir, std::string modelType, std::string checkPointPath, unsigned int gpuId) + : m_PythonPath(workingDir), + m_ModelType(modelType), + m_CheckpointPath(checkPointPath), + m_GpuId(gpuId) +{ + this->CreateTempDirs(PARENT_TEMP_DIR_PATTERN); +} + +mitk::SegmentAnythingPythonService::~SegmentAnythingPythonService() +{ + if (CurrentStatus == Status::READY) + { + this->StopAsyncProcess(); + } + CurrentStatus = Status::OFF; + std::filesystem::remove_all(this->GetMitkTempDir()); + } + +void mitk::SegmentAnythingPythonService::onPythonProcessEvent(itk::Object*, const itk::EventObject &e, void*) +{ + std::string testCOUT,testCERR; + const auto *pEvent = dynamic_cast(&e); + if (pEvent) + { + testCOUT = testCOUT + pEvent->GetOutput(); + testCOUT.erase(std::find_if(testCOUT.rbegin(), testCOUT.rend(), [](unsigned char ch) { + return !std::isspace(ch);}).base(), testCOUT.end()); // remove trailing whitespaces, if any + if (SIGNALCONSTANTS::READY == testCOUT) + { + CurrentStatus = Status::READY; + } + if (SIGNALCONSTANTS::KILL == testCOUT) + { + CurrentStatus = Status::KILLED; + } + if (SIGNALCONSTANTS::CUDA_OUT_OF_MEMORY_ERROR == testCOUT) + { + CurrentStatus = Status::CUDAError; + } + MITK_INFO << testCOUT; + } + const auto *pErrEvent = dynamic_cast(&e); + if (pErrEvent) + { + testCERR = testCERR + pErrEvent->GetOutput(); + MITK_ERROR << testCERR; + } +} + +void mitk::SegmentAnythingPythonService::StopAsyncProcess() +{ + std::stringstream controlStream; + controlStream << SIGNALCONSTANTS::KILL; + this->WriteControlFile(controlStream); + m_DaemonExec->SetStop(true); + m_Future.get(); +} + +void mitk::SegmentAnythingPythonService::StartAsyncProcess() +{ + if (nullptr != m_DaemonExec) + { + this->StopAsyncProcess(); + } + if (this->GetMitkTempDir().empty()) + { + this->CreateTempDirs(PARENT_TEMP_DIR_PATTERN); + } + std::stringstream controlStream; + controlStream << SIGNALCONSTANTS::READY; + this->WriteControlFile(controlStream); + double timeout = 1; + m_DaemonExec = SegmentAnythingProcessExecutor::New(timeout); + itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&mitk::SegmentAnythingPythonService::onPythonProcessEvent); + m_DaemonExec->AddObserver(ExternalProcessOutputEvent(), spCommand); + m_Future = std::async(std::launch::async, &mitk::SegmentAnythingPythonService::start_python_daemon, this); + } + +void mitk::SegmentAnythingPythonService::TransferPointsToProcess(std::stringstream &triggerCSV) +{ + this->CheckStatus(); + std::string triggerFilePath = m_InDir + IOUtil::GetDirectorySeparator() + TRIGGER_FILENAME; + std::ofstream csvfile; + csvfile.open(triggerFilePath, std::ofstream::out | std::ofstream::trunc); + csvfile << triggerCSV.rdbuf(); + csvfile.close(); +} + +void mitk::SegmentAnythingPythonService::WriteControlFile(std::stringstream &statusStream) +{ + std::string controlFilePath = m_InDir + IOUtil::GetDirectorySeparator() + "control.txt"; + std::ofstream controlFile; + controlFile.open(controlFilePath, std::ofstream::out | std::ofstream::trunc); + controlFile << statusStream.rdbuf(); + controlFile.close(); +} + +void mitk::SegmentAnythingPythonService::start_python_daemon() +{ + ProcessExecutor::ArgumentListType args; + std::string command = "python"; + args.push_back("-u"); + + args.push_back(SAM_PYTHON_FILE_NAME); + + args.push_back("--input-folder"); + args.push_back(m_InDir); + + args.push_back("--output-folder"); + args.push_back(m_OutDir); + + args.push_back("--trigger-file"); + args.push_back(TRIGGER_FILENAME); + + args.push_back("--model-type"); + args.push_back(m_ModelType); + + args.push_back("--checkpoint"); + args.push_back(m_CheckpointPath); + + args.push_back("--device"); + if (m_GpuId == -1) + { + args.push_back("cpu"); + } + else + { + args.push_back("cuda"); + std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(m_GpuId); + itksys::SystemTools::PutEnv(cudaEnv.c_str()); + } + + try + { + std::stringstream logStream; + for (const auto &arg : args) + logStream << arg << " "; + logStream << m_PythonPath; + MITK_INFO << logStream.str(); + m_DaemonExec->Execute(m_PythonPath, command, args); + } + catch (const mitk::Exception &e) + { + MITK_ERROR << e.GetDescription(); + return; + } + MITK_INFO << "Python process ended."; +} + +bool mitk::SegmentAnythingPythonService::CheckStatus() +{ + switch (CurrentStatus) + { + case mitk::SegmentAnythingPythonService::Status::READY: + return true; + case mitk::SegmentAnythingPythonService::Status::CUDAError: + mitkThrow() << "Error: Cuda Out of Memory. Change your model type in Preferences and Activate Segment Anything tool again."; + case mitk::SegmentAnythingPythonService::Status::KILLED: + mitkThrow() << "Error: Python process is already terminated. Cannot load requested segmentation. Activate Segment Anything tool again."; + default: + return false; + } +} + +void mitk::SegmentAnythingPythonService::CreateTempDirs(const std::string &dirPattern) +{ + this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory(dirPattern)); + m_InDir = IOUtil::CreateTemporaryDirectory("sam-in-XXXXXX", m_MitkTempDir); + m_OutDir = IOUtil::CreateTemporaryDirectory("sam-out-XXXXXX", m_MitkTempDir); +} + +mitk::LabelSetImage::Pointer mitk::SegmentAnythingPythonService::RetrieveImageFromProcess(long timeOut) +{ + std::string outputImagePath = m_OutDir + IOUtil::GetDirectorySeparator() + m_CurrentUId + ".nrrd"; + auto start = sys_clock::now(); + while (!std::filesystem::exists(outputImagePath)) + { + this->CheckStatus(); + std::this_thread::sleep_for(100ms); + if (timeOut != -1 && std::chrono::duration_cast(sys_clock::now() - start).count() > timeOut) + { + CurrentStatus = Status::OFF; + m_DaemonExec->SetStop(true); + mitkThrow() << SIGNALCONSTANTS::TIMEOUT_ERROR; + + } + } + LabelSetImage::Pointer outputBuffer = mitk::IOUtil::Load(outputImagePath); + return outputBuffer; +} + +void mitk::SegmentAnythingPythonService::TransferImageToProcess(const Image *inputAtTimeStep, std::string &UId) +{ + std::string inputImagePath = m_InDir + IOUtil::GetDirectorySeparator() + UId + ".nrrd"; + if (inputAtTimeStep->GetPixelType().GetNumberOfComponents() < 2) + { + AccessByItk_n(inputAtTimeStep, ITKWriter, (inputImagePath)); + } + else + { + mitk::IOUtil::Save(inputAtTimeStep, inputImagePath); + } + m_CurrentUId = UId; +} + +template +void mitk::SegmentAnythingPythonService::ITKWriter(const itk::Image *image, std::string& outputFilename) +{ + typedef itk::Image ImageType; + typedef itk::ImageFileWriter WriterType; + typename WriterType::Pointer writer = WriterType::New(); + mitk::LocaleSwitch localeSwitch("C"); + writer->SetFileName(outputFilename); + writer->SetInput(image); + try + { + writer->Update(); + } + catch (const itk::ExceptionObject &error) + { + MITK_ERROR << "Error: " << error << std::endl; + mitkThrow() << "Error: " << error; + } +} diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.h b/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.h new file mode 100644 index 0000000000..1e54928e6b --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingPythonService.h @@ -0,0 +1,151 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkSegmentAnythingPythonService_h +#define mitkSegmentAnythingPythonService_h + +#include +#include +#include +#include +#include +#include +#include + +namespace mitk +{ + /** + * @brief Segment Anything Model Python process handler class. + * + */ + class MITKSEGMENTATION_EXPORT SegmentAnythingPythonService : public itk::Object + { + public: + enum Status + { + READY, + OFF, + KILLED, + CUDAError + }; + + SegmentAnythingPythonService(std::string, std::string, std::string, unsigned int); + ~SegmentAnythingPythonService(); + + itkSetMacro(MitkTempDir, std::string); + itkGetConstMacro(MitkTempDir, std::string); + + /** + * @brief Static function to print out everything from itk::EventObject. + * Used as callback in mitk::ProcessExecutor object. + * + */ + static void onPythonProcessEvent(itk::Object*, const itk::EventObject&, void*); + + /** + * @brief Checks CurrentStatus enum variable and returns + * true if daemon is READY (to read files) state, false is OFF state or + * throws exception if daemon is found KILL or Cuda error state. + * + * @return bool + */ + static bool CheckStatus() /*throw(mitk::Exception)*/; + + /** + * @brief Creates temp directories and calls start_python_daemon + * function async. + * + */ + void StartAsyncProcess(); + + /** + * @brief Writes KILL to the control file to stop the daemon process. + * + */ + void StopAsyncProcess(); + + /** + * @brief Writes image as nifity file with unique id (UId) as file name. + * + */ + void TransferImageToProcess(const Image*, std::string &UId); + + /** + * @brief Writes csv stringstream of points to a csv file for + * python daemon to read. + * + */ + void TransferPointsToProcess(std::stringstream&); + + /** + * @brief Waits for output nifity file from the daemon to appear and + * reads it as a mitk::Image + * + * @return Image::Pointer + */ + LabelSetImage::Pointer RetrieveImageFromProcess(long timeOut= -1); + + static Status CurrentStatus; + + private: + /** + * @brief Runs SAM python daemon using mitk::ProcessExecutor + * + */ + void start_python_daemon(); + + /** + * @brief Writes stringstream content into control file. + * + */ + void WriteControlFile(std::stringstream&); + + /** + * @brief Create a Temp Dirs + * + */ + void CreateTempDirs(const std::string&); + + /** + * @brief ITK-based file writer for dumping inputs into python daemon + * + */ + template + void ITKWriter(const itk::Image *image, std::string& outputFilename); + + + std::string m_MitkTempDir; + std::string m_PythonPath; + std::string m_ModelType; + std::string m_CheckpointPath; + std::string m_InDir, m_OutDir; + std::string m_CurrentUId; + int m_GpuId = 0; + const std::string PARENT_TEMP_DIR_PATTERN = "mitk-sam-XXXXXX"; + const std::string TRIGGER_FILENAME = "trigger.csv"; + const std::string SAM_PYTHON_FILE_NAME = "run_inference_daemon.py"; + std::future m_Future; + SegmentAnythingProcessExecutor::Pointer m_DaemonExec; + }; + + struct SIGNALCONSTANTS + { + static const std::string READY; + static const std::string KILL; + static const std::string OFF; + static const std::string CUDA_OUT_OF_MEMORY_ERROR; + static const std::string TIMEOUT_ERROR; + }; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.cpp b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.cpp new file mode 100644 index 0000000000..94b63e8320 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.cpp @@ -0,0 +1,411 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkSegmentAnythingTool.h" + +#include +#include +#include +#include "mitkInteractionPositionEvent.h" +#include "mitkPointSetShapeProperty.h" +#include "mitkProperties.h" +#include "mitkToolManager.h" +#include +// us +#include +#include +#include +#include + +#include +#include "mitkImageAccessByItk.h" + +using namespace std::chrono_literals; + +namespace mitk +{ + MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, SegmentAnythingTool, "SegmentAnythingTool"); +} + +mitk::SegmentAnythingTool::SegmentAnythingTool() : SegWithPreviewTool(true, "PressMoveReleaseAndPointSetting") +{ + this->ResetsToEmptyPreviewOn(); + this->IsTimePointChangeAwareOff(); + this->KeepActiveAfterAcceptOn(); +} + +const char **mitk::SegmentAnythingTool::GetXPM() const +{ + return nullptr; +} + +const char *mitk::SegmentAnythingTool::GetName() const +{ + return "Segment Anything"; +} + +us::ModuleResource mitk::SegmentAnythingTool::GetIconResource() const +{ + us::Module *module = us::GetModuleContext()->GetModule(); + us::ModuleResource resource = module->GetResource("AI.svg"); + return resource; +} + +void mitk::SegmentAnythingTool::Activated() +{ + Superclass::Activated(); + m_PointSetPositive = mitk::PointSet::New(); + m_PointSetNodePositive = mitk::DataNode::New(); + m_PointSetNodePositive->SetData(m_PointSetPositive); + m_PointSetNodePositive->SetName(std::string(this->GetName()) + "_PointSetPositive"); + m_PointSetNodePositive->SetBoolProperty("helper object", true); + m_PointSetNodePositive->SetColor(0.0, 1.0, 0.0); + m_PointSetNodePositive->SetVisibility(true); + m_PointSetNodePositive->SetProperty("Pointset.2D.shape", + mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); + m_PointSetNodePositive->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); + this->GetDataStorage()->Add(m_PointSetNodePositive, this->GetToolManager()->GetWorkingData(0)); + + m_PointSetNegative = mitk::PointSet::New(); + m_PointSetNodeNegative = mitk::DataNode::New(); + m_PointSetNodeNegative->SetData(m_PointSetNegative); + m_PointSetNodeNegative->SetName(std::string(this->GetName()) + "_PointSetNegative"); + m_PointSetNodeNegative->SetBoolProperty("helper object", true); + m_PointSetNodeNegative->SetColor(1.0, 0.0, 0.0); + m_PointSetNodeNegative->SetVisibility(true); + m_PointSetNodeNegative->SetProperty("Pointset.2D.shape", + mitk::PointSetShapeProperty::New(mitk::PointSetShapeProperty::CIRCLE)); + m_PointSetNodeNegative->SetProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(true)); + this->GetDataStorage()->Add(m_PointSetNodeNegative, this->GetToolManager()->GetWorkingData(0)); + + this->SetLabelTransferScope(LabelTransferScope::ActiveLabel); + this->SetLabelTransferMode(LabelTransferMode::MapLabel); +} + +void mitk::SegmentAnythingTool::Deactivated() +{ + this->ClearSeeds(); + GetDataStorage()->Remove(m_PointSetNodePositive); + GetDataStorage()->Remove(m_PointSetNodeNegative); + m_PointSetNodePositive = nullptr; + m_PointSetNodeNegative = nullptr; + m_PointSetPositive = nullptr; + m_PointSetNegative = nullptr; + m_PythonService.reset(); + Superclass::Deactivated(); +} + +void mitk::SegmentAnythingTool::ConnectActionsAndFunctions() +{ + CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddNegativePoint); + CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPositivePoint); + CONNECT_FUNCTION("DeletePoint", OnDelete); +} + +void mitk::SegmentAnythingTool::InitSAMPythonProcess() +{ + if (nullptr != m_PythonService) + { + m_PythonService.reset(); + } + this->ClearPicks(); + m_PythonService = std::make_unique( + this->GetPythonPath(), this->GetModelType(), this->GetCheckpointPath(), this->GetGpuId()); + m_PythonService->StartAsyncProcess(); +} + +bool mitk::SegmentAnythingTool::IsPythonReady() const +{ + return m_PythonService->CheckStatus(); +} + +void mitk::SegmentAnythingTool::OnAddNegativePoint(StateMachineAction *, InteractionEvent *interactionEvent) +{ + if (!this->GetIsReady() || m_PointSetPositive->GetSize() == 0 || nullptr == this->GetWorkingPlaneGeometry() || + !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), *(this->GetWorkingPlaneGeometry()))) + { + return; + } + if (!this->IsUpdating() && m_PointSetNegative.IsNotNull()) + { + const auto positionEvent = dynamic_cast(interactionEvent); + if (positionEvent != nullptr) + { + m_PointSetNegative->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); + m_PointSetCount++; + this->UpdatePreview(); + } + } +} + +void mitk::SegmentAnythingTool::OnAddPositivePoint(StateMachineAction *, InteractionEvent *interactionEvent) +{ + if (!this->GetIsReady()) + { + return; + } + m_IsGenerateEmbeddings = false; + if ((nullptr == this->GetWorkingPlaneGeometry()) || + !mitk::Equal(*(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()), + *(this->GetWorkingPlaneGeometry()))) + { + m_IsGenerateEmbeddings = true; + this->ClearSeeds(); + this->SetWorkingPlaneGeometry(interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone()); + } + if (!this->IsUpdating() && m_PointSetPositive.IsNotNull()) + { + const auto positionEvent = dynamic_cast(interactionEvent); + if (positionEvent != nullptr) + { + m_PointSetPositive->InsertPoint(m_PointSetCount, positionEvent->GetPositionInWorld()); + ++m_PointSetCount; + this->UpdatePreview(); + } + } +} + +void mitk::SegmentAnythingTool::OnDelete(StateMachineAction *, InteractionEvent *) +{ + if (!this->IsUpdating() && m_PointSetPositive.IsNotNull() && m_PointSetNegative.IsNotNull()) + { + PointSet::Pointer removeSet = m_PointSetPositive; + decltype(m_PointSetPositive->GetMaxId().Index()) maxId = 0; + if (m_PointSetPositive->GetSize() > 0) + { + maxId = m_PointSetPositive->GetMaxId().Index(); + } + if (m_PointSetNegative->GetSize() > 0 && (maxId < m_PointSetNegative->GetMaxId().Index())) + { + removeSet = m_PointSetNegative; + } + removeSet->RemovePointAtEnd(0); + --m_PointSetCount; + this->UpdatePreview(); + } +} + +void mitk::SegmentAnythingTool::ClearPicks() +{ + this->ClearSeeds(); + this->UpdatePreview(); +} + +bool mitk::SegmentAnythingTool::HasPicks() const +{ + return this->m_PointSetPositive.IsNotNull() && this->m_PointSetPositive->GetSize() > 0; +} + +void mitk::SegmentAnythingTool::ClearSeeds() +{ + if (this->m_PointSetPositive.IsNotNull()) + { + m_PointSetCount -= m_PointSetPositive->GetSize(); + this->m_PointSetPositive = mitk::PointSet::New(); // renew pointset + this->m_PointSetNodePositive->SetData(this->m_PointSetPositive); + } + if (this->m_PointSetNegative.IsNotNull()) + { + m_PointSetCount -= m_PointSetNegative->GetSize(); + this->m_PointSetNegative = mitk::PointSet::New(); // renew pointset + this->m_PointSetNodeNegative->SetData(this->m_PointSetNegative); + } +} + +void mitk::SegmentAnythingTool::ConfirmCleanUp() +{ + auto previewImage = this->GetPreviewSegmentation(); + for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) + { + this->ResetPreviewContentAtTimeStep(timeStep); + } + this->ClearSeeds(); + RenderingManager::GetInstance()->RequestUpdateAll(); +} + +template +void mitk::SegmentAnythingTool::ITKWindowing(const itk::Image *inputImage, + Image *mitkImage, + ScalarType min, + ScalarType max) +{ + typedef itk::Image ImageType; + typedef itk::IntensityWindowingImageFilter IntensityFilterType; + typename IntensityFilterType::Pointer filter = IntensityFilterType::New(); + filter->SetInput(inputImage); + filter->SetWindowMinimum(min); + filter->SetWindowMaximum(max); + filter->SetOutputMinimum(min); + filter->SetOutputMaximum(max); + filter->Update(); + + mitkImage->SetImportVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), 0, 0, Image::ManageMemory); + filter->GetOutput()->GetPixelContainer()->ContainerManageMemoryOff(); +} + +void mitk::SegmentAnythingTool::DoUpdatePreview(const Image *inputAtTimeStep, + const Image * /*oldSegAtTimeStep*/, + LabelSetImage *previewImage, + TimeStepType timeStep) +{ + if (nullptr != previewImage && m_PointSetPositive.IsNotNull()) + { + if (this->HasPicks() && nullptr != m_PythonService) + { + mitk::LevelWindow levelWindow; + this->GetToolManager()->GetReferenceData(0)->GetLevelWindow(levelWindow); + std::string uniquePlaneID = GetHashForCurrentPlane(levelWindow); + m_ProgressCommand->SetProgress(20); + try + { + std::stringstream csvStream; + this->EmitSAMStatusMessageEvent("Prompting Segment Anything Model..."); + m_ProgressCommand->SetProgress(50); + if (inputAtTimeStep->GetPixelType().GetNumberOfComponents() < 2) + { + auto filteredImage = mitk::Image::New(); + filteredImage->Initialize(inputAtTimeStep); + AccessByItk_n(inputAtTimeStep, ITKWindowing, // apply level window filter + (filteredImage, levelWindow.GetLowerWindowBound(), levelWindow.GetUpperWindowBound())); + m_PythonService->TransferImageToProcess(filteredImage, uniquePlaneID); + csvStream = this->GetPointsAsCSVString(filteredImage->GetGeometry()); + } + else + { + m_PythonService->TransferImageToProcess(inputAtTimeStep, uniquePlaneID); + csvStream = this->GetPointsAsCSVString(inputAtTimeStep->GetGeometry()); + } + m_ProgressCommand->SetProgress(100); + m_PythonService->TransferPointsToProcess(csvStream); + m_ProgressCommand->SetProgress(150); + std::this_thread::sleep_for(100ms); + mitk::LabelSetImage::Pointer outputBuffer = m_PythonService->RetrieveImageFromProcess(this->GetTimeOutLimit()); + m_ProgressCommand->SetProgress(180); + mitk::SegTool2D::WriteSliceToVolume(previewImage, this->GetWorkingPlaneGeometry(), outputBuffer.GetPointer(), timeStep, false); + this->SetSelectedLabels({MASK_VALUE}); + this->EmitSAMStatusMessageEvent("Successfully generated segmentation."); + } + catch (const mitk::Exception &e) + { + this->ClearPicks(); + this->EmitSAMStatusMessageEvent(e.GetDescription()); + mitkThrow() << e.GetDescription(); + } + } + else if (nullptr != this->GetWorkingPlaneGeometry()) + { + this->ResetPreviewContentAtTimeStep(timeStep); + RenderingManager::GetInstance()->ForceImmediateUpdateAll(); + } + } +} + +std::string mitk::SegmentAnythingTool::GetHashForCurrentPlane(const mitk::LevelWindow &levelWindow) +{ + mitk::Vector3D normal = this->GetWorkingPlaneGeometry()->GetNormal(); + mitk::Point3D center = this->GetWorkingPlaneGeometry()->GetCenter(); + std::stringstream hashstream; + hashstream << std::setprecision(3) << std::fixed << normal[0]; //Consider only 3 digits after decimal + hashstream << std::setprecision(3) << std::fixed << normal[1]; + hashstream << std::setprecision(3) << std::fixed << normal[2]; + hashstream << std::setprecision(3) << std::fixed << center[0]; + hashstream << std::setprecision(3) << std::fixed << center[1]; + hashstream << std::setprecision(3) << std::fixed << center[2]; + hashstream << levelWindow.GetLowerWindowBound(); + hashstream << levelWindow.GetUpperWindowBound(); + size_t hashVal = std::hash{}(hashstream.str()); + return std::to_string(hashVal); +} + +std::stringstream mitk::SegmentAnythingTool::GetPointsAsCSVString(const mitk::BaseGeometry *baseGeometry) +{ + MITK_INFO << "No.of points: " << m_PointSetPositive->GetSize(); + std::stringstream pointsAndLabels; + pointsAndLabels << "Point,Label\n"; + mitk::PointSet::PointsConstIterator pointSetItPos = m_PointSetPositive->Begin(); + mitk::PointSet::PointsConstIterator pointSetItNeg = m_PointSetNegative->Begin(); + const char SPACE = ' '; + while (pointSetItPos != m_PointSetPositive->End() || pointSetItNeg != m_PointSetNegative->End()) + { + if (pointSetItPos != m_PointSetPositive->End()) + { + mitk::Point3D point = pointSetItPos.Value(); + if (baseGeometry->IsInside(point)) + { + Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); + pointsAndLabels << static_cast(p2D[0]) << SPACE << static_cast(p2D[1]) << ",1" << std::endl; + } + ++pointSetItPos; + } + if (pointSetItNeg != m_PointSetNegative->End()) + { + mitk::Point3D point = pointSetItNeg.Value(); + if (baseGeometry->IsInside(point)) + { + Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); + pointsAndLabels << static_cast(p2D[0]) << SPACE << static_cast(p2D[1]) << ",0" << std::endl; + } + ++pointSetItNeg; + } + } + return pointsAndLabels; +} + +std::vector> mitk::SegmentAnythingTool::GetPointsAsVector( + const mitk::BaseGeometry *baseGeometry) +{ + std::vector> clickVec; + clickVec.reserve(m_PointSetPositive->GetSize() + m_PointSetNegative->GetSize()); + mitk::PointSet::PointsConstIterator pointSetItPos = m_PointSetPositive->Begin(); + mitk::PointSet::PointsConstIterator pointSetItNeg = m_PointSetNegative->Begin(); + while (pointSetItPos != m_PointSetPositive->End() || pointSetItNeg != m_PointSetNegative->End()) + { + if (pointSetItPos != m_PointSetPositive->End()) + { + mitk::Point3D point = pointSetItPos.Value(); + if (baseGeometry->IsInside(point)) + { + Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); + clickVec.push_back(std::pair(p2D, "1")); + } + ++pointSetItPos; + } + if (pointSetItNeg != m_PointSetNegative->End()) + { + mitk::Point3D point = pointSetItNeg.Value(); + if (baseGeometry->IsInside(point)) + { + Point2D p2D = Get2DIndicesfrom3DWorld(baseGeometry, point); + clickVec.push_back(std::pair(p2D, "0")); + } + ++pointSetItNeg; + } + } + return clickVec; +} + +mitk::Point2D mitk::SegmentAnythingTool::Get2DIndicesfrom3DWorld(const mitk::BaseGeometry *baseGeometry, + const mitk::Point3D &point3d) +{ + mitk::Point3D index3D; + baseGeometry->WorldToIndex(point3d, index3D); + MITK_INFO << index3D[0] << " " << index3D[1] << " " << index3D[2]; // remove + Point2D point2D; + point2D.SetElement(0, index3D[0]); + point2D.SetElement(1, index3D[1]); + return point2D; +} + +void mitk::SegmentAnythingTool::EmitSAMStatusMessageEvent(const std::string& status) +{ + SAMStatusMessageEvent.Send(status); +} diff --git a/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.h b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.h new file mode 100644 index 0000000000..e096072cc2 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkSegmentAnythingTool.h @@ -0,0 +1,226 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkSegmentAnythingTool_h +#define mitkSegmentAnythingTool_h + +#include "mitkSegWithPreviewTool.h" +#include "mitkPointSet.h" +#include "mitkProcessExecutor.h" +#include "mitkSegmentAnythingPythonService.h" +#include +#include +#include + +namespace us +{ + class ModuleResource; +} + +namespace mitk +{ + /** + \brief Segment Anything Model interactive 2D tool class. + + \ingroup ToolManagerEtAl + \sa mitk::Tool + \sa QmitkInteractiveSegmentation + + */ + class MITKSEGMENTATION_EXPORT SegmentAnythingTool : public SegWithPreviewTool + { + public: + mitkClassMacro(SegmentAnythingTool, SegWithPreviewTool); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + const char **GetXPM() const override; + const char *GetName() const override; + us::ModuleResource GetIconResource() const override; + + void Activated() override; + void Deactivated() override; + + /** + * @brief Clears all picks and updates the preview. + */ + void ClearPicks(); + + /** + * @brief Checks if any point exists in the either + * of the pointsets + * + * @return bool + */ + bool HasPicks() const; + + itkSetMacro(MitkTempDir, std::string); + itkGetConstMacro(MitkTempDir, std::string); + + itkSetMacro(PythonPath, std::string); + itkGetConstMacro(PythonPath, std::string); + + itkSetMacro(ModelType, std::string); + itkGetConstMacro(ModelType, std::string); + + itkSetMacro(CheckpointPath, std::string); + itkGetConstMacro(CheckpointPath, std::string); + + itkSetMacro(GpuId, int); + itkGetConstMacro(GpuId, int); + + itkSetMacro(TimeOutLimit, long); + itkGetConstMacro(TimeOutLimit, long); + + itkSetMacro(IsReady, bool); + itkGetConstMacro(IsReady, bool); + itkBooleanMacro(IsReady); + + /** + * @brief Initializes python service and + * starts async python daemon of SegmentAnything model. + * + */ + void InitSAMPythonProcess(); + + /** + * @brief Checks if Python daemon is ready to accept inputs. + * + * @return bool + */ + bool IsPythonReady() const; + + Message1 SAMStatusMessageEvent; + + protected: + SegmentAnythingTool(); + ~SegmentAnythingTool() = default; + + void ConnectActionsAndFunctions() override; + + /* + * @brief Add positive seed point action of StateMachine pattern + */ + virtual void OnAddPositivePoint(StateMachineAction*, InteractionEvent *interactionEvent); + + /* + * @brief Add negative seed point action of StateMachine pattern + */ + virtual void OnAddNegativePoint(StateMachineAction*, InteractionEvent *interactionEvent); + + /* + * @brief Delete action of StateMachine pattern. The function deletes positive or negative points in + the reverse order of creation. This is done by finding & deleting the Point having the highest + PointIdentifier value from either of the PointSets m_PointSetPositive & m_PointSetNegative. + */ + virtual void OnDelete(StateMachineAction*, InteractionEvent*); + + /* + * @brief Clear all seed points and call UpdatePreview to reset the segmentation Preview + */ + void ClearSeeds(); + + /** + * @brief Overriden method from the tool manager to execute the segmentation + * Implementation: + * 1. Creates Hash for input image from current plane geometry. + * 2. Transfers image pointer to python service along with the hash code. + * 3. Creates seed points as CSV string & transfers to python service + * 3. Retrieves resulting segmentation Image pointer from python service and sets to previewImage. + * + * @param inputAtTimeStep + * @param oldSegAtTimeStep + * @param previewImage + * @param timeStep + */ + void DoUpdatePreview(const Image *inputAtTimeStep, const Image *oldSegAtTimeStep, LabelSetImage *previewImage, TimeStepType timeStep) override; + + /** + * @brief Get the Points from positive and negative pointsets as std::vector. + * + * @param baseGeometry of Image + * @return std::vector> + */ + std::vector> GetPointsAsVector(const mitk::BaseGeometry*); + + /** + * @brief Get the Points from positive and negative pointsets as csv string. + * + * @param baseGeometry + * @return std::stringstream + */ + std::stringstream GetPointsAsCSVString(const mitk::BaseGeometry *baseGeometry); + + /** + * @brief Get the Hash For Current Plane from current working plane geometry. + * + * @return std::string + */ + std::string GetHashForCurrentPlane(const mitk::LevelWindow&); + + /** + * @brief Emits message to connected Listnerers. + * + */ + void EmitSAMStatusMessageEvent(const std::string&); + + /** + * @brief Cleans up segmentation preview and clears all seeds. + * + */ + void ConfirmCleanUp() override; + + /** + * @brief Applies ITK IntensityWindowing Filter to input image; + * + */ + template + void ITKWindowing(const itk::Image*, mitk::Image*, ScalarType, ScalarType); + + private: + /** + * @brief Convert 3D world coordinates to 2D indices. + * + * @param baseGeometry + * @param mitk::Point3D + * @return mitk::Point2D + */ + static mitk::Point2D Get2DIndicesfrom3DWorld(const mitk::BaseGeometry*, const mitk::Point3D&); + + /** + * @brief Checks if the image has valid size across each dimension. This function is + * critical for 2D images since 2D image are not valid in Saggital and Coronal views. + * + * @param inputAtTimeStep + * @return bool + */ + bool IsImageAtTimeStepValid(const Image *inputAtTimeStep); + + std::string m_MitkTempDir; + std::string m_PythonPath; + std::string m_ModelType; + std::string m_CheckpointPath; + int m_GpuId = 0; + PointSet::Pointer m_PointSetPositive; + PointSet::Pointer m_PointSetNegative; + DataNode::Pointer m_PointSetNodePositive; + DataNode::Pointer m_PointSetNodeNegative; + bool m_IsGenerateEmbeddings = true; + bool m_IsReady = false; + int m_PointSetCount = 0; + long m_TimeOutLimit = -1; + std::unique_ptr m_PythonService; + const Label::PixelType MASK_VALUE = 1; + }; +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp index 4fd38ddb56..c3ac4dffd4 100644 --- a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp @@ -1,374 +1,354 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // MITK #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, TotalSegmentatorTool, "Total Segmentator"); } mitk::TotalSegmentatorTool::~TotalSegmentatorTool() { std::filesystem::remove_all(this->GetMitkTempDir()); } mitk::TotalSegmentatorTool::TotalSegmentatorTool() : SegWithPreviewTool(true) // prevents auto-compute across all timesteps { this->IsTimePointChangeAwareOff(); } void mitk::TotalSegmentatorTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::TotalSegmentatorTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::TotalSegmentatorTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char *mitk::TotalSegmentatorTool::GetName() const { return "TotalSegmentator"; } void mitk::TotalSegmentatorTool::onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } void mitk::TotalSegmentatorTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (this->m_MitkTempDir.empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); + m_ProgressCommand->SetProgress(5); std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; inDir = IOUtil::CreateTemporaryDirectory("totalseg-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); outDir = IOUtil::CreateTemporaryDirectory("totalseg-out-XXXXXX", this->GetMitkTempDir()); LabelSetImage::Pointer outputBuffer; - + m_ProgressCommand->SetProgress(20); IOUtil::Save(inputAtTimeStep, inputImagePath); + m_ProgressCommand->SetProgress(50); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { outputImagePath = outDir; - } - - this->run_totalsegmentator( - spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), DEFAULT_TOTAL_TASK); - - if (isSubTask) - { // Run total segmentator again this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, !isSubTask, !isSubTask, this->GetGpuId(), this->GetSubTask()); // Construct Label Id map std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); // Agglomerate individual mask files into one multi-label image. std::for_each(files.begin(), files.end(), [&](std::string &fileName) { fileName = (outDir + IOUtil::GetDirectorySeparator() + fileName); }); outputBuffer = AgglomerateLabelFiles(files, inputAtTimeStep->GetDimensions(), inputAtTimeStep->GetGeometry()); } else { + this->run_totalsegmentator( + spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), DEFAULT_TOTAL_TASK); Image::Pointer outputImage = IOUtil::Load(outputImagePath); outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } + m_ProgressCommand->SetProgress(180); mitk::ImageReadAccessor newMitkImgAcc(outputBuffer.GetPointer()); this->MapLabelsToSegmentation(outputBuffer, previewImage, m_LabelMapTotal); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::TotalSegmentatorTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); for (LabelSetImage::GroupIndexType i = 0; i < preview->GetNumberOfLayers(); ++i) { preview->GetLabelSet(i)->RemoveAllLabels(); } if (m_LabelMapTotal.empty()) { this->ParseLabelMapTotalDefault(); } const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); m_LabelMapTotal.clear(); mitk::Label::PixelType labelId = 1; for (auto const &file : files) { std::string labelName = file.substr(0, file.find('.')); m_LabelMapTotal[labelId] = labelName; labelId++; } } } mitk::LabelSetImage::Pointer mitk::TotalSegmentatorTool::AgglomerateLabelFiles(std::vector &filePaths, const unsigned int *dimensions, mitk::BaseGeometry *geometry) { Label::PixelType labelId = 1; auto aggloLabelImage = mitk::LabelSetImage::New(); auto initImage = mitk::Image::New(); initImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); aggloLabelImage->Initialize(initImage); aggloLabelImage->SetGeometry(geometry); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); newlayer->SetLayer(0); aggloLabelImage->AddLayer(newlayer); for (auto const &outputImagePath : filePaths) { double rgba[4]; aggloLabelImage->GetActiveLabelSet()->GetLookupTable()->GetTableValue(labelId, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName("object-" + std::to_string(labelId)); label->SetValue(labelId); label->SetColor(color); label->SetOpacity(rgba[3]); aggloLabelImage->GetActiveLabelSet()->AddLabel(label); Image::Pointer outputImage = IOUtil::Load(outputImagePath); auto source = mitk::LabelSetImage::New(); source->InitializeByLabeledImage(outputImage); source->SetGeometry(geometry); auto labelSet = aggloLabelImage->GetActiveLabelSet(); mitk::TransferLabelContent(source, aggloLabelImage, labelSet, 0, 0, false, {{1, labelId}}); labelId++; } return aggloLabelImage; } void mitk::TotalSegmentatorTool::run_totalsegmentator(ProcessExecutor* spExec, const std::string &inputImagePath, const std::string &outputImagePath, bool isFast, bool isMultiLabel, unsigned int gpuId, const std::string &subTask) { ProcessExecutor::ArgumentListType args; std::string command = "TotalSegmentator"; -#if defined(__APPLE__) || defined(_WIN32) - command = "python"; -#endif - - args.clear(); - #ifdef _WIN32 - std::string ending = "Scripts"; - if (0 == this->GetPythonPath().compare(this->GetPythonPath().length() - ending.length(), ending.length(), ending)) - { - args.push_back("TotalSegmentator"); - } - else - { - args.push_back("Scripts/TotalSegmentator"); - } + command += ".exe"; #endif - -#if defined(__APPLE__) - args.push_back("TotalSegmentator"); -#endif - + args.clear(); args.push_back("-i"); args.push_back(inputImagePath); args.push_back("-o"); args.push_back(outputImagePath); if (subTask != DEFAULT_TOTAL_TASK) { args.push_back("-ta"); args.push_back(subTask); } if (isMultiLabel) { args.push_back("--ml"); } if (isFast) { args.push_back("--fast"); } try { std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(gpuId); itksys::SystemTools::PutEnv(cudaEnv.c_str()); std::stringstream logStream; for (const auto &arg : args) logStream << arg << " "; logStream << this->GetPythonPath(); MITK_INFO << logStream.str(); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } void mitk::TotalSegmentatorTool::ParseLabelMapTotalDefault() { if (!this->GetLabelMapPath().empty()) { std::regex sanitizer(R"([^A-Za-z0-9_])"); std::fstream newfile; newfile.open(this->GetLabelMapPath(), ios::in); std::stringstream buffer; if (newfile.is_open()) { int line = 0; std::string temp; while (std::getline(newfile, temp)) { - if (line > 1 && line < 106) + if (line > 111 && line < 229) { buffer << temp; } ++line; } } std::string key, val; while (std::getline(std::getline(buffer, key, ':'), val, ',')) { std::string sanitized = std::regex_replace(val, sanitizer, ""); m_LabelMapTotal[std::stoi(key)] = sanitized; } } } void mitk::TotalSegmentatorTool::MapLabelsToSegmentation(const mitk::LabelSetImage* source, mitk::LabelSetImage* dest, std::map &labelMap) { auto labelset = dest->GetLabelSet(); auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : labelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { Label::Pointer label = Label::New(key, val); std::array lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); labelset->AddLabel(label, false); } } } std::string mitk::TotalSegmentatorTool::GetLabelMapPath() { std::string pythonFileName; std::filesystem::path pathToLabelMap(this->GetPythonPath()); pathToLabelMap = pathToLabelMap.parent_path(); #ifdef _WIN32 pythonFileName = pathToLabelMap.string() + "/Lib/site-packages/totalsegmentator/map_to_binary.py"; #else pathToLabelMap.append("lib"); for (auto const &dir_entry : std::filesystem::directory_iterator{pathToLabelMap}) { if (dir_entry.is_directory()) { auto dirName = dir_entry.path().filename().string(); if (dirName.rfind("python", 0) == 0) { pathToLabelMap.append(dir_entry.path().filename().string()); break; } } } pythonFileName = pathToLabelMap.string() + "/site-packages/totalsegmentator/map_to_binary.py"; #endif return pythonFileName; } diff --git a/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp b/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp index c4a9b811b1..6c45ebb90b 100644 --- a/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp +++ b/Modules/Segmentation/SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp @@ -1,414 +1,414 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMorphologicalOperations.h" #include #include #include #include #include #include #include #include #include #include #include void mitk::MorphologicalOperations::Closing(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Closing..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkClosing, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkClosing, image, factor, structuralElement); } MITK_INFO << "Finished Closing"; } void mitk::MorphologicalOperations::Erode(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Erode..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkErode, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkErode, image, factor, structuralElement); } MITK_INFO << "Finished Erode"; } void mitk::MorphologicalOperations::Dilate(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Dilate..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkDilate, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkDilate, image, factor, structuralElement); } MITK_INFO << "Finished Dilate"; } void mitk::MorphologicalOperations::Opening(mitk::Image::Pointer &image, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElement) { MITK_INFO << "Start Opening..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_3(img3D, itkOpening, img3D, factor, structuralElement); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_3(image, itkOpening, image, factor, structuralElement); } MITK_INFO << "Finished Opening"; } void mitk::MorphologicalOperations::FillHoles(mitk::Image::Pointer &image) { MITK_INFO << "Start FillHole..."; auto timeSteps = static_cast(image->GetTimeSteps()); if (timeSteps > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(image); for (int t = 0; t < timeSteps; ++t) { MITK_INFO << " Processing time step " << t; timeSelector->SetTimeNr(t); timeSelector->Update(); mitk::Image::Pointer img3D = timeSelector->GetOutput(); img3D->DisconnectPipeline(); AccessByItk_1(img3D, itkFillHoles, img3D); mitk::ImageReadAccessor accessor(img3D); image->SetVolume(accessor.GetData(), t); } } else { AccessByItk_1(image, itkFillHoles, image); } MITK_INFO << "Finished FillHole"; } template void mitk::MorphologicalOperations::itkClosing( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryMorphologicalClosingImageFilter BallClosingFilterType; typedef typename itk::BinaryMorphologicalClosingImageFilter CrossClosingFilterType; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallClosingFilterType::Pointer closingFilter = BallClosingFilterType::New(); closingFilter->SetKernel(ball); closingFilter->SetInput(sourceImage); closingFilter->SetForegroundValue(1); closingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(closingFilter->GetOutput(), resultImage); + resultImage->SetVolume(closingFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossClosingFilterType::Pointer closingFilter = CrossClosingFilterType::New(); closingFilter->SetKernel(cross); closingFilter->SetInput(sourceImage); closingFilter->SetForegroundValue(1); closingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(closingFilter->GetOutput(), resultImage); + resultImage->SetVolume(closingFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkErode( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryErodeImageFilter BallErodeFilterType; typedef typename itk::BinaryErodeImageFilter CrossErodeFilterType; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallErodeFilterType::Pointer erodeFilter = BallErodeFilterType::New(); erodeFilter->SetKernel(ball); erodeFilter->SetInput(sourceImage); erodeFilter->SetErodeValue(1); erodeFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(erodeFilter->GetOutput(), resultImage); + resultImage->SetVolume(erodeFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossErodeFilterType::Pointer erodeFilter = CrossErodeFilterType::New(); erodeFilter->SetKernel(cross); erodeFilter->SetInput(sourceImage); erodeFilter->SetErodeValue(1); erodeFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(erodeFilter->GetOutput(), resultImage); + resultImage->SetVolume(erodeFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkDilate( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryDilateImageFilter BallDilateFilterType; typedef typename itk::BinaryDilateImageFilter CrossDilateFilterType; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallDilateFilterType::Pointer dilateFilter = BallDilateFilterType::New(); dilateFilter->SetKernel(ball); dilateFilter->SetInput(sourceImage); dilateFilter->SetDilateValue(1); dilateFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(dilateFilter->GetOutput(), resultImage); + resultImage->SetVolume(dilateFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossDilateFilterType::Pointer dilateFilter = CrossDilateFilterType::New(); dilateFilter->SetKernel(cross); dilateFilter->SetInput(sourceImage); dilateFilter->SetDilateValue(1); dilateFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(dilateFilter->GetOutput(), resultImage); + resultImage->SetVolume(dilateFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkOpening( itk::Image *sourceImage, mitk::Image::Pointer &resultImage, int factor, mitk::MorphologicalOperations::StructuralElementType structuralElementFlags) { typedef itk::Image ImageType; typedef itk::BinaryBallStructuringElement BallType; typedef itk::BinaryCrossStructuringElement CrossType; typedef typename itk::BinaryMorphologicalOpeningImageFilter BallOpeningFiltertype; typedef typename itk::BinaryMorphologicalOpeningImageFilter CrossOpeningFiltertype; if (structuralElementFlags & (Ball_Axial | Ball_Coronal | Ball_Sagittal)) { BallType ball = CreateStructuringElement(structuralElementFlags, factor); typename BallOpeningFiltertype::Pointer openingFilter = BallOpeningFiltertype::New(); openingFilter->SetKernel(ball); openingFilter->SetInput(sourceImage); openingFilter->SetForegroundValue(1); openingFilter->SetBackgroundValue(0); openingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(openingFilter->GetOutput(), resultImage); + resultImage->SetVolume(openingFilter->GetOutput()->GetBufferPointer()); } else { CrossType cross = CreateStructuringElement(structuralElementFlags, factor); typename CrossOpeningFiltertype::Pointer openingFilter = CrossOpeningFiltertype::New(); openingFilter->SetKernel(cross); openingFilter->SetInput(sourceImage); openingFilter->SetForegroundValue(1); openingFilter->SetBackgroundValue(0); openingFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(openingFilter->GetOutput(), resultImage); + resultImage->SetVolume(openingFilter->GetOutput()->GetBufferPointer()); } } template void mitk::MorphologicalOperations::itkFillHoles(itk::Image *sourceImage, mitk::Image::Pointer &resultImage) { typedef itk::Image ImageType; typedef typename itk::BinaryFillholeImageFilter FillHoleFilterType; typename FillHoleFilterType::Pointer fillHoleFilter = FillHoleFilterType::New(); fillHoleFilter->SetInput(sourceImage); fillHoleFilter->SetForegroundValue(1); fillHoleFilter->UpdateLargestPossibleRegion(); - mitk::CastToMitkImage(fillHoleFilter->GetOutput(), resultImage); + resultImage->SetVolume(fillHoleFilter->GetOutput()->GetBufferPointer()); } template TStructuringElement mitk::MorphologicalOperations::CreateStructuringElement(StructuralElementType structuralElementFlag, int factor) { TStructuringElement strElem; typename TStructuringElement::SizeType size; size.Fill(0); switch (structuralElementFlag) { case Ball_Axial: case Cross_Axial: size.SetElement(0, factor); size.SetElement(1, factor); break; case Ball_Coronal: case Cross_Coronal: size.SetElement(0, factor); size.SetElement(2, factor); break; case Ball_Sagittal: case Cross_Sagittal: size.SetElement(1, factor); size.SetElement(2, factor); break; case Ball: case Cross: size.Fill(factor); break; } strElem.SetRadius(size); strElem.CreateStructuringElement(); return strElem; } diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 11155f5509..5a57291852 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,117 +1,120 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkGrowCutSegmentationFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationHelper.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkSegWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCloseRegionTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkEditableContourTool.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkLassoTool.cpp Interactions/mitkContourTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionBaseTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkGrowCutTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkPickingTool.cpp Interactions/mitknnUnetTool.cpp Interactions/mitkProcessExecutor.cpp + Interactions/mitkSegmentAnythingProcessExecutor.cpp Interactions/mitkTotalSegmentatorTool.cpp + Interactions/mitkSegmentAnythingTool.cpp + Interactions/mitkSegmentAnythingPythonService.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add.svg Add_Cursor.svg AI.svg AI_Cursor.svg Close.svg Close_Cursor.svg Erase.svg Erase_Cursor.svg Fill.svg Fill_Cursor.svg LiveWire.svg LiveWire_Cursor.svg Lasso.svg GrowCut.svg Lasso_Cursor.svg Otsu.svg Paint.svg Paint_Cursor.svg Picking.svg RegionGrowing.svg RegionGrowing_Cursor.svg Subtract.svg Subtract_Cursor.svg Threshold.svg ULThreshold.svg Wipe.svg Wipe_Cursor.svg Interactions/dummy.xml Interactions/EditableContourTool.xml Interactions/PickingTool.xml Interactions/MouseReleaseOnly.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationConfig.xml Interactions/SegmentationInteraction.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 6311220ef5..2b2002ec07 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1131 +1,1139 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include // mitk #include #include #include // Qmitk #include #include #include #include // Qt #include #include #include #include #include QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); auto* view = this->m_Controls->view; view->setItemDelegateForColumn(1, m_LockItemDelegate); view->setItemDelegateForColumn(2, m_ColorItemDelegate); view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); auto* header = view->header(); header->setSectionResizeMode(0,QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); header->setSectionResizeMode(3, QHeaderView::ResizeToContents); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; //in singel selection mode, if at least one label exist select the first label of the mode. if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { m_Controls->view->setSelectionMode(multiMode ? QAbstractItemView::SelectionMode::MultiSelection : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { m_AllowVisibilityModification = visibilityMod; this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { m_AllowLockModification = lockMod; this->m_Model->SetAllowLockModification(lockMod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); } } bool QmitkMultiLabelInspector::GetModelManipulationOngoing() const { return m_ModelManipulationOngoing; } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin()); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indices of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : qAsConst(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { auto currentIndex = m_Controls->view->currentIndex(); return m_Model->GetLabelsInSubTree(currentIndex); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetLabelInstancesOfSelectedFirstLabel() const { if (m_Segmentation.IsNull()) return {}; if (this->GetSelectedLabels().empty()) return {}; const auto index = m_Model->indexOfLabel(this->GetSelectedLabels().front()); return m_Model->GetLabelInstancesOfSameLabelClass(index); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); m_ModelManipulationOngoing = true; auto newLabel = group->AddLabel(templateLabel, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; return this->AddNewLabelInstanceInternal(currentLabel); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) emit LabelRenameRequested(newLabel, false); auto group = m_Segmentation->GetLabelSet(containingGroup); m_ModelManipulationOngoing = true; group->AddLabel(newLabel, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (!index.isValid()) mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the " "model after adding it to the segmentation. Label value: " << newLabel->GetValue(); m_Controls->view->expand(index.parent()); emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); mitk::LabelSetImage::GroupIndexType groupID = nullptr != currentLabel ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) : 0; return AddNewLabelInternal(groupID); } void QmitkMultiLabelInspector::DeleteLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInstance."; if (m_Segmentation.IsNull()) return; auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; auto index = m_Model->indexOfLabel(label->GetValue()); auto instanceName = index.data(Qt::DisplayRole); auto question = "Do you really want to delete label instance \"" + instanceName.toString() + "\"?"; auto answer = QMessageBox::question(this, QString("Delete label instances"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal({ label->GetValue() }); } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabel."; if (m_Segmentation.IsNull()) return; const auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; const auto relevantLabels = this->GetLabelInstancesOfSelectedFirstLabel(); if (relevantLabels.empty()) return; auto question = "Do you really want to delete label \"" + QString::fromStdString(label->GetName()); question = relevantLabels.size()==1 ? question + "\"?" : question + "\" with all "+QString::number(relevantLabels.size()) +" instances?"; auto answer = QMessageBox::question(this, QString("Delete label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal(relevantLabels); } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } mitk::LabelSetImage::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); this->WaitCursorOff(); newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; emit ModelUpdated(); return newLabel; } void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) return; auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } + else + { + this->SetSelectedLabels({}); + } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) { QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); return; } - auto question = QStringLiteral("Do you really want to delete the group of the selected label with all labels?"); - auto answer = QMessageBox::question(this, "Delete group", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + const auto* selectedLabel = this->GetFirstSelectedLabelObject(); - if (answer != QMessageBox::Yes) + if (selectedLabel == nullptr) return; - auto selectedLabel = this->GetFirstSelectedLabelObject(); const auto group = m_Segmentation->GetGroupIndexOfLabel(selectedLabel->GetValue()); + auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(group); + auto answer = QMessageBox::question(this, QStringLiteral("Delete group %1").arg(group), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (answer != QMessageBox::Yes) + return; + this->RemoveGroupInternal(group); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); - auto question = QStringLiteral("Do you really want to delete the current group with all labels?"); - auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(groupID); + auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); if (IndexLevelType::Group == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg")), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); if (m_Segmentation->GetNumberOfLayers() > 1) { QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg")), "Delete group", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide group", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else if (IndexLevelType::LabelClass == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "&Delete label", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr!=opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; QMenu* menu = new QMenu(this); if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete selected labels", this); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnDeleteLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "&Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); const auto selectedLabelIndex = m_Model->indexOfLabel(selectedLabelValues.front()); if (m_Model->GetLabelInstancesOfSameLabelClass(selectedLabelIndex).size() > 1) // Only labels that actually appear as instance (having additional instances) { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label instance", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete label instance", this); QObject::connect(removeInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabelInstance); menu->addAction(removeInstanceAction); } else { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); } QAction* removeLabelAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "Delete &label", this); QObject::connect(removeLabelAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeLabelAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything in group but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } menu->popup(QCursor::pos()); } } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); std::vector relevantLabels; if (!relevantLabelValues.empty()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels, group](const int value) { auto opacity = static_cast(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ); QLabel* opacityLabel = new QLabel("Opacity: "); QVBoxLayout* opacityWidgetLayout = new QVBoxLayout; opacityWidgetLayout->setContentsMargins(4, 4, 4, 4); opacityWidgetLayout->addWidget(opacityLabel); opacityWidgetLayout->addWidget(opacitySlider); QWidget* opacityWidget = new QWidget; opacityWidget->setLayout(opacityWidgetLayout); QWidgetAction* opacityAction = new QWidgetAction(this); opacityAction->setDefaultWidget(opacityWidget); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnDeleteAffectedLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto affectedLabels = GetCurrentlyAffactedLabelInstances(); auto currentLabel = m_Segmentation->GetLabel(affectedLabels.front()); QString question = "Do you really want to delete all instances of label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); } } void QmitkMultiLabelInspector::OnDeleteLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels(), m_Segmentation->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); this->AddNewLabelInternal(groupID); } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); group->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } } emit ModelUpdated(); } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); currentLabel->SetVisible(true); group->UpdateLookupTable(labelID); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index 315e7952fb..bbf9702b83 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,999 +1,1021 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMultiLabelTreeModel.h" #include "mitkRenderingManager.h" #include "QmitkStyleManager.h" class QmitkMultiLabelSegTreeItem { public: enum class ItemType { Group, Label, Instance }; QmitkMultiLabelSegTreeItem() { }; explicit QmitkMultiLabelSegTreeItem(ItemType type, QmitkMultiLabelSegTreeItem* parentItem, mitk::Label* label = nullptr, std::string className = ""): m_parentItem(parentItem), m_ItemType(type), m_Label(label), m_ClassName(className) { }; ~QmitkMultiLabelSegTreeItem() { for (auto item : m_childItems) { delete item; } }; void AppendChild(QmitkMultiLabelSegTreeItem* child) { m_childItems.push_back(child); }; void RemoveChild(std::size_t row) { if (row < m_childItems.size()) { delete m_childItems[row]; m_childItems.erase(m_childItems.begin() + row); } }; int Row() const { if (m_parentItem) { auto finding = std::find(m_parentItem->m_childItems.begin(), m_parentItem->m_childItems.end(), this); if (finding != m_parentItem->m_childItems.end()) { return std::distance(m_parentItem->m_childItems.begin(), finding); } } return 0; }; QmitkMultiLabelSegTreeItem* ParentItem() { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* ParentItem() const { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* NextSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row + 1 < m_parentItem->m_childItems.size()) return m_parentItem->m_childItems[row+1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* PrevSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row > 0) return m_parentItem->m_childItems[row-1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* RootItem() const { auto item = this; while (item->m_parentItem != nullptr) { item = item->m_parentItem; } return item; }; std::size_t GetGroupID() const { auto root = this->RootItem(); auto item = this; if (root == this) return 0; while (root != item->m_parentItem) { item = item->m_parentItem; } auto iter = std::find(root->m_childItems.begin(), root->m_childItems.end(), item); if (root->m_childItems.end() == iter) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Root does not have an currentItem as child that has root as parent."; return std::distance(root->m_childItems.begin(), iter); } bool HandleAsInstance() const { return (ItemType::Instance == m_ItemType) || ((ItemType::Label == m_ItemType) && (m_childItems.size() == 1)); } mitk::Label* GetLabel() const { if (ItemType::Instance == m_ItemType) { return m_Label; } if (ItemType::Label == m_ItemType) { if (m_childItems.empty()) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Internal label currentItem has no instance currentItem."; return m_childItems[0]->GetLabel(); } return nullptr; }; mitk::LabelSetImage::LabelValueType GetLabelValue() const { auto label = this->GetLabel(); if (nullptr == label) { mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Called GetLabelValue on an group currentItem."; } return label->GetValue(); }; /** returns a vector containing all label values of referenced by this item or its child items.*/ std::vector< mitk::LabelSetImage::LabelValueType> GetLabelsInSubTree() const { if (this->m_ItemType == ItemType::Instance) { return { this->GetLabelValue() }; } std::vector< mitk::LabelSetImage::LabelValueType> result; for (const auto child : this->m_childItems) { auto childresult = child->GetLabelsInSubTree(); result.reserve(result.size() + childresult.size()); result.insert(result.end(), childresult.begin(), childresult.end()); } return result; } std::vector m_childItems; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; ItemType m_ItemType = ItemType::Group; mitk::Label::Pointer m_Label; std::string m_ClassName; }; QModelIndex GetIndexByItem(const QmitkMultiLabelSegTreeItem* start, const QmitkMultiLabelTreeModel* model) { QModelIndex parentIndex = QModelIndex(); if (nullptr != start->m_parentItem) { parentIndex = GetIndexByItem(start->m_parentItem, model); } else { return parentIndex; } return model->index(start->Row(), 0, parentIndex); } QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::GroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) { if (nullptr != root && groupIndex < root->m_childItems.size()) { return root->m_childItems[groupIndex]; } return nullptr; } QmitkMultiLabelSegTreeItem* GetInstanceItem(QmitkMultiLabelTreeModel::LabelValueType labelValue, QmitkMultiLabelSegTreeItem* root) { QmitkMultiLabelSegTreeItem* result = nullptr; for (auto item : root->m_childItems) { result = GetInstanceItem(labelValue, item); if (nullptr != result) return result; } if (root->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Instance && root->GetLabelValue() == labelValue) { return root; } return nullptr; } const QmitkMultiLabelSegTreeItem* GetFirstInstanceLikeItem(const QmitkMultiLabelSegTreeItem* startItem) { const QmitkMultiLabelSegTreeItem* result = nullptr; if (nullptr != startItem) { if (startItem->HandleAsInstance()) { result = startItem; } else if (!startItem->m_childItems.empty()) { result = GetFirstInstanceLikeItem(startItem->m_childItems.front()); } } return result; } QmitkMultiLabelSegTreeItem* GetLabelItemInGroup(const std::string& labelName, QmitkMultiLabelSegTreeItem* group) { if (nullptr != group) { auto predicate = [labelName](const QmitkMultiLabelSegTreeItem* item) { return labelName == item->m_ClassName; }; auto finding = std::find_if(group->m_childItems.begin(), group->m_childItems.end(), predicate); if (group->m_childItems.end() != finding) { return *finding; } } return nullptr; } QmitkMultiLabelTreeModel::QmitkMultiLabelTreeModel(QObject *parent) : QAbstractItemModel(parent) , m_Observed(false) { m_RootItem = std::make_unique(); } QmitkMultiLabelTreeModel ::~QmitkMultiLabelTreeModel() { this->SetSegmentation(nullptr); }; int QmitkMultiLabelTreeModel::columnCount(const QModelIndex& /*parent*/) const { return 4; } int QmitkMultiLabelTreeModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; if (m_Segmentation.IsNull()) return 0; QmitkMultiLabelSegTreeItem* parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); if (parentItem->HandleAsInstance()) { return 0; } return parentItem->m_childItems.size(); } QVariant QmitkMultiLabelTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole||role == Qt::EditRole) { if (TableColumns::NAME_COL == index.column()) { switch (item->m_ItemType) { case QmitkMultiLabelSegTreeItem::ItemType::Group: return QVariant(QString("Group %1").arg(item->GetGroupID())); case QmitkMultiLabelSegTreeItem::ItemType::Label: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; QString name = QString::fromStdString(label->GetName()); if (!item->HandleAsInstance()) name = name + QString(" (%1 instances)").arg(item->m_childItems.size()); return QVariant(name); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName()) + QString(" [%1]").arg(item->GetLabelValue())); } } } else { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { return QVariant(label->GetLocked()); } else if (TableColumns::COLOR_COL == index.column()) { return QVariant(QColor(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255)); } else if (TableColumns::VISIBLE_COL == index.column()) { return QVariant(label->GetVisible()); } } } } else if (role == ItemModelRole::LabelDataRole) { auto label = item->GetLabel(); if (nullptr!=label) return QVariant::fromValue(label); } else if (role == ItemModelRole::LabelValueRole) { auto label = item->GetLabel(); if (nullptr != label) return QVariant(label->GetValue()); } else if (role == ItemModelRole::LabelInstanceDataRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant::fromValue(label); } } else if (role == ItemModelRole::LabelInstanceValueRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } else if (role == ItemModelRole::GroupIDRole) { QVariant v; v.setValue(item->GetGroupID()); return v; } return QVariant(); } mitk::Color QtToMitk(const QColor& color) { mitk::Color mitkColor; mitkColor.SetRed(color.red() / 255.0f); mitkColor.SetGreen(color.green() / 255.0f); mitkColor.SetBlue(color.blue() / 255.0f); return mitkColor; } bool QmitkMultiLabelTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; auto item = static_cast(index.internalPointer()); if (!item) return false; if (role == Qt::EditRole) { if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { label->SetLocked(value.toBool()); } else if (TableColumns::COLOR_COL == index.column()) { label->SetColor(QtToMitk(value.value())); } else if (TableColumns::VISIBLE_COL == index.column()) { label->SetVisible(value.toBool()); } auto groupID = m_Segmentation->GetGroupIndexOfLabel(label->GetValue()); m_Segmentation->GetLabelSet(groupID)->UpdateLookupTable(label->GetValue()); m_Segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { } return true; } } return false; } QModelIndex QmitkMultiLabelTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); auto parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); QmitkMultiLabelSegTreeItem *childItem = parentItem->m_childItems[row]; if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkMultiLabelTreeModel::indexOfLabel(mitk::Label::PixelType labelValue) const { if (labelValue == mitk::LabelSetImage::UnlabeledValue) return QModelIndex(); auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == relevantItem) return QModelIndex(); auto labelItem = relevantItem->ParentItem(); if (labelItem->m_childItems.size() == 1) { //was the only instance of the label, therefor return the label item instat. relevantItem = labelItem; } return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::indexOfGroup(mitk::LabelSetImage::GroupIndexType groupIndex) const { auto relevantItem = GetGroupItem(groupIndex, this->m_RootItem.get()); if (nullptr == relevantItem) QModelIndex(); return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkMultiLabelSegTreeItem *childItem = static_cast(child.internalPointer()); QmitkMultiLabelSegTreeItem *parentItem = childItem->ParentItem(); if (parentItem == m_RootItem.get()) return QModelIndex(); return createIndex(parentItem->Row(), 0, parentItem); } QModelIndex QmitkMultiLabelTreeModel::ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const { if (!currentIndex.isValid()) return QModelIndex(); auto currentItem = static_cast(currentIndex.internalPointer()); if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; auto searchItem = currentItem; const auto rootItem = currentItem->RootItem(); while (searchItem != rootItem) { - resultItem = GetFirstInstanceLikeItem(searchItem->NextSibblingItem()); - if (nullptr != resultItem) break; + const auto* sibling = searchItem; - //no next closest label instance on this level -> check for closest before - resultItem = GetFirstInstanceLikeItem(searchItem->PrevSibblingItem()); - if (nullptr != resultItem) break; + while (sibling != nullptr) + { + sibling = sibling->NextSibblingItem(); + resultItem = GetFirstInstanceLikeItem(sibling); + + if (nullptr != resultItem) + break; + } + + if (nullptr != resultItem) + break; + + // No next closest label instance on this level -> check for closest before + sibling = searchItem; + + while (sibling != nullptr) + { + sibling = sibling->PrevSibblingItem(); + resultItem = GetFirstInstanceLikeItem(sibling); + + if (nullptr != resultItem) + break; + } + + if (nullptr != resultItem) + break; - //no closest label instance before current on this level -> moeve one level up + // No closest label instance before current on this level -> moeve one level up searchItem = searchItem->ParentItem(); } if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } QModelIndex QmitkMultiLabelTreeModel::FirstLabelInstanceIndex(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; resultItem = GetFirstInstanceLikeItem(currentItem); if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance //or instance node). If current index is at the end, an invalid index is returned.*/ //QModelIndex QmitkMultiLabelTreeModel::PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; std::vector QmitkMultiLabelTreeModel::GetLabelsInSubTree(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; return currentItem->GetLabelsInSubTree(); } std::vector QmitkMultiLabelTreeModel::GetLabelInstancesOfSameLabelClass(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (currentIndex.isValid()) { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Group == currentItem->m_ItemType) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Instance == currentItem->m_ItemType) currentItem = currentItem->ParentItem(); return currentItem->GetLabelsInSubTree(); } Qt::ItemFlags QmitkMultiLabelTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; auto item = static_cast(index.internalPointer()); if (!item) return Qt::NoItemFlags; if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance() && ((TableColumns::VISIBLE_COL == index.column() && m_AllowVisibilityModification) || (TableColumns::COLOR_COL == index.column() && m_AllowVisibilityModification) || //m_AllowVisibilityModification controls visibility and color (TableColumns::LOCKED_COL == index.column() && m_AllowLockModification))) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } } return Qt::NoItemFlags; } QVariant QmitkMultiLabelTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (TableColumns::NAME_COL == section) { return "Name"; } else if (TableColumns::LOCKED_COL == section) { return "Locked"; } else if (TableColumns::COLOR_COL == section) { return "Color"; } else if (TableColumns::VISIBLE_COL == section) { return "Visibility"; } } return QVariant(); } const mitk::LabelSetImage* QmitkMultiLabelTreeModel::GetSegmentation() const { return m_Segmentation; } void QmitkMultiLabelTreeModel::SetSegmentation(mitk::LabelSetImage* segmentation) { if (m_Segmentation != segmentation) { this->RemoveObserver(); this->m_Segmentation = segmentation; this->AddObserver(); this->UpdateInternalTree(); } } /**Helper function that adds a labek into the item tree. Passes back the new created instance iten*/ QmitkMultiLabelSegTreeItem* AddLabelToGroupTree(mitk::Label* label, QmitkMultiLabelSegTreeItem* groupItem, bool& newLabelItemCreated) { if (nullptr == groupItem) return nullptr; if (nullptr == label) return nullptr; newLabelItemCreated = false; std::set labelNames; for (auto labelItem : groupItem->m_childItems) { labelNames.emplace(labelItem->GetLabel()->GetName()); } QmitkMultiLabelSegTreeItem* labelItem = nullptr; auto finding = labelNames.find(label->GetName()); if (finding != labelNames.end()) { //other label with same name exists labelItem = groupItem->m_childItems[std::distance(labelNames.begin(), finding)]; } else { newLabelItemCreated = true; labelItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Label, groupItem, nullptr, label->GetName()); auto predicate = [label](const std::string& name) { return name > label->GetName(); }; auto insertFinding = std::find_if(labelNames.begin(), labelNames.end(), predicate); groupItem->m_childItems.insert(groupItem->m_childItems.begin() + std::distance(labelNames.begin(), insertFinding), labelItem); } auto instanceItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Instance, labelItem, label); auto predicate = [label](const QmitkMultiLabelSegTreeItem* item) { return item->GetLabelValue() > label->GetValue(); }; auto insertFinding = std::find_if(labelItem->m_childItems.begin(), labelItem->m_childItems.end(), predicate); labelItem->m_childItems.insert(labelItem->m_childItems.begin() + std::distance(labelItem->m_childItems.begin(), insertFinding), instanceItem); return instanceItem; } void QmitkMultiLabelTreeModel::GenerateInternalGroupTree(unsigned int groupID, QmitkMultiLabelSegTreeItem* groupItem) { auto labelSet = m_Segmentation->GetLabelSet(groupID); for (auto lIter = labelSet->IteratorConstBegin(); lIter != labelSet->IteratorConstEnd(); lIter++) { if (lIter->first== mitk::LabelSetImage::UnlabeledValue) continue; bool newItemCreated = false; AddLabelToGroupTree(lIter->second, groupItem, newItemCreated); } } QmitkMultiLabelSegTreeItem* QmitkMultiLabelTreeModel::GenerateInternalTree() { auto rootItem = new QmitkMultiLabelSegTreeItem(); if (m_Segmentation.IsNotNull()) { for (unsigned int groupID = 0; groupID < m_Segmentation->GetNumberOfLayers(); ++groupID) { auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); GenerateInternalGroupTree(groupID, groupItem); } } return rootItem; } void QmitkMultiLabelTreeModel::UpdateInternalTree() { emit beginResetModel(); auto newTree = this->GenerateInternalTree(); this->m_RootItem.reset(newTree); emit endResetModel(); emit modelChanged(); } void QmitkMultiLabelTreeModel::AddObserver() { if (this->m_Segmentation.IsNotNull()) { if (m_Observed) { MITK_DEBUG << "Invalid observer state in QmitkMultiLabelTreeModel. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; } this->m_Segmentation->AddLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->AddLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->AddLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); m_Observed = true; } } void QmitkMultiLabelTreeModel::RemoveObserver() { if (this->m_Segmentation.IsNotNull()) { this->m_Segmentation->RemoveLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->RemoveLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); } m_Observed = false; } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { GroupIndexType groupIndex = 0; if (m_Segmentation->IsLabelInGroup(labelValue, groupIndex)) { auto label = m_Segmentation->GetLabel(labelValue); if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; auto groupItem = GetGroupItem(groupIndex, this->m_RootItem.get()); bool newLabelCreated = false; auto instanceItem = AddLabelToGroupTree(label, groupItem, newLabelCreated); if (newLabelCreated) { if (groupItem->m_childItems.size() == 1) { //first label added auto groupIndex = GetIndexByItem(groupItem, this); emit dataChanged(groupIndex, groupIndex); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } else { //whole new label level added to group item auto groupIndex = GetIndexByItem(groupItem, this); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } } else { if (instanceItem->ParentItem()->m_childItems.size() < 3) { //second instance item was added, so label item will now able to colapse // -> the whole label node has to be updated. auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); emit dataChanged(labelIndex, labelIndex); this->beginInsertRows(labelIndex, 0, instanceItem->ParentItem()->m_childItems.size()-1); this->endInsertRows(); } else { // instance item was added to existing label item with multiple instances //-> just notify the row insertion auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); this->endInsertRows(); } } } else { mitkThrow() << "Group less labels are not supported in the current implementation."; } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelModified signal for a label that is not represented in the model. Invalid label: " << labelValue; } auto labelItem = instanceItem->ParentItem(); if (labelItem->m_ClassName == instanceItem->GetLabel()->GetName()) { //only the state of the label changed, but not its position in the model tree. auto index = GetIndexByItem(labelItem, this); emit dataChanged(index, index); } else { //the name of the label changed and thus its place in the model tree, delete the current item and add a new one this->OnLabelRemoved(labelValue); this->OnLabelAdded(labelValue); } } void QmitkMultiLabelTreeModel::OnLabelRemoved(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelRemoved signal for a label that is not represented in the model. Invalid label: " << labelValue; auto labelItem = instanceItem->ParentItem(); if (labelItem->m_childItems.size() > 2) { auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); } else if (labelItem->m_childItems.size() == 2) { //After removal only one label is left -> the whole label node is about to be changed (no instances are shown any more). auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); emit dataChanged(labelIndex, labelIndex); } else { //was the only instance of the label, therefor also remove the label node from the tree. auto groupItem = labelItem->ParentItem(); auto groupIndex = GetIndexByItem(groupItem, this); this->beginRemoveRows(groupIndex, labelItem->Row(), labelItem->Row()); groupItem->RemoveChild(labelItem->Row()); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::OnGroupAdded(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginInsertRows(QModelIndex(), groupIndex, groupIndex); auto rootItem = m_RootItem.get(); auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); this->GenerateInternalGroupTree(groupIndex, groupItem); this->endInsertRows(); } } void QmitkMultiLabelTreeModel::OnGroupModified(GroupIndexType /*groupIndex*/) { //currently not needed } void QmitkMultiLabelTreeModel::OnGroupRemoved(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginRemoveRows(QModelIndex(), groupIndex, groupIndex); auto root = m_RootItem.get(); root->RemoveChild(groupIndex); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::SetAllowVisibilityModification(bool vmod) { m_AllowVisibilityModification = vmod; } bool QmitkMultiLabelTreeModel::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelTreeModel::SetAllowLockModification(bool lmod) { m_AllowLockModification = lmod; } bool QmitkMultiLabelTreeModel::GetAllowLockModification() const { return m_AllowLockModification; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui new file mode 100644 index 0000000000..6f1f495a00 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingGUIControls.ui @@ -0,0 +1,169 @@ + + + QmitkSegmentAnythingGUIControls + + + + 0 + 0 + 699 + 490 + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100000 + 100000 + + + + QmitkSegmentAnythingToolWidget + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Welcome to Segment Anything Model (SAM) tool in MITK. [Experimental]</p><p>Please note that this is only an interface to SAM. MITK does not ship with SAM. Make sure to have a working internet connection to install Segment Anything Model via MITK. </p><p>Refer to <a href="https://segment-anything.com/"><span style=" text-decoration: underline; color:#0000ff;">https://segment-anything.com</span></a> to learn everything about the Segment Anything Model.</p></body></html> + + + Qt::RichText + + + true + + + + + + + + 0 + 0 + + + + +Press SHIFT+Left-click for positive seeds. +Press SHIFT+Right-click for negative seeds. +Press DEL to remove last seed. + + + + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Reset Picks + + + + + + + + 0 + 0 + + + + + 100000 + 16777215 + + + + Initialize Segment Anything + + + + + + + + 0 + 0 + + + + true + + + + + + + 1 + + + 0 + + + false + + + + + + + + + ctkComboBox + QComboBox +
ctkComboBox.h
+ 1 +
+ + ctkCollapsibleGroupBox + QWidget +
ctkCollapsibleGroupBox.h
+
+
+ + +
diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp new file mode 100644 index 0000000000..59bcbfab63 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.cpp @@ -0,0 +1,300 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkSegmentAnythingToolGUI.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkSegmentAnythingToolGUI, "") + +namespace +{ + mitk::IPreferences *GetPreferences() + { + auto *preferencesService = mitk::CoreServices::GetPreferencesService(); + return preferencesService->GetSystemPreferences()->Node("org.mitk.views.segmentation"); + } +} + +QmitkSegmentAnythingToolGUI::QmitkSegmentAnythingToolGUI() : QmitkSegWithPreviewToolGUIBase(true) +{ + m_EnableConfirmSegBtnFnc = [this](bool enabled) + { + bool result = false; + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + result = enabled && tool->HasPicks(); + } + return result; + }; + m_Preferences = GetPreferences(); + m_Preferences->OnPropertyChanged += + mitk::MessageDelegate1( + this, &QmitkSegmentAnythingToolGUI::OnPreferenceChangedEvent); +} + +QmitkSegmentAnythingToolGUI::~QmitkSegmentAnythingToolGUI() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->SAMStatusMessageEvent -= mitk::MessageDelegate1( + this, &QmitkSegmentAnythingToolGUI::StatusMessageListener); + } +} + +void QmitkSegmentAnythingToolGUI::InitializeUI(QBoxLayout *mainLayout) +{ + m_Controls.setupUi(this); + m_Controls.statusLabel->setTextFormat(Qt::RichText); + + QString welcomeText; + if (m_GpuLoader.GetGPUCount() != 0) + { + welcomeText = "STATUS: Welcome to Segment Anything tool. You're in luck: " + + QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected."; + } + else + { + welcomeText = "STATUS: Welcome to Segment Anything tool. Sorry, " + + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected."; + } + connect(m_Controls.activateButton, SIGNAL(clicked()), this, SLOT(OnActivateBtnClicked())); + connect(m_Controls.resetButton, SIGNAL(clicked()), this, SLOT(OnResetPicksClicked())); + + QIcon arrowIcon = QmitkStyleManager::ThemeIcon( + QStringLiteral(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); + m_Controls.activateButton->setIcon(arrowIcon); + + bool isInstalled = this->ValidatePrefences(); + if (isInstalled) + { + QString modelType = QString::fromStdString(m_Preferences->Get("sam modeltype", "")); + welcomeText += " SAM is already found installed. Model type '" + modelType + "' selected in Preferences."; + } + else + { + welcomeText += " SAM tool is not configured correctly. Please go to Preferences (Cntl+P) > Segment Anything to configure and/or install SAM."; + } + this->EnableAll(isInstalled); + this->WriteStatusMessage(welcomeText); + this->ShowProgressBar(false); + m_Controls.samProgressBar->setMaximum(0); + mainLayout->addLayout(m_Controls.verticalLayout); + Superclass::InitializeUI(mainLayout); +} + +bool QmitkSegmentAnythingToolGUI::ValidatePrefences() +{ + const QString storageDir = QString::fromStdString(m_Preferences->Get("sam python path", "")); + bool isInstalled = QmitkSegmentAnythingToolGUI::IsSAMInstalled(storageDir); + std::string modelType = m_Preferences->Get("sam modeltype", ""); + std::string path = m_Preferences->Get("sam parent path", ""); + return (isInstalled && !modelType.empty() && !path.empty()); +} + +void QmitkSegmentAnythingToolGUI::EnableAll(bool isEnable) +{ + m_Controls.activateButton->setEnabled(isEnable); +} + +void QmitkSegmentAnythingToolGUI::WriteStatusMessage(const QString &message) +{ + m_Controls.statusLabel->setText(message); + m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); + qApp->processEvents(); +} + +void QmitkSegmentAnythingToolGUI::WriteErrorMessage(const QString &message) +{ + m_Controls.statusLabel->setText(message); + m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); + qApp->processEvents(); +} + +void QmitkSegmentAnythingToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) +{ + this->setCursor(Qt::ArrowCursor); + QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); + messageBox->exec(); + delete messageBox; + MITK_WARN << message; +} + +void QmitkSegmentAnythingToolGUI::StatusMessageListener(const std::string &message) +{ + if (message.rfind("Error", 0) == 0) + { + this->EnableAll(true); + this->WriteErrorMessage(QString::fromStdString(message)); + } + else if (message == "TimeOut") + { // trying to re init the daemon + this->WriteErrorMessage(QString("STATUS: Sorry, operation timed out. Reactivating SAM tool...")); + if (this->ActivateSAMDaemon()) + { + this->WriteStatusMessage(QString("STATUS: Segment Anything tool re-initialized.")); + } + else + { + this->WriteErrorMessage(QString("STATUS: Couldn't init tool backend.")); + this->EnableAll(true); + } + } + else + { + this->WriteStatusMessage(QString::fromStdString(message)); + } +} + +void QmitkSegmentAnythingToolGUI::OnActivateBtnClicked() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr == tool) + { + return; + } + try + { + this->EnableAll(false); + qApp->processEvents(); + QString pythonPath = QString::fromStdString(m_Preferences->Get("sam python path", "")); + if (!QmitkSegmentAnythingToolGUI::IsSAMInstalled(pythonPath)) + { + throw std::runtime_error(WARNING_SAM_NOT_FOUND); + } + tool->SetPythonPath(pythonPath.toStdString()); + tool->SetGpuId(m_Preferences->GetInt("sam gpuid", -1)); + const QString modelType = QString::fromStdString(m_Preferences->Get("sam modeltype", "")); + tool->SetModelType(modelType.toStdString()); + tool->SetTimeOutLimit(m_Preferences->GetInt("sam timeout", 300)); + tool->SetCheckpointPath(m_Preferences->Get("sam parent path", "")); + this->WriteStatusMessage( + QString("STATUS: Initializing Segment Anything Model...")); + tool->SAMStatusMessageEvent += mitk::MessageDelegate1( + this, &QmitkSegmentAnythingToolGUI::StatusMessageListener); + if (this->ActivateSAMDaemon()) + { + this->WriteStatusMessage(QString("STATUS: Segment Anything tool initialized.")); + } + else + { + this->WriteErrorMessage(QString("STATUS: Couldn't init tool backend.")); + this->EnableAll(true); + } + } + catch (const std::exception &e) + { + std::stringstream errorMsg; + errorMsg << "STATUS: Error while processing parameters for Segment Anything segmentation. Reason: " << e.what(); + this->ShowErrorMessage(errorMsg.str()); + this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); + this->EnableAll(true); + return; + } + catch (...) + { + std::string errorMsg = "Unkown error occured while generation Segment Anything segmentation."; + this->ShowErrorMessage(errorMsg); + this->EnableAll(true); + return; + } +} + +bool QmitkSegmentAnythingToolGUI::ActivateSAMDaemon() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr == tool) + { + return false; + } + this->ShowProgressBar(true); + qApp->processEvents(); + try + { + tool->InitSAMPythonProcess(); + while (!tool->IsPythonReady()) + { + qApp->processEvents(); + } + tool->IsReadyOn(); + } + catch (...) + { + tool->IsReadyOff(); + } + this->ShowProgressBar(false); + return tool->GetIsReady(); +} + +void QmitkSegmentAnythingToolGUI::ShowProgressBar(bool enabled) +{ + m_Controls.samProgressBar->setEnabled(enabled); + m_Controls.samProgressBar->setVisible(enabled); +} + +bool QmitkSegmentAnythingToolGUI::IsSAMInstalled(const QString &pythonPath) +{ + QString fullPath = pythonPath; + bool isPythonExists = false; + bool isSamExists = false; +#ifdef _WIN32 + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); + if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + isPythonExists = + (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; + } +#else + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); + if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + isPythonExists = + (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; + } +#endif + isSamExists = QFile::exists(fullPath + QDir::separator() + QString("run_inference_daemon.py")); + bool isExists = isSamExists && isPythonExists; + return isExists; +} + +void QmitkSegmentAnythingToolGUI::OnResetPicksClicked() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->ClearPicks(); + } +} + +void QmitkSegmentAnythingToolGUI::OnPreferenceChangedEvent(const mitk::IPreferences::ChangeEvent&) +{ + this->EnableAll(true); + this->WriteStatusMessage("A Preference change was detected. Please initialize the tool again."); + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->IsReadyOff(); + } +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.h new file mode 100644 index 0000000000..7349f6a578 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentAnythingToolGUI.h @@ -0,0 +1,121 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkSegmentAnythingToolGUI_h +#define QmitkSegmentAnythingToolGUI_h + +#include "QmitkSegWithPreviewToolGUIBase.h" +#include +#include "ui_QmitkSegmentAnythingGUIControls.h" +#include "QmitknnUNetGPU.h" +#include "QmitkSetupVirtualEnvUtil.h" +#include +#include +#include + +/** +\ingroup org_mitk_gui_qt_interactivesegmentation_internal +\brief GUI for mitk::SegmentAnythingTool. +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkSegmentAnythingToolGUI : public QmitkSegWithPreviewToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkSegmentAnythingToolGUI, QmitkSegWithPreviewToolGUIBase); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + /** + * @brief Checks if SegmentAnything is found inside the selected python virtual environment. + * @return bool + */ + static bool IsSAMInstalled(const QString &); + +protected slots: + /** + * @brief Qt Slot + */ + void OnResetPicksClicked(); + + /** + * @brief Qt Slot + */ + void OnActivateBtnClicked(); + +protected: + QmitkSegmentAnythingToolGUI(); + ~QmitkSegmentAnythingToolGUI(); + + void InitializeUI(QBoxLayout *mainLayout) override; + + /** + * @brief Writes any message in white on the tool pane. + */ + void WriteStatusMessage(const QString&); + + /** + * @brief Writes any message in red on the tool pane. + */ + void WriteErrorMessage(const QString&); + + /** + * @brief Function to listen to tool class status emitters. + */ + void StatusMessageListener(const std::string&); + + /** + * @brief Function to listen to preference emitters. + */ + void OnPreferenceChangedEvent(const mitk::IPreferences::ChangeEvent&); + + /** + * @brief Creates a QMessage object and shows on screen. + */ + void ShowErrorMessage(const std::string&, QMessageBox::Icon = QMessageBox::Critical); + + /** + * @brief Enable (or Disable) GUI elements. Currently, on the activate button + * is affected. + */ + void EnableAll(bool); + + /** + * @brief Enable (or Disable) progressbar on GUI + * + */ + void ShowProgressBar(bool); + + /** + * @brief Requests the tool class to spawn the SAM python daemon + * process. Waits until the daemon is started. + * + * @return bool + */ + bool ActivateSAMDaemon(); + + /** + * @brief Checks if the preferences are correctly set by the user. + * + * @return bool + */ + bool ValidatePrefences(); + +private: + mitk::IPreferences* m_Preferences; + Ui_QmitkSegmentAnythingGUIControls m_Controls; + QmitkGPULoader m_GpuLoader; + bool m_FirstPreviewComputation = true; + const std::string WARNING_SAM_NOT_FOUND = + "SAM is not detected in the selected python environment. Please reinstall SAM."; +}; +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp index 50c6fe88f9..9fd02ade30 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSetupVirtualEnvUtil.cpp @@ -1,182 +1,183 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file.s ============================================================================*/ #include "QmitkSetupVirtualEnvUtil.h" #include "mitkLog.h" #include #include QmitkSetupVirtualEnvUtil::QmitkSetupVirtualEnvUtil() { m_BaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + qApp->organizationName() + QDir::separator(); } QmitkSetupVirtualEnvUtil::QmitkSetupVirtualEnvUtil(const QString &baseDir) { m_BaseDir = baseDir; } QString& QmitkSetupVirtualEnvUtil::GetBaseDir() { return m_BaseDir; } QString QmitkSetupVirtualEnvUtil::GetVirtualEnvPath() { return m_venvPath; } QString& QmitkSetupVirtualEnvUtil::GetSystemPythonPath() { return m_SysPythonPath; } QString& QmitkSetupVirtualEnvUtil::GetPythonPath() { return m_PythonPath; } QString& QmitkSetupVirtualEnvUtil::GetPipPath() { return m_PipPath; } void QmitkSetupVirtualEnvUtil::SetVirtualEnvPath(const QString &path) { m_venvPath = path; } void QmitkSetupVirtualEnvUtil::SetPipPath(const QString &path) { m_PipPath = path; } void QmitkSetupVirtualEnvUtil::SetPythonPath(const QString &path) { if (this->IsPythonPath(path)) { m_PythonPath = path; } else { MITK_INFO << "Python was not detected in " + path.toStdString(); } } void QmitkSetupVirtualEnvUtil::SetSystemPythonPath(const QString &path) { if (this->IsPythonPath(path)) { m_SysPythonPath = path; } else { MITK_INFO << "Python was not detected in " + path.toStdString(); } } void QmitkSetupVirtualEnvUtil::PrintProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } void QmitkSetupVirtualEnvUtil::InstallPytorch(const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *)) { mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(callback); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-m"); args.push_back("pip"); args.push_back("install"); - args.push_back("light-the-torch"); + args.push_back("light-the-torch==0.7.5"); spExec->Execute(workingDir, "python", args); - PipInstall("torch", workingDir, callback, "ltt"); + PipInstall("torch==2.0.0", workingDir, callback, "ltt"); + PipInstall("torchvision==0.15.0", workingDir, callback, "ltt"); } void QmitkSetupVirtualEnvUtil::InstallPytorch() { this->InstallPytorch(GetPythonPath().toStdString(), &PrintProcessEvent); } void QmitkSetupVirtualEnvUtil::PipInstall(const std::string &library, const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command) { mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(callback); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("install"); args.push_back(library); spExec->Execute(workingDir, command, args); } void QmitkSetupVirtualEnvUtil::PipInstall(const std::string &library, void (*callback)(itk::Object*, const itk::EventObject&, void*), const std::string& command) { this->PipInstall(library, this->GetPipPath().toStdString(), callback, command); } void QmitkSetupVirtualEnvUtil::ExecutePython(const std::string &pythonCode, const std::string &workingDir, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command) { mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(callback); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-c"); args.push_back(pythonCode); spExec->Execute(workingDir, command, args); } void QmitkSetupVirtualEnvUtil::ExecutePython(const std::string &args, void (*callback)(itk::Object *, const itk::EventObject &, void *), const std::string &command) { this->ExecutePython(args, this->GetPythonPath().toStdString(), callback, command); } bool QmitkSetupVirtualEnvUtil::IsPythonPath(const QString &pythonPath) { QString fullPath = pythonPath; bool isExists = #ifdef _WIN32 QFile::exists(fullPath + QDir::separator() + QString("python.exe")); #else QFile::exists(fullPath + QDir::separator() + QString("python3")); #endif return isExists; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui index cd5b68ca9e..b4a1b287b6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorGUIControls.ui @@ -1,357 +1,357 @@ QmitkTotalSegmentatorToolGUIControls 0 0 699 352 0 0 100 0 100000 100000 QmitkTotalSegmentatorToolWidget 0 0 0 0 0 0 - <html><head/><body><p>Welcome to TotalSegmentator tool in MITK. [Experimental]</p><p>Please note that this is only an interface to TotalSegmentator. MITK does not ship with TotalSegmentator. Make sure to have a working internet connection to install TotalSegmentator. Or, choose an python environment with TotalSegmentator in it before inferencing. + <html><head/><body><p>Welcome to TotalSegmentator v2 tool in MITK. [Experimental]</p><p>Please note that this is only an interface to TotalSegmentator. MITK does not ship with TotalSegmentator. Make sure to have a working internet connection to install TotalSegmentator. Or, choose an python environment with TotalSegmentator in it before inferencing. Currently, the tool is pretrained on CT images only. </p><p>Refer to <a href="https://github.com/wasserth/TotalSegmentator"><span style=" text-decoration: underline; color:#0000ff;">https://github.com/wasserth/TotalSegmentator</span></a> to learn everything about the TotalSegmentator.</p><p><br/></p></body></html> Qt::RichText true 0 0 100000 16777215 Install TotalSegmentator 0 0 Install Options true 5 true 0 0 0 0 6 0 0 Use Custom Installation: false 0 0 100000 16777215 Clear Install 0 0 Custom Env. Path: 0 0 - System Python: + System Python (>=3.9): 0 0 Task: 0 0 Advanced true 5 true 0 0 0 0 6 0 0 Fast: true 0 0 GPU Id: 0 0 100000 16777215 Run TotalSegmentator 0 0 true ctkComboBox QComboBox
ctkComboBox.h
1
ctkCollapsibleGroupBox QWidget
ctkCollapsibleGroupBox.h
ctkCheckBox QWidget
ctkCheckBox.h
diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp index f400af38f7..8b24a854dc 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.cpp @@ -1,500 +1,513 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + #include "QmitkTotalSegmentatorToolGUI.h" #include "mitkProcessExecutor.h" #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkTotalSegmentatorToolGUI, "") QmitkTotalSegmentatorToolGUI::QmitkTotalSegmentatorToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase(), m_SuperclassEnableConfirmSegBtnFnc(m_EnableConfirmSegBtnFnc) { // Nvidia-smi command returning zero doesn't always imply lack of GPUs. // Pytorch uses its own libraries to communicate to the GPUs. Hence, only a warning can be given. if (m_GpuLoader.GetGPUCount() == 0) { std::string warning = "WARNING: No GPUs were detected on your machine. The TotalSegmentator tool can be very slow."; this->ShowErrorMessage(warning); } m_EnableConfirmSegBtnFnc = [this](bool enabled) { return !m_FirstPreviewComputation ? m_SuperclassEnableConfirmSegBtnFnc(enabled) : false; }; } void QmitkTotalSegmentatorToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) { Superclass::ConnectNewTool(newTool); m_FirstPreviewComputation = true; } void QmitkTotalSegmentatorToolGUI::InitializeUI(QBoxLayout *mainLayout) { m_Controls.setupUi(this); #ifndef _WIN32 m_Controls.sysPythonComboBox->addItem("/usr/bin"); #endif this->AutoParsePythonPaths(); m_Controls.sysPythonComboBox->addItem("Select"); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->addItem("Select"); m_Controls.pythonEnvComboBox->setDuplicatesEnabled(false); m_Controls.pythonEnvComboBox->setDisabled(true); m_Controls.previewButton->setDisabled(true); m_Controls.statusLabel->setTextFormat(Qt::RichText); m_Controls.subtaskComboBox->addItems(VALID_TASKS); QString welcomeText; this->SetGPUInfo(); if (m_GpuLoader.GetGPUCount() != 0) { welcomeText = "STATUS: Welcome to TotalSegmentator tool. You're in luck: " + QString::number(m_GpuLoader.GetGPUCount()) + " GPU(s) were detected."; } else { welcomeText = "STATUS: Welcome to TotalSegmentator tool. Sorry, " + QString::number(m_GpuLoader.GetGPUCount()) + " GPUs were detected."; } connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.clearButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); connect(m_Controls.installButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); connect(m_Controls.overrideBox, SIGNAL(stateChanged(int)), this, SLOT(OnOverrideChecked(int))); connect(m_Controls.pythonEnvComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnPythonPathChanged(m_Controls.pythonEnvComboBox->itemText(index)); }); connect(m_Controls.sysPythonComboBox, QOverload::of(&QComboBox::activated), [=](int index) { OnSystemPythonChanged(m_Controls.sysPythonComboBox->itemText(index)); }); QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (!lastSelectedPyEnv.isEmpty() && lastSelectedPyEnv!= "Select") { m_Controls.pythonEnvComboBox->insertItem(0, lastSelectedPyEnv); } const QString storageDir = m_Installer.GetVirtualEnvPath(); m_IsInstalled = this->IsTotalSegmentatorInstalled(storageDir); if (m_IsInstalled) { m_PythonPath = GetExactPythonPath(storageDir); m_Installer.SetVirtualEnvPath(m_PythonPath); this->EnableAll(m_IsInstalled); welcomeText += " TotalSegmentator is already found installed."; } else { welcomeText += " TotalSegmentator is not installed. Please click on \"Install TotalSegmentator\" above."; } this->WriteStatusMessage(welcomeText); QIcon deleteIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); QIcon arrowIcon = QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/tango/scalable/actions/media-playback-start.svg")); m_Controls.clearButton->setIcon(deleteIcon); m_Controls.previewButton->setIcon(arrowIcon); mainLayout->addLayout(m_Controls.verticalLayout); Superclass::InitializeUI(mainLayout); } void QmitkTotalSegmentatorToolGUI::SetGPUInfo() { std::vector specs = m_GpuLoader.GetAllGPUSpecs(); for (const QmitkGPUSpec &gpuSpec : specs) { m_Controls.gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); } if (specs.empty()) { m_Controls.gpuComboBox->setEditable(true); m_Controls.gpuComboBox->addItem(QString::number(0)); m_Controls.gpuComboBox->setValidator(new QIntValidator(0, 999, this)); } } unsigned int QmitkTotalSegmentatorToolGUI::FetchSelectedGPUFromUI() const { QString gpuInfo = m_Controls.gpuComboBox->currentText(); if (m_GpuLoader.GetGPUCount() == 0) { return static_cast(gpuInfo.toInt()); } else { QString gpuId = gpuInfo.split(":", QString::SplitBehavior::SkipEmptyParts).first(); return static_cast(gpuId.toInt()); } } void QmitkTotalSegmentatorToolGUI::EnableAll(bool isEnable) { m_Controls.previewButton->setEnabled(isEnable); m_Controls.subtaskComboBox->setEnabled(isEnable); m_Controls.installButton->setEnabled((!isEnable)); } void QmitkTotalSegmentatorToolGUI::OnInstallBtnClicked() { bool isInstalled = false; QString systemPython = OnSystemPythonChanged(m_Controls.sysPythonComboBox->currentText()); if (systemPython.isEmpty()) { this->WriteErrorMessage("ERROR: Couldn't find Python."); } else { this->WriteStatusMessage("STATUS: Installing TotalSegmentator..."); m_Installer.SetSystemPythonPath(systemPython); isInstalled = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); if (isInstalled) { m_PythonPath = this->GetExactPythonPath(m_Installer.GetVirtualEnvPath()); this->WriteStatusMessage("STATUS: Successfully installed TotalSegmentator."); } else { this->WriteErrorMessage("ERROR: Couldn't install TotalSegmentator."); } } this->EnableAll(isInstalled); } void QmitkTotalSegmentatorToolGUI::OnPreviewBtnClicked() { auto tool = this->GetConnectedToolAs(); if (nullptr == tool) { return; } try { m_Controls.previewButton->setEnabled(false); qApp->processEvents(); if (!this->IsTotalSegmentatorInstalled(m_PythonPath)) { throw std::runtime_error(WARNING_TOTALSEG_NOT_FOUND); } bool isFast = m_Controls.fastBox->isChecked(); QString subTask = m_Controls.subtaskComboBox->currentText(); if (subTask != VALID_TASKS[0]) { isFast = true; } tool->SetPythonPath(m_PythonPath.toStdString()); tool->SetGpuId(FetchSelectedGPUFromUI()); tool->SetFast(isFast); tool->SetSubTask(subTask.toStdString()); this->WriteStatusMessage(QString("STATUS: Starting Segmentation task... This might take a while.")); tool->UpdatePreview(); m_Controls.previewButton->setEnabled(true); m_FirstPreviewComputation = false; } catch (const std::exception &e) { std::stringstream errorMsg; errorMsg << "STATUS: Error while processing parameters for TotalSegmentator segmentation. Reason: " << e.what(); this->ShowErrorMessage(errorMsg.str()); this->WriteErrorMessage(QString::fromStdString(errorMsg.str())); m_Controls.previewButton->setEnabled(true); return; } catch (...) { std::string errorMsg = "Unkown error occured while generation TotalSegmentator segmentation."; this->ShowErrorMessage(errorMsg); m_Controls.previewButton->setEnabled(true); return; } this->SetLabelSetPreview(tool->GetPreviewSegmentation()); this->ActualizePreviewLabelVisibility(); this->WriteStatusMessage("STATUS: Segmentation task finished successfully."); QString pythonPathTextItem = m_Controls.pythonEnvComboBox->currentText(); if (!pythonPathTextItem.isEmpty() && pythonPathTextItem != "Select") // only cache if the prediction ended without errors. { QString lastSelectedPyEnv = m_Settings.value("TotalSeg/LastCustomPythonPath").toString(); if (lastSelectedPyEnv != pythonPathTextItem) { m_Settings.setValue("TotalSeg/LastCustomPythonPath", pythonPathTextItem); } } } void QmitkTotalSegmentatorToolGUI::ShowErrorMessage(const std::string &message, QMessageBox::Icon icon) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(icon, nullptr, message.c_str()); messageBox->exec(); delete messageBox; MITK_WARN << message; } void QmitkTotalSegmentatorToolGUI::WriteStatusMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: white"); qApp->processEvents(); } void QmitkTotalSegmentatorToolGUI::WriteErrorMessage(const QString &message) { m_Controls.statusLabel->setText(message); m_Controls.statusLabel->setStyleSheet("font-weight: bold; color: red"); qApp->processEvents(); } bool QmitkTotalSegmentatorToolGUI::IsTotalSegmentatorInstalled(const QString &pythonPath) { QString fullPath = pythonPath; - bool isPythonExists = false; + bool isPythonExists = false, isExists = false; #ifdef _WIN32 isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); if (!(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python.exe")) : isPythonExists; } + isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator.exe")) && isPythonExists; #else isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); if (!(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); isPythonExists = (!isPythonExists) ? QFile::exists(fullPath + QDir::separator() + QString("python3")) : isPythonExists; } + isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator")) && isPythonExists; #endif - bool isExists = QFile::exists(fullPath + QDir::separator() + QString("TotalSegmentator")) && isPythonExists; return isExists; } void QmitkTotalSegmentatorToolGUI::AutoParsePythonPaths() { QString homeDir = QDir::homePath(); std::vector searchDirs; #ifdef _WIN32 searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + QString("anaconda3")); #else // Add search locations for possible standard python paths here searchDirs.push_back(homeDir + QDir::separator() + "environments"); searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); #endif for (QString searchDir : searchDirs) { if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) { if (QDir(searchDir).exists()) { m_Controls.sysPythonComboBox->addItem("(base): " + searchDir); searchDir.append((QDir::separator() + QString("envs"))); } } for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) { subIt.next(); QString envName = subIt.fileName(); if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. { m_Controls.sysPythonComboBox->addItem("(" + envName + "): " + subIt.filePath()); } } } } QString QmitkTotalSegmentatorToolGUI::OnSystemPythonChanged(const QString &pyEnv) { QString pyPath; if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.sysPythonComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnSystemPythonChanged(path); // recall same function for new path validation bool oldState = m_Controls.sysPythonComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.sysPythonComboBox->insertItem(0, path); m_Controls.sysPythonComboBox->setCurrentIndex(0); m_Controls.sysPythonComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else { QString uiPyPath = this->GetPythonPathFromUI(pyEnv); pyPath = this->GetExactPythonPath(uiPyPath); } return pyPath; } void QmitkTotalSegmentatorToolGUI::OnPythonPathChanged(const QString &pyEnv) { if (pyEnv == QString("Select")) { m_Controls.previewButton->setDisabled(true); QString path = QFileDialog::getExistingDirectory(m_Controls.pythonEnvComboBox->parentWidget(), "Python Path", "dir"); if (!path.isEmpty()) { this->OnPythonPathChanged(path); // recall same function for new path validation bool oldState = m_Controls.pythonEnvComboBox->blockSignals(true); // block signal firing while inserting item m_Controls.pythonEnvComboBox->insertItem(0, path); m_Controls.pythonEnvComboBox->setCurrentIndex(0); m_Controls.pythonEnvComboBox->blockSignals( oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration } } else if (!this->IsTotalSegmentatorInstalled(pyEnv)) { this->ShowErrorMessage(WARNING_TOTALSEG_NOT_FOUND); m_Controls.previewButton->setDisabled(true); } else {// Show positive status meeage m_Controls.previewButton->setDisabled(false); QString uiPyPath = this->GetPythonPathFromUI(pyEnv); m_PythonPath = this->GetExactPythonPath(uiPyPath); } } QString QmitkTotalSegmentatorToolGUI::GetPythonPathFromUI(const QString &pyUI) const { QString fullPath = pyUI; if (-1 != fullPath.indexOf(")")) { fullPath = fullPath.mid(fullPath.indexOf(")") + 2); } return fullPath.simplified(); } QString QmitkTotalSegmentatorToolGUI::GetExactPythonPath(const QString &pyEnv) const { QString fullPath = pyEnv; bool isPythonExists = false; #ifdef _WIN32 isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); if (!isPythonExists && !(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("Scripts"); isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); } #else isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); if (!isPythonExists && !(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) { fullPath += QDir::separator() + QString("bin"); isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); } #endif if (!isPythonExists) { fullPath.clear(); } return fullPath; } void QmitkTotalSegmentatorToolGUI::OnOverrideChecked(int state) { bool isEnabled = false; if (state == Qt::Checked) { isEnabled = true; m_Controls.previewButton->setDisabled(true); m_PythonPath.clear(); } else { m_PythonPath.clear(); m_Controls.previewButton->setDisabled(true); if (m_IsInstalled) { const QString pythonPath = m_Installer.GetVirtualEnvPath(); m_PythonPath = this->GetExactPythonPath(pythonPath); this->EnableAll(m_IsInstalled); } } m_Controls.pythonEnvComboBox->setEnabled(isEnabled); } void QmitkTotalSegmentatorToolGUI::OnClearInstall() { QDir folderPath(m_Installer.GetVirtualEnvPath()); if (folderPath.removeRecursively()) { m_Controls.installButton->setEnabled(true); m_IsInstalled = false; if (!m_Controls.overrideBox->isChecked()) { m_Controls.previewButton->setEnabled(false); } } else { MITK_ERROR << "The virtual environment couldn't be removed. Please check if you have the required access privileges or, some other process is accessing the folders."; } } bool QmitkTotalSegmentatorToolInstaller::SetupVirtualEnv(const QString& venvName) { if (GetSystemPythonPath().isEmpty()) { return false; } QDir folderPath(GetBaseDir()); folderPath.mkdir(venvName); if (!folderPath.cd(venvName)) { return false; // Check if directory creation was successful. } mitk::ProcessExecutor::ArgumentListType args; auto spExec = mitk::ProcessExecutor::New(); auto spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&PrintProcessEvent); spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); args.push_back("-m"); args.push_back("venv"); args.push_back(venvName.toStdString()); #ifdef _WIN32 QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python.exe"; QString pythonExeFolder = "Scripts"; #else QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python3"; QString pythonExeFolder = "bin"; #endif spExec->Execute(GetBaseDir().toStdString(), pythonFile.toStdString(), args); // Setup local virtual environment if (folderPath.cd(pythonExeFolder)) { this->SetPythonPath(folderPath.absolutePath()); this->SetPipPath(folderPath.absolutePath()); this->InstallPytorch(); for (auto &package : PACKAGES) { this->PipInstall(package.toStdString(), &PrintProcessEvent); } std::string pythonCode; // python syntax to check if torch is installed with CUDA. pythonCode.append("import torch;"); pythonCode.append("print('Pytorch was installed with CUDA') if torch.cuda.is_available() else print('PyTorch was " "installed WITHOUT CUDA');"); this->ExecutePython(pythonCode, &PrintProcessEvent); return true; } return false; } QString QmitkTotalSegmentatorToolInstaller::GetVirtualEnvPath() { return STORAGE_DIR + VENV_NAME; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h index 3fcf750bd5..fb1442dfaa 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkTotalSegmentatorToolGUI.h @@ -1,177 +1,187 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + #ifndef QmitkTotalSegmentatorToolGUI_h_Included #define QmitkTotalSegmentatorToolGUI_h_Included #include "QmitkMultiLabelSegWithPreviewToolGUIBase.h" #include "QmitkSetupVirtualEnvUtil.h" #include "QmitknnUNetGPU.h" #include "ui_QmitkTotalSegmentatorGUIControls.h" #include #include #include #include /** * @brief Installer class for TotalSegmentator Tool. * Class specifies the virtual environment name, install version, packages required to pip install * and implements SetupVirtualEnv method. * */ class QmitkTotalSegmentatorToolInstaller : public QmitkSetupVirtualEnvUtil { public: - const QString VENV_NAME = ".totalsegmentator"; - const QString TOTALSEGMENTATOR_VERSION = "1.5.5"; - const std::vector PACKAGES = {QString("Totalsegmentator==") + TOTALSEGMENTATOR_VERSION, - QString("scipy==1.9.1"), - QString("urllib3==1.26.15")}; + const QString VENV_NAME = ".totalsegmentator_v2"; + const QString TOTALSEGMENTATOR_VERSION = "2.0.5"; + const std::vector PACKAGES = {QString("Totalsegmentator==") + TOTALSEGMENTATOR_VERSION}; const QString STORAGE_DIR; inline QmitkTotalSegmentatorToolInstaller( const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + qApp->organizationName() + QDir::separator()) : QmitkSetupVirtualEnvUtil(baseDir), STORAGE_DIR(baseDir){}; bool SetupVirtualEnv(const QString &) override; QString GetVirtualEnvPath() override; }; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::TotalSegmentatorTool. \sa mitk:: */ class MITKSEGMENTATIONUI_EXPORT QmitkTotalSegmentatorToolGUI : public QmitkMultiLabelSegWithPreviewToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkTotalSegmentatorToolGUI, QmitkMultiLabelSegWithPreviewToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots: /** * @brief Qt Slot */ void OnPreviewBtnClicked(); /** * @brief Qt Slot */ void OnPythonPathChanged(const QString &); /** * @brief Qt Slot */ QString OnSystemPythonChanged(const QString &); /** * @brief Qt Slot */ void OnInstallBtnClicked(); /** * @brief Qt Slot */ void OnOverrideChecked(int); /** * @brief Qt Slot */ void OnClearInstall(); protected: QmitkTotalSegmentatorToolGUI(); ~QmitkTotalSegmentatorToolGUI() = default; void ConnectNewTool(mitk::SegWithPreviewTool *newTool) override; void InitializeUI(QBoxLayout *mainLayout) override; /** * @brief Enable (or Disable) GUI elements. */ void EnableAll(bool); /** * @brief Searches and parses paths of python virtual enviroments * from predefined lookout locations */ void AutoParsePythonPaths(); /** * @brief Checks if TotalSegmentator command is valid in the selected python virtual environment. * * @return bool */ bool IsTotalSegmentatorInstalled(const QString &); /** * @brief Creates a QMessage object and shows on screen. */ void ShowErrorMessage(const std::string &, QMessageBox::Icon = QMessageBox::Critical); /** * @brief Writes any message in white on the tool pane. */ void WriteStatusMessage(const QString &); /** * @brief Writes any message in red on the tool pane. */ void WriteErrorMessage(const QString &); /** * @brief Adds GPU information to the gpu combo box. * In case, there aren't any GPUs avaialble, the combo box will be * rendered editable. */ void SetGPUInfo(); /** * @brief Returns GPU id of the selected GPU from the Combo box. * * @return unsigned int */ unsigned int FetchSelectedGPUFromUI() const; /** * @brief Get the virtual env path from UI combobox removing any * extra special characters. * * @return QString */ QString GetPythonPathFromUI(const QString &) const; /** * @brief Get the Exact Python Path for any OS * from the virtual environment path. * @return QString */ QString GetExactPythonPath(const QString &) const; /** * @brief For storing values like Python path across sessions. */ QSettings m_Settings; QString m_PythonPath; QmitkGPULoader m_GpuLoader; Ui_QmitkTotalSegmentatorToolGUIControls m_Controls; bool m_FirstPreviewComputation = true; bool m_IsInstalled = false; EnableConfirmSegBtnFunctionType m_SuperclassEnableConfirmSegBtnFnc; const std::string WARNING_TOTALSEG_NOT_FOUND = "TotalSegmentator is not detected in the selected python environment.Please select a valid " "python environment or install TotalSegmentator."; const QStringList VALID_TASKS = { "total", "cerebral_bleed", "hip_implant", "coronary_arteries", "body", "lung_vessels", "pleural_pericard_effusion" }; QmitkTotalSegmentatorToolInstaller m_Installer; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp index 00837b995c..68dfb6aa6b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetGPU.cpp @@ -1,55 +1,55 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file.s ============================================================================*/ #include "QmitknnUNetGPU.h" #include QmitkGPULoader::QmitkGPULoader() { QProcess process; process.start("nvidia-smi --query-gpu=name,memory.total --format=csv"); process.waitForFinished(-1); QStringList infoStringList; while (process.canReadLine()) { QString line = process.readLine(); if (!line.startsWith("name")) { infoStringList << line; } } unsigned int count = 0; foreach (QString infoString, infoStringList) { QmitkGPUSpec spec; QStringList gpuDetails; gpuDetails = infoString.split(","); if(gpuDetails.count() == 2) { - spec.name = gpuDetails.at(0); + spec.name = gpuDetails.at(0).trimmed(); spec.id = count; - spec.memory = gpuDetails.at(1); + spec.memory = gpuDetails.at(1).trimmed(); this->m_Gpus.push_back(spec); ++count; } } } int QmitkGPULoader::GetGPUCount() const { return static_cast(m_Gpus.size()); } std::vector QmitkGPULoader::GetAllGPUSpecs() { return m_Gpus; } diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index fff1c66311..d230e65769 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,123 +1,126 @@ set(CPP_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.cpp Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkEditableContourToolGUIBase.cpp Qmitk/QmitkGrowCutToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkLassoToolGUI.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitknnUNetFolderParser.cpp Qmitk/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetWorker.cpp Qmitk/QmitknnUNetGPU.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkStaticDynamicSegmentationDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp Qmitk/QmitkSegmentationTaskListWidget.cpp Qmitk/QmitkTotalSegmentatorToolGUI.cpp Qmitk/QmitkSetupVirtualEnvUtil.cpp Qmitk/QmitkMultiLabelInspector.cpp Qmitk/QmitkMultiLabelManager.cpp Qmitk/QmitkMultiLabelTreeModel.cpp Qmitk/QmitkMultiLabelTreeView.cpp Qmitk/QmitkMultiLabelPresetHelper.cpp Qmitk/QmitkLabelColorItemDelegate.cpp Qmitk/QmitkLabelToggleItemDelegate.cpp Qmitk/QmitkFindSegmentationTaskDialog.cpp + Qmitk/QmitkSegmentAnythingToolGUI.cpp SegmentationUtilities/QmitkBooleanOperationsWidget.cpp SegmentationUtilities/QmitkContourModelToImageWidget.cpp SegmentationUtilities/QmitkImageMaskingWidget.cpp SegmentationUtilities/QmitkMorphologicalOperationsWidget.cpp SegmentationUtilities/QmitkSurfaceToImageWidget.cpp SegmentationUtilities/QmitkDataSelectionWidget.cpp ) set(H_FILES Qmitk/QmitkMultiLabelPresetHelper.h ) set(MOC_H_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.h Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkEditableContourToolGUIBase.h Qmitk/QmitkGrowCutToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkLassoToolGUI.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitknnUNetFolderParser.h Qmitk/QmitknnUNetToolGUI.h Qmitk/QmitknnUNetGPU.h Qmitk/QmitknnUNetWorker.h Qmitk/QmitknnUNetEnsembleLayout.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkStaticDynamicSegmentationDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h Qmitk/QmitkSegmentationTaskListWidget.h Qmitk/QmitkTotalSegmentatorToolGUI.h Qmitk/QmitkSetupVirtualEnvUtil.h Qmitk/QmitkMultiLabelInspector.h Qmitk/QmitkMultiLabelManager.h Qmitk/QmitkMultiLabelTreeModel.h Qmitk/QmitkMultiLabelTreeView.h Qmitk/QmitkLabelColorItemDelegate.h Qmitk/QmitkLabelToggleItemDelegate.h Qmitk/QmitkFindSegmentationTaskDialog.h + Qmitk/QmitkSegmentAnythingToolGUI.h SegmentationUtilities/QmitkBooleanOperationsWidget.h SegmentationUtilities/QmitkContourModelToImageWidget.h SegmentationUtilities/QmitkImageMaskingWidget.h SegmentationUtilities/QmitkMorphologicalOperationsWidget.h SegmentationUtilities/QmitkSurfaceToImageWidget.h SegmentationUtilities/QmitkDataSelectionWidget.h ) set(UI_FILES Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkGrowCutToolWidgetControls.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitknnUNetToolGUIControls.ui Qmitk/QmitkEditableContourToolGUIControls.ui Qmitk/QmitkSegmentationTaskListWidget.ui Qmitk/QmitkTotalSegmentatorGUIControls.ui Qmitk/QmitkMultiLabelInspectorControls.ui Qmitk/QmitkMultiLabelManagerControls.ui Qmitk/QmitkFindSegmentationTaskDialog.ui + Qmitk/QmitkSegmentAnythingGUIControls.ui SegmentationUtilities/QmitkBooleanOperationsWidgetControls.ui SegmentationUtilities/QmitkContourModelToImageWidgetControls.ui SegmentationUtilities/QmitkImageMaskingWidgetControls.ui SegmentationUtilities/QmitkMorphologicalOperationsWidgetControls.ui SegmentationUtilities/QmitkSurfaceToImageWidgetControls.ui SegmentationUtilities/QmitkDataSelectionWidgetControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/PluginList.cmake b/Plugins/PluginList.cmake index 4b71a33320..30109b8e31 100644 --- a/Plugins/PluginList.cmake +++ b/Plugins/PluginList.cmake @@ -1,86 +1,86 @@ # 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.mitk.core.services:ON org.mitk.gui.common:ON org.mitk.planarfigure:ON org.mitk.core.jobs:OFF org.mitk.gui.qt.application:ON org.mitk.gui.qt.ext:OFF org.mitk.gui.qt.extapplication:OFF org.mitk.gui.qt.mitkworkbench.intro:OFF org.mitk.gui.qt.common:ON org.mitk.gui.qt.stdmultiwidgeteditor:ON org.mitk.gui.qt.mxnmultiwidgeteditor:OFF org.mitk.gui.qt.cmdlinemodules:OFF org.mitk.gui.qt.chartExample: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.dicombrowser:OFF org.mitk.gui.qt.dicominspector: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.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.deformableclippingplane: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.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.ultrasoundtrackingnavigation:OFF org.mitk.gui.qt.overlaymanager:OFF org.mitk.gui.qt.igt.app.hummelprotocolmeasurements: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.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.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.concentration.mri:OFF + org.mitk.gui.qt.pharmacokinetics.curvedescriptor: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 org.mitk.gui.qt.flowapplication:OFF org.mitk.gui.qt.flow.segmentation:OFF org.mitk.gui.qt.pixelvalue:ON ) diff --git a/Plugins/org.blueberry.ui.qt/CMakeLists.txt b/Plugins/org.blueberry.ui.qt/CMakeLists.txt index 362c65471a..b675068ba7 100644 --- a/Plugins/org.blueberry.ui.qt/CMakeLists.txt +++ b/Plugins/org.blueberry.ui.qt/CMakeLists.txt @@ -1,30 +1,42 @@ project(org_blueberry_ui_qt) set(PLUGIN_exported_include_suffixes src src/actions src/application src/commands src/guitk src/handlers src/intro src/model src/presentations src/services src/testing src/tweaklets src/util ) if(MITK_USE_Qt5) set(PLUGIN_package_depends PUBLIC Qt5|Widgets+PrintSupport+Svg ) endif() mitk_create_plugin( EXPORT_DIRECTIVE BERRY_UI_QT EXPORTED_INCLUDE_SUFFIXES ${PLUGIN_exported_include_suffixes} MODULE_DEPENDS PUBLIC MitkCore PACKAGE_DEPENDS ${PLUGIN_package_depends} ) + +if (TARGET ${PLUGIN_TARGET} AND MSVC) + #[[ Compiler warnings/errors because of QList on Visual Studio 2022 version 17.8: + + 'stdext::checked_array_iterator': warning STL4043: stdext::checked_array_iterator, + stdext::unchecked_array_iterator, and related factory functions are non-Standard extensions + and will be removed in the future. std::span (since C++20) and gsl::span can be used instead. + You can define _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING or _SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS + to suppress this warning. + ]] + target_compile_definitions(${PLUGIN_TARGET} PRIVATE _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING) +endif() diff --git a/Plugins/org.mitk.gui.qt.fit.demo/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.fit.demo/manifest_headers.cmake index 03480fff78..c183994ae3 100644 --- a/Plugins/org.mitk.gui.qt.fit.demo/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.fit.demo/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "Fit Generator Demo") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "r.floca@dkfz.de") +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.fit.genericfitting/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.fit.genericfitting/manifest_headers.cmake index 670a894787..2620d75de9 100644 --- a/Plugins/org.mitk.gui.qt.fit.genericfitting/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.fit.genericfitting/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "Modelfit Generic Data Fitting") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "r.floca@dkfz.de") +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.fit.inspector/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.fit.inspector/manifest_headers.cmake index df38cd0082..7c5a41373c 100644 --- a/Plugins/org.mitk.gui.qt.fit.inspector/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.fit.inspector/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "Modelfit Inspector Plugin") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "r.floca@dkfz.de") +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp index 6c3a9535a4..ac789e0d98 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp +++ b/Plugins/org.mitk.gui.qt.matchpoint.algorithm.control/src/internal/QmitkMatchPoint.cpp @@ -1,878 +1,879 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "org_mitk_gui_qt_matchpoint_algorithmcontrol_Activator.h" // Blueberry #include #include #include #include // Mitk #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include "QmitkMatchPoint.h" #include #include // Qt #include #include #include #include #include // MatchPoint #include #include #include #include #include #include #include #include #include const std::string QmitkMatchPoint::VIEW_ID = "org.mitk.views.matchpoint.algorithm.control"; QmitkMatchPoint::QmitkMatchPoint() : m_Parent(nullptr), m_LoadedDLLHandle(nullptr), m_LoadedAlgorithm(nullptr) { m_CanLoadAlgorithm = false; m_ValidInputs = false; m_Working = false; m_spSelectedTargetData = nullptr; m_spSelectedMovingData = nullptr; m_spSelectedTargetMaskData = nullptr; m_spSelectedMovingMaskData = nullptr; } QmitkMatchPoint::~QmitkMatchPoint() { // remove selection service berry::ISelectionService* s = this->GetSite()->GetWorkbenchWindow()->GetSelectionService(); if (s) { s->RemoveSelectionListener(m_AlgorithmSelectionListener.data()); } } void QmitkMatchPoint::SetFocus() { } void QmitkMatchPoint::CreateConnections() { connect(m_Controls.targetNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); connect(m_Controls.movingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); connect(m_Controls.targetMaskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); connect(m_Controls.movingMaskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPoint::OnNodeSelectionChanged); // ------ // Tab 1 - Shared library loading interface // ------ connect(m_Controls.m_pbLoadSelected, SIGNAL(clicked()), this, SLOT(OnLoadAlgorithmButtonPushed())); // ----- // Tab 2 - Execution // ----- connect(m_Controls.m_pbStartReg, SIGNAL(clicked()), this, SLOT(OnStartRegBtnPushed())); connect(m_Controls.m_pbStopReg, SIGNAL(clicked()), this, SLOT(OnStopRegBtnPushed())); connect(m_Controls.m_pbSaveLog, SIGNAL(clicked()), this, SLOT(OnSaveLogBtnPushed())); } const map::deployment::DLLInfo* QmitkMatchPoint::GetSelectedAlgorithmDLL() const { return m_SelectedAlgorithmInfo; } void QmitkMatchPoint::OnSelectedAlgorithmChanged() { std::stringstream descriptionString; ::map::deployment::DLLInfo::ConstPointer currentItemInfo = GetSelectedAlgorithmDLL(); if (!currentItemInfo) { Error(QStringLiteral("No valid algorithm is selected. ABORTING.")); return; } m_Controls.m_teAlgorithmDetails->updateInfo(currentItemInfo); m_Controls.m_lbSelectedAlgorithm->setText(QString::fromStdString( currentItemInfo->getAlgorithmUID().getName())); // enable loading m_CanLoadAlgorithm = true; this->AdaptFolderGUIElements(); } void QmitkMatchPoint::OnLoadAlgorithmButtonPushed() { map::deployment::DLLInfo::ConstPointer dllInfo = GetSelectedAlgorithmDLL(); if (!dllInfo) { Error(QStringLiteral("No valid algorithm is selected. Cannot load algorithm. ABORTING.")); return; } ::map::deployment::DLLHandle::Pointer tempDLLHandle = ::map::deployment::openDeploymentDLL( dllInfo->getLibraryFilePath()); ::map::algorithm::RegistrationAlgorithmBase::Pointer tempAlgorithm = ::map::deployment::getRegistrationAlgorithm(tempDLLHandle); if (tempAlgorithm.IsNull()) { Error(QStringLiteral("Error. Cannot load selected algorithm.")); return; } this->m_LoadedAlgorithm = tempAlgorithm; this->m_LoadedDLLHandle = tempDLLHandle; this->m_Controls.m_AlgoConfigurator->setAlgorithm(m_LoadedAlgorithm); typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<3, 3> MaskRegInterface; const MaskRegInterface* pMaskReg = dynamic_cast (m_LoadedAlgorithm.GetPointer()); if (!pMaskReg) { m_spSelectedTargetMaskData = nullptr; m_spSelectedTargetMaskNode = nullptr; m_spSelectedMovingMaskData = nullptr; m_spSelectedMovingMaskNode = nullptr; m_Controls.targetMaskNodeSelector->SetCurrentSelection(QmitkAbstractNodeSelectionWidget::NodeList()); m_Controls.movingMaskNodeSelector->SetCurrentSelection(QmitkAbstractNodeSelectionWidget::NodeList()); } this->AdaptFolderGUIElements(); this->ConfigureNodeSelectors(); this->CheckInputs(); this->ConfigureRegistrationControls(); this->ConfigureProgressInfos(); this->m_Controls.m_tabs->setCurrentIndex(1); this->m_Controls.m_teLog->clear(); } void QmitkMatchPoint::Error(QString msg) { mitk::StatusBar::GetInstance()->DisplayErrorText(msg.toLatin1()); MITK_ERROR << msg.toStdString().c_str(); m_Controls.m_teLog->append(QStringLiteral("") + msg + QStringLiteral("")); } void QmitkMatchPoint::AdaptFolderGUIElements() { m_Controls.m_pbLoadSelected->setEnabled(m_CanLoadAlgorithm); } void QmitkMatchPoint::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Parent = parent; m_Controls.m_tabs->setCurrentIndex(0); m_Controls.movingNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.movingNodeSelector->SetSelectionIsOptional(false); m_Controls.targetNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.targetNodeSelector->SetSelectionIsOptional(false); m_Controls.movingMaskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.movingMaskNodeSelector->SetSelectionIsOptional(true); m_Controls.targetMaskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.targetMaskNodeSelector->SetSelectionIsOptional(true); m_AlgorithmSelectionListener.reset(new berry::SelectionChangedAdapter(this, &QmitkMatchPoint::OnAlgorithmSelectionChanged)); // register selection listener GetSite()->GetWorkbenchWindow()->GetSelectionService()->AddSelectionListener( m_AlgorithmSelectionListener.data()); this->CreateConnections(); this->AdaptFolderGUIElements(); this->CheckInputs(); this->ConfigureProgressInfos(); this->ConfigureRegistrationControls(); this->ConfigureNodeSelectors(); berry::ISelection::ConstPointer selection = GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection("org.mitk.views.matchpoint.algorithm.browser"); this->UpdateAlgorithmSelection(selection); } mitk::Image::Pointer ExtractFirstFrame(const mitk::Image* dynamicImage) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(dynamicImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } bool QmitkMatchPoint::CheckInputs() { if (m_LoadedAlgorithm.IsNull()) { m_spSelectedMovingNode = nullptr; m_spSelectedMovingData = nullptr; m_spSelectedTargetNode = nullptr; m_spSelectedTargetData = nullptr; m_spSelectedMovingMaskNode = nullptr; m_spSelectedMovingMaskData = nullptr; m_spSelectedTargetMaskNode = nullptr; m_spSelectedTargetMaskData = nullptr; } else { if (m_Controls.movingNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedMovingNode = nullptr; m_spSelectedMovingData = nullptr; } else { m_spSelectedMovingNode = m_Controls.movingNodeSelector->GetSelectedNode(); m_spSelectedMovingData = m_spSelectedMovingNode->GetData(); auto movingImage = dynamic_cast(m_spSelectedMovingNode->GetData()); if (movingImage && movingImage->GetDimension() - 1 == m_LoadedAlgorithm->getMovingDimensions() && movingImage->GetTimeSteps() > 1) { m_spSelectedMovingData = ExtractFirstFrame(movingImage).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected moving image has multiple time steps. First time step is used as moving image.")); } } if (m_Controls.targetNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedTargetNode = nullptr; m_spSelectedTargetData = nullptr; } else { m_spSelectedTargetNode = m_Controls.targetNodeSelector->GetSelectedNode(); m_spSelectedTargetData = m_spSelectedTargetNode->GetData(); auto targetImage = dynamic_cast(m_spSelectedTargetNode->GetData()); if (targetImage && targetImage->GetDimension() - 1 == m_LoadedAlgorithm->getTargetDimensions() && targetImage->GetTimeSteps() > 1) { m_spSelectedTargetData = ExtractFirstFrame(targetImage).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected target image has multiple time steps. First time step is used as target image.")); } } if (m_Controls.movingMaskNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedMovingMaskNode = nullptr; m_spSelectedMovingMaskData = nullptr; } else { m_spSelectedMovingMaskNode = m_Controls.movingMaskNodeSelector->GetSelectedNode(); m_spSelectedMovingMaskData = dynamic_cast(m_spSelectedMovingMaskNode->GetData()); if (m_spSelectedMovingMaskData->GetDimension() - 1 == m_LoadedAlgorithm->getMovingDimensions() && m_spSelectedMovingMaskData->GetTimeSteps() > 1) { m_spSelectedMovingMaskData = ExtractFirstFrame(m_spSelectedMovingMaskData).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected moving mask has multiple time steps. First time step is used as moving mask.")); } } if (m_Controls.targetMaskNodeSelector->GetSelectedNode().IsNull()) { m_spSelectedTargetMaskNode = nullptr; m_spSelectedTargetMaskData = nullptr; } else { m_spSelectedTargetMaskNode = m_Controls.targetMaskNodeSelector->GetSelectedNode(); m_spSelectedTargetMaskData = dynamic_cast(m_spSelectedTargetMaskNode->GetData()); if (m_spSelectedTargetMaskData->GetDimension() - 1 == m_LoadedAlgorithm->getTargetDimensions() && m_spSelectedTargetMaskData->GetTimeSteps() > 1) { m_spSelectedTargetMaskData = ExtractFirstFrame(m_spSelectedTargetMaskData).GetPointer(); m_Controls.m_teLog->append( QStringLiteral("Selected target mask has multiple time steps. First time step is used as target mask.")); } } } m_ValidInputs = m_spSelectedMovingData.IsNotNull() && m_spSelectedTargetData.IsNotNull(); return m_ValidInputs; } std::string QmitkMatchPoint::GetInputNodeDisplayName(const mitk::DataNode* node) const { std::string result = "UNDEFINED/nullptr"; if (node) { result = node->GetName(); const mitk::PointSet* pointSet = dynamic_cast(node->GetData()); if (pointSet) { mitk::DataStorage::SetOfObjects::ConstPointer sources = this->GetDataStorage()->GetSources(node); if (sources.IsNotNull() && sources->Size() > 0) { result = result + " (" + sources->GetElement(0)->GetName() + ")"; } } } return result; } mitk::DataStorage::SetOfObjects::Pointer QmitkMatchPoint::GetRegNodes() const { mitk::DataStorage::SetOfObjects::ConstPointer nodes = this->GetDataStorage()->GetAll(); mitk::DataStorage::SetOfObjects::Pointer result = mitk::DataStorage::SetOfObjects::New(); for (mitk::DataStorage::SetOfObjects::const_iterator pos = nodes->begin(); pos != nodes->end(); ++pos) { if (mitk::MITKRegistrationHelper::IsRegNode(*pos)) { result->push_back(*pos); } } return result; } std::string QmitkMatchPoint::GetDefaultRegJobName() const { mitk::DataStorage::SetOfObjects::ConstPointer nodes = this->GetRegNodes().GetPointer(); mitk::DataStorage::SetOfObjects::ElementIdentifier estimatedIndex = nodes->Size(); bool isUnique = false; std::string result = "Unnamed Reg"; while (!isUnique) { ++estimatedIndex; result = "Reg #" +::map::core::convert::toStr(estimatedIndex); isUnique = this->GetDataStorage()->GetNamedNode(result) == nullptr; } return result; } void QmitkMatchPoint::ConfigureRegistrationControls() { m_Controls.m_tabSelection->setEnabled(!m_Working); m_Controls.m_leRegJobName->setEnabled(!m_Working); m_Controls.groupMasks->setEnabled(!m_Working); m_Controls.m_pbStartReg->setEnabled(false); m_Controls.m_pbStopReg->setEnabled(false); m_Controls.m_pbStopReg->setVisible(false); if (m_LoadedAlgorithm.IsNotNull()) { m_Controls.m_tabSettings->setEnabled(!m_Working); m_Controls.m_tabExecution->setEnabled(true); m_Controls.m_pbStartReg->setEnabled(m_ValidInputs && !m_Working); m_Controls.m_leRegJobName->setEnabled(!m_Working); m_Controls.m_checkMapEntity->setEnabled(!m_Working); m_Controls.targetNodeSelector->setEnabled(!m_Working); m_Controls.movingNodeSelector->setEnabled(!m_Working); m_Controls.targetMaskNodeSelector->setEnabled(!m_Working); m_Controls.movingMaskNodeSelector->setEnabled(!m_Working); const IStoppableAlgorithm* pIterativ = dynamic_cast (m_LoadedAlgorithm.GetPointer()); if (pIterativ) { m_Controls.m_pbStopReg->setVisible(pIterativ->isStoppable()); } typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<3, 3> MaskRegInterface; const MaskRegInterface* pMaskReg = dynamic_cast (m_LoadedAlgorithm.GetPointer()); m_Controls.groupMasks->setVisible(pMaskReg != nullptr); //if the stop button is set to visible and the algorithm is working -> //then the algorithm is stoppable, thus enable the button. m_Controls.m_pbStopReg->setEnabled(m_Controls.m_pbStopReg->isVisible() && m_Working); this->m_Controls.m_lbLoadedAlgorithmName->setText( QString::fromStdString(m_LoadedAlgorithm->getUID()->toStr())); } else { m_Controls.m_tabSettings->setEnabled(false); m_Controls.m_tabExecution->setEnabled(false); this->m_Controls.m_lbLoadedAlgorithmName->setText( QStringLiteral("no algorithm loaded!")); m_Controls.groupMasks->setVisible(false); } if (!m_Working) { this->m_Controls.m_leRegJobName->setText(QString::fromStdString(this->GetDefaultRegJobName())); } } void QmitkMatchPoint::ConfigureNodeSelectors() { auto isImage = mitk::MITKRegistrationHelper::ImageNodePredicate(); auto isPointSet = mitk::MITKRegistrationHelper::PointSetNodePredicate(); auto isMask = mitk::MITKRegistrationHelper::MaskNodePredicate(); mitk::NodePredicateBase::Pointer dimensionPredicate = mitk::NodePredicateOr::New(mitk::NodePredicateDimension::New(3), mitk::NodePredicateDimension::New(4)).GetPointer(); m_Controls.movingNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.targetNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.movingMaskNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.targetMaskNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); if (m_LoadedAlgorithm.IsNotNull()) { mitk::NodePredicateBase::ConstPointer dataPredicate; if (m_LoadedAlgorithm->getMovingDimensions() == 2) { dimensionPredicate = mitk::NodePredicateDimension::New(2); } if (mitk::MAPAlgorithmHelper::HasImageAlgorithmInterface(m_LoadedAlgorithm)) { dataPredicate = mitk::NodePredicateAnd::New(isImage, dimensionPredicate); m_Controls.movingNodeSelector->SetInvalidInfo("Select valid moving image."); m_Controls.movingNodeSelector->SetPopUpTitel("Select moving image."); m_Controls.movingNodeSelector->SetPopUpHint("Select the moving image that should be registered onto the target image."); m_Controls.targetNodeSelector->SetInvalidInfo("Select valid target image."); m_Controls.targetNodeSelector->SetPopUpTitel("Select target image."); m_Controls.targetNodeSelector->SetPopUpHint("Select the target image that should be used as reference for the registration."); } if (mitk::MAPAlgorithmHelper::HasPointSetAlgorithmInterface(m_LoadedAlgorithm)) { if (dataPredicate.IsNull()) { dataPredicate = isPointSet; m_Controls.movingNodeSelector->SetInvalidInfo("Select valid moving point set."); m_Controls.movingNodeSelector->SetPopUpTitel("Select moving point set."); m_Controls.movingNodeSelector->SetPopUpHint("Select the moving point set that should be registered onto the target point set."); m_Controls.targetNodeSelector->SetInvalidInfo("Select valid target point set."); m_Controls.targetNodeSelector->SetPopUpTitel("Select target point set."); m_Controls.targetNodeSelector->SetPopUpHint("Select the target point set that should be used as reference for the registration."); } else { dataPredicate = mitk::NodePredicateOr::New(dataPredicate, isPointSet); m_Controls.movingNodeSelector->SetInvalidInfo("Select valid moving data."); m_Controls.movingNodeSelector->SetPopUpTitel("Select moving data."); m_Controls.movingNodeSelector->SetPopUpHint("Select the moving data that should be registered onto the target data. The algorithm supports images as well as point sets."); m_Controls.targetNodeSelector->SetInvalidInfo("Select valid target data."); m_Controls.targetNodeSelector->SetPopUpTitel("Select target data."); m_Controls.targetNodeSelector->SetPopUpHint("Select the target data that should be used as reference for the registration. The algorithm supports images as well as point sets."); } } mitk::NodePredicateBase::ConstPointer nodePredicate = dataPredicate; m_Controls.movingNodeSelector->SetNodePredicate(nodePredicate); m_Controls.targetNodeSelector->SetNodePredicate(nodePredicate); nodePredicate = mitk::NodePredicateAnd::New(isMask, dimensionPredicate); m_Controls.movingMaskNodeSelector->SetEmptyInfo("Select moving mask. (optional)"); m_Controls.movingMaskNodeSelector->SetPopUpTitel("Select moving mask"); m_Controls.movingMaskNodeSelector->SetPopUpHint("Select a segmentation that serves as moving mask for the registration."); m_Controls.targetMaskNodeSelector->SetEmptyInfo("Select target mask. (optional)"); m_Controls.targetMaskNodeSelector->SetPopUpTitel("Select target mask"); m_Controls.targetMaskNodeSelector->SetPopUpHint("Select a segmentation that serves as target mask for the registration."); m_Controls.movingMaskNodeSelector->SetNodePredicate(nodePredicate); m_Controls.targetMaskNodeSelector->SetNodePredicate(nodePredicate); } } void QmitkMatchPoint::ConfigureProgressInfos() { const IIterativeAlgorithm* pIterative = dynamic_cast (m_LoadedAlgorithm.GetPointer()); const IMultiResAlgorithm* pMultiRes = dynamic_cast (m_LoadedAlgorithm.GetPointer()); m_Controls.m_progBarIteration->setVisible(pIterative); m_Controls.m_lbProgBarIteration->setVisible(pIterative); if (pIterative) { QString format = "%p% (%v/%m)"; if (!pIterative->hasMaxIterationCount()) { format = "%v"; m_Controls.m_progBarIteration->setMaximum(0); } else { m_Controls.m_progBarIteration->setMaximum(pIterative->getMaxIterations()); } m_Controls.m_progBarIteration->setFormat(format); } m_Controls.m_progBarLevel->setVisible(pMultiRes); m_Controls.m_lbProgBarLevel->setVisible(pMultiRes); if (pMultiRes) { m_Controls.m_progBarLevel->setMaximum(pMultiRes->getResolutionLevels()); } else { m_Controls.m_progBarLevel->setMaximum(1); } m_Controls.m_progBarIteration->reset(); m_Controls.m_progBarLevel->reset(); } void QmitkMatchPoint::OnNodeSelectionChanged(QList /*nodes*/) { if (!m_Working) { CheckInputs(); ConfigureRegistrationControls(); } } void QmitkMatchPoint::OnStartRegBtnPushed() { this->m_Working = true; //////////////////////////////// //configure GUI this->ConfigureProgressInfos(); m_Controls.m_progBarIteration->reset(); m_Controls.m_progBarLevel->reset(); this->ConfigureRegistrationControls(); if (m_Controls.m_checkClearLog->checkState() == Qt::Checked) { this->m_Controls.m_teLog->clear(); } ///////////////////////// //create job and put it into the thread pool QmitkRegistrationJob* pJob = new QmitkRegistrationJob(m_LoadedAlgorithm); pJob->setAutoDelete(true); pJob->m_spTargetData = m_spSelectedTargetData; pJob->m_spMovingData = m_spSelectedMovingData; pJob->m_TargetDataUID = mitk::EnsureUID(this->m_spSelectedTargetNode->GetData()); pJob->m_MovingDataUID = mitk::EnsureUID(this->m_spSelectedMovingNode->GetData()); if (m_spSelectedTargetMaskData.IsNotNull()) { pJob->m_spTargetMask = m_spSelectedTargetMaskData; pJob->m_TargetMaskDataUID = mitk::EnsureUID(this->m_spSelectedTargetMaskNode->GetData()); } if (m_spSelectedMovingMaskData.IsNotNull()) { pJob->m_spMovingMask = m_spSelectedMovingMaskData; pJob->m_MovingMaskDataUID = mitk::EnsureUID(this->m_spSelectedMovingMaskNode->GetData()); } pJob->m_JobName = m_Controls.m_leRegJobName->text().toStdString(); pJob->m_StoreReg = true; connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnRegJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnRegJobFinished())); connect(pJob, SIGNAL(RegResultIsAvailable(mitk::MAPRegistrationWrapper::Pointer, const QmitkRegistrationJob*)), this, SLOT(OnRegResultIsAvailable(mitk::MAPRegistrationWrapper::Pointer, const QmitkRegistrationJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(AlgorithmInfo(QString)), this, SLOT(OnAlgorithmInfo(QString))); connect(pJob, SIGNAL(AlgorithmStatusChanged(QString)), this, SLOT(OnAlgorithmStatusChanged(QString))); connect(pJob, SIGNAL(AlgorithmIterated(QString, bool, unsigned long)), this, SLOT(OnAlgorithmIterated(QString, bool, unsigned long))); connect(pJob, SIGNAL(LevelChanged(QString, bool, unsigned long)), this, SLOT(OnLevelChanged(QString, bool, unsigned long))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } void QmitkMatchPoint::OnStopRegBtnPushed() { if (m_LoadedAlgorithm.IsNotNull()) { IStoppableAlgorithm* pIterativ = dynamic_cast(m_LoadedAlgorithm.GetPointer()); if (pIterativ && pIterativ->isStoppable()) { if (pIterativ->stopAlgorithm()) { } else { } m_Controls.m_pbStopReg->setEnabled(false); } else { } } } void QmitkMatchPoint::OnSaveLogBtnPushed() { QDateTime currentTime = QDateTime::currentDateTime(); QString fileName = tr("registration_log_") + currentTime.toString(tr("yyyy-MM-dd_hh-mm-ss")) + tr(".txt"); fileName = QFileDialog::getSaveFileName(nullptr, tr("Save registration log"), fileName, tr("Text files (*.txt)")); if (fileName.isEmpty()) { QMessageBox::critical(nullptr, tr("No file selected!"), tr("Cannot save registration log file. Please selected a file.")); } else { std::ofstream file; std::ios_base::openmode iOpenFlag = std::ios_base::out | std::ios_base::trunc; file.open(fileName.toStdString().c_str(), iOpenFlag); if (!file.is_open()) { mitkThrow() << "Cannot open or create specified file to save. File path: " << fileName.toStdString(); } file << this->m_Controls.m_teLog->toPlainText().toStdString() << std::endl; file.close(); } } void QmitkMatchPoint::OnRegJobError(QString err) { Error(err); } void QmitkMatchPoint::OnRegJobFinished() { this->m_Working = false; auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) renderWindowPart->RequestUpdate(); this->CheckInputs(); this->ConfigureRegistrationControls(); this->ConfigureProgressInfos(); } void QmitkMatchPoint::OnRegResultIsAvailable(mitk::MAPRegistrationWrapper::Pointer spResultRegistration, const QmitkRegistrationJob* pRegJob) { mitk::DataNode::Pointer spResultRegistrationNode = mitk::generateRegistrationResultNode( pRegJob->m_JobName, spResultRegistration, pRegJob->GetLoadedAlgorithm()->getUID()->toStr(), pRegJob->m_MovingDataUID, pRegJob->m_TargetDataUID); if (pRegJob->m_StoreReg) { m_Controls.m_teLog->append( QStringLiteral(" Storing registration object in data manager ... ")); this->GetDataStorage()->Add(spResultRegistrationNode); auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) renderWindowPart->RequestUpdate(); } if (m_Controls.m_checkMapEntity->checkState() == Qt::Checked) { QmitkMappingJob* pMapJob = new QmitkMappingJob(); pMapJob->setAutoDelete(true); pMapJob->m_spInputData = pRegJob->m_spMovingData; pMapJob->m_InputDataUID = pRegJob->m_MovingDataUID; pMapJob->m_spRegNode = spResultRegistrationNode; pMapJob->m_doGeometryRefinement = false; pMapJob->m_spRefGeometry = pRegJob->m_spTargetData->GetGeometry()->Clone().GetPointer(); pMapJob->m_MappedName = pRegJob->m_JobName + std::string(" mapped moving data"); pMapJob->m_allowUndefPixels = true; pMapJob->m_paddingValue = 100; pMapJob->m_allowUnregPixels = true; pMapJob->m_errorValue = 200; pMapJob->m_InterpolatorLabel = "Linear Interpolation"; pMapJob->m_InterpolatorType = mitk::ImageMappingInterpolator::Linear; connect(pMapJob, SIGNAL(Error(QString)), this, SLOT(OnMapJobError(QString))); connect(pMapJob, SIGNAL(MapResultIsAvailable(mitk::BaseData::Pointer, const QmitkMappingJob*)), this, SLOT(OnMapResultIsAvailable(mitk::BaseData::Pointer, const QmitkMappingJob*)), Qt::BlockingQueuedConnection); connect(pMapJob, SIGNAL(AlgorithmInfo(QString)), this, SLOT(OnAlgorithmInfo(QString))); m_Controls.m_teLog->append( QStringLiteral("Started mapping input data...")); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pMapJob); } } void QmitkMatchPoint::OnMapJobError(QString err) { Error(err); } void QmitkMatchPoint::OnMapResultIsAvailable(mitk::BaseData::Pointer spMappedData, const QmitkMappingJob* job) { m_Controls.m_teLog->append(QStringLiteral("Mapped entity stored. Name: ") + QString::fromStdString(job->m_MappedName) + QStringLiteral("")); mitk::DataNode::Pointer spMappedNode = mitk::generateMappedResultNode(job->m_MappedName, spMappedData, job->GetRegistration()->getRegistrationUID(), job->m_InputDataUID, job->m_doGeometryRefinement, job->m_InterpolatorLabel); this->GetDataStorage()->Add(spMappedNode); auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) renderWindowPart->RequestUpdate(); } void QmitkMatchPoint::OnAlgorithmIterated(QString info, bool hasIterationCount, unsigned long currentIteration) { if (hasIterationCount) { m_Controls.m_progBarIteration->setValue(currentIteration); } m_Controls.m_teLog->append(info); } void QmitkMatchPoint::OnLevelChanged(QString info, bool hasLevelCount, unsigned long currentLevel) { if (hasLevelCount) { m_Controls.m_progBarLevel->setValue(currentLevel); } m_Controls.m_teLog->append(QStringLiteral("") + info + QStringLiteral("")); } void QmitkMatchPoint::OnAlgorithmStatusChanged(QString info) { m_Controls.m_teLog->append(QStringLiteral("") + info + QStringLiteral(" ")); } void QmitkMatchPoint::OnAlgorithmInfo(QString info) { m_Controls.m_teLog->append(QStringLiteral("") + info + QStringLiteral("")); } void QmitkMatchPoint::OnAlgorithmSelectionChanged(const berry::IWorkbenchPart::Pointer& sourcepart, const berry::ISelection::ConstPointer& selection) { // check for null selection if (selection.IsNull()) { return; } if (sourcepart != this) { UpdateAlgorithmSelection(selection); } } void QmitkMatchPoint::UpdateAlgorithmSelection(berry::ISelection::ConstPointer selection) { mitk::MAPAlgorithmInfoSelection::ConstPointer currentSelection = selection.Cast(); if (currentSelection) { mitk::MAPAlgorithmInfoSelection::AlgorithmInfoVectorType infoVector = currentSelection->GetSelectedAlgorithmInfo(); if (!infoVector.empty()) { // only the first selection is of interest, the rest will be skipped. this->m_SelectedAlgorithmInfo = infoVector[0]; } + + this->OnSelectedAlgorithmChanged(); } - this->OnSelectedAlgorithmChanged(); } diff --git a/Plugins/org.mitk.gui.qt.matchpoint.framereg/src/internal/QmitkMatchPointFrameCorrection.cpp b/Plugins/org.mitk.gui.qt.matchpoint.framereg/src/internal/QmitkMatchPointFrameCorrection.cpp index c94f8f78ad..3f723975ff 100644 --- a/Plugins/org.mitk.gui.qt.matchpoint.framereg/src/internal/QmitkMatchPointFrameCorrection.cpp +++ b/Plugins/org.mitk.gui.qt.matchpoint.framereg/src/internal/QmitkMatchPointFrameCorrection.cpp @@ -1,724 +1,724 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "org_mitk_gui_qt_matchpoint_framereg_Activator.h" // Blueberry #include #include #include #include // Mitk #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include "QmitkMatchPointFrameCorrection.h" #include #include // Qt #include #include #include #include #include // MatchPoint #include #include #include #include #include #include #include #include #include const std::string QmitkMatchPointFrameCorrection::VIEW_ID = "org.mitk.views.matchpoint.algorithm.framereg"; QmitkMatchPointFrameCorrection::QmitkMatchPointFrameCorrection() : m_Parent(nullptr), m_LoadedDLLHandle(nullptr), m_LoadedAlgorithm(nullptr), m_CanLoadAlgorithm(false), m_Working(false) { m_spSelectedTargetData = nullptr; m_spSelectedTargetMaskData = nullptr; } QmitkMatchPointFrameCorrection::~QmitkMatchPointFrameCorrection() { // remove selection service berry::ISelectionService* s = this->GetSite()->GetWorkbenchWindow()->GetSelectionService(); if (s) { s->RemoveSelectionListener(m_AlgorithmSelectionListener.data()); } } void QmitkMatchPointFrameCorrection::SetFocus() { } void QmitkMatchPointFrameCorrection::CreateConnections() { connect(m_Controls.imageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointFrameCorrection::OnNodeSelectionChanged); connect(m_Controls.maskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMatchPointFrameCorrection::OnNodeSelectionChanged); // ------ // Tab 1 - Shared library loading interface // ------ connect(m_Controls.m_pbLoadSelected, SIGNAL(clicked()), this, SLOT(OnLoadAlgorithmButtonPushed())); // ----- // Tab 2 - Execution // ----- connect(m_Controls.m_pbStartReg, SIGNAL(clicked()), this, SLOT(OnStartRegBtnPushed())); connect(m_Controls.m_pbSaveLog, SIGNAL(clicked()), this, SLOT(OnSaveLogBtnPushed())); // ----- // Tab 4 - Frames // ----- connect(m_Controls.m_btnFrameSelAll, SIGNAL(clicked()), this, SLOT(OnFramesSelectAllPushed())); connect(m_Controls.m_btnFrameDeSelAll, SIGNAL(clicked()), this, SLOT(OnFramesDeSelectAllPushed())); connect(m_Controls.m_btnFrameInvert, SIGNAL(clicked()), this, SLOT(OnFramesInvertPushed())); } const map::deployment::DLLInfo* QmitkMatchPointFrameCorrection::GetSelectedAlgorithmDLL() const { return m_SelectedAlgorithmInfo; } void QmitkMatchPointFrameCorrection::OnSelectedAlgorithmChanged() { std::stringstream descriptionString; ::map::deployment::DLLInfo::ConstPointer currentItemInfo = GetSelectedAlgorithmDLL(); if (!currentItemInfo) { return; } m_Controls.m_teAlgorithmDetails->updateInfo(currentItemInfo); m_Controls.m_lbSelectedAlgorithm->setText(QString::fromStdString( currentItemInfo->getAlgorithmUID().getName())); // enable loading m_CanLoadAlgorithm = true; this->AdaptFolderGUIElements(); } void QmitkMatchPointFrameCorrection::OnLoadAlgorithmButtonPushed() { map::deployment::DLLInfo::ConstPointer dllInfo = GetSelectedAlgorithmDLL(); if (!dllInfo) { Error(QStringLiteral("No valid algorithm is selected. Cannot load algorithm. ABORTING.")); return; } ::map::deployment::DLLHandle::Pointer tempDLLHandle = ::map::deployment::openDeploymentDLL( dllInfo->getLibraryFilePath()); ::map::algorithm::RegistrationAlgorithmBase::Pointer tempAlgorithm = ::map::deployment::getRegistrationAlgorithm(tempDLLHandle); if (tempAlgorithm.IsNull()) { Error(QStringLiteral("Error. Cannot load selected algorithm.")); return; } this->m_LoadedAlgorithm = tempAlgorithm; this->m_LoadedDLLHandle = tempDLLHandle; this->m_Controls.m_AlgoConfigurator->setAlgorithm(m_LoadedAlgorithm); this->AdaptFolderGUIElements(); this->ConfigureNodeSelectorPredicates(); this->CheckInputs(); this->ConfigureRegistrationControls(); this->ConfigureProgressInfos(); this->m_Controls.m_tabs->setCurrentIndex(1); } void QmitkMatchPointFrameCorrection::Error(QString msg) { mitk::StatusBar::GetInstance()->DisplayErrorText(msg.toLatin1()); MITK_ERROR << msg.toStdString().c_str(); m_Controls.m_teLog->append(QString("") + msg + QString("")); } void QmitkMatchPointFrameCorrection::AdaptFolderGUIElements() { m_Controls.m_pbLoadSelected->setEnabled(m_CanLoadAlgorithm); } void QmitkMatchPointFrameCorrection::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Parent = parent; this->m_Controls.imageNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.imageNodeSelector->SetSelectionIsOptional(false); this->m_Controls.maskNodeSelector->SetDataStorage(this->GetDataStorage()); this->m_Controls.maskNodeSelector->SetSelectionIsOptional(true); this->m_Controls.imageNodeSelector->SetInvalidInfo("Select dymamic image."); this->m_Controls.imageNodeSelector->SetPopUpTitel("Select dynamic image."); this->m_Controls.imageNodeSelector->SetPopUpHint("Select a dynamic image (time resolved) that should be frame corrected."); this->m_Controls.maskNodeSelector->SetInvalidInfo("Select target mask."); this->m_Controls.maskNodeSelector->SetPopUpTitel("Select target mask."); this->m_Controls.maskNodeSelector->SetPopUpHint("Select a target mask (mask of the target/first frame)."); m_Controls.m_tabs->setCurrentIndex(0); m_Controls.m_mapperSettings->AllowSampling(false); m_AlgorithmSelectionListener.reset(new berry::SelectionChangedAdapter(this, &QmitkMatchPointFrameCorrection::OnAlgorithmSelectionChanged)); // register selection listener GetSite()->GetWorkbenchWindow()->GetSelectionService()->AddSelectionListener( m_AlgorithmSelectionListener.data()); this->ConfigureNodeSelectorPredicates(); this->CreateConnections(); this->AdaptFolderGUIElements(); this->CheckInputs(); this->ConfigureProgressInfos(); this->ConfigureRegistrationControls(); berry::ISelection::ConstPointer selection = GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection("org.mitk.views.matchpoint.algorithm.browser"); this->UpdateAlgorithmSelection(selection); } void QmitkMatchPointFrameCorrection::ConfigureNodeSelectorPredicates() { auto isImage = mitk::MITKRegistrationHelper::ImageNodePredicate(); mitk::NodePredicateBase::Pointer maskDimensionPredicate = mitk::NodePredicateOr::New(mitk::NodePredicateDimension::New(3), mitk::NodePredicateDimension::New(4)).GetPointer(); mitk::NodePredicateDimension::Pointer imageDimensionPredicate = mitk::NodePredicateDimension::New(4); m_Controls.imageNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.maskNodeSelector->setEnabled(m_LoadedAlgorithm.IsNotNull()); m_Controls.imageNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New(isImage, imageDimensionPredicate)); mitk::NodePredicateAnd::Pointer maskPredicate = mitk::NodePredicateAnd::New(mitk::MITKRegistrationHelper::MaskNodePredicate(), maskDimensionPredicate); if (m_spSelectedTargetData != nullptr) { auto hasSameGeometry = mitk::NodePredicateGeometry::New(m_spSelectedTargetData->GetGeometry()); hasSameGeometry->SetCheckPrecision(1e-10); maskPredicate = mitk::NodePredicateAnd::New(maskPredicate, hasSameGeometry); } m_Controls.maskNodeSelector->SetNodePredicate(maskPredicate); } void QmitkMatchPointFrameCorrection::CheckInputs() { mitk::DataNode::Pointer oldTargetNode = m_spSelectedTargetNode; m_spSelectedTargetNode = nullptr; m_spSelectedTargetData = nullptr; m_spSelectedTargetMaskNode = nullptr; m_spSelectedTargetMaskData = nullptr; if (m_LoadedAlgorithm.IsNull()) { m_Controls.m_lbLoadedAlgorithmName->setText( QStringLiteral("No algorithm selected!")); } else { m_spSelectedTargetNode = m_Controls.imageNodeSelector->GetSelectedNode(); if (m_spSelectedTargetNode.IsNotNull()) { m_spSelectedTargetData = m_spSelectedTargetNode->GetData(); } m_spSelectedTargetMaskNode = m_Controls.maskNodeSelector->GetSelectedNode(); if (m_spSelectedTargetMaskNode.IsNotNull()) { m_spSelectedTargetMaskData = dynamic_cast(m_spSelectedTargetMaskNode->GetData()); } } if (oldTargetNode != m_spSelectedTargetNode) { ConfigureFrameList(); } } mitk::DataStorage::SetOfObjects::Pointer QmitkMatchPointFrameCorrection::GetRegNodes() const { mitk::DataStorage::SetOfObjects::ConstPointer nodes = this->GetDataStorage()->GetAll(); mitk::DataStorage::SetOfObjects::Pointer result = mitk::DataStorage::SetOfObjects::New(); for (mitk::DataStorage::SetOfObjects::const_iterator pos = nodes->begin(); pos != nodes->end(); ++pos) { if (mitk::MITKRegistrationHelper::IsRegNode(*pos)) { result->push_back(*pos); } } return result; } std::string QmitkMatchPointFrameCorrection::GetDefaultJobName() const { mitk::DataStorage::SetOfObjects::ConstPointer nodes = this->GetRegNodes().GetPointer(); mitk::DataStorage::SetOfObjects::ElementIdentifier newIndex = 0; bool isUnique = false; std::string baseName = "corrected #"; if (m_spSelectedTargetNode.IsNotNull()) { baseName = m_spSelectedTargetNode->GetName() + "corrected #"; } std::string result = baseName; while (!isUnique) { ++newIndex; result = baseName + ::map::core::convert::toStr(newIndex); isUnique = this->GetDataStorage()->GetNamedNode(result) == nullptr; } return result; } void QmitkMatchPointFrameCorrection::ConfigureRegistrationControls() { m_Controls.m_tabSelection->setEnabled(!m_Working); m_Controls.m_leRegJobName->setEnabled(!m_Working); m_Controls.m_pbStartReg->setEnabled(false); m_Controls.imageNodeSelector->setEnabled(!m_Working); m_Controls.maskNodeSelector->setEnabled(!m_Working); if (m_LoadedAlgorithm.IsNotNull()) { m_Controls.m_tabSettings->setEnabled(!m_Working); m_Controls.m_tabExclusion->setEnabled(!m_Working); m_Controls.m_tabExecution->setEnabled(true); m_Controls.m_pbStartReg->setEnabled(m_spSelectedTargetNode.IsNotNull() && !m_Working); m_Controls.m_leRegJobName->setEnabled(!m_Working); typedef ::map::algorithm::facet::MaskedRegistrationAlgorithmInterface<3, 3> MaskRegInterface; const MaskRegInterface* pMaskReg = dynamic_cast (m_LoadedAlgorithm.GetPointer()); m_Controls.maskNodeSelector->setVisible(pMaskReg != nullptr); m_Controls.label_TargetMask->setVisible(pMaskReg != nullptr); if (!pMaskReg) { m_Controls.maskNodeSelector->SetCurrentSelection(QmitkSingleNodeSelectionWidget::NodeList()); } this->m_Controls.m_lbLoadedAlgorithmName->setText( QString::fromStdString(m_LoadedAlgorithm->getUID()->toStr())); } else { m_Controls.m_tabSettings->setEnabled(false); m_Controls.m_tabExclusion->setEnabled(false); m_Controls.m_tabExecution->setEnabled(false); this->m_Controls.m_lbLoadedAlgorithmName->setText( QStringLiteral("no algorithm loaded!")); m_Controls.maskNodeSelector->setVisible(false); m_Controls.label_TargetMask->setVisible(false); } if (!m_Working) { this->m_Controls.m_leRegJobName->setText(QString::fromStdString(this->GetDefaultJobName())); } } void QmitkMatchPointFrameCorrection::ConfigureProgressInfos() { const IIterativeAlgorithm* pIterative = dynamic_cast (m_LoadedAlgorithm.GetPointer()); const IMultiResAlgorithm* pMultiRes = dynamic_cast (m_LoadedAlgorithm.GetPointer()); m_Controls.m_progBarIteration->setVisible(pIterative); m_Controls.m_lbProgBarIteration->setVisible(pIterative); if (pIterative) { QString format = "%p% (%v/%m)"; if (!pIterative->hasMaxIterationCount()) { format = "%v"; m_Controls.m_progBarIteration->setMaximum(0); } else { m_Controls.m_progBarIteration->setMaximum(pIterative->getMaxIterations()); } m_Controls.m_progBarIteration->setFormat(format); } if (pMultiRes) { m_Controls.m_progBarLevel->setMaximum(pMultiRes->getResolutionLevels()); } else { m_Controls.m_progBarLevel->setMaximum(1); } m_Controls.m_progBarIteration->reset(); m_Controls.m_progBarLevel->reset(); m_Controls.m_progBarFrame->reset(); } void QmitkMatchPointFrameCorrection::ConfigureFrameList() { m_Controls.m_listFrames->clear(); if (m_spSelectedTargetData.IsNotNull()) { mitk::TimeGeometry::ConstPointer tg = m_spSelectedTargetData->GetTimeGeometry(); for (unsigned int i = 1; i < tg->CountTimeSteps(); ++i) { QString lable = "Timepoint #" + QString::number(i) + QString(" (") + QString::number( tg->GetMinimumTimePoint(i)) + QString(" ms - " + QString::number(tg->GetMaximumTimePoint( i)) + QString(" ms)")); QListWidgetItem* item = new QListWidgetItem(lable, m_Controls.m_listFrames); item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setCheckState(Qt::Checked); } } } void QmitkMatchPointFrameCorrection::OnFramesSelectAllPushed() { for (int row = 0; row < m_Controls.m_listFrames->count(); row++) { QListWidgetItem* item = m_Controls.m_listFrames->item(row); item->setCheckState(Qt::Checked); } }; void QmitkMatchPointFrameCorrection::OnFramesDeSelectAllPushed() { for (int row = 0; row < m_Controls.m_listFrames->count(); row++) { QListWidgetItem* item = m_Controls.m_listFrames->item(row); item->setCheckState(Qt::Unchecked); } }; void QmitkMatchPointFrameCorrection::OnFramesInvertPushed() { for (int row = 0; row < m_Controls.m_listFrames->count(); row++) { QListWidgetItem* item = m_Controls.m_listFrames->item(row); if (item->checkState() == Qt::Unchecked) { item->setCheckState(Qt::Checked); } else { item->setCheckState(Qt::Unchecked); } } }; mitk::TimeFramesRegistrationHelper::IgnoreListType QmitkMatchPointFrameCorrection::GenerateIgnoreList() const { mitk::TimeFramesRegistrationHelper::IgnoreListType result; for (int row = 0; row < m_Controls.m_listFrames->count(); row++) { QListWidgetItem* item = m_Controls.m_listFrames->item(row); if (item->checkState() == Qt::Unchecked) { result.push_back(row + 1); } } return result; } void QmitkMatchPointFrameCorrection::OnNodeSelectionChanged(QList /*nodes*/) { if (!m_Working) { ConfigureNodeSelectorPredicates(); CheckInputs(); ConfigureRegistrationControls(); } } void QmitkMatchPointFrameCorrection::OnStartRegBtnPushed() { this->m_Working = true; //////////////////////////////// //configure GUI this->ConfigureProgressInfos(); m_Controls.m_progBarIteration->reset(); m_Controls.m_progBarLevel->reset(); this->ConfigureRegistrationControls(); if (m_Controls.m_checkClearLog->checkState() == Qt::Checked) { this->m_Controls.m_teLog->clear(); } ///////////////////////// //create job and put it into the thread pool QmitkFramesRegistrationJob* pJob = new QmitkFramesRegistrationJob(m_LoadedAlgorithm); pJob->setAutoDelete(true); pJob->m_spTargetData = m_spSelectedTargetData; pJob->m_TargetDataUID = mitk::EnsureUID(this->m_spSelectedTargetNode->GetData()); pJob->m_IgnoreList = this->GenerateIgnoreList(); if (m_spSelectedTargetMaskData.IsNotNull()) { pJob->m_spTargetMask = m_spSelectedTargetMaskData; pJob->m_TargetMaskDataUID = mitk::EnsureUID(this->m_spSelectedTargetMaskNode->GetData()); } pJob->m_MappedName = m_Controls.m_leRegJobName->text().toStdString(); m_Controls.m_mapperSettings->ConfigureJobSettings(pJob); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnRegJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnRegJobFinished())); connect(pJob, SIGNAL(ResultIsAvailable(mitk::Image::Pointer, const QmitkFramesRegistrationJob*)), this, SLOT(OnMapResultIsAvailable(mitk::Image::Pointer, const QmitkFramesRegistrationJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(AlgorithmInfo(QString)), this, SLOT(OnAlgorithmInfo(QString))); connect(pJob, SIGNAL(AlgorithmStatusChanged(QString)), this, SLOT(OnAlgorithmStatusChanged(QString))); connect(pJob, SIGNAL(AlgorithmIterated(QString, bool, unsigned long)), this, SLOT(OnAlgorithmIterated(QString, bool, unsigned long))); connect(pJob, SIGNAL(LevelChanged(QString, bool, unsigned long)), this, SLOT(OnLevelChanged(QString, bool, unsigned long))); connect(pJob, SIGNAL(FrameRegistered(double)), this, SLOT(OnFrameRegistered(double))); connect(pJob, SIGNAL(FrameMapped(double)), this, SLOT(OnFrameMapped(double))); connect(pJob, SIGNAL(FrameProcessed(double)), this, SLOT(OnFrameProcessed(double))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } void QmitkMatchPointFrameCorrection::OnSaveLogBtnPushed() { QDateTime currentTime = QDateTime::currentDateTime(); QString fileName = tr("registration_log_") + currentTime.toString(tr("yyyy-MM-dd_hh-mm-ss")) + tr(".txt"); fileName = QFileDialog::getSaveFileName(nullptr, tr("Save registration log"), fileName, tr("Text files (*.txt)")); if (fileName.isEmpty()) { QMessageBox::critical(nullptr, tr("No file selected!"), tr("Cannot save registration log file. Please selected a file.")); } else { std::ofstream file; std::ios_base::openmode iOpenFlag = std::ios_base::out | std::ios_base::trunc; file.open(fileName.toStdString().c_str(), iOpenFlag); if (!file.is_open()) { mitkThrow() << "Cannot open or create specified file to save. File path: " << fileName.toStdString(); } file << this->m_Controls.m_teLog->toPlainText().toStdString() << std::endl; file.close(); } } void QmitkMatchPointFrameCorrection::OnRegJobError(QString err) { Error(err); }; void QmitkMatchPointFrameCorrection::OnRegJobFinished() { this->m_Working = false; auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) renderWindowPart->RequestUpdate(); this->CheckInputs(); this->ConfigureRegistrationControls(); this->ConfigureProgressInfos(); }; void QmitkMatchPointFrameCorrection::OnMapResultIsAvailable(mitk::Image::Pointer spMappedData, const QmitkFramesRegistrationJob* job) { m_Controls.m_teLog->append(QString("Corrected image stored. Name: ") + QString::fromStdString(job->m_MappedName) + QString("")); mitk::DataNode::Pointer spResultNode = mitk::generateMappedResultNode(job->m_MappedName, spMappedData.GetPointer(), "", job->m_TargetDataUID, false, job->m_InterpolatorLabel); this->GetDataStorage()->Add(spResultNode, this->m_spSelectedTargetNode); auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) renderWindowPart->RequestUpdate(); }; void QmitkMatchPointFrameCorrection::OnMapJobError(QString err) { Error(err); }; void QmitkMatchPointFrameCorrection::OnAlgorithmIterated(QString info, bool hasIterationCount, unsigned long currentIteration) { if (hasIterationCount) { m_Controls.m_progBarIteration->setValue(currentIteration); } m_Controls.m_teLog->append(info); }; void QmitkMatchPointFrameCorrection::OnLevelChanged(QString info, bool hasLevelCount, unsigned long currentLevel) { if (hasLevelCount) { m_Controls.m_progBarLevel->setValue(currentLevel); } m_Controls.m_teLog->append(QString("") + info + QString("")); }; void QmitkMatchPointFrameCorrection::OnAlgorithmStatusChanged(QString info) { m_Controls.m_teLog->append(QString("") + info + QString(" ")); }; void QmitkMatchPointFrameCorrection::OnAlgorithmInfo(QString info) { m_Controls.m_teLog->append(QString("") + info + QString("")); }; void QmitkMatchPointFrameCorrection::OnFrameProcessed(double progress) { m_Controls.m_teLog->append(QStringLiteral("Frame processed...")); m_Controls.m_progBarFrame->setValue(100 * progress); }; void QmitkMatchPointFrameCorrection::OnFrameRegistered(double progress) { m_Controls.m_teLog->append(QStringLiteral("Frame registered...")); m_Controls.m_progBarFrame->setValue(100 * progress); }; void QmitkMatchPointFrameCorrection::OnFrameMapped(double progress) { m_Controls.m_teLog->append(QStringLiteral("Frame mapped...")); m_Controls.m_progBarFrame->setValue(100 * progress); }; void QmitkMatchPointFrameCorrection::OnAlgorithmSelectionChanged(const berry::IWorkbenchPart::Pointer& sourcepart, const berry::ISelection::ConstPointer& selection) { // check for null selection if (selection.IsNull()) { return; } if (sourcepart != this) { UpdateAlgorithmSelection(selection); } } void QmitkMatchPointFrameCorrection::UpdateAlgorithmSelection(berry::ISelection::ConstPointer selection) { mitk::MAPAlgorithmInfoSelection::ConstPointer currentSelection = selection.Cast(); if (currentSelection) { mitk::MAPAlgorithmInfoSelection::AlgorithmInfoVectorType infoVector = currentSelection->GetSelectedAlgorithmInfo(); if (!infoVector.empty()) { // only the first selection is of interest, the rest will be skipped. this->m_SelectedAlgorithmInfo = infoVector[0]; } + this->OnSelectedAlgorithmChanged(); } - this->OnSelectedAlgorithmChanged(); }; diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/Manual.dox b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/Manual.dox index 7c16b548b7..03d7930ef2 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/Manual.dox +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/Manual.dox @@ -1,35 +1,52 @@ /** \page org_mitk_views_pharmacokinetics_concentration_mri The DCE Concentration Curve Converter View \imageMacro{pharmacokinetics_concentration_doc.svg,"Icon of the DCE Concentration Curve Converter View",3.0} \tableofcontents \section org_mitk_views_pharmacokinetics_concentration_mri_overview Overview This view offers a dedicated tool for the conversion of DCE MR image signal intensities to contrast agent (CA) concentration. -It contains a subset of the conversion tools for T1-weighted signal intensities, which are a part of the DCE MR Perfusion Datafit View (see \ref FIT_DCE_Settings_concentration). +It contains a subset of the conversion tools for T1-weighted signal intensities, which are also a part of the DCE MR Perfusion Datafit View. Additionally, it allows for the conversion between T2-weighted MR signal intensities and contrast agent concentration. \section org_mitk_views_pharmacokinetics_concentration_mri_Contact Contact information If you have any questions, need support, find a bug or have a feature request, feel free to contact us at www.mitk.org. \section org_mitk_views_pharmacokinetics_concentration_mri_T1_conversion Conversion of T1-weighted MRI data \imageMacro{concentration_curve_converter_T1_weighted4D.png,"Example screenshot of the conversion of T1-weighted MR images",3.0} + The view offers the choice between a 3D Image and a 4D image. If a 4D image is selected, the Selected Time Series needs to be specified. -In this case, the baseline image is automatically extracted as the first time point image of the time series. +For 4D images, for all conversion methods which require a baseline value, the range of the time points which are part of the baseline can be specified. The baseline signal SBL will be averaged between the signal of the time points within this range. +If not specified, the baseline signal SBL is set as the signal of the first time point image of the time series. In case of a 3D image to be converted, additionally to the selected 3D image a Baseline Image (without CA) has to be specified.\n\n -In the configuration section, the following types of conversion can be chosen: -- Absolute Signal Enhancement: The contrast agent concentration c is calculated according to the formula: c = k*(S(t)-S(0)), where S(t) is the dynamic T1-weighted signal intensity, S(0) the baseline signal and k a user-defined conversion factor. -- Relative Signal Enhancement: The contrast agent concentration c is calculated according to the formula: c = k*(S(t)-S(0))/S(0), where S(t) is the dynamic T1-weighted signal intensity, S(0) the baseline signal and k a user-defined conversion factor. -- Turbo FLASH Sequence: The conversion from signal S(t) to contrast agent concentration c is calculated according to the turboFLASH (ultrafast gradient echo) sequence specific formula. - +The following types of conversion can be chosen: +- Absolute Signal Enhancement: The dynamic contrast agent concentration C(t) is calculated according to the formula: C(t) = k*(S(t)-SBL), where S(t) is the dynamic T1-weighted signal intensity, SBL the baseline signal and k a user-defined conversion factor. +- Relative Signal Enhancement: The dynamic contrast agent concentration C(t) is calculated according to the formula: C(t) = k*(S(t)-SBL)/SBL, where S(t) is the dynamic T1-weighted signal intensity, SBL the baseline signal and k a user-defined conversion factor. +- Variable Flip Angle: This conversion uses the method described in \ref org_mitk_views_pharmacokinetics_concentration_mri_lit_ref1 "[1]". As additional input to the dynamic time series, +a proton density weighted (PDW) image is provided, which has been acquired pre-contrast with the same sequence parameters as for the +dynamic image but a smaller flip angle. Both flip angles are provided by the user. +Furthermore, the repetition time TR and the longitudinal relaxivity r1 are required as input.
+It is assumed that the MR data has been acquired according to the spoiled gradient recalled echo model. +The sequence formulas for the PDW image signal and for the baseline signal of the dynamic time series are two equations with two unknowns: +the pre-contrast R1-relaxation rate R10 and the signal scaling factor S0. These are calculated by solving the system of equations.
+With the knowledge of S0, the dynamic contrast-enhanced relaxation rate R1(t) is computed. +Finally, the concentration is calculated by inverting the linear model: +R1(t)=R10+r1*C(t). \section org_mitk_views_pharmacokinetics_concentration_mri_T2_conversion Conversion of T2-weighted MRI data \imageMacro{concentration_curve_converter_T2_weighted.png,"Example screenshot of the conversion of T2-weighted MR images",3.0} -The contrast agent concentration c is calculated according to the formula: c = -k/TE*ln(S(t)/S(0)), where S(t) is the dynamic T2-weighted signal intensity, S(0) the baseline signal, k a user-defined conversion factor and TE the echo time of the employed sequence. +The dynamic contrast agent concentration C(t) is calculated according to the formula: C(t) = -k/TE*ln(S(t)/SBL), where S(t) is the dynamic T2-weighted signal intensity, SBL the baseline signal, k a user-defined conversion factor and TE the echo time of the employed sequence. In practice, the factor k is often set to unity. +\section org_mitk_views_pharmacokinetics_concentration_mri_lit References/Literature +- \anchor org_mitk_views_pharmacokinetics_concentration_mri_lit_ref1 [1] Wang, H. Z., Riederer, S. J., and Lee, J. N. (1987). Optimization the +precision in T1 relaxation estimation using limited flip angles. Magn Reson Med, +5:399–416. + + + */ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T1_weighted4D.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T1_weighted4D.png index ef521cc592..1d31055997 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T1_weighted4D.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T1_weighted4D.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T2_weighted.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T2_weighted.png index cd4158f49d..0a85d2d6d7 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T2_weighted.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/documentation/UserManual/concentration_curve_converter_T2_weighted.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/manifest_headers.cmake index 2266f968b7..4609994d87 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "Concentration Curve Converter Plugin") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "c.debus@dkfz.de") +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterView.cpp b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterView.cpp index 1ec7c9c470..4067d16e83 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterView.cpp +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterView.cpp @@ -1,551 +1,519 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include "mitkWorkbenchUtil.h" #include "ConcentrationCurveConverterView.h" #include "mitkConcentrationCurveGenerator.h" #include "mitkNodePredicateDataType.h" -#include "mitkConvertToConcentrationTurboFlashFunctor.h" #include "mitkConvertToConcentrationAbsoluteFunctor.h" #include "mitkConvertToConcentrationRelativeFunctor.h" #include "itkBinaryFunctorImageFilter.h" #include "boost/math/constants/constants.hpp" #include #include #include #include #include #include #include "mitkNodePredicateFunction.h" #include #include // Includes for image casting between ITK and MITK #include "mitkImageTimeSelector.h" #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include const std::string ConcentrationCurveConverterView::VIEW_ID = "org.mitk.views.pharmacokinetics.concentration.mri"; void ConcentrationCurveConverterView::SetFocus() { m_Controls.btnConvertToConcentration->setFocus(); } void ConcentrationCurveConverterView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnConvertToConcentration->setEnabled(false); connect(m_Controls.btnConvertToConcentration, SIGNAL(clicked()), this, SLOT(OnConvertToConcentrationButtonClicked())); m_Controls.timeSeriesNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.timeSeriesNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.timeSeriesNodeSelector->SetSelectionIsOptional(false); m_Controls.timeSeriesNodeSelector->SetInvalidInfo("Please select time series."); m_Controls.image3DNodeSelector->SetNodePredicate(this->m_isValidPDWImagePredicate); m_Controls.image3DNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.image3DNodeSelector->SetSelectionIsOptional(false); m_Controls.image3DNodeSelector->SetInvalidInfo("Please select 3D image."); m_Controls.baselineImageNodeSelector->SetNodePredicate(this->m_isValidPDWImagePredicate); m_Controls.baselineImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.baselineImageNodeSelector->SetSelectionIsOptional(false); m_Controls.baselineImageNodeSelector->SetInvalidInfo("Please select baseline image."); m_Controls.t2TimeSeriesNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.t2TimeSeriesNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.t2TimeSeriesNodeSelector->SetSelectionIsOptional(false); m_Controls.t2TimeSeriesNodeSelector->SetInvalidInfo("Please select time series."); m_Controls.PDWImageNodeSelector->SetNodePredicate(m_isValidPDWImagePredicate); m_Controls.PDWImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.PDWImageNodeSelector->SetInvalidInfo("Please select PDW Image."); m_Controls.PDWImageNodeSelector->setEnabled(false); m_Controls.groupBox_T1->hide(); m_Controls.groupBox_T2->hide(); m_Controls.groupBox3D->hide(); m_Controls.groupBox4D->hide(); - m_Controls.groupBoxTurboFlash->hide(); m_Controls.groupConcentration->hide(); + m_Controls.groupBox_baselineRangeSelection->hide(); + m_Controls.groupBox_BaselineRangeSelectionT2->hide(); connect(m_Controls.radioButton_T1, SIGNAL(toggled(bool)),this, SLOT(OnSettingChanged())); connect(m_Controls.radioButton_T2, SIGNAL(toggled(bool)),this, SLOT(OnSettingChanged())); connect(m_Controls.radioButton3D, SIGNAL(toggled(bool)),this, SLOT(OnSettingChanged())); connect(m_Controls.radioButton4D, SIGNAL(toggled(bool)),this, SLOT(OnSettingChanged())); //Concentration m_Controls.groupConcentration->hide(); m_Controls.groupBoxEnhancement->hide(); - m_Controls.groupBoxTurboFlash->hide(); m_Controls.groupBox_T1MapviaVFA->hide(); m_Controls.spinBox_baselineStartTimeStep->setValue(0); m_Controls.spinBox_baselineEndTimeStep->setValue(0); m_Controls.spinBox_baselineEndTimeStep->setMinimum(0); m_Controls.spinBox_baselineStartTimeStep->setMinimum(0); m_Controls.spinBox_baselineStartTimeStepT2->setValue(0); m_Controls.spinBox_baselineEndTimeStepT2->setValue(0); m_Controls.spinBox_baselineEndTimeStepT2->setMinimum(0); m_Controls.spinBox_baselineStartTimeStepT2->setMinimum(0); - connect(m_Controls.radioButtonTurboFlash, SIGNAL(toggled(bool)), m_Controls.groupBoxTurboFlash, SLOT(setVisible(bool))); - connect(m_Controls.radioButtonTurboFlash, SIGNAL(toggled(bool)), this, SLOT(OnSettingChanged())); - connect(m_Controls.relaxationtime, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); - connect(m_Controls.recoverytime, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); - connect(m_Controls.relaxivity, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); + connect(m_Controls.timeSeriesNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &ConcentrationCurveConverterView::OnNodeSelectionChanged); connect(m_Controls.image3DNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &ConcentrationCurveConverterView::OnNodeSelectionChanged); connect(m_Controls.baselineImageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &ConcentrationCurveConverterView::OnNodeSelectionChanged); connect(m_Controls.t2TimeSeriesNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &ConcentrationCurveConverterView::OnNodeSelectionChanged); connect(m_Controls.PDWImageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &ConcentrationCurveConverterView::OnSettingChanged); - connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), this, SLOT(OnSettingChanged())); - connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), this, SLOT(OnSettingChanged())); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); + connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); + connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), this, SLOT(OnSettingChanged())); + connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); + connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); + connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), this, SLOT(OnSettingChanged())); connect(m_Controls.factorSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); connect(m_Controls.spinBox_baselineStartTimeStep, SIGNAL(valueChanged(int)), this, SLOT(OnSettingChanged())); connect(m_Controls.spinBox_baselineEndTimeStep, SIGNAL(valueChanged(int)), this, SLOT(OnSettingChanged())); connect(m_Controls.spinBox_baselineStartTimeStepT2, SIGNAL(valueChanged(int)), this, SLOT(OnSettingChanged())); connect(m_Controls.spinBox_baselineEndTimeStepT2, SIGNAL(valueChanged(int)), this, SLOT(OnSettingChanged())); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_T1MapviaVFA, SLOT(setVisible(bool))); + connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), this, SLOT(OnSettingChanged())); connect(m_Controls.FlipangleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); connect(m_Controls.RelaxivitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); connect(m_Controls.TRSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); connect(m_Controls.T2EchoTimeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); connect(m_Controls.T2FactorSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnSettingChanged())); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.PDWImageNodeSelector, SLOT(setEnabled(bool))); } void ConcentrationCurveConverterView::OnSettingChanged() { bool ok = false; m_Controls.groupBox_T1->setVisible(m_Controls.radioButton_T1->isChecked()); m_Controls.groupBox_T2->setVisible(m_Controls.radioButton_T2->isChecked()); + m_Controls.groupBox_BaselineRangeSelectionT2->setVisible(m_Controls.radioButton_T2->isChecked()); + if(m_Controls.radioButton_T1->isChecked()) { m_Controls.groupBox3D->setVisible(m_Controls.radioButton3D->isChecked()); m_Controls.groupBox4D->setVisible(m_Controls.radioButton4D->isChecked()); if(m_Controls.radioButton4D->isChecked()) { m_Controls.groupConcentration->setVisible(true); + if (m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()) + m_Controls.groupBox_baselineRangeSelection->setVisible(true); ok = m_selectedImage.IsNotNull() && CheckSettings(); } else if(m_Controls.radioButton3D->isChecked()) { m_Controls.groupConcentration->setVisible(true); + m_Controls.groupBox_baselineRangeSelection->hide(); ok = m_selectedImage.IsNotNull() && m_selectedBaselineImage.IsNotNull() && CheckSettings(); - } } else if (m_Controls.radioButton_T2->isChecked()) { m_Controls.groupConcentration->setVisible(false); ok = m_selectedImage.IsNotNull() && CheckSettings(); } m_Controls.btnConvertToConcentration->setEnabled(ok); } bool ConcentrationCurveConverterView::CheckSettings() const { bool ok = true; if (m_Controls.radioButton_T1->isChecked()) { - if (this->m_Controls.radioButtonTurboFlash->isChecked()) - { - ok = ok && (m_Controls.recoverytime->value() > 0); - ok = ok && (m_Controls.relaxationtime->value() > 0); - ok = ok && (m_Controls.relaxivity->value() > 0); - ok = ok && (m_Controls.AifRecoverytime->value() > 0); - ok = ok && CheckBaselineSelectionSettings(); - } - else if (this->m_Controls.radioButton_absoluteEnhancement->isChecked() + if (this->m_Controls.radioButton_absoluteEnhancement->isChecked() || this->m_Controls.radioButton_relativeEnchancement->isChecked()) { ok = ok && (m_Controls.factorSpinBox->value() > 0); ok = ok && CheckBaselineSelectionSettings(); } else if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked()) { ok = ok && (m_Controls.FlipangleSpinBox->value() > 0); ok = ok && (m_Controls.TRSpinBox->value() > 0); ok = ok && (m_Controls.RelaxivitySpinBox->value() > 0); ok = ok && (m_Controls.PDWImageNodeSelector->GetSelectedNode().IsNotNull()); ok = ok && CheckBaselineSelectionSettings(); } else { ok = false; } } else if (this->m_Controls.radioButton_T2->isChecked()) { ok = ok && m_Controls.T2EchoTimeSpinBox->value() > 0; ok = ok && m_Controls.T2FactorSpinBox->value() > 0; ok = ok && CheckBaselineSelectionSettings(); } else { ok = false; } return ok; } bool ConcentrationCurveConverterView::CheckBaselineSelectionSettings() const { if (this->m_Controls.radioButton_T1->isChecked()) { return m_Controls.spinBox_baselineStartTimeStep->value() <= m_Controls.spinBox_baselineEndTimeStep->value(); } else if (this->m_Controls.radioButton_T2->isChecked()) { return m_Controls.spinBox_baselineStartTimeStepT2->value() <= m_Controls.spinBox_baselineEndTimeStepT2->value(); } else { return 0; } } void ConcentrationCurveConverterView::OnConvertToConcentrationButtonClicked() { mitk::Image::Pointer concentrationImage; mitk::DataNode::Pointer concentrationNode; if(m_Controls.radioButton_T1->isChecked()) { if(m_Controls.radioButton4D->isChecked()) { concentrationImage = this->Convert4DConcentrationImage(this->m_selectedImage); } else if(m_Controls.radioButton3D->isChecked()) { concentrationImage = Convert3DConcentrationImage(this->m_selectedImage, this->m_selectedBaselineImage); } } else if(m_Controls.radioButton_T2->isChecked()) { concentrationImage = this->ConvertT2ConcentrationImgage(this->m_selectedImage); } std::string nameOfResultImage = m_selectedNode->GetName(); nameOfResultImage.append("_Concentration"); concentrationNode = AddConcentrationImage(concentrationImage,nameOfResultImage); } mitk::Image::Pointer ConcentrationCurveConverterView::Convert3DConcentrationImage(mitk::Image::Pointer inputImage,mitk::Image::Pointer baselineImage) { typedef itk::Image InputImageType; InputImageType::Pointer itkInputImage = InputImageType::New(); InputImageType::Pointer itkBaselineImage = InputImageType::New(); mitk::CastToItkImage(inputImage, itkInputImage ); mitk::CastToItkImage(baselineImage, itkBaselineImage ); mitk::Image::Pointer outputImage; - if(this->m_Controls.radioButtonTurboFlash->isChecked()) - { - typedef mitk::ConvertToConcentrationTurboFlashFunctor ConversionFunctorTurboFlashType; - typedef itk::BinaryFunctorImageFilter FilterTurboFlashType; - - ConversionFunctorTurboFlashType ConversionTurboFlashFunctor; - ConversionTurboFlashFunctor.initialize(m_Controls.relaxationtime->value(), m_Controls.relaxivity->value(), m_Controls.recoverytime->value()); - - FilterTurboFlashType::Pointer ConversionTurboFlashFilter = FilterTurboFlashType::New(); - - ConversionTurboFlashFilter->SetFunctor(ConversionTurboFlashFunctor); - ConversionTurboFlashFilter->SetInput1(itkInputImage); - ConversionTurboFlashFilter->SetInput2(itkBaselineImage); - - ConversionTurboFlashFilter->Update(); - outputImage = mitk::ImportItkImage(ConversionTurboFlashFilter->GetOutput())->Clone(); - - - } - else if(this->m_Controls.radioButton_absoluteEnhancement->isChecked()) + if(this->m_Controls.radioButton_absoluteEnhancement->isChecked()) { typedef mitk::ConvertToConcentrationAbsoluteFunctor ConversionFunctorAbsoluteType; typedef itk::BinaryFunctorImageFilter FilterAbsoluteType; ConversionFunctorAbsoluteType ConversionAbsoluteFunctor; ConversionAbsoluteFunctor.initialize(m_Controls.factorSpinBox->value()); FilterAbsoluteType::Pointer ConversionAbsoluteFilter = FilterAbsoluteType::New(); ConversionAbsoluteFilter->SetFunctor(ConversionAbsoluteFunctor); ConversionAbsoluteFilter->SetInput1(itkInputImage); ConversionAbsoluteFilter->SetInput2(itkBaselineImage); ConversionAbsoluteFilter->Update(); outputImage = mitk::ImportItkImage(ConversionAbsoluteFilter->GetOutput())->Clone(); } else if(m_Controls.radioButton_relativeEnchancement->isChecked()) { typedef mitk::ConvertToConcentrationRelativeFunctor ConversionFunctorRelativeType; typedef itk::BinaryFunctorImageFilter FilterRelativeType; ConversionFunctorRelativeType ConversionRelativeFunctor; ConversionRelativeFunctor.initialize(m_Controls.factorSpinBox->value()); FilterRelativeType::Pointer ConversionRelativeFilter = FilterRelativeType::New(); ConversionRelativeFilter->SetFunctor(ConversionRelativeFunctor); ConversionRelativeFilter->SetInput1(itkInputImage); ConversionRelativeFilter->SetInput2(itkBaselineImage); ConversionRelativeFilter->Update(); outputImage = mitk::ImportItkImage(ConversionRelativeFilter->GetOutput())->Clone(); } return outputImage; } mitk::DataNode::Pointer ConcentrationCurveConverterView::AddConcentrationImage(mitk::Image* image, std::string nodeName) const { if (!image) { mitkThrow() << "Cannot generate concentration node. Passed image is null. parameter name: "; } mitk::DataNode::Pointer result = mitk::DataNode::New(); result->SetData(image); result->SetName(nodeName); result->SetVisibility(true); this->GetDataStorage()->Add(result, m_selectedNode); return result; }; mitk::Image::Pointer ConcentrationCurveConverterView::Convert4DConcentrationImage(mitk::Image::Pointer inputImage) { //Compute Concentration image mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); concentrationGen->SetDynamicImage(inputImage); - concentrationGen->SetisTurboFlashSequence(m_Controls.radioButtonTurboFlash->isChecked()); concentrationGen->SetAbsoluteSignalEnhancement(m_Controls.radioButton_absoluteEnhancement->isChecked()); concentrationGen->SetRelativeSignalEnhancement(m_Controls.radioButton_relativeEnchancement->isChecked()); concentrationGen->SetUsingT1Map(m_Controls.radioButtonUsingT1viaVFA->isChecked()); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStepT2->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStepT2->value()); concentrationGen->SetisT2weightedImage(false); - if (m_Controls.radioButtonTurboFlash->isChecked()) - { - concentrationGen->SetRecoveryTime(m_Controls.recoverytime->value()); - concentrationGen->SetRelaxationTime(m_Controls.relaxationtime->value()); - concentrationGen->SetRelaxivity(m_Controls.relaxivity->value()); - concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); - concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); - } - else if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked()) + if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked()) { - concentrationGen->SetRecoveryTime(m_Controls.TRSpinBox->value()); + concentrationGen->SetRepetitionTime(m_Controls.TRSpinBox->value()); concentrationGen->SetRelaxivity(m_Controls.RelaxivitySpinBox->value()); - concentrationGen->SetT10Image(dynamic_cast(m_Controls.PDWImageNodeSelector->GetSelectedNode()->GetData())); + concentrationGen->SetPDWImage(dynamic_cast(m_Controls.PDWImageNodeSelector->GetSelectedNode()->GetData())); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); //Convert Flipangle from degree to radiant - double alpha = m_Controls.FlipangleSpinBox->value()/360*2* boost::math::constants::pi(); - concentrationGen->SetFlipAngle(alpha); + double alpha = m_Controls.FlipangleSpinBox->value()/360*2* boost::math::constants::pi(); + concentrationGen->SetFlipAngle(alpha); + double alphaPDW = m_Controls.FlipanglePDWSpinBox->value() / 360 * 2 * boost::math::constants::pi(); + concentrationGen->SetFlipAnglePDW(alphaPDW); } else { concentrationGen->SetFactor(m_Controls.factorSpinBox->value()); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); return concentrationImage; } mitk::Image::Pointer ConcentrationCurveConverterView::ConvertT2ConcentrationImgage(mitk::Image::Pointer inputImage) { //Compute Concentration image mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); concentrationGen->SetDynamicImage(inputImage); - concentrationGen->SetisTurboFlashSequence(false); concentrationGen->SetAbsoluteSignalEnhancement(false); concentrationGen->SetRelativeSignalEnhancement(false); concentrationGen->SetisT2weightedImage(true); concentrationGen->SetT2Factor(m_Controls.T2FactorSpinBox->value()); concentrationGen->SetT2EchoTime(m_Controls.T2EchoTimeSpinBox->value()); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); return concentrationImage; } void ConcentrationCurveConverterView::OnNodeSelectionChanged(QList/*nodes*/) { m_selectedNode = nullptr; m_selectedImage = nullptr; m_selectedBaselineNode = nullptr; m_selectedBaselineImage = nullptr; if (m_Controls.radioButton_T1->isChecked()) { if (m_Controls.radioButton4D->isChecked()) { if (m_Controls.timeSeriesNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedNode = m_Controls.timeSeriesNodeSelector->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; } } else if (m_Controls.radioButton3D->isChecked()) { if (m_Controls.image3DNodeSelector->GetSelectedNode().IsNotNull() && m_Controls.baselineImageNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedNode = m_Controls.image3DNodeSelector->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); this->m_selectedBaselineNode = m_Controls.baselineImageNodeSelector->GetSelectedNode(); m_selectedBaselineImage = dynamic_cast(m_selectedBaselineNode->GetData()); } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; m_selectedBaselineNode = nullptr; m_selectedBaselineImage = nullptr; } } if (this->m_selectedImage.IsNotNull()) { m_Controls.spinBox_baselineStartTimeStep->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); m_Controls.spinBox_baselineEndTimeStep->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); } } if (m_Controls.radioButton_T2->isChecked()) { if (m_Controls.t2TimeSeriesNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedNode = m_Controls.t2TimeSeriesNodeSelector->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; } if (this->m_selectedImage.IsNotNull()) { m_Controls.spinBox_baselineStartTimeStepT2->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); m_Controls.spinBox_baselineEndTimeStepT2->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); } } m_Controls.btnConvertToConcentration->setEnabled(m_selectedImage.IsNotNull() && CheckSettings()); } ConcentrationCurveConverterView::ConcentrationCurveConverterView() { mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isLegacyMask = mitk::NodePredicateAnd::New(isImage, isBinary); mitk::NodePredicateDimension::Pointer is3D = mitk::NodePredicateDimension::New(3); mitk::NodePredicateOr::Pointer isMask = mitk::NodePredicateOr::New(isLegacyMask, isLabelSet); mitk::NodePredicateAnd::Pointer isNoMask = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isMask)); mitk::NodePredicateAnd::Pointer is3DImage = mitk::NodePredicateAnd::New(isImage, is3D, isNoMask); this->m_IsMaskPredicate = mitk::NodePredicateAnd::New(isMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); this->m_IsNoMaskImagePredicate = mitk::NodePredicateAnd::New(isNoMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); auto isDynamicData = mitk::NodePredicateFunction::New([](const mitk::DataNode* node) { return (node && node->GetData() && node->GetData()->GetTimeSteps() > 1); }); auto modelFitResultRelationRule = mitk::ModelFitResultRelationRule::New(); auto isNoModelFitNodePredicate = mitk::NodePredicateNot::New(modelFitResultRelationRule->GetConnectedSourcesDetector()); this->m_isValidPDWImagePredicate = mitk::NodePredicateAnd::New(is3DImage, isNoModelFitNodePredicate); this->m_isValidTimeSeriesImagePredicate = mitk::NodePredicateAnd::New(isDynamicData, isImage, isNoMask); } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui index ef67520b67..b9382c9420 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.concentration.mri/src/internal/ConcentrationCurveConverterViewControls.ui @@ -1,469 +1,416 @@ ConcentrationCurveConverterViewControls 0 0 475 - 825 + 866 0 0 QmitkTemplate 10 T1 weighted MRI T2 weighted MRI T1 weighted images 10 10 10 3D Image 4D Image 4D Image Selected Time Series: 3D Image Selected 3D Image: QFrame::NoFrame QFrame::Plain Selected Baseline Image: + + QLayout::SetDefaultConstraint + - - - 10 + + + 4 Absolute Signal Enhancement Relative Signal Enhancement - Using T1 Map via Variable Flip Angle - - - - - - - TurboFLASH Sequence + Variable Flip Angle Qt::Vertical 20 40 Enhancement Parameters: Conversion Factor k: - - - - Baseline Range Selection: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - - Start Time Frame - - - - - - - End Time Frame - - - - - - - - - T1 Map via Variable Flip Angle Parameters: + Variable Flip Angle Parameters: - - + + - Flip Angle [ ° ] : + Repetition Time [ms] : - - + + + + + - Repetition Time TR [ms] : + Proton Density Weighted Image : - + + + + + + + Relaxivity [mM⁻¹ s⁻¹] : - - - - - - - - + + - Proton Density Weighted Image : + Flip Angle PDW Image [°] 10000.000000000000000 + + + + Flip Angle [ ° ] : + + + - + - - - true - + - Turbo FLASH Parameters: + Baseline Range Selection: - - - - - Recovery Time [s]: - - - - - - - - - - AIF Recovery Time [s]: - - - + - + - - + + - - + + - Relaxation Time [s]: + Start Time Frame - - - - - + + - Relaxivity [ ]: + End Time Frame Convert To Concentration 0 2 0 0 T2 weighted images + + + + Selected Time Series: + + + + + + + Conversion Factor k + + + + + + + Echo Time TE [ms] + + + - - - - - - - - - - - - Start Time Frame - - - - - - - End Time Frame - - - - - - - Baseline Range Selection: - - - - - - - + + + + + + + Baseline Range Selection: + + + + - Echo Time TE [ms] + Start Time Frame - + - Selected Time Series: + End Time Frame - - - - Conversion Factor k - - + + + + + Qt::Vertical 20 40 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.curvedescriptor/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.pharmacokinetics.curvedescriptor/manifest_headers.cmake index 3323290acb..f57a1a3321 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.curvedescriptor/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.curvedescriptor/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "Perfusion Curve Description Parameter Plugin") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "c.debus@dkfz.de") -set(Require-Plugin org.mitk.gui.qt.common) +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") +set(Require-Plugin org.mitk.gui.qt.common org.mitk.gui.qt.pharmacokinetics.concentration.mri) diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox index dc7a2ebaa9..a698450740 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox @@ -1,103 +1,103 @@ /** \page org_mitk_views_pharmacokinetics_mri The DCE MR Perfusion DataFit View \imageMacro{pharmacokinetics_mri_doc.svg,"Icon of the DCE MR Perfusion View",3.0} \tableofcontents \section FIT_DCE_Introduction Introduction In dynamic contrast-enhanced (DCE) MRI, pharmacokinetic (PK) modeling can be used to quantify tissue physiology. Parameters describing the tissue microvasculature can be derived by fitting a pharmacokinetic model, e.g. a compartment model, to the dynamic data. This view offers a comprehensive set of tools to perform pharmacokinetic analysis. \section FIT_DCE_Contact Contact information If you have any questions, need support, find a bug or have a feature request, feel free to contact us at www.mitk.org. \subsection FIT_DCE_Cite Citation information If you use the view for your research please cite our work as reference:\n\n Debus C and Floca R, Ingrisch M, Kompan I, Maier-Hein K, Abdollahi A, Nolden M, MITK-ModelFit: generic open-source framework for model fits and their exploration in medical imaging – design, implementation and application on the example of DCE-MRI. https://doi.org/10.1186/s12859-018-2588-1 (BMC Bioinformatics 2019 20:31) \section FIT_DCE_Data_and_ROI_Selection Time series and mask selection -\imageMacro{dce_mri_maskAndFittingStrategy.png, "Time series and mask selection.", 10} +\imageMacro{dce_mri_maskAndFittingStrategy.png, "Time series and mask selection.", 3.0} In principle, every model can be fitted on the entire image. However, for model configuration reasons (e.g. AIF required) and computational time cost, this is often not advisable. Therefore, apart from the image to be fitted (Selected Time Series), a ROI segmentation can be defined (Selected Mask), within which model fitting is performed. The view currently offers Pixel based and/or ROI based averaged fits of time-varying curves. The ROI based fitting option becomes enabled, if a mask is selected. \section FIT_DCE_General_models Supported models Currently the following pharmacokinetic models for gadolinium-based contrast agent are available: -- The Descriptive Brix model \ref FIT_DCE_lit_ref1 "[1]" -- The standard tofts model \ref FIT_DCE_lit_ref2 "[2]" +- The descriptive Brix model \ref FIT_DCE_lit_ref1 "[1]" +- The standard Tofts model \ref FIT_DCE_lit_ref2 "[2]" - The extended Tofts model \ref FIT_DCE_lit_ref3 "[3]" - The two compartment exchange model (2CXM) \ref FIT_DCE_lit_ref4 "[4, 5]" \section FIT_DCE_Settings Model settings \imageMacro{dce_mri_modelSettings.png, "Model settings of the view for the standard Tofts model.", 10} \subsection FIT_DCE_Settings_model Model specific settings Selecting one of the \ref FIT_DCE_General_models "supported models" will open below tabs for further configuration of the model. - The descriptive Brix model requires only definition of the duration of the bolus, i.e. the overall time of the injection (Injection Time [min]). - The standard Tofts model, the extended Tofts model and the 2CXM are compartment models that require the input of the concentration time curve in the tissue feeding artery, the arterial input function (AIF). In the DCE MR Perfusion Datafit View, the arterial input function can be defined in several ways. For patient individual image derived AIFs, select the radio button Select AIF from Image. In that case, a segmentation ROI for the artery has to be selected. This can be done by clicking on the AIF Mask selection widget and selecting a suitable AIF segmentation from the data loaded in the Data Manager. In cases where the respective artery does not lie in the same image as the investigated tissue (e.g. in animal experiments, where a slice through the heart is used for AIF extraction), a dedicated AIF image can be selected using the corresponding Dedicated AIF image selection widget. An alternative option is to define the AIF via an external file by selecting Select AIF from File (e.g. for population derived AIFs or AIFs from blood sampling). By clicking the Browse button, one can select a csv file that holds the AIF values and corresponding timepoints (in tuple format (Time, Value)). Caution: the file must not contain a header line, but the first line must start with Time and Intensity values. Furthermore, the Hematocrit Level has to be set (from 0 to 1) for conversion from whole blood to plasma concentration. It is set as default to the literature value of 0.45. \subsection FIT_DCE_Settings_start Start parameter \imageMacro{dce_mri_start.png, "Example screenshot for start parameter settings.", 10} In cases of noisy data it can be useful to define the initial starting values of the parameter estimates, at which optimization starts, in order to prevent optimization results in local optima. Each model has default scalar values (applied to every voxel) for initial values of each parameter, however these can be adjusted. Moreover, initial values can also be defined locally for each individual voxel via starting value images. To load a starting value image, change the Type from scalar to image. This can be done by double-clicking on the type cell. In the Value column, selection of a starting value image will be available. \subsection FIT_DCE_Settings_constraint Constraints settings \imageMacro{dce_mri_constraints.png, "Example screenshot for constraints settings.", 10} To limit the fitting search space and to exclude unphysical/illogical results for model parameter estimates, constraints to individual parameters as well as combinations can be imposed. Each model has default constraints, however, new ones can be defined or removed by the + and – buttons in the table. The first column specifies the parameter(s) involved in the constraint (if multiple parameters are selected, their sum will be used) by selection in the drop down menu. The second column Type defines whether the constraint defines an upper or lower boundary. Value defines the actual constraint value, that should not be crossed, and Width allows for a certain tolerance width. \subsection FIT_DCE_Settings_concentration Signal to concentration conversion settings \imageMacro{dce_mri_concentration.png, "Example screenshot for concentration conversion settings.", 10} Most models require contrast agent concentration values as input rather than raw signal intensities (i.e. all compartment models). The DCE MR Perfusion DataFit View offers a variety of tools for the conversion from signal to concentration: -by means of relative and absolute signal enhancement, via a T1-map calculated by the variable flip angle method, as well as a special conversion for turbo flash sequences. -A more detailed description of these conversion methods, for example the exact formulas used for the absolute and relative signal enhancement calculations, can be found here: \ref org_mitk_views_pharmacokinetics_concentration_mri. +by means of relative and absolute signal enhancement and via a T1-map calculated by the variable flip angle method. +A more detailed description of these conversion methods can be found here: \ref org_mitk_views_pharmacokinetics_concentration_mri. For the conversion methods, a baseline image prior to contrast agent arrival is required. -In many data sets, multiple baseline images are available. The Baseline Range Selection allows for selection of a range of time frames, from which the average image (along the time dimension) is calculated and set as baseline input image. +In many dynamic data sets, multiple images are part of the baseline. The Baseline Range Selection allows for selection of a range of time frames, from which the average image (along the time dimension) is calculated and set as baseline input image. Remark: The number of the first time frame is 0. \section FIT_DCE_Fitting Executing a fit In order to distinguish results from different model fits to the data, a Fitting name can be defined. As default, the name of the model and the fitting strategy (pixel/ROI) are given. This name will then be appended by the respective parameter name.\n\n For development purposes and evaluation of the fits, the option Generate debug parameter images is available. Enabling this option will result in additional parameter maps displaying the status of the optimizer at fit termination. In the following definitions, an evaluation describes the process of cost function calculation and evaluation by the optimizer for a given parameter set. - Stop condition: Reasons for the fit termination, i.e. criterion reached, maximum number of iterations,... - Optimization time: The overall time from fitting start to termination. - Number of iterations: The number of iterations from fitting start to termination. - Constraint penalty ratio: Ratio between evaluations that were penalized and all evaluations. 0.0 means no evaluation was penalized; 1.0 all evaluations were. Evaluations that hit the failure threshold count as penalized, too. - Constraint last failed parameter: Ratio between evaluations that were beyond the failure threshold. 0.0 means no evaluation was a failure (but some may be penalized). - Constraint failure ratio: Index of the first (in terms of index position) parameter, which failed the constraints in the last evaluation. After all necessary configurations are set, the button Start Modelling is enabled, which starts the fitting routine. Progress can be seen in the message box on the bottom. Resulting parameter maps will afterwards be added to the Data Manager as sub-nodes of the analyzed 4D image. \section FIT_DCE_lit References/Literature - \anchor FIT_DCE_lit_ref1 [1] Brix G, Semmler W, Port R, Schad LR, Layer G, Lorenz WJ. Pharmacokinetic parameters in CNS Gd-DTPA enhanced MR imaging. J Comput Assist Tomogr. 1991;15:621–8. - \anchor FIT_DCE_lit_ref2 [2] Tofts PS, Kermode AG. Measurement of the blood-brain barrier permeability and leakage space using dynamic MR imaging. 1. Fundamental concepts. Magn Reson Med. 1991;17:357–67. - \anchor FIT_DCE_lit_ref3 [3] Sourbron SP, Buckley DL. On the scope and interpretation of the Tofts models for DCE-MRI. Magn Reson Med. 2011;66:735–45. - \anchor FIT_DCE_lit_ref4 [4] Brix G, Kiessling F, Lucht R, Darai S, Wasser K, Delorme S, et al. Microcirculation and microvasculature in breast tumors: Pharmacokinetic analysis of dynamic MR image series. Magn Reson Med. 2004;52:420–9. - \anchor FIT_DCE_lit_ref5 [5] Sourbron, Buckley. Tracer kinetic modelling in MRI: estimating perfusion and capillary permeability - pdf. Phys Med Biol. 2012. https://iopscience.iop.org/article/10.1088/0031-9155/57/2/R1/pdf. Accessed 1 May 2016. */ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png index bcf2d51953..ee055936d4 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png deleted file mode 100644 index c7ca984653..0000000000 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png index 200837c59b..a9492c8a2b 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_init.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_init.png deleted file mode 100644 index 074fdd8f4a..0000000000 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_init.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png index 65f7604cd6..18ea040b1f 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png index 45d1ac1ab0..8a3de90eee 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png index b7c2275720..c65b91894d 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/manifest_headers.cmake index e3fff30e06..c406d9776c 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "DCE MR Perfusion Datafit Plugin") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "c.debus@dkfz.de") -set(Require-Plugin org.mitk.gui.qt.common) +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") +set(Require-Plugin org.mitk.gui.qt.common org.mitk.gui.qt.pharmacokinetics.concentration.mri) diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp index 66ab43fe57..4de3092800 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.cpp @@ -1,1409 +1,1334 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "MRPerfusionView.h" #include "boost/tokenizer.hpp" #include "boost/math/constants/constants.hpp" #include #include "mitkWorkbenchUtil.h" #include "mitkAterialInputFunctionGenerator.h" #include "mitkConcentrationCurveGenerator.h" #include #include #include #include #include #include #include #include "mitkTwoCompartmentExchangeModelFactory.h" #include "mitkTwoCompartmentExchangeModelParameterizer.h" -#include "mitkNumericTwoCompartmentExchangeModelFactory.h" -#include "mitkNumericTwoCompartmentExchangeModelParameterizer.h" #include #include #include #include #include #include #include #include "mitkNodePredicateFunction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK #include #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include const std::string MRPerfusionView::VIEW_ID = "org.mitk.views.pharmacokinetics.mri"; inline double convertToDouble(const std::string& data) { std::istringstream stepStream(data); stepStream.imbue(std::locale("C")); double value = 0.0; if (!(stepStream >> value) || !(stepStream.eof())) { mitkThrow() << "Cannot convert string to double. String: " << data; } return value; } void MRPerfusionView::SetFocus() { m_Controls.btnModelling->setFocus(); } void MRPerfusionView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnModelling->setEnabled(false); this->InitModelComboBox(); m_Controls.timeSeriesNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.timeSeriesNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.timeSeriesNodeSelector->SetSelectionIsOptional(false); m_Controls.timeSeriesNodeSelector->SetInvalidInfo("Please select time series."); m_Controls.timeSeriesNodeSelector->SetAutoSelectNewNodes(true); m_Controls.maskNodeSelector->SetNodePredicate(this->m_IsMaskPredicate); m_Controls.maskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.maskNodeSelector->SetSelectionIsOptional(true); m_Controls.maskNodeSelector->SetEmptyInfo("Please select (optional) mask."); connect(m_Controls.btnModelling, SIGNAL(clicked()), this, SLOT(OnModellingButtonClicked())); connect(m_Controls.comboModel, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.radioPixelBased, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.timeSeriesNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::OnNodeSelectionChanged); connect(m_Controls.maskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::OnNodeSelectionChanged); connect(m_Controls.AIFMaskNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::UpdateGUIControls); connect(m_Controls.AIFImageNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &MRPerfusionView::UpdateGUIControls); //AIF setting m_Controls.groupAIF->hide(); m_Controls.btnAIFFile->setEnabled(false); m_Controls.btnAIFFile->setEnabled(false); m_Controls.radioAIFImage->setChecked(true); m_Controls.AIFMaskNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.AIFMaskNodeSelector->SetNodePredicate(m_IsMaskPredicate); m_Controls.AIFMaskNodeSelector->setVisible(true); m_Controls.AIFMaskNodeSelector->setEnabled(true); m_Controls.AIFMaskNodeSelector->SetAutoSelectNewNodes(true); m_Controls.AIFImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.AIFImageNodeSelector->SetNodePredicate(this->m_isValidTimeSeriesImagePredicate); m_Controls.AIFImageNodeSelector->setEnabled(false); + m_Controls.AIFImageNodeSelector->setVisible(false); m_Controls.checkDedicatedAIFImage->setEnabled(true); + m_Controls.HCLSpinBox->setValue(mitk::AterialInputFunctionGenerator::DEFAULT_HEMATOCRIT_LEVEL); - m_Controls.spinBox_baselineEndTimeStep->setMinimum(0); - m_Controls.spinBox_baselineStartTimeStep->setMinimum(0); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFMaskNodeSelector, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.labelAIFMask, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFMaskNodeSelector, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); - connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFImageNodeSelector, SLOT(setVisible(bool))); connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFImageNodeSelector, SLOT(setEnabled(bool))); + connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.AIFImageNodeSelector, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.btnAIFFile, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.aifFilePath, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); + connect(m_Controls.btnAIFFile, SIGNAL(clicked()), this, SLOT(LoadAIFfromFile())); //Brix setting m_Controls.groupDescBrix->hide(); connect(m_Controls.injectiontime, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); - //Num2CX setting - m_Controls.groupNum2CXM->hide(); - connect(m_Controls.odeStepSize, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); //Model fit configuration m_Controls.groupBox_FitConfiguration->hide(); m_Controls.checkBox_Constraints->setEnabled(false); m_Controls.constraintManager->setEnabled(false); m_Controls.initialValuesManager->setEnabled(false); m_Controls.initialValuesManager->setDataStorage(this->GetDataStorage()); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.initialValuesManager, SIGNAL(initialValuesChanged(void)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), m_Controls.initialValuesManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setVisible(bool))); //Concentration m_Controls.groupConcentration->hide(); m_Controls.groupBoxEnhancement->hide(); - m_Controls.groupBoxTurboFlash->hide(); m_Controls.radioButtonNoConversion->setChecked(true); m_Controls.groupBox_T1MapviaVFA->hide(); m_Controls.spinBox_baselineStartTimeStep->setValue(0); m_Controls.spinBox_baselineEndTimeStep->setValue(0); + m_Controls.spinBox_baselineEndTimeStep->setMinimum(0); + m_Controls.spinBox_baselineStartTimeStep->setMinimum(0); + m_Controls.groupBox_baselineRangeSelection->hide(); + + - connect(m_Controls.radioButtonTurboFlash, SIGNAL(toggled(bool)), m_Controls.groupBoxTurboFlash, SLOT(setVisible(bool))); - connect(m_Controls.radioButtonTurboFlash, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); - connect(m_Controls.relaxationtime, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); - connect(m_Controls.recoverytime, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); - connect(m_Controls.relaxivity, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); + connect(m_Controls.radioButton_absoluteEnhancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBoxEnhancement, SLOT(setVisible(bool))); + connect(m_Controls.radioButton_relativeEnchancement, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.factorSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.spinBox_baselineStartTimeStep, SIGNAL(valueChanged(int)), this, SLOT(UpdateGUIControls())); connect(m_Controls.spinBox_baselineEndTimeStep, SIGNAL(valueChanged(int)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_T1MapviaVFA, SLOT(setVisible(bool))); + connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.groupBox_baselineRangeSelection, SLOT(setVisible(bool))); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.FlipangleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.RelaxivitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); connect(m_Controls.TRSpinBox, SIGNAL(valueChanged(double)), this, SLOT(UpdateGUIControls())); m_Controls.PDWImageNodeSelector->SetNodePredicate(m_isValidPDWImagePredicate); m_Controls.PDWImageNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.PDWImageNodeSelector->SetInvalidInfo("Please select PDW Image."); m_Controls.PDWImageNodeSelector->setEnabled(false); connect(m_Controls.radioButtonUsingT1viaVFA, SIGNAL(toggled(bool)), m_Controls.PDWImageNodeSelector, SLOT(setEnabled(bool))); UpdateGUIControls(); } -bool MRPerfusionView::IsTurboFlashSequenceFlag() const -{ - return this->m_Controls.radioButtonTurboFlash->isChecked(); -}; void MRPerfusionView::UpdateGUIControls() { m_Controls.lineFitName->setPlaceholderText(QString::fromStdString(this->GetDefaultFitName())); m_Controls.lineFitName->setEnabled(!m_FittingInProgress); m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr || dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast - (m_selectedModelFactory.GetPointer()) != nullptr || - dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; - bool isNum2CXMFactory = dynamic_cast - (m_selectedModelFactory.GetPointer()) != nullptr; m_Controls.groupAIF->setVisible(isToftsFactory || is2CXMFactory); m_Controls.groupDescBrix->setVisible(isDescBrixFactory); - m_Controls.groupNum2CXM->setVisible(isNum2CXMFactory); m_Controls.groupConcentration->setVisible(isToftsFactory || is2CXMFactory ); m_Controls.groupBox_FitConfiguration->setVisible(m_selectedModelFactory); m_Controls.groupBox->setEnabled(!m_FittingInProgress); m_Controls.comboModel->setEnabled(!m_FittingInProgress); m_Controls.groupAIF->setEnabled(!m_FittingInProgress); m_Controls.groupDescBrix->setEnabled(!m_FittingInProgress); - m_Controls.groupNum2CXM->setEnabled(!m_FittingInProgress); m_Controls.groupConcentration->setEnabled(!m_FittingInProgress); m_Controls.groupBox_FitConfiguration->setEnabled(!m_FittingInProgress); m_Controls.radioROIbased->setEnabled(m_selectedMask.IsNotNull()); m_Controls.btnModelling->setEnabled(m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings()); - m_Controls.spinBox_baselineStartTimeStep->setEnabled(m_Controls.radioButtonTurboFlash->isChecked() || m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()); - m_Controls.spinBox_baselineEndTimeStep->setEnabled(m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked() || m_Controls.radioButtonTurboFlash->isChecked()); + m_Controls.spinBox_baselineStartTimeStep->setEnabled( m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()); + m_Controls.spinBox_baselineEndTimeStep->setEnabled(m_Controls.radioButton_absoluteEnhancement->isChecked() || m_Controls.radioButton_relativeEnchancement->isChecked() || m_Controls.radioButtonUsingT1viaVFA->isChecked()); } void MRPerfusionView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } if (m_selectedModelFactory) { this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); m_Controls.initialValuesManager->setInitialValues(m_selectedModelFactory->GetParameterNames(), m_selectedModelFactory->GetDefaultInitialParameterization()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.constraintManager->setChecker(this->m_modelConstraints, this->m_selectedModelFactory->GetParameterNames()); } UpdateGUIControls(); } std::string MRPerfusionView::GetFitName() const { std::string fitName = m_Controls.lineFitName->text().toStdString(); if (fitName.empty()) { fitName = m_Controls.lineFitName->placeholderText().toStdString(); } return fitName; } std::string MRPerfusionView::GetDefaultFitName() const { std::string defaultName = "undefined model"; if (this->m_selectedModelFactory.IsNotNull()) { defaultName = this->m_selectedModelFactory->GetClassID(); } if (this->m_Controls.radioPixelBased->isChecked()) { defaultName += "_pixel"; } else { defaultName += "_roi"; } return defaultName; } void MRPerfusionView::OnModellingButtonClicked() { //check if all static parameters set if (m_selectedModelFactory.IsNotNull() && CheckModelSettings()) { m_HasGeneratedNewInput = false; m_HasGeneratedNewInputAIF = false; mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExtToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isStanToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; - bool isNum2CXMFactory = dynamic_cast - (m_selectedModelFactory.GetPointer()) != nullptr; if (isDescBrixFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateDescriptiveBrixModel_PixelBased(fitSession, generator); } else { GenerateDescriptiveBrixModel_ROIBased(fitSession, generator); } } else if (isStanToftsFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } else if (isExtToftsFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } else if (is2CXMFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateAIFbasedModelFit_PixelBased(fitSession, generator); } else { GenerateAIFbasedModelFit_ROIBased(fitSession, generator); } } - else if (isNum2CXMFactory) - { - if (this->m_Controls.radioPixelBased->isChecked()) - { - GenerateAIFbasedModelFit_PixelBased(fitSession, - generator); - } - else - { - GenerateAIFbasedModelFit_ROIBased(fitSession, - generator); - } - } //add other models with else if if (generator.IsNotNull() && fitSession.IsNotNull()) { m_FittingInProgress = true; UpdateGUIControls(); DoFit(fitSession, generator); } else { QMessageBox box; box.setText("Fitting error!"); box.setInformativeText("Could not establish fitting job. Error when setting ab generator, model parameterizer or session info."); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } else { QMessageBox box; box.setText("Static parameters for model are not set!"); box.setInformativeText("Some static parameters, that are needed for calculation are not set and equal to zero. Modeling not possible"); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } void MRPerfusionView::OnNodeSelectionChanged(QList/*nodes*/) { m_selectedMaskNode = nullptr; m_selectedMask = nullptr; if (m_Controls.timeSeriesNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedNode = m_Controls.timeSeriesNodeSelector->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); if (m_selectedImage) { this->m_Controls.initialValuesManager->setReferenceImageGeometry(m_selectedImage->GetGeometry()); } else { this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } if (m_Controls.maskNodeSelector->GetSelectedNode().IsNotNull()) { this->m_selectedMaskNode = m_Controls.maskNodeSelector->GetSelectedNode(); this->m_selectedMask = dynamic_cast(m_selectedMaskNode->GetData()); if (this->m_selectedMask.IsNotNull() && this->m_selectedMask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit. Mask name: " << m_Controls.maskNodeSelector->GetSelectedNode()->GetName(); mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedMask = maskedImageTimeSelector->GetOutput(); } } if (m_selectedMask.IsNull()) { this->m_Controls.radioPixelBased->setChecked(true); } if (this->m_selectedImage.IsNotNull()) { m_Controls.spinBox_baselineStartTimeStep->setMaximum((this->m_selectedImage->GetDimension(3))-1); m_Controls.spinBox_baselineEndTimeStep->setMaximum((this->m_selectedImage->GetDimension(3)) - 1); } UpdateGUIControls(); } bool MRPerfusionView::CheckModelSettings() const { bool ok = true; //check whether any model is set at all. Otherwise exit with false if (m_selectedModelFactory.IsNotNull()) { bool isDescBrixFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isToftsFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr|| dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2CXMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; - bool isNum2CXMFactory = dynamic_cast - (m_selectedModelFactory.GetPointer()) != nullptr; if (isDescBrixFactory) { //if all static parameters for this model are set, exit with true, Otherwise exit with false ok = m_Controls.injectiontime->value() > 0; } - else if (isToftsFactory || is2CXMFactory || isNum2CXMFactory) + else if (isToftsFactory || is2CXMFactory) { if (this->m_Controls.radioAIFImage->isChecked()) { ok = ok && m_Controls.AIFMaskNodeSelector->GetSelectedNode().IsNotNull(); if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { ok = ok && m_Controls.AIFImageNodeSelector->GetSelectedNode().IsNotNull(); } } else if (this->m_Controls.radioAIFFile->isChecked()) { ok = ok && (this->AIFinputGrid.size() != 0) && (this->AIFinputFunction.size() != 0); } else { ok = false; } - if (this->m_Controls.radioButtonTurboFlash->isChecked() ) - { - ok = ok && (m_Controls.recoverytime->value() > 0); - ok = ok && (m_Controls.relaxationtime->value() > 0); - ok = ok && (m_Controls.relaxivity->value() > 0); - ok = ok && (m_Controls.AifRecoverytime->value() > 0); - ok = ok && CheckBaselineSelectionSettings(); - - } - else if (this->m_Controls.radioButton_absoluteEnhancement->isChecked() + if (this->m_Controls.radioButton_absoluteEnhancement->isChecked() || this->m_Controls.radioButton_relativeEnchancement->isChecked() ) { ok = ok && (m_Controls.factorSpinBox->value() > 0); ok = ok && CheckBaselineSelectionSettings(); } else if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked() ) { ok = ok && (m_Controls.FlipangleSpinBox->value() > 0); ok = ok && (m_Controls.TRSpinBox->value() > 0); ok = ok && (m_Controls.RelaxivitySpinBox->value() > 0); ok = ok && (m_Controls.PDWImageNodeSelector->GetSelectedNode().IsNotNull()); ok = ok && CheckBaselineSelectionSettings(); } else if (this->m_Controls.radioButtonNoConversion->isChecked()) { ok = ok && true; } else { ok = false; } - if (isNum2CXMFactory) - { - ok = ok && (this->m_Controls.odeStepSize->value() > 0); - } - } //add other models as else if and check whether all needed static parameters are set else { ok = false; } if (this->m_Controls.radioButton_StartParameters->isChecked() && !this->m_Controls.initialValuesManager->hasValidInitialValues()) { std::string warning = "Warning. Invalid start parameters. At least one parameter as an invalid image setting as source."; MITK_ERROR << warning; m_Controls.infoBox->append(QString("") + QString::fromStdString(warning) + QString("")); ok = false; }; } else { ok = false; } return ok; } bool MRPerfusionView::CheckBaselineSelectionSettings() const { return m_Controls.spinBox_baselineStartTimeStep->value() <= m_Controls.spinBox_baselineEndTimeStep->value(); } void MRPerfusionView::ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const { if (m_Controls.radioButton_StartParameters->isChecked()) { //use user defined initial parameters mitk::InitialParameterizationDelegateBase::Pointer paramDelegate = m_Controls.initialValuesManager->getInitialParametrizationDelegate(); parameterizer->SetInitialParameterizationDelegate(paramDelegate); } } void MRPerfusionView::GenerateDescriptiveBrixModel_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelParameterizer::New(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(m_Controls.injectiontime->value()); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(this->m_selectedImage); imageTimeSelector->SetTimeNr(0); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::DescriptivePharmacokineticBrixModelParameterizer::BaseImageType::Pointer baseImage; mitk::CastToItkImage(imageTimeSelector->GetOutput(), baseImage); modelParameterizer->SetBaseImage(baseImage); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } void MRPerfusionView::GenerateDescriptiveBrixModel_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::Pointer modelParameterizer = mitk::DescriptivePharmacokineticBrixModelValueBasedParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Model configuration (static parameters) can be done now modelParameterizer->SetTau(m_Controls.injectiontime->value()); modelParameterizer->SetBaseValue(roiSignal[0]); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void MRPerfusionView::GenerateLinearModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = this->m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); } template void MRPerfusionView::GenerateLinearModelFit_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Model configuration (static parameters) can be done now this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); } template void MRPerfusionView::GenerateAIFbasedModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); PrepareConcentrationImage(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; GetAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); - mitk::NumericTwoCompartmentExchangeModelParameterizer* numTCXParametrizer = - dynamic_cast - (modelParameterizer.GetPointer()); - - if (numTCXParametrizer) - { - numTCXParametrizer->SetODEINTStepSize(this->m_Controls.odeStepSize->value()); - } //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = this->m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(this->m_inputImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, this->m_inputImage, mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } template void MRPerfusionView::GenerateAIFbasedModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { if (m_selectedMask.IsNull()) { return; } mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); PrepareConcentrationImage(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::AterialInputFunctionType aifTimeGrid; GetAIF(aif, aifTimeGrid); modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); - mitk::NumericTwoCompartmentExchangeModelParameterizer* numTCXParametrizer = - dynamic_cast - (modelParameterizer.GetPointer()); - - if (numTCXParametrizer) - { - numTCXParametrizer->SetODEINTStepSize(this->m_Controls.odeStepSize->value()); - } //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(this->m_inputImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(this->m_inputImage)); generator = fitGenerator.GetPointer(); std::string roiUID = this->m_selectedMask->GetUID(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, this->m_inputImage, mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); infoSignal.clear(); for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } void MRPerfusionView::DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator) { this->m_Controls.infoBox->append(QString("" + QString("Fitting Data Set . . .") + QString (""))); ///////////////////////// //create job and put it into the thread pool mitk::modelFit::ModelFitResultNodeVectorType additionalNodes; if (m_HasGeneratedNewInput) { additionalNodes.push_back(m_inputNode); } if (m_HasGeneratedNewInputAIF) { additionalNodes.push_back(m_inputAIFNode); } ParameterFitBackgroundJob* pJob = new ParameterFitBackgroundJob(generator, fitSession, this->m_selectedNode, additionalNodes); pJob->setAutoDelete(true); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnJobFinished())); connect(pJob, SIGNAL(ResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), this, SLOT(OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(JobProgress(double)), this, SLOT(OnJobProgress(double))); connect(pJob, SIGNAL(JobStatusChanged(QString)), this, SLOT(OnJobStatusChanged(QString))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } MRPerfusionView::MRPerfusionView() : m_FittingInProgress(false), m_HasGeneratedNewInput(false), m_HasGeneratedNewInputAIF(false) { m_selectedImage = nullptr; m_selectedMask = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::DescriptivePharmacokineticBrixModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::StandardToftsModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExtendedToftsModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoCompartmentExchangeModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); - factory = mitk::NumericTwoCompartmentExchangeModelFactory::New().GetPointer(); - m_FactoryStack.push_back(factory); mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isLegacyMask = mitk::NodePredicateAnd::New(isImage, isBinary); mitk::NodePredicateDimension::Pointer is3D = mitk::NodePredicateDimension::New(3); mitk::NodePredicateOr::Pointer isMask = mitk::NodePredicateOr::New(isLegacyMask, isLabelSet); mitk::NodePredicateAnd::Pointer isNoMask = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isMask)); mitk::NodePredicateAnd::Pointer is3DImage = mitk::NodePredicateAnd::New(isImage, is3D, isNoMask); this->m_IsMaskPredicate = mitk::NodePredicateAnd::New(isMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); this->m_IsNoMaskImagePredicate = mitk::NodePredicateAnd::New(isNoMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); auto isDynamicData = mitk::NodePredicateFunction::New([](const mitk::DataNode* node) { return (node && node->GetData() && node->GetData()->GetTimeSteps() > 1); }); auto modelFitResultRelationRule = mitk::ModelFitResultRelationRule::New(); auto isNoModelFitNodePredicate = mitk::NodePredicateNot::New(modelFitResultRelationRule->GetConnectedSourcesDetector()); this->m_isValidPDWImagePredicate = mitk::NodePredicateAnd::New(is3DImage, isNoModelFitNodePredicate); this->m_isValidTimeSeriesImagePredicate = mitk::NodePredicateAnd::New(isDynamicData, isImage, isNoMask); } void MRPerfusionView::OnJobFinished() { this->m_Controls.infoBox->append(QString("Fitting finished.")); this->m_FittingInProgress = false; this->UpdateGUIControls(); }; void MRPerfusionView::OnJobError(QString err) { MITK_ERROR << err.toStdString().c_str(); m_Controls.infoBox->append(QString("") + err + QString("")); }; void MRPerfusionView::OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob) { //Store the resulting parameter fit image via convenience helper function in data storage //(handles the correct generation of the nodes and their properties) mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), results, pJob->GetParentNode()); //this stores the concentration image and AIF concentration image, if generated for this fit in the storage. //if not generated for this fit, relevant nodes are empty. mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), pJob->GetAdditionalRelevantNodes(), pJob->GetParentNode()); }; void MRPerfusionView::OnJobProgress(double progress) { QString report = QString("Progress. ") + QString::number(progress); this->m_Controls.infoBox->append(report); }; void MRPerfusionView::OnJobStatusChanged(QString info) { this->m_Controls.infoBox->append(info); } void MRPerfusionView::InitModelComboBox() const { this->m_Controls.comboModel->clear(); this->m_Controls.comboModel->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.comboModel->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.comboModel->setCurrentIndex(0); }; mitk::DataNode::Pointer MRPerfusionView::GenerateConcentrationNode(mitk::Image* image, const std::string& nodeName) const { if (!image) { mitkThrow() << "Cannot generate concentration node. Passed image is null. parameter name: "; } mitk::DataNode::Pointer result = mitk::DataNode::New(); result->SetData(image); result->SetName(nodeName); result->SetVisibility(true); return result; }; mitk::Image::Pointer MRPerfusionView::ConvertConcentrationImage(bool AIFMode) { //Compute Concentration image mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); if (m_Controls.checkDedicatedAIFImage->isChecked() && AIFMode) { concentrationGen->SetDynamicImage(this->m_selectedAIFImage); } else { concentrationGen->SetDynamicImage(this->m_selectedImage); } - concentrationGen->SetisTurboFlashSequence(IsTurboFlashSequenceFlag()); concentrationGen->SetAbsoluteSignalEnhancement(m_Controls.radioButton_absoluteEnhancement->isChecked()); concentrationGen->SetRelativeSignalEnhancement(m_Controls.radioButton_relativeEnchancement->isChecked()); concentrationGen->SetUsingT1Map(m_Controls.radioButtonUsingT1viaVFA->isChecked()); - if (IsTurboFlashSequenceFlag()) - { - if (AIFMode) - { - concentrationGen->SetRecoveryTime(m_Controls.AifRecoverytime->value()); - } - else - { - concentrationGen->SetRecoveryTime(m_Controls.recoverytime->value()); - } - concentrationGen->SetRelaxationTime(m_Controls.relaxationtime->value()); - concentrationGen->SetRelaxivity(m_Controls.relaxivity->value()); - concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); - concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); - - } - else if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked()) + if (this->m_Controls.radioButtonUsingT1viaVFA->isChecked()) { - concentrationGen->SetRecoveryTime(m_Controls.TRSpinBox->value()); + concentrationGen->SetRepetitionTime(m_Controls.TRSpinBox->value()); concentrationGen->SetRelaxivity(m_Controls.RelaxivitySpinBox->value()); - concentrationGen->SetT10Image(dynamic_cast(m_Controls.PDWImageNodeSelector->GetSelectedNode()->GetData())); + concentrationGen->SetPDWImage(dynamic_cast(m_Controls.PDWImageNodeSelector->GetSelectedNode()->GetData())); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); //Convert Flipangle from degree to radiant double alpha = m_Controls.FlipangleSpinBox->value()/360*2* boost::math::constants::pi(); concentrationGen->SetFlipAngle(alpha); + double alphaPDW = m_Controls.FlipanglePDWSpinBox->value() / 360 * 2 * boost::math::constants::pi(); + concentrationGen->SetFlipAnglePDW(alphaPDW); + } else { concentrationGen->SetFactor(m_Controls.factorSpinBox->value()); concentrationGen->SetBaselineStartTimeStep(m_Controls.spinBox_baselineStartTimeStep->value()); concentrationGen->SetBaselineEndTimeStep(m_Controls.spinBox_baselineEndTimeStep->value()); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); return concentrationImage; } void MRPerfusionView::GetAIF(mitk::AIFBasedModelBase::AterialInputFunctionType& aif, mitk::AIFBasedModelBase::AterialInputFunctionType& aifTimeGrid) { if (this->m_Controls.radioAIFFile->isChecked()) { aif.clear(); aifTimeGrid.clear(); aif.SetSize(AIFinputFunction.size()); aifTimeGrid.SetSize(AIFinputGrid.size()); aif.fill(0.0); aifTimeGrid.fill(0.0); itk::Array::iterator aifPos = aif.begin(); for (std::vector::const_iterator pos = AIFinputFunction.begin(); pos != AIFinputFunction.end(); ++pos, ++aifPos) { *aifPos = *pos; } itk::Array::iterator gridPos = aifTimeGrid.begin(); for (std::vector::const_iterator pos = AIFinputGrid.begin(); pos != AIFinputGrid.end(); ++pos, ++gridPos) { *gridPos = *pos; } } else if (this->m_Controls.radioAIFImage->isChecked()) { aif.clear(); aifTimeGrid.clear(); mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); //Hematocrit level aifGenerator->SetHCL(this->m_Controls.HCLSpinBox->value()); //mask settings this->m_selectedAIFMaskNode = m_Controls.AIFMaskNodeSelector->GetSelectedNode(); this->m_selectedAIFMask = dynamic_cast(this->m_selectedAIFMaskNode->GetData()); if (this->m_selectedAIFMask->GetTimeSteps() > 1) { MITK_INFO << "Selected AIF mask has multiple timesteps. Only use first timestep to mask model fit. AIF Mask name: " << m_selectedAIFMaskNode->GetName() ; mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedAIFMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedAIFMask = maskedImageTimeSelector->GetOutput(); } if (this->m_selectedAIFMask.IsNotNull()) { aifGenerator->SetMask(this->m_selectedAIFMask); } //image settings if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { this->m_selectedAIFImageNode = m_Controls.AIFImageNodeSelector->GetSelectedNode(); this->m_selectedAIFImage = dynamic_cast(this->m_selectedAIFImageNode->GetData()); } else { this->m_selectedAIFImageNode = m_selectedNode; this->m_selectedAIFImage = m_selectedImage; } this->PrepareAIFConcentrationImage(); aifGenerator->SetDynamicImage(this->m_inputAIFImage); aif = aifGenerator->GetAterialInputFunction(); aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); } else { mitkThrow() << "Cannot generate AIF. View is in a invalid state. No AIF mode selected."; } } void MRPerfusionView::LoadAIFfromFile() { QFileDialog dialog; dialog.setNameFilter(tr("Images (*.csv")); QString fileName = dialog.getOpenFileName(); m_Controls.aifFilePath->setText(fileName); std::string m_aifFilePath = fileName.toStdString(); //Read Input typedef boost::tokenizer< boost::escaped_list_separator > Tokenizer; ///////////////////////////////////////////////////////////////////////////////////////////////// //AIF Data std::ifstream in1(m_aifFilePath.c_str()); if (!in1.is_open()) { this->m_Controls.infoBox->append(QString("Could not open AIF File!")); } std::vector< std::string > vec1; std::string line1; while (getline(in1, line1)) { Tokenizer tok(line1); vec1.assign(tok.begin(), tok.end()); this->AIFinputGrid.push_back(convertToDouble(vec1[0])); this->AIFinputFunction.push_back(convertToDouble(vec1[1])); } } void MRPerfusionView::PrepareConcentrationImage() { mitk::Image::Pointer concentrationImage = this->m_selectedImage; mitk::DataNode::Pointer concentrationNode = this->m_selectedNode; m_HasGeneratedNewInput = false; if (!this->m_Controls.radioButtonNoConversion->isChecked()) { concentrationImage = this->ConvertConcentrationImage(false); concentrationNode = GenerateConcentrationNode(concentrationImage, "Concentration"); m_HasGeneratedNewInput = true; } m_inputImage = concentrationImage; m_inputNode = concentrationNode; } void MRPerfusionView::PrepareAIFConcentrationImage() { mitk::Image::Pointer concentrationImage = this->m_selectedImage; mitk::DataNode::Pointer concentrationNode = this->m_selectedNode; m_HasGeneratedNewInputAIF = false; if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { concentrationImage = this->m_selectedAIFImage; concentrationNode = this->m_selectedAIFImageNode; } if (!this->m_Controls.radioButtonNoConversion->isChecked()) { - if (!IsTurboFlashSequenceFlag() && !this->m_Controls.checkDedicatedAIFImage->isChecked()) + if (!this->m_Controls.checkDedicatedAIFImage->isChecked()) { if (m_inputImage.IsNull()) { mitkThrow() << "Cannot get AIF concentration image. Invalid view state. Input image is not defined yet, but should be."; } //we can directly use the concentration input image/node (generated by GetConcentrationImage) also for the AIF concentrationImage = this->m_inputImage; concentrationNode = this->m_inputNode; } else { concentrationImage = this->ConvertConcentrationImage(true); concentrationNode = GenerateConcentrationNode(concentrationImage, "AIF Concentration"); m_HasGeneratedNewInputAIF = true; } } m_inputAIFImage = concentrationImage; m_inputAIFNode = concentrationNode; } mitk::ModelFitFunctorBase::Pointer MRPerfusionView::CreateDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) const { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::Pointer chi2 = mitk::NormalizedSumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); if (m_Controls.checkBox_Constraints->isChecked()) { fitFunctor->SetConstraintChecker(m_modelConstraints); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); fitFunctor->SetDebugParameterMaps(m_Controls.checkDebug->isChecked()); return fitFunctor.GetPointer(); } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.h b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.h index 7c65eca0a4..7c85b296a4 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.h +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionView.h @@ -1,206 +1,205 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef MRPerfusionView_h #define MRPerfusionView_h #include #include "QmitkAbstractView.h" #include "itkCommand.h" #include "ui_MRPerfusionViewControls.h" #include "mitkModelBase.h" #include "QmitkParameterFitBackgroundJob.h" #include "mitkModelFitResultHelper.h" #include "mitkModelFactoryBase.h" #include "mitkLevenbergMarquardtModelFitFunctor.h" #include "mitkSimpleBarrierConstraintChecker.h" #include "mitkAIFBasedModelBase.h" /*! * @brief Test Plugin for SUV calculations of PET images */ class MRPerfusionView : public QmitkAbstractView { Q_OBJECT public: /*! @brief The view's unique ID - required by MITK */ static const std::string VIEW_ID; MRPerfusionView(); protected slots: void OnModellingButtonClicked(); void OnJobFinished(); void OnJobError(QString err); void OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob); void OnJobProgress(double progress); void OnJobStatusChanged(QString info); void OnModellSet(int); void LoadAIFfromFile(); /**Sets visibility and enabled state of the GUI depending on the settings and workflow state.*/ void UpdateGUIControls(); protected: typedef QList SelectedDataNodeVectorType; // Overridden base class functions /*! * @brief Sets up the UI controls and connects the slots and signals. Gets * called by the framework to create the GUI at the right time. * @param[in,out] parent The parent QWidget, as this class itself is not a QWidget * subclass. */ void CreateQtPartControl(QWidget* parent) override; /*! * @brief Sets the focus to the plot curve button. Gets called by the framework to set the * focus on the right widget. */ void SetFocus() override; /*! @brief Generates a configured fit generator and the corresponding modelinfo for a descriptive brix model with pixel based strategy. * @remark add GenerateFunction for each model in the Combo box*/ void GenerateDescriptiveBrixModel_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator); void GenerateDescriptiveBrixModel_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator); template void GenerateLinearModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator); template void GenerateLinearModelFit_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator); template void GenerateAIFbasedModelFit_ROIBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator); template void GenerateAIFbasedModelFit_PixelBased(mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator); /** Helper function that configures the initial parameter strategy of a parameterizer according to the settings of the GUI.*/ void ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const; /*! Starts the fitting job with the passed generator and session info*/ void DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator); /**Checks if the settings in the GUI are valid for the chosen model.*/ bool CheckModelSettings() const; bool CheckBaselineSelectionSettings() const; void InitModelComboBox() const; /*! Helper method that generates a node for the passed concentration image.*/ mitk::DataNode::Pointer GenerateConcentrationNode(mitk::Image* image, const std::string& nodeName) const; void OnNodeSelectionChanged(QList /*nodes*/); /*! @brief The view's UI controls */ Ui::MRPerfusionViewControls m_Controls; /* Nodes selected by user/ui for the fit */ mitk::DataNode::Pointer m_selectedNode; mitk::DataNode::Pointer m_selectedMaskNode; mitk::DataNode::Pointer m_selectedAIFMaskNode; mitk::DataNode::Pointer m_selectedAIFImageNode; /* Images selected by user/ui for the fit */ mitk::Image::Pointer m_selectedImage; mitk::Image::Pointer m_selectedMask; mitk::Image::Pointer m_selectedAIFMask; mitk::Image::Pointer m_selectedAIFImage; mitk::ModelFactoryBase::Pointer m_selectedModelFactory; mitk::SimpleBarrierConstraintChecker::Pointer m_modelConstraints; private: - bool IsTurboFlashSequenceFlag() const; bool m_FittingInProgress; typedef std::vector ModelFactoryStackType; ModelFactoryStackType m_FactoryStack; /**Converts the selected image to a concentration image based on the given gui settings. AIFMode controls if the concentration image for the fit input or the AIF will be converted.*/ mitk::Image::Pointer ConvertConcentrationImage(bool AIFMode); /**Helper function that (depending on the gui settings) prepares m_inputNode and m_inputImage. Either by directly pass back the selected image/node or the newly generated concentration image/node. After calling this method m_inputImage are always what should be used as input image for the fitting.*/ void PrepareConcentrationImage(); /**Helper function that (depending on the gui settings) prepares m_inputAIFNode and m_inputAIFImage. Either by directly pass back the selected image/node or the newly generated concentration image/node. After calling this method m_inputAIFImage are always what should be used as AIF image for the fitting.*/ void PrepareAIFConcentrationImage(); /**Helper function that (depending on the gui settings) generates and passes back the AIF and its time grid that should be used for fitting. @remark the parameters aif and aifTimeGrid will be initialized accordingly if the method returns.*/ void GetAIF(mitk::AIFBasedModelBase::AterialInputFunctionType& aif, mitk::AIFBasedModelBase::AterialInputFunctionType& aifTimeGrid); /**Helper function that generates a default fitting functor * default is a levenberg marquart based optimizer with all scales set to 1.0. * Constraint setter will be set based on the gui setting and a evaluation parameter * "sum of squared differences" will always be set.*/ mitk::ModelFitFunctorBase::Pointer CreateDefaultFitFunctor(const mitk::ModelParameterizerBase* parameterizer) const; /**Returns the default fit name, derived from the current GUI settings.*/ std::string GetDefaultFitName() const; /**Returns the current set name of the fit (either default name or use defined name).*/ std::string GetFitName() const; std::vector AIFinputGrid; std::vector AIFinputFunction; mitk::NodePredicateBase::Pointer m_IsNoMaskImagePredicate; mitk::NodePredicateBase::Pointer m_IsMaskPredicate; mitk::NodePredicateBase::Pointer m_isValidPDWImagePredicate; mitk::NodePredicateBase::Pointer m_isValidTimeSeriesImagePredicate; /* Node used for the fit (my be the selected image or converted ones (depending on the ui settings */ mitk::DataNode::Pointer m_inputNode; mitk::DataNode::Pointer m_inputAIFNode; bool m_HasGeneratedNewInput; bool m_HasGeneratedNewInputAIF; /* Image used for the fit (my be the selected image or converted ones (depending on the ui settings */ mitk::Image::Pointer m_inputImage; mitk::Image::Pointer m_inputAIFImage; }; #endif diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui index b4e0cb396d..fcdc0cbdd8 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/src/internal/MRPerfusionViewControls.ui @@ -1,745 +1,662 @@ MRPerfusionViewControls 0 0 489 1124 0 0 0 0 QmitkTemplate QLayout::SetDefaultConstraint 4 QLayout::SetDefaultConstraint Selected Time Series: Selected Mask: 0 40 0 40 0 0 0 40 Fitting strategy 5 QLayout::SetMinAndMaxSize 5 5 5 5 0 0 Pixel based true 0 0 ROI based Select pharmacokinetic model... - AIF Mask: + Arterial Input Function (AIF): Select AIF from Image: 20 AIF Mask: false Dedicated AIF Image: 0 40 false 0 40 0 Select AIF from File: false Browse 5 Hematocrit Level [ ]: 1.000000000000000 0.010000000000000 Descriptive Brix-Model Parameters: QFormLayout::AllNonFixedFieldsGrow Injection Time [min]: - - - - Numeric Two Compartment Exchange Model Parameters: - - - - - - ODE Int Step Size [s]: - - - - - - - 3 - - - 0.001000000000000 - - - 0.050000000000000 - - - - - - 0 0 Model Fit Configuration 0 0 0 0 459 - 84 + 112 Start parameter Enter Fit Starting Parameters 0 0 0 0 - 459 + 158 232 Constraints Enter Constraints for Fit Parameters 0 0 0 200 0 0 - 449 - 397 + 345 + 306 Conversion: Signal to Concentration 10 No Signal Conversion true Absolute Signal Enhancement Relative Signal Enhancement - Using T1 Map via Variable Flip Angle - - - - - - - TurboFLASH Sequence + Variable Flip Angle Qt::Vertical 20 40 Enhancement Parameters: Conversion Factor k: - - - - Baseline Range Selection: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - - Start Time Frame - - - - - - - End Time Frame - - - - - - - - - T1 Map via Variable Flip Angle Parameters: + Variable Flip Angle Parameters: + + + + Repetition Time TR [ms] : + + + + + + Flip Angle [ ° ] : - - + + - Repetition Time TR [ms] : + Proton Density Weighted Image : - + + + + Relaxivity [mM⁻¹ s⁻¹] : - - - - + - - - - Proton Density Weighted Image : - - - 10000.000000000000000 - - + + + + Flip Angle PDW Image [ ° ]: + + + + + - - - true - + - Turbo FLASH Parameters: + Baseline Range Selection: - - - - - Recovery Time [s]: - - - - - - - - - - AIF Recovery Time [s]: - - + + + - + - - - - - + + - Relaxation Time [s]: + Start Time Frame - - - - + - Relaxivity [ ]: + End Time Frame 5 Fitting name: <html><head/><body><p>Name/prefix that should be used for the fitting results.</p><p>May be explicitly defined by the user.</p></body></html> default fit name Start Modelling Generate debug parameter images 0 0 true Qt::Vertical QSizePolicy::Expanding 20 40 QmitkSimpleBarrierManagerWidget QWidget
QmitkSimpleBarrierManagerWidget.h
QmitkInitialValuesManagerWidget QWidget
QmitkInitialValuesManagerWidget.h
QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/manifest_headers.cmake index 87c3fac8ac..704eed6542 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "PET Dynamic DataFit Plugin") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "c.debus@dkfz.de") +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp index 204d40ef80..5b5eadb97b 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.pet/src/internal/PETDynamicView.cpp @@ -1,946 +1,923 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "PETDynamicView.h" #include "mitkWorkbenchUtil.h" #include "mitkAterialInputFunctionGenerator.h" #include "mitkOneTissueCompartmentModelFactory.h" #include "mitkOneTissueCompartmentModelParameterizer.h" #include "mitkExtendedOneTissueCompartmentModelFactory.h" #include "mitkExtendedOneTissueCompartmentModelParameterizer.h" #include "mitkTwoTissueCompartmentFDGModelFactory.h" #include "mitkTwoTissueCompartmentFDGModelParameterizer.h" #include "mitkTwoTissueCompartmentModelFactory.h" #include "mitkTwoTissueCompartmentModelParameterizer.h" -#include "mitkNumericTwoTissueCompartmentModelFactory.h" -#include "mitkNumericTwoTissueCompartmentModelParameterizer.h" - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Includes for image casting between ITK and MITK #include #include "mitkImageCast.h" #include "mitkITKImageImport.h" #include #include #include const std::string PETDynamicView::VIEW_ID = "org.mitk.views.pharmacokinetics.pet"; inline double convertToDouble(const std::string& data) { std::istringstream stepStream(data); double value = 0.0; stepStream >> value; return value; } void PETDynamicView::SetFocus() { m_Controls.btnModelling->setFocus(); } void PETDynamicView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.btnModelling->setEnabled(false); m_Controls.errorMessageLabel->hide(); this->InitModelComboBox(); connect(m_Controls.btnModelling, SIGNAL(clicked()), this, SLOT(OnModellingButtonClicked())); connect(m_Controls.comboModel, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.radioPixelBased, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); //AIF setting m_Controls.groupAIF->hide(); m_Controls.btnAIFFile->setEnabled(false); m_Controls.btnAIFFile->setEnabled(false); m_Controls.radioAIFImage->setChecked(true); m_Controls.comboAIFMask->SetDataStorage(this->GetDataStorage()); m_Controls.comboAIFMask->SetPredicate(m_IsMaskPredicate); m_Controls.comboAIFMask->setVisible(true); m_Controls.comboAIFMask->setEnabled(true); m_Controls.comboAIFImage->SetDataStorage(this->GetDataStorage()); m_Controls.comboAIFImage->SetPredicate(m_IsNoMaskImagePredicate); m_Controls.comboAIFImage->setEnabled(false); m_Controls.checkDedicatedAIFImage->setEnabled(true); m_Controls.HCLSpinBox->setValue(0.0); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFMask, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.labelAIFMask, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFMask, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.checkDedicatedAIFImage, SLOT(setVisible(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFImage, SLOT(setVisible(bool))); connect(m_Controls.checkDedicatedAIFImage, SIGNAL(toggled(bool)), m_Controls.comboAIFImage, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFImage, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.btnAIFFile, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), m_Controls.aifFilePath, SLOT(setEnabled(bool))); connect(m_Controls.radioAIFFile, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.btnAIFFile, SIGNAL(clicked()), this, SLOT(LoadAIFfromFile())); //Model fit configuration m_Controls.groupBox_FitConfiguration->hide(); m_Controls.checkBox_Constraints->setEnabled(false); m_Controls.constraintManager->setEnabled(false); m_Controls.initialValuesManager->setEnabled(false); m_Controls.initialValuesManager->setDataStorage(this->GetDataStorage()); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), this, SLOT(UpdateGUIControls())); connect(m_Controls.initialValuesManager, SIGNAL(initialValuesChanged(void)), this, SLOT(UpdateGUIControls())); connect(m_Controls.radioButton_StartParameters, SIGNAL(toggled(bool)), m_Controls.initialValuesManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setEnabled(bool))); connect(m_Controls.checkBox_Constraints, SIGNAL(toggled(bool)), m_Controls.constraintManager, SLOT(setVisible(bool))); UpdateGUIControls(); } void PETDynamicView::UpdateGUIControls() { m_Controls.lineFitName->setPlaceholderText(QString::fromStdString(this->GetDefaultFitName())); m_Controls.lineFitName->setEnabled(!m_FittingInProgress); m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); bool is1TCMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isExt1TCMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isFDGCMFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool is2TCMFactory = dynamic_cast - (m_selectedModelFactory.GetPointer()) != nullptr || - dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; m_Controls.groupAIF->setVisible(is1TCMFactory || isExt1TCMFactory || isFDGCMFactory || is2TCMFactory); m_Controls.groupBox_FitConfiguration->setVisible(m_selectedModelFactory); m_Controls.groupBox->setEnabled(!m_FittingInProgress); m_Controls.comboModel->setEnabled(!m_FittingInProgress); m_Controls.groupAIF->setEnabled(!m_FittingInProgress); m_Controls.groupBox_FitConfiguration->setEnabled(!m_FittingInProgress); m_Controls.radioROIbased->setEnabled(m_selectedMask.IsNotNull()); m_Controls.btnModelling->setEnabled(m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings()); } //void PETDynamicView::OnModelSettingChanged() //{ // bool ok = m_selectedImage.IsNotNull() && m_selectedModelFactory.IsNotNull() && !m_FittingInProgress && CheckModelSettings(); // m_Controls.btnModelling->setEnabled(ok); //} void PETDynamicView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } if (m_selectedModelFactory) { this->m_modelConstraints = dynamic_cast (m_selectedModelFactory->CreateDefaultConstraints().GetPointer()); m_Controls.initialValuesManager->setInitialValues(m_selectedModelFactory->GetParameterNames(), m_selectedModelFactory->GetDefaultInitialParameterization()); if (this->m_modelConstraints.IsNull()) { this->m_modelConstraints = mitk::SimpleBarrierConstraintChecker::New(); } m_Controls.constraintManager->setChecker(this->m_modelConstraints, this->m_selectedModelFactory->GetParameterNames()); } m_Controls.checkBox_Constraints->setEnabled(m_modelConstraints.IsNotNull()); UpdateGUIControls(); } std::string PETDynamicView::GetFitName() const { std::string fitName = m_Controls.lineFitName->text().toStdString(); if (fitName.empty()) { fitName = m_Controls.lineFitName->placeholderText().toStdString(); } return fitName; } std::string PETDynamicView::GetDefaultFitName() const { std::string defaultName = "undefined model"; if (this->m_selectedModelFactory.IsNotNull()) { defaultName = this->m_selectedModelFactory->GetClassID(); } if (this->m_Controls.radioPixelBased->isChecked()) { defaultName += "_pixel"; } else { defaultName += "_roi"; } return defaultName; } void PETDynamicView::OnModellingButtonClicked() { //check if all static parameters set if (m_selectedModelFactory.IsNotNull() && CheckModelSettings()) { mitk::ParameterFitImageGeneratorBase::Pointer generator = nullptr; mitk::modelFit::ModelFitInfo::Pointer fitSession = nullptr; bool isOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isextOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isFDGFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isTTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; - bool isNumTTCFactory = dynamic_cast - (m_selectedModelFactory.GetPointer()) != nullptr; if (isOTCFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isextOTCFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isFDGFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } else if (isTTCFactory) { if (this->m_Controls.radioPixelBased->isChecked()) { GenerateModelFit_PixelBased(fitSession, generator); } else { GenerateModelFit_ROIBased(fitSession, generator); } } - else if (isNumTTCFactory) - { - if (this->m_Controls.radioPixelBased->isChecked()) - { - GenerateModelFit_PixelBased(fitSession, - generator); - } - else - { - GenerateModelFit_ROIBased(fitSession, - generator); - } - } //add other models with else if if (generator.IsNotNull() && fitSession.IsNotNull()) { m_FittingInProgress = true; DoFit(fitSession, generator); } else { QMessageBox box; box.setText("Fitting error!"); box.setInformativeText("Could not establish fitting job. Error when setting ab generator, model parameterizer or session info."); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } else { QMessageBox box; box.setText("Static parameters for model are not set!"); box.setInformativeText("Some static parameters, that are needed for calculation are not set and equal to zero. Modeling not possible"); box.setStandardButtons(QMessageBox::Ok); box.setDefaultButton(QMessageBox::Ok); box.setIcon(QMessageBox::Warning); box.exec(); } } void PETDynamicView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList& selectedNodes) { m_selectedMaskNode = nullptr; m_selectedMask = nullptr; m_Controls.errorMessageLabel->setText(""); m_Controls.masklabel->setText("No (valid) mask selected."); m_Controls.timeserieslabel->setText("No (valid) series selected."); QList nodes = selectedNodes; if (nodes.size() > 0 && this->m_IsNoMaskImagePredicate->CheckNode(nodes.front())) { this->m_selectedNode = nodes.front(); auto selectedImage = dynamic_cast(this->m_selectedNode->GetData()); m_Controls.timeserieslabel->setText((this->m_selectedNode->GetName()).c_str()); if (selectedImage != this->m_selectedImage) { if (selectedImage) { this->m_Controls.initialValuesManager->setReferenceImageGeometry(selectedImage->GetGeometry()); } else { this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } } this->m_selectedImage = selectedImage; nodes.pop_front(); } else { this->m_selectedNode = nullptr; this->m_selectedImage = nullptr; this->m_Controls.initialValuesManager->setReferenceImageGeometry(nullptr); } if (nodes.size() > 0 && this->m_IsMaskPredicate->CheckNode(nodes.front())) { this->m_selectedMaskNode = nodes.front(); this->m_selectedMask = dynamic_cast(this->m_selectedMaskNode->GetData()); if (this->m_selectedMask->GetTimeSteps() > 1) { MITK_INFO << "Selected mask has multiple timesteps. Only use first timestep to mask model fit. Mask name: " << m_selectedMaskNode->GetName(); mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedMask = maskedImageTimeSelector->GetOutput(); } m_Controls.masklabel->setText((this->m_selectedMaskNode->GetName()).c_str()); } if (m_selectedMask.IsNull()) { this->m_Controls.radioPixelBased->setChecked(true); } m_Controls.errorMessageLabel->show(); UpdateGUIControls(); } bool PETDynamicView::CheckModelSettings() const { bool ok = true; //check whether any model is set at all. Otherwise exit with false if (m_selectedModelFactory.IsNotNull()) { bool isOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isextOTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isFDGFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; bool isTTCFactory = dynamic_cast (m_selectedModelFactory.GetPointer()) != nullptr; - bool isNumTTCFactory = dynamic_cast - (m_selectedModelFactory.GetPointer()) != nullptr; - if (isOTCFactory || isextOTCFactory || isFDGFactory || isTTCFactory || isNumTTCFactory) + if (isOTCFactory || isextOTCFactory || isFDGFactory || isTTCFactory) { if (this->m_Controls.radioAIFImage->isChecked()) { ok = ok && m_Controls.comboAIFMask->GetSelectedNode().IsNotNull(); if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { ok = ok && m_Controls.comboAIFImage->GetSelectedNode().IsNotNull(); } } else if (this->m_Controls.radioAIFFile->isChecked()) { ok = ok && (this->AIFinputGrid.size() != 0) && (this->AIFinputFunction.size() != 0); } else { ok = false; } } //add other models as else if and check whether all needed static parameters are set else { ok = false; } if (this->m_Controls.radioButton_StartParameters->isChecked() && !this->m_Controls.initialValuesManager->hasValidInitialValues()) { std::string warning = "Warning. Invalid start parameters. At least one parameter as an invalid image setting as source."; MITK_ERROR << warning; m_Controls.infoBox->append(QString("") + QString::fromStdString(warning) + QString("")); ok = false; }; } else { ok = false; } return ok; } void PETDynamicView::ConfigureInitialParametersOfParameterizer(mitk::ModelParameterizerBase* parameterizer) const { if (m_Controls.radioButton_StartParameters->isChecked()) { //use user defined initial parameters mitk::InitialParameterizationDelegateBase::Pointer paramDelegate = m_Controls.initialValuesManager->getInitialParametrizationDelegate(); parameterizer->SetInitialParameterizationDelegate(paramDelegate); } } template void PETDynamicView::GenerateModelFit_PixelBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::PixelBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::PixelBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); mitk::AIFBasedModelBase::AterialInputFunctionType aif; mitk::AIFBasedModelBase::TimeGridType aifTimeGrid; GetAIF(aif, aifTimeGrid); //Model configuration (static parameters) can be done now modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { fitGenerator->SetMask(m_selectedMask); roiUID = m_selectedMask->GetUID(); } fitGenerator->SetDynamicImage(this->m_selectedImage); fitGenerator->SetFitFunctor(fitFunctor); generator = fitGenerator.GetPointer(); //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_PIXELBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } template void PETDynamicView::GenerateModelFit_ROIBased( mitk::modelFit::ModelFitInfo::Pointer& modelFitInfo, mitk::ParameterFitImageGeneratorBase::Pointer& generator) { mitk::ROIBasedParameterFitImageGenerator::Pointer fitGenerator = mitk::ROIBasedParameterFitImageGenerator::New(); typename TParameterizer::Pointer modelParameterizer = TParameterizer::New(); //Compute AIF mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); aifGenerator->SetDynamicImage(this->m_selectedImage); aifGenerator->SetMask(this->m_selectedAIFMask); mitk::AIFBasedModelBase::AterialInputFunctionType aif = aifGenerator->GetAterialInputFunction(); mitk::AIFBasedModelBase::TimeGridType aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); //Model configuration (static parameters) can be done now modelParameterizer->SetAIF(aif); modelParameterizer->SetAIFTimeGrid(aifTimeGrid); this->ConfigureInitialParametersOfParameterizer(modelParameterizer); //Compute ROI signal mitk::MaskedDynamicImageStatisticsGenerator::Pointer signalGenerator = mitk::MaskedDynamicImageStatisticsGenerator::New(); signalGenerator->SetMask(m_selectedMask); signalGenerator->SetDynamicImage(m_selectedImage); signalGenerator->Generate(); mitk::MaskedDynamicImageStatisticsGenerator::ResultType roiSignal = signalGenerator->GetMean(); //Specify fitting strategy and criterion parameters mitk::ModelFitFunctorBase::Pointer fitFunctor = CreateDefaultFitFunctor(modelParameterizer); //Parametrize fit generator fitGenerator->SetModelParameterizer(modelParameterizer); fitGenerator->SetMask(m_selectedMask); fitGenerator->SetFitFunctor(fitFunctor); fitGenerator->SetSignal(roiSignal); fitGenerator->SetTimeGrid(mitk::ExtractTimeGrid(m_selectedImage)); generator = fitGenerator.GetPointer(); std::string roiUID = ""; if (m_selectedMask.IsNotNull()) { roiUID = m_selectedMask->GetUID(); } //Create model info modelFitInfo = mitk::modelFit::CreateFitInfoFromModelParameterizer(modelParameterizer, m_selectedNode->GetData(), mitk::ModelFitConstants::FIT_TYPE_VALUE_ROIBASED(), this->GetFitName(), roiUID); mitk::ScalarListLookupTable::ValueType infoSignal; for (mitk::MaskedDynamicImageStatisticsGenerator::ResultType::const_iterator pos = roiSignal.begin(); pos != roiSignal.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("ROI", infoSignal); infoSignal.clear(); for (mitk::AIFBasedModelBase::AterialInputFunctionType::const_iterator pos = aif.begin(); pos != aif.end(); ++pos) { infoSignal.push_back(*pos); } modelFitInfo->inputData.SetTableValue("AIF", infoSignal); } void PETDynamicView::DoFit(const mitk::modelFit::ModelFitInfo* fitSession, mitk::ParameterFitImageGeneratorBase* generator) { std::stringstream message; message << "" << "Fitting Data Set . . ." << ""; m_Controls.errorMessageLabel->setText(message.str().c_str()); m_Controls.errorMessageLabel->show(); ///////////////////////// //create job and put it into the thread pool ParameterFitBackgroundJob* pJob = new ParameterFitBackgroundJob(generator, fitSession, this->m_selectedNode); pJob->setAutoDelete(true); connect(pJob, SIGNAL(Error(QString)), this, SLOT(OnJobError(QString))); connect(pJob, SIGNAL(Finished()), this, SLOT(OnJobFinished())); connect(pJob, SIGNAL(ResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), this, SLOT(OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType, const ParameterFitBackgroundJob*)), Qt::BlockingQueuedConnection); connect(pJob, SIGNAL(JobProgress(double)), this, SLOT(OnJobProgress(double))); connect(pJob, SIGNAL(JobStatusChanged(QString)), this, SLOT(OnJobStatusChanged(QString))); QThreadPool* threadPool = QThreadPool::globalInstance(); threadPool->start(pJob); } PETDynamicView::PETDynamicView() : m_FittingInProgress(false) { m_selectedImage = nullptr; m_selectedMask = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::OneTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::ExtendedOneTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoTissueCompartmentFDGModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); - factory = mitk::NumericTwoTissueCompartmentModelFactory::New().GetPointer(); - m_FactoryStack.push_back(factory); + mitk::NodePredicateDataType::Pointer isLabelSet = mitk::NodePredicateDataType::New("LabelSetImage"); mitk::NodePredicateDataType::Pointer isImage = mitk::NodePredicateDataType::New("Image"); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isLegacyMask = mitk::NodePredicateAnd::New(isImage, isBinary); mitk::NodePredicateOr::Pointer isMask = mitk::NodePredicateOr::New(isLegacyMask, isLabelSet); mitk::NodePredicateAnd::Pointer isNoMask = mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isMask)); this->m_IsMaskPredicate = mitk::NodePredicateAnd::New(isMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); this->m_IsNoMaskImagePredicate = mitk::NodePredicateAnd::New(isNoMask, mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); } void PETDynamicView::OnJobFinished() { this->m_Controls.infoBox->append(QString("Fitting finished")); this->m_FittingInProgress = false; }; void PETDynamicView::OnJobError(QString err) { MITK_ERROR << err.toStdString().c_str(); m_Controls.infoBox->append(QString("") + err + QString("")); }; void PETDynamicView::OnJobResultsAreAvailable(mitk::modelFit::ModelFitResultNodeVectorType results, const ParameterFitBackgroundJob* pJob) { //Store the resulting parameter fit image via convenience helper function in data storage //(handles the correct generation of the nodes and their properties) mitk::modelFit::StoreResultsInDataStorage(this->GetDataStorage(), results, pJob->GetParentNode()); m_Controls.errorMessageLabel->setText(""); m_Controls.errorMessageLabel->hide(); }; void PETDynamicView::OnJobProgress(double progress) { QString report = QString("Progress. ") + QString::number(progress); this->m_Controls.infoBox->append(report); }; void PETDynamicView::OnJobStatusChanged(QString info) { this->m_Controls.infoBox->append(info); } void PETDynamicView::InitModelComboBox() const { this->m_Controls.comboModel->clear(); this->m_Controls.comboModel->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.comboModel->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.comboModel->setCurrentIndex(0); }; mitk::ModelFitFunctorBase::Pointer PETDynamicView::CreateDefaultFitFunctor( const mitk::ModelParameterizerBase* parameterizer) const { mitk::LevenbergMarquardtModelFitFunctor::Pointer fitFunctor = mitk::LevenbergMarquardtModelFitFunctor::New(); mitk::SumOfSquaredDifferencesFitCostFunction::Pointer evaluation = mitk::SumOfSquaredDifferencesFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("sum_diff^2", evaluation); mitk::ChiSquareFitCostFunction::Pointer chi2 = mitk::ChiSquareFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("Chi^2", chi2); mitk::ReducedChiSquareFitCostFunction::Pointer redchi2 = mitk::ReducedChiSquareFitCostFunction::New(); fitFunctor->RegisterEvaluationParameter("redChi^2", redchi2); if (m_Controls.checkBox_Constraints->isChecked()) { fitFunctor->SetConstraintChecker(m_modelConstraints); } mitk::ModelBase::Pointer refModel = parameterizer->GenerateParameterizedModel(); ::itk::LevenbergMarquardtOptimizer::ScalesType scales; scales.SetSize(refModel->GetNumberOfParameters()); scales.Fill(1.0); fitFunctor->SetScales(scales); return fitFunctor.GetPointer(); } void PETDynamicView::GetAIF(mitk::AIFBasedModelBase::AterialInputFunctionType& aif, mitk::AIFBasedModelBase::AterialInputFunctionType& aifTimeGrid) { if (this->m_Controls.radioAIFFile->isChecked()) { aif.clear(); aifTimeGrid.clear(); aif.SetSize(AIFinputFunction.size()); aifTimeGrid.SetSize(AIFinputGrid.size()); aif.fill(0.0); aifTimeGrid.fill(0.0); itk::Array::iterator aifPos = aif.begin(); for (std::vector::const_iterator pos = AIFinputFunction.begin(); pos != AIFinputFunction.end(); ++pos, ++aifPos) { *aifPos = *pos; } itk::Array::iterator gridPos = aifTimeGrid.begin(); for (std::vector::const_iterator pos = AIFinputGrid.begin(); pos != AIFinputGrid.end(); ++pos, ++gridPos) { *gridPos = *pos; } } else if (this->m_Controls.radioAIFImage->isChecked()) { aif.clear(); aifTimeGrid.clear(); mitk::AterialInputFunctionGenerator::Pointer aifGenerator = mitk::AterialInputFunctionGenerator::New(); //Hematocrit level aifGenerator->SetHCL(this->m_Controls.HCLSpinBox->value()); //mask settings this->m_selectedAIFMaskNode = m_Controls.comboAIFMask->GetSelectedNode(); this->m_selectedAIFMask = dynamic_cast(this->m_selectedAIFMaskNode->GetData()); if (this->m_selectedAIFMask->GetTimeSteps() > 1) { MITK_INFO << "Selected AIF mask has multiple timesteps. Only use first timestep to mask model fit. AIF Mask name: " << m_selectedAIFMaskNode->GetName() ; mitk::ImageTimeSelector::Pointer maskedImageTimeSelector = mitk::ImageTimeSelector::New(); maskedImageTimeSelector->SetInput(this->m_selectedAIFMask); maskedImageTimeSelector->SetTimeNr(0); maskedImageTimeSelector->UpdateLargestPossibleRegion(); this->m_selectedAIFMask = maskedImageTimeSelector->GetOutput(); } if (this->m_selectedAIFMask.IsNotNull()) { aifGenerator->SetMask(this->m_selectedAIFMask); } //image settings if (this->m_Controls.checkDedicatedAIFImage->isChecked()) { this->m_selectedAIFImageNode = m_Controls.comboAIFImage->GetSelectedNode(); this->m_selectedAIFImage = dynamic_cast(this->m_selectedAIFImageNode->GetData()); } else { this->m_selectedAIFImageNode = m_selectedNode; this->m_selectedAIFImage = m_selectedImage; } aifGenerator->SetDynamicImage(this->m_selectedAIFImage); aif = aifGenerator->GetAterialInputFunction(); aifTimeGrid = aifGenerator->GetAterialInputFunctionTimeGrid(); } else { mitkThrow() << "Cannot generate AIF. View is in a invalid state. No AIF mode selected."; } } void PETDynamicView::LoadAIFfromFile() { QFileDialog dialog; dialog.setNameFilter(tr("Images (*.csv")); QString fileName = dialog.getOpenFileName(); m_Controls.aifFilePath->setText(fileName); std::string m_aifFilePath = fileName.toStdString(); //Read Input typedef boost::tokenizer< boost::escaped_list_separator > Tokenizer; ///////////////////////////////////////////////////////////////////////////////////////////////// //AIF Data std::ifstream in1(m_aifFilePath.c_str()); if (!in1.is_open()) { m_Controls.errorMessageLabel->setText("Could not open AIF File!"); } std::vector< std::string > vec1; std::string line1; while (getline(in1, line1)) { Tokenizer tok(line1); vec1.assign(tok.begin(), tok.end()); // if (vec1.size() < 3) continue; this->AIFinputGrid.push_back(convertToDouble(vec1[0])); this->AIFinputFunction.push_back(convertToDouble(vec1[1])); } } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/manifest_headers.cmake b/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/manifest_headers.cmake index 4134f6923e..5cbce78577 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/manifest_headers.cmake +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/manifest_headers.cmake @@ -1,5 +1,5 @@ set(Plugin-Name "Perfusion Data Simulation Plugin") set(Plugin-Version "1.0") -set(Plugin-Vendor "DKFZ, Software Development For Integrated Diagnostics and Therapy") -set(Plugin-ContactAddress "c.debus@dkfz.de") +set(Plugin-Vendor "German Cancer Research Center (DKFZ) Heidelberg, Division of Medical Image Computing, Germany") +set(Plugin-ContactAddress "Contact via www.MITK.org") set(Require-Plugin org.mitk.gui.qt.common) diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.cpp b/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.cpp index 35ac6847cf..64cef01de2 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.cpp +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.cpp @@ -1,1000 +1,880 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include "mitkWorkbenchUtil.h" #include "PerfusionDataSimulationView.h" #include "itkUnaryFunctorImageFilter.h" #include #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include "mitkITKImageImport.h" #include "mitkGaussianNoiseFunctor.h" #include "mitkTwoCompartmentExchangeModel.h" #include "mitkTwoCompartmentExchangeModelParameterizer.h" #include "mitkTwoCompartmentExchangeModelFactory.h" -#include "mitkNumericTwoCompartmentExchangeModel.h" -#include "mitkNumericTwoCompartmentExchangeModelParameterizer.h" -#include "mitkNumericTwoCompartmentExchangeModelFactory.h" #include "mitkExtendedToftsModel.h" #include "mitkExtendedToftsModelFactory.h" #include "mitkExtendedToftsModelParameterizer.h" #include "mitkTwoTissueCompartmentModel.h" #include "mitkTwoTissueCompartmentModelParameterizer.h" #include "mitkTwoTissueCompartmentModelFactory.h" #include "mitkOneTissueCompartmentModel.h" #include "mitkOneTissueCompartmentModelParameterizer.h" #include "mitkOneTissueCompartmentModelFactory.h" #include "mitkModelSignalImageGenerator.h" #include #include #include #include #include "mitkSimpleFunctorBase.h" #include "mitkArbitraryTimeGeometry.h" #include #include const std::string PerfusionDataSimulationView::VIEW_ID = "org.mitk.views.pharmacokinetics.simulation"; inline double convertToDouble(const std::string& data) { std::istringstream stepStream(data); stepStream.imbue(std::locale("C")); double value = 0.0; if (!(stepStream >> value) || !(stepStream.eof())) { mitkThrow() << "Cannot convert string to double. String: " << data; } return value; } inline double FindeMaxConcentration(std::vector Concentration) { double maximum = Concentration[0]; for (std::vector::size_type i =0; isetFocus(); } void PerfusionDataSimulationView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.GenerateBtn->setEnabled(false); m_Controls.groupBox_TM->hide(); m_Controls.groupBox_2CXM->hide(); m_Controls.groupBox_2TCM->hide(); m_Controls.groupBox_1TCM->hide(); m_Controls.groupBox_CNR->hide(); this->InitModelComboBox(); /** #2 @todo Reduce code ? */ m_Controls.comboBox_F->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_F->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_PS->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_PS->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_fp->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_fp->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_fi->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_fi->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_Ktrans->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_Ktrans->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_vp->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_vp->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_ve->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_ve->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_K1->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_K1->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_K2->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_K2->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_K3->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_K3->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_K4->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_K4->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_VB->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_VB->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_k1->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_k1->SetPredicate(m_IsNotABinaryImagePredicate); m_Controls.comboBox_k2->SetDataStorage(this->GetDataStorage()); m_Controls.comboBox_k2->SetPredicate(m_IsNotABinaryImagePredicate); connect(m_Controls.AifFileBtn, SIGNAL(clicked()), this, SLOT(LoadAIFFile())); connect(m_Controls.ModelSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(OnModellSet(int))); connect(m_Controls.GenerateBtn, SIGNAL(clicked()), this, SLOT(OnGenerateDataButtonClicked())); connect(m_Controls.comboBox_F, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_PS, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_fp, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_fi, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_VB, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_K1, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_K2, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_K3, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_K4, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_k1, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_k2, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_Ktrans, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_vp, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.comboBox_ve, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.CNRSpinBox, SIGNAL(valueChanged(double)),this, SLOT(OnSimulationConfigurationChanged())); connect(m_Controls.NoiseCheckBox, SIGNAL(stateChanged(int)),this, SLOT(OnSimulationConfigurationChanged())); // UpdateDataSelection(); } void PerfusionDataSimulationView::UpdateDataSelection() { } //void PerfusionDataSimulationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, // const QList& selectedNodes) //{ // UpdateDataSelection(); // m_Controls.GenerateBtn->setEnabled(CheckModelSettings()); //} void PerfusionDataSimulationView::OnSimulationConfigurationChanged() { // UpdateDataSelection(); m_Controls.groupBox_CNR->setVisible(m_Controls.NoiseCheckBox->isChecked()); this->m_CNR = m_Controls.CNRSpinBox->value(); m_Controls.GenerateBtn->setEnabled( CheckModelSettings() ); } void PerfusionDataSimulationView::OnModellSet(int index) { m_selectedModelFactory = nullptr; if (index > 0) { if (static_cast(index) <= m_FactoryStack.size() ) { m_selectedModelFactory = m_FactoryStack[index - 1]; } else { MITK_WARN << "Invalid model index. Index outside of the factory stack. Factory stack size: "<< m_FactoryStack.size() << "; invalid index: "<< index; } } bool isToftsFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; - bool is2CXMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr || dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; + bool is2CXMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; bool is2TCMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; bool is1TCMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; m_Controls.groupBox_TM->setVisible(isToftsFactory); m_Controls.groupBox_2CXM->setVisible(is2CXMFactory ); m_Controls.groupBox_2TCM->setVisible(is2TCMFactory ); m_Controls.groupBox_1TCM->setVisible(is1TCMFactory ); m_Controls.GenerateBtn->setEnabled( CheckModelSettings() ); // UpdateDataSelection(); } bool PerfusionDataSimulationView::CheckModelSettings() { bool ok = true; if(m_selectedModelFactory.IsNull()) { return false; } if(this->m_AterialInputFunction.GetSize() == 0 || this->m_TimeGrid.GetSize() == 0) { return false; } bool isToftsFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; - bool is2CXMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr || dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; + bool is2CXMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; bool is2TCMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; bool is1TCMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; if(isToftsFactory) { ok = ok && m_Controls.comboBox_Ktrans->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_vp->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_ve->GetSelectedNode().IsNotNull(); } else if(is2CXMFactory) { ok = ok && m_Controls.comboBox_F->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_PS->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_fp->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_fi->GetSelectedNode().IsNotNull(); } else if(is2TCMFactory) { ok = ok && m_Controls.comboBox_K1->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_K2->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_K3->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_K4->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_VB->GetSelectedNode().IsNotNull(); } else if(is1TCMFactory) { ok = ok && m_Controls.comboBox_k1->GetSelectedNode().IsNotNull(); ok = ok && m_Controls.comboBox_k2->GetSelectedNode().IsNotNull(); } else { return false; } if(m_Controls.NoiseCheckBox->isChecked()) { if(m_CNR !=0 && m_MaxConcentration !=0) { this->m_Sigma = m_MaxConcentration/m_CNR; } if(m_Sigma==0) { return false; } } return ok; } void PerfusionDataSimulationView::InitModelComboBox() const { this->m_Controls.ModelSelection->clear(); this->m_Controls.ModelSelection->addItem(tr("No model selected")); for (ModelFactoryStackType::const_iterator pos = m_FactoryStack.begin(); pos != m_FactoryStack.end(); ++pos) { this->m_Controls.ModelSelection->addItem(QString::fromStdString((*pos)->GetClassID())); } this->m_Controls.ModelSelection->setCurrentIndex(0); }; void PerfusionDataSimulationView::LoadAIFFile() { QFileDialog dialog; dialog.setNameFilter(tr("Images (*.csv")); QString fileName = dialog.getOpenFileName(); m_Controls.AifFilePath->setText( fileName ); std::string aifFilePath = fileName.toStdString(); //Read Input typedef boost::tokenizer< boost::escaped_list_separator > Tokenizer; ///////////////////////////////////////////////////////////////////////////////////////////////// //AIF Data std::vector inputFunction; std::vector inputGrid; std::ifstream in1(aifFilePath.c_str()); if(!in1.is_open()) { m_Controls.errorMessageLabel->setText("Could not open AIF File!"); } std::vector< std::string > vec1; std::string line1; while (getline(in1,line1)) { Tokenizer tok(line1); vec1.assign(tok.begin(),tok.end()); // if (vec1.size() < 3) continue; inputGrid.push_back(convertToDouble(vec1[0])); inputFunction.push_back(convertToDouble(vec1[1])); } this->m_MaxConcentration = FindeMaxConcentration(inputFunction); itk::Array aif; itk::Array grid; aif.SetSize(inputFunction.size()); grid.SetSize(inputGrid.size()); aif.fill(0.0); grid.fill(0.0); itk::Array::iterator aifPos = aif.begin(); for(std::vector::const_iterator pos = inputFunction.begin(); pos != inputFunction.end(); ++pos, ++aifPos) { *aifPos = *pos; } itk::Array::iterator gridPos = grid.begin(); for(std::vector::const_iterator pos = inputGrid.begin(); pos != inputGrid.end(); ++pos, ++gridPos) { *gridPos = *pos; } this->m_AterialInputFunction = aif; this->m_TimeGrid = grid; m_Controls.GenerateBtn->setEnabled( CheckModelSettings() ); // UpdateDataSelection(); } -/** @todo #2 Same function for Numeric and analytic version of FillParameterMap2CXM +/** @todo #2 Same function for analytic version of FillParameterMap2CXM */ void PerfusionDataSimulationView::FillParameterMap2CXM() { ParameterMapType stack; mitk::DataNode::Pointer m_selectedNode = m_Controls.comboBox_F->GetSelectedNode(); mitk::Image::Pointer m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_F,m_selectedImage)); m_selectedNode = m_Controls.comboBox_PS->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_PS,m_selectedImage)); m_selectedNode = m_Controls.comboBox_fp->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_vp,m_selectedImage)); m_selectedNode = m_Controls.comboBox_fi->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_ve,m_selectedImage)); this->m_ParameterImageMap = stack; } -void PerfusionDataSimulationView::FillParameterMapNumeric2CXM() -{ - ParameterMapType stack; - - mitk::DataNode::Pointer m_selectedNode = m_Controls.comboBox_F->GetSelectedNode(); - mitk::Image::Pointer m_selectedImage = dynamic_cast(m_selectedNode->GetData()); - stack.insert(std::make_pair(mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_F,m_selectedImage)); - - m_selectedNode = m_Controls.comboBox_PS->GetSelectedNode(); - m_selectedImage = dynamic_cast(m_selectedNode->GetData()); - stack.insert(std::make_pair(mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_PS,m_selectedImage)); - - m_selectedNode = m_Controls.comboBox_fp->GetSelectedNode(); - m_selectedImage = dynamic_cast(m_selectedNode->GetData()); - stack.insert(std::make_pair(mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_vp,m_selectedImage)); - - m_selectedNode = m_Controls.comboBox_fi->GetSelectedNode(); - m_selectedImage = dynamic_cast(m_selectedNode->GetData()); - stack.insert(std::make_pair(mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_ve,m_selectedImage)); - - this->m_ParameterImageMap = stack; - -} void PerfusionDataSimulationView::FillParameterMapETM() { ParameterMapType stack; mitk::DataNode::Pointer m_selectedNode = m_Controls.comboBox_Ktrans->GetSelectedNode(); mitk::Image::Pointer m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::ExtendedToftsModel::NAME_PARAMETER_Ktrans,m_selectedImage)); m_selectedNode = m_Controls.comboBox_vp->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::ExtendedToftsModel::NAME_PARAMETER_vp,m_selectedImage)); m_selectedNode = m_Controls.comboBox_ve->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::ExtendedToftsModel::NAME_PARAMETER_ve,m_selectedImage)); this->m_ParameterImageMap = stack; } void PerfusionDataSimulationView::FillParameterMap2TCM() { ParameterMapType stack; mitk::DataNode::Pointer m_selectedNode = m_Controls.comboBox_K1->GetSelectedNode(); mitk::Image::Pointer m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoTissueCompartmentModel::NAME_PARAMETER_K1,m_selectedImage)); m_selectedNode = m_Controls.comboBox_K2->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoTissueCompartmentModel::NAME_PARAMETER_k2,m_selectedImage)); m_selectedNode = m_Controls.comboBox_K3->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoTissueCompartmentModel::NAME_PARAMETER_k3,m_selectedImage)); m_selectedNode = m_Controls.comboBox_K4->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoTissueCompartmentModel::NAME_PARAMETER_k4,m_selectedImage)); m_selectedNode = m_Controls.comboBox_VB->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::TwoTissueCompartmentModel::NAME_PARAMETER_vb,m_selectedImage)); this->m_ParameterImageMap = stack; } void PerfusionDataSimulationView::FillParameterMap1TCM() { ParameterMapType stack; mitk::DataNode::Pointer m_selectedNode = m_Controls.comboBox_k1->GetSelectedNode(); mitk::Image::Pointer m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::OneTissueCompartmentModel::NAME_PARAMETER_K1,m_selectedImage)); m_selectedNode = m_Controls.comboBox_k2->GetSelectedNode(); m_selectedImage = dynamic_cast(m_selectedNode->GetData()); stack.insert(std::make_pair(mitk::OneTissueCompartmentModel::NAME_PARAMETER_k2,m_selectedImage)); this->m_ParameterImageMap = stack; } void PerfusionDataSimulationView::OnGenerateDataButtonClicked() { mitk::Image::Pointer m_DynamicImage = mitk::Image::New(); bool isToftsFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; bool isPhysBrixFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; - bool isNumPhysBrixFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; bool is2TCMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; bool is1TCMFactory = dynamic_cast(m_selectedModelFactory.GetPointer()) != nullptr; if(isToftsFactory) { this->FillParameterMapETM(); m_DynamicImage = this->GenerateETModelData(); } if (isPhysBrixFactory) { this->FillParameterMap2CXM(); m_DynamicImage = this->Generate2CXModelData(); } - if (isNumPhysBrixFactory) - { - this->FillParameterMapNumeric2CXM(); - m_DynamicImage = this->GenerateNumeric2CXModelData(); - } if (is2TCMFactory) { this->FillParameterMap2TCM(); m_DynamicImage = this->Generate2TCModelData(); } if (is1TCMFactory) { this->FillParameterMap1TCM(); m_DynamicImage = this->Generate1TCModelData(); } mitk::DataNode::Pointer resultNode = mitk::DataNode::New(); std::string nameOfResultImage = "SimulationData_"; nameOfResultImage.append(m_selectedModelFactory->GetModelDisplayName()); resultNode->SetProperty("name", mitk::StringProperty::New(nameOfResultImage) ); resultNode->SetData(m_DynamicImage); // set data of new node this->GetDataStorage()->Add(resultNode); } mitk::Image::Pointer PerfusionDataSimulationView::Generate2CXModelData() { mitk::ModelSignalImageGenerator::Pointer generator = mitk::ModelSignalImageGenerator::New(); mitk::TwoCompartmentExchangeModelParameterizer::Pointer modelParameterizer = mitk::TwoCompartmentExchangeModelParameterizer::New(); /** @todo #2 necessary? Generator need to have a map with Parameters in order of Model in order to pass vector parameters correctly to Model. * I wanted to make it independent from the order the images are passed on */ for(ParameterMapType::const_iterator pos = this->m_ParameterImageMap.begin(); pos != this->m_ParameterImageMap.end(); ++pos) { if(pos->first == mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_F) { generator->SetParameterInputImage(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_F, pos->second); } if(pos->first == mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_PS) { generator->SetParameterInputImage(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_PS, pos->second); } if(pos->first == mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_ve) { generator->SetParameterInputImage(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_ve, pos->second); } if(pos->first == mitk::TwoCompartmentExchangeModel::NAME_PARAMETER_vp) { generator->SetParameterInputImage(mitk::TwoCompartmentExchangeModel::POSITION_PARAMETER_vp, pos->second); } } modelParameterizer->SetAIF(this->m_AterialInputFunction); modelParameterizer->SetAIFTimeGrid(this->m_TimeGrid); modelParameterizer->SetDefaultTimeGrid(this->m_TimeGrid); generator->SetParameterizer(modelParameterizer); mitk::Image::Pointer generatedImage = generator->GetGeneratedImage(); mitk::Image::Pointer resultImage = mitk::Image::New(); if(m_Controls.NoiseCheckBox->isChecked()) { typedef itk::Image ImageType; mitk::Image::Pointer tempImage = mitk::Image::New(); tempImage->Initialize(generatedImage); mitk::ArbitraryTimeGeometry* timeGeometry = dynamic_cast (generatedImage->GetTimeGeometry()); tempImage->SetTimeGeometry(timeGeometry); ImageType::Pointer itkImage = ImageType::New(); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(generatedImage); for(unsigned int i = 0; i< generatedImage->GetTimeSteps(); ++i) { imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkInputImage = imageTimeSelector->GetOutput(); mitk::CastToItkImage(mitkInputImage, itkImage ); typedef mitk::GaussianNoiseFunctor NoiseFunctorType; typedef itk::UnaryFunctorImageFilter NoiseFilterType; NoiseFilterType::Pointer noiseFilter = NoiseFilterType::New(); NoiseFunctorType noiseFunctor; noiseFunctor.SetMean(0.0); noiseFunctor.SetSigma(this->m_Sigma); noiseFilter->SetFunctor(noiseFunctor); noiseFilter->SetInput(itkImage); mitk::Image::Pointer outputImage = mitk::ImportItkImage(noiseFilter->GetOutput())->Clone(); mitk::ImageReadAccessor accessor(outputImage); tempImage->SetVolume(accessor.GetData(), i); } resultImage = tempImage->Clone(); } else { resultImage = generatedImage; } return resultImage; } -/** @todo #2 Synergie? Function implementation for every Model to complicated? - */ -mitk::Image::Pointer PerfusionDataSimulationView::GenerateNumeric2CXModelData() -{ -mitk::ModelSignalImageGenerator::Pointer generator = mitk::ModelSignalImageGenerator::New(); -mitk::NumericTwoCompartmentExchangeModelParameterizer::Pointer modelParameterizer = mitk::NumericTwoCompartmentExchangeModelParameterizer::New(); - -for(ParameterMapType::const_iterator pos = this->m_ParameterImageMap.begin(); pos != this->m_ParameterImageMap.end(); ++pos) -{ - if(pos->first == mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_F) - { - generator->SetParameterInputImage(mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_F, pos->second); - } - if(pos->first == mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_PS) - { - generator->SetParameterInputImage(mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_PS, pos->second); - } - if(pos->first == mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_ve) - { - generator->SetParameterInputImage(mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_ve, pos->second); - } - if(pos->first == mitk::NumericTwoCompartmentExchangeModel::NAME_PARAMETER_vp) - { - generator->SetParameterInputImage(mitk::NumericTwoCompartmentExchangeModel::POSITION_PARAMETER_vp, pos->second); - } -} - -modelParameterizer->SetAIF(this->m_AterialInputFunction); -modelParameterizer->SetAIFTimeGrid(this->m_TimeGrid); -modelParameterizer->SetDefaultTimeGrid(this->m_TimeGrid); -modelParameterizer->SetODEINTStepSize(0.05); - -generator->SetParameterizer(modelParameterizer); - -mitk::Image::Pointer generatedImage = generator->GetGeneratedImage(); - - -mitk::Image::Pointer resultImage = mitk::Image::New(); -if(m_Controls.NoiseCheckBox->isChecked()) -{ - typedef itk::Image ImageType; - - mitk::Image::Pointer tempImage = mitk::Image::New(); - tempImage->Initialize(generatedImage); - - mitk::ArbitraryTimeGeometry* timeGeometry = dynamic_cast (generatedImage->GetTimeGeometry()); - - tempImage->SetTimeGeometry(timeGeometry); - - ImageType::Pointer itkImage = ImageType::New(); - mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); - imageTimeSelector->SetInput(generatedImage); - - for(unsigned int i = 0; i< generatedImage->GetTimeSteps(); ++i) - { - imageTimeSelector->SetTimeNr(i); - imageTimeSelector->UpdateLargestPossibleRegion(); - - mitk::Image::Pointer mitkInputImage = imageTimeSelector->GetOutput(); - - mitk::CastToItkImage(mitkInputImage, itkImage ); - - typedef mitk::GaussianNoiseFunctor NoiseFunctorType; - typedef itk::UnaryFunctorImageFilter NoiseFilterType; - NoiseFilterType::Pointer noiseFilter = NoiseFilterType::New(); - NoiseFunctorType noiseFunctor; - noiseFunctor.SetMean(0.0); - noiseFunctor.SetSigma(this->m_Sigma); - noiseFilter->SetFunctor(noiseFunctor); - - noiseFilter->SetInput(itkImage); - - mitk::Image::Pointer outputImage = mitk::ImportItkImage(noiseFilter->GetOutput())->Clone(); - - mitk::ImageReadAccessor accessor(outputImage); - tempImage->SetVolume(accessor.GetData(), i); - } - - resultImage = tempImage->Clone(); -} -else -{ - resultImage = generatedImage; -} -return resultImage; -} mitk::Image::Pointer PerfusionDataSimulationView::GenerateETModelData() { mitk::ModelSignalImageGenerator::Pointer generator = mitk::ModelSignalImageGenerator::New(); mitk::ExtendedToftsModelParameterizer::Pointer modelParameterizer = mitk::ExtendedToftsModelParameterizer::New(); for(ParameterMapType::const_iterator pos = this->m_ParameterImageMap.begin(); pos != this->m_ParameterImageMap.end(); ++pos) { if(pos->first == mitk::ExtendedToftsModel::NAME_PARAMETER_Ktrans) { generator->SetParameterInputImage(mitk::ExtendedToftsModel::POSITION_PARAMETER_Ktrans, pos->second); } if(pos->first == mitk::ExtendedToftsModel::NAME_PARAMETER_vp) { generator->SetParameterInputImage(mitk::ExtendedToftsModel::POSITION_PARAMETER_vp, pos->second); } if(pos->first == mitk::ExtendedToftsModel::NAME_PARAMETER_ve) { generator->SetParameterInputImage(mitk::ExtendedToftsModel::POSITION_PARAMETER_ve, pos->second); } } modelParameterizer->SetAIF(this->m_AterialInputFunction); modelParameterizer->SetAIFTimeGrid(this->m_TimeGrid); modelParameterizer->SetDefaultTimeGrid(this->m_TimeGrid); generator->SetParameterizer(modelParameterizer); mitk::Image::Pointer generatedImage = generator->GetGeneratedImage(); mitk::Image::Pointer resultImage = mitk::Image::New(); if(m_Controls.NoiseCheckBox->isChecked()) { typedef itk::Image ImageType; mitk::Image::Pointer tempImage = mitk::Image::New(); tempImage->Initialize(generatedImage); mitk::ArbitraryTimeGeometry* timeGeometry = dynamic_cast (generatedImage->GetTimeGeometry()); tempImage->SetTimeGeometry(timeGeometry); ImageType::Pointer itkImage = ImageType::New(); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(generatedImage); for(unsigned int i = 0; i< generatedImage->GetTimeSteps(); ++i) { imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkInputImage = imageTimeSelector->GetOutput(); mitk::CastToItkImage(mitkInputImage, itkImage ); typedef mitk::GaussianNoiseFunctor NoiseFunctorType; typedef itk::UnaryFunctorImageFilter NoiseFilterType; NoiseFilterType::Pointer noiseFilter = NoiseFilterType::New(); NoiseFunctorType noiseFunctor; noiseFunctor.SetMean(0.0); noiseFunctor.SetSigma(this->m_Sigma); noiseFilter->SetFunctor(noiseFunctor); noiseFilter->SetInput(itkImage); mitk::Image::Pointer outputImage = mitk::ImportItkImage(noiseFilter->GetOutput())->Clone(); mitk::ImageReadAccessor accessor(outputImage); tempImage->SetVolume(accessor.GetData(), i); } resultImage = tempImage->Clone(); } else { resultImage = generatedImage; } return resultImage; } mitk::Image::Pointer PerfusionDataSimulationView::Generate2TCModelData() { mitk::ModelSignalImageGenerator::Pointer generator = mitk::ModelSignalImageGenerator::New(); mitk::TwoTissueCompartmentModelParameterizer::Pointer modelParameterizer = mitk::TwoTissueCompartmentModelParameterizer::New(); for(ParameterMapType::const_iterator pos = this->m_ParameterImageMap.begin(); pos != this->m_ParameterImageMap.end(); ++pos) { if(pos->first == mitk::TwoTissueCompartmentModel::NAME_PARAMETER_K1) { generator->SetParameterInputImage(mitk::TwoTissueCompartmentModel::POSITION_PARAMETER_K1, pos->second); } if(pos->first == mitk::TwoTissueCompartmentModel::NAME_PARAMETER_k2) { generator->SetParameterInputImage(mitk::TwoTissueCompartmentModel::POSITION_PARAMETER_k2, pos->second); } if(pos->first == mitk::TwoTissueCompartmentModel::NAME_PARAMETER_k3) { generator->SetParameterInputImage(mitk::TwoTissueCompartmentModel::POSITION_PARAMETER_k3, pos->second); } if(pos->first == mitk::TwoTissueCompartmentModel::NAME_PARAMETER_k4) { generator->SetParameterInputImage(mitk::TwoTissueCompartmentModel::POSITION_PARAMETER_k4, pos->second); } if(pos->first == mitk::TwoTissueCompartmentModel::NAME_PARAMETER_vb) { generator->SetParameterInputImage(mitk::TwoTissueCompartmentModel::POSITION_PARAMETER_vb, pos->second); } } modelParameterizer->SetAIF(this->m_AterialInputFunction); modelParameterizer->SetAIFTimeGrid(this->m_TimeGrid); modelParameterizer->SetDefaultTimeGrid(this->m_TimeGrid); generator->SetParameterizer(modelParameterizer); mitk::Image::Pointer generatedImage = generator->GetGeneratedImage(); mitk::Image::Pointer resultImage = mitk::Image::New(); if(m_Controls.NoiseCheckBox->isChecked()) { typedef itk::Image ImageType; mitk::Image::Pointer tempImage = mitk::Image::New(); tempImage->Initialize(generatedImage); mitk::ArbitraryTimeGeometry* timeGeometry = dynamic_cast (generatedImage->GetTimeGeometry()); tempImage->SetTimeGeometry(timeGeometry); ImageType::Pointer itkImage = ImageType::New(); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(generatedImage); for(unsigned int i = 0; i< generatedImage->GetTimeSteps(); ++i) { imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkInputImage = imageTimeSelector->GetOutput(); mitk::CastToItkImage(mitkInputImage, itkImage ); typedef mitk::GaussianNoiseFunctor NoiseFunctorType; typedef itk::UnaryFunctorImageFilter NoiseFilterType; NoiseFilterType::Pointer noiseFilter = NoiseFilterType::New(); NoiseFunctorType noiseFunctor; noiseFunctor.SetMean(0.0); noiseFunctor.SetSigma(this->m_Sigma); noiseFilter->SetFunctor(noiseFunctor); noiseFilter->SetInput(itkImage); mitk::Image::Pointer outputImage = mitk::ImportItkImage(noiseFilter->GetOutput())->Clone(); mitk::ImageReadAccessor accessor(outputImage); tempImage->SetVolume(accessor.GetData(), i); } resultImage = tempImage->Clone(); } else { resultImage = generatedImage; } return resultImage; } mitk::Image::Pointer PerfusionDataSimulationView::Generate1TCModelData() { mitk::ModelSignalImageGenerator::Pointer generator = mitk::ModelSignalImageGenerator::New(); mitk::OneTissueCompartmentModelParameterizer::Pointer modelParameterizer = mitk::OneTissueCompartmentModelParameterizer::New(); for(ParameterMapType::const_iterator pos = this->m_ParameterImageMap.begin(); pos != this->m_ParameterImageMap.end(); ++pos) { if(pos->first == mitk::OneTissueCompartmentModel::NAME_PARAMETER_K1) { generator->SetParameterInputImage(mitk::OneTissueCompartmentModel::POSITION_PARAMETER_K1, pos->second); } if(pos->first == mitk::OneTissueCompartmentModel::NAME_PARAMETER_k2) { generator->SetParameterInputImage(mitk::OneTissueCompartmentModel::POSITION_PARAMETER_k2, pos->second); } } modelParameterizer->SetAIF(this->m_AterialInputFunction); modelParameterizer->SetAIFTimeGrid(this->m_TimeGrid); modelParameterizer->SetDefaultTimeGrid(this->m_TimeGrid); generator->SetParameterizer(modelParameterizer); mitk::Image::Pointer generatedImage = generator->GetGeneratedImage(); mitk::Image::Pointer resultImage = mitk::Image::New(); if(m_Controls.NoiseCheckBox->isChecked()) { typedef itk::Image ImageType; mitk::Image::Pointer tempImage = mitk::Image::New(); tempImage->Initialize(generatedImage); mitk::ArbitraryTimeGeometry* timeGeometry = dynamic_cast (generatedImage->GetTimeGeometry()); tempImage->SetTimeGeometry(timeGeometry); ImageType::Pointer itkImage = ImageType::New(); mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(generatedImage); for(unsigned int i = 0; i< generatedImage->GetTimeSteps(); ++i) { imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkInputImage = imageTimeSelector->GetOutput(); mitk::CastToItkImage(mitkInputImage, itkImage ); typedef mitk::GaussianNoiseFunctor NoiseFunctorType; typedef itk::UnaryFunctorImageFilter NoiseFilterType; NoiseFilterType::Pointer noiseFilter = NoiseFilterType::New(); NoiseFunctorType noiseFunctor; noiseFunctor.SetMean(0.0); noiseFunctor.SetSigma(this->m_Sigma); noiseFilter->SetFunctor(noiseFunctor); noiseFilter->SetInput(itkImage); mitk::Image::Pointer outputImage = mitk::ImportItkImage(noiseFilter->GetOutput())->Clone(); mitk::ImageReadAccessor accessor(outputImage); tempImage->SetVolume(accessor.GetData(), i); } resultImage = tempImage->Clone(); } else { resultImage = generatedImage; } return resultImage; } PerfusionDataSimulationView::PerfusionDataSimulationView() { m_Sigma = 0; m_CNR=0; m_MaxConcentration=0; m_selectedModelFactory = nullptr; mitk::ModelFactoryBase::Pointer factory = mitk::ExtendedToftsModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::TwoCompartmentExchangeModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); - factory = mitk::NumericTwoCompartmentExchangeModelFactory::New().GetPointer(); - m_FactoryStack.push_back(factory); factory = mitk::TwoTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); factory = mitk::OneTissueCompartmentModelFactory::New().GetPointer(); m_FactoryStack.push_back(factory); m_IsNotABinaryImagePredicate = mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true))), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))).GetPointer(); } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.h b/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.h index 7956d413f1..27cf365e0a 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.h +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.simulation/src/internal/PerfusionDataSimulationView.h @@ -1,114 +1,112 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef PerfusionDataSimulationView_h #define PerfusionDataSimulationView_h #include #include #include "ui_PerfusionDataSimulationViewControls.h" #include "mitkModelBase.h" #include #include "itkArray.h" #include "mitkImage.h" #include /*! * @brief Test Plugin for SUV calculations of PET images */ class PerfusionDataSimulationView : public QmitkAbstractView { Q_OBJECT public: /*! @brief The view's unique ID - required by MITK */ static const std::string VIEW_ID; PerfusionDataSimulationView(); protected slots: /*! * @brief Is triggered of the update button is clicked and the selected node should get the (new) iso level set. */ void OnGenerateDataButtonClicked(); void LoadAIFFile(); void OnModellSet(int); void OnSimulationConfigurationChanged(); void UpdateDataSelection(); protected: typedef std::map ParameterMapType; // Overridden base class functions /*! * @brief Sets up the UI controls and connects the slots and signals. Gets * called by the framework to create the GUI at the right time. * @param[in,out] parent The parent QWidget, as this class itself is not a QWidget * subclass. */ void CreateQtPartControl(QWidget* parent) override; /*! * @brief Sets the focus to the plot curve button. Gets called by the framework to set the * focus on the right widget. */ void SetFocus() override; void InitModelComboBox() const; mitk::Image::Pointer Generate2CXModelData(); - mitk::Image::Pointer GenerateNumeric2CXModelData(); mitk::Image::Pointer GenerateETModelData(); void FillParameterMap2CXM(); - void FillParameterMapNumeric2CXM(); void FillParameterMapETM(); ///////////////////// dynamic PET Models/////////////// mitk::Image::Pointer Generate2TCModelData(); mitk::Image::Pointer Generate1TCModelData(); void FillParameterMap2TCM(); void FillParameterMap1TCM(); bool CheckModelSettings(); // Variables /*! @brief The view's UI controls */ Ui::PerfusionDataSimulationViewControls m_Controls; mitk::DataNode::Pointer m_selectedNode; ParameterMapType m_ParameterImageMap; itk::Array m_AterialInputFunction; itk::Array m_TimeGrid; mitk::ModelFactoryBase::Pointer m_selectedModelFactory; private: typedef std::vector ModelFactoryStackType; ModelFactoryStackType m_FactoryStack; mitk::NodePredicateBase::Pointer m_IsNotABinaryImagePredicate; double m_CNR, m_MaxConcentration, m_Sigma; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox index 734643d7b6..76be4436c8 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,440 +1,467 @@ /** \page org_mitk_views_segmentation The Segmentation View \imageMacro{segmentation-dox.svg,"Icon of the Segmentation View",2.00} \tableofcontents \section org_mitk_views_segmentationoverview Overview Segmentation is the act of separating an image into foreground and background subsets by manual or automated delineation, while the foreground is defined to be part of the segmentation. Such a segmented image subset is also called a label as it typically labels a specific region of interest. A multilabel segmentation may contain multiple labels organized in distinct groups. You can create multiple labels for different regions of interest contained within a single segmentation image. Labels in the same group cannot overlap each other but labels from different groups may overlap. The MITK Segmentation Plugin allows you to create multilabel segmentations of anatomical and pathological structures in medical images. The plugin consists of three views:
  • Segmentation View: Manual and (semi-)automatic segmentation
  • \subpage org_mitk_views_segmentationutilities : Post-processing of segmentations
  • \subpage org_mitk_views_segmentationtasklist : Optimized workflow for batches of segmentation tasks based on a user-defined task list
In this user guide, the features of the Segmentation View are described. For an introduction to the Segmentation Utilities or Segmentation Task List, refer to the respective user guides. \imageMacro{QmitkSegmentationPlugin_Overview.png,"Segmentation View", 16.00} \section org_mitk_views_segmentationtechnicalissues Image and segmentation prerequisites The Segmentation View has a few prerequisites regarding segmentations and their reference image:
  • Images must be two or three-dimensional and may be either static or dynamic, e.g., are time-resolved resp. have different pixel values for different time steps.
  • Images must be single-valued, i.e. CT, MRI or ultrasound. Images from color doppler or photographic (RGB) images are only partially supported (please be aware that some tools might not be compatible with this image type).
  • Segmentations must be congruent to their reference images.
\section org_mitk_views_segmentationdataselection Image selection and creating new segmentations To select a reference image for a new segmentation, click on the Image widget in the Data selection section at the very top of the Segmentation View. Choose an image from the displayed list of Data Manager images. Once an image is selected, a new segmentation for this reference image can be created by clicking the button right next to the Segmentation widget in the Data selection section. A new multilabel segmentation with an initial, empty label is automatically generated if not set otherwise in the preferences. The new segmentation will be added to the Data Manager as a child node of its reference image node. It is automatically selected and can be edited in the Segmentation View right away. Instead of creating a new segmentation, an existing segmentation can be selected and edited as well. The selection list of existing segmentations for a certain reference image consists of matching/congruent segmentations only. \imageMacro{"QmitkSegmentation_DataSelection.png","Data selection and creating new segmentations",12} \section org_mitk_views_segmentationgroups Groups Segmentation images consist of at least a single group called "Group 0" in which the first default label is created. More groups can be added and removed but there will always be at least a single group. Labels of the same group cannot overlap each other. Labels of different groups may overlap each other. For example, you could segment the whole heart as "Heart" label in "Group 0", add "Group 1" and create multiple labels of the anatomical details of the heart in that group. Naturally, all these labels lie within the extents of the "Heart" label of "Group 0" but in principle they are completely independent of "Group 0". Some pixels are now labelled twice, e.g., as "Heart" and "Left ventricle". Since the labels of "Group 1" cannot overlap each other, it is impossible to accidentally label a pixel as both "Left ventricle" and "Right ventricle". If you would like to segment even more details you could create "Group 2" to have up to three labels per pixel. Nevertheless, groups are technically a flat data structure and cannot contain nested groups. It is all about possibly overlapping labels from distinct groups and spatially exclusive, non-overlapping labels within the same group. \imageMacro{"QmitkSegmentation_Groups.png","Groups",10} \section org_mitk_views_segmentationlabelinstances Labels vs. label instances The Segmentation View supports label instances. That is, segmenting multiple distributed entities of the same thing like metastases for example. A label, as we used the term before, is already a single instance of itself but it may consist of multiple label instances. If a label consists of multiple label instances, they each show their own distinct pixel value in square brackets as a clue for distinction and identification. It is important to understand that this number is not a separate, consequtive index for each label. It is just the plain pixel value of the label instance, which is unique across all label instances of the whole segmentation. \imageMacro{"QmitkSegmentation_LabelInstances.png","Label instances",10} \section org_mitk_views_segmentationlock_color_visibility Unlocking, changing color of, and hiding label instances Label instances are locked by default: label instances from the same group cannot accidentally override pixels from other label instances. Locked label instances behave like cookie cutters for other label instances of the same group. You can unlock label instances to remove that protection from other label instances of the same group. Their pixel contents can then be overridden by other label instances of the same group. Remember that label instances from distinct groups do not interact with each other. They can always overlap (not override) each other. You can also change the color of label instances as well as show (default) or hide their pixel contents. The icons at the right side of the rows of the groups and labels widget reflect their state in all these regards. Renaming of labels and label instances can be found in their content menu as shown further below. \imageMacro{"QmitkSegmentation_LockColorVisibility.png","Unlocking\, changing color of\, and hiding label instances",10} \section org_mitk_views_segmentationcontextmenus Context menus Actions for organization of groups, labels, and label instances (as well as other operations) can be also found in their right-click context menus. \imageMacro{"QmitkSegmentation_ContextMenus.png","Context menus of groups\, labels\, and label instances",12} Most actions available in these context menus are self-explanatory or were already described above by other means of access like the tool button bar for adding and removing groups, labels, and label instances. Labels and label instances can be renamed, while groups have fixed names. Note that renaming a label instance will make a separate label out of it, since all instances of the same label share a single common name. Clear content only clears the pixels of a label instance but won't delete the actual label instance. Groups can be locked and unlocked as a whole from their context menu, while label instances can be directly locked and unlocked outside the context menu as decribed further below. \section org_mitk_views_segmentationlabelsuggestions Label name and color suggestions When renaming label instances or creating new label instances with enforced manual naming in the Segmentation preferences, entering names is supported by auto-completion for common label names. The list of predefined label names and colors for the auto-completion feature can be either extended or replaced by a custom list of label name and color suggestions. This custom list must be specified as a JSON file, just containing an array of objects, each with a mandatory "name" string and an optional "color" string. The JSON file can be set in the Segmentation preferences as well as a few options on how to apply these suggestions. \section org_mitk_views_segmentationlabelpresets Saving and loading label set presets Label set presets are useful to share a certain style or scheme between different segmentation sessions or to provide templates for new segmentation sessions. The properties of all label instances in all groups like their names, colors, and visibilities are saved as a label set preset by clicking on the 'Save label set preset' button. Label set presets are applied to any segmentation session by clicking on the 'Load label set preset' button. If a label instance for a certain value already exists, its properties are overridden by the preset. If a label instance for a certain value does not yet exist, an empty label instance with the label properties of the preset is created. The actual pixel contents of label instances are unaffected as label set presets only store label properties. \imageMacro{QmitkSegmentation_Preset.png,"Saving and loading label set presets", 10.00} If you work on a repetitive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "Label 1" label instance. If you work on a repetitive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "Label 1" label instance. \section org_mitk_views_segmentationpreferences Preferences The Segmentation Plugin offers a number of preferences which can be set via the MITK Workbench application preferences (Ctrl+P): \imageMacro{QmitkSegmentationPreferences.png,"Segmentation preferences", 10.00}
  • Compact view: Hide the tool button texts to save some space on screen (6 instead of 4 buttons per row)
  • 2D display: Draw segmentations as as outlines or transparent overlays
  • Data node selection mode: Hide everything but the selected segmentation and its reference image
  • Default label set preset: Start a new segmentation with this preset instead of a default label
  • Label creation: Assign default names and colors to new label instances or ask users for name and color
  • Label suggestions: Specify custom suggestions for label names and colors
\section org_mitk_views_segmentationtooloverview Segmentation tool overview MITK offers a comprehensive set of slice-based 2D and (semi-)automated 3D segmentation tools. The manual 2D tools require some user interaction and can only be applied to a single image slice whereas the 3D tools operate on the whole image. The 3D tools usually only require a small amount of user interaction, i.e. placing seed points or setting / adjusting parameters. You can switch between the different toolsets by selecting the 2D or 3D tab in the segmentation view. \imageMacro{QmitkSegmentation_ToolOverview.png,"An overview of the existing 2D and 3D tools in MITK.",5.50} \section org_mitk_views_segmentation2dsegmentation 2D segmentation tools With 2D manual contouring you define which voxels are part of the segmentation and which are not. This allows you to create segmentations of any structures of interest in an image. You can also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is mitigated by the interpolation feature, which will make suggestions for a segmentation. To start using one of the editing tools, click its button from the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one.\n If you have to delineate a lot of images, shortcuts to switch between tools becomes convenient. For that, just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). All of the editing tools work by the same principle: using the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), moving the mouse while holding the mouse button and releasing the button to finish the editing action. Multi-step undo and redo is fully supported by all editing tools by using the application-wide undo / redo buttons in the toolbar. Remark: Clicking and moving the mouse in any of the 2D render windows will move the crosshair that defines what part of the image is displayed. This behavior is disabled as long as any of the manual segmentation tools are active - otherwise you might have a hard time concentrating on the contour you are drawing. \subsection org_mitk_views_segmentationaddsubtracttools Add and subtract tools \imageMacro{QmitkSegmentation_IMGIconAddSubtract.png,"Add and subtract tools",7.70} Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed (Subtract tool) from the current segmentation. Adding and subtracting voxels can be iteratively repeated for the same segmentation. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of adding voxels, they will be subtracted). \subsection org_mitk_views_segmentationlassotool Lasso tool \imageMacro{QmitkSegmentation_Lasso.png,"Lasso tool",7.70} The tool is a more advanced version of the add/subtract tool. It offers you the following features:
  1. Generating a polygon segmentation (click left mouse button to set ancor point)
  2. Freehand contouring (like the add tool; press left mouse button while moving the mouse)
  3. Move ancor points (select an ancor point, press left mouse button while moving the mouse)
  4. Add new ancor points (press CTRL while click left mouse to add an ancor to the contour)
  5. Delete an ancor point (press Del while ancor point is selected)
  6. Segmentation can be added to the label (Add mode) or subtracted (Subtract mode)
To start a segmentation double left click where the first ancor point should be. To end the segmentation double left click where the last ancor point should be. Please note that:
  • feature 3-6 are only available, if auto confirm is *not* activated
  • feature 3-5 is not available for freehand contour segments
\subsection org_mitk_views_segmentationpaintwipetools Paint and wipe tools \imageMacro{QmitkSegmentation_IMGIconPaintWipe.png,"Paint and wipe tools",7.68} Use the Size slider to change the radius of the round paintbrush tool. Move the mouse in any 2D window and press the left button to draw or erase pixels. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of painting voxels, they will be wiped). \subsection org_mitk_views_segmentationregiongrowingtool Region growing tool \imageMacro{QmitkSegmentation_IMGIconRegionGrowing.png,"Region growing tool",3.81} Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This allows to quickly create segmentations of structures that have a good contrast to surrounding tissue. The tool operates based on the current level window, so changing the level window to optimize the contrast for the ROI is encouraged. Moving the mouse up / down is different from left / right: Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. Moving the mouse left and right will shift the range. The tool will select more or less pixels, corresponding to the changing gray value range. \if THISISNOTIMPLEMENTEDATTHEMOMENT A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \imageMacro{QmitkSegmentation_Leakage.png,"Leakage correction feature of the region growing tool",11.28} \endif \subsection org_mitk_views_segmentationfilltool Fill tool \imageMacro{QmitkSegmentation_IMGIconFill.png,"Fill tool",3.81} Left-click inside a region/segmentation to flood fill all connected pixels that have the same label with the active label. This tool will only work on regions of unlocked labels or on regions that are not labeled at all. \subsection org_mitk_views_segmentationerasetool Erase tool \imageMacro{QmitkSegmentation_IMGIconErase.png,"Erase tool",3.79} This tool removes a connected part of pixels that form a segmentation. You may use it to remove single segmented regions (left-click on specific segmentation) or to clear a whole slice at once (left-click at the non-labeled background). This tool will only work and regions of unlocked labels or on regions of the active label. \subsection org_mitk_views_segmentationclosetool Close tool \imageMacro{QmitkSegmentation_IMGIconClose.png,"Close tool",3.79} Left-click inside the region/segmentation to fill all "holes" (pixels labelled with another label or no label) inside the region. Therefore this tool behaves like a local closing operation. This tool will not work, when a non-labeled region is selected and holes of locked labels will not be filled. \remark This tool always uses the label of the selected region (even if this label is not the active label). Therefore you can use this tool on regions of the active label and of none locked labels (without the need to change the active label). \subsection org_mitk_views_segmentationlivewiretool Live wire tool \imageMacro{QmitkSegmentation_IMGIconLiveWire.png,"Live wire tool",3.01} The Live Wire Tool acts as a magnetic lasso with a contour snapping to edges of objects. \imageMacro{QmitkSegmentation_IMGLiveWireUsage.PNG,"Steps for using the Live Wire Tool",16.00} The tool handling is the same like the Lasso tool (see for more info), except it generates live wire contours instead of straight lines. +\subsection org_mitk_views_segmentationSegmentAnything Segment Anything Tool + +\imageMacro{QmitkSegmentation_nnUnetTool.png,"Segment Anything tool",10.00} + +\imageMacro{QmitkSegmentation_SAMTool.png,"Segment Anything tool",10.00} + +The Segment Anything Tool is a deep learning-based interactive segmentation tool. Originally created by MetaAI, MITK presents this model for medical image segmentation tasks. +The tool requires that you have Python 3 installed and available on your machine. Note: On Debian/Ubuntu systems, you need to install git, python3-pip, python3-venv package using the following command: `apt install git python3-pip python3-venv`. For best experience, your machine should be ideally equipped with a CUDA-enabled GPU. +For a detailed explanation of what this algorithm is able to, please refer to https://ai.facebook.com/research/publications/segment-anything/
+Any adjustments to the \subpage org_mitk_editors_stdmultiwidget_Levelwindow setting impacts the segmentation. However, any applied color maps are ignored. + +\subsubsection org_mitk_views_segmentationSegmentAnythingWorkflow Workflow: + -# Install Segment Anything: Goto Preferences (Cntl+P) > Segment Anything and click "Install Segment Anything" to install Segment Anything (version: 1.0). + The installation process implicitly creates a python virtual environment using the Python located in "System Python" in an MITK mainitained directory. Make sure you have a working internet connection. This might take a while. It is a one time job, though. + Once installed, the "Install Segment Anything" button is grayed out. + \imageMacro{QmitkSegmentation_SAMTool_Preferences.png,"Segment Anything Preferences",10.00} + + -# Note: If Python is not listed by MITK in "System Python", click "Select..." in the dropdown to choose an unlisted installation of Python. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "/usr/bin/". + No need to navigate all the way into "../usr/bin/python3", for example. + -# Select a specific model type in the "Model Type" dropdown. The default is "vit_b" for low memory footprint. However, more larger models "vit_l" and "vit_h" are also available for selection. + -# Select inference hardware, i.e. any GPU or CPU. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. + -# Click "OK" to save the preference settings. + -# Goto Segmentation View > 2D tools > Segment Anything. + -# Click "Initialize Segment Anything" to start the tool backend. This will invoke downloading of the selected model type from the internet. This might take a while. It is a one time job, though. + -# Once the tool is initialized, Press SHIFT+Left Click on any of the 3 render windows to start click guided segmentation on that slice. + -# Press SHIFT+Right Click for negative clicks to adjust the preview mask on the render window. + \subsection org_mitk_views_segmentationinterpolation 2D and 3D Interpolation Creating segmentations using 2D manual contouring for large image volumes may be very time-consuming, because structures of interest may cover a large range of slices. The segmentation view offers two helpful features to mitigate this drawback:
  • 2D Interpolation
  • 3D Interpolation
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation, i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
\imageMacro{QmitkSegmentation_2DInterpolation.png,"2D interpolation usage",3.01} Interpolated suggestions are displayed as outlines, until you confirm them as part of the segmentation. To confirm single slices, click the Confirm for single slice button below the toolbox. You may also review the interpolations visually and then accept all of them at once by selecting Confirm for all slices. The 3D interpolation creates suggestions for 3D segmentations. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation, but rather segmentations in i.e. the axial, coronal and sagittal plane. \imageMacro{QmitkSegmentation_3DInterpolationWrongRight.png,"3D interpolation usage",16.00} You can accept the interpolation result by clicking the Confirm-button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be post-processed without any interpolation running in the background. Additional to the surface, black contours are shown in the 3D render window, which mark all the drawn contours used for the interpolation. You can navigate between the drawn contours by clicking on the corresponding position nodes in the data manager which are stored as sub-nodes of the selected segmentation. If you do not want to see these nodes just uncheck the Show Position Nodes checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since undo / redo is not yet working for 3D interpolation. The current state of the 3D interpolation can be saved across application restart. For that, just click on save project during the interpolation is active. After restarting the application and load your project you can click on "Reinit Interpolation" within the 3D interpolation GUI area. \section org_mitk_views_segmentation3dsegmentation 3D segmentation tools The 3D tools operate on the whole image and require usually a small amount of interaction like placing seed-points or specifying certain parameters. All 3D tools provide an immediate segmentation feedback, which is displayed as a transparent green overlay. For accepting a preview you have to press the Confirm button of the selected tool. The following 3D tools are available: \subsection org_mitk_views_segmentation3dthresholdtool 3D Threshold tool The thresholding tool simply applies a 3D threshold to the patient image. All pixels with values equal or above the selected threshold are labeled as part of the segmentation. You can change the threshold by either moving the slider of setting a certain value in the spinbox. \imageMacro{QmitkSegmentation_3DThresholdTool.png,"3D Threshold tool",10.00} \subsection org_mitk_views_segmentation3dulthresholdTool 3D upper / lower threshold tool The Upper/Lower Thresholding tool works similar to the simple 3D threshold tool but allows you to define an upper and lower threshold. All pixels with values within this threshold interval will be labeled as part of the segmentation. \imageMacro{QmitkSegmentation_3DULThresholdTool.png,"3D upper / lower threshold tool",10.00} \subsection org_mitk_views_segmentation3dotsutool 3D Otsu tool The 3D Otsu tool provides a more sophisticated thresholding algorithm. It allows you to define a number of regions. Based on the image histogram the pixels will then be divided into different regions. The more regions you define the longer the calculation will take. You can select afterwards which of these regions you want to confirm as segmentation. \imageMacro{QmitkSegmentation_3DOtsuTool.png,"3D Otsu tool",10.00} \subsection org_mitk_views_segmentation3dgrowcuttool 3D GrowCut tool The 3D GrowCut tool uses previously created segmentation labels (e.g. by the "Add"-tool) stored in the segmentation layer 0. The GrowCut tool will use these segmentation labels to create a seedimage that will serve as input to the algorithm. As an advanced setting option, a Distance penalty can be set, which increases the cohesion in the immediate surroundings of the initial labels. Based on the seedimage and the Distance penalty, a growing is started, which includes all areas that are not initially assigned to a specific label. During this process, initially unassigned areas are assigned to the best fitting labels. After the segmentation process, the user can decide which newly generated labels should be confirmed. \imageMacro{QmitkSegmentation_3DGrowCutTool.png,"3D GrowCut tool",16.00} \subsection org_mitk_views_segmentationpickingtool Picking Tool The Picking tool offers two modes that allow you to manipulate "islands" within your segmentation. This is especially useful if e.g. a thresholding provided you with several areas within your image but you are just interested in one special region. - Picking mode: Allows you to select certain "islands". When the pick is confirmed, the complete content of the active label will be removed except the pick. This mode is beneficial if you have a lot segmentation noise and want to pick the relevant parts and dismiss the rest. Hint: You can also pick from other labels, but this will only work if these labels are unlocked. - Relabel mode: Allows you to select certain "islands". When the pick is confirmed, it will be relabeled and added to the active label content. Hint: This mode ignores the locks of other labels, hence you do not need to unlock them explicitly. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} \subsection org_mitk_views_segmentationnnUNetTool nnU-Net Tool (Ubuntu only) \imageMacro{QmitkSegmentation_nnUnetTool.png,"nnUNet tool",10.00} This tool provides a GUI to the deep learning-based segmentation algorithm called the nnU-Net v1. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) for your organ or task concerned, before using the tool. For a detailed explanation of the parameters and pre-trained weights folder structure etc., please refer to https://github.com/MIC-DKFZ/nnUNet.
Remark: The tool assumes that you have a Python3 environment with nnU-Net v1 (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. \subsubsection org_mitk_views_segmentationnnUNetToolWorkflow Workflow: -# Select the "Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting environment for the nnUNet inference or click "Select" in the dropdown to choose an unlisted python environment. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "myenv". No need to select all the way until "../myenv/bin/python", for example. -# Click on the "nnUNet Results Folder" directory icon to navigate to the results folder on your hard disk. This is equivalent to setting the RESULTS_FOLDER environment variable. If your results folder is as per the nnUNet required folder structure, the configuration, trainers, tasks and folds are automatically parsed and correspondingly loaded in the drop-down boxes as shown below. Note that MITK automatically checks for the RESULTS_FOLDER environment variable value and, if found, auto parses that directory when the tool is started. \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} -# Choose your required Task-Configuration-Trainer-Planner-Fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). Note that, even if you uncheck all entries from the "Fold" dropdown (shown: "None"), then too, all folds would be considered for inferencing. -# For ensemble predictions, you will get the option to select parameters irrespective on postprocessing files available in the ensembles folder of RESULTS_FOLDER. Note that, if a postprocessing json file exists for the selected combination then it will used for ensembling, by default. To choose not to, uncheck the "Use PostProcessing JSON" in the "Advanced" section. \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} -# If your task is trained with multi-modal inputs, then "Multi-Modal" checkbox is checked and the no.of modalities are preloaded and shown next to "Required Modalities". Instantly, as much node selectors with corresponding modality names should appear below to select the Data Manager along including a selector with preselected with the reference node. Now, select the image nodes in the node selectors accordingly for accurate inferencing. \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} -# Click on "Preview". -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# Every inferred segmentation is cached to prevent a redundant computation. In case, a user doesn't wish to cache a Preview, uncheck the "Enable Caching" in the "Advanced" section. This will ensure that the current parameters will neither be checked against the existing cache nor a segmentation be loaded from it when Preview is clicked. -# You may always clear all the cached segmentations by clicking "Clear Cache" button. \subsubsection org_mitk_views_segmentationnnUNetToolMisc Miscellaneous: -# In case you want to reload/reparse the folders in the "nnUNet Results Folder", eg. after adding new tasks into it, you may do so without reselecting the folder again by clicking the "Refresh Results Folder" button. -# The "Advanced" > "GPU Id" combobox lists the Nvidia GPUs available by parsing the nvidia-smi utility output. In case your machine has Nvidia CUDA enabled GPUs but the nvidia-smi fails for some reason, the "GPU Id" combobox will show no entries. In such a situation, it's still possible to execute inferencing by manually entering the preferred GPU Id, eg. 0 in the combobox. -# The "Advanced" > "Available Models" lists the available pre-trained tasks for download. Make sure you have internet connection. Then, choose a Task from the dropdown and click the Download button. The pre-trained models for the selected Task will be downloaded and placed to the RESULTS_FOLDER directory automatically. -# In the RESULTS_FOLDER directory, inside the trainer-planner folder of every task, MITK keeps a "mitk_export.json" file for fast loading for multi-modal information. It is recommended not to delete this file(s) for a fast responsive UI. Tip: If multi-modal information shown on MITK is not correct for a given task, you may modify this JSON file and try again. \subsection org_mitk_views_segmentationTotalSegmentator TotalSegmentator Tool \imageMacro{QmitkSegmentation_nnUnetTool.png,"TotalSegmentator tool",10.00} -This tool provides a GUI to the deep learning-based segmentation algorithm called the TotalSegmentator. With this tool, you can get a segmentation mask predicted for 104 classes in CT images, loaded in MITK. +This tool provides a GUI to the deep learning-based segmentation algorithm called the TotalSegmentator (v2). With this tool, you can get a segmentation mask predicted for 117 classes in CT images, loaded in MITK. For a detailed explanation on tasks and supported classes etc., please refer to https://github.com/wasserth/TotalSegmentator
-The tool assumes that you have Python 3 installed and available on your machine. We recommend to install TotalSegmentator via MITK. The "Install TotalSegmentator" action implicitly creates a python virtual environment in an MITK mainitained +The tool assumes that you have Python >= 3.9 installed and available on your machine. We recommend to install TotalSegmentator via MITK. The "Install TotalSegmentator" action implicitly creates a python virtual environment in an MITK mainitained directory. Note: on Debian/Ubuntu systems, you need to install the python3-venv package using the following command: `apt install python3-venv`. For best results, your machine should be ideally equipped with a CUDA-enabled GPU. \imageMacro{QmitkSegmentation_TotalsegmentatorTool.png, "TotalSegmentator Settings",5} \subsubsection org_mitk_views_segmentationTotalSegmentatorWorkflow Workflow: - -# Install TotalSegmentator: Click "Install TotalSegmentator" to install TotalSegmentator (version: 1.5.5) in a virtual environment. Make sure you have a working internet connection. This might take a while. It is a one time job, though. + -# Install TotalSegmentator: Click "Install TotalSegmentator" to install TotalSegmentator (version: 2.0.5) in a virtual environment. Make sure you have a working internet connection. This might take a while. It is a one time job, though. Once installed, the "Install TotalSegmentator" button is grayed out. -# If Python is not found by MITK goto "Install Options" & select the "System Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting Python installation for TotalSegmentator to use or click "Select" in the dropdown to choose an unlisted installation of Python. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "/usr/bin/". No need to navigate all the way into "../usr/bin/python3", for example. -# Select a specific subtask in the "Tasks" drop-downs. The default is "total" for non-specific total segmentation. -# Click on "Run TotalSegmentator" for a preview. -# In the "Advanced" section, you can also activate other options like "Fast" for faster runtime and less memory requirements. Use "Fast" if you only have a CPU for inferencing. -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# In case you want to use your own virtual environment containing TotalSegmentator, goto "Install Options" & check "Use Custom Installation" checkbox. Then, select the environment of your choice by using "Custom Env. Path". \section org_mitk_views_segmentationpostprocessing Additional things you can do with segmentations Segmentations are never an end in themselves. Consequently, the segmentation view adds a couple of "post-processing" actions, accessible through the context-menu of the data manager. \imageMacro{QmitkSegmentation_IMGDataManagerContextMenu.png,"Context menu items for segmentations",10.58}
  • Create polygon %model applies the marching cubes algorithm to the segmentation. This polygon %model can be used for visualization in 3D or other applications such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithm, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_views_segmentationof3dtimages Segmentation of 3D+t images For segmentation of 3D+t images, some tools give you the option to choose between creating dynamic and static masks.
  • Dynamic masks can be created on each time frame individually.
  • Static masks will be defined on one time frame and will be the same for all other time frames.
In general, segmentation is applied on the time frame that is selected when execution is performed. If you alter the time frame, the segmentation preview is adapted. \section org_mitk_views_segmentationtechnicaldetail Technical information for developers For technical specifications see \subpage QmitkSegmentationTechnicalPage and for information on the extensions of the tools system \subpage toolextensions. */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_SAMTool.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_SAMTool.png new file mode 100644 index 0000000000..e0cb76269d Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_SAMTool.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_SAMTool_Preferences.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_SAMTool_Preferences.png new file mode 100644 index 0000000000..06a07774e6 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_SAMTool_Preferences.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/files.cmake b/Plugins/org.mitk.gui.qt.segmentation/files.cmake index c92f6fd02d..38abb65df5 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/files.cmake +++ b/Plugins/org.mitk.gui.qt.segmentation/files.cmake @@ -1,78 +1,81 @@ set(SRC_CPP_FILES QmitkSegmentationPreferencePage.cpp QmitkNewSegmentationDialog.cpp QmitkLabelSetWidget.cpp + QmitkSegmentAnythingPreferencePage.cpp ) set(INTERNAL_CPP_FILES mitkPluginActivator.cpp QmitkSegmentationView.cpp QmitkSegmentationUtilitiesView.cpp QmitkSegmentationTaskListView.cpp QmitkAutocropAction.cpp QmitkAutocropLabelSetImageAction.cpp QmitkCreatePolygonModelAction.cpp QmitkLoadMultiLabelPresetAction.cpp QmitkSaveMultiLabelPresetAction.cpp QmitkConvertSurfaceToLabelAction.cpp QmitkConvertMaskToLabelAction.cpp QmitkConvertToMultiLabelSegmentationAction.cpp QmitkCreateMultiLabelSegmentationAction.cpp Common/QmitkLabelsWidget.cpp Common/QmitkLayersWidget.cpp ) set(UI_FILES src/QmitkSegmentationPreferencePageControls.ui + src/QmitkSegmentAnythingPreferencePage.ui src/QmitkNewSegmentationDialog.ui src/QmitkLabelSetWidgetControls.ui src/internal/QmitkSegmentationViewControls.ui src/internal/QmitkSegmentationUtilitiesViewControls.ui src/internal/QmitkSegmentationTaskListView.ui src/internal/Common/QmitkLabelsWidgetControls.ui src/internal/Common/QmitkLayersWidgetControls.ui ) set(MOC_H_FILES src/QmitkSegmentationPreferencePage.h + src/QmitkSegmentAnythingPreferencePage.h src/QmitkNewSegmentationDialog.h src/QmitkLabelSetWidget.h src/internal/mitkPluginActivator.h src/internal/QmitkSegmentationView.h src/internal/QmitkSegmentationUtilitiesView.h src/internal/QmitkSegmentationTaskListView.h src/internal/QmitkAutocropAction.h src/internal/QmitkAutocropLabelSetImageAction.h src/internal/QmitkCreatePolygonModelAction.h src/internal/QmitkLoadMultiLabelPresetAction.h src/internal/QmitkSaveMultiLabelPresetAction.h src/internal/QmitkConvertSurfaceToLabelAction.h src/internal/QmitkConvertMaskToLabelAction.h src/internal/QmitkConvertToMultiLabelSegmentationAction.h src/internal/QmitkCreateMultiLabelSegmentationAction.h src/internal/Common/QmitkLabelsWidget.h src/internal/Common/QmitkLayersWidget.h ) set(CACHED_RESOURCE_FILES resources/segmentation.svg resources/segmentation_utilities.svg resources/SegmentationTaskListIcon.svg plugin.xml ) set(QRC_FILES resources/segmentation.qrc resources/SegmentationUtilities.qrc resources/SegmentationTaskList.qrc ) set(CPP_FILES) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_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.segmentation/plugin.xml b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml index 70faf41d0e..d585f47707 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml +++ b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml @@ -1,102 +1,105 @@ Allows the segmentation of images using different tools. Edit segmentations using standard operations. Create or edit a batch of segmentations according to a task list in a streamlined workflow. + + + diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.cpp new file mode 100644 index 0000000000..3229b83a62 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.cpp @@ -0,0 +1,368 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "QmitkSegmentAnythingPreferencePage.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +namespace +{ + mitk::IPreferences* GetPreferences() + { + auto* preferencesService = mitk::CoreServices::GetPreferencesService(); + return preferencesService->GetSystemPreferences()->Node("org.mitk.views.segmentation"); + } +} + +QmitkSegmentAnythingPreferencePage::QmitkSegmentAnythingPreferencePage() + : m_Ui(new Ui::QmitkSegmentAnythingPreferencePage), + m_Control(nullptr){} + +QmitkSegmentAnythingPreferencePage::~QmitkSegmentAnythingPreferencePage(){} + +void QmitkSegmentAnythingPreferencePage::Init(berry::IWorkbench::Pointer){} + +void QmitkSegmentAnythingPreferencePage::CreateQtControl(QWidget* parent) +{ + m_Control = new QWidget(parent); + m_Ui->setupUi(m_Control); +#ifndef _WIN32 + m_Ui->sysPythonComboBox->addItem("/usr/bin"); +#endif + this->AutoParsePythonPaths(); + m_Ui->timeoutEdit->setValidator(new QIntValidator(0, 1000, this)); + m_Ui->sysPythonComboBox->addItem("Select..."); + m_Ui->sysPythonComboBox->setCurrentIndex(0); + connect(m_Ui->installSAMButton, SIGNAL(clicked()), this, SLOT(OnInstallBtnClicked())); + connect(m_Ui->clearSAMButton, SIGNAL(clicked()), this, SLOT(OnClearInstall())); + connect(m_Ui->sysPythonComboBox, + QOverload::of(&QComboBox::activated), + [=](int index) { OnSystemPythonChanged(m_Ui->sysPythonComboBox->itemText(index)); }); + QIcon deleteIcon = + QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/edit-delete.svg")); + m_Ui->clearSAMButton->setIcon(deleteIcon); + const QString storageDir = m_Installer.GetVirtualEnvPath(); + bool isInstalled = QmitkSegmentAnythingToolGUI::IsSAMInstalled(storageDir); + QString welcomeText; + if (isInstalled) + { + m_PythonPath = GetExactPythonPath(storageDir); + m_Installer.SetVirtualEnvPath(m_PythonPath); + welcomeText += " Segment Anything tool is already found installed."; + m_Ui->installSAMButton->setEnabled(false); + } + else + { + welcomeText += " Segment Anything tool not installed. Please click on \"Install SAM\" above. \ + The installation will create a new virtual environment using the System Python selected above."; + m_Ui->installSAMButton->setEnabled(true); + } + this->WriteStatusMessage(welcomeText); + m_Ui->samModelTypeComboBox->addItems(VALID_MODELS); + m_Ui->gpuComboBox->addItem(CPU_ID); + this->SetGPUInfo(); + this->Update(); +} + +QWidget* QmitkSegmentAnythingPreferencePage::GetQtControl() const +{ + return m_Control; +} + +bool QmitkSegmentAnythingPreferencePage::PerformOk() +{ + auto* prefs = GetPreferences(); + prefs->Put("sam parent path", m_Installer.STORAGE_DIR.toStdString()); + prefs->Put("sam python path", m_PythonPath.toStdString()); + prefs->Put("sam modeltype", m_Ui->samModelTypeComboBox->currentText().toStdString()); + prefs->PutInt("sam gpuid", FetchSelectedGPUFromUI()); + prefs->PutInt("sam timeout", std::stoi(m_Ui->timeoutEdit->text().toStdString())); + return true; +} + +void QmitkSegmentAnythingPreferencePage::PerformCancel(){} + +void QmitkSegmentAnythingPreferencePage::Update() +{ + auto* prefs = GetPreferences(); + m_Ui->samModelTypeComboBox->setCurrentText(QString::fromStdString(prefs->Get("sam modeltype", "vit_b"))); + m_Ui->timeoutEdit->setText(QString::number(prefs->GetInt("sam timeout", 300))); + int gpuId = prefs->GetInt("sam gpuid", -1); + if (gpuId == -1) + { + m_Ui->gpuComboBox->setCurrentText(CPU_ID); + } + else if (m_GpuLoader.GetGPUCount() == 0) + { + m_Ui->gpuComboBox->setCurrentText(QString::number(gpuId)); + } + else + { + std::vector specs = m_GpuLoader.GetAllGPUSpecs(); + QmitkGPUSpec gpuSpec = specs[gpuId]; + m_Ui->gpuComboBox->setCurrentText(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); + } +} + +QString QmitkSegmentAnythingPreferencePage::OnSystemPythonChanged(const QString &pyEnv) +{ + QString pyPath; + if (pyEnv == QString("Select...")) + { + QString path = QFileDialog::getExistingDirectory(m_Ui->sysPythonComboBox->parentWidget(), "Python Path", "dir"); + if (!path.isEmpty()) + { + this->OnSystemPythonChanged(path); // recall same function for new path validation + bool oldState = m_Ui->sysPythonComboBox->blockSignals(true); // block signal firing while inserting item + m_Ui->sysPythonComboBox->insertItem(0, path); + m_Ui->sysPythonComboBox->setCurrentIndex(0); + m_Ui->sysPythonComboBox->blockSignals(oldState); // unblock signal firing after inserting item. Remove this after Qt6 migration + } + } + else + { + QString uiPyPath = this->GetPythonPathFromUI(pyEnv); + pyPath = this->GetExactPythonPath(uiPyPath); + } + return pyPath; +} + +QString QmitkSegmentAnythingPreferencePage::GetPythonPathFromUI(const QString &pyUI) const +{ + QString fullPath = pyUI; + if (-1 != fullPath.indexOf(")")) + { + fullPath = fullPath.mid(fullPath.indexOf(")") + 2); + } + return fullPath.simplified(); +} + +QString QmitkSegmentAnythingPreferencePage::GetExactPythonPath(const QString &pyEnv) const +{ + QString fullPath = pyEnv; + bool isPythonExists = false; +#ifdef _WIN32 + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); + if (!isPythonExists && + !(fullPath.endsWith("Scripts", Qt::CaseInsensitive) || fullPath.endsWith("Scripts/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("Scripts"); + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python.exe")); + } +#else + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); + if (!isPythonExists && + !(fullPath.endsWith("bin", Qt::CaseInsensitive) || fullPath.endsWith("bin/", Qt::CaseInsensitive))) + { + fullPath += QDir::separator() + QString("bin"); + isPythonExists = QFile::exists(fullPath + QDir::separator() + QString("python3")); + } +#endif + if (!isPythonExists) + { + fullPath.clear(); + } + return fullPath; +} + +void QmitkSegmentAnythingPreferencePage::AutoParsePythonPaths() +{ + QString homeDir = QDir::homePath(); + std::vector searchDirs; +#ifdef _WIN32 + searchDirs.push_back(QString("C:") + QDir::separator() + QString("ProgramData") + QDir::separator() + + QString("anaconda3")); +#else + // Add search locations for possible standard python paths here + searchDirs.push_back(homeDir + QDir::separator() + "anaconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "miniconda3"); + searchDirs.push_back(homeDir + QDir::separator() + "opt" + QDir::separator() + "anaconda3"); +#endif + for (QString searchDir : searchDirs) + { + if (searchDir.endsWith("anaconda3", Qt::CaseInsensitive)) + { + if (QDir(searchDir).exists()) + { + m_Ui->sysPythonComboBox->addItem("(base): " + searchDir); + searchDir.append((QDir::separator() + QString("envs"))); + } + } + for (QDirIterator subIt(searchDir, QDir::AllDirs, QDirIterator::NoIteratorFlags); subIt.hasNext();) + { + subIt.next(); + QString envName = subIt.fileName(); + if (!envName.startsWith('.')) // Filter out irrelevent hidden folders, if any. + { + m_Ui->sysPythonComboBox->addItem("(" + envName + "): " + subIt.filePath()); + } + } + } +} + +void QmitkSegmentAnythingPreferencePage::SetGPUInfo() +{ + std::vector specs = m_GpuLoader.GetAllGPUSpecs(); + for (const QmitkGPUSpec &gpuSpec : specs) + { + m_Ui->gpuComboBox->addItem(QString::number(gpuSpec.id) + ": " + gpuSpec.name + " (" + gpuSpec.memory + ")"); + } + if (specs.empty()) + { + m_Ui->gpuComboBox->setCurrentIndex(m_Ui->gpuComboBox->findText("cpu")); + } + else + { + m_Ui->gpuComboBox->setCurrentIndex(m_Ui->gpuComboBox->count()-1); + } +} + +int QmitkSegmentAnythingPreferencePage::FetchSelectedGPUFromUI() const +{ + QString gpuInfo = m_Ui->gpuComboBox->currentText(); + if ("cpu" == gpuInfo) + { + return -1; + } + else if(m_GpuLoader.GetGPUCount() == 0) + { + return static_cast(gpuInfo.toInt()); + } + else + { + QString gpuId = gpuInfo.split(":", QString::SplitBehavior::SkipEmptyParts).first(); + return static_cast(gpuId.toInt()); + } +} + +void QmitkSegmentAnythingPreferencePage::OnInstallBtnClicked() +{ + QString systemPython = OnSystemPythonChanged(m_Ui->sysPythonComboBox->currentText()); + if (!systemPython.isEmpty()) + { + this->WriteStatusMessage("STATUS: Installing SAM..."); + m_Ui->installSAMButton->setEnabled(false); + m_Installer.SetSystemPythonPath(systemPython); + bool isInstalled = false; + bool isFinished = m_Installer.SetupVirtualEnv(m_Installer.VENV_NAME); + if (isFinished) + { + isInstalled = QmitkSegmentAnythingToolGUI::IsSAMInstalled(m_Installer.GetVirtualEnvPath()); + } + if (isInstalled) + { + m_PythonPath = this->GetExactPythonPath(m_Installer.GetVirtualEnvPath()); + this->WriteStatusMessage("STATUS: Successfully installed SAM."); + } + else + { + this->WriteErrorMessage("ERROR: Couldn't install SAM."); + m_Ui->installSAMButton->setEnabled(true); + } + } +} + +void QmitkSegmentAnythingPreferencePage::OnClearInstall() +{ + QDir folderPath(m_Installer.GetVirtualEnvPath()); + bool isDeleted = folderPath.removeRecursively(); + if (isDeleted) + { + this->WriteStatusMessage("Deleted SAM installation."); + m_Ui->installSAMButton->setEnabled(true); + m_PythonPath.clear(); + } + else + { + MITK_ERROR << "The virtual environment couldn't be removed. Please check if you have the required access " + "privileges or, some other process is accessing the folders."; + } +} + +void QmitkSegmentAnythingPreferencePage::WriteStatusMessage(const QString &message) +{ + m_Ui->samInstallStatusLabel->setText(message); + m_Ui->samInstallStatusLabel->setStyleSheet("font-weight: bold; color: white"); + qApp->processEvents(); +} + +void QmitkSegmentAnythingPreferencePage::WriteErrorMessage(const QString &message) +{ + m_Ui->samInstallStatusLabel->setText(message); + m_Ui->samInstallStatusLabel->setStyleSheet("font-weight: bold; color: red"); + qApp->processEvents(); +} + +QString QmitkSAMInstaller::GetVirtualEnvPath() +{ + return STORAGE_DIR + VENV_NAME; +} + +bool QmitkSAMInstaller::SetupVirtualEnv(const QString &venvName) +{ + if (GetSystemPythonPath().isEmpty()) + { + return false; + } + QDir folderPath(GetBaseDir()); + folderPath.mkdir(venvName); + if (!folderPath.cd(venvName)) + { + return false; // Check if directory creation was successful. + } + mitk::ProcessExecutor::ArgumentListType args; + auto spExec = mitk::ProcessExecutor::New(); + auto spCommand = itk::CStyleCommand::New(); + spCommand->SetCallback(&PrintProcessEvent); + spExec->AddObserver(mitk::ExternalProcessOutputEvent(), spCommand); + + args.push_back("-m"); + args.push_back("venv"); + args.push_back(venvName.toStdString()); +#ifdef _WIN32 + QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python.exe"; + QString pythonExeFolder = "Scripts"; +#else + QString pythonFile = GetSystemPythonPath() + QDir::separator() + "python3"; + QString pythonExeFolder = "bin"; +#endif + spExec->Execute(GetBaseDir().toStdString(), pythonFile.toStdString(), args); // Setup local virtual environment + if (folderPath.cd(pythonExeFolder)) + { + this->SetPythonPath(folderPath.absolutePath()); + this->SetPipPath(folderPath.absolutePath()); + this->InstallPytorch(); + for (auto &package : PACKAGES) + { + this->PipInstall(package.toStdString(), &PrintProcessEvent); + } + std::string pythonCode; // python syntax to check if torch is installed with CUDA. + pythonCode.append("import torch;"); + pythonCode.append("print('Pytorch was installed with CUDA') if torch.cuda.is_available() else print('PyTorch was " + "installed WITHOUT CUDA');"); + this->ExecutePython(pythonCode, &PrintProcessEvent); + return true; + } + return false; +} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.h new file mode 100644 index 0000000000..3a4118eaec --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.h @@ -0,0 +1,114 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef QmitkSegmentAnythingPreferencePage_h +#define QmitkSegmentAnythingPreferencePage_h + +#include +#include +#include +#include +#include +#include + +class QWidget; + +namespace Ui +{ + class QmitkSegmentAnythingPreferencePage; +} + +class QmitkSAMInstaller : public QmitkSetupVirtualEnvUtil +{ +public: + const QString VENV_NAME = ".sam"; + const QString SAM_VERSION = "1.0"; // currently, unused + const std::vector PACKAGES = {QString("git+https://github.com/MIC-DKFZ/agent-sam.git@v0.1")}; + const QString STORAGE_DIR; + inline QmitkSAMInstaller( + const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + + qApp->organizationName() + QDir::separator()) + : QmitkSetupVirtualEnvUtil(baseDir), STORAGE_DIR(baseDir){}; + bool SetupVirtualEnv(const QString &) override; + QString GetVirtualEnvPath() override; +}; + +class QmitkSegmentAnythingPreferencePage : public QObject, public berry::IQtPreferencePage +{ + Q_OBJECT + Q_INTERFACES(berry::IPreferencePage) + +public: + QmitkSegmentAnythingPreferencePage(); + ~QmitkSegmentAnythingPreferencePage() override; + + void Init(berry::IWorkbench::Pointer workbench) override; + void CreateQtControl(QWidget* parent) override; + QWidget* GetQtControl() const override; + bool PerformOk() override; + void PerformCancel() override; + void Update() override; + +private slots: + void OnInstallBtnClicked(); + void OnClearInstall(); + QString OnSystemPythonChanged(const QString&); + +protected: + /** + * @brief Searches and parses paths of python virtual enviroments + * from predefined lookout locations + */ + void AutoParsePythonPaths(); + + /** + * @brief Get the virtual env path from UI combobox removing any + * extra special characters. + * + * @return QString + */ + QString GetPythonPathFromUI(const QString &) const; + + /** + * @brief Get the Exact Python Path for any OS + * from the virtual environment path. + * @return QString + */ + QString GetExactPythonPath(const QString &) const; + + /** + * @brief Adds GPU information to the gpu combo box. + * In case, there aren't any GPUs avaialble, the combo box will be + * rendered editable. + */ + void SetGPUInfo(); + + /** + * @brief Returns GPU id of the selected GPU from the Combo box. + * @return int + */ + int FetchSelectedGPUFromUI() const; + + void WriteStatusMessage(const QString &); + void WriteErrorMessage(const QString &); + +private: + Ui::QmitkSegmentAnythingPreferencePage* m_Ui; + QmitkSAMInstaller m_Installer; + QWidget* m_Control; + QmitkGPULoader m_GpuLoader; + QString m_PythonPath; + const QString CPU_ID = "cpu"; + const QStringList VALID_MODELS = {"vit_b", "vit_l", "vit_h"}; +}; + +#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.ui b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.ui new file mode 100644 index 0000000000..b68da828a3 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkSegmentAnythingPreferencePage.ui @@ -0,0 +1,153 @@ + + + QmitkSegmentAnythingPreferencePage + + + + 0 + 0 + 656 + 779 + + + + Form + + + + + + + + + + + + 0 + 0 + + + + Install Segment Anything + + + + + + + + 0 + 0 + + + + Clear Install + + + + + + + System Python + + + + + + + + + + Model Type + + + + + + + + + + <html><head/><body><p><span style=" font-style:italic; color:#808080;">Tip: Select vit_b for VRAM &lt; 4GB, vit_l for VRAM &lt; 6GB or vit_h for VRAM &gt; 6GB.</span></p></body></html> + + + Qt::RichText + + + true + + + + + + + + + + + 0 + 0 + + + + Device Id: + + + + + + + 300 + + + + + + + + 0 + 0 + + + + Time Out (s): + + + + + + + Qt::RichText + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + ctkComboBox + QWidget +
ctkComboBox.h
+
+
+ + +
diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 53b3af6b51..72ee007341 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1083 +1,1083 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include namespace { QList Get2DWindows(const QList allWindows) { QList all2DWindows; for (auto* window : allWindows) { if (window->GetRenderer()->GetMapperID() == mitk::BaseRenderer::Standard2D) { all2DWindows.append(window); } } return all2DWindows; } } const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); this->RemoveObserversFromWorkingImage(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &Self::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; this->RemoveObserversFromWorkingImage(); // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); this->AddObserversToWorkingImage(); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } void QmitkSegmentationView::OnLabelAdded(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnLabelRemoved(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnGroupRemoved(mitk::LabelSetImage::GroupIndexType) { this->ValidateSelectionInput(); } mitk::LabelSetImage* QmitkSegmentationView::GetWorkingImage() { if (m_WorkingNode.IsNull()) return nullptr; return dynamic_cast(m_WorkingNode->GetData()); } void QmitkSegmentationView::AddObserversToWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { workingImage->AddLabelAddedListener(mitk::MessageDelegate1(this, &Self::OnLabelAdded)); workingImage->AddLabelRemovedListener(mitk::MessageDelegate1(this, &Self::OnLabelRemoved)); workingImage->AddGroupRemovedListener(mitk::MessageDelegate1(this, &Self::OnGroupRemoved)); } } void QmitkSegmentationView::RemoveObserversFromWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { workingImage->RemoveLabelAddedListener(mitk::MessageDelegate1(this, &Self::OnLabelAdded)); workingImage->RemoveLabelRemovedListener(mitk::MessageDelegate1(this, &Self::OnLabelRemoved)); workingImage->RemoveGroupRemovedListener(mitk::MessageDelegate1(this, &Self::OnGroupRemoved)); } } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) QmitkNewSegmentationDialog::DoRenameLabel(newLabel, nullptr, m_Parent); newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels) { auto segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); const auto groupID = segmentation->GetGroupIndexOfLabel(labelValue); if (groupID != segmentation->GetActiveLayer()) segmentation->SetActiveLayer(groupID); if (labelValue != segmentation->GetActiveLabelSet()->GetActiveLabel()->GetValue()) segmentation->GetActiveLabelSet()->SetActiveLabel(labelValue); segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnGoToLabel(mitk::LabelSetImage::LabelValueType /*label*/, const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelRenameRequested(mitk::Label* label, bool rename) const { auto segmentation = this->GetCurrentSegmentation(); if (rename) { QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::RenameLabel); return; } QmitkNewSegmentationDialog::DoRenameLabel(label, nullptr, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); } mitk::LabelSetImage* QmitkSegmentationView::GetCurrentSegmentation() const { auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; auto segmentation = dynamic_cast(workingNode->GetData()); if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; return segmentation; } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &Self::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &Self::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); - QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire'"); + QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire' 'Segment Anything'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut TotalSegmentator"); #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &Self::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &Self::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &Self::OnShowMarkerNodes); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &Self::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &Self::OnGoToLabel); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &Self::OnLabelRenameRequested); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { if (nullptr == m_RenderWindowPart) { return; } mitk::TimeGeometry* interactionReferenceGeometry = nullptr; auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr != activeTool && m_ReferenceNode.IsNotNull()) { mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNotNull()) { // tool activated, reference image available: set reference geometry interactionReferenceGeometry = m_ReferenceNode->GetData()->GetTimeGeometry(); } } // set the interaction reference geometry for the render window part (might be nullptr) m_RenderWindowPart->SetInteractionReferenceGeometry(interactionReferenceGeometry); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } if (nullptr != m_RenderWindowPart) { auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &Self::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &Self::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } void QmitkSegmentationView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) { if (nullptr == m_RenderWindowPart) { return; } m_Controls->slicesInterpolator->Uninitialize(); auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); } void QmitkSegmentationView::OnPreferencesChanged(const mitk::IPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->multiLabelWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool compactView = prefs->GetBool("compact view", false); int numberOfColumns = compactView ? 6 : 4; m_Controls->toolSelectionBox2D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox2D->SetShowNames(!compactView); m_Controls->toolSelectionBox3D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox3D->SetShowNames(!compactView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { auto referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool hasReferenceNode = referenceNode.IsNotNull(); bool hasWorkingNode = workingNode.IsNotNull(); bool hasBothNodes = hasReferenceNode && hasWorkingNode; QString warning; bool toolSelectionBoxesEnabled = hasReferenceNode && hasWorkingNode; unsigned int numberOfLabels = 0; m_Controls->multiLabelWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "
" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->multiLabelWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); numberOfLabels = labelSetImage->GetTotalNumberOfLabels(); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast(workingNode->GetData())); } else { m_Controls->multiLabelWidget->SetMultiLabelSegmentation(nullptr); } toolSelectionBoxesEnabled &= numberOfLabels > 0; // Here we need to check whether the geometry of the selected segmentation image (working image geometry) // is aligned with the geometry of the 3D render window. // It is not allowed to use a geometry different from the working image geometry for segmenting. // We only need to this if the tool selection box would be enabled without this check. // Additionally this check only has to be performed for render window parts with coupled render windows. // For different render window parts the user is given the option to reinitialize each render window individually // (see QmitkRenderWindow::ShowOverlayMessage). if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "
" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("" + text + ""); m_Controls->selectionWarningLabel->show(); } } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp index 70ab9cfbb3..8bbb2fd9f7 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp @@ -1,80 +1,82 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPluginActivator.h" #include "QmitkSegmentationView.h" #include "QmitkSegmentationPreferencePage.h" +#include "QmitkSegmentAnythingPreferencePage.h" #include "QmitkSegmentationUtilitiesView.h" #include "QmitkSegmentationTaskListView.h" #include "QmitkAutocropAction.h" #include "QmitkAutocropLabelSetImageAction.h" #include "QmitkCreatePolygonModelAction.h" #include "QmitkLoadMultiLabelPresetAction.h" #include "QmitkSaveMultiLabelPresetAction.h" #include "QmitkConvertSurfaceToLabelAction.h" #include "QmitkConvertMaskToLabelAction.h" #include "QmitkConvertToMultiLabelSegmentationAction.h" #include "QmitkCreateMultiLabelSegmentationAction.h" #include US_INITIALIZE_MODULE using namespace mitk; ctkPluginContext* PluginActivator::m_context = nullptr; PluginActivator* PluginActivator::m_Instance = nullptr; PluginActivator::PluginActivator() { m_Instance = this; } PluginActivator::~PluginActivator() { m_Instance = nullptr; } void PluginActivator::start(ctkPluginContext *context) { BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationView, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationPreferencePage, context) + BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentAnythingPreferencePage, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationUtilitiesView, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationTaskListView, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkAutocropAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkAutocropLabelSetImageAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkCreatePolygonModelAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkLoadMultiLabelPresetAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSaveMultiLabelPresetAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkConvertSurfaceToLabelAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkConvertMaskToLabelAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkConvertToMultiLabelSegmentationAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkCreateMultiLabelSegmentationAction, context) this->m_context = context; } void PluginActivator::stop(ctkPluginContext *) { this->m_context = nullptr; } PluginActivator* PluginActivator::getDefault() { return m_Instance; } ctkPluginContext*PluginActivator::getContext() { return m_context; }